From eedab2347c191badb48af9b618187c7a33f27240 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 01:59:25 +0530 Subject: [PATCH 1/8] feat: add static file serving and refactor dashboard form to use FormData --- src/index.ts | 2 ++ src/views/dashboard.html | 62 ++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/index.ts b/src/index.ts index ef8a144..5ba9e88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { OpenAPIHono } from "@hono/zod-openapi"; import { Scalar } from "@scalar/hono-api-reference"; +import { serveStatic } from "hono/bun"; import { logger } from "hono/logger"; import { prettyJSON } from "hono/pretty-json"; import { config } from "./config"; @@ -41,6 +42,7 @@ app.use("*", prettyJSON()); app.get("/", (c) => c.redirect("/app")); +app.use("/app/*", serveStatic({ root: "./src/views" })); app.route("/auth", authRoutes); app.route("/api/config", configRoutes); app.route("/api", apiRoutes); diff --git a/src/views/dashboard.html b/src/views/dashboard.html index c4f7c02..716801d 100644 --- a/src/views/dashboard.html +++ b/src/views/dashboard.html @@ -11,7 +11,9 @@ - + @@ -54,20 +59,20 @@ -
@@ -126,7 +131,8 @@
-
- +
-
@@ -175,7 +182,7 @@
-
-
@@ -218,7 +225,7 @@

Provider Registry

