Work on log support
This commit is contained in:
parent
285e867128
commit
1df36ae196
|
|
@ -22,17 +22,18 @@ message StartActionRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartActionResponse {
|
message StartActionResponse {
|
||||||
string stdout = 1;
|
LogEntry logEntry = 1;
|
||||||
string stderr = 2;
|
|
||||||
bool timedOut = 3;
|
|
||||||
int32 exitCode = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetLogsRequest{};
|
message GetLogsRequest{};
|
||||||
|
|
||||||
message LogEntry {
|
message LogEntry {
|
||||||
string datetime = 1;
|
string datetime = 1;
|
||||||
string content = 2;
|
string actionTitle = 2;
|
||||||
|
string stdout = 3;
|
||||||
|
string stderr = 4;
|
||||||
|
bool timedOut = 5;
|
||||||
|
int32 exitCode = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetLogsResponse {
|
message GetLogsResponse {
|
||||||
|
|
@ -52,10 +53,9 @@ service OliveTinApi {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc GetLogs(GetLogsRequst) returns (GetLogsResponse) {
|
rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/api/GetLogs"
|
get: "/api/GetLogs"
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,22 @@ import (
|
||||||
"time"
|
"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.
|
// 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{
|
log.WithFields(log.Fields{
|
||||||
"actionName": action,
|
"actionName": action,
|
||||||
}).Infof("StartAction")
|
}).Infof("StartAction")
|
||||||
|
|
@ -23,16 +37,31 @@ func ExecAction(cfg *config.Config, action string) *pb.StartActionResponse {
|
||||||
log.Errorf("Error finding action %s, %s", err, action)
|
log.Errorf("Error finding action %s, %s", err, action)
|
||||||
|
|
||||||
return &pb.StartActionResponse{
|
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 {
|
func execAction(cfg *config.Config, actualAction *config.ActionButton) *InternalLogEntry {
|
||||||
res := &pb.StartActionResponse{}
|
res := &InternalLogEntry {
|
||||||
res.TimedOut = false
|
Datetime: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
TimedOut: false,
|
||||||
|
ActionTitle: actualAction.Title,
|
||||||
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"title": actualAction.Title,
|
"title": actualAction.Title,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
|
ex = executor.Executor{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type oliveTinAPI struct {
|
type oliveTinAPI struct {
|
||||||
|
|
@ -22,7 +23,7 @@ type oliveTinAPI struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) {
|
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) {
|
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
|
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.
|
// Start will start the GRPC API.
|
||||||
func Start(globalConfig *config.Config) {
|
func Start(globalConfig *config.Config) {
|
||||||
cfg = globalConfig
|
cfg = globalConfig
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@
|
||||||
|
|
||||||
<section id = "contentLogs" title = "Logs" hidden>
|
<section id = "contentLogs" title = "Logs" hidden>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<thead>
|
||||||
<td>12 seconds ago</td> <td>blat blat <button>Logs</button></td>
|
<tr>
|
||||||
</tr>
|
<th>Timestamp</th>
|
||||||
<tr>
|
<th>Log</th>
|
||||||
<td>4 seconds ago</td> <td>blat blat foo foo <button>Logs</button></td>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
|
<tbody id = "logTableBody" />
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -46,6 +47,22 @@
|
||||||
<p role = "title" class = "title">Untitled Button</p>
|
<p role = "title" class = "title">Untitled Button</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template id = "tplLogRow">
|
||||||
|
<tr>
|
||||||
|
<td class = "timestamp">?</td>
|
||||||
|
<td>
|
||||||
|
<span class = "content">?</span>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>stdout</summary>
|
||||||
|
<pre>
|
||||||
|
?
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script type = "module" src = "main.js"></script>
|
<script type = "module" src = "main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { marshalLogsJsonToHtml } from './marshaller.js';
|
||||||
|
|
||||||
class ActionButton extends window.HTMLButtonElement {
|
class ActionButton extends window.HTMLButtonElement {
|
||||||
constructFromJson (json) {
|
constructFromJson (json) {
|
||||||
this.updateIterationTimestamp = 0;
|
this.updateIterationTimestamp = 0;
|
||||||
|
|
@ -40,6 +42,8 @@ class ActionButton extends window.HTMLButtonElement {
|
||||||
|
|
||||||
window.fetch(this.actionCallUrl).then(res => res.json()
|
window.fetch(this.actionCallUrl).then(res => res.json()
|
||||||
).then((json) => {
|
).then((json) => {
|
||||||
|
marshalLogsJsonToHtml({"logs": [json.logEntry]})
|
||||||
|
|
||||||
if (json.timedOut) {
|
if (json.timedOut) {
|
||||||
this.onActionResult('actionTimeout', 'Timed out')
|
this.onActionResult('actionTimeout', 'Timed out')
|
||||||
} else if (json.exitCode !== 0) {
|
} else if (json.exitCode !== 0) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,6 @@ export function marshalActionButtonsJsonToHtml (json) {
|
||||||
htmlButton.updateHtml()
|
htmlButton.updateHtml()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("action", jsonButton.title)
|
|
||||||
htmlButton.updateIterationTimestamp = currentIterationTimestamp;
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import { marshalActionButtonsJsonToHtml } from './js/marshaller.js'
|
import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js'
|
||||||
|
|
||||||
function showBigError (type, friendlyType, message) {
|
function showBigError (type, friendlyType, message) {
|
||||||
clearInterval(window.buttonInterval)
|
clearInterval(window.buttonInterval)
|
||||||
|
|
@ -34,7 +34,6 @@ function setupSections() {
|
||||||
function fetchGetButtons() {
|
function fetchGetButtons() {
|
||||||
window.fetch(window.restBaseUrl + 'GetButtons', {
|
window.fetch(window.restBaseUrl + 'GetButtons', {
|
||||||
cors: 'cors'
|
cors: 'cors'
|
||||||
// No fetch options
|
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
return res.json()
|
return res.json()
|
||||||
}).then(res => {
|
}).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) {
|
function processWebuiSettingsJson (settings) {
|
||||||
window.restBaseUrl = settings.Rest
|
window.restBaseUrl = settings.Rest
|
||||||
|
|
||||||
|
|
@ -65,6 +76,7 @@ window.fetch('webUiSettings.json').then(res => {
|
||||||
processWebuiSettingsJson(res)
|
processWebuiSettingsJson(res)
|
||||||
|
|
||||||
fetchGetButtons()
|
fetchGetButtons()
|
||||||
|
fetchGetLogs()
|
||||||
|
|
||||||
window.buttonInterval = setInterval(fetchGetButtons, 3000);
|
window.buttonInterval = setInterval(fetchGetButtons, 3000);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
|
|
||||||
|
|
@ -37,17 +37,23 @@ fieldset#switcher button:last-child{
|
||||||
table {
|
table {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
th, td {
|
||||||
border: 1px solid #efefef;
|
border: 1px solid #efefef;
|
||||||
|
text-align: left;
|
||||||
|
padding: .6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
width: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.activeSection {
|
button.activeSection {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
@ -189,3 +195,16 @@ img.logo {
|
||||||
main {
|
main {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] {
|
||||||
|
margin-top: 1em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue