log table
This commit is contained in:
parent
da8d0fcc7e
commit
68005e8492
@ -91,8 +91,8 @@ class BuildController extends EventEmitter {
|
|||||||
return (data: Buffer | string) => {
|
return (data: Buffer | string) => {
|
||||||
const str = data.toString();
|
const str = data.toString();
|
||||||
const readyToLog = remainder[type] + str.substring(0, str.lastIndexOf('\n'));
|
const readyToLog = remainder[type] + str.substring(0, str.lastIndexOf('\n'));
|
||||||
remainder[type] = str.substring(str.lastIndexOf('\n'));
|
remainder[type] = str.substring(str.lastIndexOf('\n') + 1);
|
||||||
this.db.appendLog(build.id, readyToLog);
|
this.db.appendLog(build.id, type, readyToLog);
|
||||||
this.emitLog({
|
this.emitLog({
|
||||||
id: build.id,
|
id: build.id,
|
||||||
type: type,
|
type: type,
|
||||||
@ -153,4 +153,4 @@ class BuildController extends EventEmitter {
|
|||||||
|
|
||||||
export default BuildController;
|
export default BuildController;
|
||||||
export { BuildController };
|
export { BuildController };
|
||||||
export type { };
|
export type { BuildEvent, LogType };
|
68
src/DB.ts
68
src/DB.ts
@ -1,5 +1,6 @@
|
|||||||
import { Sequelize, DataTypes, Op, } from 'sequelize';
|
import { Sequelize, DataTypes, Op, } from 'sequelize';
|
||||||
import type { ModelStatic, Filterable } from 'sequelize';
|
import type { ModelStatic, Filterable } from 'sequelize';
|
||||||
|
import type { LogType } from './BuildController.ts';
|
||||||
|
|
||||||
type Status = 'queued' | 'running' | 'cancelled' | 'success' | 'error';
|
type Status = 'queued' | 'running' | 'cancelled' | 'success' | 'error';
|
||||||
type Dependencies = 'stable' | 'testing' | 'staging';
|
type Dependencies = 'stable' | 'testing' | 'staging';
|
||||||
@ -23,7 +24,13 @@ interface Build {
|
|||||||
endTime?: Date;
|
endTime?: Date;
|
||||||
status: Status;
|
status: Status;
|
||||||
pid?: number;
|
pid?: number;
|
||||||
log?: string;
|
}
|
||||||
|
|
||||||
|
interface LogChunk {
|
||||||
|
id: number
|
||||||
|
buildId: number
|
||||||
|
type: LogType,
|
||||||
|
chunk: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTH = 1000 * 60 * 60 * 24 * 24;
|
const MONTH = 1000 * 60 * 60 * 24 * 24;
|
||||||
@ -37,6 +44,7 @@ const SELECT = ['id', 'repo', 'commit', 'distro', 'dependencies', 'startTime', '
|
|||||||
|
|
||||||
class DB {
|
class DB {
|
||||||
private build: ModelStatic<any>;
|
private build: ModelStatic<any>;
|
||||||
|
private logChunk: ModelStatic<any>;
|
||||||
private sequelize: Sequelize;
|
private sequelize: Sequelize;
|
||||||
|
|
||||||
constructor(config: DBConfig = {}) {
|
constructor(config: DBConfig = {}) {
|
||||||
@ -88,14 +96,42 @@ class DB {
|
|||||||
pid: {
|
pid: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
},
|
|
||||||
log: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.build.sync();
|
this.logChunk = this.sequelize.define('logChunk', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
buildId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: 'builds',
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: DataTypes.ENUM('std', 'err'),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'std'
|
||||||
|
},
|
||||||
|
chunk: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sync(): Promise<void> {
|
||||||
|
await this.build.sync();
|
||||||
|
await this.logChunk.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createBuild(repo: string, commit: string, patch: string, distro: string): Promise<number> {
|
public async createBuild(repo: string, commit: string, patch: string, distro: string): Promise<number> {
|
||||||
@ -132,13 +168,19 @@ class DB {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async appendLog(id: number, log: string): Promise<void> {
|
public async appendLog(buildId: number, type: LogType, chunk: string): Promise<void> {
|
||||||
const sanitizedLog = log.replace(/'/g, "''");
|
await this.logChunk.create({
|
||||||
await this.build.update({
|
buildId,
|
||||||
log: Sequelize.literal(`log || '${sanitizedLog}'`)
|
type,
|
||||||
}, {
|
chunk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLog(buildId: number): Promise<LogChunk[]> {
|
||||||
|
return await this.logChunk.findAll({
|
||||||
|
order: [['id', 'ASC']],
|
||||||
where: {
|
where: {
|
||||||
id
|
buildId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -151,7 +193,7 @@ class DB {
|
|||||||
return await this.build.findAll({
|
return await this.build.findAll({
|
||||||
attributes: SELECT,
|
attributes: SELECT,
|
||||||
order: [['id', 'DESC']],
|
order: [['id', 'DESC']],
|
||||||
where: FRESH,
|
where: FRESH
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/Web.ts
18
src/Web.ts
@ -21,10 +21,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 port: number;
|
||||||
|
|
||||||
constructor(options: WebConfig = {}) {
|
constructor(options: WebConfig = {}) {
|
||||||
const app: Express = express();
|
const app: Express = this.app = express();
|
||||||
const port: number = 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);
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
@ -84,6 +86,8 @@ class Web {
|
|||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const log = (await this.db.getLog(build.id)).map(logChunk => logChunk.chunk.split('\n')).flat();
|
||||||
|
|
||||||
res.render('build', {
|
res.render('build', {
|
||||||
page: {
|
page: {
|
||||||
title: 'Archery',
|
title: 'Archery',
|
||||||
@ -91,7 +95,7 @@ class Web {
|
|||||||
description: `Building ${build.repo} on ${build.distro}`
|
description: `Building ${build.repo} on ${build.distro}`
|
||||||
},
|
},
|
||||||
build,
|
build,
|
||||||
log: build.log?.split('\n')
|
log
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,14 +105,13 @@ class Web {
|
|||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.set('Content-Type', 'text/plain').send(build.log);
|
const log = (await this.db.getLog(build.id)).map(logChunk => logChunk.chunk).join('\n');
|
||||||
|
res.set('Content-Type', 'text/plain').send(log);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/healthcheck', (_, res) => {
|
app.get('/healthcheck', (_, res) => {
|
||||||
res.send('Healthy');
|
res.send('Healthy');
|
||||||
});
|
});
|
||||||
|
|
||||||
this._webserver = app.listen(port, () => console.log(`archery is running on port ${port}`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
@ -119,6 +122,9 @@ class Web {
|
|||||||
|
|
||||||
setDB = (db: DB) => {
|
setDB = (db: DB) => {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
if (!this._webserver) {
|
||||||
|
this._webserver = this.app.listen(this.port, () => console.log(`archery is running on port ${this.port}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setBuildController = (buildController: BuildController) => {
|
setBuildController = (buildController: BuildController) => {
|
||||||
|
@ -141,6 +141,22 @@ input[type="submit"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar_search {
|
||||||
|
padding: .3em 1em;
|
||||||
|
|
||||||
|
form input {
|
||||||
|
font-size: 1.1em;
|
||||||
|
width: 100%;
|
||||||
|
background: #222;
|
||||||
|
color: var(--color-text);
|
||||||
|
border: var(--primary-light) solid .12em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ul.sidebar_links {
|
ul.sidebar_links {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -166,6 +182,10 @@ input[type="submit"] {
|
|||||||
.content {
|
.content {
|
||||||
margin-left: 16em;
|
margin-left: 16em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
|
&.no-padding {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-2col {
|
.grid-2col {
|
||||||
@ -196,18 +216,25 @@ input[type="submit"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar_search {
|
.content.footer {
|
||||||
padding: .3em 1em;
|
background: var(--primary-dark);
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-top: .5em;
|
||||||
|
padding: .25em 1em;
|
||||||
|
|
||||||
form input {
|
a {
|
||||||
font-size: 1.1em;
|
|
||||||
width: 100%;
|
|
||||||
background: #222;
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
border: var(--primary-light) solid .12em;
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
ul {
|
||||||
background: #2a2a2a;
|
list-style: none;
|
||||||
|
font-size: .65em;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1em 0 0;
|
||||||
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,6 @@
|
|||||||
<div class="span-2"><button type="submit">Build</button></div>
|
<div class="span-2"><button type="submit">Build</button></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<%- include("footer", locals) %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -24,5 +24,6 @@
|
|||||||
<% }) %>
|
<% }) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<%- include("footer", locals) %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
8
views/footer.ejs
Normal file
8
views/footer.ejs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div class="content footer">
|
||||||
|
<footer>
|
||||||
|
<ul>
|
||||||
|
<li>Developed by Cory Sanin</li>
|
||||||
|
<li><a href="/?TODO">Source</a></li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
|
</div>
|
@ -28,5 +28,6 @@
|
|||||||
<% }) %>
|
<% }) %>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<%- include("footer", locals) %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user