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';
16 CollectionOldVersionIcon,
23 } from 'components/icon/icon';
24 import { formatDate, formatFileSize, formatTime } from 'common/formatters';
25 import { resourceLabel } from 'common/labels';
26 import { connect, DispatchProp } from 'react-redux';
27 import { RootState } from 'store/store';
28 import { getResource, filterResources } from 'store/resources/resources';
29 import { GroupContentsResource } from 'services/groups-service/groups-service';
30 import { getProcess, Process, getProcessStatus, getProcessStatusColor, getProcessRuntime } from 'store/processes/process';
31 import { ArvadosTheme } from 'common/custom-theme';
32 import { compose, Dispatch } from 'redux';
33 import { WorkflowResource } from 'models/workflow';
34 import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
35 import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
36 import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
37 import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
38 import { toggleIsAdmin } from 'store/users/users-actions';
39 import { LinkClass, LinkResource } from 'models/link';
40 import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
41 import { withResourceData } from 'views-components/data-explorer/with-resources';
42 import { CollectionResource } from 'models/collection';
43 import { IllegalNamingWarning } from 'components/warning/warning';
44 import { loadResource } from 'store/resources/resources-actions';
45 import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
46 import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
47 import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
48 import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
49 import { PermissionLevel } from 'models/permission';
50 import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
51 import { getUserUuid } from 'common/getuser';
52 import { VirtualMachinesResource } from 'models/virtual-machines';
54 const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
56 const navFunc = ("groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo);
57 return <Grid container alignItems="center" wrap="nowrap" spacing={16}>
62 <Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
63 {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION
64 ? <IllegalNamingWarning name={item.name} />
70 <Typography variant="caption">
71 <FavoriteStar resourceUuid={item.uuid} />
72 <PublicFavoriteStar resourceUuid={item.uuid} />
78 export const ResourceName = connect(
79 (state: RootState, props: { uuid: string }) => {
80 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
82 })((resource: GroupContentsResource & DispatchProp<any>) => renderName(resource.dispatch, resource));
84 const renderIcon = (item: GroupContentsResource) => {
86 case ResourceKind.PROJECT:
87 if (item.groupClass === GroupClass.FILTER) {
88 return <FilterGroupIcon />;
90 return <ProjectIcon />;
91 case ResourceKind.COLLECTION:
92 if (item.uuid === item.currentVersionUuid) {
93 return <CollectionIcon />;
95 return <CollectionOldVersionIcon />;
96 case ResourceKind.PROCESS:
97 return <ProcessIcon />;
98 case ResourceKind.WORKFLOW:
99 return <WorkflowIcon />;
101 return <DefaultIcon />;
105 const renderDate = (date?: string) => {
106 return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
109 const renderWorkflowName = (item: WorkflowResource) =>
110 <Grid container alignItems="center" wrap="nowrap" spacing={16}>
115 <Typography color="primary" style={{ width: '100px' }}>
121 export const ResourceWorkflowName = connect(
122 (state: RootState, props: { uuid: string }) => {
123 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
125 })(renderWorkflowName);
127 const getPublicUuid = (uuidPrefix: string) => {
128 return `${uuidPrefix}-tpzed-anonymouspublic`;
131 const resourceShare = (dispatch: Dispatch, uuidPrefix: string, ownerUuid?: string, uuid?: string) => {
132 const isPublic = ownerUuid === getPublicUuid(uuidPrefix);
135 {!isPublic && uuid &&
136 <Tooltip title="Share">
137 <IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
146 export const ResourceShare = connect(
147 (state: RootState, props: { uuid: string }) => {
148 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
149 const uuidPrefix = getUuidPrefix(state);
151 uuid: resource ? resource.uuid : '',
152 ownerUuid: resource ? resource.ownerUuid : '',
155 })((props: { ownerUuid?: string, uuidPrefix: string, uuid?: string } & DispatchProp<any>) =>
156 resourceShare(props.dispatch, props.uuidPrefix, props.ownerUuid, props.uuid));
159 const renderFirstName = (item: { firstName: string }) => {
160 return <Typography noWrap>{item.firstName}</Typography>;
163 export const ResourceFirstName = connect(
164 (state: RootState, props: { uuid: string }) => {
165 const resource = getResource<UserResource>(props.uuid)(state.resources);
166 return resource || { firstName: '' };
169 const renderLastName = (item: { lastName: string }) =>
170 <Typography noWrap>{item.lastName}</Typography>;
172 export const ResourceLastName = connect(
173 (state: RootState, props: { uuid: string }) => {
174 const resource = getResource<UserResource>(props.uuid)(state.resources);
175 return resource || { lastName: '' };
178 const renderFullName = (dispatch: Dispatch ,item: { uuid: string, firstName: string, lastName: string }, link?: boolean) => {
179 const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
180 return link ? <Typography noWrap
182 style={{ 'cursor': 'pointer' }}
183 onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
186 <Typography noWrap>{displayName}</Typography>;
189 export const UserResourceFullName = connect(
190 (state: RootState, props: { uuid: string, link?: boolean }) => {
191 const resource = getResource<UserResource>(props.uuid)(state.resources);
192 return {item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link};
193 })((props: {item: {uuid: string, firstName: string, lastName: string}, link?: boolean} & DispatchProp<any>) => renderFullName(props.dispatch, props.item, props.link));
196 const renderUuid = (item: { uuid: string }) =>
197 <Typography data-cy="uuid" noWrap>{item.uuid}</Typography>;
199 export const ResourceUuid = connect(
200 (state: RootState, props: { uuid: string }) => {
201 const resource = getResource<UserResource>(props.uuid)(state.resources);
202 return resource || { uuid: '' };
205 const renderEmail = (item: { email: string }) =>
206 <Typography noWrap>{item.email}</Typography>;
208 export const ResourceEmail = connect(
209 (state: RootState, props: { uuid: string }) => {
210 const resource = getResource<UserResource>(props.uuid)(state.resources);
211 return resource || { email: '' };
214 enum UserAccountStatus {
216 INACTIVE = 'Inactive',
221 const renderAccountStatus = (props: {status: UserAccountStatus}) =>
222 <Grid container alignItems="center" wrap="nowrap" spacing={8}>
225 switch(props.status) {
226 case UserAccountStatus.ACTIVE:
227 return <ActiveIcon style={{color: '#4caf50'}} />;
228 case UserAccountStatus.SETUP:
229 return <SetupIcon style={{color: '#2196f3'}} />;
230 case UserAccountStatus.INACTIVE:
231 return <InactiveIcon style={{color: '#9e9e9e'}} />;
244 const getUserAccountStatus = (state: RootState, props: { uuid: string }) => {
245 const user = getResource<UserResource>(props.uuid)(state.resources);
246 // Get membership links for all users group
247 const allUsersGroupUuid = getBuiltinGroupUuid(state.auth.localCluster, BuiltinGroups.ALL);
248 const permissions = filterResources((resource: LinkResource) =>
249 resource.kind === ResourceKind.LINK &&
250 resource.linkClass === LinkClass.PERMISSION &&
251 resource.headUuid === allUsersGroupUuid &&
252 resource.tailUuid === props.uuid
256 return user.isActive ? {status: UserAccountStatus.ACTIVE} : permissions.length > 0 ? {status: UserAccountStatus.SETUP} : {status: UserAccountStatus.INACTIVE};
258 return {status: UserAccountStatus.UNKNOWN};
262 export const ResourceLinkTailAccountStatus = connect(
263 (state: RootState, props: { uuid: string }) => {
264 const link = getResource<LinkResource>(props.uuid)(state.resources);
265 return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, {uuid: link.tailUuid}) : {status: UserAccountStatus.UNKNOWN};
266 })(renderAccountStatus);
268 export const UserResourceAccountStatus = connect(getUserAccountStatus)(renderAccountStatus);
270 const renderIsHidden = (props: {
271 memberLinkUuid: string,
272 permissionLinkUuid: string,
275 setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void
277 if (props.memberLinkUuid) {
279 data-cy="user-visible-checkbox"
281 checked={props.visible}
282 disabled={!props.canManage}
285 props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
288 return <Typography />;
292 export const ResourceLinkTailIsVisible = connect(
293 (state: RootState, props: { uuid: string }) => {
294 const link = getResource<LinkResource>(props.uuid)(state.resources);
295 const member = getResource<Resource>(link?.tailUuid || '')(state.resources);
296 const group = getResource<GroupResource>(link?.headUuid || '')(state.resources);
297 const permissions = filterResources((resource: LinkResource) => {
298 return resource.linkClass === LinkClass.PERMISSION
299 && resource.headUuid === link?.tailUuid
300 && resource.tailUuid === group?.uuid
301 && resource.name === PermissionLevel.CAN_READ;
304 const permissionLinkUuid = permissions.length > 0 ? permissions[0].uuid : '';
305 const isVisible = link && group && permissions.length > 0;
306 // Consider whether the current user canManage this resurce in addition when it's possible
307 const isBuiltin = isBuiltinGroup(link?.headUuid || '');
309 return member?.kind === ResourceKind.USER
310 ? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin }
311 : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false };
312 }, { setMemberIsHidden }
315 const renderIsAdmin = (props: { uuid: string, isAdmin: boolean, toggleIsAdmin: (uuid: string) => void }) =>
318 checked={props.isAdmin}
321 props.toggleIsAdmin(props.uuid);
324 export const ResourceIsAdmin = connect(
325 (state: RootState, props: { uuid: string }) => {
326 const resource = getResource<UserResource>(props.uuid)(state.resources);
327 return resource || { isAdmin: false };
331 const renderUsername = (item: { username: string, uuid: string }) =>
332 <Typography noWrap>{item.username || item.uuid}</Typography>;
334 export const ResourceUsername = connect(
335 (state: RootState, props: { uuid: string }) => {
336 const resource = getResource<UserResource>(props.uuid)(state.resources);
337 return resource || { username: '', uuid: props.uuid };
340 // Virtual machine resource
342 const renderHostname = (item: { hostname: string }) =>
343 <Typography noWrap>{item.hostname}</Typography>;
345 export const VirtualMachineHostname = connect(
346 (state: RootState, props: { uuid: string }) => {
347 const resource = getResource<VirtualMachinesResource>(props.uuid)(state.resources);
348 return resource || { hostname: '' };
351 const renderVirtualMachineLogin = (login: {user: string}) =>
352 <Typography noWrap>{login.user}</Typography>
354 export const VirtualMachineLogin = connect(
355 (state: RootState, props: { linkUuid: string }) => {
356 const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
357 const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
359 return {user: user?.username || permission?.tailUuid || ''};
360 })(renderVirtualMachineLogin);
363 const renderCommonData = (data: string) =>
364 <Typography noWrap>{data}</Typography>;
366 const renderCommonDate = (date: string) =>
367 <Typography noWrap>{formatDate(date)}</Typography>;
369 export const CommonUuid = withResourceData('uuid', renderCommonData);
371 // Api Client Authorizations
372 export const TokenApiClientId = withResourceData('apiClientId', renderCommonData);
374 export const TokenApiToken = withResourceData('apiToken', renderCommonData);
376 export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate);
378 export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData);
380 export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate);
382 export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate);
384 export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData);
386 export const TokenScopes = withResourceData('scopes', renderCommonData);
388 export const TokenUserId = withResourceData('userId', renderCommonData);
390 const clusterColors = [
398 export const ResourceCluster = (props: { uuid: string }) => {
399 const CLUSTER_ID_LENGTH = 5;
400 const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
401 const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : '';
402 const ci = pos >= CLUSTER_ID_LENGTH ? (((((
403 (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1))
404 + props.uuid.charCodeAt(2))
405 * props.uuid.charCodeAt(3))
406 + props.uuid.charCodeAt(4))) % clusterColors.length) : 0;
407 return <span style={{
408 backgroundColor: clusterColors[ci][0],
409 color: clusterColors[ci][1],
412 }}>{clusterId}</span>;
416 const renderLinkName = (item: { name: string }) =>
417 <Typography noWrap>{item.name || '(none)'}</Typography>;
419 export const ResourceLinkName = connect(
420 (state: RootState, props: { uuid: string }) => {
421 const resource = getResource<LinkResource>(props.uuid)(state.resources);
422 return resource || { name: '' };
425 const renderLinkClass = (item: { linkClass: string }) =>
426 <Typography noWrap>{item.linkClass}</Typography>;
428 export const ResourceLinkClass = connect(
429 (state: RootState, props: { uuid: string }) => {
430 const resource = getResource<LinkResource>(props.uuid)(state.resources);
431 return resource || { linkClass: '' };
434 const getResourceDisplayName = (resource: Resource): string => {
435 if ((resource as UserResource).kind === ResourceKind.USER
436 && typeof (resource as UserResource).firstName !== 'undefined') {
437 // We can be sure the resource is UserResource
438 return getUserDisplayName(resource as UserResource);
440 return (resource as GroupContentsResource).name;
444 const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
445 var displayName = getResourceDisplayName(item);
447 return <Typography noWrap color="primary" style={{ 'cursor': 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
448 {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid}
452 export const ResourceLinkTail = connect(
453 (state: RootState, props: { uuid: string }) => {
454 const resource = getResource<LinkResource>(props.uuid)(state.resources);
455 const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
458 item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE }
460 })((props: { item: Resource } & DispatchProp<any>) =>
461 renderResourceLink(props.dispatch, props.item));
463 export const ResourceLinkHead = connect(
464 (state: RootState, props: { uuid: string }) => {
465 const resource = getResource<LinkResource>(props.uuid)(state.resources);
466 const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
469 item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE }
471 })((props: { item: Resource } & DispatchProp<any>) =>
472 renderResourceLink(props.dispatch, props.item));
474 export const ResourceLinkUuid = connect(
475 (state: RootState, props: { uuid: string }) => {
476 const resource = getResource<LinkResource>(props.uuid)(state.resources);
477 return resource || { uuid: '' };
480 export const ResourceLinkHeadUuid = connect(
481 (state: RootState, props: { uuid: string }) => {
482 const link = getResource<LinkResource>(props.uuid)(state.resources);
483 const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
485 return headResource || { uuid: '' };
488 export const ResourceLinkTailUuid = connect(
489 (state: RootState, props: { uuid: string }) => {
490 const link = getResource<LinkResource>(props.uuid)(state.resources);
491 const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
493 return tailResource || { uuid: '' };
496 const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => {
500 <IconButton data-cy="resource-delete-button" onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
505 <IconButton disabled data-cy="resource-delete-button">
510 return <Typography noWrap></Typography>;
514 export const ResourceLinkDelete = connect(
515 (state: RootState, props: { uuid: string }) => {
516 const link = getResource<LinkResource>(props.uuid)(state.resources);
517 const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
520 item: link || { uuid: '', kind: ResourceKind.NONE },
521 canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
523 })((props: { item: LinkResource, canManage: boolean } & DispatchProp<any>) =>
524 renderLinkDelete(props.dispatch, props.item, props.canManage));
526 export const ResourceLinkTailEmail = connect(
527 (state: RootState, props: { uuid: string }) => {
528 const link = getResource<LinkResource>(props.uuid)(state.resources);
529 const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
531 return resource || { email: '' };
534 export const ResourceLinkTailUsername = connect(
535 (state: RootState, props: { uuid: string }) => {
536 const link = getResource<LinkResource>(props.uuid)(state.resources);
537 const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
539 return resource || { username: '' };
542 const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => {
543 return <Typography noWrap>
544 {formatPermissionLevel(link.name as PermissionLevel)}
546 <IconButton data-cy="edit-permission-button" onClick={(event) => dispatch<any>(openPermissionEditContextMenu(event, link))}>
554 export const ResourceLinkHeadPermissionLevel = connect(
555 (state: RootState, props: { uuid: string }) => {
556 const link = getResource<LinkResource>(props.uuid)(state.resources);
557 const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
560 link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
561 canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
563 })((props: { link: LinkResource, canManage: boolean } & DispatchProp<any>) =>
564 renderPermissionLevel(props.dispatch, props.link, props.canManage));
566 export const ResourceLinkTailPermissionLevel = connect(
567 (state: RootState, props: { uuid: string }) => {
568 const link = getResource<LinkResource>(props.uuid)(state.resources);
569 const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
572 link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
573 canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
575 })((props: { link: LinkResource, canManage: boolean } & DispatchProp<any>) =>
576 renderPermissionLevel(props.dispatch, props.link, props.canManage));
578 const getResourceLinkCanManage = (state: RootState, link: LinkResource) => {
579 const headResource = getResource<Resource>(link.headUuid)(state.resources);
580 // const tailResource = getResource<Resource>(link.tailUuid)(state.resources);
581 const userUuid = getUserUuid(state);
583 if (headResource && headResource.kind === ResourceKind.GROUP) {
584 return userUuid ? (headResource as GroupResource).writableBy?.includes(userUuid) : false;
592 const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
596 <Tooltip title="Run process">
597 <IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
605 export const ResourceRunProcess = connect(
606 (state: RootState, props: { uuid: string }) => {
607 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
609 uuid: resource ? resource.uuid : ''
611 })((props: { uuid: string } & DispatchProp<any>) =>
612 resourceRunProcess(props.dispatch, props.uuid));
614 const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
615 if (ownerUuid === getPublicUuid(uuidPrefix)) {
616 return renderStatus(WorkflowStatus.PUBLIC);
618 return renderStatus(WorkflowStatus.PRIVATE);
622 const renderStatus = (status: string) =>
623 <Typography noWrap style={{ width: '60px' }}>{status}</Typography>;
625 export const ResourceWorkflowStatus = connect(
626 (state: RootState, props: { uuid: string }) => {
627 const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
628 const uuidPrefix = getUuidPrefix(state);
630 ownerUuid: resource ? resource.ownerUuid : '',
633 })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
635 export const ResourceLastModifiedDate = connect(
636 (state: RootState, props: { uuid: string }) => {
637 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
638 return { date: resource ? resource.modifiedAt : '' };
639 })((props: { date: string }) => renderDate(props.date));
641 export const ResourceCreatedAtDate = connect(
642 (state: RootState, props: { uuid: string }) => {
643 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
644 return { date: resource ? resource.createdAt : '' };
645 })((props: { date: string }) => renderDate(props.date));
647 export const ResourceTrashDate = connect(
648 (state: RootState, props: { uuid: string }) => {
649 const resource = getResource<TrashableResource>(props.uuid)(state.resources);
650 return { date: resource ? resource.trashAt : '' };
651 })((props: { date: string }) => renderDate(props.date));
653 export const ResourceDeleteDate = connect(
654 (state: RootState, props: { uuid: string }) => {
655 const resource = getResource<TrashableResource>(props.uuid)(state.resources);
656 return { date: resource ? resource.deleteAt : '' };
657 })((props: { date: string }) => renderDate(props.date));
659 export const renderFileSize = (fileSize?: number) =>
660 <Typography noWrap style={{ minWidth: '45px' }}>
661 {formatFileSize(fileSize)}
664 export const ResourceFileSize = connect(
665 (state: RootState, props: { uuid: string }) => {
666 const resource = getResource<CollectionResource>(props.uuid)(state.resources);
668 if (resource && resource.kind !== ResourceKind.COLLECTION) {
669 return { fileSize: '' };
672 return { fileSize: resource ? resource.fileSizeTotal : 0 };
673 })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
675 const renderOwner = (owner: string) =>
680 export const ResourceOwner = connect(
681 (state: RootState, props: { uuid: string }) => {
682 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
683 return { owner: resource ? resource.ownerUuid : '' };
684 })((props: { owner: string }) => renderOwner(props.owner));
686 export const ResourceOwnerName = connect(
687 (state: RootState, props: { uuid: string }) => {
688 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
689 const ownerNameState = state.ownerName;
690 const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
691 return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
692 })((props: { owner: string }) => renderOwner(props.owner));
696 (state: RootState, props: { uuid: string }) => {
697 let userFullname = '';
698 const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
701 userFullname = getUserFullname(resource as User) || (resource as GroupContentsResource).name;
704 return { uuid: props.uuid, userFullname };
707 export const ResourceOwnerWithName =
710 withStyles({}, { withTheme: true }))
711 ((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => {
712 const { uuid, userFullname, dispatch, theme } = props;
714 if (userFullname === '') {
715 dispatch<any>(loadResource(uuid, false));
716 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
721 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
722 {userFullname} ({uuid})
726 export const UserNameFromID =
728 (props: { uuid: string, userFullname: string, dispatch: Dispatch }) => {
729 const { uuid, userFullname, dispatch } = props;
731 if (userFullname === '') {
732 dispatch<any>(loadResource(uuid, false));
735 {userFullname ? userFullname : uuid}
739 export const ResponsiblePerson =
742 (state: RootState, props: { uuid: string, parentRef: HTMLElement | null }) => {
743 let responsiblePersonName: string = '';
744 let responsiblePersonUUID: string = '';
745 let responsiblePersonProperty: string = '';
747 if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
749 const keys = Object.keys(state.auth.config.clusterConfig.Collections.ManagedProperties);
751 while (!responsiblePersonProperty && keys[index]) {
752 const key = keys[index];
753 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
754 responsiblePersonProperty = key;
760 let resource: Resource | undefined = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
762 while (resource && resource.kind !== ResourceKind.USER && responsiblePersonProperty) {
763 responsiblePersonUUID = (resource as CollectionResource).properties[responsiblePersonProperty];
764 resource = getResource<GroupContentsResource & UserResource>(responsiblePersonUUID)(state.resources);
767 if (resource && resource.kind === ResourceKind.USER) {
768 responsiblePersonName = getUserFullname(resource as UserResource) || (resource as GroupContentsResource).name;
771 return { uuid: responsiblePersonUUID, responsiblePersonName, parentRef: props.parentRef };
773 withStyles({}, { withTheme: true }))
774 ((props: { uuid: string | null, responsiblePersonName: string, parentRef: HTMLElement | null, theme: ArvadosTheme }) => {
775 const { uuid, responsiblePersonName, parentRef, theme } = props;
777 if (!uuid && parentRef) {
778 parentRef.style.display = 'none';
780 } else if (parentRef) {
781 parentRef.style.display = 'block';
784 if (!responsiblePersonName) {
785 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
790 return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
791 {responsiblePersonName} ({uuid})
795 const renderType = (type: string, subtype: string) =>
797 {resourceLabel(type, subtype)}
800 export const ResourceType = connect(
801 (state: RootState, props: { uuid: string }) => {
802 const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
803 return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
804 })((props: { type: string, subtype: string }) => renderType(props.type, props.subtype));
806 export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
807 return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
808 })((props: { resource: GroupContentsResource }) =>
809 (props.resource && props.resource.kind === ResourceKind.COLLECTION)
810 ? <CollectionStatus uuid={props.resource.uuid} />
811 : <ProcessStatus uuid={props.resource.uuid} />
814 export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
815 return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
816 })((props: { collection: CollectionResource }) =>
817 (props.collection.uuid !== props.collection.currentVersionUuid)
818 ? <Typography>version {props.collection.version}</Typography>
819 : <Typography>head version</Typography>
822 export const ProcessStatus = compose(
823 connect((state: RootState, props: { uuid: string }) => {
824 return { process: getProcess(props.uuid)(state.resources) };
826 withStyles({}, { withTheme: true }))
827 ((props: { process?: Process, theme: ArvadosTheme }) => {
828 const status = props.process ? getProcessStatus(props.process) : "-";
831 style={{ color: getProcessStatusColor(status, props.theme) }} >
836 export const ProcessStartDate = connect(
837 (state: RootState, props: { uuid: string }) => {
838 const process = getProcess(props.uuid)(state.resources);
839 return { date: (process && process.container) ? process.container.startedAt : '' };
840 })((props: { date: string }) => renderDate(props.date));
842 export const renderRunTime = (time: number) =>
843 <Typography noWrap style={{ minWidth: '45px' }}>
844 {formatTime(time, true)}
847 interface ContainerRunTimeProps {
851 interface ContainerRunTimeState {
855 export const ContainerRunTime = connect((state: RootState, props: { uuid: string }) => {
856 return { process: getProcess(props.uuid)(state.resources) };
857 })(class extends React.Component<ContainerRunTimeProps, ContainerRunTimeState> {
860 constructor(props: ContainerRunTimeProps) {
862 this.state = { runtime: this.getRuntime() };
866 return this.props.process ? getProcessRuntime(this.props.process) : 0;
870 this.setState({ runtime: this.getRuntime() });
873 componentDidMount() {
874 this.timer = setInterval(this.updateRuntime.bind(this), 5000);
877 componentWillUnmount() {
878 clearInterval(this.timer);
882 return renderRunTime(this.state.runtime);