- @@ -391,16 +398,16 @@
- + Connect -
@@ -505,24 +512,17 @@ return; } - const name = document.getElementById('providerName').value.trim(); - const config = { - clientId: document.getElementById('clientId').value.trim(), - clientSecret: document.getElementById('clientSecret').value.trim(), - authUrl: document.getElementById('authUrl').value.trim(), - tokenUrl: document.getElementById('tokenUrl').value.trim(), - redirectUri: document.getElementById('redirectUri').value.trim() || undefined, - scope: document.getElementById('scope').value.trim(), - }; + const formData = new FormData(configForm); + const data = Object.fromEntries(formData.entries()); try { - const response = await fetch(`/api/config/${name}`, { + const response = await fetch(`/api/config/${data.providerName}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, - body: JSON.stringify(config) + body: JSON.stringify(data) }); if (!response.ok) throw new Error(await response.text()); -- 2.52.0 From 2c2a2eb6c46e11390ef2932613df54295e390505 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 03:32:44 +0530 Subject: [PATCH 2/8] feat: migrate dashboard to JSX and move client-side scripts to JS --- src/index.ts | 2 +- src/routes/dashboard.ts | 17 -- src/routes/dashboard.tsx | 292 ++++++++++++++++++++ src/views/dashboard.html | 557 --------------------------------------- src/views/dashboard.js | 164 ++++++++++++ tsconfig.json | 2 + 6 files changed, 459 insertions(+), 575 deletions(-) delete mode 100644 src/routes/dashboard.ts create mode 100644 src/routes/dashboard.tsx delete mode 100644 src/views/dashboard.html create mode 100644 src/views/dashboard.js diff --git a/src/index.ts b/src/index.ts index 5ba9e88..d88247e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,7 @@ app.use("*", prettyJSON()); app.get("/", (c) => c.redirect("/app")); -app.use("/app/*", serveStatic({ root: "./src/views" })); +app.get("/app/dashboard.js", serveStatic({ path: "./src/views/dashboard.js" })); app.route("/auth", authRoutes); app.route("/api/config", configRoutes); app.route("/api", apiRoutes); diff --git a/src/routes/dashboard.ts b/src/routes/dashboard.ts deleted file mode 100644 index 0c8c4aa..0000000 --- a/src/routes/dashboard.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { readFile } from "node:fs/promises"; -import { join } from "node:path"; -import { Hono } from "hono"; - -const dashboardRoutes = new Hono({ strict: false }); - -dashboardRoutes.get("/", async (c) => { - try { - const htmlPath = join(process.cwd(), "src/views/dashboard.html"); - const html = await readFile(htmlPath, "utf-8"); - return c.html(html); - } catch (_error) { - return c.text("Error loading dashboard", 500); - } -}); - -export { dashboardRoutes }; diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx new file mode 100644 index 0000000..a204e7f --- /dev/null +++ b/src/routes/dashboard.tsx @@ -0,0 +1,292 @@ +/** @jsxImportSource hono/jsx */ +import { Hono } from "hono"; +import { html } from "hono/html"; +import type { Child } from "hono/jsx"; + +const dashboardRoutes = new Hono({ strict: false }); + +export const Layout = (props: { title: string; children: Child }) => ( + <> + {html``} + + + + + {props.title} + + + + + + + + + + {props.children} + + + + + +); + +export const Dashboard = () => ( + + + +
+
+
+
+
+
+

Configure Provider

+
+ +
+
+ + +
+ +
Credentials
+ +
+ + +
+
+ +
+ + +
+
+ +
Endpoints
+ +
+ + +
+
+ + +
+
+ +
+ + +
+
+ Must match provider's callback URL +
+
+
+ + +
+ +
+ +
+
+
+
+ +
+
+
+
+
+

Provider Registry

+
+ +
+ +
+
+ +
+ +
+
+ +

Enter Master API Key to access registry

+
+
+ +
+
+ +

No providers configured yet

+
+
+ +
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+); + +dashboardRoutes.get("/", async (c) => { + return c.html(); +}); + +export { dashboardRoutes }; diff --git a/src/views/dashboard.html b/src/views/dashboard.html deleted file mode 100644 index 716801d..0000000 --- a/src/views/dashboard.html +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - toknd — Auth Broker Dashboard - - - - - - - - - - - - - -
-
-
-
-
-
-

Configure Provider

-
- -
-
- - -
- -
Credentials -
- -
- - -
-
- -
- - -
-
- -
Endpoints -
- -
- - -
-
- - -
-
- -
- - -
- -
-
- - -
- -
- -
-
-
-
- -
-
-
-
-
-

Provider Registry

-
- -
-
- - - -
- -
-
- -

Enter Master API Key to access registry

-
-
-
-
-
-
-
-
- - - - - - - diff --git a/src/views/dashboard.js b/src/views/dashboard.js new file mode 100644 index 0000000..b62d12b --- /dev/null +++ b/src/views/dashboard.js @@ -0,0 +1,164 @@ +document.addEventListener("alpine:init", () => { + const formatTime = (timestamp) => { + if (!timestamp) return "Never"; + const date = new Date(timestamp); + const diff = Math.floor((Date.now() - date) / 1000); + + if (diff < 60) return "Just now"; + if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; + return date.toLocaleDateString(); + }; + + window.Alpine.data("dashboard", () => ({ + apiKey: localStorage.getItem("toknd_api_key") || "", + isUnlocked: false, + loading: false, + providers: [], + form: { + providerName: "", + clientId: "", + clientSecret: "", + authUrl: "", + tokenUrl: "", + scope: "public", + }, + notification: { + show: false, + message: "", + type: "success", + }, + + init() { + if (this.apiKey) { + this.unlock(); + } + }, + + async unlock() { + if (!this.apiKey) return; + this.loading = true; + try { + localStorage.setItem("toknd_api_key", this.apiKey); + await this.fetchProviders(); + this.isUnlocked = true; + } catch (err) { + this.showNotification(`Failed to unlock: ${err.message}`, "error"); + } finally { + this.loading = false; + } + }, + + async fetchProviders() { + this.loading = true; + try { + const [configRes, statusRes] = await Promise.all([ + fetch("/api/config", { headers: { Authorization: `Bearer ${this.apiKey}` } }), + fetch("/api/status", { headers: { Authorization: `Bearer ${this.apiKey}` } }), + ]); + + if (!configRes.ok || !statusRes.ok) throw new Error("Unauthorized"); + + const config = await configRes.json(); + const status = await statusRes.json(); + + this.providers = Object.entries(config).map(([name, cfg]) => ({ + name, + config: cfg, + status: status[name] || { accessToken: null, refreshToken: null, lastUpdated: null }, + })); + } catch (err) { + this.showNotification(err.message, "error"); + throw err; + } finally { + this.loading = false; + } + }, + + async saveConfig(event) { + if (event) event.preventDefault(); + this.loading = true; + try { + const res = await fetch(`/api/config/${this.form.providerName}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + body: JSON.stringify(this.form), + }); + + if (!res.ok) throw new Error("Failed to save"); + + this.showNotification("Saved successfully"); + await this.fetchProviders(); + + this.form = { + providerName: "", + clientId: "", + clientSecret: "", + authUrl: "", + tokenUrl: "", + scope: "public", + }; + } catch (err) { + this.showNotification(err.message, "error"); + } finally { + this.loading = false; + } + }, + + async forceRefresh(name) { + this.loading = true; + try { + const res = await fetch(`/api/refresh/${name}`, { + method: "POST", + headers: { Authorization: `Bearer ${this.apiKey}` }, + }); + + if (!res.ok) throw new Error("Refresh failed"); + + this.showNotification(`Refreshed ${name}`); + await this.fetchProviders(); + } catch (err) { + this.showNotification(err.message, "error"); + } finally { + this.loading = false; + } + }, + + editProvider(provider) { + this.form = { + providerName: provider.name, + clientId: provider.config.clientId, + clientSecret: provider.config.clientSecret, + authUrl: provider.config.authUrl, + tokenUrl: provider.config.tokenUrl, + scope: provider.config.scope, + }; + window.scrollTo({ top: 0, behavior: "smooth" }); + }, + + getRedirectUri() { + return `${window.location.origin}/auth/${this.form.providerName || "{provider}"}/callback`; + }, + + copyToClipboard(text) { + if (!text) return; + navigator.clipboard.writeText(text).then(() => { + this.showNotification("Copied"); + }); + }, + + showNotification(message, type = "success") { + this.notification = { show: true, message, type }; + setTimeout(() => { + this.notification.show = false; + }, 3000); + }, + + formatTime(timestamp) { + return formatTime(timestamp); + }, + })); +}); diff --git a/tsconfig.json b/tsconfig.json index 1fac6c7..8fdce01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "moduleResolution": "bundler", "strict": true, "skipLibCheck": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", "lib": ["ESNext"], "types": ["node", "bun-types"] }, -- 2.52.0 From 72548db9af4ddb7e6c1a4f6839d2744ca2c61c6f Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 03:39:39 +0530 Subject: [PATCH 3/8] refactor: migrate success view from static HTML to dynamic JSX route component --- src/routes/auth.ts | 7 +----- src/routes/dashboard.tsx | 38 +++++++++++++++++++++++++++++ src/views/success.html | 52 ---------------------------------------- 3 files changed, 39 insertions(+), 58 deletions(-) delete mode 100644 src/views/success.html diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 45fc314..0b3d3b0 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,5 +1,3 @@ -import { readFile } from "node:fs/promises"; -import { join } from "node:path"; import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { ConfigManager } from "../core/ConfigManager"; import { redis } from "../core/RedisClient"; @@ -115,10 +113,7 @@ authRoutes.openapi(callbackRoute, async (c) => { const tokens = await provider.exchangeCode(code, redirectUri); await tokenManager.saveTokens(providerName, tokens); - const htmlPath = join(process.cwd(), "src/views/success.html"); - let html = await readFile(htmlPath, "utf-8"); - html = html.replaceAll("__PROVIDER_NAME__", providerName); - return c.html(html); + return c.redirect(`/app/success?provider=${providerName}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred."; console.error(`[OAuth Error] ${errorMessage}`); diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index a204e7f..04b9d7d 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -285,8 +285,46 @@ export const Dashboard = () => ( ); +export const Success = (props: { provider: string }) => ( + +
+
+
+
+ +
+ +

Authenticated!

+

+ Successfully connected to{" "} + {props.provider}. + You can now close this window or return to the dashboard. +

+ +
+ + +
+
+
+
+); + dashboardRoutes.get("/", async (c) => { return c.html(); }); +dashboardRoutes.get("/success", async (c) => { + const provider = c.req.query("provider") || "Provider"; + return c.html(); +}); + export { dashboardRoutes }; diff --git a/src/views/success.html b/src/views/success.html deleted file mode 100644 index 9099337..0000000 --- a/src/views/success.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - toknd — Authentication Successful - - - - - - - - - - - -
-
-
- -
- -

