Skip to content
This repository was archived by the owner on Mar 13, 2023. It is now read-only.

[types] Add global types from ParallelCluster and weave through code. #244

Merged
merged 7 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/locales/en/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@
"copyAmiId": "AMI Id copied to clipboard."
},
"components": {
"ClusterFailedHelp": {
"errorMessage": "Stack failed to create, see <0>CloudFormation Stack Events</0> &nbsp; or &nbsp;<1>Cluster Logs</1>&nbsp; to see why."
},
"ValidationErrors": {
"update": "Update",
"validation": "Validation",
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/components/DeleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ export function DeleteDialog({
<Box float="right">
<SpaceBetween direction="horizontal" size="xs">
<Button onClick={cancel}>Cancel</Button>
{/* @ts-expect-error TS(2322) FIXME: Type '{ children: string; onClick: any; autoFocus:... Remove this comment to see the full error message */}
<Button onClick={deleteCallback} autoFocus>Delete!</Button>
<Button onClick={deleteCallback}>Delete!</Button>
</SpaceBetween>
</Box>
}
Expand Down
158 changes: 126 additions & 32 deletions frontend/src/components/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
// 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 React from 'react';
import { CloudFormationResourceStatus } from '../types/base'
import { ClusterStatus, ClusterDescription, ClusterInfoSummary, ComputeFleetStatus } from '../types/clusters'
import { InstanceState, Instance, EC2Instance } from '../types/instances'
import { StackEvent } from '../types/stackevents'
import { JobStateCode } from '../types/jobs'
import React, { useCallback } from 'react';
import { Trans } from 'react-i18next';
import { Link as InternalLink } from "react-router-dom"
import { useNavigate } from "react-router-dom"

Expand All @@ -18,9 +24,8 @@ import { useState } from '../store'

export type StatusMap = Record<string, StatusIndicatorProps.Type>

function ClusterFailedHelp({
clusterName
}: any) {
function ClusterFailedHelp({cluster}: {cluster: ClusterInfoSummary | ClusterDescription}) {
const {clusterName} = cluster;
let navigate = useNavigate();

const clusterPath = ['clusters', 'index', clusterName];
Expand All @@ -30,47 +35,136 @@ function ClusterFailedHelp({
if(headNode)
href += `?instance=${headNode.instanceId}`

const navigateLogs = useCallback((e) => {navigate(href); e.preventDefault()},[href])

return <HelpTooltip>
Stack failed to create, see <InternalLink to={cfnHref}>CloudFormation Stack Events</InternalLink>
&nbsp; or &nbsp;
<Link onFollow={(e) => {navigate(href); e.preventDefault()}}>Cluster Logs</Link>
&nbsp; to see why.
<Trans i18nKey="components.ClusterFailedHelp.errorMessage" >
<InternalLink to={cfnHref}></InternalLink>
<Link onFollow={navigateLogs}></Link>
</Trans>
</HelpTooltip>
}

export default function Status({
status,
cluster,
statusMapOverrides
}: {status: string, cluster?: any, statusMapOverrides?: StatusMap}) {
const failedStatuses = new Set(['CREATE_FAILED', 'DELETE_FAILED', 'UPDATE_FAILED']);
function ClusterStatusIndicator({cluster}: {cluster: ClusterInfoSummary | ClusterDescription}) {
const {clusterStatus}: {clusterStatus: ClusterStatus} = cluster;
const failedStatuses = new Set<ClusterStatus>([
ClusterStatus.CreateFailed,
ClusterStatus.DeleteFailed,
ClusterStatus.UpdateFailed,
]);

const defaultStatusMap: StatusMap = {'CREATE_IN_PROGRESS': 'in-progress',
const statusMap: Record<ClusterStatus, StatusIndicatorProps.Type> = {
'CREATE_COMPLETE': 'success',
'CREATE_FAILED': 'error',
'CANCELLED': 'error',
'CONFIGURING': 'in-progress',
'COMPLETING': 'in-progress',
'COMPLETED': 'success',
'DELETE_IN_PROGRESS': 'in-progress',
'CREATE_IN_PROGRESS': 'in-progress',
'DELETE_FAILED': 'error',
'FAILED': 'error',
'RUNNING': 'success',
'STOPPED': 'error',
'SUCCESS': 'success',
'STOP_REQUESTED': 'in-progress',
'DELETE_IN_PROGRESS': 'in-progress',
'DELETE_COMPLETE': 'error',
'UPDATE_COMPLETE': 'success',
'UPDATE_FAILED': 'error',
'UPDATE_IN_PROGRESS': 'in-progress',
'UNKNOWN': 'error',
'UPDATE_COMPLETE': 'success'};
};

const statusMap: StatusMap = {...defaultStatusMap, ...(statusMapOverrides || {})}
return <StatusIndicator type={statusMap[clusterStatus]}>
{clusterStatus.replaceAll("_", " ")}
{failedStatuses.has(clusterStatus) && <ClusterFailedHelp cluster={cluster} /> }
</StatusIndicator>
}

if(!(status in statusMap))
return <span>{status ? status.replaceAll("_", " ") : "<unknown>"}</span>
function JobStatusIndicator({status}: {status: JobStateCode})
{
const statusMap: Record<JobStateCode, StatusIndicatorProps.Type> = {
"BOOT_FAIL": 'error',
"CANCELLED": 'error',
"COMPLETED": 'success',
"COMPLETING": 'in-progress',
"CONFIGURING": 'loading',
"DEADLINE": 'info',
"FAILED": 'error',
"NODE_FAIL": 'error',
"OUT_OF_MEMORY": 'error',
"PENDING": 'pending',
"PREEMPTED": 'info',
"REQUEUED": 'info',
"REQUEUE_FED": 'info',
"REQUEUE_HOLD": 'info',
"RESIZING": 'info',
"RESV_DEL_HOLD": 'info',
"REVOKED": 'info',
"RUNNING": 'success',
"SIGNALING": 'info',
"SPECIAL_EXIT": 'info',
"STAGE_OUT": 'info',
"STOPPED": 'stopped',
"SUSPENDED": 'stopped',
"TIMEOUT": 'error',
};

return <StatusIndicator type={statusMap[status]}>
{status ? status.replaceAll("_", " ") : "<unknown>"}
{ cluster && failedStatuses.has(status) && <ClusterFailedHelp clusterName={cluster.clusterName} /> }
{status.replaceAll("_", " ")}
</StatusIndicator>
}

function ComputeFleetStatusIndicator({status}: {status: ComputeFleetStatus})
{
const statusMap: Record<ComputeFleetStatus, StatusIndicatorProps.Type> = {
"START_REQUESTED": 'loading',
"STARTING": 'pending',
"RUNNING": 'success',
"PROTECTED": 'stopped',
"STOP_REQUESTED": 'loading',
"STOPPING": 'stopped',
"STOPPED": 'stopped',
"UNKNOWN": 'info',
"ENABLED": 'success',
"DISABLED": 'stopped',
};

return <StatusIndicator type={statusMap[status]}>
{status.replaceAll("_", " ")}
</StatusIndicator>
}

function InstanceStatusIndicator({instance}: {instance: EC2Instance | Instance})
{
const statusMap: Record<InstanceState, StatusIndicatorProps.Type> = {
"pending": 'pending',
"running": 'success',
"shutting-down": 'loading',
"stopped": 'stopped',
"stopping": 'pending',
"terminated": 'stopped',
};

return <StatusIndicator type={statusMap[instance.state]}>
{instance.state.replaceAll("-", " ").toUpperCase()}
</StatusIndicator>
}

function StackEventStatusIndicator({stackEvent, children}: {stackEvent: StackEvent, children?: React.ReactNode})
{
const statusMap: Record<CloudFormationResourceStatus, StatusIndicatorProps.Type> = {
'CREATE_COMPLETE': 'success',
'CREATE_FAILED': 'error',
'CREATE_IN_PROGRESS': 'in-progress',
'DELETE_COMPLETE': 'success',
'DELETE_FAILED': 'error',
'DELETE_IN_PROGRESS': 'error',
'DELETE_SKIPPED': 'error',
'IMPORT_COMPLETE': 'success',
'IMPORT_FAILED': 'error',
'IMPORT_IN_PROGRESS': 'in-progress',
'IMPORT_ROLLBACK_COMPLETE': 'success',
'IMPORT_ROLLBACK_FAILED': 'error',
'IMPORT_ROLLBACK_IN_PROGRESS': 'in-progress',
'UPDATE_COMPLETE': 'success',
'UPDATE_FAILED': 'error',
'UPDATE_IN_PROGRESS': 'info',
}
return <StatusIndicator type={statusMap[stackEvent.resourceStatus]}>
{stackEvent.resourceStatus.replaceAll("_", " ")}
{children}
</StatusIndicator>
}

export { ClusterStatusIndicator, ComputeFleetStatusIndicator, InstanceStatusIndicator, JobStatusIndicator, StackEventStatusIndicator }
2 changes: 1 addition & 1 deletion frontend/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function regions(selected: any) {
})
}

export default function Topbar(props: any) {
export default function Topbar() {
let username = useState(['identity', 'attributes', 'email']);
const queryClient = useQueryClient();

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// 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 { ClusterInfoSummary } from './types/clusters'
import axios from 'axios'
import { setState, getState, clearState, updateState, clearAllState } from './store'
import { USER_ROLES_CLAIM } from './auth/constants';
Expand Down Expand Up @@ -169,7 +170,7 @@ function DeleteCluster(clusterName: any, callback?: Callback) {
})
}

async function ListClusters() {
async function ListClusters(): Promise<ClusterInfoSummary[]> {
var url = 'api?path=/v3/clusters';
try {
const { data } = await request('get', url);
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/old-pages/Clusters/Accounting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// 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 { AccountingJobSummary } from '../../types/jobs'
import React from 'react';
import { useCollection } from '@awsui/collection-hooks';

Expand All @@ -18,7 +19,7 @@ import { SlurmAccounting } from '../../model'
import { clusterDefaultUser, getIn, findFirst } from '../../util'

// Components
import Status from "../../components/Status";
import { JobStatusIndicator } from "../../components/Status";
import EmptyState from '../../components/EmptyState';
import Loading from '../../components/Loading'
import HelpTooltip from '../../components/HelpTooltip'
Expand Down Expand Up @@ -228,7 +229,7 @@ function JobProperties({
<ValueWithLabel label="Time">{getIn(job, ['time', 'elapsed'])} s</ValueWithLabel>
</SpaceBetween>
<SpaceBetween direction="vertical" size="l">
<ValueWithLabel label="State">{<Status status={getIn(job, ['state', 'current'])} />}</ValueWithLabel>
<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>
Expand All @@ -237,7 +238,7 @@ function JobProperties({
<SpaceBetween direction="vertical" size="l">
<ValueWithLabel label="Queue">{job.partition}</ValueWithLabel>
<ValueWithLabel label="Return Code">{getIn(job, ['exit_code', 'return_code'])}</ValueWithLabel>
<ValueWithLabel label="Exit Status">{<Status status={getIn(job, ['exit_code', 'status'])} />}</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>
Expand Down Expand Up @@ -265,8 +266,7 @@ function JobModal() {
footer={
<Box float="right">
<SpaceBetween direction="horizontal" size="xs">
{/* @ts-expect-error TS(2322) FIXME: Type '{ children: string; onClick: () => void; aut... Remove this comment to see the full error message */}
<Button onClick={close} autoFocus>Close</Button>
<Button onClick={close}>Close</Button>
</SpaceBetween>
</Box>
}
Expand All @@ -288,7 +288,7 @@ export default function ClusterAccounting() {
const nodes = useState(['app', 'clusters', 'accounting', 'nodes']) || []
const user = useState(['app', 'clusters', 'accounting', 'user']) || ''
const jobName = useState(['app', 'clusters', 'accounting', 'jobName']) || []
const jobs = useState(['clusters', 'index', clusterName, 'accounting', 'jobs']);
const jobs: AccountingJobSummary[] = useState(['clusters', 'index', clusterName, 'accounting', 'jobs']);

React.useEffect(() => {
refreshAccounting({}, null, true);
Expand Down Expand Up @@ -446,35 +446,35 @@ export default function ClusterAccounting() {
</Container>

{jobs ? <SpaceBetween direction="vertical" size="s">
<Table {...collectionProps} trackBy={i => `${(i as any).job_id}-${(i as any).name}`} columnDefinitions={[
<Table {...collectionProps} trackBy={i => `${i.job_id}-${i.name}`} columnDefinitions={[
{
id: "id",
header: "ID",
cell: item => <Link onFollow={() => selectJob((item as any).job_id)}>{(item as any).job_id}</Link>,
cell: job => <Link onFollow={() => selectJob(job.job_id)}>{job.job_id}</Link>,
sortingField: "job_id"
},
{
id: "name",
header: "name",
cell: item => (item as any).name,
cell: job => job.name,
sortingField: "name"
},
{
id: "queue",
header: "queue",
cell: item => (item as any).partition,
cell: job => job.partition,
sortingField: "partition"
},
{
id: "user",
header: "user",
cell: item => (item as any).user,
cell: job => job.user,
sortingField: "user"
},
{
id: "state",
header: "state",
cell: item => <Status status={getIn(item, ['state', 'current'])}/>,
cell: job => <JobStatusIndicator status={getIn(job, ['state', 'current'])}/>,
sortingField: "job_state"
}
]} items={items} loadingText="Loading jobs..." pagination={<Pagination {...paginationProps}/>} filter={<TextFilter {...filterProps} countText={`Results: ${filteredItemsCount}`} filteringAriaLabel="Filter jobs"/>}/>
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/old-pages/Clusters/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// 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 { ClusterStatus } from '../../types/clusters'
import React from 'react';
import { useNavigate } from "react-router-dom"

Expand Down Expand Up @@ -65,7 +66,6 @@ export default function Actions () {
navigate('/configure');

GetConfiguration(clusterName, (configuration: any) => {
// @ts-expect-error TS(2554) FIXME: Expected 2 arguments, but got 1.
loadTemplate(jsyaml.load(configuration));
});
}
Expand Down Expand Up @@ -100,7 +100,7 @@ export default function Actions () {
</DeleteDialog>
<StopDialog clusterName={clusterName} />
<SpaceBetween direction="horizontal" size="xs">
<Button className="action" disabled={clusterStatus === 'CREATING' || clusterStatus === 'DELETE_IN_PROGRESS' || clusterStatus === 'CREATE_FAILED' || !isAdmin()} variant="normal" onClick={editConfiguration}>
<Button className="action" disabled={clusterStatus === ClusterStatus.CreateInProgress || clusterStatus === ClusterStatus.DeleteInProgress || clusterStatus === ClusterStatus.CreateFailed || !isAdmin()} variant="normal" onClick={editConfiguration}>
<div className="container">
<EditIcon /> {t("cluster.list.actions.edit")}
</div>
Expand All @@ -115,25 +115,25 @@ export default function Actions () {
<CancelIcon /> {t("cluster.list.actions.stop")}
</div>
</Button>}
<Button className="action" disabled={clusterStatus === 'DELETE_IN_PROGRESS' || !isAdmin()} onClick={() => {showDialog('deleteCluster')}}>
<Button className="action" disabled={clusterStatus === ClusterStatus.DeleteInProgress || !isAdmin()} onClick={() => {showDialog('deleteCluster')}}>
<div className="container">
<DeleteIcon /> {t("cluster.list.actions.delete")}
</div>
</Button>
{headNode && headNode.publicIpAddress && headNode.publicIpAddress !== "" && ssmEnabled &&
<Button className="action" disabled={clusterStatus === 'DELETE_IN_PROGRESS'} onClick={() => {ssmFilesystem(headNode.instanceId)}}>
<Button className="action" disabled={clusterStatus === ClusterStatus.DeleteInProgress} onClick={() => {ssmFilesystem(headNode.instanceId)}}>
<div className="container">
<FolderIcon /> {t("cluster.list.actions.filesystem")}
</div>
</Button>}
{headNode && headNode.publicIpAddress && headNode.publicIpAddress !== "" && ssmEnabled &&
<Button className="action" disabled={clusterStatus === 'DELETE_IN_PROGRESS'} onClick={() => {shellCluster(headNode.instanceId)}}>
<Button className="action" disabled={clusterStatus === ClusterStatus.DeleteInProgress} onClick={() => {shellCluster(headNode.instanceId)}}>
<div className="container">
<FeaturedPlayListIcon /> {t("cluster.list.actions.shell")}
</div>
</Button>}
{headNode && headNode.publicIpAddress && headNode.publicIpAddress !== "" && dcvEnabled &&
<Button className="action" disabled={clusterStatus === 'DELETE_IN_PROGRESS'} onClick={() => {dcvConnect(headNode)}}>
<Button className="action" disabled={clusterStatus === ClusterStatus.DeleteInProgress} onClick={() => {dcvConnect(headNode)}}>
<div className="container">
<MonitorIcon /> {t("cluster.list.actions.dcv")}
</div>
Expand Down
Loading