From 1df36ae196da2831ef597be424314ff02238cde8 Mon Sep 17 00:00:00 2001 From: jamesread Date: Fri, 16 Jul 2021 21:03:14 +0100 Subject: [PATCH] Work on log support --- OliveTin.proto | 16 ++++++------- internal/executor/executor.go | 41 +++++++++++++++++++++++++++++----- internal/grpcapi/grpcApi.go | 22 +++++++++++++++++- webui/index.html | 29 +++++++++++++++++++----- webui/js/ActionButton.js | 4 ++++ webui/js/logger.js | 14 ------------ webui/js/marshaller.js | 14 +++++++++++- webui/main.js | 16 +++++++++++-- webui/style.css | 23 +++++++++++++++++-- webui/themes/waffles/theme.css | 3 +++ 10 files changed, 142 insertions(+), 40 deletions(-) delete mode 100644 webui/js/logger.js create mode 100644 webui/themes/waffles/theme.css diff --git a/OliveTin.proto b/OliveTin.proto index 7094296..4ea1fca 100644 --- a/OliveTin.proto +++ b/OliveTin.proto @@ -22,17 +22,18 @@ message StartActionRequest { } message StartActionResponse { - string stdout = 1; - string stderr = 2; - bool timedOut = 3; - int32 exitCode = 4; + LogEntry logEntry = 1; } message GetLogsRequest{}; message LogEntry { string datetime = 1; - string content = 2; + string actionTitle = 2; + string stdout = 3; + string stderr = 4; + bool timedOut = 5; + int32 exitCode = 6; } message GetLogsResponse { @@ -52,10 +53,9 @@ service OliveTinApi { }; } - rpc GetLogs(GetLogsRequst) returns (GetLogsResponse) { + rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) { option (google.api.http) = { get: "/api/GetLogs" - } + }; } - } diff --git a/internal/executor/executor.go b/internal/executor/executor.go index 58c7263..8296c2b 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -11,8 +11,22 @@ import ( "time" ) +type InternalLogEntry struct { + Datetime string + Content string + Stdout string + Stderr string + TimedOut bool + ExitCode int32 + ActionTitle string +} + +type Executor struct { + Logs []InternalLogEntry +} + // ExecAction executes an action. -func ExecAction(cfg *config.Config, action string) *pb.StartActionResponse { +func (e *Executor) ExecAction(cfg *config.Config, action string) *pb.StartActionResponse { log.WithFields(log.Fields{ "actionName": action, }).Infof("StartAction") @@ -23,16 +37,31 @@ func ExecAction(cfg *config.Config, action string) *pb.StartActionResponse { log.Errorf("Error finding action %s, %s", err, action) return &pb.StartActionResponse{ - TimedOut: false, + LogEntry: nil, } } - return execAction(cfg, actualAction) + res := execAction(cfg, actualAction) + + e.Logs = append(e.Logs, *res); + + return &pb.StartActionResponse{ + LogEntry: &pb.LogEntry { + ActionTitle: actualAction.Title, + TimedOut: res.TimedOut, + Stderr: res.Stderr, + Stdout: res.Stdout, + ExitCode: res.ExitCode, + }, + }; } -func execAction(cfg *config.Config, actualAction *config.ActionButton) *pb.StartActionResponse { - res := &pb.StartActionResponse{} - res.TimedOut = false +func execAction(cfg *config.Config, actualAction *config.ActionButton) *InternalLogEntry { + res := &InternalLogEntry { + Datetime: time.Now().Format("2006-01-02 15:04:05"), + TimedOut: false, + ActionTitle: actualAction.Title, + } log.WithFields(log.Fields{ "title": actualAction.Title, diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index a9373c0..2ead0ba 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -15,6 +15,7 @@ import ( var ( cfg *config.Config + ex = executor.Executor{} ) type oliveTinAPI struct { @@ -22,7 +23,7 @@ type oliveTinAPI struct { } func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) { - return executor.ExecAction(cfg, req.ActionName), nil + return ex.ExecAction(cfg, req.ActionName), nil } func (api *oliveTinAPI) GetButtons(ctx ctx.Context, req *pb.GetButtonsRequest) (*pb.GetButtonsResponse, error) { @@ -43,6 +44,25 @@ func (api *oliveTinAPI) GetButtons(ctx ctx.Context, req *pb.GetButtonsRequest) ( return res, nil } +func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *pb.GetLogsRequest) (*pb.GetLogsResponse, error) { + ret := &pb.GetLogsResponse{}; + + // TODO Limit to 10 entries or something to prevent browser lag. + + for _, logEntry := range ex.Logs { + ret.Logs = append(ret.Logs, &pb.LogEntry{ + ActionTitle: logEntry.ActionTitle, + Datetime: logEntry.Datetime, + Stdout: logEntry.Stdout, + Stderr: logEntry.Stderr, + TimedOut: logEntry.TimedOut, + ExitCode: logEntry.ExitCode, + }) + } + + return ret, nil +} + // Start will start the GRPC API. func Start(globalConfig *config.Config) { cfg = globalConfig diff --git a/webui/index.html b/webui/index.html index 0eae6e3..fab9ab6 100644 --- a/webui/index.html +++ b/webui/index.html @@ -19,12 +19,13 @@ @@ -46,6 +47,22 @@

