diff --git a/src/webportal/src/app/job/job-view/fabric/job-detail/components/task-role-container-list.jsx b/src/webportal/src/app/job/job-view/fabric/job-detail/components/task-role-container-list.jsx index 6679871667..47d26ecb24 100644 --- a/src/webportal/src/app/job/job-view/fabric/job-detail/components/task-role-container-list.jsx +++ b/src/webportal/src/app/job/job-view/fabric/job-detail/components/task-role-container-list.jsx @@ -25,6 +25,7 @@ import { } from '@uifabric/styling'; import c from 'classnames'; import { capitalize, isEmpty, isNil, flatten } from 'lodash'; +import { DateTime } from 'luxon'; import { CommandBarButton, PrimaryButton, @@ -41,6 +42,7 @@ import { } from 'office-ui-fabric-react/lib/DetailsList'; import PropTypes from 'prop-types'; import React from 'react'; +import yaml from 'js-yaml'; import localCss from './task-role-container-list.scss'; import t from '../../../../../components/tachyons.scss'; @@ -48,7 +50,7 @@ import t from '../../../../../components/tachyons.scss'; import Context from './context'; import Timer from './timer'; import { getContainerLog } from '../conn'; -import { parseGpuAttr } from '../util'; +import { parseGpuAttr, printDateTime } from '../util'; import config from '../../../../../config/webportal.config'; import MonacoPanel from '../../../../../components/monaco-panel'; import StatusBadge from '../../../../../components/status-badge'; @@ -321,29 +323,165 @@ export default class TaskRoleContainerList extends React.Component { } } - getColumns() { - const columns = [ + getColumns(showDebugInfo) { + const optionalColumns = [ { - key: 'number', - name: 'No.', + key: 'accountableRetries', + name: 'Accountable Retries', headerClassName: FontClassNames.medium, - minWidth: 50, - maxWidth: 50, + minWidth: 150, + maxWidth: 200, isResizable: true, onRender: (item, idx) => { return ( - !isNil(idx) && ( -
{idx}
- ) +
+ {item.accountableRetries} +
); }, }, { - key: 'name', - name: 'Container ID', + key: 'startTime', + name: 'Start Time', + headerClassName: FontClassNames.medium, + minWidth: 150, + maxWidth: 200, + isResizable: true, + onRender: item => { + return ( +
+ {printDateTime(DateTime.fromMillis(item.createdTime))} +
+ ); + }, + }, + { + key: 'currentAttemptLaunchedTime', + name: 'Current Attempt Launched Time', + headerClassName: FontClassNames.medium, + minWidth: 200, + maxWidth: 250, + isResizable: true, + onRender: item => { + return ( +
+ {printDateTime( + DateTime.fromMillis(item.currentAttemptLaunchedTime), + )} +
+ ); + }, + }, + { + key: 'currentAttemptCompletedTime', + name: 'Current Attempt Completion Time', + headerClassName: FontClassNames.medium, + minWidth: 250, + maxWidth: 250, + isResizable: true, + onRender: item => { + return ( +
+ {printDateTime( + DateTime.fromMillis(item.currentAttemptCompletedTime), + )} +
+ ); + }, + }, + { + key: 'completionTime', + name: 'Completion Time', + headerClassName: FontClassNames.medium, + minWidth: 150, + maxWidth: 200, + isResizable: true, + onRender: item => { + return ( +
+ {printDateTime(DateTime.fromMillis(item.completedTime))} +
+ ); + }, + }, + { + key: 'nodeName', + name: 'Node Name', headerClassName: FontClassNames.medium, minWidth: 100, - maxWidth: 500, + isResizable: true, + onRender: item => { + return ( +
+ {item.containerNodeName} +
+ ); + }, + }, + { + key: 'exitDiagonostic', + name: 'Exit Diagnostics', + headerClassName: FontClassNames.medium, + minWidth: 200, + isResizable: true, + onRender: item => { + return ( + { + const result = []; + // exit spec + const spec = item.containerExitSpec; + if (spec) { + // divider + result.push(Array.from({ length: 80 }, () => '-').join('')); + result.push(''); + // content + result.push('[Exit Spec]'); + result.push(''); + result.push(yaml.safeDump(spec)); + result.push(''); + } + + // diagnostics + const diag = item.containerExitDiagnostics; + if (diag) { + // divider + result.push(Array.from({ length: 80 }, () => '-').join('')); + result.push(''); + // content + result.push('[Exit Diagnostics]'); + result.push(''); + result.push(diag); + result.push(''); + } + + this.setState({ + monacoProps: { + language: 'text', + value: result.join('\n'), + options: { + wordWrap: 'off', + readOnly: true, + }, + }, + monacoTitle: `Task Exit Diagonostics`, + }); + }} + /> + ); + }, + }, + { + key: 'containerId', + name: 'Container ID', + headerClassName: FontClassNames.medium, + minWidth: 300, isResizable: true, onRender: item => { const id = item.containerId; @@ -356,12 +494,29 @@ export default class TaskRoleContainerList extends React.Component { ); }, }, + ]; + const defaultColumns = [ + { + key: 'number', + name: 'No.', + headerClassName: FontClassNames.medium, + minWidth: 30, + maxWidth: 50, + isResizable: true, + onRender: (item, idx) => { + return ( + !isNil(idx) && ( +
{idx}
+ ) + ); + }, + }, { key: 'ip', name: 'IP', className: FontClassNames.mediumPlus, headerClassName: FontClassNames.medium, - minWidth: 80, + minWidth: 90, maxWidth: 140, isResizable: true, fieldName: 'containerIp', @@ -391,8 +546,8 @@ export default class TaskRoleContainerList extends React.Component { name: 'Ports', className: FontClassNames.mediumPlus, headerClassName: FontClassNames.medium, - minWidth: 120, - maxWidth: 180, + minWidth: 150, + maxWidth: 300, isResizable: true, onRender: item => { const ports = item.containerPorts; @@ -429,7 +584,7 @@ export default class TaskRoleContainerList extends React.Component { name: 'GPUs', className: FontClassNames.mediumPlus, headerClassName: FontClassNames.medium, - minWidth: 40, + minWidth: 35, maxWidth: 60, isResizable: true, onRender: item => { @@ -486,17 +641,60 @@ export default class TaskRoleContainerList extends React.Component { name: 'Status', headerClassName: FontClassNames.medium, minWidth: 100, - maxWidth: 100, + maxWidth: 150, isResizable: true, onRender: item => , }, + { + key: 'retries', + name: 'Retries', + headerClassName: FontClassNames.medium, + minWidth: 50, + maxWidth: 100, + isResizable: true, + onRender: (item, idx) => { + return ( +
{item.retries}
+ ); + }, + }, + { + key: 'exitCode', + name: 'Exit Code', + headerClassName: FontClassNames.medium, + minWidth: 100, + maxWidth: 150, + isResizable: true, + onRender: item => { + return ( +
+ {item.containerExitCode} +
+ ); + }, + }, + { + key: 'exitType', + name: 'Exit Type', + headerClassName: FontClassNames.medium, + minWidth: 150, + maxWidth: 200, + isResizable: true, + onRender: item => { + return ( +
+ {item.containerExitSpec.type} +
+ ); + }, + }, { key: 'info', - name: 'Info', + name: 'Info & Logs', className: localCss.pa0I, headerClassName: FontClassNames.medium, minWidth: 300, - maxWidth: 340, + maxWidth: 500, onRender: item => (
{ this.showSshInfo( item.containerId, @@ -595,6 +793,11 @@ export default class TaskRoleContainerList extends React.Component { }, ]; + let columns = defaultColumns; + if (showDebugInfo) { + columns = defaultColumns.concat(optionalColumns); + } + return columns; } @@ -613,7 +816,7 @@ export default class TaskRoleContainerList extends React.Component { render() { const { monacoTitle, monacoProps, monacoFooterButton, logUrl } = this.state; - const { className, style, taskInfo } = this.props; + const { className, style, taskInfo, showDebugInfo } = this.props; const status = taskInfo.taskStatuses; return (
{/* right */} -
+ + { + this.setState({ + showDebugInfo: checked, + }); + }} + /> {containerListExpanded ? ( )} -
+
{containerListExpanded && ( - + )}