Compare commits
4 Commits
4435c0baee
...
12cf09d408
Author | SHA1 | Date | |
---|---|---|---|
12cf09d408 | |||
8f19175c3a | |||
eeac35a0a6 | |||
b5d57228f0 |
755
package-lock.json
generated
755
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "archery",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"description": "Build Arch packages through a web interface",
|
||||
"keywords": [
|
||||
"docker",
|
||||
@@ -20,21 +20,20 @@
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.3",
|
||||
"body-parser": "^2.2.0",
|
||||
"ejs": "3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"express": "^5.1.0",
|
||||
"express-ws": "^5.0.2",
|
||||
"pg": "^8.15.6",
|
||||
"pg": "^8.16.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7"
|
||||
"sequelize": "^6.37.7",
|
||||
"sqids": "0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/csso": "5.0.4",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/express-ws": "3.0.5",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/uglify-js": "3.17.5",
|
||||
"forking-build-shit": "0.0.2"
|
||||
"@types/node": "^24.5.2",
|
||||
"forking-build-shit": "1.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "5.7.3"
|
||||
|
@@ -2,6 +2,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const logContainer = this.getElementById('logs');
|
||||
const followLogsBtn = this.getElementById('followCheckmark');
|
||||
const buildStatusTxt = this.getElementById('buildStatus');
|
||||
let started = logContainer.childElementCount > 0;
|
||||
|
||||
/**
|
||||
* Get the correct path to establish a ws connection
|
||||
@@ -32,6 +33,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
}
|
||||
|
||||
function handleScrollToggleClick() {
|
||||
window.scrollTo(0, followLogsBtn.checked ? document.body.scrollHeight : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split string by newline char
|
||||
* @param {string} str
|
||||
@@ -53,9 +58,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
ws.onmessage = function (message) {
|
||||
const buildEvent = JSON.parse(message.data);
|
||||
|
||||
if (!started) {
|
||||
started = true;
|
||||
buildStatusTxt.replaceChild(document.createTextNode('running'), buildStatusTxt.firstChild);
|
||||
}
|
||||
if (buildEvent.type === 'finish') {
|
||||
ws.close();
|
||||
buildStatusTxt.replaceChild(document.createTextNode(buildEvent.message), buildStatusTxt.firstChild);
|
||||
appendLines([`finished: ${buildEvent.message}`]);
|
||||
const cancelBtn = document.getElementById('cancelRow');
|
||||
cancelBtn.parentElement.removeChild(cancelBtn);
|
||||
}
|
||||
else {
|
||||
appendLines(splitLines(buildEvent.message), buildEvent.type === 'err');
|
||||
@@ -66,5 +78,5 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
connect();
|
||||
followLogsBtn.checked = false;
|
||||
followLogsBtn.addEventListener('change', scrollToBottom);
|
||||
followLogsBtn.addEventListener('change', handleScrollToggleClick);
|
||||
});
|
@@ -24,6 +24,7 @@ interface Build {
|
||||
endTime?: Date;
|
||||
status: Status;
|
||||
pid?: number;
|
||||
sqid?: string;
|
||||
}
|
||||
|
||||
interface LogChunk {
|
||||
@@ -51,7 +52,8 @@ class DB {
|
||||
this.sequelize = new Sequelize(config.db || 'archery', config.user || 'archery', process.env.PASSWORD || config.password || '', {
|
||||
host: config.host || 'localhost',
|
||||
port: config.port || 5432,
|
||||
dialect: 'postgres'
|
||||
dialect: 'postgres',
|
||||
logging: false
|
||||
});
|
||||
this.build = this.sequelize.define('builds', {
|
||||
id: {
|
||||
|
45
src/Web.ts
45
src/Web.ts
@@ -4,6 +4,7 @@ import type { Express } from "express";
|
||||
import express from 'express';
|
||||
import expressWs from "express-ws";
|
||||
import bodyParser from "body-parser";
|
||||
import Sqids from 'sqids';
|
||||
import type { DB, LogChunk } from "./DB.ts";
|
||||
import type { BuildController, BuildEvent } from "./BuildController.ts";
|
||||
|
||||
@@ -41,6 +42,10 @@ class Web {
|
||||
private port: number;
|
||||
|
||||
constructor(options: WebConfig = {}) {
|
||||
const sqids = new Sqids({
|
||||
minLength: 6,
|
||||
alphabet: 'abcdefghijkmnprstuvwxyz'
|
||||
});
|
||||
const app: Express = express();
|
||||
const wsApp = this.app = expressWs(app).app;
|
||||
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
|
||||
@@ -66,6 +71,9 @@ class Web {
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const builds = 'q' in req.query ? await this.db.searchBuilds(req.query.q as string) : await this.db.getBuildsBy(req.query);
|
||||
builds.forEach(b => {
|
||||
b.sqid = sqids.encode([b.id]);
|
||||
});
|
||||
res.render('index', {
|
||||
page: {
|
||||
title: 'Archery',
|
||||
@@ -82,17 +90,19 @@ class Web {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/build/?', (_, res) => {
|
||||
app.get('/build{/}', async(req, res) => {
|
||||
const query = ('id' in req.query && typeof req.query.id === 'string' && await this.db.getBuild(sqids.decode(req.query.id)?.[0])) || req.query;
|
||||
res.render('build-new', {
|
||||
page: {
|
||||
title: 'Archery',
|
||||
titlesuffix: 'New Build',
|
||||
description: 'Kick off a build'
|
||||
}
|
||||
description: 'Kick off a build',
|
||||
},
|
||||
query
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/build/?', async (req, res) => {
|
||||
app.post('/build{/}', async (req, res) => {
|
||||
const buildId = await this.db.createBuild(
|
||||
req.body.repo,
|
||||
req.body.commit || null,
|
||||
@@ -100,22 +110,23 @@ class Web {
|
||||
req.body.distro || 'arch',
|
||||
req.body.dependencies || 'stable'
|
||||
);
|
||||
res.redirect(`/build/${buildId}`);
|
||||
res.redirect(`/build/${sqids.encode([buildId])}`);
|
||||
this.buildController.triggerBuild();
|
||||
});
|
||||
|
||||
app.get('/build/:num/?', async (req, res) => {
|
||||
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||
app.get('/build/:id{/}', async (req, res) => {
|
||||
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
|
||||
if (!build) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
build.sqid = sqids.encode([build.id]);
|
||||
const log = splitLines(await this.db.getLog(build.id));
|
||||
|
||||
res.render('build', {
|
||||
page: {
|
||||
title: 'Archery',
|
||||
titlesuffix: `Build #${req.params.num}`,
|
||||
titlesuffix: `Build #${build.id}`,
|
||||
description: `Building ${build.repo} on ${build.distro}`
|
||||
},
|
||||
build,
|
||||
@@ -124,8 +135,8 @@ class Web {
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/build/:num/cancel', async (req, res) => {
|
||||
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||
app.get('/build/:id/cancel', async (req, res) => {
|
||||
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
|
||||
if (!build) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
@@ -136,11 +147,11 @@ class Web {
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
res.redirect(`/build/${build.id}`);
|
||||
res.redirect(`/build/${req.params.id}`);
|
||||
});
|
||||
|
||||
app.get('/build/:num/logs/?', async (req, res) => {
|
||||
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||
app.get('/build/:id/logs{/}', async (req, res) => {
|
||||
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
|
||||
if (!build) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
@@ -149,8 +160,8 @@ class Web {
|
||||
res.set('Content-Type', 'text/plain').send(log);
|
||||
});
|
||||
|
||||
app.get('/build/:num/patch/?', async (req, res) => {
|
||||
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||
app.get('/build/:id/patch{/}', async (req, res) => {
|
||||
const build = await this.db.getBuild(sqids.decode(req.params.id)?.[0]);
|
||||
if (!build || !build.patch) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
@@ -162,10 +173,10 @@ class Web {
|
||||
res.send('Healthy');
|
||||
});
|
||||
|
||||
wsApp.ws('/build/:num/ws', (ws, req) => {
|
||||
wsApp.ws('/build/:id/ws', (ws, req) => {
|
||||
console.log('WS Opened');
|
||||
const eventListener = (be: BuildEvent) => {
|
||||
if (be.id === notStupidParseInt(req.params.num)) {
|
||||
if (be.id === sqids.decode(req.params.id)?.[0]) {
|
||||
ws.send(JSON.stringify(be));
|
||||
}
|
||||
};
|
||||
|
@@ -10,19 +10,19 @@
|
||||
<div class="content">
|
||||
<h1>Start a build</h1>
|
||||
<form action="/build" method="post" class="grid-2col">
|
||||
<label for="repoTxt">Repo</label> <input type="text" name="repo" id="repoTxt" required />
|
||||
<label for="commitTxt">Commit</label> <input type="text" name="commit" id="commitTxt" />
|
||||
<label for="patchTxt">Patch</label> <textarea name="patch" id="patchTxt"></textarea>
|
||||
<label for="repoTxt">Repo</label> <input type="text" name="repo" id="repoTxt" required <% if (typeof query.repo === 'string') { %>value="<%= query.repo %>"<% } %> />
|
||||
<label for="commitTxt">Commit</label> <input type="text" name="commit" id="commitTxt" <% if (typeof query.commit === 'string') { %>value="<%= query.commit %>"<% } %> />
|
||||
<label for="patchTxt">Patch</label> <textarea name="patch" id="patchTxt"><% if (typeof query.patch === 'string') { %><%= query.patch %><% } %></textarea>
|
||||
<label for="distroTxt">Distro</label>
|
||||
<select name="distro" id="distroTxt" required>
|
||||
<option value="arch">Arch</option>
|
||||
<option value="artix">Artix</option>
|
||||
<option value="arch" <% if (query.distro === 'arch') { %>selected<% } %>>Arch</option>
|
||||
<option value="artix" <% if (query.distro === 'artix') { %>selected<% } %>>Artix</option>
|
||||
</select>
|
||||
<label for="dependsTxt">Dependencies</label>
|
||||
<select name="dependencies" id="dependsTxt" required>
|
||||
<option value="stable">Stable</option>
|
||||
<option value="testing">Testing</option>
|
||||
<option value="staging">Staging</option>
|
||||
<option value="stable" <% if (query.dependencies === 'stable') { %>selected<% } %>>Stable</option>
|
||||
<option value="testing" <% if (query.dependencies === 'testing') { %>selected<% } %>>Testing</option>
|
||||
<option value="staging" <% if (query.dependencies === 'staging') { %>selected<% } %>>Staging</option>
|
||||
</select>
|
||||
<div class="span-2"><button type="submit">Build</button></div>
|
||||
</form>
|
||||
|
@@ -14,18 +14,21 @@
|
||||
<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>Patch</label> <span><% if (build.patch) { %><a href="/build/<%= build.sqid %>/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 class="to-local-time"><%= build.startTime %></span>
|
||||
</div>
|
||||
</div>
|
||||
<% if (!ended) { %>
|
||||
<div>
|
||||
<a href="/build/<%= build.id %>/cancel" class="button">Cancel build</a>
|
||||
<a href="/build?id=<%= build.sqid %>" class="button">Clone</a>
|
||||
</div>
|
||||
<% if (!ended) { %>
|
||||
<div id="cancelRow">
|
||||
<a href="/build/<%= build.sqid %>/cancel" class="button">Cancel build</a>
|
||||
</div>
|
||||
<% } %>
|
||||
<p><a href="/build/<%= build.id %>/logs">Full logs</a></p>
|
||||
<p><a href="/build/<%= build.sqid %>/logs">Full logs</a></p>
|
||||
<pre class="overflow-x"><div class="logs" id="logs"><% (log || []).forEach(line => { %><p><%= line %></p><% }) %></div></pre>
|
||||
|
||||
<% if (!ended) { %>
|
||||
@@ -37,7 +40,7 @@
|
||||
<%- include("footer", locals) %>
|
||||
<script src="/assets/js/timezone.js?v1" nonce="<%= cspNonce %>"></script>
|
||||
<% if (!ended) { %>
|
||||
<script src="/assets/js/build.js?v2" nonce="<%= cspNonce %>"></script>
|
||||
<script src="/assets/js/build.js?v3" nonce="<%= cspNonce %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -20,7 +20,7 @@
|
||||
</tr>
|
||||
<% builds.forEach(build => { %>
|
||||
<tr>
|
||||
<td><a href="/build/<%= build.id %>"><%= build.repo %></a></td>
|
||||
<td><a href="/build/<%= build.sqid %>"><%= build.repo %></a></td>
|
||||
<td><%= build.distro %></td>
|
||||
<td class="to-local-time"><%= build.startTime %></td>
|
||||
<td><%= timeElapsed(build.startTime, build.endTime) %></td>
|
||||
|
Reference in New Issue
Block a user