/** @jsxImportSource hono/jsx */ import type { App } from "App"; import { config, isDebug } from "core"; import { addFlashMessage } from "core/server/flash"; import { html } from "hono/html"; import { Fragment } from "hono/jsx"; import { Controller } from "modules/Controller"; import * as SystemPermissions from "modules/permissions"; import type { AppTheme } from "modules/server/AppServer"; const htmlBkndContextReplace = ""; // @todo: add migration to remove admin path from config export type AdminControllerOptions = { basepath?: string; assets_path?: string; html?: string; forceDev?: boolean | { mainPath: string }; debug_rerenders?: boolean; }; export class AdminController extends Controller { constructor( private readonly app: App, private _options: AdminControllerOptions = {} ) { super(); } get ctx() { return this.app.modules.ctx(); } get options() { return { ...this._options, basepath: this._options.basepath ?? "/", assets_path: this._options.assets_path ?? config.server.assets_path }; } get basepath() { return this.options.basepath ?? "/"; } private withBasePath(route: string = "") { return (this.basepath + route).replace(/(? { const obj = { user: c.get("auth")?.user, logout_route: this.withBasePath(authRoutes.logout), color_scheme: configs.server.admin.color_scheme }; const html = await this.getHtml(obj); if (!html) { console.warn("Couldn't generate HTML for admin UI"); // re-casting to void as a return is not required return c.notFound() as unknown as void; } c.set("html", html); await next(); }); if (auth_enabled) { hono.get( authRoutes.login, permission([SystemPermissions.accessAdmin, SystemPermissions.schemaRead], { // @ts-ignore onGranted: async (c) => { // @todo: add strict test to permissions middleware? if (c.get("auth")?.user) { console.log("redirecting to success"); return c.redirect(authRoutes.success); } } }), async (c) => { return c.html(c.get("html")!); } ); hono.get(authRoutes.logout, async (c) => { await auth.authenticator?.logout(c); return c.redirect(authRoutes.loggedOut); }); } // @todo: only load known paths hono.get( "/*", permission(SystemPermissions.accessAdmin, { onDenied: async (c) => { addFlashMessage(c, "You are not authorized to access the Admin UI", "error"); console.log("redirecting"); return c.redirect(authRoutes.login); } }), permission(SystemPermissions.schemaRead, { onDenied: async (c) => { addFlashMessage(c, "You not allowed to read the schema", "warning"); } }), async (c) => { return c.html(c.get("html")!); } ); return hono; } private async getHtml(obj: any = {}) { const bknd_context = `window.__BKND__ = JSON.parse('${JSON.stringify(obj)}');`; if (this.options.html) { if (this.options.html.includes(htmlBkndContextReplace)) { return this.options.html.replace( htmlBkndContextReplace, "" ); } console.warn( `Custom HTML needs to include '${htmlBkndContextReplace}' to inject BKND context` ); return this.options.html as string; } const configs = this.app.modules.configs(); const isProd = !isDebug() && !this.options.forceDev; const mainPath = typeof this.options.forceDev === "object" && "mainPath" in this.options.forceDev ? this.options.forceDev.mainPath : "/src/ui/main.tsx"; const assets = { js: "main.js", css: "styles.css" }; if (isProd) { // @ts-ignore const manifest = await import("bknd/dist/manifest.json", { assert: { type: "json" } }); // @todo: load all marked as entry (incl. css) assets.js = manifest.default["src/ui/main.tsx"].file; assets.css = manifest.default["src/ui/main.tsx"].css[0] as any; } const theme = configs.server.admin.color_scheme ?? "light"; const favicon = isProd ? this.options.assets_path + "favicon.ico" : "/favicon.ico"; return ( {/* dnd complains otherwise */} {html``} BKND {this.options.debug_rerenders && (