From fab0264d9b133ba8c6269d07d882ef26e9d3539f Mon Sep 17 00:00:00 2001 From: James Read Date: Thu, 8 Aug 2024 21:46:46 +0100 Subject: [PATCH] feature: Cleanup execution dialog, and log display status. (#378) * feature: Cleanup execution dialog, and log display status. * fmt: Remove empty block * cicd: Use ActionStatusDisplay in test * bugfix: missed promise * bugfix: missed promise * bugfix: Didnt return getText on ActionStatusDisplay --- integration-tests/lib/elements.js | 4 +- webui.dev/index.html | 21 ++------ webui.dev/js/ActionStatusDisplay.js | 41 +++++++++++++++ webui.dev/js/ExecutionDialog.js | 81 +++++++++++++---------------- webui.dev/js/marshaller.js | 15 ++---- webui.dev/style.css | 28 +++++----- 6 files changed, 100 insertions(+), 90 deletions(-) create mode 100644 webui.dev/js/ActionStatusDisplay.js diff --git a/integration-tests/lib/elements.js b/integration-tests/lib/elements.js index a13b5ae..14c41cb 100644 --- a/integration-tests/lib/elements.js +++ b/integration-tests/lib/elements.js @@ -28,13 +28,11 @@ export async function getRootAndWait() { } export async function requireExecutionDialogStatus (webdriver, expected) { - const domStatus = await webdriver.findElement(By.id('execution-dialog-status')) - // It seems that webdriver will not give us text if domStatus is hidden (which it will be until complete) await webdriver.executeScript('window.executionDialog.domExecutionDetails.hidden = false') await webdriver.wait(new Condition('wait for action to be running', async function () { - const actual = await domStatus.getText() + const actual = await webdriver.executeScript('return window.executionDialog.domStatus.getText()') if (actual === expected) { return true diff --git a/webui.dev/index.html b/webui.dev/index.html index 4946380..14f8850 100644 --- a/webui.dev/index.html +++ b/webui.dev/index.html @@ -56,7 +56,7 @@ Timestamp Log - Exit Code + Status @@ -127,27 +127,16 @@ -
- Started: unknown +
+ Duration: unknown
-
-

- Finished: unknown -

-

- Exit Code: unknown -

+

Status: unknown

-
-
- Output -
-
-
+
diff --git a/webui.dev/js/ActionStatusDisplay.js b/webui.dev/js/ActionStatusDisplay.js new file mode 100644 index 0000000..757a77a --- /dev/null +++ b/webui.dev/js/ActionStatusDisplay.js @@ -0,0 +1,41 @@ +export class ActionStatusDisplay { + constructor (parentElement) { + this.exitCodeElement = document.createElement('span') + this.statusElement = document.createElement('span') + this.statusElement.innerText = 'unknown' + + parentElement.innerText = '' + parentElement.appendChild(this.statusElement) + parentElement.appendChild(this.exitCodeElement) + } + + getText () { + return this.statusElement.innerText + } + + update (logEntry) { + this.statusElement.classList.remove(...this.statusElement.classList) + + if (logEntry.executionFinished) { + this.statusElement.innerText = 'Completed' + this.exitCodeElement.innerText = ', Exit code: ' + logEntry.exitCode + + if (logEntry.exitCode === 0) { + this.statusElement.classList.add('action-success') + } else if (logEntry.blocked) { + this.statusElement.innerText = 'Blocked' + this.statusElement.classList.add('action-blocked') + this.exitCodeElement.innerText = '' + } else if (logEntry.timedOut) { + this.statusElement.innerText = 'Timed out' + this.statusElement.classList.add('action-timeout') + this.exitCodeElement.innerText = '' + } else { + this.statusElement.classList.add('action-nonzero-exit') + } + } else { + this.statusElement.innerText = 'Still running...' + this.exitCodeElement.innerText = '' + } + } +} diff --git a/webui.dev/js/ExecutionDialog.js b/webui.dev/js/ExecutionDialog.js index a4bac44..340e8fc 100644 --- a/webui.dev/js/ExecutionDialog.js +++ b/webui.dev/js/ExecutionDialog.js @@ -1,3 +1,5 @@ +import { ActionStatusDisplay } from './ActionStatusDisplay.js' + // This ExecutionDialog is NOT a custom HTML element, but rather just picks up // the element out of index.html and just re-uses that - as only // one dialog can be shown at a time. @@ -8,7 +10,6 @@ export class ExecutionDialog { this.domIcon = document.getElementById('execution-dialog-icon') this.domTitle = document.getElementById('execution-dialog-title') this.domOutput = document.getElementById('execution-dialog-xterm') - this.domOutputDetails = document.getElementById('execution-dialog-output-details') this.domOutputToggleBig = document.getElementById('execution-dialog-toggle-size') this.domOutputToggleBig.onclick = () => { this.toggleSize() @@ -16,32 +17,28 @@ export class ExecutionDialog { this.domBtnKill = document.getElementById('execution-dialog-kill-action') - this.domDatetimeStarted = document.getElementById('execution-dialog-datetime-started') - this.domDatetimeFinished = document.getElementById('execution-dialog-datetime-finished') - this.domExitCode = document.getElementById('execution-dialog-exit-code') - this.domStatus = document.getElementById('execution-dialog-status') + this.domDuration = document.getElementById('execution-dialog-duration') + this.domStatus = new ActionStatusDisplay(document.getElementById('execution-dialog-status')) this.domExecutionBasics = document.getElementById('execution-dialog-basics') this.domExecutionDetails = document.getElementById('execution-dialog-details') - this.domExecutionOutput = document.getElementById('execution-dialog-output') window.terminal.open(this.domOutput) } showOutput () { this.domOutput.hidden = false - this.domOutputDetails.open = true - this.domExecutionOutput.hidden = false + this.domOutput.hidden = false } toggleSize () { if (this.dlg.classList.contains('big')) { this.dlg.classList.remove('big') - this.domOutputDetails.open = false } else { this.dlg.classList.add('big') - this.domOutputDetails.open = true } + + window.terminal.fit.fit() } reset () { @@ -54,10 +51,7 @@ export class ExecutionDialog { this.domIcon.innerText = '' this.domTitle.innerText = 'Waiting for result... ' - this.domExitCode.innerText = '?' - this.domStatus.className = '' - this.domDatetimeStarted.innerText = '' - this.domDatetimeFinished.innerText = '' + this.domDuration.innerText = '' // window.terminal.close() @@ -68,12 +62,9 @@ export class ExecutionDialog { this.domExecutionBasics.hidden = false this.domExecutionDetails.hidden = true - this.domOutputDetails.open = false window.terminal.reset() window.terminal.fit.fit() - - this.domExecutionOutput.hidden = true } show (actionButton) { @@ -122,7 +113,7 @@ export class ExecutionDialog { executionTick () { this.executionSeconds++ - this.domDatetimeStarted.innerText = this.executionSeconds + ' seconds ago' + this.updateDuration(this.executionSeconds + ' seconds ago', '') } hideEverythingApartFromOutput () { @@ -158,50 +149,52 @@ export class ExecutionDialog { }) } + updateDuration (started, finished) { + if (finished === '') { + this.domDuration.innerHTML = started + } else { + let delta = '' + + try { + delta = (new Date(finished) - new Date(started)) / 1000 + delta = new Intl.RelativeTimeFormat().format(delta, 'seconds').replace('in ', '').replace('ago', '') + } catch (e) { + console.warn('Failed to calculate delta', e) + } + + this.domDuration.innerHTML = started + ' → ' + finished + + if (delta !== '') { + this.domDuration.innerHTML += ' (' + delta + ')' + } + } + } + renderExecutionResult (res) { this.res = res clearInterval(window.executionDialogTicker) - this.domExecutionOutput.hidden = false + this.domOutput.hidden = false if (!this.hideDetailsOnResult) { this.domExecutionDetails.hidden = false - } else { - this.domOutputDetails.open = true } this.executionTrackingId = res.logEntry.executionTrackingId this.domBtnKill.disabled = res.logEntry.executionFinished - if (res.logEntry.executionFinished) { - this.domStatus.innerText = 'Completed' - this.domStatus.classList.add('action-success') - this.domDatetimeFinished.innerText = res.logEntry.datetimeFinished - - if (res.logEntry.timedOut) { - this.domExitCode.innerText = 'Timed out' - this.domStatus.classList.add('action-timeout') - } else if (res.logEntry.blocked) { - this.domStatus.innerText = 'Blocked' - this.domStatus.classList.add('action-blocked') - } else if (res.logEntry.exitCode !== 0) { - this.domStatus.innerText = 'Non-Zero Exit' - this.domStatus.classList.add('action-nonzero-exit') - } else { - this.domExitCode.innerText = res.logEntry.exitCode - } - } else { - this.domDatetimeFinished.innerText = 'Still running...' - this.domExitCode.innerText = 'Still running...' - this.domStatus.innerText = 'Still running...' - } + this.domStatus.update(res.logEntry) this.domIcon.innerHTML = res.logEntry.actionIcon this.domTitle.innerText = res.logEntry.actionTitle - this.domDatetimeStarted.innerText = res.logEntry.datetimeStarted + if (res.logEntry.executionFinished) { + this.updateDuration(res.logEntry.datetimeStarted, res.logEntry.datetimeFinished) + } else { + this.updateDuration(res.logEntry.datetimeStarted, 'Still running...') + } window.terminal.reset() window.terminal.write(res.logEntry.output, () => { diff --git a/webui.dev/js/marshaller.js b/webui.dev/js/marshaller.js index fce3add..2d91a25 100644 --- a/webui.dev/js/marshaller.js +++ b/webui.dev/js/marshaller.js @@ -2,6 +2,7 @@ import './ActionButton.js' // To define action-button import { ExecutionDialog } from './ExecutionDialog.js' import { Terminal } from '@xterm/xterm' import { FitAddon } from '@xterm/addon-fit' +import { ActionStatusDisplay } from './ActionStatusDisplay.js' /** * This is a weird function that just sets some globals. @@ -510,22 +511,14 @@ export function marshalLogsJsonToHtml (json) { const tpl = document.getElementById('tplLogRow') const row = tpl.content.querySelector('tr').cloneNode(true) - let logTableExitCode = logEntry.exitCode - - if (logEntry.exitCode === 0) { - logTableExitCode = 'OK' - } - - if (logEntry.timedOut) { - logTableExitCode += ' (timed out)' - } - row.querySelector('.timestamp').innerText = logEntry.datetimeStarted row.querySelector('.content').innerText = logEntry.actionTitle row.querySelector('.icon').innerHTML = logEntry.actionIcon - row.querySelector('.exit-code').innerText = logTableExitCode row.setAttribute('title', logEntry.actionTitle) + const exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code')) + exitCodeDisplay.update(logEntry) + row.querySelector('.content').onclick = () => { window.executionDialog.reset() window.executionDialog.show() diff --git a/webui.dev/style.css b/webui.dev/style.css index 551a23b..a4fb604 100644 --- a/webui.dev/style.css +++ b/webui.dev/style.css @@ -17,10 +17,16 @@ dialog { padding: 0; } +dialog[open] { + display: flex; + flex-direction: column; +} + dialog.big { max-width: 100vw; width: 100vw; height: 100vh; + max-height: 100dvh; border: none; margin: 0; } @@ -226,11 +232,6 @@ details { flex-grow: 1; } -details[open] { - margin-top: 1em; - display: block; -} - /* General Buttons */ button, @@ -471,24 +472,19 @@ div.display { font-size: small; } -#execution-dialog-output { - display: flex; - flex-direction: row; - justify-content: start; -} - #execution-dialog-xterm { - overflow: auto; -} - -.xterm .xterm-viewport { - overflow-y: auto !important; + flex-grow: 1; } .padded-content { padding: 1em; } +.padded-content-sides { + padding-left: 1em; + padding-right: 1em; +} + .ta-left { text-align: left; }