From 68236287b4f707cc4c10c4ea179c6bbb1917d7a2 Mon Sep 17 00:00:00 2001 From: Alex Sorafumo Date: Tue, 31 Dec 2024 13:41:34 +1100 Subject: [PATCH] feat(admin): add section for managing build jobs (PPT-1758) --- .../src/app/admin/admin.component.ts | 5 + apps/backoffice/src/app/admin/admin.module.ts | 2 + apps/backoffice/src/app/admin/admin.routes.ts | 2 + .../src/app/admin/build-list.component.ts | 198 ++++++++++++++++++ apps/backoffice/src/app/app.module.ts | 19 +- apps/backoffice/src/assets/locale/en.json | 11 +- apps/backoffice/src/assets/locale/jp.json | 9 +- config/proxy.conf.js | 2 +- 8 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 apps/backoffice/src/app/admin/build-list.component.ts diff --git a/apps/backoffice/src/app/admin/admin.component.ts b/apps/backoffice/src/app/admin/admin.component.ts index d2311eea..f8eabfcf 100644 --- a/apps/backoffice/src/app/admin/admin.component.ts +++ b/apps/backoffice/src/app/admin/admin.component.ts @@ -165,6 +165,11 @@ export class PlaceComponent extends AsyncHandler { name: i18n('ADMIN.TAB_UPLOADS_LIBRARY'), icon: { value: 'photo_album' }, }, + { + id: 'build-jobs', + name: i18n('ADMIN.TAB_BUILD_JOBS'), + icon: { value: 'laps' }, + }, // { // id: 'mailing-list', // name: 'Email Templates', diff --git a/apps/backoffice/src/app/admin/admin.module.ts b/apps/backoffice/src/app/admin/admin.module.ts index ac5be78c..8d5d0638 100644 --- a/apps/backoffice/src/app/admin/admin.module.ts +++ b/apps/backoffice/src/app/admin/admin.module.ts @@ -32,6 +32,7 @@ import { ResourceImportsComponent } from './resource-imports.component'; import { EmailTemplatesComponent } from './mailing-lists/email-templates.component'; import { EmailTemplateFormComponent } from './mailing-lists/email-template-form.component'; import { SharedContentModule } from '../ui/ui.module'; +import { PlaceBuildListComponent } from './build-list.component'; @NgModule({ declarations: [ @@ -60,6 +61,7 @@ import { SharedContentModule } from '../ui/ui.module'; ResourceImportsComponent, EmailTemplatesComponent, EmailTemplateFormComponent, + PlaceBuildListComponent, ], imports: [ CommonModule, diff --git a/apps/backoffice/src/app/admin/admin.routes.ts b/apps/backoffice/src/app/admin/admin.routes.ts index df7a69ed..300d820e 100644 --- a/apps/backoffice/src/app/admin/admin.routes.ts +++ b/apps/backoffice/src/app/admin/admin.routes.ts @@ -19,6 +19,7 @@ import { UploadLibraryComponent } from './upload-library.component'; import { ResourceImportsComponent } from './resource-imports.component'; import { EmailTemplatesComponent } from './mailing-lists/email-templates.component'; import { EmailTemplateFormComponent } from './mailing-lists/email-template-form.component'; +import { PlaceBuildListComponent } from './build-list.component'; export const ROUTES: Routes = [ { @@ -49,6 +50,7 @@ export const ROUTES: Routes = [ { path: '**', redirectTo: '' }, ], }, + { path: 'build-jobs', component: PlaceBuildListComponent }, { path: 'extend/:id', component: ExtensionOutletComponent }, { path: '**', redirectTo: 'about' }, ], diff --git a/apps/backoffice/src/app/admin/build-list.component.ts b/apps/backoffice/src/app/admin/build-list.component.ts new file mode 100644 index 00000000..c4a40eda --- /dev/null +++ b/apps/backoffice/src/app/admin/build-list.component.ts @@ -0,0 +1,198 @@ +import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { del, get, PlaceEdge } from '@placeos/ts-client'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; +import { + catchError, + debounceTime, + map, + shareReplay, + startWith, + switchMap, +} from 'rxjs/operators'; +import { openConfirmModal } from '../common/general'; +import { notifyError, notifySuccess } from '../common/notifications'; +import { toQueryString } from '../common/api'; +import { i18n } from '../common/translate'; + +interface BuildJob { + state: 'pending' | 'running' | 'cancelled' | 'error' | 'done'; + id: string; + message: string; + driver: string; + repo: string; + branch: string; + commit: string; + timestamp: string; +} + +function queryBuildJobs(q: any = {}): Observable { + const query = toQueryString(q); + return get(`/api/build/v1/monitor${query ? '?' + query : ''}`) as any; +} + +function cancelBuildJob(id, q = {}) { + const query = toQueryString(q); + return del(`/api/build/v1/cancel/${id}${query ? '?' + query : ''}`); +} + +@Component({ + selector: '[admin-build-list]', + template: ` +
+
+
+ {{ 'ADMIN.BUILD_LIST_HEADER' | translate }} +
+
+
+ + +
+
+ +
+
{{ row.repo }}
+
{{ row.branch }}
+
+
+ +
+
{{ row.driver }}
+
{{ row.commit }}
+
+
+ +
+ {{ data }} + + {{ 'COMMON.DESCRIPTION_EMPTY' | translate }} + +
+
+ +
+ +
+
+ `, + styles: [ + ` + :host { + height: 100%; + width: 100%; + min-height: 10rem; + } + `, + ], +}) +export class PlaceBuildListComponent { + public loading = ''; + + private _change = new BehaviorSubject(0); + private _hide = new BehaviorSubject(''); + public last_change = new BehaviorSubject(null); + + public get item() { + return this.last_change.getValue(); + } + + private _job_list: Observable = this._change.pipe( + debounceTime(300), + switchMap((_) => { + this.loading = 'Loading Edges...'; + return queryBuildJobs(); + }), + catchError((_) => of({})), + map((details?: { data: PlaceEdge[] }) => { + this.loading = ''; + return (details?.data || []).sort((a, b) => + a.id?.localeCompare(b.id) + ); + }), + startWith([]), + shareReplay(1) + ); + + public readonly jobs = combineLatest([this._job_list, this._hide]).pipe( + debounceTime(500), + map(([list, hide]) => { + if (!hide) return list; + const edges = list.filter((_) => _.id !== hide); + return edges.sort((a, b) => a.id?.localeCompare(b.id)); + }) + ); + + public readonly remove = async (i: BuildJob) => { + const details = await openConfirmModal( + { + title: i18n('ADMIN.BUILD_LIST_REMOVE'), + content: i18n('ADMIN.BUILD_LIST_REMOVE_MSG', { + driver: i.driver, + repo: i.repo, + }), + icon: { type: 'icon', content: 'delete' }, + }, + this._dialog + ); + if (!details) return; + details.loading(i18n('ADMIN.BUILD_LIST_REMOVE_LOADING')); + const err = await cancelBuildJob(i.id) + .toPromise() + .catch((_) => _); + details.close(); + if (err) + return notifyError( + i18n('ADMIN.BUILD_LIST_REMOVE_ERROR', { + error: err.statusText || err.message || err, + }) + ); + this.last_change.next(null); + notifySuccess(i18n('ADMIN.BUILD_LIST_REMOVE_SUCCESS')); + this._hide.next(i.id); + }; + + constructor(private _dialog: MatDialog) {} +} diff --git a/apps/backoffice/src/app/app.module.ts b/apps/backoffice/src/app/app.module.ts index 8cbd17f8..13791380 100644 --- a/apps/backoffice/src/app/app.module.ts +++ b/apps/backoffice/src/app/app.module.ts @@ -18,6 +18,14 @@ import { AuthorisedAdminGuard } from './ui/guards/authorised-admin.guard'; import './mocks'; import { LocaleService } from './common/locale.service'; +import localeFr from '@angular/common/locales/fr'; +import localeJa from '@angular/common/locales/ja'; +import localeAr from '@angular/common/locales/ar'; +import localeZh from '@angular/common/locales/zh'; +import localeEs from '@angular/common/locales/es'; +import localeIt from '@angular/common/locales/it'; +import { registerLocaleData } from '@angular/common'; + @NgModule({ declarations: [AppComponent], bootstrap: [AppComponent], @@ -49,4 +57,13 @@ import { LocaleService } from './common/locale.service'; }, ], }) -export class AppModule {} +export class AppModule { + constructor() { + registerLocaleData(localeFr); + registerLocaleData(localeAr); + registerLocaleData(localeJa); + registerLocaleData(localeZh); + registerLocaleData(localeEs); + registerLocaleData(localeIt); + } +} diff --git a/apps/backoffice/src/assets/locale/en.json b/apps/backoffice/src/assets/locale/en.json index cb2f8a3b..fc08619e 100644 --- a/apps/backoffice/src/assets/locale/en.json +++ b/apps/backoffice/src/assets/locale/en.json @@ -756,6 +756,7 @@ "TAB_CUSTOM_SCHEMAS": "Custom Schemas", "TAB_UPLOAD_STORAGE": "Data Stores", "TAB_UPLOADS_LIBRARY": "Uploads Library", + "TAB_BUILD_JOBS": "Build Jobs", "APPLICATION_DETAILS": "Application Details", "VIEW_CHANGELOG": "View Changelog", "BUILD": "Build", @@ -964,6 +965,14 @@ "UPLOADS_LIB_REMOVE_LOADING": "Removing upload...", "UPLOADS_LIB_FIELD_TYPE": "File Type", "UPLOADS_LIB_FIELD_SIZE": "Size", - "UPLOADS_LIB_LIST_EMPTY": "No uploads for the selected domain" + "UPLOADS_LIB_LIST_EMPTY": "No uploads for the selected domain", + + "BUILD_LIST_HEADER": "Build Service Jobs", + "BUILD_LIST_EMPTY": "No build jobs available to monitor", + "BUILD_LIST_REMOVE": "Cancel Build Job", + "BUILD_LIST_REMOVE_MSG": "Are you sure you wish to cancel the build job for {{ driver }} from {{ repo }}?", + "BUILD_LIST_REMOVE_LOADING": "Cancelling build job...", + "BUILD_LIST_REMOVE_ERROR": "Failed to cancel build job. Error: {{ error }}", + "BUILD_LIST_REMOVE_SUCCESS": "Successfully cancelled build job." } } diff --git a/apps/backoffice/src/assets/locale/jp.json b/apps/backoffice/src/assets/locale/jp.json index c0557206..cdabbb01 100644 --- a/apps/backoffice/src/assets/locale/jp.json +++ b/apps/backoffice/src/assets/locale/jp.json @@ -270,7 +270,8 @@ "TRIGGER_ACTIVE": "トリガー有効", "TRIGGER_ENABLED": "有効", "TRIGGER_EXECUTE_ENABLED": "実行有効", - "TRIGGER_IMPORTANT": "重要" + "TRIGGER_IMPORTANT": "重要", + "MISCONFIGURED": "ルームシステムに必要なすべてのゾーンが含まれていません" }, "MODULES": { "SINGULAR": "モジュール", @@ -325,7 +326,8 @@ "CUSTOM_NAME": "カスタム名", "STATE": "モジュール状態", "STATE_UPDATE": "状態を更新", - "STATE_LOADING": "モジュール状態を読み込み中..." + "STATE_LOADING": "モジュール状態を読み込み中...", + "ERROR": "モジュールでランタイムエラーが発生しました" }, "ZONES": { "SINGULAR": "ゾーン", @@ -373,7 +375,8 @@ "TRIGGERS_EMPTY": "選択されたゾーンにはトリガーがありません", "PARENT_ZONE": "親ゾーン", "NAME_REQUIRED": "一意のゾーン名が必要です", - "DISPLAY_NAME": "表示名" + "DISPLAY_NAME": "表示名", + "MISCONFIGURED": "ゾーン内のタグには親ゾーンが必要です" }, "DRIVERS": { "SINGULAR": "ドライバー", diff --git a/config/proxy.conf.js b/config/proxy.conf.js index 3f297384..44c634f2 100644 --- a/config/proxy.conf.js +++ b/config/proxy.conf.js @@ -14,7 +14,7 @@ const context = [ '/backoffice', '/stylesheets', ]; -const ws_context = ['/api']; +const ws_context = ['/api', '/control']; function add(endpoint, extras = {}) { PROXY_CONFIG[`${endpoint}/**`] = {