diff --git a/OliveTin.proto b/OliveTin.proto index 9ae4116..9554cc8 100644 --- a/OliveTin.proto +++ b/OliveTin.proto @@ -199,6 +199,10 @@ message EventExecutionFinished { LogEntry log_entry = 1; } +message EventExecutionStarted { + LogEntry log_entry = 1; +} + message KillActionRequest { string execution_tracking_id = 1; } diff --git a/internal/executor/executor.go b/internal/executor/executor.go index aa5e758..95cebf3 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -125,7 +125,7 @@ func DefaultExecutor(cfg *config.Config) *Executor { } type listener interface { - OnExecutionStarted(actionTitle string) + OnExecutionStarted(logEntry *InternalLogEntry) OnExecutionFinished(logEntry *InternalLogEntry) OnOutputChunk(o []byte, executionTrackingId string) OnActionMapRebuilt() @@ -233,7 +233,7 @@ func (e *Executor) execChain(req *ExecutionRequest) { // This isn't a step, because we want to notify all listeners, irrespective // of how many steps were actually executed. - notifyListeners(req) + notifyListenersFinished(req) } func getConcurrentCount(req *ExecutionRequest) int { @@ -400,6 +400,8 @@ func stepRequestAction(req *ExecutionRequest) bool { "tags": req.Tags, }).Infof("Action requested") + notifyListenersStarted(req) + return true } @@ -425,12 +427,18 @@ func stepLogFinish(req *ExecutionRequest) bool { return true } -func notifyListeners(req *ExecutionRequest) { +func notifyListenersFinished(req *ExecutionRequest) { for _, listener := range req.executor.listeners { listener.OnExecutionFinished(req.logEntry) } } +func notifyListenersStarted(req *ExecutionRequest) { + for _, listener := range req.executor.listeners { + listener.OnExecutionStarted(req.logEntry) + } +} + func appendErrorToStderr(err error, logEntry *InternalLogEntry) { if err != nil { logEntry.Output = err.Error() + "\n\n" + logEntry.Output diff --git a/internal/websocket/websocket.go b/internal/websocket/websocket.go index 9dd5e27..acc8ef9 100644 --- a/internal/websocket/websocket.go +++ b/internal/websocket/websocket.go @@ -35,13 +35,10 @@ var ExecutionListener WebsocketExecutionListener type WebsocketExecutionListener struct{} -func (WebsocketExecutionListener) OnExecutionStarted(title string) { - /* - broadcast(ExecutionStarted{ - Type: "ExecutionStarted", - Action: title, - }); - */ +func (WebsocketExecutionListener) OnExecutionStarted(ile *executor.InternalLogEntry) { + broadcast(&pb.EventExecutionStarted{ + LogEntry: internalLogEntryToPb(ile), + }); } func OnEntityChanged() { @@ -82,22 +79,7 @@ func (WebsocketExecutionListener) OnOutputChunk(chunk []byte, executionTrackingI func (WebsocketExecutionListener) OnExecutionFinished(logEntry *executor.InternalLogEntry) { evt := &pb.EventExecutionFinished{ - LogEntry: &pb.LogEntry{ - ActionTitle: logEntry.ActionTitle, - ActionIcon: logEntry.ActionIcon, - ActionId: logEntry.ActionId, - DatetimeStarted: logEntry.DatetimeStarted.Format("2006-01-02 15:04:05"), - DatetimeFinished: logEntry.DatetimeFinished.Format("2006-01-02 15:04:05"), - Output: logEntry.Output, - TimedOut: logEntry.TimedOut, - Blocked: logEntry.Blocked, - ExitCode: logEntry.ExitCode, - Tags: logEntry.Tags, - ExecutionTrackingId: logEntry.ExecutionTrackingID, - ExecutionStarted: logEntry.ExecutionStarted, - ExecutionFinished: logEntry.ExecutionFinished, - User: logEntry.Username, - }, + LogEntry: internalLogEntryToPb(logEntry), } log.Infof("Execution finished: %v+v", evt.LogEntry) @@ -178,3 +160,23 @@ func HandleWebsocket(w http.ResponseWriter, r *http.Request) bool { return true } + +func internalLogEntryToPb(logEntry *executor.InternalLogEntry) *pb.LogEntry { + return &pb.LogEntry{ + ActionTitle: logEntry.ActionTitle, + ActionIcon: logEntry.ActionIcon, + ActionId: logEntry.ActionId, + DatetimeStarted: logEntry.DatetimeStarted.Format("2006-01-02 15:04:05"), + DatetimeFinished: logEntry.DatetimeFinished.Format("2006-01-02 15:04:05"), + Output: logEntry.Output, + TimedOut: logEntry.TimedOut, + Blocked: logEntry.Blocked, + ExitCode: logEntry.ExitCode, + Tags: logEntry.Tags, + ExecutionTrackingId: logEntry.ExecutionTrackingID, + ExecutionStarted: logEntry.ExecutionStarted, + ExecutionFinished: logEntry.ExecutionFinished, + User: logEntry.Username, + } +} + diff --git a/webui.dev/js/marshaller.js b/webui.dev/js/marshaller.js index 4023b3c..6881d77 100644 --- a/webui.dev/js/marshaller.js +++ b/webui.dev/js/marshaller.js @@ -54,12 +54,13 @@ export function initMarshaller () { window.executionDialog = new ExecutionDialog() - window.logEntries = {} + window.logEntries = new Map() window.registeredPaths = new Map() window.breadcrumbNavigation = [] window.currentPath = '' + window.addEventListener('EventExecutionStarted', onExecutionStarted) window.addEventListener('EventExecutionFinished', onExecutionFinished) window.addEventListener('EventOutputChunk', onOutputChunk) } @@ -125,6 +126,14 @@ function onOutputChunk (evt) { } } +function onExecutionStarted (evt) { + const logEntry = evt.payload.logEntry + + marshalLogsJsonToHtml({ + logs: [logEntry] + }) +} + function onExecutionFinished (evt) { const logEntry = evt.payload.logEntry @@ -136,7 +145,9 @@ function onExecutionFinished (evt) { switch (actionButton.popupOnStart) { case 'execution-button': - document.querySelector('execution-button#execution-' + logEntry.executionTrackingId).onExecutionFinished(logEntry) + if (document.querySelector('execution-button#execution-' + logEntry.executionTrackingId) !== null) { // If the button was created in our instance + document.querySelector('execution-button#execution-' + logEntry.executionTrackingId).onExecutionFinished(logEntry) + } break case 'execution-dialog-stdout-only': case 'execution-dialog': @@ -617,41 +628,46 @@ function marshalDirectory (item, section) { export function marshalLogsJsonToHtml (json) { for (const logEntry of json.logs) { - const existing = window.logEntries[logEntry.executionTrackingId] + let row - if (existing !== undefined) { - continue + if (window.logEntries.has(logEntry.executionTrackingId)) { + row = window.logEntries.get(logEntry.executionTrackingId).dom + } else { + const tpl = document.getElementById('tplLogRow') + row = tpl.content.querySelector('tr').cloneNode(true) + + row.querySelector('.content').onclick = () => { + window.executionDialog.reset() + window.executionDialog.show() + window.executionDialog.renderExecutionResult({ + logEntry: window.logEntries.get(logEntry.executionTrackingId) + }) + pushNewNavigationPath('/logs/' + logEntry.executionTrackingId) + } + + row.exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code')) + + logEntry.dom = row + + window.logEntries.set(logEntry.executionTrackingId, logEntry) + + document.querySelector('#logTableBody').prepend(row) } - window.logEntries[logEntry.executionTrackingId] = logEntry - - const tpl = document.getElementById('tplLogRow') - const row = tpl.content.querySelector('tr').cloneNode(true) - row.querySelector('.timestamp').innerText = logEntry.datetimeStarted row.querySelector('.content').innerText = logEntry.actionTitle row.querySelector('.icon').innerHTML = logEntry.actionIcon row.setAttribute('title', logEntry.actionTitle) - const exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code')) - exitCodeDisplay.update(logEntry) + row.exitCodeDisplay.update(logEntry) - row.querySelector('.content').onclick = () => { - window.executionDialog.reset() - window.executionDialog.show() - window.executionDialog.renderExecutionResult({ - logEntry: window.logEntries[logEntry.executionTrackingId] - }) - pushNewNavigationPath('/logs/' + logEntry.executionTrackingId) - } + row.querySelector('.tags').innerHTML = '' for (const tag of logEntry.tags) { row.querySelector('.tags').append(createTag(tag)) } row.querySelector('.tags').append(createAnnotation('user', logEntry.user)) - - document.querySelector('#logTableBody').prepend(row) } } diff --git a/webui.dev/js/websocket.js b/webui.dev/js/websocket.js index e6f1c7a..f0eb768 100644 --- a/webui.dev/js/websocket.js +++ b/webui.dev/js/websocket.js @@ -54,6 +54,7 @@ function websocketOnMessage (msg) { case 'EventOutputChunk': case 'EventConfigChanged': case 'EventExecutionFinished': + case 'EventExecutionStarted': case 'EventEntityChanged': window.dispatchEvent(e) break