Untitled Button

+ + diff --git a/webui/js/ActionButton.js b/webui/js/ActionButton.js index 4779940..6a3de19 100644 --- a/webui/js/ActionButton.js +++ b/webui/js/ActionButton.js @@ -1,3 +1,5 @@ +import { marshalLogsJsonToHtml } from './marshaller.js'; + class ActionButton extends window.HTMLButtonElement { constructFromJson (json) { this.updateIterationTimestamp = 0; @@ -40,6 +42,8 @@ class ActionButton extends window.HTMLButtonElement { window.fetch(this.actionCallUrl).then(res => res.json() ).then((json) => { + marshalLogsJsonToHtml({"logs": [json.logEntry]}) + if (json.timedOut) { this.onActionResult('actionTimeout', 'Timed out') } else if (json.exitCode !== 0) { diff --git a/webui/js/logger.js b/webui/js/logger.js deleted file mode 100644 index 86ad0ea..0000000 --- a/webui/js/logger.js +++ /dev/null @@ -1,14 +0,0 @@ -window.logs = [] - -export function addToLog (evt) { - window.logs.append(evt) - - showLog(evt) -} - -function showLog (evt) { - let msg = document.createElement('pre') - msg.innerText = evt; - - document.body.appendChild(msg) -} diff --git a/webui/js/marshaller.js b/webui/js/marshaller.js index faebcbf..8337f03 100644 --- a/webui/js/marshaller.js +++ b/webui/js/marshaller.js @@ -16,7 +16,6 @@ export function marshalActionButtonsJsonToHtml (json) { htmlButton.updateHtml() } - console.log("action", jsonButton.title) htmlButton.updateIterationTimestamp = currentIterationTimestamp; } @@ -26,3 +25,16 @@ export function marshalActionButtonsJsonToHtml (json) { } } } + +export function marshalLogsJsonToHtml (json) { + for (const logEntry of json.logs) { + const tpl = document.getElementById('tplLogRow') + const row = tpl.content.cloneNode(true) + + row.querySelector('.timestamp').innerText = logEntry.datetime + row.querySelector('.content').innerText = logEntry.actionTitle + row.querySelector('pre').innerText = logEntry.stdout + + document.querySelector('#logTableBody').prepend(row) + } +} diff --git a/webui/main.js b/webui/main.js index 32d749f..b771cb1 100644 --- a/webui/main.js +++ b/webui/main.js @@ -1,6 +1,6 @@ 'use strict' -import { marshalActionButtonsJsonToHtml } from './js/marshaller.js' +import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js' function showBigError (type, friendlyType, message) { clearInterval(window.buttonInterval) @@ -34,7 +34,6 @@ function setupSections() { function fetchGetButtons() { window.fetch(window.restBaseUrl + 'GetButtons', { cors: 'cors' - // No fetch options }).then(res => { return res.json() }).then(res => { @@ -44,6 +43,18 @@ function fetchGetButtons() { }) } +function fetchGetLogs() { + window.fetch(window.restBaseUrl + 'GetLogs', { + cors: 'cors' + }).then(res => { + return res.json() + }).then(res => { + marshalLogsJsonToHtml(res) + }).catch(err => { + showBigError('fetch-buttons', 'getting buttons', err, 'blat') + }) +} + function processWebuiSettingsJson (settings) { window.restBaseUrl = settings.Rest @@ -65,6 +76,7 @@ window.fetch('webUiSettings.json').then(res => { processWebuiSettingsJson(res) fetchGetButtons() + fetchGetLogs() window.buttonInterval = setInterval(fetchGetButtons, 3000); }).catch(err => { diff --git a/webui/style.css b/webui/style.css index db8015f..a19f83c 100644 --- a/webui/style.css +++ b/webui/style.css @@ -37,17 +37,23 @@ fieldset#switcher button:last-child{ table { background-color: white; border-collapse: collapse; + width: 100%; } -td { +th, td { border: 1px solid #efefef; + text-align: left; + padding: .6em; +} + +th:first-child { + width: 5%; } button.activeSection { font-weight: bold; } - legend { padding-top: 1em; } @@ -189,3 +195,16 @@ img.logo { main { padding: 1em; } + +summary { + cursor: pointer; +} + +details { + display: inline-block; +} + +details[open] { + margin-top: 1em; + display: block; +} diff --git a/webui/themes/waffles/theme.css b/webui/themes/waffles/theme.css new file mode 100644 index 0000000..f815695 --- /dev/null +++ b/webui/themes/waffles/theme.css @@ -0,0 +1,3 @@ +body { + color: green; +}