From 736ea10154a532961ed7b1e14823bf4682e53257 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 1 Jun 2026 23:13:09 +0200 Subject: [PATCH 01/11] Begin working on the client-side mailbroker management --- src/apps/admin/adminportal/types.ts | 5 + src/apps/admin/mailbroker/Mailbroker.svelte | 44 ++ .../mailbroker/Mailbroker/Sidebar.svelte | 19 + src/apps/admin/mailbroker/MailbrokerApp.ts | 43 ++ src/apps/admin/mailbroker/Pages/Logs.svelte | 13 + src/apps/admin/mailbroker/Pages/Sent.svelte | 7 + .../admin/mailbroker/Pages/Templates.svelte | 7 + src/apps/admin/mailbroker/runtime.ts | 43 ++ src/apps/admin/mailbroker/store.ts | 35 + src/apps/admin/mailbroker/types.ts | 15 + src/css/apps/admin/adminportal.css | 1 + src/css/apps/admin/adminportal/table.css | 35 + src/interfaces/runtimes/IMailbrokerRuntime.ts | 9 + src/interfaces/services/IAdminBootstrapper.ts | 27 +- src/ts/apps/store.ts | 1 + .../services/AdminBootstrapper/index.ts | 643 ++++++++++-------- .../services/AdminBootstrapper/store.ts | 16 + src/types/server/admin.ts | 20 +- src/types/server/mailbroker.ts | 66 ++ 19 files changed, 747 insertions(+), 302 deletions(-) create mode 100644 src/apps/admin/mailbroker/Mailbroker.svelte create mode 100644 src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte create mode 100644 src/apps/admin/mailbroker/MailbrokerApp.ts create mode 100644 src/apps/admin/mailbroker/Pages/Logs.svelte create mode 100644 src/apps/admin/mailbroker/Pages/Sent.svelte create mode 100644 src/apps/admin/mailbroker/Pages/Templates.svelte create mode 100644 src/apps/admin/mailbroker/runtime.ts create mode 100644 src/apps/admin/mailbroker/store.ts create mode 100644 src/apps/admin/mailbroker/types.ts create mode 100644 src/css/apps/admin/adminportal/table.css create mode 100644 src/interfaces/runtimes/IMailbrokerRuntime.ts create mode 100644 src/types/server/mailbroker.ts diff --git a/src/apps/admin/adminportal/types.ts b/src/apps/admin/adminportal/types.ts index 052997047..10ea56d9e 100755 --- a/src/apps/admin/adminportal/types.ts +++ b/src/apps/admin/adminportal/types.ts @@ -16,6 +16,7 @@ import type { SharedDriveType } from "$types/server/shares"; import type { ExpandedUserInfo, UserInfo } from "$types/user"; import type { ReadableStore } from "$types/shared/writable"; import type { Component } from "svelte"; +import type { Mailbroker } from "$types/server/mailbroker"; export interface AdminPortalPage { name: string; @@ -111,6 +112,10 @@ export type VersioningData = { users: ExpandedUserInfo[]; }; +export type MailbrokerTemplatesData = { + templates: ICommandResult; +}; + export type ScopesData = { admins: ExpandedUserInfo[]; }; diff --git a/src/apps/admin/mailbroker/Mailbroker.svelte b/src/apps/admin/mailbroker/Mailbroker.svelte new file mode 100644 index 000000000..b5aef1bfe --- /dev/null +++ b/src/apps/admin/mailbroker/Mailbroker.svelte @@ -0,0 +1,44 @@ + + + +
+ +
+ {#if loading} + loading + {:else if Page && data?.success} + {@const result = data.result!} + + {:else} + error: {data?.errorMessage ?? "Unknown error"} + {/if} +
+
diff --git a/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte b/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte new file mode 100644 index 000000000..3055452fd --- /dev/null +++ b/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte @@ -0,0 +1,19 @@ + + +{#each [...mailbrokerPages] as [id, page]} + {#if !page.hidden} + + {#if page.separator} +
+ {/if} + {/if} +{/each} diff --git a/src/apps/admin/mailbroker/MailbrokerApp.ts b/src/apps/admin/mailbroker/MailbrokerApp.ts new file mode 100644 index 000000000..b0b43422d --- /dev/null +++ b/src/apps/admin/mailbroker/MailbrokerApp.ts @@ -0,0 +1,43 @@ +import type { App } from "$types/apps/app"; +import Mailbroker from "./Mailbroker.svelte"; +import { MailbrokerRuntime } from "./runtime"; + +const MailbrokerApp: App = { + metadata: { + name: "Mailbroker Manager", + version: "1.0.0", + author: "Izaak Kuipers", + appGroup: "adminTools", + icon: "ComponentIcon", + }, + position: { centered: true }, + size: { w: 640, h: 480 }, + minSize: { w: 640, h: 480 }, + maxSize: { w: 640, h: 480 }, + state: { + maximized: false, + minimized: false, + resizable: true, + fullscreen: false, + headless: true, + }, + controls: { + minimize: true, + maximize: true, + close: true, + }, + assets: { + runtime: MailbrokerRuntime, + component: Mailbroker as any, + }, + glass: true, + elevated: true, + hidden: false, + core: false, + overlay: false, + noSafeMode: false, + vital: true, + id: "Mailbroker", +}; + +export default MailbrokerApp; diff --git a/src/apps/admin/mailbroker/Pages/Logs.svelte b/src/apps/admin/mailbroker/Pages/Logs.svelte new file mode 100644 index 000000000..55a0a6a20 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/Logs.svelte @@ -0,0 +1,13 @@ + + + + + + + + +
\ No newline at end of file diff --git a/src/apps/admin/mailbroker/Pages/Sent.svelte b/src/apps/admin/mailbroker/Pages/Sent.svelte new file mode 100644 index 000000000..a2efb6c1c --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/Sent.svelte @@ -0,0 +1,7 @@ + + +{data.length} diff --git a/src/apps/admin/mailbroker/Pages/Templates.svelte b/src/apps/admin/mailbroker/Pages/Templates.svelte new file mode 100644 index 000000000..15aece798 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/Templates.svelte @@ -0,0 +1,7 @@ + + +{data.length} diff --git a/src/apps/admin/mailbroker/runtime.ts b/src/apps/admin/mailbroker/runtime.ts new file mode 100644 index 000000000..24da860c3 --- /dev/null +++ b/src/apps/admin/mailbroker/runtime.ts @@ -0,0 +1,43 @@ +import type { IMailbrokerRuntime } from "$interfaces/runtimes/IMailbrokerRuntime"; +import type { IAdminBootstrapper } from "$interfaces/services/IAdminBootstrapper"; +import { AppProcess } from "$ts/apps/process"; +import { Daemon } from "$ts/env"; +import { Store } from "$ts/writable"; +import type { AppProcessData } from "$types/apps/app"; +import { mailbrokerPages } from "./store"; + +export class MailbrokerRuntime extends AppProcess implements IMailbrokerRuntime { + currentPage = Store(""); + get admin() { + return Daemon.serviceHost?.getService("AdminBootstrapper")!; + } + + //#region LIFECYCLE + + constructor(pid: number, parentPid: number, app: AppProcessData) { + super(pid, parentPid, app); + + this.switchPage("templates"); + + this.setSource(__SOURCE__); + } + + switchPage(pageId: string) { + this.Log(`Loading page '${pageId}'`); + + if (!mailbrokerPages.has(pageId)) return; + + const page = mailbrokerPages.get(pageId); + + this.currentPage.set(pageId); + this.windowTitle.set(`${page?.name}`); + } + + async start() {} + + async stop() {} + + async render() {} + + //#endregion LIFECYCLE +} diff --git a/src/apps/admin/mailbroker/store.ts b/src/apps/admin/mailbroker/store.ts new file mode 100644 index 000000000..79f267b4c --- /dev/null +++ b/src/apps/admin/mailbroker/store.ts @@ -0,0 +1,35 @@ +import Logs from "./Pages/Logs.svelte"; +import Sent from "./Pages/Sent.svelte"; +import Templates from "./Pages/Templates.svelte"; +import type { MailbrokerPage, MailbrokerPages } from "./types"; + +export const mailbrokerPages: MailbrokerPages = new Map([ + [ + "templates", + { + name: "Templates", + icon: "mails", + content: Templates, + data: async (process) => await process.admin.getMailbrokerTemplates(), + }, + ], + [ + "sent", + { + name: "Sent emails", + icon: "send", + content: Sent, + separator: true, + data: async (process) => await process.admin.getMailbrokerSentRecords(), + }, + ], + [ + "logs", + { + name: "Logs", + icon: "list", + content: Logs, + data: async (process) => await process.admin.getMailbrokerLogs(), + }, + ], +]); diff --git a/src/apps/admin/mailbroker/types.ts b/src/apps/admin/mailbroker/types.ts new file mode 100644 index 000000000..cae438e25 --- /dev/null +++ b/src/apps/admin/mailbroker/types.ts @@ -0,0 +1,15 @@ +import type { ICommandResult } from "$interfaces/ICommandResult"; +import type { IMailbrokerRuntime } from "$interfaces/runtimes/IMailbrokerRuntime"; +import type { MaybePromise } from "$types/shared/common"; +import type { Component } from "svelte"; + +export interface MailbrokerPage { + name: string; + icon: string; + content: Component; + hidden?: boolean; + separator?: boolean; + data?: (process: IMailbrokerRuntime) => MaybePromise>>; +} + +export type MailbrokerPages = Map; diff --git a/src/css/apps/admin/adminportal.css b/src/css/apps/admin/adminportal.css index 741efa73f..4d1610c03 100755 --- a/src/css/apps/admin/adminportal.css +++ b/src/css/apps/admin/adminportal.css @@ -17,6 +17,7 @@ @import url("./adminportal/page/auditlog.css"); @import url("./adminportal/page/logs.css"); @import url("./adminportal/sidebar.css"); +@import url("./adminportal/table.css"); @import url("./adminportal/userdata.css"); #AdminPortal { diff --git a/src/css/apps/admin/adminportal/table.css b/src/css/apps/admin/adminportal/table.css new file mode 100644 index 000000000..66c9a4f43 --- /dev/null +++ b/src/css/apps/admin/adminportal/table.css @@ -0,0 +1,35 @@ +#AdminPortal > div.body table { + display: block; + width: 100%; + width: max-content; + max-width: 100%; + overflow: auto; + border-spacing: 0; + border-collapse: collapse; + margin: -1px; +} + +#AdminPortal > div.body table tr { + background-color: var(--button-bg); + border-top: var(--win-border); +} + +#AdminPortal > div.body table tr:hover td { + background-color: var(--button-glass-hover-bg); +} + +#AdminPortal > div.body table th, +#AdminPortal > div.body table td { + padding: 5px 8px; + border: var(--win-border); + text-align: left !important; + white-space: nowrap; +} + +#AdminPortal > div.body table tr th { + font-weight: bold !important; +} + +#AdminPortal > div.body table tr:nth-child(2n) { + background-color: var(--button-bg) 1px solid; +} diff --git a/src/interfaces/runtimes/IMailbrokerRuntime.ts b/src/interfaces/runtimes/IMailbrokerRuntime.ts new file mode 100644 index 000000000..657b2c09c --- /dev/null +++ b/src/interfaces/runtimes/IMailbrokerRuntime.ts @@ -0,0 +1,9 @@ +import type { IAppProcess } from "$interfaces/IAppProcess"; +import type { IAdminBootstrapper } from "$interfaces/services/IAdminBootstrapper"; +import type { ReadableStore } from "$types/shared/writable"; + +export interface IMailbrokerRuntime extends IAppProcess { + get admin(): IAdminBootstrapper; + currentPage: ReadableStore; + switchPage(pageId: string): void; +} diff --git a/src/interfaces/services/IAdminBootstrapper.ts b/src/interfaces/services/IAdminBootstrapper.ts index 56b180199..bdb7520e7 100644 --- a/src/interfaces/services/IAdminBootstrapper.ts +++ b/src/interfaces/services/IAdminBootstrapper.ts @@ -16,10 +16,11 @@ import type { UserTotp, } from "$types/server/admin"; import type { BugReport, ReportStatistics } from "$types/server/bughunt"; -import type { FilesystemProgressCallback, FsAccess, UserQuota } from "$types/system/fs"; -import type { StoreItem } from "$types/tpa/package"; +import type { Mailbroker } from "$types/server/mailbroker"; import type { QueryResult } from "$types/server/query"; import type { SharedDriveType } from "$types/server/shares"; +import type { FilesystemProgressCallback, FsAccess, UserQuota } from "$types/system/fs"; +import type { StoreItem } from "$types/tpa/package"; import type { ExpandedUserInfo, UserInfo, UserPreferences } from "$types/user"; export interface IAdminBootstrapper extends IBaseService { @@ -118,4 +119,26 @@ export interface IAdminBootstrapper extends IBaseService { getMigrationIndexFor(userId: string): Promise>; GetIpAddresses(): Promise; getReportSourceFile(report: BugReport): Promise>; + getMailbrokerKey(id: string): Promise>; + getMailbrokerLogs(): Promise>; + getMailbrokerTemplate(id: string): Promise>; + getMailbrokerTemplates(): Promise>; + getMailbrokerSentRecord(id: string): Promise>; + getMailbrokerSentRecords(): Promise>; + deleteMailbrokerKey(id: string): Promise; + disableMailbrokerKey(id: string): Promise; + enableMailbrokerKey(id: string): Promise; + createMailbrokerKey(serverName: string): Promise>; + sendMailTemplateById(id: string, userId: string, props: Record): Promise>; + sendMailTemplateByName( + name: string, + userId: string, + props: Record + ): Promise>; + deleteMailbrokerSentRecord(id: string): Promise; + deprecateMailbrokerTemplate(id: string): Promise; + undeprecateMailbrokerTemplate(id: string): Promise; + deleteMailbrokerTemplate(id: string): Promise; + updateMailbrokerTemplate(id: string, update: Mailbroker.MailTemplateUpdate): Promise; + createMailbrokerTemplate(data: Mailbroker.MailTemplateCreate): Promise; } diff --git a/src/ts/apps/store.ts b/src/ts/apps/store.ts index 03a0e9595..6f3cb7c49 100755 --- a/src/ts/apps/store.ts +++ b/src/ts/apps/store.ts @@ -68,6 +68,7 @@ export const BuiltinAppImportPathAbsolutes = import.meta.glob([ export const AdminAppImportPathAbsolutes = import.meta.glob([ "$apps/admin/adminportal/AdminPortal", "$apps/admin/executequery/ExecuteQueryApp", + "$apps/admin/mailbroker/MailbrokerApp", ]); export const appShortcuts: [number, AppKeyCombinations][] = []; diff --git a/src/ts/servicehost/services/AdminBootstrapper/index.ts b/src/ts/servicehost/services/AdminBootstrapper/index.ts index 7df637542..5ecae8c70 100644 --- a/src/ts/servicehost/services/AdminBootstrapper/index.ts +++ b/src/ts/servicehost/services/AdminBootstrapper/index.ts @@ -1,3 +1,4 @@ +import type { ICommandResult } from "$interfaces/ICommandResult"; import type { IServiceHost } from "$interfaces/IServiceHost"; import type { IStoreConnector } from "$interfaces/modules/server/IStoreConnector"; import type { IUserConnector } from "$interfaces/modules/server/IUserConnector"; @@ -12,6 +13,7 @@ import { AdminServerDrive } from "$ts/kernel/mods/fs/drives/aefs"; import { Backend } from "$ts/kernel/mods/server/axios"; import { ArcBuild } from "$ts/metadata/build"; import { ArcMode } from "$ts/metadata/mode"; +import { CommandResult } from "$ts/result"; import { BaseService } from "$ts/servicehost/base"; import { UserPaths } from "$ts/user/store"; import { deepCopyWithBlobs } from "$ts/util"; @@ -21,6 +23,7 @@ import { toForm } from "$ts/util/form"; import { join } from "$ts/util/fs"; import { tryJsonParse } from "$ts/util/json"; import { compareVersion } from "$ts/util/version"; +import type { App, InstalledApp } from "$types/apps/app"; import type { Activity, AuditLog, @@ -36,26 +39,33 @@ import type { UserStatistics, UserTotp, } from "$types/server/admin"; -import type { App, InstalledApp } from "$types/apps/app"; import type { BugReport, ReportStatistics } from "$types/server/bughunt"; +import type { Mailbroker } from "$types/server/mailbroker"; +import type { QueryResult } from "$types/server/query"; +import type { SharedDriveType } from "$types/server/shares"; +import type { Service } from "$types/services/service"; import type { FilesystemProgressCallback, UserQuota } from "$types/system/fs"; import type { ArcPackage, StoreItem } from "$types/tpa/package"; -import type { Service } from "$types/services/service"; -import type { SharedDriveType } from "$types/server/shares"; import type { ExpandedUserInfo, UserInfo, UserPreferences } from "$types/user"; +import axios from "axios"; import { fromExtension } from "human-filetypes"; +import beautify from "js-beautify"; import JSZip from "jszip"; +import { parse } from "stacktrace-parser"; import { AdminProtocolHandlers } from "./proto"; import { AdminScopes } from "./store"; -import type { ICommandResult } from "$interfaces/ICommandResult"; -import type { QueryResult } from "$types/server/query"; -import { CommandResult } from "$ts/result"; -import { parse } from "stacktrace-parser"; -import beautify from "js-beautify"; -import axios from "axios"; export class AdminBootstrapper extends BaseService implements IAdminBootstrapper { private userInfo: UserInfo | undefined; + private get adminClient() { + return Backend.create({ + headers: { + Authorization: `Bearer ${Daemon.token}`, + }, + responseType: "json", + baseURL: Server.url + "/admin", + }); + } //#region LIFECYCLE @@ -175,7 +185,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper if (this._disposed) return []; try { - const response = await Backend.get("/admin/users/list", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get("/users/list"); return (response.data as ExpandedUserInfo[]).map((u) => { u.profile.profilePicture = `${Server.url}${u.profile.profilePicture}`; @@ -199,7 +209,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper if (this._disposed) return []; try { - const response = await Backend.get("/admin/logs", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get("/logs"); return response.data as ServerLogItem[]; } catch { @@ -211,7 +221,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper if (this._disposed) return []; try { - const response = await Backend.get("/admin/auditlog", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get("/auditlog"); return response.data as AuditLog[]; } catch { @@ -222,9 +232,8 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async queryAuditLog(query: AuditLogQueryOptions): Promise>> { try { return CommandResult.FromResponse( - await Backend.get(`/admin/audit/query`, { + await this.adminClient.get(`/audit/query`, { params: query, - headers: { Authorization: `Bearer ${Daemon!.token}` }, }) ); } catch (e) { @@ -234,11 +243,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async grantAdmin(username: string) { if (this._disposed) return false; - try { - const response = await Backend.post("/admin/grant", toForm({ target: username }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post("/grant", toForm({ target: username })); return response.status === 200; } catch { return false; @@ -247,11 +254,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async revokeAdmin(username: string) { if (this._disposed) return false; - try { - const response = await Backend.post("/admin/revoke", toForm({ target: username }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post("/revoke", toForm({ target: username })); return response.status === 200; } catch { return false; @@ -261,9 +266,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getPreferencesOf(username: string) { if (this._disposed) return; try { - const response = await Backend.get(`/admin/preferences/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/preferences/${username}`); return response.data as UserPreferences; } catch { @@ -273,11 +276,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async setPreferencesOf(username: string, preferences: UserPreferences) { if (this._disposed) return false; - try { - const response = await Backend.put(`/admin/preferences/${username}`, preferences, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.put(`/preferences/${username}`, preferences); return response.status === 200; } catch { return false; @@ -286,11 +287,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteUser(username: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/users/delete/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/users/delete/${username}`); return response.status === 200; } catch { return false; @@ -300,9 +299,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getStatistics() { if (this._disposed) return; try { - const response = await Backend.get(`/admin/stats`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/stats`); return response.data as ServerStatistics; } catch { @@ -313,9 +310,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllTokens() { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/tokens`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/tokens`); return response.data as Token[]; } catch { @@ -325,13 +320,11 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async purgeAllTokens() { if (this._disposed) return false; + try { - const response = await Backend.delete(`/admin/tokens/purge/all`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.delete(`/tokens/purge/all`); location.reload(); - return response.status === 200; } catch { return false; @@ -340,11 +333,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async purgeOneToken(id: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/tokens/purge/one/${id}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/tokens/purge/one/${id}`); return response.status === 200; } catch { return false; @@ -353,11 +344,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async purgeUserTokens(userId: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/tokens/purge/user/${userId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/tokens/purge/user/${userId}`); return response.status === 200; } catch { return false; @@ -366,11 +355,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteBugReport(reportId: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/bughunt/report/${reportId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/bughunt/report/${reportId}`); return response.status === 200; } catch { return false; @@ -379,15 +366,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async closeBugReport(reportId: string) { if (this._disposed) return false; - try { - const response = await Backend.patch( - `/admin/bughunt/close/${reportId}`, - {}, - { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - } - ); + try { + const response = await this.adminClient.patch(`/bughunt/close/${reportId}`); return response.status === 200; } catch { return false; @@ -396,15 +377,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async reopenBugReport(reportId: string) { if (this._disposed) return false; - try { - const response = await Backend.patch( - `/admin/bughunt/open/${reportId}`, - {}, - { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - } - ); + try { + const response = await this.adminClient.patch(`/bughunt/open/${reportId}`); return response.status === 200; } catch { return false; @@ -414,9 +389,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllBugReports() { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/bughunt/list`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/bughunt/list`); return response.data as BugReport[]; } catch { @@ -427,7 +400,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getBugReport(id: string): Promise { if (this._disposed) return; try { - const response = await Backend.get(`/bughunt/report/${id}`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get(`/report/${id}`); return response.data as BugReport; } catch { @@ -438,9 +411,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getBugHuntStatistics() { if (this._disposed) return; try { - const response = await Backend.get(`/admin/bughunt/stats`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/bughunt/stats`); return response.data as ReportStatistics; } catch { @@ -450,11 +421,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async approveUser(username: string) { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/users/approve`, toForm({ target: username }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/users/approve`, toForm({ target: username })); return response.status === 200; } catch { return false; @@ -463,11 +432,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async disapproveUser(username: string) { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/users/disapprove`, toForm({ target: username }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/users/disapprove`, toForm({ target: username })); return response.status === 200; } catch { return false; @@ -476,11 +443,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async changeEmailOf(username: string, newEmail: string) { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/users/changeemail`, toForm({ target: username, newEmail }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/users/changeemail`, toForm({ target: username, newEmail })); return response.status === 200; } catch { return false; @@ -489,11 +454,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async changePasswordOf(username: string, newPassword: string) { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/users/changepswd`, toForm({ target: username, newPassword }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/users/changepswd`, toForm({ target: username, newPassword })); return response.status === 200; } catch { return false; @@ -503,9 +466,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAvailableScopes(): Promise> { if (this._disposed) return {}; try { - const response = await Backend.get(`/admin/scopes/available`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/scopes/available`); return response.data as Record; } catch { @@ -516,9 +477,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getScopesOf(username: string): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/scopes/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/scopes/${username}`); return response.data as string[]; } catch { @@ -528,11 +487,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async setScopesOf(username: string, scopes: string[]): Promise { if (this._disposed) return false; - try { - const response = await Backend.put(`/admin/scopes`, toForm({ target: username, scopes: JSON.stringify(scopes) }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.put(`/admin/scopes`, toForm({ target: username, scopes: JSON.stringify(scopes) })); return response.status === 200; } catch { return false; @@ -542,9 +499,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getQuotaOf(username: string): Promise { if (this._disposed) return; try { - const response = await Backend.get(`/admin/fs/quota/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/fs/quota/${username}`); return response.data as UserQuota; } catch { @@ -554,11 +509,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async setQuotaOf(username: string, newQuota: number) { if (this._disposed) return false; - try { - const response = await Backend.put(`/admin/fs/quota/${username}`, toForm({ limit: newQuota }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.put(`/fs/quota/${username}`, toForm({ limit: newQuota })); return response.status === 200; } catch { return false; @@ -568,9 +521,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllActivity(): Promise { if (this._disposed) return []; try { - const response = await Backend.get("/admin/activities/list", { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get("/activities/list"); return response.data as Activity[]; } catch { @@ -581,9 +532,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getActivityOf(username: string): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/activities/user/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/activities/user/${username}`); return response.data as Activity[]; } catch { @@ -593,11 +542,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteAllActivities(): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/activities`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/activities`); return response.status === 200; } catch { return false; @@ -606,11 +553,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteActivitiesOf(username: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/activities/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/activities/${username}`); return response.status === 200; } catch { return false; @@ -620,7 +565,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllTotp(): Promise { if (this._disposed) return []; try { - const response = await Backend.get("/admin/totp", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get("/totp"); return response.data as PartialUserTotp[]; } catch { @@ -631,7 +576,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getTotpOf(username: string): Promise { if (this._disposed) return; try { - const response = await Backend.get(`/admin/totp/${username}`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get(`/totp/${username}`); return response.status === 200 ? response.data : undefined; } catch { @@ -641,15 +586,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deActivateTotpOf(username: string) { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/totp/deactivate/${username}`, - {}, - { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - } - ); + try { + const response = await this.adminClient.post(`/totp/deactivate/${username}`, {}); return response.status === 200; } catch { return false; @@ -658,11 +597,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteTotpOf(username: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/totp/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/totp/${username}`); return response.status === 200; } catch { return false; @@ -672,7 +609,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllFsAccessors(): Promise { if (this._disposed) return []; try { - const response = await Backend.get("/admin/accessors", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get("/accessors"); return response.data as FsAccess[]; } catch { @@ -683,9 +620,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getFsAccessorsOf(username: string): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/accessors/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/accessors/${username}`); return response.data; } catch { @@ -695,9 +630,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteAllFsAccessors(): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete("/admin/accessors", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + try { + const response = await this.adminClient.delete("/accessors"); return response.status === 200; } catch { return false; @@ -706,11 +641,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteFsAccessorsOf(username: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/accessors/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/accessors/${username}`); return response.status === 200; } catch { return false; @@ -720,7 +653,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllIndexingNodes(): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/index`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get(`/index`); return response.data as FSItem[]; } catch { @@ -731,7 +664,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getIndexingNodesOf(username: string): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/index/${username}`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get(`/index/${username}`); return response.data as FSItem[]; } catch { @@ -742,11 +675,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async forceIndexFor(username: string): Promise { if (this._disposed) return []; try { - const response = await Backend.post( - `/admin/index/${username}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + const response = await this.adminClient.post(`/index/${username}`, {}); return response.data as string[]; } catch { @@ -756,11 +685,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteIndexingOf(username: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/index/${username}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/index/${username}`); return response.status === 200; } catch { return false; @@ -798,9 +725,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllShares(): Promise { if (this._disposed) return []; - try { - const response = await Backend.get("/admin/share/list", { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + try { + const response = await this.adminClient.get("/share/list"); return response.data as SharedDriveType[]; } catch { return []; @@ -809,10 +736,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getSharesOf(userId: string): Promise { if (this._disposed) return []; + try { - const response = await Backend.get(`/admin/share/list/${userId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/share/list/${userId}`); return response.data as SharedDriveType[]; } catch { @@ -822,9 +748,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteShare(shareId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/share/${shareId}`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + try { + const response = await this.adminClient.delete(`/share/${shareId}`); return response.status === 200; } catch { return false; @@ -833,11 +759,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async kickUserFromShare(shareId: string, userId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/share/kick/${shareId}`, toForm({ userId }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/share/kick/${shareId}`, toForm({ userId })); return response.status === 200; } catch { return false; @@ -846,11 +770,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async addUserToShare(shareId: string, userId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/share/adduser/${shareId}`, toForm({ userId }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/share/adduser/${shareId}`, toForm({ userId })); return response.status === 200; } catch { return false; @@ -860,9 +782,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getShareAccessors(shareId: string): Promise { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/share/accessors/${shareId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/share/accessors/${shareId}`); return response.data as FSItem[]; } catch { @@ -872,11 +792,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteShareAccessors(shareId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/share/accessors/${shareId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/share/accessors/${shareId}`); return response.status === 200; } catch { return false; @@ -885,11 +803,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async changeSharePassword(shareId: string, newPassword: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/share/changepswd/${shareId}`, toForm({ newPassword }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/share/changepswd/${shareId}`, toForm({ newPassword })); return response.status === 200; } catch { return false; @@ -898,11 +814,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async renameShare(shareId: string, newName: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/share/rename/${shareId}`, toForm({ newName }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/share/rename/${shareId}`, toForm({ newName })); return response.status === 200; } catch { return false; @@ -911,11 +825,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async changeShareOwner(shareId: string, newUserId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/share/chown/${shareId}`, toForm({ newUserId }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.post(`/share/chown/${shareId}`, toForm({ newUserId })); return response.status === 200; } catch { return false; @@ -925,9 +837,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getStatisticsOf(userId: string): Promise { if (this._disposed) return; try { - const response = await Backend.get(`/admin/users/stats/${userId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/users/stats/${userId}`); return response.data as UserStatistics; } catch { @@ -937,11 +847,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async setShareQuotaOf(shareId: string, quota: number): Promise { if (this._disposed) return false; - try { - const response = await Backend.put(`/admin/share/quota/${shareId}`, toForm({ limit: quota }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.put(`/share/quota/${shareId}`, toForm({ limit: quota })); return response.status === 200; } catch { return false; @@ -951,9 +859,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getShareQuotaOf(shareId: string): Promise { if (this._disposed) return undefined; try { - const response = await Backend.get(`/admin/share/quota/${shareId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/share/quota/${shareId}`); return response.data as UserQuota; } catch { @@ -963,13 +869,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async unlockShare(shareId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/share/unlock/${shareId}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/share/unlock/${shareId}`); return response.status === 2000; } catch { return false; @@ -978,13 +880,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async lockShare(shareId: string): Promise { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/share/lock/${shareId}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/share/lock/${shareId}`); return response.status === 2000; } catch { return false; @@ -994,10 +892,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteStoreItem(_id: string): Promise { false; try { - const response = await Backend.delete(`/admin/store/delete/one/${_id}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); - + const response = await this.adminClient.delete(`/store/delete/one/${_id}`); return response.status === 200; } catch { return false; @@ -1007,10 +902,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteUserStoreItems(userId: string): Promise { false; try { - const response = await Backend.delete(`/admin/store/delete/user/${userId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); - + const response = await this.adminClient.delete(`/store/delete/user/${userId}`); return response.status === 200; } catch { return false; @@ -1020,7 +912,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getAllStoreItems() { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/store/list`, { headers: { Authorization: `Bearer ${Daemon!.token}` } }); + const response = await this.adminClient.get(`/store/list`); return response.data as StoreItem[]; } catch { @@ -1031,9 +923,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async getUserStoreItems(userId: string) { if (this._disposed) return []; try { - const response = await Backend.get(`/admin/storel/list/${userId}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + const response = await this.adminClient.get(`/storel/list/${userId}`); return response.data as StoreItem[]; } catch { @@ -1043,13 +933,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deprecatePackage(itemId: string) { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/store/deprecate/${itemId}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/store/deprecate/${itemId}`); return response.status === 200; } catch { return false; @@ -1058,13 +944,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async undeprecatePackage(itemId: string) { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/store/undeprecate/${itemId}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/store/undeprecate/${itemId}`); return response.status === 200; } catch { return false; @@ -1100,12 +982,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } try { - const response = await Backend.post( - `/admin/store/block/${id}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); - + const response = await this.adminClient.post(`/store/block/${id}`); return response.status === 200; } catch { return false; @@ -1133,12 +1010,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } try { - const response = await Backend.post( - `/admin/store/unblock/${id}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); - + const response = await this.adminClient.post(`/store/unblock/${id}`); return response.status === 200; } catch { return false; @@ -1147,13 +1019,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async storeItemMakeOfficial(id: string) { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/store/official/on/${id}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/store/official/on/${id}`); return response.status === 200; } catch { return false; @@ -1162,13 +1030,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async storeItemMakeNotOfficial(id: string) { if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/store/official/off/${id}`, - {}, - { headers: { Authorization: `Bearer ${Daemon!.token}` } } - ); + try { + const response = await this.adminClient.post(`/store/official/off/${id}`); return response.status === 200; } catch { return false; @@ -1249,11 +1113,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async deleteStoreItemVerification(id: string) { if (this._disposed) return false; - try { - const response = await Backend.delete(`/admin/store/verification/${id}`, { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); + try { + const response = await this.adminClient.delete(`/store/verification/${id}`); return response.status === 200; } catch { return false; @@ -1262,15 +1124,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper async verifyStoreItem(id: string, note: string) { if (this._disposed) return false; - try { - const response = await Backend.post(`/admin/store/verification/${id}`, toForm({ note }), { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - }); - - if (response.status !== 200) { - return false; - } + try { + await this.adminClient.post(`/store/verification/${id}`, toForm({ note })); await Fs.createDirectory(join(UserPaths.Documents, `AdminBootstrapper`)); await Fs.writeFile(join(UserPaths.Documents, `AdminBootstrapper/Verification_${id}_${Date.now()}.txt`), textToBlob(note)); @@ -1281,14 +1137,13 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } async getRegisteredVersionFor(username: string): Promise { + if (this._disposed) return ""; this.Log(`getRegisteredVersionFor: ${username}`); try { - const contents = await Backend.get(`/admin/fs/file/${username}/System/RegisteredVersion`, { + const contents = await this.adminClient.get(`/fs/file/${username}/System/RegisteredVersion`, { responseType: "text", - headers: { Authorization: `Bearer ${Daemon.token}` }, }); - if (contents.status !== 200) throw ""; return contents.data; } catch { @@ -1297,14 +1152,11 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } async getMigrationIndexFor(username: string): Promise> { + if (this._disposed) return {}; this.Log(`getMigrationIndexFor: ${username}`); try { - const contents = await Backend.get(`/admin/fs/file/${username}/System/Migrations/Index.json`, { - responseType: "json", - headers: { Authorization: `Bearer ${Daemon.token}` }, - }); - if (contents.status !== 200) throw ""; + const contents = await this.adminClient.get(`/fs/file/${username}/System/Migrations/Index.json`); return contents.data; } catch { @@ -1313,12 +1165,9 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } async GetIpAddresses(): Promise { + if (this._disposed) return []; try { - const response = await Backend.get(`/admin/ip/list`, { - responseType: "json", - headers: { Authorization: `Bearer ${Daemon.token}` }, - }); - if (response.status !== 200) throw ""; + const response = await this.adminClient.get(`/ip/list`); return response.data; } catch { @@ -1327,6 +1176,8 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } async getReportSourceFile(report: BugReport): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + const trace = parse(report.body) .filter(Boolean) .filter((f) => (f.file?.startsWith("./assets") || f.file?.startsWith(report.location.origin)) && f.file.endsWith(".js")); @@ -1343,7 +1194,7 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper if (url.toString().includes("team.arcweb.nl")) { return CommandResult.Error("Previews are not supported because their JS files aren't retained"); } - + try { const file = (await axios.get(url.toString(), { responseType: "text" })).data as string; @@ -1374,6 +1225,218 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper return CommandResult.Error(`${e} -- URL: ${url}`); } } + + async getMailbrokerKey(id: string): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/keys/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async getMailbrokerLogs(): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/logs`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async getMailbrokerTemplates(): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/templates`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async getMailbrokerSentRecords(): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/sent`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async getMailbrokerTemplate(id: string): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/templates/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async getMailbrokerSentRecord(id: string): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.get(`/mailbroker/sent/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async deleteMailbrokerKey(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.delete(`/mailbroker/keys/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async disableMailbrokerKey(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post(`/mailbroker/keys/disable/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async enableMailbrokerKey(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post(`/mailbroker/keys/enable/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async createMailbrokerKey(serverName: string): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post(`/mailbroker/keys`, toForm({ serverName })); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async sendMailTemplateById( + id: string, + userId: string, + props: Record + ): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post( + `/mailbroker/send/byid/${id}`, + toForm({ userId, props: JSON.stringify(props) }) + ); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async sendMailTemplateByName( + name: string, + userId: string, + props: Record + ): Promise> { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post( + `/mailbroker/send/byname/${name}`, + toForm({ userId, props: JSON.stringify(props) }) + ); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async deleteMailbrokerSentRecord(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.delete(`/mailbroker/sent/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async deprecateMailbrokerTemplate(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post(`/mailbroker/templates/deprecate/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async undeprecateMailbrokerTemplate(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.post(`/mailbroker/templates/undeprecate/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async deleteMailbrokerTemplate(id: string): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.delete(`/mailbroker/templates/${id}`); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async updateMailbrokerTemplate(id: string, update: Mailbroker.MailTemplateUpdate): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.patch(`/mailbroker/templates/${id}`, toForm(update)); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } + + async createMailbrokerTemplate(data: Mailbroker.MailTemplateCreate): Promise { + if (this._disposed) return CommandResult.Error("Disposed"); + + try { + const response = await this.adminClient.patch(`/mailbroker/templates`, toForm(data)); + return CommandResult.Ok(response.data); + } catch (e) { + return CommandResult.AxiosError(e); + } + } } export const adminService: Service = { diff --git a/src/ts/servicehost/services/AdminBootstrapper/store.ts b/src/ts/servicehost/services/AdminBootstrapper/store.ts index f1d0638c3..f1aa26b78 100644 --- a/src/ts/servicehost/services/AdminBootstrapper/store.ts +++ b/src/ts/servicehost/services/AdminBootstrapper/store.ts @@ -80,6 +80,14 @@ export const AdminScopes = { adminAfsWrite: "admin.afs.write", adminAfsQuota: "admin.afs.quota", adminIpList: "admin.ip.list", + adminMailbrokerTemplatesRead: "admin.mailbroker.templates.read", + adminMailbrokerTemplatesWrite: "admin.mailbroker.templates.write", + adminMailbrokerSentRead: "admin.mailbroker.sent.read", + adminMailbrokerSentWrite: "admin.mailbroker.sent.write", + adminMailbrokerLogsRead: "admin.mailbroker.logs.read", + adminMailbrokerKeysRead: "admin.mailbroker.keys.read", + adminMailbrokerKeysWrite: "admin.mailbroker.keys.write", + adminMailbrokerSend: "admin.mailbroker.send", } as const; export type AdminScopesType = (typeof AdminScopes)[keyof typeof AdminScopes]; @@ -166,4 +174,12 @@ export const AdminScopeCaptions: Record = { adminAfsWrite: "write admin resource drive", adminAfsQuota: "get admin resource drive quota", adminIpList: "get IP addresses of users", + adminMailbrokerTemplatesRead: "Read mailbroker templates", + adminMailbrokerTemplatesWrite: "Write/delete/create mailbroker templates", + adminMailbrokerSentRead: "Read mailbroker sent records", + adminMailbrokerSentWrite: "Delete mailbroker sent records", + adminMailbrokerLogsRead: "Read mailbroker logs", + adminMailbrokerKeysRead: "Read mailbroker API keys", + adminMailbrokerKeysWrite: "Write/delete/create mailbroker keys", + adminMailbrokerSend: "Manually send mails using the mailbroker", }; diff --git a/src/types/server/admin.ts b/src/types/server/admin.ts index 002e4d950..6c938a03c 100644 --- a/src/types/server/admin.ts +++ b/src/types/server/admin.ts @@ -1,5 +1,5 @@ -import type { QueryableOptions } from "./query"; import type { ExpandedUserInfo } from "../user"; +import type { QueryableOptions } from "./query"; export interface Activity { authorId: string; @@ -180,13 +180,13 @@ export interface AuditLogQueryOptions extends QueryableOptions { } export interface BugReportSourceInformation { - line: number; - column: number; - originalSource: string; - prettySource: string; - prettyLine?: number; - prettyColumn?: number; - errorMessage?: string; - fileUrl?: string; - filename?: string; + line: number; + column: number; + originalSource: string; + prettySource: string; + prettyLine?: number; + prettyColumn?: number; + errorMessage?: string; + fileUrl?: string; + filename?: string; } diff --git a/src/types/server/mailbroker.ts b/src/types/server/mailbroker.ts new file mode 100644 index 000000000..e05097b7f --- /dev/null +++ b/src/types/server/mailbroker.ts @@ -0,0 +1,66 @@ +export namespace Mailbroker { + export interface MailKey { + _id: string; + serverName: string; + value: string; + disabled: boolean; + createdAt: string; + updatedAt: string; + } + + export interface MailLog { + _id: string; + code: string; + message: string; + details?: string; + createdAt: string; + updatedAt: string; + } + + export interface SentMail { + _id: string; + subject: string; + textContent: string; + htmlContent: string; + keyId: string; + to: SentMailRecipient; + createdAt: string; + updatedAt: string; + } + + export interface SentMailRecipient { + email: string; + serverName?: string; + username?: string; + } + + export interface SentMailCreate { + subject: string; + keyId: string; + textContent?: string; + htmlContent: string; + to: SentMailRecipient; + } + + export interface MailTemplate { + _id: string; + name: string; + htmlContent: string; + textContent: string; + subjectContent: string; + fromSuffix: string; + deprecated: boolean; + createdAt: string; + updatedAt: string; + } + + export interface MailTemplateCreate { + name: string; + htmlContent: string; + textContent?: string; + subjectContent?: string; + fromSuffix?: string; + } + + export type MailTemplateUpdate = Partial; +} From c94921942fffa2b5bdfeb238a3667fdcff47587a Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Sun, 2 Aug 2026 21:51:42 +0200 Subject: [PATCH 02/11] Some fixes to go with the mergeback --- .../services/AdminBootstrapper/index.ts | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/ts/servicehost/services/AdminBootstrapper/index.ts b/src/ts/servicehost/services/AdminBootstrapper/index.ts index 19d51e994..9914e1a38 100644 --- a/src/ts/servicehost/services/AdminBootstrapper/index.ts +++ b/src/ts/servicehost/services/AdminBootstrapper/index.ts @@ -19,7 +19,6 @@ import { arrayBufferToBlob, arrayBufferToText, textToBlob } from "$ts/util/conve import { toForm } from "$ts/util/form"; import { join } from "$ts/util/fs"; import { tryJsonParse } from "$ts/util/json"; -import type { App, InstalledApp } from "$types/apps/app"; import type { Activity, AdminTemporaryPassword, @@ -37,11 +36,11 @@ import type { UserTotp, } from "$types/server/admin"; import type { BugReport, ReportStatistics } from "$types/server/bughunt"; +import type { Mailbroker } from "$types/server/mailbroker"; import type { QueryResult } from "$types/server/query"; import type { SharedDriveType } from "$types/server/shares"; import type { Service } from "$types/services/service"; import type { BetaFeedback } from "$types/system/beta"; -import type { Mailbroker } from "$types/server/mailbroker"; import type { FilesystemProgressCallback, UserQuota } from "$types/system/fs"; import type { ArcPackage, StoreItem } from "$types/tpa/package"; import type { ExpandedUserInfo, UserInfo, UserPreferences } from "$types/user"; @@ -217,25 +216,6 @@ export class AdminBootstrapper extends BaseService implements IAdminBootstrapper } } - async setSystemFor(userId: string, value: boolean) { - if (this._disposed) return false; - try { - const response = await Backend.post( - `/admin/users/system/set/${userId}/${value}`, - {}, - { - headers: { Authorization: `Bearer ${Daemon!.token}` }, - } - ); - - try { - const response = await this.adminClient.post("/revoke", toForm({ target: username })); - return response.status === 200; - } catch { - return false; - } - } - async setSystemFor(userId: string, value: boolean) { if (this._disposed) return false; try { From d054e28e46a12b4017939c47c3dacdc63d1e1642 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 3 Aug 2026 01:03:36 +0200 Subject: [PATCH 03/11] Continue working on the mailbroker manager app --- src/apps/admin/mailbroker/Mailbroker.svelte | 25 +++-- .../mailbroker/Mailbroker/Sidebar.svelte | 27 +++-- .../Mailbroker/TemplateListing.svelte | 75 +++++++++++++ .../TemplateListing/ListingOption.svelte | 20 ++++ .../Mailbroker/TemplatePills.svelte | 20 ++++ src/apps/admin/mailbroker/MailbrokerApp.ts | 11 +- .../admin/mailbroker/Pages/GenericList.svelte | 18 ++++ .../admin/mailbroker/Pages/Templates.svelte | 7 -- .../mailbroker/Pages/ViewTemplate.svelte | 49 +++++++++ .../Pages/ViewTemplate/TemplateEditor.svelte | 32 ++++++ .../TemplateEditor/GeneralTab.svelte | 12 +++ .../TemplateEditor/HtmlContentTab.svelte | 10 ++ .../TemplateEditor/TextContentTab.svelte | 10 ++ .../Pages/ViewTemplate/TemplateHeader.svelte | 40 +++++++ src/apps/admin/mailbroker/runtime.ts | 9 +- src/apps/admin/mailbroker/store.ts | 36 ++++++- src/apps/admin/mailbroker/types.ts | 3 +- .../filemanager/FileManager/Sidebar.svelte | 12 +-- .../FileManager/Sidebar/DriveEntry.svelte | 2 +- .../FileManager/Sidebar/RootFolder.svelte | 2 +- src/assets/apps/mailbrokeradmin.svg | 9 ++ src/css/apps/admin/mailbroker.css | 74 +++++++++++++ .../admin/mailbroker/pages/viewtemplate.css | 42 ++++++++ src/css/apps/admin/mailbroker/sidebar.css | 8 ++ .../apps/admin/mailbroker/templatelisting.css | 101 ++++++++++++++++++ src/css/apps/user/filemanager/sidebar.css | 76 ++----------- src/css/window.css | 9 +- src/css/window/sidebar/symbolic.css | 72 +++++++++++++ src/interfaces/runtimes/IMailbrokerRuntime.ts | 3 +- src/interfaces/services/IAdminBootstrapper.ts | 2 +- src/ts/images/apps.ts | 1 + .../services/AdminBootstrapper/index.ts | 12 ++- 32 files changed, 708 insertions(+), 121 deletions(-) create mode 100644 src/apps/admin/mailbroker/Mailbroker/TemplateListing.svelte create mode 100644 src/apps/admin/mailbroker/Mailbroker/TemplateListing/ListingOption.svelte create mode 100644 src/apps/admin/mailbroker/Mailbroker/TemplatePills.svelte create mode 100644 src/apps/admin/mailbroker/Pages/GenericList.svelte delete mode 100644 src/apps/admin/mailbroker/Pages/Templates.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/GeneralTab.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/HtmlContentTab.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/TextContentTab.svelte create mode 100644 src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateHeader.svelte create mode 100644 src/assets/apps/mailbrokeradmin.svg create mode 100644 src/css/apps/admin/mailbroker.css create mode 100644 src/css/apps/admin/mailbroker/pages/viewtemplate.css create mode 100644 src/css/apps/admin/mailbroker/sidebar.css create mode 100644 src/css/apps/admin/mailbroker/templatelisting.css create mode 100644 src/css/window/sidebar/symbolic.css diff --git a/src/apps/admin/mailbroker/Mailbroker.svelte b/src/apps/admin/mailbroker/Mailbroker.svelte index b5aef1bfe..a3c5b8153 100644 --- a/src/apps/admin/mailbroker/Mailbroker.svelte +++ b/src/apps/admin/mailbroker/Mailbroker.svelte @@ -7,9 +7,10 @@ import Sidebar from "./Mailbroker/Sidebar.svelte"; import { mailbrokerPages } from "./store"; import type { MailbrokerPage } from "./types"; + import Spinner from "$lib/Spinner.svelte"; let { process }: { process: IMailbrokerRuntime } = $props(); - const { currentPage } = process; + const { currentPage, pageProps } = process; let Page: Component | undefined = $state(); let pageData = $state(); let loading = $state(false); @@ -19,7 +20,11 @@ const sub = currentPage.subscribe(async (v) => { loading = true; pageData = mailbrokerPages.get(v); - data = (await pageData?.data?.(process)) ?? CommandResult.Ok(); + const canAccess = !pageData?.scopes || process.admin.canAccess(...pageData?.scopes); + data = canAccess + ? ((await pageData?.data?.(process, pageProps())) ?? CommandResult.Ok()) + : CommandResult.Error("You're missing the required scopes to access this resource."); + Page = pageData?.content; loading = false; }); @@ -31,14 +36,22 @@
-
+
{#if loading} - loading + {:else if Page && data?.success} {@const result = data.result!} - + {:else} - error: {data?.errorMessage ?? "Unknown error"} +
+
+ +
+

The page failed to load

+

{data?.errorMessage ?? "An unknown error occurred."}

+
+
+
{/if}
diff --git a/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte b/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte index 3055452fd..ec8b88909 100644 --- a/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte +++ b/src/apps/admin/mailbroker/Mailbroker/Sidebar.svelte @@ -6,14 +6,19 @@ const { currentPage } = process; -{#each [...mailbrokerPages] as [id, page]} - {#if !page.hidden} - - {#if page.separator} -
- {/if} - {/if} -{/each} +
+ +
diff --git a/src/apps/admin/mailbroker/Mailbroker/TemplateListing.svelte b/src/apps/admin/mailbroker/Mailbroker/TemplateListing.svelte new file mode 100644 index 000000000..23f3a3366 --- /dev/null +++ b/src/apps/admin/mailbroker/Mailbroker/TemplateListing.svelte @@ -0,0 +1,75 @@ + + +
+
+

{filteredTemplates.length} {Plural("template", filteredTemplates.length)}

+ + {#if !hideNewButton && process.admin.canAccess(AdminScopes.adminMailbrokerTemplatesWrite)} + + {/if} +
+ +
+ {#each filteredTemplates as template (template._id)} + + {/each} + + {#if !filteredTemplates.length} +

+ There are no templates that match the criteria. +

+ {/if} +
+
diff --git a/src/apps/admin/mailbroker/Mailbroker/TemplateListing/ListingOption.svelte b/src/apps/admin/mailbroker/Mailbroker/TemplateListing/ListingOption.svelte new file mode 100644 index 000000000..9e6cedf8b --- /dev/null +++ b/src/apps/admin/mailbroker/Mailbroker/TemplateListing/ListingOption.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/apps/admin/mailbroker/Mailbroker/TemplatePills.svelte b/src/apps/admin/mailbroker/Mailbroker/TemplatePills.svelte new file mode 100644 index 000000000..b92f5fb1c --- /dev/null +++ b/src/apps/admin/mailbroker/Mailbroker/TemplatePills.svelte @@ -0,0 +1,20 @@ + + +
+
+ + {formatBytes(template.htmlContent.length)} +
+
+ + {formatBytes(template.textContent.length)} +
+ {#if template.deprecated} +
Deprecated
+ {/if} +
diff --git a/src/apps/admin/mailbroker/MailbrokerApp.ts b/src/apps/admin/mailbroker/MailbrokerApp.ts index b0b43422d..45c82f16a 100644 --- a/src/apps/admin/mailbroker/MailbrokerApp.ts +++ b/src/apps/admin/mailbroker/MailbrokerApp.ts @@ -1,3 +1,4 @@ +import "$css/apps/admin/mailbroker.css"; import type { App } from "$types/apps/app"; import Mailbroker from "./Mailbroker.svelte"; import { MailbrokerRuntime } from "./runtime"; @@ -8,12 +9,12 @@ const MailbrokerApp: App = { version: "1.0.0", author: "Izaak Kuipers", appGroup: "adminTools", - icon: "ComponentIcon", + icon: "MailbrokerAdminIcon", }, position: { centered: true }, - size: { w: 640, h: 480 }, - minSize: { w: 640, h: 480 }, - maxSize: { w: 640, h: 480 }, + size: { w: 800, h: 480 }, + minSize: { w: 800, h: 480 }, + maxSize: { w: NaN, h: NaN }, state: { maximized: false, minimized: false, @@ -37,7 +38,7 @@ const MailbrokerApp: App = { overlay: false, noSafeMode: false, vital: true, - id: "Mailbroker", + id: "MailbrokerApp", }; export default MailbrokerApp; diff --git a/src/apps/admin/mailbroker/Pages/GenericList.svelte b/src/apps/admin/mailbroker/Pages/GenericList.svelte new file mode 100644 index 000000000..1c580e925 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/GenericList.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/apps/admin/mailbroker/Pages/Templates.svelte b/src/apps/admin/mailbroker/Pages/Templates.svelte deleted file mode 100644 index 15aece798..000000000 --- a/src/apps/admin/mailbroker/Pages/Templates.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -{data.length} diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate.svelte new file mode 100644 index 000000000..9db898137 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate.svelte @@ -0,0 +1,49 @@ + + + + \ No newline at end of file diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor.svelte new file mode 100644 index 000000000..103370e9d --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor.svelte @@ -0,0 +1,32 @@ + + +
+
+ {#each Object.entries(tabs) as [tab]} + + {/each} +
+
+ {#if Component} + + {/if} +
+
diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/GeneralTab.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/GeneralTab.svelte new file mode 100644 index 000000000..944e710ad --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/GeneralTab.svelte @@ -0,0 +1,12 @@ + + +general + + \ No newline at end of file diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/HtmlContentTab.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/HtmlContentTab.svelte new file mode 100644 index 000000000..93a8038b3 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/HtmlContentTab.svelte @@ -0,0 +1,10 @@ + + +html content diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/TextContentTab.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/TextContentTab.svelte new file mode 100644 index 000000000..17c3c3474 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateEditor/TextContentTab.svelte @@ -0,0 +1,10 @@ + + +text content diff --git a/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateHeader.svelte b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateHeader.svelte new file mode 100644 index 000000000..d651639f2 --- /dev/null +++ b/src/apps/admin/mailbroker/Pages/ViewTemplate/TemplateHeader.svelte @@ -0,0 +1,40 @@ + + +
+ +
+

{$updateData.name} - ArcOS {$updateData.fromSuffix}

+

{$updateData.subjectContent}

+ +
+
+ +
+

Created at:

+

{dayjs(template.createdAt).format("D MMMM YYYY, HH:mm:ss")}

+
+ +
+

Updated at:

+

{dayjs(template.updatedAt).format("D MMMM YYYY, HH:mm:ss")}

+
+ +
+
diff --git a/src/apps/admin/mailbroker/runtime.ts b/src/apps/admin/mailbroker/runtime.ts index 24da860c3..1a8ffed55 100644 --- a/src/apps/admin/mailbroker/runtime.ts +++ b/src/apps/admin/mailbroker/runtime.ts @@ -8,6 +8,8 @@ import { mailbrokerPages } from "./store"; export class MailbrokerRuntime extends AppProcess implements IMailbrokerRuntime { currentPage = Store(""); + pageProps = Store>({}); + get admin() { return Daemon.serviceHost?.getService("AdminBootstrapper")!; } @@ -17,18 +19,21 @@ export class MailbrokerRuntime extends AppProcess implements IMailbrokerRuntime constructor(pid: number, parentPid: number, app: AppProcessData) { super(pid, parentPid, app); - this.switchPage("templates"); + this.switchPage("activeTemplates"); this.setSource(__SOURCE__); } - switchPage(pageId: string) { + switchPage(pageId: string, props?: Record) { this.Log(`Loading page '${pageId}'`); if (!mailbrokerPages.has(pageId)) return; + this.pageProps.set({}); + const page = mailbrokerPages.get(pageId); + this.pageProps.set(props ?? {}); this.currentPage.set(pageId); this.windowTitle.set(`${page?.name}`); } diff --git a/src/apps/admin/mailbroker/store.ts b/src/apps/admin/mailbroker/store.ts index 79f267b4c..42666fa68 100644 --- a/src/apps/admin/mailbroker/store.ts +++ b/src/apps/admin/mailbroker/store.ts @@ -1,16 +1,40 @@ +import { AdminScopes } from "$ts/servicehost/services/AdminBootstrapper/store"; +import GenericList from "./Pages/GenericList.svelte"; import Logs from "./Pages/Logs.svelte"; import Sent from "./Pages/Sent.svelte"; -import Templates from "./Pages/Templates.svelte"; +import ViewTemplate from "./Pages/ViewTemplate.svelte"; import type { MailbrokerPage, MailbrokerPages } from "./types"; export const mailbrokerPages: MailbrokerPages = new Map([ [ - "templates", + "activeTemplates", { - name: "Templates", + name: "Active templates", icon: "mails", - content: Templates, - data: async (process) => await process.admin.getMailbrokerTemplates(), + content: GenericList, + data: async (process) => await process.admin.getMailbrokerTemplates((template) => !template.deprecated), + scopes: [AdminScopes.adminMailbrokerTemplatesRead], + }, + ], + [ + "deprecatedTemplates", + { + name: "Deprecated templates", + icon: "mails", + content: GenericList, + data: async (process) => await process.admin.getMailbrokerTemplates((template) => template.deprecated), + scopes: [AdminScopes.adminMailbrokerTemplatesRead], + }, + ], + [ + "viewTemplate", + { + name: "View template", + icon: "eye", + hidden: true, + content: ViewTemplate, + data: async (process, props: { templateId: string }) => await process.admin.getMailbrokerTemplate(props.templateId), + scopes: [AdminScopes.adminMailbrokerTemplatesRead], }, ], [ @@ -21,6 +45,7 @@ export const mailbrokerPages: MailbrokerPages = new Map( content: Sent, separator: true, data: async (process) => await process.admin.getMailbrokerSentRecords(), + scopes: [AdminScopes.adminMailbrokerSentRead], }, ], [ @@ -30,6 +55,7 @@ export const mailbrokerPages: MailbrokerPages = new Map( icon: "list", content: Logs, data: async (process) => await process.admin.getMailbrokerLogs(), + scopes: [AdminScopes.adminMailbrokerLogsRead], }, ], ]); diff --git a/src/apps/admin/mailbroker/types.ts b/src/apps/admin/mailbroker/types.ts index cae438e25..ebdc170b3 100644 --- a/src/apps/admin/mailbroker/types.ts +++ b/src/apps/admin/mailbroker/types.ts @@ -9,7 +9,8 @@ export interface MailbrokerPage { content: Component; hidden?: boolean; separator?: boolean; - data?: (process: IMailbrokerRuntime) => MaybePromise>>; + data?: (process: IMailbrokerRuntime, pageProps: any) => MaybePromise>>; + scopes?: string[]; } export type MailbrokerPages = Map; diff --git a/src/apps/user/filemanager/FileManager/Sidebar.svelte b/src/apps/user/filemanager/FileManager/Sidebar.svelte index 7099d6d6d..6e9fa46cd 100755 --- a/src/apps/user/filemanager/FileManager/Sidebar.svelte +++ b/src/apps/user/filemanager/FileManager/Sidebar.svelte @@ -10,11 +10,11 @@ const { path, rootFolders, drives } = process; -