diff --git a/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx b/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx index c4c74fe6f7f5..d808a2fb4010 100644 --- a/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx +++ b/ui/src/app/archived-workflows/components/archived-workflow-details/archived-workflow-details.tsx @@ -12,6 +12,7 @@ import {ResourceEditor} from '../../../shared/components/resource-editor/resourc import {services} from '../../../shared/services'; import {WorkflowArtifacts} from '../../../workflows/components/workflow-artifacts'; +import {Utils} from '../../../shared/utils'; import {WorkflowResourcePanel} from '../../../workflows/components/workflow-details/workflow-resource-panel'; import {WorkflowLogsViewer} from '../../../workflows/components/workflow-logs-viewer/workflow-logs-viewer'; import {WorkflowNodeInfo} from '../../../workflows/components/workflow-node-info/workflow-node-info'; @@ -217,7 +218,9 @@ export class ArchivedWorkflowDetails extends BasePage, )} (this.sidePanel = null)}> {this.sidePanel === 'yaml' && } - {this.sidePanel === 'logs' && } + {this.sidePanel === 'logs' && ( + + )} {this.sidePanel === 'resubmit' && ( editing={true} @@ -246,6 +249,14 @@ export class ArchivedWorkflowDetails extends BasePage, return this.nodeId && this.state.workflow.status.nodes[this.nodeId]; } + private get podName() { + if (this.nodeId && this.state.workflow) { + const workflowName = this.state.workflow.metadata.name; + const {name, templateName} = this.node; + return Utils.getPodName(workflowName, name, templateName, this.nodeId); + } + } + private deleteArchivedWorkflow() { if (!confirm('Are you sure you want to delete this archived workflow?\nThere is no undo.')) { return; diff --git a/ui/src/app/shared/utils.ts b/ui/src/app/shared/utils.ts index 123e0a47cef1..f049208b21ec 100644 --- a/ui/src/app/shared/utils.ts +++ b/ui/src/app/shared/utils.ts @@ -4,6 +4,9 @@ import {NODE_PHASE} from '../../models'; const managedNamespaceKey = 'managedNamespace'; const currentNamespaceKey = 'current_namespace'; +const maxK8sResourceNameLength = 253; +const k8sNamingHashLength = 10; + export const Utils = { statusIconClasses(status: string): string { let classes = []; @@ -117,5 +120,42 @@ export const Utils = { // return a namespace, never return null/undefined, defaults to "default" getNamespaceWithDefault(namespace: string) { return this.managedNamespace || namespace || this.currentNamespace || 'default'; + }, + + ensurePodNamePrefixLength(prefix: string): string { + const maxPrefixLength = maxK8sResourceNameLength - k8sNamingHashLength; + + if (prefix.length > maxPrefixLength - 1) { + return prefix.substring(0, maxPrefixLength - 1); + } + + return prefix; + }, + + // getPodName returns a deterministic pod name + getPodName(workflowName: string, nodeName: string, templateName: string, nodeID: string): string { + if (workflowName === nodeName) { + return workflowName; + } + + let prefix = `${workflowName}-${templateName}`; + prefix = this.ensurePodNamePrefixLength(prefix); + + const hash = createFNVHash(nodeName); + return `${prefix}-${hash}`; + } +}; + +const createFNVHash = (input: string): number => { + const data = new Buffer(input); + + let hashint = 2166136261; + + /* tslint:disable:no-bitwise */ + for (const character of data) { + hashint = hashint ^ character; + hashint += (hashint << 1) + (hashint << 4) + (hashint << 7) + (hashint << 8) + (hashint << 24); } + + return hashint >>> 0; }; diff --git a/ui/src/app/tsconfig.json b/ui/src/app/tsconfig.json index 1272efca772f..5b42aece164b 100644 --- a/ui/src/app/tsconfig.json +++ b/ui/src/app/tsconfig.json @@ -9,6 +9,7 @@ "experimentalDecorators": true, "noUnusedLocals": true, "declaration": false, + "downlevelIteration": true, "lib": [ "es2017", "dom" diff --git a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx index 23750cdd783f..33a8a78dded1 100644 --- a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx +++ b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx @@ -3,7 +3,7 @@ import * as classNames from 'classnames'; import * as React from 'react'; import {useContext, useEffect, useState} from 'react'; import {RouteComponentProps} from 'react-router'; -import {execSpec, Link, Workflow} from '../../../../models'; +import {execSpec, Link, NodeStatus, Workflow} from '../../../../models'; import {uiUrl} from '../../../shared/base'; import {CostOptimisationNudge} from '../../../shared/components/cost-optimisation-nudge'; import {ErrorNotice} from '../../../shared/components/error-notice'; @@ -16,6 +16,7 @@ import {historyUrl} from '../../../shared/history'; import {RetryWatch} from '../../../shared/retry-watch'; import {services} from '../../../shared/services'; import {useQueryParams} from '../../../shared/use-query-params'; +import {Utils} from '../../../shared/utils'; import * as Operations from '../../../shared/workflow-operations-map'; import {WorkflowOperations} from '../../../shared/workflow-operations-map'; import {WidgetGallery} from '../../../widgets/widget-gallery'; @@ -232,7 +233,17 @@ export const WorkflowDetails = ({history, location, match}: RouteComponentProps< }); }; + const getPodName = (wf: Workflow, node: NodeStatus, nodeID: string): string => { + if (workflow && node) { + return Utils.getPodName(wf.metadata.name, node.name, node.templateName, node.id); + } + + return nodeID; + }; + const selectedNode = workflow && workflow.status && workflow.status.nodes && workflow.status.nodes[nodeId]; + const podName = getPodName(workflow, selectedNode, nodeId); + return ( setSidePanel(null)}> {parsedSidePanel.type === 'logs' && ( - + )} {parsedSidePanel.type === 'events' && } {parsedSidePanel.type === 'share' && } diff --git a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx index 212dad6f2443..cd873677ed04 100644 --- a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx +++ b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx @@ -9,11 +9,13 @@ import {ErrorNotice} from '../../../shared/components/error-notice'; import {InfoIcon, WarningIcon} from '../../../shared/components/fa-icons'; import {Links} from '../../../shared/components/links'; import {services} from '../../../shared/services'; +import {Utils} from '../../../shared/utils'; import {FullHeightLogsViewer} from './full-height-logs-viewer'; interface WorkflowLogsViewerProps { workflow: models.Workflow; nodeId?: string; + initialPodName: string; container: string; archived: boolean; } @@ -22,8 +24,8 @@ function identity(value: T) { return () => value; } -export const WorkflowLogsViewer = ({workflow, nodeId, container, archived}: WorkflowLogsViewerProps) => { - const [podName, setPodName] = useState(nodeId || ''); +export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container, archived}: WorkflowLogsViewerProps) => { + const [podName, setPodName] = useState(initialPodName || ''); const [selectedContainer, setContainer] = useState(container); const [grep, setGrep] = useState(''); const [error, setError] = useState(); @@ -59,7 +61,11 @@ export const WorkflowLogsViewer = ({workflow, nodeId, container, archived}: Work const podNames = [{value: '', label: 'All'}].concat( Object.values(workflow.status.nodes || {}) .filter(x => x.type === 'Pod') - .map(x => ({value: x.id, label: (x.displayName || x.name) + ' (' + x.id + ')'})) + .map(targetNode => { + const {name, id, templateName, displayName} = targetNode; + const targetPodName = Utils.getPodName(workflow.metadata.name, name, templateName, id); + return {value: targetPodName, label: (displayName || name) + ' (' + targetPodName + ')'}; + }) ); const node = workflow.status.nodes[nodeId]; diff --git a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx index b5621fa1f3aa..c361037878cd 100644 --- a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx +++ b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx @@ -15,6 +15,7 @@ import {Timestamp} from '../../../shared/components/timestamp'; import {ResourcesDuration} from '../../../shared/resources-duration'; import {services} from '../../../shared/services'; import {getResolvedTemplates} from '../../../shared/template-resolution'; +import {Utils} from '../../../shared/utils'; require('./workflow-node-info.scss'); @@ -79,6 +80,10 @@ const AttributeRows = (props: {attributes: {title: string; value: any}[]}) => ( ); const WorkflowNodeSummary = (props: Props) => { + const {workflow, node} = props; + + const podName = Utils.getPodName(workflow.metadata.name, node.name, node.templateName, node.id); + const attributes = [ {title: 'NAME', value: }, {title: 'TYPE', value: props.node.type}, @@ -131,7 +136,7 @@ const WorkflowNodeSummary = (props: Props) => { attributes.splice( 2, 0, - {title: 'POD NAME', value: }, + {title: 'POD NAME', value: }, { title: 'HOST NODE NAME', value: