Merge branch 'main' into 15768-multi-select-operations Arvados-DCO-1.1-Signed-off...
[arvados.git] / src / views-components / data-explorer / renderers.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 <<<<<<< HEAD
6 import React from 'react';
7 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from '@material-ui/core';
8 import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
9 import { Resource, ResourceKind, TrashableResource } from 'models/resource';
10 =======
11 import React from "react";
12 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from "@material-ui/core";
13 import { FavoriteStar, PublicFavoriteStar } from "../favorite-star/favorite-star";
14 import { Resource, ResourceKind, TrashableResource } from "models/resource";
15 >>>>>>> main
16 import {
17     FreezeIcon,
18     ProjectIcon,
19     FilterGroupIcon,
20     CollectionIcon,
21     ProcessIcon,
22     DefaultIcon,
23     ShareIcon,
24     CollectionOldVersionIcon,
25     WorkflowIcon,
26     RemoveIcon,
27     RenameIcon,
28     ActiveIcon,
29     SetupIcon,
30     InactiveIcon,
31 <<<<<<< HEAD
32 } from 'components/icon/icon';
33 import { formatDate, formatFileSize, formatTime } from 'common/formatters';
34 import { resourceLabel } from 'common/labels';
35 import { connect, DispatchProp } from 'react-redux';
36 import { RootState } from 'store/store';
37 import { getResource, filterResources } from 'store/resources/resources';
38 import { GroupContentsResource } from 'services/groups-service/groups-service';
39 import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from 'store/processes/process';
40 import { ArvadosTheme } from 'common/custom-theme';
41 import { compose, Dispatch } from 'redux';
42 import { WorkflowResource } from 'models/workflow';
43 import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
44 import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
45 import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
46 import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
47 import { toggleIsAdmin } from 'store/users/users-actions';
48 import { LinkClass, LinkResource } from 'models/link';
49 import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
50 import { withResourceData } from 'views-components/data-explorer/with-resources';
51 import { CollectionResource } from 'models/collection';
52 import { IllegalNamingWarning } from 'components/warning/warning';
53 import { loadResource } from 'store/resources/resources-actions';
54 import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
55 import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
56 import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
57 import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
58 import { PermissionLevel } from 'models/permission';
59 import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
60 import { getUserUuid } from 'common/getuser';
61 import { VirtualMachinesResource } from 'models/virtual-machines';
62 import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar';
63 import { ProjectResource } from 'models/project';
64 import { ProcessResource } from 'models/process';
65
66 const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
67     const navFunc = 'groupClass' in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
68     return (
69         <Grid container alignItems='center' wrap='nowrap' spacing={16}>
70             <Grid item>{renderIcon(item)}</Grid>
71             <Grid item>
72                 <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
73 =======
74 } from "components/icon/icon";
75 import { formatDate, formatFileSize, formatTime } from "common/formatters";
76 import { resourceLabel } from "common/labels";
77 import { connect, DispatchProp } from "react-redux";
78 import { RootState } from "store/store";
79 import { getResource, filterResources } from "store/resources/resources";
80 import { GroupContentsResource } from "services/groups-service/groups-service";
81 import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from "store/processes/process";
82 import { ArvadosTheme } from "common/custom-theme";
83 import { compose, Dispatch } from "redux";
84 import { WorkflowResource } from "models/workflow";
85 import { ResourceStatus as WorkflowStatus } from "views/workflow-panel/workflow-panel-view";
86 import { getUuidPrefix, openRunProcess } from "store/workflow-panel/workflow-panel-actions";
87 import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
88 import { getUserFullname, getUserDisplayName, User, UserResource } from "models/user";
89 import { toggleIsAdmin } from "store/users/users-actions";
90 import { LinkClass, LinkResource } from "models/link";
91 import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from "store/navigation/navigation-action";
92 import { withResourceData } from "views-components/data-explorer/with-resources";
93 import { CollectionResource } from "models/collection";
94 import { IllegalNamingWarning } from "components/warning/warning";
95 import { loadResource } from "store/resources/resources-actions";
96 import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from "models/group";
97 import { openRemoveGroupMemberDialog } from "store/group-details-panel/group-details-panel-actions";
98 import { setMemberIsHidden } from "store/group-details-panel/group-details-panel-actions";
99 import { formatPermissionLevel } from "views-components/sharing-dialog/permission-select";
100 import { PermissionLevel } from "models/permission";
101 import { openPermissionEditContextMenu } from "store/context-menu/context-menu-actions";
102 import { VirtualMachinesResource } from "models/virtual-machines";
103 import { CopyToClipboardSnackbar } from "components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar";
104 import { ProjectResource } from "models/project";
105 import { ProcessResource } from "models/process";
106
107 const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
108     const navFunc = "groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
109     return (
110         <Grid
111             container
112             alignItems="center"
113             wrap="nowrap"
114             spacing={16}
115         >
116             <Grid item>{renderIcon(item)}</Grid>
117             <Grid item>
118                 <Typography
119                     color="primary"
120                     style={{ width: "auto", cursor: "pointer" }}
121                     onClick={() => dispatch<any>(navFunc(item.uuid))}
122                 >
123 >>>>>>> main
124                     {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION ? <IllegalNamingWarning name={item.name} /> : null}
125                     {item.name}
126                 </Typography>
127             </Grid>
128             <Grid item>
129 <<<<<<< HEAD
130                 <Typography variant='caption'>
131 =======
132                 <Typography variant="caption">
133 >>>>>>> main
134                     <FavoriteStar resourceUuid={item.uuid} />
135                     <PublicFavoriteStar resourceUuid={item.uuid} />
136                     {item.kind === ResourceKind.PROJECT && <FrozenProject item={item} />}
137                 </Typography>
138             </Grid>
139         </Grid>
140     );
141 };
142
143 const FrozenProject = (props: { item: ProjectResource }) => {
144     const [fullUsername, setFullusername] = React.useState<any>(null);
145     const getFullName = React.useCallback(() => {
146         if (props.item.frozenByUuid) {
147             setFullusername(<UserNameFromID uuid={props.item.frozenByUuid} />);
148         }
149     }, [props.item, setFullusername]);
150
151     if (props.item.frozenByUuid) {
152         return (
153 <<<<<<< HEAD
154             <Tooltip onOpen={getFullName} enterDelay={500} title={<span>Project was frozen by {fullUsername}</span>}>
155                 <FreezeIcon style={{ fontSize: 'inherit' }} />
156 =======
157             <Tooltip
158                 onOpen={getFullName}
159                 enterDelay={500}
160                 title={<span>Project was frozen by {fullUsername}</span>}
161             >
162                 <FreezeIcon style={{ fontSize: "inherit" }} />
163 >>>>>>> main
164             </Tooltip>
165         );
166     } else {
167         return null;
168     }
169 };
170
171 export const ResourceName = connect((state: RootState, props: { uuid: string }) => {
172     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
173     return resource;
174 })((resource: GroupContentsResource & DispatchProp<any>) => renderName(resource.dispatch, resource));
175
176 const renderIcon = (item: GroupContentsResource) => {
177     switch (item.kind) {
178         case ResourceKind.PROJECT:
179             if (item.groupClass === GroupClass.FILTER) {
180                 return <FilterGroupIcon />;
181             }
182             return <ProjectIcon />;
183         case ResourceKind.COLLECTION:
184             if (item.uuid === item.currentVersionUuid) {
185                 return <CollectionIcon />;
186             }
187             return <CollectionOldVersionIcon />;
188         case ResourceKind.PROCESS:
189             return <ProcessIcon />;
190         case ResourceKind.WORKFLOW:
191             return <WorkflowIcon />;
192         default:
193             return <DefaultIcon />;
194     }
195 };
196
197 const renderDate = (date?: string) => {
198     return (
199 <<<<<<< HEAD
200         <Typography noWrap style={{ minWidth: '100px' }}>
201 =======
202         <Typography
203             noWrap
204             style={{ minWidth: "100px" }}
205         >
206 >>>>>>> main
207             {formatDate(date)}
208         </Typography>
209     );
210 };
211
212 const renderWorkflowName = (item: WorkflowResource) => (
213 <<<<<<< HEAD
214     <Grid container alignItems='center' wrap='nowrap' spacing={16}>
215         <Grid item>{renderIcon(item)}</Grid>
216         <Grid item>
217             <Typography color='primary' style={{ width: '100px' }}>
218 =======
219     <Grid
220         container
221         alignItems="center"
222         wrap="nowrap"
223         spacing={16}
224     >
225         <Grid item>{renderIcon(item)}</Grid>
226         <Grid item>
227             <Typography
228                 color="primary"
229                 style={{ width: "100px" }}
230             >
231 >>>>>>> main
232                 {item.name}
233             </Typography>
234         </Grid>
235     </Grid>
236 );
237
238 export const ResourceWorkflowName = connect((state: RootState, props: { uuid: string }) => {
239     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
240     return resource;
241 })(renderWorkflowName);
242
243 const getPublicUuid = (uuidPrefix: string) => {
244     return `${uuidPrefix}-tpzed-anonymouspublic`;
245 };
246
247 const resourceShare = (dispatch: Dispatch, uuidPrefix: string, ownerUuid?: string, uuid?: string) => {
248     const isPublic = ownerUuid === getPublicUuid(uuidPrefix);
249     return (
250         <div>
251             {!isPublic && uuid && (
252 <<<<<<< HEAD
253                 <Tooltip title='Share'>
254 =======
255                 <Tooltip title="Share">
256 >>>>>>> main
257                     <IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
258                         <ShareIcon />
259                     </IconButton>
260                 </Tooltip>
261             )}
262         </div>
263     );
264 };
265
266 export const ResourceShare = connect((state: RootState, props: { uuid: string }) => {
267     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
268     const uuidPrefix = getUuidPrefix(state);
269     return {
270 <<<<<<< HEAD
271         uuid: resource ? resource.uuid : '',
272         ownerUuid: resource ? resource.ownerUuid : '',
273 =======
274         uuid: resource ? resource.uuid : "",
275         ownerUuid: resource ? resource.ownerUuid : "",
276 >>>>>>> main
277         uuidPrefix,
278     };
279 })((props: { ownerUuid?: string; uuidPrefix: string; uuid?: string } & DispatchProp<any>) =>
280     resourceShare(props.dispatch, props.uuidPrefix, props.ownerUuid, props.uuid)
281 );
282
283 // User Resources
284 const renderFirstName = (item: { firstName: string }) => {
285     return <Typography noWrap>{item.firstName}</Typography>;
286 };
287
288 export const ResourceFirstName = connect((state: RootState, props: { uuid: string }) => {
289     const resource = getResource<UserResource>(props.uuid)(state.resources);
290 <<<<<<< HEAD
291     return resource || { firstName: '' };
292 =======
293     return resource || { firstName: "" };
294 >>>>>>> main
295 })(renderFirstName);
296
297 const renderLastName = (item: { lastName: string }) => <Typography noWrap>{item.lastName}</Typography>;
298
299 export const ResourceLastName = connect((state: RootState, props: { uuid: string }) => {
300     const resource = getResource<UserResource>(props.uuid)(state.resources);
301 <<<<<<< HEAD
302     return resource || { lastName: '' };
303 })(renderLastName);
304
305 const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
306     const displayName = (item.firstName + ' ' + item.lastName).trim() || item.uuid;
307     return link ? (
308         <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
309 =======
310     return resource || { lastName: "" };
311 })(renderLastName);
312
313 const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
314     const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
315     return link ? (
316         <Typography
317             noWrap
318             color="primary"
319             style={{ cursor: "pointer" }}
320             onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}
321         >
322 >>>>>>> main
323             {displayName}
324         </Typography>
325     ) : (
326         <Typography noWrap>{displayName}</Typography>
327     );
328 };
329
330 export const UserResourceFullName = connect((state: RootState, props: { uuid: string; link?: boolean }) => {
331     const resource = getResource<UserResource>(props.uuid)(state.resources);
332 <<<<<<< HEAD
333     return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
334 =======
335     return { item: resource || { uuid: "", firstName: "", lastName: "" }, link: props.link };
336 >>>>>>> main
337 })((props: { item: { uuid: string; firstName: string; lastName: string }; link?: boolean } & DispatchProp<any>) =>
338     renderFullName(props.dispatch, props.item, props.link)
339 );
340
341 const renderUuid = (item: { uuid: string }) => (
342 <<<<<<< HEAD
343     <Typography data-cy='uuid' noWrap>
344         {item.uuid}
345         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
346 =======
347     <Typography
348         data-cy="uuid"
349         noWrap
350     >
351         {item.uuid}
352         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
353 >>>>>>> main
354     </Typography>
355 );
356
357 const renderUuidCopyIcon = (item: { uuid: string }) => (
358 <<<<<<< HEAD
359     <Typography data-cy='uuid' noWrap>
360         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
361     </Typography>
362 );
363
364 export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' })(renderUuid);
365 =======
366     <Typography
367         data-cy="uuid"
368         noWrap
369     >
370         {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
371     </Typography>
372 );
373
374 export const ResourceUuid = connect(
375     (state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: "" }
376 )(renderUuid);
377 >>>>>>> main
378
379 const renderEmail = (item: { email: string }) => <Typography noWrap>{item.email}</Typography>;
380
381 export const ResourceEmail = connect((state: RootState, props: { uuid: string }) => {
382     const resource = getResource<UserResource>(props.uuid)(state.resources);
383 <<<<<<< HEAD
384     return resource || { email: '' };
385 })(renderEmail);
386
387 enum UserAccountStatus {
388     ACTIVE = 'Active',
389     INACTIVE = 'Inactive',
390     SETUP = 'Setup',
391     UNKNOWN = '',
392 }
393
394 const renderAccountStatus = (props: { status: UserAccountStatus }) => (
395     <Grid container alignItems='center' wrap='nowrap' spacing={8} data-cy='account-status'>
396 =======
397     return resource || { email: "" };
398 })(renderEmail);
399
400 enum UserAccountStatus {
401     ACTIVE = "Active",
402     INACTIVE = "Inactive",
403     SETUP = "Setup",
404     UNKNOWN = "",
405 }
406
407 const renderAccountStatus = (props: { status: UserAccountStatus }) => (
408     <Grid
409         container
410         alignItems="center"
411         wrap="nowrap"
412         spacing={8}
413         data-cy="account-status"
414     >
415 >>>>>>> main
416         <Grid item>
417             {(() => {
418                 switch (props.status) {
419                     case UserAccountStatus.ACTIVE:
420 <<<<<<< HEAD
421                         return <ActiveIcon style={{ color: '#4caf50', verticalAlign: 'middle' }} />;
422                     case UserAccountStatus.SETUP:
423                         return <SetupIcon style={{ color: '#2196f3', verticalAlign: 'middle' }} />;
424                     case UserAccountStatus.INACTIVE:
425                         return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: 'middle' }} />;
426 =======
427                         return <ActiveIcon style={{ color: "#4caf50", verticalAlign: "middle" }} />;
428                     case UserAccountStatus.SETUP:
429                         return <SetupIcon style={{ color: "#2196f3", verticalAlign: "middle" }} />;
430                     case UserAccountStatus.INACTIVE:
431                         return <InactiveIcon style={{ color: "#9e9e9e", verticalAlign: "middle" }} />;
432 >>>>>>> main
433                     default:
434                         return <></>;
435                 }
436             })()}
437         </Grid>
438         <Grid item>
439             <Typography noWrap>{props.status}</Typography>
440         </Grid>
441     </Grid>
442 );
443
444 const getUserAccountStatus = (state: RootState, props: { uuid: string }) => {
445     const user = getResource<UserResource>(props.uuid)(state.resources);
446     // Get membership links for all users group
447     const allUsersGroupUuid = getBuiltinGroupUuid(state.auth.localCluster, BuiltinGroups.ALL);
448     const permissions = filterResources(
449         (resource: LinkResource) =>
450             resource.kind === ResourceKind.LINK &&
451             resource.linkClass === LinkClass.PERMISSION &&
452             resource.headUuid === allUsersGroupUuid &&
453             resource.tailUuid === props.uuid
454     )(state.resources);
455
456     if (user) {
457         return user.isActive
458             ? { status: UserAccountStatus.ACTIVE }
459             : permissions.length > 0
460             ? { status: UserAccountStatus.SETUP }
461             : { status: UserAccountStatus.INACTIVE };
462     } else {
463         return { status: UserAccountStatus.UNKNOWN };
464     }
465 };
466
467 export const ResourceLinkTailAccountStatus = connect((state: RootState, props: { uuid: string }) => {
468     const link = getResource<LinkResource>(props.uuid)(state.resources);
469     return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, { uuid: link.tailUuid }) : { status: UserAccountStatus.UNKNOWN };
470 })(renderAccountStatus);
471
472 export const UserResourceAccountStatus = connect(getUserAccountStatus)(renderAccountStatus);
473
474 const renderIsHidden = (props: {
475     memberLinkUuid: string;
476     permissionLinkUuid: string;
477     visible: boolean;
478     canManage: boolean;
479     setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void;
480 }) => {
481     if (props.memberLinkUuid) {
482         return (
483             <Checkbox
484 <<<<<<< HEAD
485                 data-cy='user-visible-checkbox'
486                 color='primary'
487                 checked={props.visible}
488                 disabled={!props.canManage}
489                 onClick={(e) => {
490 =======
491                 data-cy="user-visible-checkbox"
492                 color="primary"
493                 checked={props.visible}
494                 disabled={!props.canManage}
495                 onClick={e => {
496 >>>>>>> main
497                     e.stopPropagation();
498                     props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
499                 }}
500             />
501         );
502     } else {
503         return <Typography />;
504     }
505 };
506
507 export const ResourceLinkTailIsVisible = connect(
508     (state: RootState, props: { uuid: string }) => {
509         const link = getResource<LinkResource>(props.uuid)(state.resources);
510         const member = getResource<Resource>(link?.tailUuid || "")(state.resources);
511         const group = getResource<GroupResource>(link?.headUuid || "")(state.resources);
512         const permissions = filterResources((resource: LinkResource) => {
513             return (
514                 resource.linkClass === LinkClass.PERMISSION &&
515                 resource.headUuid === link?.tailUuid &&
516                 resource.tailUuid === group?.uuid &&
517                 resource.name === PermissionLevel.CAN_READ
518             );
519         })(state.resources);
520
521         const permissionLinkUuid = permissions.length > 0 ? permissions[0].uuid : "";
522         const isVisible = link && group && permissions.length > 0;
523         // Consider whether the current user canManage this resurce in addition when it's possible
524         const isBuiltin = isBuiltinGroup(link?.headUuid || "");
525
526         return member?.kind === ResourceKind.USER
527             ? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin }
528 <<<<<<< HEAD
529             : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false };
530 =======
531             : { memberLinkUuid: "", permissionLinkUuid: "", visible: false, canManage: false };
532 >>>>>>> main
533     },
534     { setMemberIsHidden }
535 )(renderIsHidden);
536
537 const renderIsAdmin = (props: { uuid: string; isAdmin: boolean; toggleIsAdmin: (uuid: string) => void }) => (
538     <Checkbox
539         color='primary'
540         checked={props.isAdmin}
541         onClick={e => {
542             e.stopPropagation();
543             props.toggleIsAdmin(props.uuid);
544         }}
545     />
546 );
547
548 export const ResourceIsAdmin = connect(
549     (state: RootState, props: { uuid: string }) => {
550         const resource = getResource<UserResource>(props.uuid)(state.resources);
551         return resource || { isAdmin: false };
552     },
553     { toggleIsAdmin }
554 )(renderIsAdmin);
555
556 const renderUsername = (item: { username: string; uuid: string }) => <Typography noWrap>{item.username || item.uuid}</Typography>;
557
558 export const ResourceUsername = connect((state: RootState, props: { uuid: string }) => {
559     const resource = getResource<UserResource>(props.uuid)(state.resources);
560 <<<<<<< HEAD
561     return resource || { username: '', uuid: props.uuid };
562 =======
563     return resource || { username: "", uuid: props.uuid };
564 >>>>>>> main
565 })(renderUsername);
566
567 // Virtual machine resource
568
569 const renderHostname = (item: { hostname: string }) => <Typography noWrap>{item.hostname}</Typography>;
570
571 export const VirtualMachineHostname = connect((state: RootState, props: { uuid: string }) => {
572     const resource = getResource<VirtualMachinesResource>(props.uuid)(state.resources);
573 <<<<<<< HEAD
574     return resource || { hostname: '' };
575 =======
576     return resource || { hostname: "" };
577 >>>>>>> main
578 })(renderHostname);
579
580 const renderVirtualMachineLogin = (login: { user: string }) => <Typography noWrap>{login.user}</Typography>;
581
582 export const VirtualMachineLogin = connect((state: RootState, props: { linkUuid: string }) => {
583     const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
584 <<<<<<< HEAD
585     const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
586
587     return { user: user?.username || permission?.tailUuid || '' };
588 =======
589     const user = getResource<UserResource>(permission?.tailUuid || "")(state.resources);
590
591     return { user: user?.username || permission?.tailUuid || "" };
592 >>>>>>> main
593 })(renderVirtualMachineLogin);
594
595 // Common methods
596 const renderCommonData = (data: string) => <Typography noWrap>{data}</Typography>;
597
598 const renderCommonDate = (date: string) => <Typography noWrap>{formatDate(date)}</Typography>;
599
600 export const CommonUuid = withResourceData("uuid", renderCommonData);
601
602 // Api Client Authorizations
603 export const TokenApiClientId = withResourceData("apiClientId", renderCommonData);
604
605 export const TokenApiToken = withResourceData("apiToken", renderCommonData);
606
607 export const TokenCreatedByIpAddress = withResourceData("createdByIpAddress", renderCommonDate);
608
609 export const TokenDefaultOwnerUuid = withResourceData("defaultOwnerUuid", renderCommonData);
610
611 export const TokenExpiresAt = withResourceData("expiresAt", renderCommonDate);
612
613 export const TokenLastUsedAt = withResourceData("lastUsedAt", renderCommonDate);
614
615 export const TokenLastUsedByIpAddress = withResourceData("lastUsedByIpAddress", renderCommonData);
616
617 export const TokenScopes = withResourceData("scopes", renderCommonData);
618
619 export const TokenUserId = withResourceData("userId", renderCommonData);
620
621 const clusterColors = [
622 <<<<<<< HEAD
623     ['#f44336', '#fff'],
624     ['#2196f3', '#fff'],
625     ['#009688', '#fff'],
626     ['#cddc39', '#fff'],
627     ['#ff9800', '#fff'],
628 =======
629     ["#f44336", "#fff"],
630     ["#2196f3", "#fff"],
631     ["#009688", "#fff"],
632     ["#cddc39", "#fff"],
633     ["#ff9800", "#fff"],
634 >>>>>>> main
635 ];
636
637 export const ResourceCluster = (props: { uuid: string }) => {
638     const CLUSTER_ID_LENGTH = 5;
639 <<<<<<< HEAD
640     const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
641     const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : '';
642     const ci =
643         pos >= CLUSTER_ID_LENGTH
644             ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) + props.uuid.charCodeAt(4)) %
645 =======
646     const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf("-") : 5;
647     const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : "";
648     const ci =
649         pos >= CLUSTER_ID_LENGTH
650             ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) +
651                   props.uuid.charCodeAt(4)) %
652 >>>>>>> main
653               clusterColors.length
654             : 0;
655     return (
656         <span
657             style={{
658                 backgroundColor: clusterColors[ci][0],
659                 color: clusterColors[ci][1],
660 <<<<<<< HEAD
661                 padding: '2px 7px',
662 =======
663                 padding: "2px 7px",
664 >>>>>>> main
665                 borderRadius: 3,
666             }}
667         >
668             {clusterId}
669         </span>
670     );
671 };
672
673 // Links Resources
674 <<<<<<< HEAD
675 const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || '-'}</Typography>;
676
677 export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
678     const resource = getResource<LinkResource>(props.uuid)(state.resources);
679     return resource || { name: '' };
680 =======
681 const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || "-"}</Typography>;
682
683 export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
684     const resource = getResource<LinkResource>(props.uuid)(state.resources);
685     return resource || { name: "" };
686 >>>>>>> main
687 })(renderLinkName);
688
689 const renderLinkClass = (item: { linkClass: string }) => <Typography noWrap>{item.linkClass}</Typography>;
690
691 export const ResourceLinkClass = connect((state: RootState, props: { uuid: string }) => {
692     const resource = getResource<LinkResource>(props.uuid)(state.resources);
693 <<<<<<< HEAD
694     return resource || { linkClass: '' };
695 })(renderLinkClass);
696
697 const getResourceDisplayName = (resource: Resource): string => {
698     if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== 'undefined') {
699 =======
700     return resource || { linkClass: "" };
701 })(renderLinkClass);
702
703 const getResourceDisplayName = (resource: Resource): string => {
704     if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== "undefined") {
705 >>>>>>> main
706         // We can be sure the resource is UserResource
707         return getUserDisplayName(resource as UserResource);
708     } else {
709         return (resource as GroupContentsResource).name;
710     }
711 };
712
713 const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
714     var displayName = getResourceDisplayName(item);
715
716     return (
717 <<<<<<< HEAD
718         <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
719             {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid}
720 =======
721         <Typography
722             noWrap
723             color="primary"
724             style={{ cursor: "pointer" }}
725             onClick={() => {
726                 console.log(item);
727                 item.kind === ResourceKind.GROUP && (item as GroupResource).groupClass === "role"
728                     ? dispatch<any>(navigateToGroupDetails(item.uuid))
729                     : dispatch<any>(navigateTo(item.uuid));
730             }}
731         >
732             {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || "" : "")}:{" "}
733             {displayName || item.uuid}
734 >>>>>>> main
735         </Typography>
736     );
737 };
738
739 export const ResourceLinkTail = connect((state: RootState, props: { uuid: string }) => {
740     const resource = getResource<LinkResource>(props.uuid)(state.resources);
741 <<<<<<< HEAD
742     const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
743
744     return {
745         item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE },
746 =======
747     const tailResource = getResource<Resource>(resource?.tailUuid || "")(state.resources);
748
749     return {
750         item: tailResource || { uuid: resource?.tailUuid || "", kind: resource?.tailKind || ResourceKind.NONE },
751 >>>>>>> main
752     };
753 })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
754
755 export const ResourceLinkHead = connect((state: RootState, props: { uuid: string }) => {
756     const resource = getResource<LinkResource>(props.uuid)(state.resources);
757 <<<<<<< HEAD
758     const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
759
760     return {
761         item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE },
762 =======
763     const headResource = getResource<Resource>(resource?.headUuid || "")(state.resources);
764
765     return {
766         item: headResource || { uuid: resource?.headUuid || "", kind: resource?.headKind || ResourceKind.NONE },
767 >>>>>>> main
768     };
769 })((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
770
771 export const ResourceLinkUuid = connect((state: RootState, props: { uuid: string }) => {
772     const resource = getResource<LinkResource>(props.uuid)(state.resources);
773 <<<<<<< HEAD
774     return resource || { uuid: '' };
775 =======
776     return resource || { uuid: "" };
777 >>>>>>> main
778 })(renderUuid);
779
780 export const ResourceLinkHeadUuid = connect((state: RootState, props: { uuid: string }) => {
781     const link = getResource<LinkResource>(props.uuid)(state.resources);
782 <<<<<<< HEAD
783     const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
784
785     return headResource || { uuid: '' };
786 =======
787     const headResource = getResource<Resource>(link?.headUuid || "")(state.resources);
788
789     return headResource || { uuid: "" };
790 >>>>>>> main
791 })(renderUuid);
792
793 export const ResourceLinkTailUuid = connect((state: RootState, props: { uuid: string }) => {
794     const link = getResource<LinkResource>(props.uuid)(state.resources);
795 <<<<<<< HEAD
796     const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
797
798     return tailResource || { uuid: '' };
799 =======
800     const tailResource = getResource<Resource>(link?.tailUuid || "")(state.resources);
801
802     return tailResource || { uuid: "" };
803 >>>>>>> main
804 })(renderUuid);
805
806 const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => {
807     if (item.uuid) {
808         return canManage ? (
809             <Typography noWrap>
810 <<<<<<< HEAD
811                 <IconButton data-cy='resource-delete-button' onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
812 =======
813                 <IconButton
814                     data-cy="resource-delete-button"
815                     onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}
816                 >
817 >>>>>>> main
818                     <RemoveIcon />
819                 </IconButton>
820             </Typography>
821         ) : (
822             <Typography noWrap>
823 <<<<<<< HEAD
824                 <IconButton disabled data-cy='resource-delete-button'>
825 =======
826                 <IconButton
827                     disabled
828                     data-cy="resource-delete-button"
829                 >
830 >>>>>>> main
831                     <RemoveIcon />
832                 </IconButton>
833             </Typography>
834         );
835     } else {
836         return <Typography noWrap></Typography>;
837     }
838 };
839
840 export const ResourceLinkDelete = connect((state: RootState, props: { uuid: string }) => {
841     const link = getResource<LinkResource>(props.uuid)(state.resources);
842 <<<<<<< HEAD
843     const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
844
845     return {
846         item: link || { uuid: '', kind: ResourceKind.NONE },
847 =======
848     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
849
850     return {
851         item: link || { uuid: "", kind: ResourceKind.NONE },
852 >>>>>>> main
853         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
854     };
855 })((props: { item: LinkResource; canManage: boolean } & DispatchProp<any>) => renderLinkDelete(props.dispatch, props.item, props.canManage));
856
857 export const ResourceLinkTailEmail = connect((state: RootState, props: { uuid: string }) => {
858     const link = getResource<LinkResource>(props.uuid)(state.resources);
859 <<<<<<< HEAD
860     const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
861
862     return resource || { email: '' };
863 =======
864     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
865
866     return resource || { email: "" };
867 >>>>>>> main
868 })(renderEmail);
869
870 export const ResourceLinkTailUsername = connect((state: RootState, props: { uuid: string }) => {
871     const link = getResource<LinkResource>(props.uuid)(state.resources);
872 <<<<<<< HEAD
873     const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
874
875     return resource || { username: '' };
876 =======
877     const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
878
879     return resource || { username: "" };
880 >>>>>>> main
881 })(renderUsername);
882
883 const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => {
884     return (
885         <Typography noWrap>
886             {formatPermissionLevel(link.name as PermissionLevel)}
887             {canManage ? (
888 <<<<<<< HEAD
889                 <IconButton data-cy='edit-permission-button' onClick={(event) => dispatch<any>(openPermissionEditContextMenu(event, link))}>
890                     <RenameIcon />
891                 </IconButton>
892             ) : (
893                 ''
894 =======
895                 <IconButton
896                     data-cy="edit-permission-button"
897                     onClick={event => dispatch<any>(openPermissionEditContextMenu(event, link))}
898                 >
899                     <RenameIcon />
900                 </IconButton>
901             ) : (
902                 ""
903 >>>>>>> main
904             )}
905         </Typography>
906     );
907 };
908
909 export const ResourceLinkHeadPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
910     const link = getResource<LinkResource>(props.uuid)(state.resources);
911 <<<<<<< HEAD
912     const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
913
914     return {
915         link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
916 =======
917     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
918
919     return {
920         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
921 >>>>>>> main
922         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
923     };
924 })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
925
926 export const ResourceLinkTailPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
927     const link = getResource<LinkResource>(props.uuid)(state.resources);
928 <<<<<<< HEAD
929     const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
930
931     return {
932         link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
933 =======
934     const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
935
936     return {
937         link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
938 >>>>>>> main
939         canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
940     };
941 })((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
942
943 const getResourceLinkCanManage = (state: RootState, link: LinkResource) => {
944     const headResource = getResource<Resource>(link.headUuid)(state.resources);
945     if (headResource && headResource.kind === ResourceKind.GROUP) {
946         return (headResource as GroupResource).canManage;
947     } else {
948         // true for now
949         return true;
950     }
951 };
952
953 // Process Resources
954 const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
955     return (
956         <div>
957             {uuid && (
958 <<<<<<< HEAD
959                 <Tooltip title='Run process'>
960 =======
961                 <Tooltip title="Run process">
962 >>>>>>> main
963                     <IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
964                         <ProcessIcon />
965                     </IconButton>
966                 </Tooltip>
967             )}
968         </div>
969     );
970 };
971
972 export const ResourceRunProcess = connect((state: RootState, props: { uuid: string }) => {
973     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
974     return {
975 <<<<<<< HEAD
976         uuid: resource ? resource.uuid : '',
977 =======
978         uuid: resource ? resource.uuid : "",
979 >>>>>>> main
980     };
981 })((props: { uuid: string } & DispatchProp<any>) => resourceRunProcess(props.dispatch, props.uuid));
982
983 const renderWorkflowStatus = (uuidPrefix: string, ownerUuid?: string) => {
984     if (ownerUuid === getPublicUuid(uuidPrefix)) {
985         return renderStatus(WorkflowStatus.PUBLIC);
986     } else {
987         return renderStatus(WorkflowStatus.PRIVATE);
988     }
989 };
990
991 const renderStatus = (status: string) => (
992 <<<<<<< HEAD
993     <Typography noWrap style={{ width: '60px' }}>
994 =======
995     <Typography
996         noWrap
997         style={{ width: "60px" }}
998     >
999 >>>>>>> main
1000         {status}
1001     </Typography>
1002 );
1003
1004 export const ResourceWorkflowStatus = connect((state: RootState, props: { uuid: string }) => {
1005     const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
1006     const uuidPrefix = getUuidPrefix(state);
1007     return {
1008 <<<<<<< HEAD
1009         ownerUuid: resource ? resource.ownerUuid : '',
1010 =======
1011         ownerUuid: resource ? resource.ownerUuid : "",
1012 >>>>>>> main
1013         uuidPrefix,
1014     };
1015 })((props: { ownerUuid?: string; uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
1016
1017 export const ResourceContainerUuid = connect((state: RootState, props: { uuid: string }) => {
1018     const process = getProcess(props.uuid)(state.resources);
1019 <<<<<<< HEAD
1020     return { uuid: process?.container?.uuid ? process?.container?.uuid : '' };
1021 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
1022
1023 enum ColumnSelection {
1024     OUTPUT_UUID = 'outputUuid',
1025     LOG_UUID = 'logUuid',
1026 =======
1027     return { uuid: process?.container?.uuid ? process?.container?.uuid : "" };
1028 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
1029
1030 enum ColumnSelection {
1031     OUTPUT_UUID = "outputUuid",
1032     LOG_UUID = "logUuid",
1033 >>>>>>> main
1034 }
1035
1036 const renderUuidLinkWithCopyIcon = (dispatch: Dispatch, item: ProcessResource, column: string) => {
1037     const selectedColumnUuid = item[column];
1038     return (
1039 <<<<<<< HEAD
1040         <Grid container alignItems='center' wrap='nowrap'>
1041             <Grid item>
1042                 {selectedColumnUuid ? (
1043                     <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} noWrap onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}>
1044                         {selectedColumnUuid}
1045                     </Typography>
1046                 ) : (
1047                     '-'
1048 =======
1049         <Grid
1050             container
1051             alignItems="center"
1052             wrap="nowrap"
1053         >
1054             <Grid item>
1055                 {selectedColumnUuid ? (
1056                     <Typography
1057                         color="primary"
1058                         style={{ width: "auto", cursor: "pointer" }}
1059                         noWrap
1060                         onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}
1061                     >
1062                         {selectedColumnUuid}
1063                     </Typography>
1064                 ) : (
1065                     "-"
1066 >>>>>>> main
1067                 )}
1068             </Grid>
1069             <Grid item>{selectedColumnUuid && renderUuidCopyIcon({ uuid: selectedColumnUuid })}</Grid>
1070         </Grid>
1071     );
1072 };
1073
1074 export const ResourceOutputUuid = connect((state: RootState, props: { uuid: string }) => {
1075     const resource = getResource<ProcessResource>(props.uuid)(state.resources);
1076     return resource;
1077 })((process: ProcessResource & DispatchProp<any>) => renderUuidLinkWithCopyIcon(process.dispatch, process, ColumnSelection.OUTPUT_UUID));
1078
1079 export const ResourceLogUuid = connect((state: RootState, props: { uuid: string }) => {
1080     const resource = getResource<ProcessResource>(props.uuid)(state.resources);
1081     return resource;
1082 })((process: ProcessResource & DispatchProp<any>) => renderUuidLinkWithCopyIcon(process.dispatch, process, ColumnSelection.LOG_UUID));
1083
1084 export const ResourceParentProcess = connect((state: RootState, props: { uuid: string }) => {
1085     const process = getProcess(props.uuid)(state.resources);
1086 <<<<<<< HEAD
1087     return { parentProcess: process?.containerRequest?.requestingContainerUuid || '' };
1088 =======
1089     return { parentProcess: process?.containerRequest?.requestingContainerUuid || "" };
1090 >>>>>>> main
1091 })((props: { parentProcess: string }) => renderUuid({ uuid: props.parentProcess }));
1092
1093 export const ResourceModifiedByUserUuid = connect((state: RootState, props: { uuid: string }) => {
1094     const process = getProcess(props.uuid)(state.resources);
1095 <<<<<<< HEAD
1096     return { userUuid: process?.containerRequest?.modifiedByUserUuid || '' };
1097 =======
1098     return { userUuid: process?.containerRequest?.modifiedByUserUuid || "" };
1099 >>>>>>> main
1100 })((props: { userUuid: string }) => renderUuid({ uuid: props.userUuid }));
1101
1102 export const ResourceCreatedAtDate = connect((state: RootState, props: { uuid: string }) => {
1103     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1104 <<<<<<< HEAD
1105     return { date: resource ? resource.createdAt : '' };
1106 =======
1107     return { date: resource ? resource.createdAt : "" };
1108 >>>>>>> main
1109 })((props: { date: string }) => renderDate(props.date));
1110
1111 export const ResourceLastModifiedDate = connect((state: RootState, props: { uuid: string }) => {
1112     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1113 <<<<<<< HEAD
1114     return { date: resource ? resource.modifiedAt : '' };
1115 =======
1116     return { date: resource ? resource.modifiedAt : "" };
1117 >>>>>>> main
1118 })((props: { date: string }) => renderDate(props.date));
1119
1120 export const ResourceTrashDate = connect((state: RootState, props: { uuid: string }) => {
1121     const resource = getResource<TrashableResource>(props.uuid)(state.resources);
1122 <<<<<<< HEAD
1123     return { date: resource ? resource.trashAt : '' };
1124 =======
1125     return { date: resource ? resource.trashAt : "" };
1126 >>>>>>> main
1127 })((props: { date: string }) => renderDate(props.date));
1128
1129 export const ResourceDeleteDate = connect((state: RootState, props: { uuid: string }) => {
1130     const resource = getResource<TrashableResource>(props.uuid)(state.resources);
1131 <<<<<<< HEAD
1132     return { date: resource ? resource.deleteAt : '' };
1133 })((props: { date: string }) => renderDate(props.date));
1134
1135 export const renderFileSize = (fileSize?: number) => (
1136     <Typography noWrap style={{ minWidth: '45px' }}>
1137 =======
1138     return { date: resource ? resource.deleteAt : "" };
1139 })((props: { date: string }) => renderDate(props.date));
1140
1141 export const renderFileSize = (fileSize?: number) => (
1142     <Typography
1143         noWrap
1144         style={{ minWidth: "45px" }}
1145     >
1146 >>>>>>> main
1147         {formatFileSize(fileSize)}
1148     </Typography>
1149 );
1150
1151 export const ResourceFileSize = connect((state: RootState, props: { uuid: string }) => {
1152     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
1153
1154     if (resource && resource.kind !== ResourceKind.COLLECTION) {
1155 <<<<<<< HEAD
1156         return { fileSize: '' };
1157 =======
1158         return { fileSize: "" };
1159 >>>>>>> main
1160     }
1161
1162     return { fileSize: resource ? resource.fileSizeTotal : 0 };
1163 })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
1164
1165 <<<<<<< HEAD
1166 const renderOwner = (owner: string) => <Typography noWrap>{owner || '-'}</Typography>;
1167
1168 export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
1169     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1170     return { owner: resource ? resource.ownerUuid : '' };
1171 =======
1172 const renderOwner = (owner: string) => <Typography noWrap>{owner || "-"}</Typography>;
1173
1174 export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
1175     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1176     return { owner: resource ? resource.ownerUuid : "" };
1177 >>>>>>> main
1178 })((props: { owner: string }) => renderOwner(props.owner));
1179
1180 export const ResourceOwnerName = connect((state: RootState, props: { uuid: string }) => {
1181     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1182     const ownerNameState = state.ownerName;
1183 <<<<<<< HEAD
1184     const ownerName = ownerNameState.find((it) => it.uuid === resource!.ownerUuid);
1185 =======
1186     const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
1187 >>>>>>> main
1188     return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
1189 })((props: { owner: string }) => renderOwner(props.owner));
1190
1191 export const ResourceUUID = connect((state: RootState, props: { uuid: string }) => {
1192     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
1193 <<<<<<< HEAD
1194     return { uuid: resource ? resource.uuid : '' };
1195 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
1196
1197 const renderVersion = (version: number) => {
1198     return <Typography>{version ?? '-'}</Typography>;
1199 =======
1200     return { uuid: resource ? resource.uuid : "" };
1201 })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
1202
1203 const renderVersion = (version: number) => {
1204     return <Typography>{version ?? "-"}</Typography>;
1205 >>>>>>> main
1206 };
1207
1208 export const ResourceVersion = connect((state: RootState, props: { uuid: string }) => {
1209     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
1210 <<<<<<< HEAD
1211     return { version: resource ? resource.version : '' };
1212 =======
1213     return { version: resource ? resource.version : "" };
1214 >>>>>>> main
1215 })((props: { version: number }) => renderVersion(props.version));
1216
1217 const renderPortableDataHash = (portableDataHash: string | null) => (
1218     <Typography noWrap>
1219         {portableDataHash ? (
1220             <>
1221                 {portableDataHash}
1222                 <CopyToClipboardSnackbar value={portableDataHash} />
1223             </>
1224         ) : (
1225 <<<<<<< HEAD
1226             '-'
1227 =======
1228             "-"
1229 >>>>>>> main
1230         )}
1231     </Typography>
1232 );
1233
1234 export const ResourcePortableDataHash = connect((state: RootState, props: { uuid: string }) => {
1235     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
1236 <<<<<<< HEAD
1237     return { portableDataHash: resource ? resource.portableDataHash : '' };
1238 })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
1239
1240 const renderFileCount = (fileCount: number) => {
1241     return <Typography>{fileCount ?? '-'}</Typography>;
1242 =======
1243     return { portableDataHash: resource ? resource.portableDataHash : "" };
1244 })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
1245
1246 const renderFileCount = (fileCount: number) => {
1247     return <Typography>{fileCount ?? "-"}</Typography>;
1248 >>>>>>> main
1249 };
1250
1251 export const ResourceFileCount = connect((state: RootState, props: { uuid: string }) => {
1252     const resource = getResource<CollectionResource>(props.uuid)(state.resources);
1253 <<<<<<< HEAD
1254     return { fileCount: resource ? resource.fileCount : '' };
1255 })((props: { fileCount: number }) => renderFileCount(props.fileCount));
1256
1257 const userFromID = connect((state: RootState, props: { uuid: string }) => {
1258     let userFullname = '';
1259 =======
1260     return { fileCount: resource ? resource.fileCount : "" };
1261 })((props: { fileCount: number }) => renderFileCount(props.fileCount));
1262
1263 const userFromID = connect((state: RootState, props: { uuid: string }) => {
1264     let userFullname = "";
1265 >>>>>>> main
1266     const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
1267
1268     if (resource) {
1269         userFullname = getUserFullname(resource as User) || (resource as GroupContentsResource).name;
1270     }
1271
1272     return { uuid: props.uuid, userFullname };
1273 });
1274
1275 const ownerFromResourceId = compose(
1276     connect((state: RootState, props: { uuid: string }) => {
1277         const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
1278 <<<<<<< HEAD
1279         return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
1280 =======
1281         return { uuid: childResource ? (childResource as Resource).ownerUuid : "" };
1282 >>>>>>> main
1283     }),
1284     userFromID
1285 );
1286
1287 const _resourceWithName = withStyles(
1288     {},
1289     { withTheme: true }
1290 )((props: { uuid: string; userFullname: string; dispatch: Dispatch; theme: ArvadosTheme }) => {
1291     const { uuid, userFullname, dispatch, theme } = props;
1292 <<<<<<< HEAD
1293     if (userFullname === '') {
1294         dispatch<any>(loadResource(uuid, false));
1295         return (
1296             <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
1297 =======
1298     if (userFullname === "") {
1299         dispatch<any>(loadResource(uuid, false));
1300         return (
1301             <Typography
1302                 style={{ color: theme.palette.primary.main }}
1303                 inline
1304                 noWrap
1305             >
1306 >>>>>>> main
1307                 {uuid}
1308             </Typography>
1309         );
1310     }
1311
1312     return (
1313 <<<<<<< HEAD
1314         <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
1315 =======
1316         <Typography
1317             style={{ color: theme.palette.primary.main }}
1318             inline
1319             noWrap
1320         >
1321 >>>>>>> main
1322             {userFullname} ({uuid})
1323         </Typography>
1324     );
1325 });
1326
1327 export const ResourceOwnerWithName = ownerFromResourceId(_resourceWithName);
1328
1329 export const ResourceWithName = userFromID(_resourceWithName);
1330
1331 export const UserNameFromID = compose(userFromID)((props: { uuid: string; displayAsText?: string; userFullname: string; dispatch: Dispatch }) => {
1332     const { uuid, userFullname, dispatch } = props;
1333
1334 <<<<<<< HEAD
1335     if (userFullname === '') {
1336 =======
1337     if (userFullname === "") {
1338 >>>>>>> main
1339         dispatch<any>(loadResource(uuid, false));
1340     }
1341     return <span>{userFullname ? userFullname : uuid}</span>;
1342 });
1343
1344 export const ResponsiblePerson = compose(
1345     connect((state: RootState, props: { uuid: string; parentRef: HTMLElement | null }) => {
1346 <<<<<<< HEAD
1347         let responsiblePersonName: string = '';
1348         let responsiblePersonUUID: string = '';
1349         let responsiblePersonProperty: string = '';
1350 =======
1351         let responsiblePersonName: string = "";
1352         let responsiblePersonUUID: string = "";
1353         let responsiblePersonProperty: string = "";
1354 >>>>>>> main
1355
1356         if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
1357             let index = 0;
1358             const keys = Object.keys(state.auth.config.clusterConfig.Collections.ManagedProperties);
1359
1360             while (!responsiblePersonProperty && keys[index]) {
1361                 const key = keys[index];
1362 <<<<<<< HEAD
1363                 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
1364 =======
1365                 if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === "original_owner") {
1366 >>>>>>> main
1367                     responsiblePersonProperty = key;
1368                 }
1369                 index++;
1370             }
1371         }
1372
1373         let resource: Resource | undefined = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
1374
1375         while (resource && resource.kind !== ResourceKind.USER && responsiblePersonProperty) {
1376             responsiblePersonUUID = (resource as CollectionResource).properties[responsiblePersonProperty];
1377             resource = getResource<GroupContentsResource & UserResource>(responsiblePersonUUID)(state.resources);
1378         }
1379
1380         if (resource && resource.kind === ResourceKind.USER) {
1381             responsiblePersonName = getUserFullname(resource as UserResource) || (resource as GroupContentsResource).name;
1382         }
1383
1384         return { uuid: responsiblePersonUUID, responsiblePersonName, parentRef: props.parentRef };
1385     }),
1386     withStyles({}, { withTheme: true })
1387 )((props: { uuid: string | null; responsiblePersonName: string; parentRef: HTMLElement | null; theme: ArvadosTheme }) => {
1388     const { uuid, responsiblePersonName, parentRef, theme } = props;
1389
1390     if (!uuid && parentRef) {
1391 <<<<<<< HEAD
1392         parentRef.style.display = 'none';
1393         return null;
1394     } else if (parentRef) {
1395         parentRef.style.display = 'block';
1396 =======
1397         parentRef.style.display = "none";
1398         return null;
1399     } else if (parentRef) {
1400         parentRef.style.display = "block";
1401 >>>>>>> main
1402     }
1403
1404     if (!responsiblePersonName) {
1405         return (
1406 <<<<<<< HEAD
1407             <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
1408 =======
1409             <Typography
1410                 style={{ color: theme.palette.primary.main }}
1411                 inline
1412                 noWrap
1413             >
1414 >>>>>>> main
1415                 {uuid}
1416             </Typography>
1417         );
1418     }
1419
1420     return (
1421 <<<<<<< HEAD
1422         <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
1423 =======
1424         <Typography
1425             style={{ color: theme.palette.primary.main }}
1426             inline
1427             noWrap
1428         >
1429 >>>>>>> main
1430             {responsiblePersonName} ({uuid})
1431         </Typography>
1432     );
1433 });
1434
1435 const renderType = (type: string, subtype: string) => <Typography noWrap>{resourceLabel(type, subtype)}</Typography>;
1436
1437 export const ResourceType = connect((state: RootState, props: { uuid: string }) => {
1438     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
1439 <<<<<<< HEAD
1440     return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
1441 =======
1442     return { type: resource ? resource.kind : "", subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : "" };
1443 >>>>>>> main
1444 })((props: { type: string; subtype: string }) => renderType(props.type, props.subtype));
1445
1446 export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
1447     return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
1448 })((props: { resource: GroupContentsResource }) =>
1449 <<<<<<< HEAD
1450     props.resource && props.resource.kind === ResourceKind.COLLECTION ? <CollectionStatus uuid={props.resource.uuid} /> : <ProcessStatus uuid={props.resource.uuid} />
1451 =======
1452     props.resource && props.resource.kind === ResourceKind.COLLECTION ? (
1453         <CollectionStatus uuid={props.resource.uuid} />
1454     ) : (
1455         <ProcessStatus uuid={props.resource.uuid} />
1456     )
1457 >>>>>>> main
1458 );
1459
1460 export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
1461     return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
1462 })((props: { collection: CollectionResource }) =>
1463 <<<<<<< HEAD
1464     props.collection.uuid !== props.collection.currentVersionUuid ? <Typography>version {props.collection.version}</Typography> : <Typography>head version</Typography>
1465 =======
1466     props.collection.uuid !== props.collection.currentVersionUuid ? (
1467         <Typography>version {props.collection.version}</Typography>
1468     ) : (
1469         <Typography>head version</Typography>
1470     )
1471 >>>>>>> main
1472 );
1473
1474 export const CollectionName = connect((state: RootState, props: { uuid: string; className?: string }) => {
1475     return {
1476         collection: getResource<CollectionResource>(props.uuid)(state.resources),
1477         uuid: props.uuid,
1478         className: props.className,
1479     };
1480 })((props: { collection: CollectionResource; uuid: string; className?: string }) => (
1481     <Typography className={props.className}>{props.collection?.name || props.uuid}</Typography>
1482 ));
1483
1484 export const ProcessStatus = compose(
1485     connect((state: RootState, props: { uuid: string }) => {
1486         return { process: getProcess(props.uuid)(state.resources) };
1487     }),
1488     withStyles({}, { withTheme: true })
1489 )((props: { process?: Process; theme: ArvadosTheme }) =>
1490     props.process ? (
1491         <Chip
1492             label={getProcessStatus(props.process)}
1493             style={{
1494                 height: props.theme.spacing.unit * 3,
1495                 width: props.theme.spacing.unit * 12,
1496                 ...getProcessStatusStyles(getProcessStatus(props.process), props.theme),
1497 <<<<<<< HEAD
1498                 fontSize: '0.875rem',
1499 =======
1500                 fontSize: "0.875rem",
1501 >>>>>>> main
1502                 borderRadius: props.theme.spacing.unit * 0.625,
1503             }}
1504         />
1505     ) : (
1506         <Typography>-</Typography>
1507     )
1508 );
1509
1510 export const ProcessStartDate = connect((state: RootState, props: { uuid: string }) => {
1511     const process = getProcess(props.uuid)(state.resources);
1512 <<<<<<< HEAD
1513     return { date: process && process.container ? process.container.startedAt : '' };
1514 })((props: { date: string }) => renderDate(props.date));
1515
1516 export const renderRunTime = (time: number) => (
1517     <Typography noWrap style={{ minWidth: '45px' }}>
1518 =======
1519     return { date: process && process.container ? process.container.startedAt : "" };
1520 })((props: { date: string }) => renderDate(props.date));
1521
1522 export const renderRunTime = (time: number) => (
1523     <Typography
1524         noWrap
1525         style={{ minWidth: "45px" }}
1526     >
1527 >>>>>>> main
1528         {formatTime(time, true)}
1529     </Typography>
1530 );
1531
1532 interface ContainerRunTimeProps {
1533     process: Process;
1534 }
1535
1536 interface ContainerRunTimeState {
1537     runtime: number;
1538 }
1539
1540 export const ContainerRunTime = connect((state: RootState, props: { uuid: string }) => {
1541     return { process: getProcess(props.uuid)(state.resources) };
1542 })(
1543     class extends React.Component<ContainerRunTimeProps, ContainerRunTimeState> {
1544         private timer: any;
1545
1546         constructor(props: ContainerRunTimeProps) {
1547             super(props);
1548             this.state = { runtime: this.getRuntime() };
1549         }
1550
1551         getRuntime() {
1552             return this.props.process ? getProcessRuntime(this.props.process) : 0;
1553         }
1554
1555         updateRuntime() {
1556             this.setState({ runtime: this.getRuntime() });
1557         }
1558
1559         componentDidMount() {
1560             this.timer = setInterval(this.updateRuntime.bind(this), 5000);
1561         }
1562
1563         componentWillUnmount() {
1564             clearInterval(this.timer);
1565         }
1566
1567         render() {
1568             return this.props.process ? renderRunTime(this.state.runtime) : <Typography>-</Typography>;
1569         }
1570     }
1571 );