Add cancel button
This commit is contained in:
parent
40f04162d0
commit
413a756951
@ -7,6 +7,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const elements = document.getElementsByClassName('to-local-time');
|
const elements = document.getElementsByClassName('to-local-time');
|
||||||
for (let element of elements) {
|
for (let element of elements) {
|
||||||
const innerText = element.innerText;
|
const innerText = element.innerText;
|
||||||
|
if (element.firstChild) {
|
||||||
element.replaceChild(document.createTextNode(window.formatTime(innerText)), element.firstChild);
|
element.replaceChild(document.createTextNode(window.formatTime(innerText)), element.firstChild);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
element.appendChild(document.createTextNode('-'));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
@ -15,11 +15,15 @@ interface BuildEvent {
|
|||||||
message: any;
|
message: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContainerName(id: number) {
|
||||||
|
return `archery-build-${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
class BuildController extends EventEmitter {
|
class BuildController extends EventEmitter {
|
||||||
private db: DB;
|
private db: DB;
|
||||||
private process: spawn.ChildProcess | null = null;
|
|
||||||
private running: boolean = false;
|
private running: boolean = false;
|
||||||
private interval: NodeJS.Timeout;
|
private interval: NodeJS.Timeout;
|
||||||
|
private cancelled: boolean = false;
|
||||||
|
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
super();
|
super();
|
||||||
@ -37,6 +41,7 @@ class BuildController extends EventEmitter {
|
|||||||
|
|
||||||
private kickOffBuild = async () => {
|
private kickOffBuild = async () => {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
this.cancelled = false;
|
||||||
const build = await this.db.dequeue();
|
const build = await this.db.dequeue();
|
||||||
if (build === null) {
|
if (build === null) {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
@ -80,7 +85,7 @@ class BuildController extends EventEmitter {
|
|||||||
|
|
||||||
private build = async (build: Build) => {
|
private build = async (build: Build) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const docker = this.process = spawn.spawn('docker', this.createBuildParams(build));
|
const docker = spawn.spawn('docker', this.createBuildParams(build));
|
||||||
docker.on('spawn', () => {
|
docker.on('spawn', () => {
|
||||||
const remainder = {
|
const remainder = {
|
||||||
std: '',
|
std: '',
|
||||||
@ -106,8 +111,7 @@ class BuildController extends EventEmitter {
|
|||||||
docker.stderr.on('data', createLogFunction('err'));
|
docker.stderr.on('data', createLogFunction('err'));
|
||||||
});
|
});
|
||||||
docker.on('close', (code) => {
|
docker.on('close', (code) => {
|
||||||
this.process = null;
|
const status = code === 0 ? 'success' : (this.cancelled ? 'cancelled' : 'error');
|
||||||
const status = code === 0 ? 'success' : 'error';
|
|
||||||
this.emitLog({
|
this.emitLog({
|
||||||
id: build.id,
|
id: build.id,
|
||||||
type: 'finish',
|
type: 'finish',
|
||||||
@ -115,12 +119,7 @@ class BuildController extends EventEmitter {
|
|||||||
});
|
});
|
||||||
this.db.finishBuild(build.id, status);
|
this.db.finishBuild(build.id, status);
|
||||||
|
|
||||||
if (code === 0) {
|
|
||||||
resolve();
|
resolve();
|
||||||
}
|
|
||||||
else {
|
|
||||||
reject(code);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -136,15 +135,40 @@ class BuildController extends EventEmitter {
|
|||||||
// TODO: implement COMMIT
|
// TODO: implement COMMIT
|
||||||
params.push('-e', `COMMIT=${build.commit}`);
|
params.push('-e', `COMMIT=${build.commit}`);
|
||||||
}
|
}
|
||||||
|
params.push('--name', getContainerName(build.id));
|
||||||
params.push(docker_images[build.distro]);
|
params.push(docker_images[build.distro]);
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelBuild = async (pid: number) => {
|
cancelBuild = async (id: number) => {
|
||||||
const p = this.process
|
const running = this.running;
|
||||||
if (p && p.pid === pid) {
|
const build = await this.db.getBuild(id);
|
||||||
return p.kill();
|
if (running && build.status === 'queued') {
|
||||||
|
await this.db.finishBuild(id, 'cancelled');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const dockerPs = spawn.spawn('docker', ['ps', '--filter', `name=${getContainerName(id)}`, '--format', '{{.ID}}']);
|
||||||
|
let output = '';
|
||||||
|
dockerPs.on('spawn', () => {
|
||||||
|
dockerPs.stdout.on('data', (data: Buffer | string) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dockerPs.on('close', (code) => {
|
||||||
|
if (code > 0) {
|
||||||
|
return reject('failed to get container id');
|
||||||
|
}
|
||||||
|
this.cancelled = true;
|
||||||
|
const dockerKill = spawn.spawn('docker', ['stop', output.trim()]);
|
||||||
|
dockerKill.on('close', (code) => {
|
||||||
|
if (code > 0) {
|
||||||
|
return reject('failed to kill container');
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setDB = (db: DB) => {
|
setDB = (db: DB) => {
|
||||||
|
23
src/Web.ts
23
src/Web.ts
@ -19,7 +19,7 @@ function notStupidParseInt(v: string | undefined): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function timeElapsed(date1: Date, date2: Date) {
|
function timeElapsed(date1: Date, date2: Date) {
|
||||||
if (!date2 || ! date1) {
|
if (!date2 || !date1) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
const ms = Math.abs(date2.getTime() - date1.getTime());
|
const ms = Math.abs(date2.getTime() - date1.getTime());
|
||||||
@ -33,12 +33,12 @@ class Web {
|
|||||||
private _webserver: http.Server | null = null;
|
private _webserver: http.Server | null = null;
|
||||||
private db: DB;
|
private db: DB;
|
||||||
private buildController: BuildController;
|
private buildController: BuildController;
|
||||||
private app: Express;
|
private app: expressWs.Application;
|
||||||
private port: number;
|
private port: number;
|
||||||
|
|
||||||
constructor(options: WebConfig = {}) {
|
constructor(options: WebConfig = {}) {
|
||||||
const app: Express = this.app = express();
|
const app: Express = express();
|
||||||
const wsApp = expressWs(app).app;
|
const wsApp = this.app = expressWs(app).app;
|
||||||
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
|
this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080;
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
@ -120,6 +120,21 @@ class Web {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/build/:num/cancel', async (req, res) => {
|
||||||
|
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||||
|
if (!build) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.buildController.cancelBuild(build.id);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
res.redirect(`/build/${build.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/build/:num/logs/?', async (req, res) => {
|
app.get('/build/:num/logs/?', async (req, res) => {
|
||||||
const build = await this.db.getBuild(parseInt(req.params.num));
|
const build = await this.db.getBuild(parseInt(req.params.num));
|
||||||
if (!build) {
|
if (!build) {
|
||||||
|
@ -99,6 +99,7 @@ form {
|
|||||||
button,
|
button,
|
||||||
a.button,
|
a.button,
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
|
font-size: 12pt;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
border: .4em double var(--primary);
|
border: .4em double var(--primary);
|
||||||
border-radius: .6em;
|
border-radius: .6em;
|
||||||
@ -106,7 +107,7 @@ input[type="submit"] {
|
|||||||
padding: .3em .6em;
|
padding: .3em .6em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-bottom: .4em;
|
margin-bottom: .4em;
|
||||||
|
text-decoration: none;
|
||||||
box-shadow: 0px .4em 0 0 var(--primary-dark);
|
box-shadow: 0px .4em 0 0 var(--primary-dark);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -18,6 +18,11 @@
|
|||||||
<label>Dependencies</label> <span><%= build.dependencies %></span>
|
<label>Dependencies</label> <span><%= build.dependencies %></span>
|
||||||
<label>Start time</label> <span class="to-local-time"><%= build.startTime %></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>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<p><a href="/build/<%= build.id %>/logs">Full logs</a></p>
|
<p><a href="/build/<%= build.id %>/logs">Full logs</a></p>
|
||||||
<div class="logs" id="logs">
|
<div class="logs" id="logs">
|
||||||
<% (log || []).forEach(line => { %>
|
<% (log || []).forEach(line => { %>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user