web sockets and live logging
This commit is contained in:
parent
68005e8492
commit
16e62ff901
1
.gitignore
vendored
1
.gitignore
vendored
@ -104,7 +104,6 @@ dist
|
||||
.tern-port
|
||||
|
||||
# Custom
|
||||
*.js
|
||||
assets/css/
|
||||
assets/js/
|
||||
assets/webp/
|
||||
|
60
package-lock.json
generated
60
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"body-parser": "^1.20.3",
|
||||
"ejs": "3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"express-ws": "^5.0.2",
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.5"
|
||||
@ -19,6 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@types/csso": "5.0.4",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-ws": "3.0.5",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/uglify-js": "3.17.5",
|
||||
"csso": "5.0.5",
|
||||
@ -412,6 +414,18 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-ws": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.5.tgz",
|
||||
"integrity": "sha512-lbWMjoHrm/v85j81UCmb/GNZFO3genxRYBW1Ob7rjRI+zxUBR+4tcFuOpKKsYQ1LYTYiy3356epLeYi/5zxUwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||
@ -494,6 +508,16 @@
|
||||
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@ -930,6 +954,21 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-ws": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
||||
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"ws": "^7.4.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.0.0 || ^5.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
@ -2102,6 +2141,27 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"body-parser": "^1.20.3",
|
||||
"ejs": "3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"express-ws": "^5.0.2",
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.5"
|
||||
@ -30,6 +31,7 @@
|
||||
"devDependencies": {
|
||||
"@types/csso": "5.0.4",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-ws": "3.0.5",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/uglify-js": "3.17.5",
|
||||
"csso": "5.0.5",
|
||||
|
61
scripts/build.js
Normal file
61
scripts/build.js
Normal file
@ -0,0 +1,61 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const logContainer = this.getElementById('logs');
|
||||
const followLogsBtn = this.getElementById('followCheckmark');
|
||||
const buildStatusTxt = this.getElementById('buildStatus');
|
||||
|
||||
/**
|
||||
* Get the correct path to establish a ws connection
|
||||
* @param {Location} loc
|
||||
*/
|
||||
function wsPath(loc) {
|
||||
return loc.pathname.replace(/\/$/, '') + '/ws';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add log line to the DOM
|
||||
* @param {string} str
|
||||
*/
|
||||
function appendLine(str, e = false) {
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(document.createTextNode(str));
|
||||
logContainer.appendChild(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to bottom of page if checkbox is checked
|
||||
*/
|
||||
function scrollToBottom() {
|
||||
if (followLogsBtn.checked) {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish websocket connection
|
||||
*/
|
||||
function connect() {
|
||||
const loc = window.location;
|
||||
let new_uri = loc.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
new_uri += "//" + loc.host;
|
||||
new_uri += wsPath(loc);
|
||||
var ws = new WebSocket(new_uri);
|
||||
|
||||
ws.onmessage = function (message) {
|
||||
console.log('Got message: ', message);
|
||||
const buildEvent = JSON.parse(message.data);
|
||||
|
||||
if (buildEvent.type === 'finish') {
|
||||
ws.close();
|
||||
buildStatusTxt.replaceChild(document.createTextNode(buildEvent.message), buildStatusTxt.firstChild);
|
||||
}
|
||||
else {
|
||||
appendLine(buildEvent.message, buildEvent.type === 'err');
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect();
|
||||
followLogsBtn.checked = false;
|
||||
followLogsBtn.addEventListener('change', scrollToBottom);
|
||||
});
|
@ -19,10 +19,11 @@ class BuildController extends EventEmitter {
|
||||
private db: DB;
|
||||
private process: spawn.ChildProcess | null = null;
|
||||
private running: boolean = false;
|
||||
private interval: NodeJS.Timeout;
|
||||
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
setInterval(this.triggerBuild, 60000);
|
||||
// this.interval = setInterval(this.triggerBuild, 60000);
|
||||
}
|
||||
|
||||
triggerBuild = () => {
|
||||
@ -106,18 +107,18 @@ class BuildController extends EventEmitter {
|
||||
});
|
||||
docker.on('close', (code) => {
|
||||
this.process = null;
|
||||
const status = code === 0 ? 'success' : 'error';
|
||||
this.emitLog({
|
||||
id: build.id,
|
||||
type: 'finish',
|
||||
message: code
|
||||
message: status
|
||||
});
|
||||
this.db.finishBuild(build.id, status);
|
||||
|
||||
if (code === 0) {
|
||||
this.db.finishBuild(build.id, 'success');
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
this.db.finishBuild(build.id, 'error');
|
||||
reject(code);
|
||||
}
|
||||
});
|
||||
@ -149,6 +150,12 @@ class BuildController extends EventEmitter {
|
||||
setDB = (db: DB) => {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BuildController;
|
||||
|
25
src/Web.ts
25
src/Web.ts
@ -1,10 +1,11 @@
|
||||
import * as http from "http";
|
||||
import crypto from 'crypto';
|
||||
import type { Express } from "express";
|
||||
import express, { application } from 'express';
|
||||
import express from 'express';
|
||||
import expressWs from "express-ws";
|
||||
import bodyParser from "body-parser";
|
||||
import type { DB } from "./DB.ts";
|
||||
import type { BuildController } from "./BuildController.ts";
|
||||
import type { BuildController, BuildEvent } from "./BuildController.ts";
|
||||
|
||||
interface WebConfig {
|
||||
port?: number;
|
||||
@ -26,6 +27,7 @@ class Web {
|
||||
|
||||
constructor(options: WebConfig = {}) {
|
||||
const app: Express = this.app = express();
|
||||
const wsApp = expressWs(app).app;
|
||||
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
|
||||
|
||||
app.set('trust proxy', 1);
|
||||
@ -95,7 +97,8 @@ class Web {
|
||||
description: `Building ${build.repo} on ${build.distro}`
|
||||
},
|
||||
build,
|
||||
log
|
||||
log,
|
||||
ended: build.status !== 'queued' && build.status !== 'running'
|
||||
});
|
||||
});
|
||||
|
||||
@ -112,6 +115,22 @@ class Web {
|
||||
app.get('/healthcheck', (_, res) => {
|
||||
res.send('Healthy');
|
||||
});
|
||||
|
||||
wsApp.ws('/build/:num/ws', (ws, req) => {
|
||||
console.log('WS Opened');
|
||||
const eventListener = (be: BuildEvent) => {
|
||||
if (be.id === notStupidParseInt(req.params.num)) {
|
||||
ws.send(JSON.stringify(be));
|
||||
}
|
||||
};
|
||||
this.buildController.on('log', eventListener);
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('WS Closed');
|
||||
this.buildController.removeListener('log', eventListener);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
close = () => {
|
||||
|
@ -24,4 +24,5 @@ buildController.setDB(db);
|
||||
process.on('SIGTERM', () => {
|
||||
web.close();
|
||||
db.close();
|
||||
buildController.close();
|
||||
});
|
||||
|
@ -105,11 +105,13 @@ input[type="submit"] {
|
||||
display: inline-block;
|
||||
padding: .3em .6em;
|
||||
color: #fff;
|
||||
margin-bottom: .4em;
|
||||
|
||||
box-shadow: 0px .4em 0 0 var(--primary-dark);
|
||||
|
||||
&:active {
|
||||
margin-top: .4em;
|
||||
margin-bottom: 0;
|
||||
box-shadow: 0px .1em 0 0 var(--primary-dark);
|
||||
}
|
||||
}
|
||||
@ -238,3 +240,17 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#followCheckmarkContainer {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
right: 1.3em;
|
||||
bottom: 1.6em;
|
||||
padding: 1em;
|
||||
background-color: rgba(15,15,15,.8);
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
cursor: inherit;
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,13 @@
|
||||
<%- include("navigation", locals) %>
|
||||
<div class="content">
|
||||
<h1>Build #<%= build.id %></h1>
|
||||
<h2><%= build.status %></h2>
|
||||
<h2 id="buildStatus"><%= build.status %></h2>
|
||||
<div class="grid-2col">
|
||||
<label>Repo</label> <span><%= build.repo %></span>
|
||||
<label>Commit</label> <span><% if (build.commit) { %><%= build.commit %><% } else { %>latest<% } %></span>
|
||||
<label>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.id %>/patch">patch file</a><% } else { %>none<% } %></span>
|
||||
<label>Distro</label> <span><%= build.distro %></span>
|
||||
<label>Dependencies</label> <span><%= build.dependencies %></span>
|
||||
<label>Start time</label> <span><%= build.startTime %></span>
|
||||
</div>
|
||||
<p><a href="/build/<%= build.id %>/logs">Full logs</a></p>
|
||||
@ -23,7 +24,16 @@
|
||||
<p><%= line %></p>
|
||||
<% }) %>
|
||||
</div>
|
||||
|
||||
<% if (!ended) { %>
|
||||
<label id="followCheckmarkContainer" title="Follow logs">
|
||||
<input type="checkbox" id="followCheckmark" />
|
||||
</label>
|
||||
<% } %>
|
||||
</div>
|
||||
<%- include("footer", locals) %>
|
||||
<% if (!ended) { %>
|
||||
<script src="/assets/js/build.js?v1" nonce="<%= cspNonce %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -9,11 +9,11 @@
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="sidebar_links">
|
||||
<li><a href="/build">New Build</a></li>
|
||||
<li><a href="/">All Builds</a></li>
|
||||
<li><a href="/?status=error">Failed Builds</a></li>
|
||||
<li><a href="/?distro=arch">Arch Builds</a></li>
|
||||
<li><a href="/?distro=artix">Artix Builds</a></li>
|
||||
<li><a href="/build">New Build</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user