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 && (
-
+
)}