From c4c25efc501bbe3907a178310ca954b0039c3ba7 Mon Sep 17 00:00:00 2001
From: shuaiplus <2327005759@qq.com>
Date: Fri, 27 Feb 2026 01:56:32 +0800
Subject: [PATCH] Add runtime configuration loader and styles for web
application
---
public/index.html | 14 ++++++++++
src/webclient/script.ts => public/web/app.js | 27 ++++++++-----------
public/web/runtime-config.js | 15 +++++++++++
.../styles.ts => public/web/styles.css | 3 +--
src/handlers/web.ts | 9 -------
src/router.ts | 13 ++++-----
src/webclient/page.ts | 25 -----------------
wrangler.toml | 1 +
8 files changed, 49 insertions(+), 58 deletions(-)
create mode 100644 public/index.html
rename src/webclient/script.ts => public/web/app.js (98%)
create mode 100644 public/web/runtime-config.js
rename src/webclient/styles.ts => public/web/styles.css (99%)
delete mode 100644 src/handlers/web.ts
delete mode 100644 src/webclient/page.ts
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..4b4f505
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,14 @@
+
+
+
+ '
@@ -1376,7 +1375,7 @@
setMsg('Change master password failed: '+(e&&e.message?e.message:String(e)), 'err');
}
}
- async function onEnableTotp(form){ var fd=new FormData(form); state.totpSetupSecret=String(fd.get('secret')||'').toUpperCase().replace(/[\\s-]/g,'').replace(/=+$/g,''); state.totpSetupToken=String(fd.get('token')||'').trim(); if(!state.totpSetupSecret) return setMsg('TOTP secret is required.', 'err'); if(!state.totpSetupToken) return setMsg('TOTP token is required.', 'err'); var r=await authFetch('/api/accounts/totp',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:true,secret:state.totpSetupSecret,token:state.totpSetupToken})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Enable TOTP failed.', 'err'); state.totpSetupToken=''; render(); setMsg('TOTP enabled.', 'ok'); }
+ async function onEnableTotp(form){ var fd=new FormData(form); state.totpSetupSecret=String(fd.get('secret')||'').toUpperCase().replace(/[\s-]/g,'').replace(/=+$/g,''); state.totpSetupToken=String(fd.get('token')||'').trim(); if(!state.totpSetupSecret) return setMsg('TOTP secret is required.', 'err'); if(!state.totpSetupToken) return setMsg('TOTP token is required.', 'err'); var r=await authFetch('/api/accounts/totp',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:true,secret:state.totpSetupSecret,token:state.totpSetupToken})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Enable TOTP failed.', 'err'); state.totpSetupToken=''; render(); setMsg('TOTP enabled.', 'ok'); }
function onDisableTotp(){ state.totpDisableOpen=true; state.totpDisablePassword=''; state.totpDisableError=''; render(); }
async function onDisableTotpSubmit(form){
var fd=new FormData(form); state.totpDisablePassword=String(fd.get('masterPassword')||'');
@@ -1395,7 +1394,7 @@
}
async function onBulkDelete(){ var ids=[]; for(var k in state.selectedMap){ if(state.selectedMap[k]) ids.push(k);} if(ids.length===0) return setMsg('Select items first.', 'err'); if(!window.confirm('Delete selected '+ids.length+' items?')) return; for(var i=0;i
state.folders.length) return setMsg('Invalid folder selection.', 'err'); var folderId=idx===0?null:state.folders[idx-1].id; var r=await authFetch('/api/ciphers/move',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ids:ids,folderId:folderId})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Bulk move failed.', 'err'); await loadVault(); render(); setMsg('Moved selected items.', 'ok'); }
+ async function onBulkMove(){ var ids=[]; for(var k in state.selectedMap){ if(state.selectedMap[k]) ids.push(k);} if(ids.length===0) return setMsg('Select items first.', 'err'); var opts=['0) No folder']; for(var i=0;istate.folders.length) return setMsg('Invalid folder selection.', 'err'); var folderId=idx===0?null:state.folders[idx-1].id; var r=await authFetch('/api/ciphers/move',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ids:ids,folderId:folderId})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Bulk move failed.', 'err'); await loadVault(); render(); setMsg('Moved selected items.', 'ok'); }
async function onCreateInvite(form){ var fd=new FormData(form); var h=Number(fd.get('hours')||168); var r=await authFetch('/api/admin/invites',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({expiresInHours:h})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Create invite failed.', 'err'); await loadAdminData(); render(); setMsg('Invite created.', 'ok'); }
async function onToggleUserStatus(id,status){ var n=status==='active'?'banned':'active'; var r=await authFetch('/api/admin/users/'+encodeURIComponent(id)+'/status',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({status:n})}); var j=await jsonOrNull(r); if(!r.ok) return setMsg((j&&(j.error||j.error_description))||'Update user status failed.', 'err'); await loadAdminData(); render(); setMsg('User status updated.', 'ok'); }
@@ -1449,9 +1448,9 @@
if(a==='select-all'){ var list=filteredCiphers(); state.selectedMap={}; for(var i=0;i {
- void request;
- void env;
- return htmlResponse(renderWebClientHTML());
-}
diff --git a/src/router.ts b/src/router.ts
index 6a2b472..b3139a7 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -49,7 +49,6 @@ import { handleSync } from './handlers/sync';
// Setup handlers
import { handleSetupStatus } from './handlers/setup';
-import { handleWebClientPage } from './handlers/web';
import { handleKnownDevice, handleGetDevices, handleUpdateDeviceToken } from './handlers/devices';
// Import handler
@@ -186,16 +185,18 @@ export async function handleRequest(request: Request, env: Env): Promise
-
-
-
-
- NodeWarden Web
-
-
-
-
-
-
-`;
-}
diff --git a/wrangler.toml b/wrangler.toml
index 6d578b5..e6c8961 100644
--- a/wrangler.toml
+++ b/wrangler.toml
@@ -1,6 +1,7 @@
name = "nodewarden"
main = "src/index.ts"
compatibility_date = "2024-01-01"
+assets = { directory = "./public", not_found_handling = "single-page-application", run_worker_first = ["/api/*", "/identity/*", "/icons/*", "/setup/*", "/config", "/notifications/*", "/.well-known/*", "/favicon.ico", "/favicon.svg"] }
# D1 Database for storing vault data
[[d1_databases]]