// SPDX-License-Identifier: AGPL-3.0
import React from 'react';
-import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
+import {
+ Grid,
+ Typography,
+ withStyles,
+ Tooltip,
+ IconButton,
+ Checkbox,
+ Chip
+} from '@material-ui/core';
import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
import { Resource, ResourceKind, TrashableResource } from 'models/resource';
-import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon, RemoveIcon, RenameIcon } from 'components/icon/icon';
+import {
+ FreezeIcon,
+ ProjectIcon,
+ FilterGroupIcon,
+ CollectionIcon,
+ ProcessIcon,
+ DefaultIcon,
+ ShareIcon,
+ CollectionOldVersionIcon,
+ WorkflowIcon,
+ RemoveIcon,
+ RenameIcon,
+ ActiveIcon,
+ SetupIcon,
+ InactiveIcon,
+} from 'components/icon/icon';
import { formatDate, formatFileSize, formatTime } from 'common/formatters';
import { resourceLabel } from 'common/labels';
import { connect, DispatchProp } from 'react-redux';
import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
-import { toggleIsActive, toggleIsAdmin } from 'store/users/users-actions';
+import { toggleIsAdmin } from 'store/users/users-actions';
import { LinkClass, LinkResource } from 'models/link';
-import { navigateTo, navigateToGroupDetails } from 'store/navigation/navigation-action';
+import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
import { withResourceData } from 'views-components/data-explorer/with-resources';
import { CollectionResource } from 'models/collection';
import { IllegalNamingWarning } from 'components/warning/warning';
import { loadResource } from 'store/resources/resources-actions';
-import { GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
+import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
import { getUserUuid } from 'common/getuser';
import { VirtualMachinesResource } from 'models/virtual-machines';
+import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar';
+import { ProjectResource } from 'models/project';
+import {ContainerRequestResource} from 'models/container-request'
const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
<Typography variant="caption">
<FavoriteStar resourceUuid={item.uuid} />
<PublicFavoriteStar resourceUuid={item.uuid} />
+ {
+ item.kind === ResourceKind.PROJECT && <FrozenProject item={item} />
+ }
</Typography>
</Grid>
</Grid>;
};
+const FrozenProject = (props: {item: ProjectResource}) => {
+ const [fullUsername, setFullusername] = React.useState<any>(null);
+ const getFullName = React.useCallback(() => {
+ if (props.item.frozenByUuid) {
+ setFullusername(<UserNameFromID uuid={props.item.frozenByUuid} />);
+ }
+ }, [props.item, setFullusername])
+
+ if (props.item.frozenByUuid) {
+
+ return <Tooltip onOpen={getFullName} enterDelay={500} title={<span>Project was frozen by {fullUsername}</span>}>
+ <FreezeIcon style={{ fontSize: "inherit" }}/>
+ </Tooltip>;
+ } else {
+ return null;
+ }
+}
+
export const ResourceName = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
return resource || { lastName: '' };
})(renderLastName);
-const renderFullName = (item: { firstName: string, lastName: string }) =>
- <Typography noWrap>{(item.firstName + " " + item.lastName).trim()}</Typography>;
+const renderFullName = (dispatch: Dispatch, item: { uuid: string, firstName: string, lastName: string }, link?: boolean) => {
+ const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
+ return link ? <Typography noWrap
+ color="primary"
+ style={{ 'cursor': 'pointer' }}
+ onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
+ {displayName}
+ </Typography> :
+ <Typography noWrap>{displayName}</Typography>;
+}
-export const ResourceFullName = connect(
- (state: RootState, props: { uuid: string }) => {
+export const UserResourceFullName = connect(
+ (state: RootState, props: { uuid: string, link?: boolean }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
- return resource || { firstName: '', lastName: '' };
- })(renderFullName);
-
+ return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
+ })((props: { item: { uuid: string, firstName: string, lastName: string }, link?: boolean } & DispatchProp<any>) => renderFullName(props.dispatch, props.item, props.link));
const renderUuid = (item: { uuid: string }) =>
- <Typography data-cy="uuid" noWrap>{item.uuid}</Typography>;
+ <Typography data-cy="uuid" noWrap>
+ {item.uuid}
+ <CopyToClipboardSnackbar value={item.uuid} />
+ </Typography>;
-export const ResourceUuid = connect(
- (state: RootState, props: { uuid: string }) => {
- const resource = getResource<UserResource>(props.uuid)(state.resources);
- return resource || { uuid: '' };
- })(renderUuid);
+export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => (
+ getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' }
+))(renderUuid);
const renderEmail = (item: { email: string }) =>
<Typography noWrap>{item.email}</Typography>;
return resource || { email: '' };
})(renderEmail);
-const renderIsActive = (props: { uuid: string, kind: ResourceKind, isActive: boolean, toggleIsActive: (uuid: string) => void, disabled?: boolean }) => {
- if (props.kind === ResourceKind.USER) {
- return <Checkbox
- color="primary"
- checked={props.isActive}
- disabled={!!props.disabled}
- onClick={() => props.toggleIsActive(props.uuid)} />;
+enum UserAccountStatus {
+ ACTIVE = 'Active',
+ INACTIVE = 'Inactive',
+ SETUP = 'Setup',
+ UNKNOWN = ''
+}
+
+const renderAccountStatus = (props: { status: UserAccountStatus }) =>
+ <Grid container alignItems="center" wrap="nowrap" spacing={8} data-cy="account-status">
+ <Grid item>
+ {(() => {
+ switch (props.status) {
+ case UserAccountStatus.ACTIVE:
+ return <ActiveIcon style={{ color: '#4caf50', verticalAlign: "middle" }} />;
+ case UserAccountStatus.SETUP:
+ return <SetupIcon style={{ color: '#2196f3', verticalAlign: "middle" }} />;
+ case UserAccountStatus.INACTIVE:
+ return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: "middle" }} />;
+ default:
+ return <></>;
+ }
+ })()}
+ </Grid>
+ <Grid item>
+ <Typography noWrap>
+ {props.status}
+ </Typography>
+ </Grid>
+ </Grid>;
+
+const getUserAccountStatus = (state: RootState, props: { uuid: string }) => {
+ const user = getResource<UserResource>(props.uuid)(state.resources);
+ // Get membership links for all users group
+ const allUsersGroupUuid = getBuiltinGroupUuid(state.auth.localCluster, BuiltinGroups.ALL);
+ const permissions = filterResources((resource: LinkResource) =>
+ resource.kind === ResourceKind.LINK &&
+ resource.linkClass === LinkClass.PERMISSION &&
+ resource.headUuid === allUsersGroupUuid &&
+ resource.tailUuid === props.uuid
+ )(state.resources);
+
+ if (user) {
+ return user.isActive ? { status: UserAccountStatus.ACTIVE } : permissions.length > 0 ? { status: UserAccountStatus.SETUP } : { status: UserAccountStatus.INACTIVE };
} else {
- return <Typography />;
+ return { status: UserAccountStatus.UNKNOWN };
}
}
-export const ResourceIsActive = connect(
- (state: RootState, props: { uuid: string, disabled?: boolean }) => {
- const resource = getResource<UserResource>(props.uuid)(state.resources);
- return resource ? {...resource, disabled: !!props.disabled} : { isActive: false, kind: ResourceKind.NONE };
- }, { toggleIsActive }
-)(renderIsActive);
-
-export const ResourceLinkTailIsActive = connect(
- (state: RootState, props: { uuid: string, disabled?: boolean }) => {
+export const ResourceLinkTailAccountStatus = connect(
+ (state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
- const tailResource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
+ return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, { uuid: link.tailUuid }) : { status: UserAccountStatus.UNKNOWN };
+ })(renderAccountStatus);
- return tailResource ? {...tailResource, disabled: !!props.disabled} : { isActive: false, kind: ResourceKind.NONE };
- }, { toggleIsActive }
-)(renderIsActive);
+export const UserResourceAccountStatus = connect(getUserAccountStatus)(renderAccountStatus);
const renderIsHidden = (props: {
- memberLinkUuid: string,
- permissionLinkUuid: string,
- visible: boolean,
- canManage: boolean,
- setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void
- }) => {
+ memberLinkUuid: string,
+ permissionLinkUuid: string,
+ visible: boolean,
+ canManage: boolean,
+ setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void
+}) => {
if (props.memberLinkUuid) {
return <Checkbox
- data-cy="user-visible-checkbox"
- color="primary"
- checked={props.visible}
- disabled={!props.canManage}
- onClick={() => props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible)} />;
+ data-cy="user-visible-checkbox"
+ color="primary"
+ checked={props.visible}
+ disabled={!props.canManage}
+ onClick={(e) => {
+ e.stopPropagation();
+ props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
+ }} />;
} else {
return <Typography />;
}
<Checkbox
color="primary"
checked={props.isAdmin}
- onClick={() => props.toggleIsAdmin(props.uuid)} />;
+ onClick={(e) => {
+ e.stopPropagation();
+ props.toggleIsAdmin(props.uuid);
+ }} />;
export const ResourceIsAdmin = connect(
(state: RootState, props: { uuid: string }) => {
return resource || { hostname: '' };
})(renderHostname);
-const renderVirtualMachineLogin = (login: {user: string}) =>
+const renderVirtualMachineLogin = (login: { user: string }) =>
<Typography noWrap>{login.user}</Typography>
export const VirtualMachineLogin = connect(
const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
- return {user: user?.username || permission?.tailUuid || ''};
+ return { user: user?.username || permission?.tailUuid || '' };
})(renderVirtualMachineLogin);
// Common methods
const getResourceDisplayName = (resource: Resource): string => {
if ((resource as UserResource).kind === ResourceKind.USER
- && typeof (resource as UserResource).firstName !== 'undefined') {
+ && typeof (resource as UserResource).firstName !== 'undefined') {
// We can be sure the resource is UserResource
return getUserDisplayName(resource as UserResource);
} else {
</IconButton>
</Typography>;
} else {
- return <Typography noWrap></Typography>;
+ return <Typography noWrap></Typography>;
}
}
canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
};
})((props: { item: LinkResource, canManage: boolean } & DispatchProp<any>) =>
- renderLinkDelete(props.dispatch, props.item, props.canManage));
+ renderLinkDelete(props.dispatch, props.item, props.canManage));
export const ResourceLinkTailEmail = connect(
(state: RootState, props: { uuid: string }) => {
};
})((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
-export const ResourceLastModifiedDate = connect(
+const renderProcessState = (processState: string) => <Typography>{processState}</Typography>
+
+export const ResourceProcessState = connect(
(state: RootState, props: { uuid: string }) => {
- const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
- return { date: resource ? resource.modifiedAt : '' };
- })((props: { date: string }) => renderDate(props.date));
+ const resource = getResource<ContainerRequestResource>(props.uuid)(state.resources);
+ return { state: resource?.state ? resource.state: '' };
+ })((props: { state: string }) => renderProcessState(props.state));
export const ResourceCreatedAtDate = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
return { date: resource ? resource.createdAt : '' };
})((props: { date: string }) => renderDate(props.date));
+
+export const ResourceLastModifiedDate = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+ return { date: resource ? resource.modifiedAt : '' };
+ })((props: { date: string }) => renderDate(props.date));
export const ResourceTrashDate = connect(
(state: RootState, props: { uuid: string }) => {
return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
})((props: { owner: string }) => renderOwner(props.owner));
+export const ResourceUUID = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+ return { uuid: resource ? resource.uuid : '' };
+ })((props: { uuid: string }) => renderUuid({uuid: props.uuid}));
+
+const renderPortableDataHash = (portableDataHash:string | null) =>
+ <Typography noWrap>
+ {portableDataHash ? <>{portableDataHash}
+ <CopyToClipboardSnackbar value={portableDataHash} /></> : '-' }
+ </Typography>
+
+export const ResourcePortableDataHash = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+ // console.log('COLLECTION_RESOIRCE', resource)
+ return { portableDataHash: resource ? resource.portableDataHash : '' };
+ })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
+
+const renderVersion = (version: number) =>{
+ return <Typography>{version ?? '-'}</Typography>
+}
+
+export const ResourceVersion = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+ return { version: resource ? resource.version: '' };
+ })((props: { version: number }) => renderVersion(props.version));
+
+const renderDescription = (description: string)=>{
+ const truncatedDescription = description ? description.slice(0, 18) + '...' : '-'
+ return <Typography title={description}>{truncatedDescription}</Typography>;
+}
+
+const renderFileCount = (fileCount: number) =>{
+ return <Typography>{fileCount ?? '-'}</Typography>
+}
+
+export const ResourceFileCount = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+ return { fileCount: resource ? resource.fileCount: '' };
+ })((props: { fileCount: number }) => renderFileCount(props.fileCount));
+
+export const ResourceDescription = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+ //testing---------------
+ const containerRequestDescription = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+ if (resource && !resource.description && resource.kind === ResourceKind.PROCESS) resource.description = containerRequestDescription
+ //testing---------------
+ return { description: resource ? resource.description : '' };
+ })((props: { description: string }) => renderDescription(props.description));
+
const userFromID =
connect(
(state: RootState, props: { uuid: string }) => {
return { uuid: props.uuid, userFullname };
});
-export const ResourceOwnerWithName =
+const ownerFromResourceId =
compose(
- userFromID,
- withStyles({}, { withTheme: true }))
+ connect((state: RootState, props: { uuid: string }) => {
+ const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
+ return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
+ }),
+ userFromID
+ );
+
+const _resourceWithName =
+ withStyles({}, { withTheme: true })
((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => {
const { uuid, userFullname, dispatch, theme } = props;
</Typography>;
});
+export const ResourceOwnerWithName = ownerFromResourceId(_resourceWithName);
+
+export const ResourceWithName = userFromID(_resourceWithName);
+
export const UserNameFromID =
compose(userFromID)(
- (props: { uuid: string, userFullname: string, dispatch: Dispatch }) => {
+ (props: { uuid: string, displayAsText?: string, userFullname: string, dispatch: Dispatch }) => {
const { uuid, userFullname, dispatch } = props;
if (userFullname === '') {
return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
{responsiblePersonName} ({uuid})
- </Typography>;
+ </Typography>;
});
const renderType = (type: string, subtype: string) =>
: <Typography>head version</Typography>
);
+export const CollectionName = connect((state: RootState, props: { uuid: string, className?: string }) => {
+ return {
+ collection: getResource<CollectionResource>(props.uuid)(state.resources),
+ uuid: props.uuid,
+ className: props.className,
+ };
+})((props: { collection: CollectionResource, uuid: string, className?: string }) =>
+ <Typography className={props.className}>{props.collection?.name || props.uuid}</Typography>
+);
+
export const ProcessStatus = compose(
connect((state: RootState, props: { uuid: string }) => {
return { process: getProcess(props.uuid)(state.resources) };
}),
withStyles({}, { withTheme: true }))
- ((props: { process?: Process, theme: ArvadosTheme }) => {
- const status = props.process ? getProcessStatus(props.process) : "-";
- return <Typography
- noWrap
- style={{ color: getProcessStatusColor(status, props.theme) }} >
- {status}
- </Typography>;
- });
+ ((props: { process?: Process, theme: ArvadosTheme }) =>
+ props.process
+ ? <Chip label={getProcessStatus(props.process)}
+ style={{
+ height: props.theme.spacing.unit * 3,
+ width: props.theme.spacing.unit * 12,
+ backgroundColor: getProcessStatusColor(
+ getProcessStatus(props.process), props.theme),
+ color: props.theme.palette.common.white,
+ fontSize: '0.875rem',
+ borderRadius: props.theme.spacing.unit * 0.625,
+ }}
+ />
+ : <Typography>-</Typography>
+ );
export const ProcessStartDate = connect(
(state: RootState, props: { uuid: string }) => {