// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
// with the License. A copy of the License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
// limitations under the License.
import {JobSummary, Job} from '../../types/jobs'
import React from 'react'
import {
clearState,
consoleDomain,
getState,
setState,
ssmPolicy,
useState,
} from '../../store'
import {QueueStatus, CancelJob, JobInfo} from '../../model'
import {clusterDefaultUser, findFirst} from '../../util'
import {useCollection} from '@cloudscape-design/collection-hooks'
// UI Elements
import {
Button,
Box,
ColumnLayout,
Container,
Link,
Modal,
Pagination,
SpaceBetween,
Table,
TextFilter,
} from '@cloudscape-design/components'
// Components
import {JobStatusIndicator} from '../../components/Status'
import EmptyState from '../../components/EmptyState'
import Loading from '../../components/Loading'
import {useTranslation} from 'react-i18next'
// Key:Value pair (label / children)
const ValueWithLabel = ({
label,
children,
}: {
label: string
children?: React.ReactNode
}) => (
)
function refreshQueues(callback?: (arg: any) => void) {
const clusterName = getState(['app', 'clusters', 'selected'])
const region = getState(['aws', 'region'])
if (clusterName) {
const clusterPath = ['clusters', 'index', clusterName]
const cluster = getState(clusterPath)
let user = clusterDefaultUser(cluster)
const headNode = getState([...clusterPath, 'headNode'])
headNode &&
region &&
QueueStatus(clusterName, headNode.instanceId, user, callback)
}
}
function JobActions({
job,
disabled,
cancelCallback,
}: {
job: JobSummary
disabled: boolean
cancelCallback?: () => void
}) {
let pendingPath = [
'app',
'clusters',
'queue',
'action',
job.job_id,
'pending',
]
const pending = useState(pendingPath)
const cancelJob = (jobId: any, cancelCallback: any) => {
const clusterName = getState(['app', 'clusters', 'selected'])
const clusterPath = ['clusters', 'index', clusterName]
const cluster = getState(clusterPath)
let user = clusterDefaultUser(cluster)
const headNode = getState([...clusterPath, 'headNode'])
setState(pendingPath, true)
CancelJob(headNode.instanceId, user, jobId, () =>
refreshQueues(() => {
clearState(pendingPath)
cancelCallback && cancelCallback()
}),
)
}
return (
{job.job_state !== 'COMPLETED' && job.job_state !== 'CANCELLED' && (
{
cancelJob(job.job_id, cancelCallback)
}}
>
Stop Job
)}
)
}
function FileLink({path, isFile}: {path: string; isFile?: boolean}) {
const clusterName = useState(['app', 'clusters', 'selected'])
const clusterPath = ['clusters', 'index', clusterName]
const defaultRegion = useState(['aws', 'region'])
const region = useState(['app', 'selectedRegion']) || defaultRegion
const headNode = useState([...clusterPath, 'headNode'])
const linkPath = isFile ? path.slice(0, path.lastIndexOf('/')) : path
return (
{path}
)
}
function JobProperties({job}: {job: Job}) {
return (
{job.JobId}
{job.JobName}
{job.Partition}
{job.UserId}
{job.GroupId}
{job.Priority}
{job.Account}
{job.Reason}
{job.Requeue}
{job.NodeList}
{job.Restarts}
{job.Reboot}
{job.ExitCode}
{job.RunTime}
{job.TimeLimit}
{job.SubmitTime}
{job.BatchHost}
{job.EndTime}
{job.NumNodes}
{job.NumCPUs}
{job.NumTasks}
{job['CPUs/Task']}
{job.TRES}
{job.Command}
)
}
function JobModal() {
const clusterName = useState(['app', 'clusters', 'selected'])
const clusterPath = ['clusters', 'index', clusterName]
const fleetStatus = useState([...clusterPath, 'computeFleetStatus'])
const open = useState(['app', 'clusters', 'jobInfo', 'dialog'])
const job: Job = useState(['app', 'clusters', 'jobInfo', 'data'])
const close = () => {
setState(['app', 'clusters', 'jobInfo', 'dialog'], false)
}
return (
{job && (
)}
Close
}
header={`Job Info: ${job ? job.JobName : ''}`}
>
{job ? (
) : (
)}
)
}
export default function ClusterScheduling() {
const {t} = useTranslation()
const clusterName = useState(['app', 'clusters', 'selected'])
const clusterPath = ['clusters', 'index', clusterName]
const cluster = useState(clusterPath)
const fleetStatus = useState([...clusterPath, 'computeFleetStatus'])
const clusterMinor = cluster.version
? parseInt(cluster.version.split('.')[1])
: 0
const jobs: JobSummary[] = useState([
'clusters',
'index',
clusterName,
'jobs',
])
const defaultRegion = useState(['aws', 'region'])
const region = useState(['app', 'selectedRegion']) || defaultRegion
function isSsmPolicy(p: any) {
return p.hasOwnProperty('Policy') && p.Policy === ssmPolicy(region)
}
const iamPolicies = useState([
...clusterPath,
'config',
'HeadNode',
'Iam',
'AdditionalIamPolicies',
])
const ssmEnabled = iamPolicies && findFirst(iamPolicies, isSsmPolicy)
React.useEffect(() => {
const tick = () => {
clusterMinor > 0 && ssmEnabled && refreshQueues()
}
clusterMinor > 0 && ssmEnabled && refreshQueues()
const timerId = setInterval(tick, 10000)
return () => {
clearInterval(timerId)
}
}, [clusterMinor, ssmEnabled])
const selectJobCallback = (jobInfo: any) => {
setState(['app', 'clusters', 'jobInfo', 'data'], jobInfo)
}
const selectJob = (jobId: string) => {
const clusterName = getState(['app', 'clusters', 'selected'])
if (clusterName) {
const clusterPath = ['clusters', 'index', clusterName]
const cluster = getState(clusterPath)
let user = clusterDefaultUser(cluster)
const headNode = getState([...clusterPath, 'headNode'])
clearState(['app', 'clusters', 'jobInfo', 'data'])
headNode && setState(['app', 'clusters', 'jobInfo', 'dialog'], true)
headNode && JobInfo(headNode.instanceId, user, jobId, selectJobCallback)
}
}
const {
items,
actions,
filteredItemsCount,
collectionProps,
filterProps,
paginationProps,
} = useCollection(jobs || [], {
filtering: {
empty: ,
noMatch: (
actions.setFiltering('')}>
Clear filter
}
/>
),
},
pagination: {pageSize: 10},
sorting: {},
selection: {},
})
return (
{clusterMinor > 0 &&
ssmEnabled &&
(jobs ? (
(
selectJob(job.job_id)}>
{job.job_id}
),
sortingField: 'job_id',
},
{
id: 'name',
header: t('cluster.scheduling.name'),
cell: job => job.name,
sortingField: 'name',
},
{
id: 'partition',
header: t('cluster.scheduling.partition'),
cell: job => job.partition,
sortingField: 'partition',
},
{
id: 'nodes',
header: t('cluster.scheduling.nodes'),
cell: job => job.nodes,
sortingField: 'nodes',
},
{
id: 'state',
header: t('cluster.scheduling.state'),
cell: job => ,
sortingField: 'job_state',
},
{
id: 'time',
header: t('cluster.scheduling.runTime'),
cell: job => job.time,
sortingField: 'time',
},
{
id: 'actions',
header: t('cluster.scheduling.actions'),
cell: job => (
),
},
]}
items={items}
loadingText={t('cluster.scheduling.loadingJobs')}
pagination={ }
filter={
}
/>
) : (
))}
{clusterMinor === 0 && (
{t('cluster.scheduling.schedulingEnabled')}
)}
{!ssmEnabled && {t('cluster.scheduling.ssmEnabled')}
}
)
}