1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React from 'react';
6 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
7 import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
8 import { Resource, ResourceKind, TrashableResource } from 'models/resource';
9 import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon, RemoveIcon, RenameIcon } from 'components/icon/icon';
10 import { formatDate, formatFileSize, formatTime } from 'common/formatters';
11 import { resourceLabel } from 'common/labels';
12 import { connect, DispatchProp } from 'react-redux';
13 import { RootState } from 'store/store';
14 import { getResource } from 'store/resources/resources';
15 import { GroupContentsResource } from 'services/groups-service/groups-service';
16 import { getProcess, Process, getProcessStatus, getProcessStatusColor, getProcessRuntime } from 'store/processes/process';
17 import { ArvadosTheme } from 'common/custom-theme';
18 import { compose, Dispatch } from 'redux';
19 import { WorkflowResource } from 'models/workflow';
20 import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
21 import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
22 import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
23 import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
24 import { toggleIsActive, toggleIsAdmin } from 'store/users/users-actions';
25 import { LinkResource } from 'models/link';
26 import { navigateTo, navigateToGroupDetails } from 'store/navigation/navigation-action';
27 import { withResourceData } from 'views-components/data-explorer/with-resources';
28 import { CollectionResource } from 'models/collection';
29 import { IllegalNamingWarning } from 'components/warning/warning';
30 import { loadResource } from 'store/resources/resources-actions';
31 import { GroupClass } from 'models/group';
32 import { openRemoveGroupMemberDialog, openEditPermissionLevelDialog } from 'store/group-details-panel/group-details-panel-actions';
33 import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
34 import { PermissionLevel } from 'models/permission';
36 const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
38 const navFunc = ("groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo);
39 return <Grid container alignItems="center" wrap="nowrap" spacing={16}>
44 <Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
45 {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION
46 ? <IllegalNamingWarning name={item.name} />
52 <Typography variant="caption">
53 <FavoriteStar resourceUuid={item.uuid} />
54 <PublicFavoriteStar resourceUuid={item.uuid} />
60 export const ResourceName = connect(
61 (state: RootState, props: { uuid: string }) => {
62 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
64 })((resource: GroupContentsResource & DispatchProp<any>) => renderName(resource.dispatch, resource));
66 const renderIcon = (item: GroupContentsResource) => {
68 case ResourceKind.PROJECT:
69 if (item.groupClass === GroupClass.FILTER) {
70 return <FilterGroupIcon />;
72 return <ProjectIcon />;
73 case ResourceKind.COLLECTION:
74 if (item.uuid === item.currentVersionUuid) {
75 return <CollectionIcon />;
77 return <CollectionOldVersionIcon />;
78 case ResourceKind.PROCESS:
79 return <ProcessIcon />;
80 case ResourceKind.WORKFLOW:
81 return <WorkflowIcon />;
83 return <DefaultIcon />;
87 const renderDate = (date?: string) => {
88 return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
91 const renderWorkflowName = (item: WorkflowResource) =>
92 <Grid container alignItems="center" wrap="nowrap" spacing={16}>
97 <Typography color="primary" style={{ width: '100px' }}>
103 export const ResourceWorkflowName = connect(
104 (state: RootState, props: { uuid: string }) => {
105 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
107 })(renderWorkflowName);
109 const getPublicUuid = (uuidPrefix: string) => {
110 return `${uuidPrefix}-tpzed-anonymouspublic`;
113 const resourceShare = (dispatch: Dispatch, uuidPrefix: string, ownerUuid?: string, uuid?: string) => {
114 const isPublic = ownerUuid === getPublicUuid(uuidPrefix);
117 {!isPublic && uuid &&
118 <Tooltip title="Share">
119 <IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
128 export const ResourceShare = connect(
129 (state: RootState, props: { uuid: string }) => {
130 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
131 const uuidPrefix = getUuidPrefix(state);
133 uuid: resource ? resource.uuid : '',
134 ownerUuid: resource ? resource.ownerUuid : '',
137 })((props: { ownerUuid?: string, uuidPrefix: string, uuid?: string } & DispatchProp<any>) =>
138 resourceShare(props.dispatch, props.uuidPrefix, props.ownerUuid, props.uuid));
141 const renderFirstName = (item: { firstName: string }) => {
142 return <Typography noWrap>{item.firstName}</Typography>;
145 export const ResourceFirstName = connect(
146 (state: RootState, props: { uuid: string }) => {
147 const resource = getResource<UserResource>(props.uuid)(state.resources);
148 return resource || { firstName: '' };
151 const renderLastName = (item: { lastName: string }) =>
152 <Typography noWrap>{item.lastName}</Typography>;
154 export const ResourceLastName = connect(
155 (state: RootState, props: { uuid: string }) => {
156 const resource = getResource<UserResource>(props.uuid)(state.resources);
157 return resource || { lastName: '' };
160 const renderFullName = (item: { firstName: string, lastName: string }) =>
161 <Typography noWrap>{(item.firstName + " " + item.lastName).trim()}</Typography>;
163 export const ResourceFullName = connect(
164 (state: RootState, props: { uuid: string }) => {
165 const resource = getResource<UserResource>(props.uuid)(state.resources);
166 return resource || { firstName: '', lastName: '' };
170 const renderUuid = (item: { uuid: string }) =>
171 <Typography noWrap>{item.uuid}</Typography>;
173 export const ResourceUuid = connect(
174 (state: RootState, props: { uuid: string }) => {
175 const resource = getResource<UserResource>(props.uuid)(state.resources);
176 return resource || { uuid: '' };
179 const renderEmail = (item: { email: string }) =>
180 <Typography noWrap>{item.email}</Typography>;
182 export const ResourceEmail = connect(
183 (state: RootState, props: { uuid: string }) => {
184 const resource = getResource<UserResource>(props.uuid)(state.resources);
185 return resource || { email: '' };
188 const renderIsActive = (props: { uuid: string, isActive: boolean, toggleIsActive: (uuid: string) => void }) =>
191 checked={props.isActive}
192 onClick={() => props.toggleIsActive(props.uuid)} />;
194 export const ResourceIsActive = connect(
195 (state: RootState, props: { uuid: string }) => {
196 const resource = getResource<UserResource>(props.uuid)(state.resources);
197 return resource || { isActive: false };
198 }, { toggleIsActive }
201 const renderIsAdmin = (props: { uuid: string, isAdmin: boolean, toggleIsAdmin: (uuid: string) => void }) =>
204 checked={props.isAdmin}
205 onClick={() => props.toggleIsAdmin(props.uuid)} />;
207 export const ResourceIsAdmin = connect(
208 (state: RootState, props: { uuid: string }) => {
209 const resource = getResource<UserResource>(props.uuid)(state.resources);
210 return resource || { isAdmin: false };
214 const renderUsername = (item: { username: string }) =>
215 <Typography noWrap>{item.username}</Typography>;
217 export const ResourceUsername = connect(
218 (state: RootState, props: { uuid: string }) => {
219 const resource = getResource<UserResource>(props.uuid)(state.resources);
220 return resource || { username: '' };
224 const renderCommonData = (data: string) =>
225 <Typography noWrap>{data}</Typography>;
227 const renderCommonDate = (date: string) =>
228 <Typography noWrap>{formatDate(date)}</Typography>;
230 export const CommonUuid = withResourceData('uuid', renderCommonData);
232 // Api Client Authorizations
233 export const TokenApiClientId = withResourceData('apiClientId', renderCommonData);
235 export const TokenApiToken = withResourceData('apiToken', renderCommonData);
237 export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate);
239 export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData);
241 export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate);
243 export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate);
245 export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData);
247 export const TokenScopes = withResourceData('scopes', renderCommonData);
249 export const TokenUserId = withResourceData('userId', renderCommonData);
251 const clusterColors = [
259 export const ResourceCluster = (props: { uuid: string }) => {
260 const CLUSTER_ID_LENGTH = 5;
261 const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
262 const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substr(0, pos) : '';
263 const ci = pos >= CLUSTER_ID_LENGTH ? (((((
264 (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1))
265 + props.uuid.charCodeAt(2))
266 * props.uuid.charCodeAt(3))
267 + props.uuid.charCodeAt(4))) % clusterColors.length) : 0;
268 return <span style={{
269 backgroundColor: clusterColors[ci][0],
270 color: clusterColors[ci][1],
273 }}>{clusterId}</span>;
277 const renderLinkName = (item: { name: string }) =>
278 <Typography noWrap>{item.name || '(none)'}</Typography>;
280 export const ResourceLinkName = connect(
281 (state: RootState, props: { uuid: string }) => {
282 const resource = getResource<LinkResource>(props.uuid)(state.resources);
283 return resource || { name: '' };
286 const renderLinkClass = (item: { linkClass: string }) =>
287 <Typography noWrap>{item.linkClass}</Typography>;
289 export const ResourceLinkClass = connect(
290 (state: RootState, props: { uuid: string }) => {
291 const resource = getResource<LinkResource>(props.uuid)(state.resources);
292 return resource || { linkClass: '' };
295 const getResourceDisplayName = (resource: Resource): string => {
296 if ((resource as UserResource).kind === ResourceKind.USER
297 && typeof (resource as UserResource).firstName !== 'undefined') {
298 // We can be sure the resource is UserResource
299 return getUserDisplayName(resource as UserResource);
301 return (resource as GroupContentsResource).name;
305 const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
306 var displayName = getResourceDisplayName(item);
308 return <Typography noWrap color="primary" style={{ 'cursor': 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
309 {resourceLabel(item.kind)}: {displayName || item.uuid}
313 const renderResource = (dispatch: Dispatch, item: Resource) => {
314 var displayName = getResourceDisplayName(item);
316 return <Typography variant='body2'>
317 {resourceLabel(item.kind)}: {displayName || item.uuid}
321 export const ResourceLinkTail = connect(
322 (state: RootState, props: { uuid: string }) => {
323 const resource = getResource<LinkResource>(props.uuid)(state.resources);
324 const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
327 item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE }
329 })((props: { item: Resource } & DispatchProp<any>) =>
330 renderResourceLink(props.dispatch, props.item));
332 export const ResourceLinkHead = connect(
333 (state: RootState, props: { uuid: string }) => {
334 const resource = getResource<LinkResource>(props.uuid)(state.resources);
335 const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
338 item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE }
340 })((props: { item: Resource } & DispatchProp<any>) =>
341 renderResourceLink(props.dispatch, props.item));
343 export const ResourceLinkUuid = connect(
344 (state: RootState, props: { uuid: string }) => {
345 const resource = getResource<LinkResource>(props.uuid)(state.resources);
346 return resource || { uuid: '' };
349 export const ResourceLinkHeadUuid = connect(
350 (state: RootState, props: { uuid: string }) => {
351 const link = getResource<LinkResource>(props.uuid)(state.resources);
352 const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
354 return headResource || { uuid: '' };
357 export const ResourceLinkTailUuid = connect(
358 (state: RootState, props: { uuid: string }) => {
359 const link = getResource<LinkResource>(props.uuid)(state.resources);
360 const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
362 return tailResource || { uuid: '' };
365 const renderLinkDelete = (dispatch: Dispatch, item: LinkResource) => {
367 return <Typography noWrap>
368 <IconButton onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
373 return <Typography noWrap></Typography>;
377 export const ResourceLinkDelete = connect(
378 (state: RootState, props: { uuid: string }) => {
379 const link = getResource<LinkResource>(props.uuid)(state.resources);
381 item: link || { uuid: '', kind: ResourceKind.NONE }
383 })((props: { item: LinkResource } & DispatchProp<any>) =>
384 renderLinkDelete(props.dispatch, props.item));
386 export const ResourceLinkTailEmail = connect(
387 (state: RootState, props: { uuid: string }) => {
388 const link = getResource<LinkResource>(props.uuid)(state.resources);
389 const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
391 return resource || { email: '' };
394 export const ResourceLinkTailUsername = connect(
395 (state: RootState, props: { uuid: string }) => {
396 const link = getResource<LinkResource>(props.uuid)(state.resources);
397 const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
399 return resource || { username: '' };
402 const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, resource: Resource) => {
403 return <Typography noWrap>
404 {formatPermissionLevel(link.name as PermissionLevel)}
405 <IconButton onClick={() => dispatch<any>(openEditPermissionLevelDialog(link.uuid, resource.uuid))}>
411 export const ResourceLinkHeadPermissionLevel = connect(
412 (state: RootState, props: { uuid: string }) => {
413 const link = getResource<LinkResource>(props.uuid)(state.resources);
414 const resource = getResource<Resource>(link?.headUuid || '')(state.resources);
417 link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
418 resource: resource || { uuid: '', kind: ResourceKind.NONE }
420 })((props: { link: LinkResource, resource: Resource } & DispatchProp<any>) =>
421 renderPermissionLevel(props.dispatch, props.link, props.resource));
423 export const ResourceLinkTailPermissionLevel = connect(
424 (state: RootState, props: { uuid: string }) => {
425 const link = getResource<LinkResource>(props.uuid)(state.resources);
426 const resource = getResource<Resource>(link?.tailUuid || '')(state.resources);
429 link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
430 resource: resource || { uuid: '', kind: ResourceKind.NONE }
432 })((props: { link: LinkResource, resource: Resource } & DispatchProp<any>) =>
433 renderPermissionLevel(props.dispatch, props.link, props.resource));
435 // Displays resource type and display name without link
436 export const ResourceLabel = connect(
437 (state: RootState, props: { uuid: string }) => {
438 const resource = getResource<Resource>(props.uuid)(state.resources);
440 item: resource || { uuid: '', kind: ResourceKind.NONE }
442 })((props: { item: Resource } & DispatchProp<any>) =>
443 renderResource(props.dispatch, props.item));
446 const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
450 <Tooltip title="Run process">
451 <IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
459 export const ResourceRunProcess = connect(
460 (state: RootState, props: { uuid: string }) => {
461 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
463 uuid: resource ? resource.uuid : ''
465 })((props: { uuid: string } & DispatchProp<any>) =>
466 resourceRunProcess(props.dispatch, props.uuid));
468 const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
469 if (ownerUuid === getPublicUuid(uuidPrefix)) {
470 return renderStatus(WorkflowStatus.PUBLIC);
472 return renderStatus(WorkflowStatus.PRIVATE);
476 const renderStatus = (status: string) =>
477 <Typography noWrap style={{ width: '60px' }}>{status}</Typography>;
479 export const ResourceWorkflowStatus = connect(
480 (state: RootState, props: { uuid: string }) => {
481 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
482 const uuidPrefix = getUuidPrefix(state);
484 ownerUuid: resource ? resource.ownerUuid : '',
487 })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
489 export const ResourceLastModifiedDate = connect(
490 (state: RootState, props: { uuid: string }) => {
491 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
492 return { date: resource ? resource.modifiedAt : '' };
493 })((props: { date: string }) => renderDate(props.date));
495 export const ResourceCreatedAtDate = connect(
496 (state: RootState, props: { uuid: string }) => {
497 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
498 return { date: resource ? resource.createdAt : '' };
499 })((props: { date: string }) => renderDate(props.date));
501 export const ResourceTrashDate = connect(
502 (state: RootState, props: { uuid: string }) => {
503 const resource = getResource<TrashableResource>(props.uuid)(state.resources);
504 return { date: resource ? resource.trashAt : '' };
505 })((props: { date: string }) => renderDate(props.date));
507 export const ResourceDeleteDate = connect(
508 (state: RootState, props: { uuid: string }) => {
509 const resource = getResource<TrashableResource>(props.uuid)(state.resources);
510 return { date: resource ? resource.deleteAt : '' };
511 })((props: { date: string }) => renderDate(props.date));
513 export const renderFileSize = (fileSize?: number) =>
514 <Typography noWrap style={{ minWidth: '45px' }}>
515 {formatFileSize(fileSize)}
518 export const ResourceFileSize = connect(
519 (state: RootState, props: { uuid: string }) => {
520 const resource = getResource<CollectionResource>(props.uuid)(state.resources);
522 if (resource && resource.kind !== ResourceKind.COLLECTION) {
523 return { fileSize: '' };
526 return { fileSize: resource ? resource.fileSizeTotal : 0 };
527 })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
529 const renderOwner = (owner: string) =>
534 export const ResourceOwner = connect(
535 (state: RootState, props: { uuid: string }) => {
536 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
537 return { owner: resource ? resource.ownerUuid : '' };
538 })((props: { owner: string }) => renderOwner(props.owner));
540 export const ResourceOwnerName = connect(
541 (state: RootState, props: { uuid: string }) => {
542 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
543 const ownerNameState = state.ownerName;
544 const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
545 return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
546 })((props: { owner: string }) => renderOwner(props.owner));
550 (state: RootState, props: { uuid: string }) => {
551 let userFullname = '';
552 const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
555 userFullname = getUserFullname(resource as User) || (resource as GroupContentsResource).name;
558 return { uuid: props.uuid, userFullname };
561 export const ResourceOwnerWithName =
564 withStyles({}, { withTheme: true }))
565 ((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => {
566 const { uuid, userFullname, dispatch, theme } = props;
568 if (userFullname === '') {
569 dispatch<any>(loadResource(uuid, false));
570 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
575 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
576 {userFullname} ({uuid})
580 export const UserNameFromID =
582 (props: { uuid: string, userFullname: string, dispatch: Dispatch }) => {
583 const { uuid, userFullname, dispatch } = props;
585 if (userFullname === '') {
586 dispatch<any>(loadResource(uuid, false));
589 {userFullname ? userFullname : uuid}
593 export const ResponsiblePerson =
596 (state: RootState, props: { uuid: string, parentRef: HTMLElement | null }) => {
597 let responsiblePersonName: string = '';
598 let responsiblePersonUUID: string = '';
599 let responsiblePersonProperty: string = '';
601 if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
603 const keys = Object.keys(state.auth.config.clusterConfig.Collections.ManagedProperties);
605 while (!responsiblePersonProperty && keys[index]) {
606 const key = keys[index];
607 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
608 responsiblePersonProperty = key;
614 let resource: Resource | undefined = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
616 while (resource && resource.kind !== ResourceKind.USER && responsiblePersonProperty) {
617 responsiblePersonUUID = (resource as CollectionResource).properties[responsiblePersonProperty];
618 resource = getResource<GroupContentsResource & UserResource>(responsiblePersonUUID)(state.resources);
621 if (resource && resource.kind === ResourceKind.USER) {
622 responsiblePersonName = getUserFullname(resource as UserResource) || (resource as GroupContentsResource).name;
625 return { uuid: responsiblePersonUUID, responsiblePersonName, parentRef: props.parentRef };
627 withStyles({}, { withTheme: true }))
628 ((props: { uuid: string | null, responsiblePersonName: string, parentRef: HTMLElement | null, theme: ArvadosTheme }) => {
629 const { uuid, responsiblePersonName, parentRef, theme } = props;
631 if (!uuid && parentRef) {
632 parentRef.style.display = 'none';
634 } else if (parentRef) {
635 parentRef.style.display = 'block';
638 if (!responsiblePersonName) {
639 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
644 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
645 {responsiblePersonName} ({uuid})
649 const renderType = (type: string, subtype: string) =>
651 {resourceLabel(type, subtype)}
654 export const ResourceType = connect(
655 (state: RootState, props: { uuid: string }) => {
656 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
657 return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
658 })((props: { type: string, subtype: string }) => renderType(props.type, props.subtype));
660 export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
661 return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
662 })((props: { resource: GroupContentsResource }) =>
663 (props.resource && props.resource.kind === ResourceKind.COLLECTION)
664 ? <CollectionStatus uuid={props.resource.uuid} />
665 : <ProcessStatus uuid={props.resource.uuid} />
668 export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
669 return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
670 })((props: { collection: CollectionResource }) =>
671 (props.collection.uuid !== props.collection.currentVersionUuid)
672 ? <Typography>version {props.collection.version}</Typography>
673 : <Typography>head version</Typography>
676 export const ProcessStatus = compose(
677 connect((state: RootState, props: { uuid: string }) => {
678 return { process: getProcess(props.uuid)(state.resources) };
680 withStyles({}, { withTheme: true }))
681 ((props: { process?: Process, theme: ArvadosTheme }) => {
682 const status = props.process ? getProcessStatus(props.process) : "-";
685 style={{ color: getProcessStatusColor(status, props.theme) }} >
690 export const ProcessStartDate = connect(
691 (state: RootState, props: { uuid: string }) => {
692 const process = getProcess(props.uuid)(state.resources);
693 return { date: (process && process.container) ? process.container.startedAt : '' };
694 })((props: { date: string }) => renderDate(props.date));
696 export const renderRunTime = (time: number) =>
697 <Typography noWrap style={{ minWidth: '45px' }}>
698 {formatTime(time, true)}
701 interface ContainerRunTimeProps {
705 interface ContainerRunTimeState {
709 export const ContainerRunTime = connect((state: RootState, props: { uuid: string }) => {
710 return { process: getProcess(props.uuid)(state.resources) };
711 })(class extends React.Component<ContainerRunTimeProps, ContainerRunTimeState> {
714 constructor(props: ContainerRunTimeProps) {
716 this.state = { runtime: this.getRuntime() };
720 return this.props.process ? getProcessRuntime(this.props.process) : 0;
724 this.setState({ runtime: this.getRuntime() });
727 componentDidMount() {
728 this.timer = setInterval(this.updateRuntime.bind(this), 5000);
731 componentWillUnmount() {
732 clearInterval(this.timer);
736 return renderRunTime(this.state.runtime);