import yellow from '@material-ui/core/colors/yellow';
import red from '@material-ui/core/colors/red';
-interface ArvadosThemeOptions extends ThemeOptions {
+export interface ArvadosThemeOptions extends ThemeOptions {
customs: any;
}
interface Colors {
green700: string;
yellow700: string;
+ red900: string;
+ blue500: string;
+ grey500: string;
}
const red900 = red["900"];
const grey900 = grey["900"];
const rocheBlue = '#06C';
-const themeOptions: ArvadosThemeOptions = {
+export const themeOptions: ArvadosThemeOptions = {
customs: {
colors: {
green700: green["700"],
- yellow700: yellow["700"]
+ yellow700: yellow["700"],
+ red900: red['900'],
+ blue500: blue['500'],
+ grey500,
}
},
overrides: {
return "";
};
+export const formatTime = (time: number) => {
+ const minutes = Math.floor(time / (1000 * 60) % 60).toFixed(0);
+ const hours = Math.floor(time / (1000 * 60 * 60)).toFixed(0);
+
+ return hours + "h " + minutes + "m";
+};
+
+export const getTimeDiff = (endTime: string, startTime: string) => {
+ return new Date(endTime).getTime() - new Date(startTime).getTime();
+};
+
export const formatProgress = (loaded: number, total: number) => {
const progress = loaded >= 0 && total > 0 ? loaded * 100 / total : 0;
return `${progress.toFixed(2)}%`;
import { ResourcesState, getResource } from '~/store/resources/resources';
import { filterResources } from '../resources/resources';
import { ResourceKind, Resource } from '~/models/resource';
+import { getTimeDiff } from '~/common/formatters';
+import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses-card';
+import { ArvadosTheme } from '~/common/custom-theme';
export interface Process {
containerRequest: ContainerRequestResource;
return [];
};
+export const getProcessRuntime = ({ container }: Process) =>
+ container
+ ? getTimeDiff(container.finishedAt || '', container.startedAt || '')
+ : 0;
+
+export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme) => {
+ switch (status) {
+ case SubprocessesStatus.COMPLETED:
+ return customs.colors.green700;
+ case SubprocessesStatus.CANCELED:
+ return customs.colors.red900;
+ case SubprocessesStatus.QUEUED:
+ return customs.colors.grey500;
+ case SubprocessesStatus.FAILED:
+ return customs.colors.red900;
+ case SubprocessesStatus.ACTIVE:
+ return customs.colors.blue500;
+ default:
+ return customs.colors.grey500;
+ }
+};
+
export const getProcessStatus = (process: Process) =>
process.container
? process.container.state
const isSubprocess = (containerUuid: string) => (resource: Resource) =>
resource.kind === ResourceKind.CONTAINER_REQUEST
&& (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;
+
import { ArvadosTheme } from '~/common/custom-theme';
import { MoreOptionsIcon, ProcessIcon } from '~/components/icon/icon';
import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
-import { Process } from '~/store/processes/process';
-import { getProcessStatus } from '../../store/processes/process';
+import { Process, getProcessStatusColor } from '~/store/processes/process';
+import { getProcessStatus } from '~/store/processes/process';
-type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'headerText' | 'link' | 'content' | 'title' | 'avatar';
+
+type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
card: {
color: theme.customs.colors.green700,
},
avatar: {
- alignSelf: 'flex-start'
+ alignSelf: 'flex-start',
+ paddingTop: theme.spacing.unit * 0.5
},
label: {
display: 'flex',
chip: {
height: theme.spacing.unit * 3,
width: theme.spacing.unit * 12,
- backgroundColor: theme.customs.colors.green700,
color: theme.palette.common.white,
fontSize: '0.875rem',
borderRadius: theme.spacing.unit * 0.625,
},
- headerText: {
- fontSize: '0.875rem',
- marginLeft: theme.spacing.unit * 3,
- },
content: {
'&:last-child': {
paddingBottom: theme.spacing.unit * 2,
}
},
title: {
- overflow: 'hidden'
+ overflow: 'hidden',
+ paddingTop: theme.spacing.unit * 0.5
}
});
type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules>;
-export const ProcessInformationCard = withStyles(styles)(
- ({ classes, process, onContextMenu }: ProcessInformationCardProps) =>
+export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
+ ({ classes, process, onContextMenu, theme }: ProcessInformationCardProps) =>
<Card className={classes.card}>
<CardHeader
classes={{
avatar={<ProcessIcon className={classes.iconHeader} />}
action={
<div>
- <Chip label={getProcessStatus(process)} className={classes.chip} />
+ <Chip label={getProcessStatus(process)}
+ className={classes.chip}
+ style={{ backgroundColor: getProcessStatusColor(getProcessStatus(process), theme as ArvadosTheme) }}/>
<IconButton
aria-label="More options"
onClick={event => onContextMenu(event)}>
</div>
}
title={
- <Tooltip title={process.containerRequest.name} placement="bottom-start">
+ <Tooltip title={process.containerRequest.name} placement="bottom-start" color='inherit'>
<Typography noWrap variant="title">
{process.containerRequest.name}
</Typography>
import { ProcessIcon } from '~/components/icon/icon';
import { Process } from '~/store/processes/process';
import { SubprocessesCard } from './subprocesses-card';
+import { ProcessSubprocesses } from '~/views/process-panel/process-subprocesses';
+import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses-card';
+
+type CssRules = 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
export interface ProcessPanelRootDataProps {
process?: Process;
+ subprocesses: Array<Process>;
}
export interface ProcessPanelRootActionProps {
onToggle={() => { return; }}
/>
</Grid>
+ <Grid item xs={12}>
+ <ProcessSubprocesses
+ subprocesses={props.subprocesses}
+ onContextMenu={props.onContextMenu} />
+ </Grid>
</Grid>
: <Grid container
alignItems='center'
- justify='center'>
+ justify='center'
+ style={{ minHeight: '100%' }}>
<DefaultView
icon={ProcessIcon}
messages={['Process not found']} />
import * as React from 'react';
import { RootState } from '~/store/store';
import { connect } from 'react-redux';
-import { getProcess } from '~/store/processes/process';
+import { getProcess, getSubprocesses } from '~/store/processes/process';
import { Dispatch } from 'redux';
import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions';
import { matchProcessRoute } from '~/routes/routes';
const match = matchProcessRoute(pathname);
const uuid = match ? match.params.id : '';
return {
- process: getProcess(uuid)(resources)
+ process: getProcess(uuid)(resources),
+ subprocesses: getSubprocesses(uuid)(resources)
};
};
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import {
+ StyleRulesCallback, WithStyles, withStyles, Card,
+ CardHeader, IconButton, CardContent, Typography, Tooltip
+} from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { MoreOptionsIcon } from '~/components/icon/icon';
+import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
+import { Process, getProcessStatus, getProcessRuntime } from '~/store/processes/process';
+import { formatTime } from '~/common/formatters';
+import { getProcessStatusColor } from '~/store/processes/process';
+
+export type CssRules = 'label' | 'value' | 'title' | 'content' | 'action' | 'options' | 'status' | 'rightSideHeader' | 'titleHeader'| 'header';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ label: {
+ fontSize: '0.875rem',
+ },
+ value: {
+ textTransform: 'none',
+ fontSize: '0.875rem',
+ },
+ title: {
+ overflow: 'hidden'
+ },
+ content: {
+ paddingTop: theme.spacing.unit * 0.5,
+ '&:last-child': {
+ paddingBottom: 0
+ }
+ },
+ action: {
+ marginTop: 0
+ },
+ options: {
+ width: theme.spacing.unit * 4,
+ height: theme.spacing.unit * 4,
+ color: theme.palette.common.white,
+ },
+ status: {
+ paddingTop: theme.spacing.unit * 0.5,
+ color: theme.palette.common.white,
+ },
+ rightSideHeader: {
+ display: 'flex'
+ },
+ titleHeader: {
+ color: theme.palette.common.white,
+ fontWeight: 600
+ },
+ header: {
+ paddingTop: 0,
+ paddingBottom: 0,
+ },
+});
+
+export enum SubprocessesStatus {
+ ACTIVE = 'Active',
+ COMPLETED = 'Completed',
+ QUEUED = 'Queued',
+ FAILED = 'Failed',
+ CANCELED = 'Canceled'
+}
+
+export interface SubprocessItemProps {
+ title: string;
+ status: string;
+ runtime?: string;
+}
+
+export interface ProcessSubprocessesCardDataProps {
+ onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+ subprocess: Process;
+}
+
+type ProcessSubprocessesCardProps = ProcessSubprocessesCardDataProps & WithStyles<CssRules>;
+
+export const ProcessSubprocessesCard = withStyles(styles, { withTheme: true })(
+ ({ classes, onContextMenu, subprocess, theme }: ProcessSubprocessesCardProps) => {
+ return <Card>
+ <CardHeader
+ className={classes.header}
+ style={{ backgroundColor: getProcessStatusColor(getProcessStatus(subprocess), theme as ArvadosTheme) }}
+ classes={{ content: classes.title, action: classes.action }}
+ action={
+ <div className={classes.rightSideHeader}>
+ <Typography noWrap variant="body2" className={classes.status}>
+ {getProcessStatus(subprocess)}
+ </Typography>
+ <IconButton
+ className={classes.options}
+ aria-label="More options"
+ onClick={onContextMenu}>
+ <MoreOptionsIcon />
+ </IconButton>
+ </div>
+ }
+ title={
+ <Tooltip title={subprocess.containerRequest.name}>
+ <Typography noWrap variant="body2" className={classes.titleHeader}>
+ {subprocess.containerRequest.name}
+ </Typography>
+ </Tooltip>
+ } />
+ <CardContent className={classes.content}>
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label="Runtime" value={formatTime(getProcessRuntime(subprocess))} />
+ </CardContent>
+ </Card>;
+ });
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Grid } from '@material-ui/core';
+import { ProcessSubprocessesCard } from '~/views/process-panel/process-subprocesses-card';
+import { Process } from '~/store/processes/process';
+
+export interface ProcessSubprocessesDataProps {
+ subprocesses: Array<Process>;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+}
+
+export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubprocessesDataProps) => {
+ return <Grid container spacing={16}>
+ {subprocesses.map(subprocess =>
+ <Grid item xs={2} key={subprocess.containerRequest.uuid}>
+ <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess}/>
+ </Grid>
+ )}
+ </Grid>;
+};