1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { Dispatch } from 'redux';
6 import { dialogActions } from '~/store/dialog/dialog-actions';
7 import { RootState } from '~/store/store';
8 import { ResourceKind, extractUuidKind } from '~/models/resource';
9 import { getResource } from '~/store/resources/resources';
10 import { GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
11 import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
12 import { ContainerRequestResource } from '~/models/container-request';
13 import { CollectionResource } from '~/models/collection';
14 import { ProjectResource } from '~/models/project';
15 import { ServiceRepository } from '~/services/services';
16 import { FilterBuilder } from '~/services/api/filter-builder';
17 import { RepositoryResource } from '~/models/repositories';
18 import { SshKeyResource } from '~/models/ssh-key';
19 import { VirtualMachinesResource } from '~/models/virtual-machines';
20 import { UserResource } from '~/models/user';
21 import { ListResults } from '~/services/common-service/common-resource-service';
22 import { LinkResource } from '~/models/link';
23 import { KeepServiceResource } from '~/models/keep-services';
24 import { NodeResource } from '~/models/node';
26 export const ADVANCED_TAB_DIALOG = 'advancedTabDialog';
28 interface AdvancedTabDialogData {
30 metadata: ListResults<LinkResource> | string;
31 user: UserResource | string;
33 pythonExample: string;
35 cliGetExample: string;
36 cliUpdateHeader: string;
37 cliUpdateExample: string;
43 COLLECTION = 'collection',
44 STORAGE_CLASSES_CONFIRMED = 'storage_classes_confirmed'
48 CONTAINER_REQUEST = 'container_request',
49 OUTPUT_NAME = 'output_name'
54 DELETE_AT = 'delete_at'
58 REPOSITORY = 'repository',
59 CREATED_AT = 'created_at'
63 SSH_KEY = 'authorized_key',
64 CREATED_AT = 'created_at'
67 enum VirtualMachineData {
68 VIRTUAL_MACHINE = 'virtual_machine',
69 CREATED_AT = 'created_at'
73 REPOSITORIES = 'repositories',
74 AUTORIZED_KEYS = 'authorized_keys',
75 VIRTUAL_MACHINES = 'virtual_machines',
76 KEEP_SERVICES = 'keep_services',
78 COMPUTE_NODES = 'nodes'
81 enum KeepServiceData {
82 KEEP_SERVICE = 'keep_services',
83 CREATED_AT = 'created_at'
91 enum ComputeNodeData {
92 COMPUTE_NODE = 'node',
93 PROPERTIES = 'properties'
96 type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | ComputeNodeData | UserData;
97 type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix;
98 type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | NodeResource | UserResource | undefined;
100 export const openAdvancedTabDialog = (uuid: string) =>
101 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
102 const kind = extractUuidKind(uuid);
104 case ResourceKind.COLLECTION:
105 const { data: dataCollection, metadata: metaCollection, user: userCollection } = await dispatch<any>(getDataForAdvancedTab(uuid));
106 const advanceDataCollection = advancedTabData({
108 metadata: metaCollection,
109 user: userCollection,
110 apiResponseKind: collectionApiResponse,
111 data: dataCollection,
112 resourceKind: CollectionData.COLLECTION,
113 resourcePrefix: GroupContentsResourcePrefix.COLLECTION,
114 resourceKindProperty: CollectionData.STORAGE_CLASSES_CONFIRMED,
115 property: dataCollection.storageClassesConfirmed
117 dispatch<any>(initAdvancedTabDialog(advanceDataCollection));
119 case ResourceKind.PROCESS:
120 const { data: dataProcess, metadata: metaProcess, user: userProcess } = await dispatch<any>(getDataForAdvancedTab(uuid));
121 const advancedDataProcess = advancedTabData({
123 metadata: metaProcess,
125 apiResponseKind: containerRequestApiResponse,
127 resourceKind: ProcessData.CONTAINER_REQUEST,
128 resourcePrefix: GroupContentsResourcePrefix.PROCESS,
129 resourceKindProperty: ProcessData.OUTPUT_NAME,
130 property: dataProcess.outputName
132 dispatch<any>(initAdvancedTabDialog(advancedDataProcess));
134 case ResourceKind.PROJECT:
135 const { data: dataProject, metadata: metaProject, user: userProject } = await dispatch<any>(getDataForAdvancedTab(uuid));
136 const advanceDataProject = advancedTabData({
138 metadata: metaProject,
140 apiResponseKind: groupRequestApiResponse,
142 resourceKind: ProjectData.GROUP,
143 resourcePrefix: GroupContentsResourcePrefix.PROJECT,
144 resourceKindProperty: ProjectData.DELETE_AT,
145 property: dataProject.deleteAt
147 dispatch<any>(initAdvancedTabDialog(advanceDataProject));
149 case ResourceKind.REPOSITORY:
150 const dataRepository = getState().repositories.items.find(it => it.uuid === uuid);
151 const advanceDataRepository = advancedTabData({
155 apiResponseKind: repositoryApiResponse,
156 data: dataRepository,
157 resourceKind: RepositoryData.REPOSITORY,
158 resourcePrefix: ResourcePrefix.REPOSITORIES,
159 resourceKindProperty: RepositoryData.CREATED_AT,
160 property: dataRepository!.createdAt
162 dispatch<any>(initAdvancedTabDialog(advanceDataRepository));
164 case ResourceKind.SSH_KEY:
165 const dataSshKey = getState().auth.sshKeys.find(it => it.uuid === uuid);
166 const advanceDataSshKey = advancedTabData({
170 apiResponseKind: sshKeyApiResponse,
172 resourceKind: SshKeyData.SSH_KEY,
173 resourcePrefix: ResourcePrefix.AUTORIZED_KEYS,
174 resourceKindProperty: SshKeyData.CREATED_AT,
175 property: dataSshKey!.createdAt
177 dispatch<any>(initAdvancedTabDialog(advanceDataSshKey));
179 case ResourceKind.VIRTUAL_MACHINE:
180 const dataVirtualMachine = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
181 const advanceDataVirtualMachine = advancedTabData({
185 apiResponseKind: virtualMachineApiResponse,
186 data: dataVirtualMachine,
187 resourceKind: VirtualMachineData.VIRTUAL_MACHINE,
188 resourcePrefix: ResourcePrefix.VIRTUAL_MACHINES,
189 resourceKindProperty: VirtualMachineData.CREATED_AT,
190 property: dataVirtualMachine.createdAt
192 dispatch<any>(initAdvancedTabDialog(advanceDataVirtualMachine));
194 case ResourceKind.KEEP_SERVICE:
195 const dataKeepService = getState().keepServices.find(it => it.uuid === uuid);
196 const advanceDataKeepService = advancedTabData({
200 apiResponseKind: keepServiceApiResponse,
201 data: dataKeepService,
202 resourceKind: KeepServiceData.KEEP_SERVICE,
203 resourcePrefix: ResourcePrefix.KEEP_SERVICES,
204 resourceKindProperty: KeepServiceData.CREATED_AT,
205 property: dataKeepService!.createdAt
207 dispatch<any>(initAdvancedTabDialog(advanceDataKeepService));
209 case ResourceKind.USER:
210 const { resources } = getState();
211 const data = getResource<UserResource>(uuid)(resources);
212 const metadata = await services.linkService.list({
213 filters: new FilterBuilder()
214 .addEqual('headUuid', uuid)
217 const advanceDataUser = advancedTabData({
221 apiResponseKind: userApiResponse,
223 resourceKind: UserData.USER,
224 resourcePrefix: ResourcePrefix.USERS,
225 resourceKindProperty: UserData.USERNAME,
226 property: data!.username
228 dispatch<any>(initAdvancedTabDialog(advanceDataUser));
230 case ResourceKind.NODE:
231 const dataComputeNode = getState().computeNodes.find(node => node.uuid === uuid);
232 const advanceDataComputeNode = advancedTabData({
236 apiResponseKind: computeNodeApiResponse,
237 data: dataComputeNode,
238 resourceKind: ComputeNodeData.COMPUTE_NODE,
239 resourcePrefix: ResourcePrefix.COMPUTE_NODES,
240 resourceKindProperty: ComputeNodeData.PROPERTIES,
241 property: dataComputeNode!.properties
243 dispatch<any>(initAdvancedTabDialog(advanceDataComputeNode));
246 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
250 const getDataForAdvancedTab = (uuid: string) =>
251 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
252 const { resources } = getState();
253 const data = getResource<any>(uuid)(resources);
254 const metadata = await services.linkService.list({
255 filters: new FilterBuilder()
256 .addEqual('headUuid', uuid)
259 const user = metadata.itemsAvailable && await services.userService.get(metadata.items[0].tailUuid || '');
260 return { data, metadata, user };
263 const initAdvancedTabDialog = (data: AdvancedTabDialogData) => dialogActions.OPEN_DIALOG({ id: ADVANCED_TAB_DIALOG, data });
265 interface AdvancedTabData {
267 metadata: ListResults<LinkResource> | string;
268 user: UserResource | string;
269 apiResponseKind: (apiResponse: AdvanceResponseData) => string;
270 data: AdvanceResponseData;
271 resourceKind: AdvanceResourceKind;
272 resourcePrefix: AdvanceResourcePrefix;
273 resourceKindProperty: AdvanceResourceKind;
277 const advancedTabData = ({ uuid, user, metadata, apiResponseKind, data, resourceKind, resourcePrefix, resourceKindProperty, property }: AdvancedTabData) => {
282 apiResponse: apiResponseKind(data),
283 pythonHeader: pythonHeader(resourceKind),
284 pythonExample: pythonExample(uuid, resourcePrefix),
285 cliGetHeader: cliGetHeader(resourceKind),
286 cliGetExample: cliGetExample(uuid, resourceKind),
287 cliUpdateHeader: cliUpdateHeader(resourceKind, resourceKindProperty),
288 cliUpdateExample: cliUpdateExample(uuid, resourceKind, property, resourceKindProperty),
289 curlHeader: curlHeader(resourceKind, resourceKindProperty),
290 curlExample: curlExample(uuid, resourcePrefix, property, resourceKind, resourceKindProperty),
294 const pythonHeader = (resourceKind: string) =>
295 `An example python command to get a ${resourceKind} using its uuid:`;
297 const pythonExample = (uuid: string, resourcePrefix: string) => {
298 const pythonExample = `import arvados
300 x = arvados.api().${resourcePrefix}().get(uuid='${uuid}').execute()`;
302 return pythonExample;
305 const cliGetHeader = (resourceKind: string) =>
306 `An example arv command to get a ${resourceKind} using its uuid:`;
308 const cliGetExample = (uuid: string, resourceKind: string) => {
309 const cliGetExample = `arv ${resourceKind} get \\
312 return cliGetExample;
315 const cliUpdateHeader = (resourceKind: string, resourceName: string) =>
316 `An example arv command to update the "${resourceName}" attribute for the current ${resourceKind}:`;
318 const cliUpdateExample = (uuid: string, resourceKind: string, resource: string | string[], resourceName: string) => {
319 const CLIUpdateCollectionExample = `arv ${resourceKind} update \\
321 --${resourceKind} '{"${resourceName}":${JSON.stringify(resource)}}'`;
323 return CLIUpdateCollectionExample;
326 const curlHeader = (resourceKind: string, resource: string) =>
327 `An example curl command to update the "${resource}" attribute for the current ${resourceKind}:`;
329 const curlExample = (uuid: string, resourcePrefix: string, resource: string | string[], resourceKind: string, resourceName: string) => {
330 const curlExample = `curl -X PUT \\
331 -H "Authorization: OAuth2 $ARVADOS_API_TOKEN" \\
332 --data-urlencode ${resourceKind}@/dev/stdin \\
333 https://$ARVADOS_API_HOST/arvados/v1/${resourcePrefix}/${uuid} \\
336 "${resourceName}": ${JSON.stringify(resource, null, 4)}
343 const stringify = (item: string | null | number | boolean) =>
344 JSON.stringify(item) || 'null';
346 const stringifyObject = (item: any) =>
347 JSON.stringify(item, null, 2) || 'null';
349 const containerRequestApiResponse = (apiResponse: ContainerRequestResource) => {
350 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, description, properties, state, requestingContainerUuid, containerUuid,
351 containerCountMax, mounts, runtimeConstraints, containerImage, environment, cwd, command, outputPath, priority, expiresAt, filters, containerCount,
352 useExisting, schedulingParameters, outputUuid, logUuid, outputName, outputTtl } = apiResponse;
353 const response = `"uuid": "${uuid}",
354 "owner_uuid": "${ownerUuid}",
355 "created_at": "${createdAt}",
356 "modified_at": ${stringify(modifiedAt)},
357 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
358 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
359 "name": ${stringify(name)},
360 "description": ${stringify(description)},
361 "properties": ${stringifyObject(properties)},
362 "state": ${stringify(state)},
363 "requesting_container_uuid": ${stringify(requestingContainerUuid)},
364 "container_uuid": ${stringify(containerUuid)},
365 "container_count_max": ${stringify(containerCountMax)},
366 "mounts": ${stringifyObject(mounts)},
367 "runtime_constraints": ${stringifyObject(runtimeConstraints)},
368 "container_image": "${stringify(containerImage)}",
369 "environment": ${stringifyObject(environment)},
370 "cwd": ${stringify(cwd)},
371 "command": ${stringifyObject(command)},
372 "output_path": ${stringify(outputPath)},
373 "priority": ${stringify(priority)},
374 "expires_at": ${stringify(expiresAt)},
375 "filters": ${stringify(filters)},
376 "container_count": ${stringify(containerCount)},
377 "use_existing": ${stringify(useExisting)},
378 "scheduling_parameters": ${stringifyObject(schedulingParameters)},
379 "output_uuid": ${stringify(outputUuid)},
380 "log_uuid": ${stringify(logUuid)},
381 "output_name": ${stringify(outputName)},
382 "output_ttl": ${stringify(outputTtl)}`;
387 const collectionApiResponse = (apiResponse: CollectionResource) => {
388 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, description, properties, portableDataHash, replicationDesired,
389 replicationConfirmedAt, replicationConfirmed, manifestText, deleteAt, trashAt, isTrashed, storageClassesDesired,
390 storageClassesConfirmed, storageClassesConfirmedAt } = apiResponse;
391 const response = `"uuid": "${uuid}",
392 "owner_uuid": "${ownerUuid}",
393 "created_at": "${createdAt}",
394 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
395 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
396 "modified_at": ${stringify(modifiedAt)},
397 "portable_data_hash": ${stringify(portableDataHash)},
398 "replication_desired": ${stringify(replicationDesired)},
399 "replication_confirmed_at": ${stringify(replicationConfirmedAt)},
400 "replication_confirmed": ${stringify(replicationConfirmed)},
401 "manifest_text": ${stringify(manifestText)},
402 "name": ${stringify(name)},
403 "description": ${stringify(description)},
404 "properties": ${stringifyObject(properties)},
405 "delete_at": ${stringify(deleteAt)},
406 "trash_at": ${stringify(trashAt)},
407 "is_trashed": ${stringify(isTrashed)},
408 "storage_classes_desired": ${JSON.stringify(storageClassesDesired, null, 2)},
409 "storage_classes_confirmed": ${JSON.stringify(storageClassesConfirmed, null, 2)},
410 "storage_classes_confirmed_at": ${stringify(storageClassesConfirmedAt)}`;
415 const groupRequestApiResponse = (apiResponse: ProjectResource) => {
416 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, description, groupClass, trashAt, isTrashed, deleteAt, properties } = apiResponse;
417 const response = `"uuid": "${uuid}",
418 "owner_uuid": "${ownerUuid}",
419 "created_at": "${createdAt}",
420 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
421 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
422 "modified_at": ${stringify(modifiedAt)},
423 "name": ${stringify(name)},
424 "description": ${stringify(description)},
425 "group_class": ${stringify(groupClass)},
426 "trash_at": ${stringify(trashAt)},
427 "is_trashed": ${stringify(isTrashed)},
428 "delete_at": ${stringify(deleteAt)},
429 "properties": ${stringifyObject(properties)}`;
434 const repositoryApiResponse = (apiResponse: RepositoryResource) => {
435 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name } = apiResponse;
436 const response = `"uuid": "${uuid}",
437 "owner_uuid": "${ownerUuid}",
438 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
439 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
440 "modified_at": ${stringify(modifiedAt)},
441 "name": ${stringify(name)},
442 "created_at": "${createdAt}"`;
447 const sshKeyApiResponse = (apiResponse: SshKeyResource) => {
448 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, authorizedUserUuid, expiresAt } = apiResponse;
449 const response = `"uuid": "${uuid}",
450 "owner_uuid": "${ownerUuid}",
451 "authorized_user_uuid": "${authorizedUserUuid}",
452 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
453 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
454 "modified_at": ${stringify(modifiedAt)},
455 "name": ${stringify(name)},
456 "created_at": "${createdAt}",
457 "expires_at": "${expiresAt}"`;
461 const virtualMachineApiResponse = (apiResponse: VirtualMachinesResource) => {
462 const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = apiResponse;
463 const response = `"hostname": ${stringify(hostname)},
465 "owner_uuid": "${ownerUuid}",
466 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
467 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
468 "modified_at": ${stringify(modifiedAt)},
469 "modified_at": ${stringify(modifiedAt)},
470 "created_at": "${createdAt}"`;
475 const keepServiceApiResponse = (apiResponse: KeepServiceResource) => {
477 uuid, readOnly, serviceHost, servicePort, serviceSslFlag, serviceType,
478 ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid
480 const response = `"uuid": "${uuid}",
481 "owner_uuid": "${ownerUuid}",
482 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
483 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
484 "modified_at": ${stringify(modifiedAt)},
485 "service_host": "${serviceHost}",
486 "service_port": "${servicePort}",
487 "service_ssl_flag": "${stringify(serviceSslFlag)}",
488 "service_type": "${serviceType}",
489 "created_at": "${createdAt}",
490 "read_only": "${stringify(readOnly)}"`;
495 const userApiResponse = (apiResponse: UserResource) => {
497 uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
498 email, firstName, lastName, identityUrl, isActive, isAdmin, prefs, defaultOwnerUuid, username
500 const response = `"uuid": "${uuid}",
501 "owner_uuid": "${ownerUuid}",
502 "created_at": "${createdAt}",
503 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
504 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
505 "modified_at": ${stringify(modifiedAt)},
507 "first_name": "${firstName}",
508 "last_name": "${stringify(lastName)}",
509 "identity_url": "${identityUrl}",
510 "is_active": "${isActive},
511 "is_admin": "${isAdmin},
512 "prefs": "${stringifyObject(prefs)},
513 "default_owner_uuid": "${defaultOwnerUuid},
514 "username": "${username}"`;
519 const computeNodeApiResponse = (apiResponse: NodeResource) => {
521 uuid, slotNumber, hostname, domain, ipAddress, firstPingAt, lastPingAt, jobUuid,
522 ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
525 const response = `"uuid": "${uuid}",
526 "owner_uuid": "${ownerUuid}",
527 "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
528 "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
529 "modified_at": ${stringify(modifiedAt)},
530 "created_at": "${createdAt}",
531 "slot_number": "${stringify(slotNumber)}",
532 "hostname": "${stringify(hostname)}",
533 "domain": "${stringify(domain)}",
534 "ip_address": "${stringify(ipAddress)}",
535 "first_ping_at": "${stringify(firstPingAt)}",
536 "last_ping_at": "${stringify(lastPingAt)}",
537 "job_uuid": "${stringify(jobUuid)}",
538 "properties": "${JSON.stringify(properties, null, 4)}",
539 "info": "${JSON.stringify(info, null, 4)}"`;