mirror of
https://github.com/OliveTin/OliveTin
synced 2025-12-13 17:45:36 +00:00
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
This commit is contained in:
@@ -28,13 +28,11 @@ export async function getRootAndWait() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function requireExecutionDialogStatus (webdriver, expected) {
|
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)
|
// 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.executeScript('window.executionDialog.domExecutionDetails.hidden = false')
|
||||||
|
|
||||||
await webdriver.wait(new Condition('wait for action to be running', async function () {
|
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) {
|
if (actual === expected) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<tr title = "untitled">
|
<tr title = "untitled">
|
||||||
<th>Timestamp</th>
|
<th>Timestamp</th>
|
||||||
<th>Log</th>
|
<th>Log</th>
|
||||||
<th>Exit Code</th>
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id = "logTableBody" />
|
<tbody id = "logTableBody" />
|
||||||
@@ -127,27 +127,16 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id = "execution-dialog-basics" class = "padded-content">
|
<div id = "execution-dialog-basics" class = "padded-content-sides">
|
||||||
<strong>Started: </strong><span id = "execution-dialog-datetime-started">unknown</span>
|
<strong>Duration: </strong><span id = "execution-dialog-duration">unknown</span>
|
||||||
</div>
|
</div>
|
||||||
<div id = "execution-dialog-details" class = "padded-content">
|
<div id = "execution-dialog-details" class = "padded-content-sides">
|
||||||
<p>
|
|
||||||
<strong>Finished: </strong><span id = "execution-dialog-datetime-finished">unknown</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Exit Code: </strong><span id = "execution-dialog-exit-code">unknown</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<strong>Status: </strong><span id = "execution-dialog-status">unknown</span>
|
<strong>Status: </strong><span id = "execution-dialog-status">unknown</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id = "execution-dialog-output">
|
<div id = "execution-dialog-xterm"></div>
|
||||||
<details id = "execution-dialog-output-details">
|
|
||||||
<summary class = "padded-content">Output</summary>
|
|
||||||
<div id = "execution-dialog-xterm" />
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class = "buttons padded-content">
|
<div class = "buttons padded-content">
|
||||||
<button name = "kill" title = "Kill" id = "execution-dialog-kill-action">Kill</button>
|
<button name = "kill" title = "Kill" id = "execution-dialog-kill-action">Kill</button>
|
||||||
|
|||||||
41
webui.dev/js/ActionStatusDisplay.js
Normal file
41
webui.dev/js/ActionStatusDisplay.js
Normal file
@@ -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 = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ActionStatusDisplay } from './ActionStatusDisplay.js'
|
||||||
|
|
||||||
// This ExecutionDialog is NOT a custom HTML element, but rather just picks up
|
// This ExecutionDialog is NOT a custom HTML element, but rather just picks up
|
||||||
// the <dialog /> element out of index.html and just re-uses that - as only
|
// the <dialog /> element out of index.html and just re-uses that - as only
|
||||||
// one dialog can be shown at a time.
|
// one dialog can be shown at a time.
|
||||||
@@ -8,7 +10,6 @@ export class ExecutionDialog {
|
|||||||
this.domIcon = document.getElementById('execution-dialog-icon')
|
this.domIcon = document.getElementById('execution-dialog-icon')
|
||||||
this.domTitle = document.getElementById('execution-dialog-title')
|
this.domTitle = document.getElementById('execution-dialog-title')
|
||||||
this.domOutput = document.getElementById('execution-dialog-xterm')
|
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 = document.getElementById('execution-dialog-toggle-size')
|
||||||
this.domOutputToggleBig.onclick = () => {
|
this.domOutputToggleBig.onclick = () => {
|
||||||
this.toggleSize()
|
this.toggleSize()
|
||||||
@@ -16,32 +17,28 @@ export class ExecutionDialog {
|
|||||||
|
|
||||||
this.domBtnKill = document.getElementById('execution-dialog-kill-action')
|
this.domBtnKill = document.getElementById('execution-dialog-kill-action')
|
||||||
|
|
||||||
this.domDatetimeStarted = document.getElementById('execution-dialog-datetime-started')
|
this.domDuration = document.getElementById('execution-dialog-duration')
|
||||||
this.domDatetimeFinished = document.getElementById('execution-dialog-datetime-finished')
|
this.domStatus = new ActionStatusDisplay(document.getElementById('execution-dialog-status'))
|
||||||
this.domExitCode = document.getElementById('execution-dialog-exit-code')
|
|
||||||
this.domStatus = document.getElementById('execution-dialog-status')
|
|
||||||
|
|
||||||
this.domExecutionBasics = document.getElementById('execution-dialog-basics')
|
this.domExecutionBasics = document.getElementById('execution-dialog-basics')
|
||||||
this.domExecutionDetails = document.getElementById('execution-dialog-details')
|
this.domExecutionDetails = document.getElementById('execution-dialog-details')
|
||||||
this.domExecutionOutput = document.getElementById('execution-dialog-output')
|
|
||||||
|
|
||||||
window.terminal.open(this.domOutput)
|
window.terminal.open(this.domOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
showOutput () {
|
showOutput () {
|
||||||
this.domOutput.hidden = false
|
this.domOutput.hidden = false
|
||||||
this.domOutputDetails.open = true
|
this.domOutput.hidden = false
|
||||||
this.domExecutionOutput.hidden = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSize () {
|
toggleSize () {
|
||||||
if (this.dlg.classList.contains('big')) {
|
if (this.dlg.classList.contains('big')) {
|
||||||
this.dlg.classList.remove('big')
|
this.dlg.classList.remove('big')
|
||||||
this.domOutputDetails.open = false
|
|
||||||
} else {
|
} else {
|
||||||
this.dlg.classList.add('big')
|
this.dlg.classList.add('big')
|
||||||
this.domOutputDetails.open = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.terminal.fit.fit()
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset () {
|
||||||
@@ -54,10 +51,7 @@ export class ExecutionDialog {
|
|||||||
|
|
||||||
this.domIcon.innerText = ''
|
this.domIcon.innerText = ''
|
||||||
this.domTitle.innerText = 'Waiting for result... '
|
this.domTitle.innerText = 'Waiting for result... '
|
||||||
this.domExitCode.innerText = '?'
|
this.domDuration.innerText = ''
|
||||||
this.domStatus.className = ''
|
|
||||||
this.domDatetimeStarted.innerText = ''
|
|
||||||
this.domDatetimeFinished.innerText = ''
|
|
||||||
|
|
||||||
// window.terminal.close()
|
// window.terminal.close()
|
||||||
|
|
||||||
@@ -68,12 +62,9 @@ export class ExecutionDialog {
|
|||||||
this.domExecutionBasics.hidden = false
|
this.domExecutionBasics.hidden = false
|
||||||
|
|
||||||
this.domExecutionDetails.hidden = true
|
this.domExecutionDetails.hidden = true
|
||||||
this.domOutputDetails.open = false
|
|
||||||
|
|
||||||
window.terminal.reset()
|
window.terminal.reset()
|
||||||
window.terminal.fit.fit()
|
window.terminal.fit.fit()
|
||||||
|
|
||||||
this.domExecutionOutput.hidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show (actionButton) {
|
show (actionButton) {
|
||||||
@@ -122,7 +113,7 @@ export class ExecutionDialog {
|
|||||||
executionTick () {
|
executionTick () {
|
||||||
this.executionSeconds++
|
this.executionSeconds++
|
||||||
|
|
||||||
this.domDatetimeStarted.innerText = this.executionSeconds + ' seconds ago'
|
this.updateDuration(this.executionSeconds + ' seconds ago', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
hideEverythingApartFromOutput () {
|
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) {
|
renderExecutionResult (res) {
|
||||||
this.res = res
|
this.res = res
|
||||||
|
|
||||||
clearInterval(window.executionDialogTicker)
|
clearInterval(window.executionDialogTicker)
|
||||||
|
|
||||||
this.domExecutionOutput.hidden = false
|
this.domOutput.hidden = false
|
||||||
|
|
||||||
if (!this.hideDetailsOnResult) {
|
if (!this.hideDetailsOnResult) {
|
||||||
this.domExecutionDetails.hidden = false
|
this.domExecutionDetails.hidden = false
|
||||||
} else {
|
|
||||||
this.domOutputDetails.open = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.executionTrackingId = res.logEntry.executionTrackingId
|
this.executionTrackingId = res.logEntry.executionTrackingId
|
||||||
|
|
||||||
this.domBtnKill.disabled = res.logEntry.executionFinished
|
this.domBtnKill.disabled = res.logEntry.executionFinished
|
||||||
|
|
||||||
if (res.logEntry.executionFinished) {
|
this.domStatus.update(res.logEntry)
|
||||||
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.domIcon.innerHTML = res.logEntry.actionIcon
|
this.domIcon.innerHTML = res.logEntry.actionIcon
|
||||||
this.domTitle.innerText = res.logEntry.actionTitle
|
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.reset()
|
||||||
window.terminal.write(res.logEntry.output, () => {
|
window.terminal.write(res.logEntry.output, () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import './ActionButton.js' // To define action-button
|
|||||||
import { ExecutionDialog } from './ExecutionDialog.js'
|
import { ExecutionDialog } from './ExecutionDialog.js'
|
||||||
import { Terminal } from '@xterm/xterm'
|
import { Terminal } from '@xterm/xterm'
|
||||||
import { FitAddon } from '@xterm/addon-fit'
|
import { FitAddon } from '@xterm/addon-fit'
|
||||||
|
import { ActionStatusDisplay } from './ActionStatusDisplay.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a weird function that just sets some globals.
|
* This is a weird function that just sets some globals.
|
||||||
@@ -510,22 +511,14 @@ export function marshalLogsJsonToHtml (json) {
|
|||||||
const tpl = document.getElementById('tplLogRow')
|
const tpl = document.getElementById('tplLogRow')
|
||||||
const row = tpl.content.querySelector('tr').cloneNode(true)
|
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('.timestamp').innerText = logEntry.datetimeStarted
|
||||||
row.querySelector('.content').innerText = logEntry.actionTitle
|
row.querySelector('.content').innerText = logEntry.actionTitle
|
||||||
row.querySelector('.icon').innerHTML = logEntry.actionIcon
|
row.querySelector('.icon').innerHTML = logEntry.actionIcon
|
||||||
row.querySelector('.exit-code').innerText = logTableExitCode
|
|
||||||
row.setAttribute('title', logEntry.actionTitle)
|
row.setAttribute('title', logEntry.actionTitle)
|
||||||
|
|
||||||
|
const exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code'))
|
||||||
|
exitCodeDisplay.update(logEntry)
|
||||||
|
|
||||||
row.querySelector('.content').onclick = () => {
|
row.querySelector('.content').onclick = () => {
|
||||||
window.executionDialog.reset()
|
window.executionDialog.reset()
|
||||||
window.executionDialog.show()
|
window.executionDialog.show()
|
||||||
|
|||||||
@@ -17,10 +17,16 @@ dialog {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog[open] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
dialog.big {
|
dialog.big {
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
max-height: 100dvh;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -226,11 +232,6 @@ details {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] {
|
|
||||||
margin-top: 1em;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* General Buttons */
|
/* General Buttons */
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@@ -471,24 +472,19 @@ div.display {
|
|||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
#execution-dialog-output {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
#execution-dialog-xterm {
|
#execution-dialog-xterm {
|
||||||
overflow: auto;
|
flex-grow: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-viewport {
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.padded-content {
|
.padded-content {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padded-content-sides {
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.ta-left {
|
.ta-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user