// 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 }) => (
{label}
{children}
) 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' && (
)}
) } 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 && ( )} } 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')}
} ) }