mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
reworked html serving, added new permissions for api/auth, updated adapters
This commit is contained in:
104
app/src/modules/server/AdminController.tsx
Normal file
104
app/src/modules/server/AdminController.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
import type { App } from "App";
|
||||
import { type ClassController, isDebug } from "core";
|
||||
import { Hono } from "hono";
|
||||
import { html, raw } from "hono/html";
|
||||
import { Fragment } from "hono/jsx";
|
||||
import * as SystemPermissions from "modules/permissions";
|
||||
import type { Manifest } from "vite";
|
||||
|
||||
const viteInject = `
|
||||
import RefreshRuntime from "/@react-refresh"
|
||||
RefreshRuntime.injectIntoGlobalHook(window)
|
||||
window.$RefreshReg$ = () => {}
|
||||
window.$RefreshSig$ = () => (type) => type
|
||||
window.__vite_plugin_react_preamble_installed__ = true
|
||||
`;
|
||||
|
||||
export type AdminControllerOptions = {
|
||||
html?: string;
|
||||
viteManifest?: Manifest;
|
||||
};
|
||||
|
||||
export class AdminController implements ClassController {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private options: AdminControllerOptions = {}
|
||||
) {}
|
||||
|
||||
get ctx() {
|
||||
return this.app.modules.ctx();
|
||||
}
|
||||
|
||||
getController(): Hono {
|
||||
const hono = new Hono();
|
||||
const configs = this.app.modules.configs();
|
||||
const basepath = (String(configs.server.admin.basepath) + "/").replace(/\/+$/, "/");
|
||||
|
||||
this.ctx.server.get(basepath + "*", async (c) => {
|
||||
if (this.options.html) {
|
||||
return c.html(this.options.html);
|
||||
}
|
||||
|
||||
// @todo: implement guard redirect once cookie sessions arrive
|
||||
|
||||
const isProd = !isDebug();
|
||||
let script: string | undefined;
|
||||
let css: string[] = [];
|
||||
|
||||
if (isProd) {
|
||||
const manifest: Manifest = this.options.viteManifest
|
||||
? this.options.viteManifest
|
||||
: isProd
|
||||
? // @ts-ignore cases issues when building types
|
||||
await import("bknd/dist/manifest.json", { assert: { type: "json" } }).then(
|
||||
(m) => m.default
|
||||
)
|
||||
: {};
|
||||
//console.log("manifest", manifest, manifest["index.html"]);
|
||||
const entry = Object.values(manifest).find((f: any) => f.isEntry === true);
|
||||
if (!entry) {
|
||||
// do something smart
|
||||
return;
|
||||
}
|
||||
|
||||
script = "/" + entry.file;
|
||||
css = entry.css?.map((c: string) => "/" + c) ?? [];
|
||||
}
|
||||
|
||||
return c.html(
|
||||
<html lang="en" class={configs.server.admin.color_scheme ?? "light"}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<title>BKND</title>
|
||||
{isProd ? (
|
||||
<Fragment>
|
||||
<script type="module" CrossOrigin src={script} />
|
||||
{css.map((c) => (
|
||||
<link rel="stylesheet" CrossOrigin href={c} key={c} />
|
||||
))}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: I know what I do here :) */}
|
||||
<script type="module" dangerouslySetInnerHTML={{ __html: viteInject }} />
|
||||
<script type="module" src="/@vite/client" />
|
||||
</Fragment>
|
||||
)}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" />
|
||||
{!isProd && <script type="module" src="/src/ui/main.tsx" />}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
});
|
||||
|
||||
return hono;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import type { ClassController } from "core";
|
||||
import { SimpleRenderer } from "core";
|
||||
import { FetchTask, Flow, LogTask } from "flows";
|
||||
import { Hono } from "hono";
|
||||
import { endTime, startTime } from "hono/timing";
|
||||
import type { App } from "../../App";
|
||||
|
||||
export class AppController implements ClassController {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private config: any = {}
|
||||
) {}
|
||||
|
||||
getController(): Hono {
|
||||
const hono = new Hono();
|
||||
|
||||
// @todo: add test endpoints
|
||||
|
||||
hono
|
||||
.get("/config", (c) => {
|
||||
return c.json(this.app.toJSON());
|
||||
})
|
||||
.get("/ping", (c) => {
|
||||
//console.log("c", c);
|
||||
try {
|
||||
// @ts-ignore @todo: fix with env
|
||||
const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf;
|
||||
const cf = {
|
||||
colo: context.colo,
|
||||
city: context.city,
|
||||
postal: context.postalCode,
|
||||
region: context.region,
|
||||
regionCode: context.regionCode,
|
||||
continent: context.continent,
|
||||
country: context.country,
|
||||
eu: context.isEUCountry,
|
||||
lat: context.latitude,
|
||||
lng: context.longitude,
|
||||
timezone: context.timezone
|
||||
};
|
||||
return c.json({ pong: true, cf, another: 6 });
|
||||
} catch (e) {
|
||||
return c.json({ pong: true, cf: null });
|
||||
}
|
||||
});
|
||||
|
||||
// test endpoints
|
||||
if (this.config?.registerTest) {
|
||||
hono.get("/test/kv", async (c) => {
|
||||
// @ts-ignore
|
||||
const cache = c.env!.CACHE as KVNamespace;
|
||||
startTime(c, "kv-get");
|
||||
const value: any = await cache.get("count");
|
||||
endTime(c, "kv-get");
|
||||
console.log("value", value);
|
||||
startTime(c, "kv-put");
|
||||
if (!value) {
|
||||
await cache.put("count", "1");
|
||||
} else {
|
||||
await cache.put("count", (Number(value) + 1).toString());
|
||||
}
|
||||
endTime(c, "kv-put");
|
||||
|
||||
let cf: any = {};
|
||||
// @ts-ignore
|
||||
if ("cf" in c.req.raw) {
|
||||
cf = {
|
||||
// @ts-ignore
|
||||
colo: c.req.raw.cf?.colo
|
||||
};
|
||||
}
|
||||
|
||||
return c.json({ pong: true, value, cf });
|
||||
});
|
||||
|
||||
hono.get("/test/flow", async (c) => {
|
||||
const first = new LogTask("Task 0");
|
||||
const second = new LogTask("Task 1");
|
||||
const third = new LogTask("Task 2", { delay: 250 });
|
||||
const fourth = new FetchTask("Fetch Something", {
|
||||
url: "https://jsonplaceholder.typicode.com/todos/1"
|
||||
});
|
||||
const fifth = new LogTask("Task 4"); // without connection
|
||||
|
||||
const flow = new Flow("flow", [first, second, third, fourth, fifth]);
|
||||
flow.task(first).asInputFor(second);
|
||||
flow.task(first).asInputFor(third);
|
||||
flow.task(fourth).asOutputFor(third);
|
||||
|
||||
flow.setRespondingTask(fourth);
|
||||
|
||||
const execution = flow.createExecution();
|
||||
await execution.start();
|
||||
|
||||
const results = flow.tasks.map((t) => t.toJSON());
|
||||
|
||||
return c.json({ results, response: execution.getResponse() });
|
||||
});
|
||||
|
||||
hono.get("/test/template", async (c) => {
|
||||
const renderer = new SimpleRenderer({ var: 123 });
|
||||
const template = "Variable: {{ var }}";
|
||||
|
||||
return c.text(await renderer.render(template));
|
||||
});
|
||||
}
|
||||
|
||||
return hono;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { timing } from "hono/timing";
|
||||
import { Module } from "modules/Module";
|
||||
import * as SystemPermissions from "modules/permissions";
|
||||
|
||||
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
|
||||
export const serverConfigSchema = Type.Object(
|
||||
@@ -49,7 +50,7 @@ export type AppServerConfig = Static<typeof serverConfigSchema>;
|
||||
}*/
|
||||
|
||||
export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
private admin_html?: string;
|
||||
//private admin_html?: string;
|
||||
|
||||
override getRestrictedPaths() {
|
||||
return [];
|
||||
@@ -64,12 +65,6 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
}
|
||||
|
||||
override async build() {
|
||||
//this.client.use(timing());
|
||||
|
||||
/*this.client.use("*", async (c, next) => {
|
||||
console.log(`[${c.req.method}] ${c.req.url}`);
|
||||
await next();
|
||||
});*/
|
||||
this.client.use(
|
||||
"*",
|
||||
cors({
|
||||
@@ -79,18 +74,6 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
})
|
||||
);
|
||||
|
||||
/*this.client.use(async (c, next) => {
|
||||
c.res.headers.set("X-Powered-By", "BKND");
|
||||
try {
|
||||
c.res.headers.set("X-Colo", c.req.raw.cf.colo);
|
||||
} catch (e) {}
|
||||
await next();
|
||||
});
|
||||
this.client.use(async (c, next) => {
|
||||
console.log(`[${c.req.method}] ${c.req.url}`);
|
||||
await next();
|
||||
});*/
|
||||
|
||||
this.client.onError((err, c) => {
|
||||
//throw err;
|
||||
console.error(err);
|
||||
@@ -124,18 +107,31 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
this.setBuilt();
|
||||
}
|
||||
|
||||
setAdminHtml(html: string) {
|
||||
/*setAdminHtml(html: string) {
|
||||
this.admin_html = html;
|
||||
const basepath = (String(this.config.admin.basepath) + "/").replace(/\/+$/, "/");
|
||||
|
||||
const allowed_prefix = basepath + "auth";
|
||||
const login_path = basepath + "auth/login";
|
||||
|
||||
this.client.get(basepath + "*", async (c, next) => {
|
||||
const path = new URL(c.req.url).pathname;
|
||||
if (!path.startsWith(allowed_prefix)) {
|
||||
console.log("guard check permissions");
|
||||
try {
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.admin);
|
||||
} catch (e) {
|
||||
return c.redirect(login_path);
|
||||
}
|
||||
}
|
||||
|
||||
return c.html(this.admin_html!);
|
||||
});
|
||||
}
|
||||
|
||||
getAdminHtml() {
|
||||
return this.admin_html;
|
||||
}
|
||||
}*/
|
||||
|
||||
override toJSON(secrets?: boolean) {
|
||||
return this.config;
|
||||
|
||||
@@ -98,7 +98,12 @@ export class SystemController implements ClassController {
|
||||
// you must explicitly set force to override existing values
|
||||
// because omitted values gets removed
|
||||
if (force === true) {
|
||||
await this.app.mutateConfig(module).set(value);
|
||||
// force overwrite defined keys
|
||||
const newConfig = {
|
||||
...this.app.module[module].config,
|
||||
...value
|
||||
};
|
||||
await this.app.mutateConfig(module).set(newConfig);
|
||||
} else {
|
||||
await this.app.mutateConfig(module).patch("", value);
|
||||
}
|
||||
@@ -287,7 +292,7 @@ export class SystemController implements ClassController {
|
||||
|
||||
hono.get("/openapi.json", async (c) => {
|
||||
//const config = this.app.toJSON();
|
||||
const config = JSON.parse(getDefaultConfig() as any);
|
||||
const config = getDefaultConfig();
|
||||
return c.json(generateOpenAPI(config));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user