From 736ea10154a532961ed7b1e14823bf4682e53257 Mon Sep 17 00:00:00 2001 From: Izaak Kuipers Date: Mon, 1 Jun 2026 23:13:09 +0200 Subject: [PATCH 01/31] 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 43b8301bcaa29c570ef63973b4c926927f5c301e Mon Sep 17 00:00:00 2001 From: Blockyheadman <80011716+Blockyheadman@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:06:11 -0500 Subject: [PATCH 02/31] fix some css stuff with light mode --- .../components/shell/taskbar/calendar.css | 8 +++-- src/css/apps/user/settings/page/account.css | 12 ++++++-- src/css/window.css | 17 ++++++++--- src/css/window/button.css | 29 ++++++++++++------- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/css/apps/components/shell/taskbar/calendar.css b/src/css/apps/components/shell/taskbar/calendar.css index 2cc98383c..c8a21a93d 100644 --- a/src/css/apps/components/shell/taskbar/calendar.css +++ b/src/css/apps/components/shell/taskbar/calendar.css @@ -28,9 +28,9 @@ pointer-events: none !important; } -#arcShell div.taskbar div.calendar-popup > div { +#arcShell div.taskbar div.calendar-popup>div { border-radius: 10px; - box-shadow: 0 2px 5px 2px #0002; + box-shadow: var(--secondary-box-shadow); outline: var(--shell-component-outline) 1px solid; outline-offset: -1px; background-color: var(--shell-component-bg); @@ -119,6 +119,7 @@ #arcShell div.taskbar div.calendar-popup div.calendar div.day p.day-number:hover { background-color: var(--button-glass-bg); } + #arcShell div.taskbar div.calendar-popup div.calendar div.day.today p.day-number { background-color: var(--accent-light); color: var(--win-bg) !important; @@ -186,6 +187,7 @@ #arcShell div.taskbar div.calendar-popup div.buttons button.today:hover { background-color: var(--button-glass-bg); } + #arcShell div.taskbar div.calendar-popup div.buttons button.today:active { background-color: var(--button-glass-active-bg); -} +} \ No newline at end of file diff --git a/src/css/apps/user/settings/page/account.css b/src/css/apps/user/settings/page/account.css index 17f5b8421..7ab1d183f 100755 --- a/src/css/apps/user/settings/page/account.css +++ b/src/css/apps/user/settings/page/account.css @@ -39,6 +39,11 @@ translate: 0 -50%; z-index: 0; filter: blur(40px); + + .theme-light & { + opacity: .2; + } + } #systemSettings div.container.account div.centered-layout { @@ -82,7 +87,7 @@ overflow: hidden; } -#systemSettings div.change-menu div.inner > span { +#systemSettings div.change-menu div.inner>span { width: 30px; height: 30px; line-height: 30px; @@ -126,7 +131,7 @@ background-color: transparent; } -#systemSettings div.container.account div.header h1.account-name > span { +#systemSettings div.container.account div.header h1.account-name>span { border-bottom: transparent 1px solid; } @@ -146,6 +151,7 @@ #systemSettings div.container.account div.header h1.account-name:hover button { opacity: 1; } + #systemSettings div.container.account div.header h1.account-name.editing button { color: var(--clr-green-fg); } @@ -154,4 +160,4 @@ opacity: 1; font-weight: bold; margin-top: 20px; -} +} \ No newline at end of file diff --git a/src/css/window.css b/src/css/window.css index 963fb97ed..8e63b3ffa 100755 --- a/src/css/window.css +++ b/src/css/window.css @@ -84,7 +84,7 @@ div.window:not(.core).fullscreen div.titlebar { display: none !important; } -div.window:not(.core).fullscreen > div.body { +div.window:not(.core).fullscreen>div.body { height: 100vh !important; border-top: none !important; border-radius: 0 !important; @@ -94,11 +94,11 @@ div.window:not(.core):not(.glass).focused { background-color: var(--glass-bg); } -div.window:not(.core):not(.glass).focused > div.titlebar { +div.window:not(.core):not(.glass).focused>div.titlebar { backdrop-filter: blur(var(--blur)); } -div.window:not(.core):not(.glass) > div.body { +div.window:not(.core):not(.glass)>div.body { background-color: var(--win-bg); } @@ -131,6 +131,7 @@ div.window:not(.core).visible:not(.closing) { scale: 1; margin-top: 0; } + /* div.window:not(.core).resizable { resize: both; @@ -144,6 +145,14 @@ div.window.overlay.glass { div.window:not(.core) a { color: var(--accent-light); text-underline-offset: 2px; + + .theme-light & { + /* + * Uses the calculated result if available, + * otherwise, uses uglier dark accent color. + */ + color: var(--accent-suggested-end, var(--accent-dark)); + } } div.window:not(.core) div.resizer { @@ -196,4 +205,4 @@ div.window.minimized.no-shell { left: unset !important; transform: none !important; translate: none !important; -} +} \ No newline at end of file diff --git a/src/css/window/button.css b/src/css/window/button.css index e7bc08b83..2dde914d5 100755 --- a/src/css/window/button.css +++ b/src/css/window/button.css @@ -12,16 +12,16 @@ div.window div.body button:not(.reset) { text-align: center; } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link) { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link) { outline: var(--button-glass-bg) 1px solid; outline-offset: -1px; } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link):hover { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link):hover { background-color: var(--button-glass-hover-bg); } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link):active { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link):active { background-color: var(--button-glass-active-bg); } @@ -43,6 +43,15 @@ div.window div.body button:not(.reset).link { text-underline-offset: 2px; width: fit-content; border-radius: 0; + + .theme-light & { + /* + * Uses the calculated result if available, + * otherwise, uses uglier dark accent color. + */ + color: var(--accent-suggested-end, var(--accent-dark)); + } + } div.window div.body button:not(.reset):disabled { @@ -88,26 +97,26 @@ div.window div.body button:not(.reset).suggested:active { filter: brightness(0.8); } -div.window.glass > div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass) { +div.window.glass>div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass) { background-color: var(--button-glass-bg, #fff1); } -div.window.glass > div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass):hover { +div.window.glass>div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass):hover { background-color: var(--button-glass-hover-bg, #fff3); } -div.window.glass > div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass):active { +div.window.glass>div.body button:not(.reset):not(.suggested):not(.menu-item):not(.no-glass):active { background-color: var(--button-glass-active-bg, #0005); } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide { background-color: transparent; } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide:hover { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide:hover { background-color: var(--button-glass-bg); } -div.window:not(.core) > div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide:active { +div.window:not(.core)>div.body button:not(.reset):not(.no-glass):not(.suggested):not(.link).lucide:active { background-color: var(--button-glass-active-bg); -} +} \ No newline at end of file From 338e1ad81751c04be59872657eb3b78a14c8944b Mon Sep 17 00:00:00 2001 From: Blockyheadman <80011716+Blockyheadman@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:35:15 -0500 Subject: [PATCH 03/31] add email login support (super simple) --- src/apps/core/loginapp/LoginForm.svelte | 2 +- src/apps/core/loginapp/runtime.ts | 24 +++++++++++++++------ src/interfaces/runtimes/ILoginAppRuntime.ts | 2 +- src/ts/user/auth.ts | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/apps/core/loginapp/LoginForm.svelte b/src/apps/core/loginapp/LoginForm.svelte index afe7e0e0d..89900cdd2 100755 --- a/src/apps/core/loginapp/LoginForm.svelte +++ b/src/apps/core/loginapp/LoginForm.svelte @@ -17,7 +17,7 @@ diff --git a/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Notes.svelte b/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Notes.svelte deleted file mode 100755 index 83f1957ac..000000000 --- a/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Notes.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -
- -
diff --git a/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Weather.svelte b/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Weather.svelte deleted file mode 100755 index b8f2b317d..000000000 --- a/src/apps/components/shell/Shell/ActionCenter/CardStack/Cards/Weather.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -
{ - refresh(true); - }, - icon: "rotate-cw", - }, - { - caption: "Change location...", - icon: "map-pin", - action: () => { - changeLocation(); - }, - }, - ], - process, - ]} -> - {#if $userPreferences.shell.actionCenter.weatherLocation.name === "unset"} -

Set-up weather

- - {:else if !loading} - {#if data} - {#if data.icon && data.iconColor} - - {/if} -

- {data.temperature.toFixed(1)} °C -

-

- - {#if data.condition} - - {data.condition} - - {/if} -

- {:else} -

Failed to get weather info

- - {/if} - {:else} - - {/if} -
diff --git a/src/apps/components/shell/Shell/ActionCenter/CardStack/Indicator.svelte b/src/apps/components/shell/Shell/ActionCenter/CardStack/Indicator.svelte deleted file mode 100755 index a119ce3e2..000000000 --- a/src/apps/components/shell/Shell/ActionCenter/CardStack/Indicator.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -
- {#each new Array(max).fill(0) as _, i} -
- {/each} -
diff --git a/src/apps/components/shell/Shell/Taskbar.svelte b/src/apps/components/shell/Shell/Taskbar.svelte index ec1b118f3..8b10b6a80 100755 --- a/src/apps/components/shell/Shell/Taskbar.svelte +++ b/src/apps/components/shell/Shell/Taskbar.svelte @@ -9,6 +9,7 @@ import StartButton from "./Taskbar/StartButton.svelte"; import Clock from "./Taskbar/SystemArea/Clock.svelte"; import SystemTray from "./Taskbar/SystemTray.svelte"; + import TaskbarWeatherApplet from "./Taskbar/TaskbarWeatherApplet.svelte"; import WorkspaceManagerButton from "./Taskbar/WorkspaceManagerButton.svelte"; const { process }: { process: IShellRuntime } = $props(); @@ -26,6 +27,7 @@ + {#snippet ifActive(service: ITrayHostService)} diff --git a/src/apps/components/shell/Shell/Taskbar/TaskbarWeatherApplet.svelte b/src/apps/components/shell/Shell/Taskbar/TaskbarWeatherApplet.svelte new file mode 100644 index 000000000..b1ebc4fbb --- /dev/null +++ b/src/apps/components/shell/Shell/Taskbar/TaskbarWeatherApplet.svelte @@ -0,0 +1,97 @@ + + +{#if $userPreferences.weatherSettings.displayMode !== "disabled"} +
refresh(), + icon: "rotate-cw", + }, + { + caption: "Change location...", + icon: "map-pin", + action: () => changeLocation(), + }, + ], + process, + ]} + > + {#if $userPreferences.weatherSettings.name === "unset"} +

Set-up weather

+ + {:else if data} + {#if data.icon && data.iconColor} + + {/if} + {#if $userPreferences.weatherSettings.displayMode !== "onlyTemperature"} +
+

+ {data.temperature.toFixed(1)} °C +

+

+ + {#if $userPreferences.weatherSettings.displayMode === "withCity" && city} + + {city} + + {:else if $userPreferences.weatherSettings.displayMode === "withCondition" && data.condition} + + {data.condition} + + {/if} +

+
+ {:else} +

+ {data.temperature.toFixed(1)} °C + +

+ {/if} + {:else} +

Failed to get weather info

+ + {/if} +
+{/if} diff --git a/src/apps/components/shell/runtime.ts b/src/apps/components/shell/runtime.ts index 0934d47b5..4891fd25c 100755 --- a/src/apps/components/shell/runtime.ts +++ b/src/apps/components/shell/runtime.ts @@ -9,9 +9,7 @@ import { UserPaths } from "$ts/user/store"; import { Store } from "$ts/writable"; import type { AppKeyCombinations } from "$types/apps/accelerator"; import type { AppContextMenu, AppProcessData } from "$types/apps/app"; -import type { SearchItem } from "$types/services/search"; import type { RecursiveDirectoryReadReturn } from "$types/system/fs"; -import type { Workspace } from "$types/user"; import { ShellAccelerators } from "./accelerators"; import { ShellContextMenu } from "./context"; @@ -46,25 +44,6 @@ export class ShellRuntime extends AppProcess implements IShellRuntime { this.setSource(__SOURCE__); } - gotReadySignal(): Promise { - throw new Error("Method not implemented."); - } - deleteWorkspace(workspace: Workspace): Promise { - throw new Error("Method not implemented."); - } - MutateIndex(e: KeyboardEvent): void | -1 { - throw new Error("Method not implemented."); - } - Trigger(result: SearchItem): Promise { - throw new Error("Method not implemented."); - } - Submit(): void { - throw new Error("Method not implemented."); - } - changeShell(id: string): Promise { - throw new Error("Method not implemented."); - } - async start() { if (Stack.getProcess(+Env.get("shell_pid"))) return false; @@ -201,9 +180,6 @@ export class ShellRuntime extends AppProcess implements IShellRuntime { }); } - //#endregion - //#region STARTMENU - public async refreshStartMenu(): Promise { try { const tree = await Fs.tree(this.STARTMENU_FOLDER); @@ -220,12 +196,6 @@ export class ShellRuntime extends AppProcess implements IShellRuntime { } } - //#endregion - //#region CALENDAR - - //#endregion - //#region WEATHER - async exit() { this.startMenuOpened.set(false); // First close the start menu await this.spawnOverlayApp("ExitApp", this.pid); // Then spawn the exit overlay @@ -254,6 +224,4 @@ export class ShellRuntime extends AppProcess implements IShellRuntime { this.FullscreenCount.set(fullscreenCount); } - - //#endregion } diff --git a/src/apps/user/settings/Settings/Page/Shell.svelte b/src/apps/user/settings/Settings/Page/Shell.svelte index 0d8dc5890..bfb939109 100755 --- a/src/apps/user/settings/Settings/Page/Shell.svelte +++ b/src/apps/user/settings/Settings/Page/Shell.svelte @@ -56,10 +56,22 @@ -
+ +
+ +
+ +