in frontend/src/old-pages/Clusters/Accounting.tsx [268:714]
function JobProperties({job}: any) {
console.log("Processing job properties for job", job)
return (
<Container>
<SpaceBetween direction="vertical" size="l">
<ColumnLayout columns={3} variant="text-grid">
<SpaceBetween direction="vertical" size="l">
<ValueWithLabel label="Job Id">{job.job_id}</ValueWithLabel>
<ValueWithLabel label="Cluster">{job.cluster}</ValueWithLabel>
<ValueWithLabel label="Group">{job.group}</ValueWithLabel>
<ValueWithLabel label="User">{job.user}</ValueWithLabel>
<ValueWithLabel label="Time">
{getIn(job, ['time', 'elapsed'])} s
</ValueWithLabel>
</SpaceBetween>
<SpaceBetween direction="vertical" size="l">
<ValueWithLabel label="State">
{<JobStatusIndicator status={getIn(job, ['state', 'current'])} />}
</ValueWithLabel>
<ValueWithLabel label="Name">{job.name}</ValueWithLabel>
<ValueWithLabel label="Nodes">{job.nodes}</ValueWithLabel>
<ValueWithLabel label="Account">{job.account}</ValueWithLabel>
<ValueWithLabel label="Nodes">
{job.allocation_nodes}
</ValueWithLabel>
</SpaceBetween>
<SpaceBetween direction="vertical" size="l">
<ValueWithLabel label="Queue">{job.partition}</ValueWithLabel>
<ValueWithLabel label="Return Code">
{getIn(job, ['exit_code', 'return_code', 'number'])}
</ValueWithLabel>
<ValueWithLabel label="Exit Status">
{
<JobStatusIndicator
status={getIn(job, ['exit_code', 'status'])}
/>
}
</ValueWithLabel>
{getIn(job, ['price_estimate']) && (
<ValueWithLabel label="Cost Estimate">
<CostEstimate job={job} />
</ValueWithLabel>
)}
</SpaceBetween>
</ColumnLayout>
<ValueWithLabel label="Steps">{<JobSteps job={job} />}</ValueWithLabel>
</SpaceBetween>
</Container>
)
}
function JobModal() {
const clusterName = useState(['app', 'clusters', 'selected'])
const open = useState([
'clusters',
'index',
clusterName,
'accounting',
'dialog',
])
const selectedJob = useState([
'clusters',
'index',
clusterName,
'accounting',
'selectedJob',
])
const job = useState([
'clusters',
'index',
clusterName,
'accounting',
'job',
selectedJob,
])
const close = () => {
setState(['clusters', 'index', clusterName, 'accounting', 'dialog'], false)
}
return (
<Modal
onDismiss={close}
visible={open}
closeAriaLabel="Close modal"
size="large"
footer={
<Box float="right">
<SpaceBetween direction="horizontal" size="xs">
<Button onClick={close}>Close</Button>
</SpaceBetween>
</Box>
}
header={`Job Info: ${job ? job.name : ''}`}
>
{job && <JobProperties job={job} />}
{!job && <div>Loading...</div>}
</Modal>
)
}
export default function ClusterAccounting() {
const {t} = useTranslation()
const clusterName = useState(['app', 'clusters', 'selected'])
//const accounting = useState(['clusters', 'index', clusterName, 'accounting']);
//const errors = useState(['clusters', 'index', clusterName, 'accounting', 'errors']) || [];
const pending = useState(['app', 'clusters', 'accounting', 'pending'])
//const startTime = useState(['app', 'clusters', 'accounting', 'startTime']) || '';
//const endTime = useState(['app', 'clusters', 'accounting', 'endTime']) || '';
const nodes = useState(['app', 'clusters', 'accounting', 'nodes']) || []
const user = useState(['app', 'clusters', 'accounting', 'user']) || ''
const jobName = useState(['app', 'clusters', 'accounting', 'jobName']) || []
const jobs: AccountingJobSummary[] = useState([
'clusters',
'index',
clusterName,
'accounting',
'jobs',
]) || []
React.useEffect(() => {
refreshAccounting({}, null, true)
}, [])
const setDateRange = (val: any) => {
if (!val) {
clearState(['app', 'clusters', 'accounting', 'startTime'])
clearState(['app', 'clusters', 'accounting', 'endTime'])
} else {
if (val.type === 'relative') {
if (val.unit === 'month') {
setState(
['app', 'clusters', 'accounting', 'startTime'],
`now-${val.amount * 4}weeks`,
)
setState(['app', 'clusters', 'accounting', 'endTime'], 'now')
} else if (val.unit === 'year') {
setState(
['app', 'clusters', 'accounting', 'startTime'],
`now-${val.amount * 52}weeks`,
)
setState(['app', 'clusters', 'accounting', 'endTime'], 'now')
} else {
setState(
['app', 'clusters', 'accounting', 'startTime'],
`now-${val.amount}${val.unit}s`,
)
setState(['app', 'clusters', 'accounting', 'endTime'], 'now')
}
} else {
const start = new Date(val.startDate)
const end = new Date(val.endDate)
setState(
['app', 'clusters', 'accounting', 'startTime'],
start.toISOString().substring(0, 19),
)
setState(
['app', 'clusters', 'accounting', 'endTime'],
end.toISOString().substring(0, 19),
)
}
}
refreshAccounting({}, null, true)
}
const {
items,
actions,
filteredItemsCount,
collectionProps,
filterProps,
paginationProps,
} = useCollection(
jobs || [],
extendCollectionsOptions({
filtering: {
empty: (
<EmptyState
title={t('cluster.accounting.filter.empty.title')}
subtitle={t('cluster.accounting.filter.empty.subtitle')}
/>
),
noMatch: (
<EmptyState
title="No matches"
subtitle="No jobs match the filters."
action={
<Button onClick={() => actions.setFiltering('')}>
Clear filter
</Button>
}
/>
),
},
sorting: {
defaultState: {
sortingColumn: {
sortingField: 'job_id',
},
},
},
selection: {},
}),
)
const selectJob = (job_id: any) => {
setState(['clusters', 'index', clusterName, 'accounting', 'dialog'], true)
clearState(['clusters', 'index', clusterName, 'accounting', 'selectedJob'])
clearState(['clusters', 'index', clusterName, 'accounting', 'job', job_id])
refreshAccounting(
{jobs: job_id},
(ret: any) => {
setState(
['clusters', 'index', clusterName, 'accounting', 'selectedJob'],
job_id,
)
setState(
['clusters', 'index', clusterName, 'accounting', 'job', job_id],
ret.jobs[0],
)
},
false,
)
}
const [dateValue, setDateValue] = React.useState(undefined)
return (
<>
<JobModal />
<SpaceBetween direction="vertical" size="s">
<Container
header={
<Header
variant="h2"
actions={
<Button
loading={pending}
onClick={() => refreshAccounting({}, null, true)}
>
Refresh
</Button>
}
>
Filters
</Header>
}
>
<SpaceBetween direction="horizontal" size="s">
<FormField label="Time range filter">
<div
onKeyPress={e =>
e.key === 'Enter' && refreshAccounting({}, null, true)
}
>
<DateRangePicker
onChange={({detail}) => {
setDateRange(detail.value)
/* @ts-expect-error TS(2345) FIXME: Argument of type 'Value | null' is not assignable ... Remove this comment to see the full error message */
setDateValue(detail.value)
}}
/* @ts-expect-error FIXME: Argument of type 'Value | null' is not assignable ... Remove this comment to see the full error message */
value={dateValue}
relativeOptions={[
{
key: 'previous-5-minutes',
amount: 5,
unit: 'minute',
type: 'relative',
},
{
key: 'previous-30-minutes',
amount: 30,
unit: 'minute',
type: 'relative',
},
{
key: 'previous-1-hour',
amount: 1,
unit: 'hour',
type: 'relative',
},
{
key: 'previous-6-hours',
amount: 6,
unit: 'hour',
type: 'relative',
},
]}
i18nStrings={{
todayAriaLabel: 'Today',
nextMonthAriaLabel: 'Next month',
previousMonthAriaLabel: 'Previous month',
customRelativeRangeDurationLabel: 'Duration',
customRelativeRangeDurationPlaceholder: 'Enter duration',
customRelativeRangeOptionLabel: 'Custom range',
customRelativeRangeOptionDescription:
'Set a custom range in the past',
customRelativeRangeUnitLabel: 'Unit of time',
formatRelativeRange: e => {
const t = 1 === e.amount ? e.unit : `${e.unit}s`
return `Last ${e.amount} ${t}`
},
formatUnit: (e, t) => (1 === t ? e : `${e}s`),
dateTimeConstraintText:
'Range must be between 6 - 30 days. Use 24 hour format.',
relativeModeTitle: 'Relative range',
absoluteModeTitle: 'Absolute range',
relativeRangeSelectionHeading: 'Choose a range',
startDateLabel: 'Start date',
endDateLabel: 'End date',
startTimeLabel: 'Start time',
endTimeLabel: 'End time',
clearButtonLabel: 'Clear',
cancelButtonLabel: 'Cancel',
applyButtonLabel: 'Apply',
}}
placeholder="Filter by a date and time range"
/>
</div>
</FormField>
<FormField label="Queue">
<QueueSelect />
</FormField>
<FormField label="Job State">
<JobStateSelect />
</FormField>
<FormField label="User">
<div
onKeyPress={e =>
e.key === 'Enter' && refreshAccounting({}, null, true)
}
>
<Input
value={user}
placeholder="ec2-user"
onChange={({detail}) => {
setState(
['app', 'clusters', 'accounting', 'user'],
detail.value,
)
}}
/>
</div>
</FormField>
<FormField label="Nodes">
<div
onKeyPress={e =>
e.key === 'Enter' && refreshAccounting({}, null, true)
}
>
<Input
value={nodes}
placeholder="queue0-c5n-large-1"
onChange={({detail}) => {
setState(
['app', 'clusters', 'accounting', 'nodes'],
detail.value,
)
}}
/>
</div>
</FormField>
<FormField label="Job Name">
<div
onKeyPress={e =>
e.key === 'Enter' && refreshAccounting({}, null, true)
}
>
<Input
value={jobName}
placeholder="job0"
onChange={({detail}) => {
setState(
['app', 'clusters', 'accounting', 'jobName'],
detail.value,
)
}}
/>
</div>
</FormField>
</SpaceBetween>
</Container>
{jobs ? (
<SpaceBetween direction="vertical" size="s">
<Table
{...collectionProps}
trackBy={i => `${i.job_id}-${i.name}`}
columnDefinitions={[
{
id: 'id',
header: t('cluster.accounting.id'),
cell: job => (
<Link onFollow={() => selectJob(job.job_id)}>
{job.job_id}
</Link>
),
sortingField: 'job_id',
},
{
id: 'name',
header: t('cluster.accounting.name'),
cell: job => job.name,
sortingField: 'name',
},
{
id: 'queue',
header: t('cluster.accounting.queue'),
cell: job => job.partition,
sortingField: 'partition',
},
{
id: 'user',
header: t('cluster.accounting.user'),
cell: job => job.user,
sortingField: 'user',
},
{
id: 'state',
header: t('cluster.accounting.state'),
cell: job => (
<JobStatusIndicator
status={getIn(job, ['state', 'current'])}
/>
),
sortingField: 'job_state',
},
]}
items={items}
loadingText={t('cluster.accounting.loadingJobs')}
pagination={<Pagination {...paginationProps} />}
filter={
<TextFilter
{...filterProps}
countText={`Results: ${filteredItemsCount}`}
filteringAriaLabel={t('cluster.accounting.filterJobs')}
/>
}
/>
</SpaceBetween>
) : (
<div style={{textAlign: 'center', paddingTop: '40px'}}>
<Loading />
</div>
)}