From 68005e84922ba228da8c3ffb1d6770e47577695c Mon Sep 17 00:00:00 2001 From: Cory Sanin Date: Mon, 13 Jan 2025 01:58:38 -0500 Subject: [PATCH] log table --- src/BuildController.ts | 6 ++-- src/DB.ts | 68 ++++++++++++++++++++++++++++++++++-------- src/Web.ts | 18 +++++++---- styles/01-styles.scss | 45 ++++++++++++++++++++++------ views/build-new.ejs | 1 + views/build.ejs | 1 + views/footer.ejs | 8 +++++ views/index.ejs | 1 + 8 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 views/footer.ejs diff --git a/src/BuildController.ts b/src/BuildController.ts index 975cec6..48adb78 100644 --- a/src/BuildController.ts +++ b/src/BuildController.ts @@ -91,8 +91,8 @@ class BuildController extends EventEmitter { return (data: Buffer | string) => { const str = data.toString(); const readyToLog = remainder[type] + str.substring(0, str.lastIndexOf('\n')); - remainder[type] = str.substring(str.lastIndexOf('\n')); - this.db.appendLog(build.id, readyToLog); + remainder[type] = str.substring(str.lastIndexOf('\n') + 1); + this.db.appendLog(build.id, type, readyToLog); this.emitLog({ id: build.id, type: type, @@ -153,4 +153,4 @@ class BuildController extends EventEmitter { export default BuildController; export { BuildController }; -export type { }; \ No newline at end of file +export type { BuildEvent, LogType }; \ No newline at end of file diff --git a/src/DB.ts b/src/DB.ts index 73fce8a..2c38067 100644 --- a/src/DB.ts +++ b/src/DB.ts @@ -1,5 +1,6 @@ import { Sequelize, DataTypes, Op, } from 'sequelize'; import type { ModelStatic, Filterable } from 'sequelize'; +import type { LogType } from './BuildController.ts'; type Status = 'queued' | 'running' | 'cancelled' | 'success' | 'error'; type Dependencies = 'stable' | 'testing' | 'staging'; @@ -23,7 +24,13 @@ interface Build { endTime?: Date; status: Status; pid?: number; - log?: string; +} + +interface LogChunk { + id: number + buildId: number + type: LogType, + chunk: string } const MONTH = 1000 * 60 * 60 * 24 * 24; @@ -37,6 +44,7 @@ const SELECT = ['id', 'repo', 'commit', 'distro', 'dependencies', 'startTime', ' class DB { private build: ModelStatic; + private logChunk: ModelStatic; private sequelize: Sequelize; constructor(config: DBConfig = {}) { @@ -88,14 +96,42 @@ class DB { pid: { type: DataTypes.INTEGER, 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 { + await this.build.sync(); + await this.logChunk.sync(); } public async createBuild(repo: string, commit: string, patch: string, distro: string): Promise { @@ -132,13 +168,19 @@ class DB { }); } - public async appendLog(id: number, log: string): Promise { - const sanitizedLog = log.replace(/'/g, "''"); - await this.build.update({ - log: Sequelize.literal(`log || '${sanitizedLog}'`) - }, { + public async appendLog(buildId: number, type: LogType, chunk: string): Promise { + await this.logChunk.create({ + buildId, + type, + chunk + }); + } + + public async getLog(buildId: number): Promise { + return await this.logChunk.findAll({ + order: [['id', 'ASC']], where: { - id + buildId } }); } @@ -151,7 +193,7 @@ class DB { return await this.build.findAll({ attributes: SELECT, order: [['id', 'DESC']], - where: FRESH, + where: FRESH }); } diff --git a/src/Web.ts b/src/Web.ts index f022e1f..ad94c8c 100644 --- a/src/Web.ts +++ b/src/Web.ts @@ -21,10 +21,12 @@ class Web { private _webserver: http.Server | null = null; private db: DB; private buildController: BuildController; + private app: Express; + private port: number; constructor(options: WebConfig = {}) { - const app: Express = express(); - const port: number = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080; + const app: Express = this.app = express(); + this.port = notStupidParseInt(process.env.PORT) || options['port'] as number || 8080; app.set('trust proxy', 1); app.set('view engine', 'ejs'); @@ -84,6 +86,8 @@ class Web { res.sendStatus(404); return; } + const log = (await this.db.getLog(build.id)).map(logChunk => logChunk.chunk.split('\n')).flat(); + res.render('build', { page: { title: 'Archery', @@ -91,7 +95,7 @@ class Web { description: `Building ${build.repo} on ${build.distro}` }, build, - log: build.log?.split('\n') + log }); }); @@ -101,14 +105,13 @@ class Web { res.sendStatus(404); 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) => { res.send('Healthy'); }); - - this._webserver = app.listen(port, () => console.log(`archery is running on port ${port}`)); } close = () => { @@ -119,6 +122,9 @@ class Web { setDB = (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) => { diff --git a/styles/01-styles.scss b/styles/01-styles.scss index 5308587..c854a80 100644 --- a/styles/01-styles.scss +++ b/styles/01-styles.scss @@ -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 { list-style: none; padding: 0; @@ -166,6 +182,10 @@ input[type="submit"] { .content { margin-left: 16em; padding: 1em; + + &.no-padding { + padding: 0; + } } .grid-2col { @@ -196,18 +216,25 @@ input[type="submit"] { } } -.sidebar_search { - padding: .3em 1em; +.content.footer { + background: var(--primary-dark); + color: var(--color-text); + margin-top: .5em; + padding: .25em 1em; - form input { - font-size: 1.1em; - width: 100%; - background: #222; + a { color: var(--color-text); - border: var(--primary-light) solid .12em; + text-decoration: underline; + } - &:hover { - background: #2a2a2a; + ul { + list-style: none; + font-size: .65em; + + li { + display: inline-block; + padding: 0 1em 0 0; + font-size: inherit; } } } diff --git a/views/build-new.ejs b/views/build-new.ejs index 44d57b1..106e37c 100644 --- a/views/build-new.ejs +++ b/views/build-new.ejs @@ -21,5 +21,6 @@
+ <%- include("footer", locals) %> diff --git a/views/build.ejs b/views/build.ejs index 839012c..8b16948 100644 --- a/views/build.ejs +++ b/views/build.ejs @@ -24,5 +24,6 @@ <% }) %> + <%- include("footer", locals) %> diff --git a/views/footer.ejs b/views/footer.ejs new file mode 100644 index 0000000..bc19c7f --- /dev/null +++ b/views/footer.ejs @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index 4b9395e..4f7ae55 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -28,5 +28,6 @@ <% }) %> + <%- include("footer", locals) %>