Authenticated!

-

- Successfully connected to __PROVIDER_NAME__. - You can now close this window or return to the dashboard. -

- -
- - -
-
- - - -- 2.52.0 From 7192ba381dba80b6dda8508d2cadb68fcb6f0939 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 03:41:08 +0530 Subject: [PATCH 4/8] fix: clear invalid API key and reset state on unlock failure --- src/views/dashboard.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/dashboard.js b/src/views/dashboard.js index b62d12b..17bd765 100644 --- a/src/views/dashboard.js +++ b/src/views/dashboard.js @@ -43,7 +43,10 @@ document.addEventListener("alpine:init", () => { await this.fetchProviders(); this.isUnlocked = true; } catch (err) { - this.showNotification(`Failed to unlock: ${err.message}`, "error"); + this.showNotification(`Failed to unlock: ${err.message}. Check your API Key`, "error"); + localStorage.removeItem("toknd_api_key"); + this.isUnlocked = false; + this.apiKey = ""; } finally { this.loading = false; } -- 2.52.0 From bd6c3790e6d78ebc6cad6aa9c6b6cb8c31845605 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 03:42:39 +0530 Subject: [PATCH 5/8] chore: update Alpine.js to latest major version via unpkg CDN --- src/routes/dashboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index 04b9d7d..1bab581 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -34,7 +34,7 @@ export const Layout = (props: { title: string; children: Child }) => ( > {props.children} - + -- 2.52.0 From ced524dc31ffe145796496fdf9d5a7f2b42eb240 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 03:59:06 +0530 Subject: [PATCH 6/8] refactor: enhance dashboard UI styling --- src/routes/dashboard.tsx | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index 1bab581..d47c5f3 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -27,6 +27,13 @@ export const Layout = (props: { title: string; children: Child }) => ( href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet" /> + ( -
+
-- 2.52.0 From 0be9a05c0988e1b4ac6e3a3ef21383f5c6585875 Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Tue, 12 May 2026 04:07:34 +0530 Subject: [PATCH 7/8] refactor: fix lint and formatting --- src/routes/dashboard.tsx | 238 ++++++++++++++++++++++++++++++++------- 1 file changed, 198 insertions(+), 40 deletions(-) diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index d47c5f3..8bf833a 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -83,7 +83,12 @@ export const Dashboard = () => (
-
-
Credentials
+
+ Credentials +
- +
- -
@@ -140,37 +172,73 @@ export const Dashboard = () => ( - +
- +
- -
- Must match provider's callback URL + + Must match provider's callback URL +
- +
- @@ -186,14 +254,22 @@ export const Dashboard = () => (

Provider Registry

-
-
+
@@ -204,74 +280,151 @@ export const Dashboard = () => (
-
+

No providers configured yet

-
+