feat: update setup pages and router to enhance UI and favicon handling

This commit is contained in:
shuaiplus
2026-02-14 01:03:40 +08:00
parent 4772c17e44
commit ff7b44e501
3 changed files with 208 additions and 175 deletions
+86 -70
View File
@@ -21,10 +21,12 @@ function t(lang: Lang, key: string): string {
cfgDescDefault: '检测到你正在使用示例/默认 JWT_SECRET。为了安全起见,请先修改为随机强密钥后再注册/使用。', cfgDescDefault: '检测到你正在使用示例/默认 JWT_SECRET。为了安全起见,请先修改为随机强密钥后再注册/使用。',
cfgDescTooShort: '检测到 JWT_SECRET 长度不足 32 个字符。为了安全起见,请使用至少 32 位的随机字符串。', cfgDescTooShort: '检测到 JWT_SECRET 长度不足 32 个字符。为了安全起见,请使用至少 32 位的随机字符串。',
cfgStepsTitle: '如何在 Cloudflare 修改 JWT_SECRET', cfgStepsTitle: '如何在 Cloudflare 修改 JWT_SECRET',
cfgSteps: '打开 Cloudflare 控制台 → Workers 和 Pages → 选择 nodewarden → 设置 → 变量和机密 → 添加变量。\n类型:密钥\n名称:JWT_SECRET\n值:粘贴你生成的随机密钥\n保存后,等待重新部署生效。', cfgStepsAdd: '打开 Cloudflare 控制台 → Workers 和 Pages → 选择 nodewarden → 设置 → 变量和机密 → 添加变量。\n类型:密钥\n名称:JWT_SECRET\n值:粘贴你生成的随机密钥\n保存后,等待重新部署生效。',
cfgStepsEdit: '打开 Cloudflare 控制台 → Workers 和 Pages → 选择 nodewarden → 设置 → 变量和机密 → 找到 JWT_SECRET 并编辑。\n类型:密钥\n名称:JWT_SECRET\n值:替换为新的随机强密钥\n保存后,等待重新部署生效。',
cfgGenTitle: '随机密钥生成器', cfgGenTitle: '随机密钥生成器',
cfgGenHint: '建议长度:至少 32 字符(推荐 64+)。点击刷新生成新的随机值。', cfgGenHint: '建议长度:至少 32 字符(推荐 64+)。点击刷新生成新的随机值。',
cfgCopy: '复制', cfgCopy: '复制',
cfgCopied: '已复制',
cfgRefresh: '刷新', cfgRefresh: '刷新',
// Shared // Shared
@@ -42,10 +44,12 @@ function t(lang: Lang, key: string): string {
cfgDescDefault: 'You are using the sample/default JWT_SECRET. For safety, please change it to a strong random secret before registration/usage.', cfgDescDefault: 'You are using the sample/default JWT_SECRET. For safety, please change it to a strong random secret before registration/usage.',
cfgDescTooShort: 'JWT_SECRET is shorter than 32 characters. For safety, use a random string with at least 32 characters.', cfgDescTooShort: 'JWT_SECRET is shorter than 32 characters. For safety, use a random string with at least 32 characters.',
cfgStepsTitle: 'How to set JWT_SECRET in Cloudflare', cfgStepsTitle: 'How to set JWT_SECRET in Cloudflare',
cfgSteps: 'Open Cloudflare Dashboard → Workers & Pages → select nodewarden → Settings → Variables and Secrets → Add variable.\nType: Secret\nName: JWT_SECRET\nValue: paste a random secret\nSave, and wait for redeploy to take effect.', cfgStepsAdd: 'Open Cloudflare Dashboard → Workers & Pages → select nodewarden → Settings → Variables and Secrets → Add variable.\nType: Secret\nName: JWT_SECRET\nValue: paste a random secret\nSave, and wait for redeploy to take effect.',
cfgStepsEdit: 'Open Cloudflare Dashboard → Workers & Pages → select nodewarden → Settings → Variables and Secrets → find JWT_SECRET and edit it.\nType: Secret\nName: JWT_SECRET\nValue: replace with a new strong random secret\nSave, and wait for redeploy to take effect.',
cfgGenTitle: 'Random secret generator', cfgGenTitle: 'Random secret generator',
cfgGenHint: 'Recommended length: 32+ characters (64+ preferred). Click refresh to generate a new one.', cfgGenHint: 'Recommended length: 32+ characters (64+ preferred). Click refresh to generate a new one.',
cfgCopy: 'Copy', cfgCopy: 'Copy',
cfgCopied: 'Copied',
cfgRefresh: 'Refresh', cfgRefresh: 'Refresh',
// Shared // Shared
@@ -61,21 +65,16 @@ function baseStyles(): string {
return ` return `
:root { :root {
color-scheme: light; color-scheme: light;
--bg0: #0b0b0f; --bg: #f3f4f6;
--bg1: #0f1020; --card: #ffffff;
--card: rgba(255, 255, 255, 0.08); --border: #d0d5dd;
--card2: rgba(255, 255, 255, 0.06); --text: #101828;
--border: rgba(255, 255, 255, 0.14); --muted: #475467;
--text: rgba(255, 255, 255, 0.92); --muted2: #667085;
--muted: rgba(255, 255, 255, 0.62); --accent: #111418;
--muted2: rgba(255, 255, 255, 0.52); --shadow: 0 16px 44px rgba(16, 24, 40, 0.08);
--accent: #0a84ff; --radius: 20px;
--accent2: #64d2ff; --radius2: 16px;
--danger: #ff453a;
--ok: #32d74b;
--shadow: 0 16px 60px rgba(0, 0, 0, 0.50);
--radius: 18px;
--radius2: 14px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
@@ -83,107 +82,112 @@ function baseStyles(): string {
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: background: var(--bg);
radial-gradient(900px 600px at 15% 10%, rgba(100, 210, 255, 0.25), transparent 60%),
radial-gradient(900px 600px at 85% 20%, rgba(10, 132, 255, 0.22), transparent 60%),
radial-gradient(900px 600px at 50% 90%, rgba(50, 215, 75, 0.10), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
color: var(--text); color: var(--text);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 24px; padding: 40px 24px;
} }
.shell { width: max(500px); } .shell { width: min(920px, 100%); }
.panel { .panel {
padding: 22px; padding: 40px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(255,255,255,0.06); background: var(--card);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: var(--shadow); box-shadow: var(--shadow);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
} }
.top { .top {
display: flex; display: flex;
gap: 14px; gap: 14px;
align-items: center; align-items: center;
margin-bottom: 14px; margin-bottom: 10px;
} }
.mark { .mark {
width: 46px; width: 60px;
height: 46px; height: 60px;
border-radius: 14px; border-radius: 16px;
background: linear-gradient(135deg, rgba(10,132,255,0.85), rgba(100,210,255,0.55)); background: #111418;
border: 1px solid rgba(255,255,255,0.20); border: 1px solid #111418;
box-shadow: 0 10px 40px rgba(10, 132, 255, 0.30);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 24px;
font-weight: 800; font-weight: 800;
letter-spacing: 1px; letter-spacing: 0.6px;
color: rgba(255,255,255,0.96); line-height: 1;
color: #ffffff;
text-transform: uppercase; text-transform: uppercase;
user-select: none; user-select: none;
} }
.title { display: flex; flex-direction: column; gap: 4px; } .title { display: flex; flex-direction: column; gap: 4px; }
.title h1 { font-size: 22px; margin: 0; letter-spacing: -0.3px; } .title h1 { font-size: 30px; margin: 0; letter-spacing: -0.6px; }
.title p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; } .title p { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.6; }
h2 { font-size: 16px; margin: 14px 0 10px 0; letter-spacing: -0.2px; } h2 { font-size: 22px; margin: 20px 0 14px 0; letter-spacing: -0.3px; }
.lead { font-size: 13px; line-height: 1.7; color: rgba(255,255,255,0.86); } .lead { font-size: 16px; line-height: 1.75; color: #344054; }
.kv { .kv {
border-radius: var(--radius2); border-radius: var(--radius2);
border: 1px solid rgba(255,255,255,0.14); border: 1px solid var(--border);
background: rgba(255,255,255,0.05); background: #fafbfc;
padding: 14px; padding: 18px;
margin-top: 12px; margin-top: 0;
} }
.kv h3 { margin: 0 0 8px 0; font-size: 13px; color: rgba(255,255,255,0.86); } .kv h3 { margin: 0 0 10px 0; font-size: 17px; color: #1d2939; }
.kv p { margin: 0; font-size: 12px; line-height: 1.55; color: var(--muted); white-space: pre-line; } .kv p { margin: 0; font-size: 15px; line-height: 1.7; color: var(--muted); white-space: pre-line; }
.server { .server {
margin-top: 10px; margin-top: 10px;
font-family: var(--mono); font-family: var(--mono);
font-size: 12px; font-size: 14px;
padding: 10px 12px; padding: 12px 14px;
border-radius: 12px; border-radius: 14px;
background: rgba(0,0,0,0.25); background: #ffffff;
border: 1px solid rgba(255,255,255,0.12); border: 1px solid #d5dae1;
word-break: break-all; word-break: break-all;
color: rgba(255,255,255,0.90); color: #111418;
} }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } .row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
}
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } }
.btn { .btn {
height: 38px; height: 46px;
padding: 0 12px; padding: 0 14px;
border-radius: 12px; border-radius: 14px;
border: 1px solid rgba(255,255,255,0.18); border: 1px solid #d5dae1;
background: rgba(0,0,0,0.18); background: #ffffff;
color: rgba(255,255,255,0.92); color: #111418;
font-weight: 700; font-weight: 700;
font-size: 15px;
cursor: pointer; cursor: pointer;
} }
.btn.primary { .btn.primary {
background: linear-gradient(135deg, rgba(10,132,255,0.95), rgba(100,210,255,0.60)); border-color: #111418;
background: #111418;
color: #ffffff;
} }
.btn:disabled { opacity: 0.55; cursor: not-allowed; } .btn:disabled { opacity: 0.55; cursor: not-allowed; }
a { color: rgba(100, 210, 255, 0.92); text-decoration: none; } a { color: #175cd3; text-decoration: none; }
a:hover { text-decoration: underline; } a:hover { text-decoration: underline; }
.footer { .footer {
margin-top: 18px; margin-top: 24px;
padding-top: 14px; padding-top: 18px;
border-top: 1px solid rgba(255,255,255,0.10); border-top: 1px solid var(--border);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 12px; font-size: 14px;
color: rgba(255,255,255,0.55); color: var(--muted2);
} }
`; `;
} }
@@ -194,6 +198,7 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
const lang: Lang = isChineseFromRequest(request) ? 'zh' : 'en'; const lang: Lang = isChineseFromRequest(request) ? 'zh' : 'en';
const descKey = state === 'missing' ? 'cfgDescMissing' : state === 'default' ? 'cfgDescDefault' : 'cfgDescTooShort'; const descKey = state === 'missing' ? 'cfgDescMissing' : state === 'default' ? 'cfgDescDefault' : 'cfgDescTooShort';
const stepsKey = state === 'missing' ? 'cfgStepsAdd' : 'cfgStepsEdit';
return `<!DOCTYPE html> return `<!DOCTYPE html>
<html lang="${lang === 'zh' ? 'zh-CN' : 'en'}"> <html lang="${lang === 'zh' ? 'zh-CN' : 'en'}">
@@ -217,9 +222,10 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
<h2>${t(lang, 'cfgTitle')}</h2> <h2>${t(lang, 'cfgTitle')}</h2>
<div class="lead">${t(lang, descKey)}</div> <div class="lead">${t(lang, descKey)}</div>
<div class="grid">
<div class="kv"> <div class="kv">
<h3>${t(lang, 'cfgStepsTitle')}</h3> <h3>${t(lang, 'cfgStepsTitle')}</h3>
<p>${t(lang, 'cfgSteps') <p>${t(lang, stepsKey)
.replace(/^类型:密钥/m, '<b>类型:密钥</b>') .replace(/^类型:密钥/m, '<b>类型:密钥</b>')
.replace(/^名称:JWT_SECRET/m, '<b>名称:JWT_SECRET</b>') .replace(/^名称:JWT_SECRET/m, '<b>名称:JWT_SECRET</b>')
.replace(/^Type: Secret/m, '<b>Type: Secret</b>') .replace(/^Type: Secret/m, '<b>Type: Secret</b>')
@@ -234,7 +240,8 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
<div style="height: 10px"></div> <div style="height: 10px"></div>
<div class="row"> <div class="row">
<button class="btn primary" type="button" onclick="refreshSecret()">${t(lang, 'cfgRefresh')}</button> <button class="btn primary" type="button" onclick="refreshSecret()">${t(lang, 'cfgRefresh')}</button>
<button class="btn" type="button" onclick="copySecret()">${t(lang, 'cfgCopy')}</button> <button class="btn" id="copyBtn" type="button" onclick="copySecret()">${t(lang, 'cfgCopy')}</button>
</div>
</div> </div>
</div> </div>
@@ -253,7 +260,7 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
<script> <script>
// Generate a URL-safe random secret (default length: 64) // Generate a URL-safe random secret (default length: 64)
function genSecret(len) { function genSecret(len) {
len = len || 50; len = len || 64;
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
const bytes = new Uint8Array(len); const bytes = new Uint8Array(len);
crypto.getRandomValues(bytes); crypto.getRandomValues(bytes);
@@ -265,12 +272,13 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
} }
function refreshSecret() { function refreshSecret() {
const s = genSecret(50); const s = genSecret(64);
document.getElementById('secret').textContent = s; document.getElementById('secret').textContent = s;
} }
async function copySecret() { async function copySecret() {
const s = document.getElementById('secret').textContent || ''; const s = document.getElementById('secret').textContent || '';
const btn = document.getElementById('copyBtn');
try { try {
await navigator.clipboard.writeText(s); await navigator.clipboard.writeText(s);
} catch { } catch {
@@ -281,6 +289,14 @@ export function renderJwtSecretWarningPage(request: Request, state: JwtSecretSta
document.execCommand('copy'); document.execCommand('copy');
ta.remove(); ta.remove();
} }
if (btn) {
const original = btn.textContent;
btn.textContent = '${t(lang, 'cfgCopied')}';
setTimeout(() => {
btn.textContent = original;
}, 1200);
}
} }
refreshSecret(); refreshSecret();
+79 -87
View File
@@ -13,21 +13,18 @@ const registerPageHTML = `<!DOCTYPE html>
<style> <style>
:root { :root {
color-scheme: light; color-scheme: light;
--bg0: #0b0b0f; --bg: #f3f4f6;
--bg1: #0f1020; --card: #ffffff;
--card: rgba(255, 255, 255, 0.08); --border: #d0d5dd;
--card2: rgba(255, 255, 255, 0.06); --text: #101828;
--border: rgba(255, 255, 255, 0.14); --muted: #475467;
--text: rgba(255, 255, 255, 0.92); --muted2: #667085;
--muted: rgba(255, 255, 255, 0.62); --accent: #111418;
--muted2: rgba(255, 255, 255, 0.52); --danger: #b42318;
--accent: #0a84ff; --ok: #027a48;
--accent2: #64d2ff; --shadow: 0 16px 44px rgba(16, 24, 40, 0.08);
--danger: #ff453a; --radius: 20px;
--ok: #32d74b; --radius2: 16px;
--shadow: 0 16px 60px rgba(0, 0, 0, 0.50);
--radius: 18px;
--radius2: 14px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
@@ -35,26 +32,20 @@ const registerPageHTML = `<!DOCTYPE html>
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: background: var(--bg);
radial-gradient(900px 600px at 15% 10%, rgba(100, 210, 255, 0.25), transparent 60%),
radial-gradient(900px 600px at 85% 20%, rgba(10, 132, 255, 0.22), transparent 60%),
radial-gradient(900px 600px at 50% 90%, rgba(50, 215, 75, 0.10), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
color: var(--text); color: var(--text);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 24px; padding: 40px 24px;
} }
.shell { width: max(500px); } .shell { width: min(920px, 100%); }
.panel { .panel {
padding: 22px; padding: 40px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(255,255,255,0.06); background: var(--card);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: var(--shadow); box-shadow: var(--shadow);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
} }
.top { .top {
display: flex; display: flex;
@@ -63,83 +54,85 @@ const registerPageHTML = `<!DOCTYPE html>
margin-bottom: 14px; margin-bottom: 14px;
} }
.mark { .mark {
width: 46px; width: 60px;
height: 46px; height: 60px;
border-radius: 14px; border-radius: 16px;
background: linear-gradient(135deg, rgba(10,132,255,0.85), rgba(100,210,255,0.55)); background: #111418;
border: 1px solid rgba(255,255,255,0.20); border: 1px solid #111418;
box-shadow: 0 10px 40px rgba(10, 132, 255, 0.30);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 24px;
font-weight: 800; font-weight: 800;
letter-spacing: 1px; letter-spacing: 0.6px;
color: rgba(255,255,255,0.96); line-height: 1;
color: #ffffff;
text-transform: uppercase; text-transform: uppercase;
user-select: none; user-select: none;
} }
.title { display: flex; flex-direction: column; gap: 4px; } .title { display: flex; flex-direction: column; gap: 4px; }
.title h1 { font-size: 22px; margin: 0; letter-spacing: -0.3px; } .title h1 { font-size: 30px; margin: 0; letter-spacing: -0.6px; }
.title p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; } .title p { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.6; }
h2 { font-size: 16px; margin: 14px 0 10px 0; letter-spacing: -0.2px; } h2 { font-size: 22px; margin: 20px 0 14px 0; letter-spacing: -0.3px; }
.message { .message {
display: none; display: none;
border-radius: 14px; border-radius: 12px;
padding: 12px 12px; padding: 14px 14px;
margin: 0 0 12px 0; margin: 0 0 12px 0;
font-size: 13px; font-size: 15px;
line-height: 1.45; line-height: 1.45;
border: 1px solid rgba(255,255,255,0.14); border: 1px solid var(--border);
background: rgba(255,255,255,0.06); background: #fafbfc;
} }
.message.error { .message.error {
display: block; display: block;
border-color: rgba(255, 69, 58, 0.40); border-color: #fecdca;
background: rgba(255, 69, 58, 0.10); background: #fff6f5;
color: rgba(255, 255, 255, 0.92); color: var(--danger);
} }
.message.success { .message.success {
display: block; display: block;
border-color: rgba(50, 215, 75, 0.35); border-color: #abefc6;
background: rgba(50, 215, 75, 0.10); background: #f0fdf4;
color: rgba(255, 255, 255, 0.92); color: var(--ok);
} }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 540px) { .grid { grid-template-columns: 1fr; } } @media (max-width: 540px) { .grid { grid-template-columns: 1fr; } }
.field { display: flex; flex-direction: column; gap: 7px; } .field { display: flex; flex-direction: column; gap: 7px; }
label { font-size: 12px; color: var(--muted); letter-spacing: 0.2px; } label { font-size: 14px; color: var(--muted); letter-spacing: 0.1px; }
input { input {
height: 42px; height: 50px;
padding: 0 12px; padding: 0 14px;
border-radius: 12px; border-radius: 14px;
border: 1px solid rgba(255,255,255,0.18); border: 1px solid #d5dae1;
background: rgba(0,0,0,0.18); background: #ffffff;
color: rgba(255,255,255,0.92); color: var(--text);
outline: none; outline: none;
font-size: 14px; font-size: 16px;
transition: border-color 160ms ease, box-shadow 160ms ease; transition: border-color 160ms ease, box-shadow 160ms ease;
} }
input::placeholder { color: rgba(255,255,255,0.35); } input::placeholder { color: #98a2b3; }
input:focus { input:focus {
border-color: rgba(10, 132, 255, 0.55); border-color: #111418;
box-shadow: 0 0 0 6px rgba(10, 132, 255, 0.12); box-shadow: 0 0 0 5px rgba(17, 20, 24, 0.08);
} }
.hint { margin: 0; color: var(--muted2); font-size: 12px; line-height: 1.55; } .hint { margin: 0; color: var(--muted2); font-size: 14px; line-height: 1.6; }
.actions { margin-top: 12px; display: flex; gap: 10px; align-items: center; } .actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; }
.primary { .primary {
width: 100%; width: 100%;
height: 44px; height: 52px;
border-radius: 14px; border-radius: 14px;
border: 1px solid rgba(255,255,255,0.18); border: 1px solid #111418;
background: linear-gradient(135deg, rgba(10,132,255,0.95), rgba(100,210,255,0.60)); background: #111418;
color: rgba(255,255,255,0.96); color: #ffffff;
font-weight: 700; font-weight: 700;
letter-spacing: 0.2px; font-size: 16px;
letter-spacing: 0.1px;
cursor: pointer; cursor: pointer;
transition: transform 120ms ease, filter 120ms ease; transition: transform 120ms ease, filter 120ms ease;
} }
@@ -150,37 +143,37 @@ const registerPageHTML = `<!DOCTYPE html>
.sideCard { display: flex; flex-direction: column; gap: 12px; } .sideCard { display: flex; flex-direction: column; gap: 12px; }
.kv { .kv {
border-radius: var(--radius2); border-radius: var(--radius2);
border: 1px solid rgba(255,255,255,0.14); border: 1px solid var(--border);
background: rgba(255,255,255,0.05); background: #fafbfc;
padding: 14px; padding: 18px;
margin-bottom: 10px; margin-bottom: 14px;
} }
.kv h3 { margin: 0 0 8px 0; font-size: 13px; color: rgba(255,255,255,0.86); } .kv h3 { margin: 0 0 10px 0; font-size: 17px; color: #1d2939; }
.kv p { margin: 0; font-size: 12px; line-height: 1.55; color: var(--muted); } .kv p { margin: 0; font-size: 15px; line-height: 1.65; color: var(--muted); }
.server { .server {
margin-top: 10px; margin-top: 10px;
font-family: var(--mono); font-family: var(--mono);
font-size: 12px; font-size: 14px;
padding: 10px 12px; padding: 12px 14px;
border-radius: 12px; border-radius: 14px;
background: rgba(0,0,0,0.25); background: #ffffff;
border: 1px solid rgba(255,255,255,0.12); border: 1px solid #d5dae1;
word-break: break-all; word-break: break-all;
color: rgba(255,255,255,0.90); color: #111418;
} }
a { color: rgba(100, 210, 255, 0.92); text-decoration: none; } a { color: #175cd3; text-decoration: none; }
a:hover { text-decoration: underline; } a:hover { text-decoration: underline; }
.footer { .footer {
margin-top: 18px; margin-top: 24px;
padding-top: 14px; padding-top: 18px;
border-top: 1px solid rgba(255,255,255,0.10); border-top: 1px solid var(--border);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 12px; font-size: 14px;
color: rgba(255,255,255,0.55); color: var(--muted2);
} }
.muted { color: var(--muted); } .muted { color: var(--muted); }
</style> </style>
@@ -196,11 +189,10 @@ const registerPageHTML = `<!DOCTYPE html>
</div> </div>
</div> </div>
<div class="muted" id="t_intro" style="font-size: 13px; line-height: 1.7;"> <div class="muted" id="t_intro" style="font-size: 16px; line-height: 1.7;">
Create your first account to finish setup. Then use any official Bitwarden client to sign in. Create your first account to finish setup. Then use any official Bitwarden client to sign in.
</div> </div>
<div style="height: 14px"></div>
<h2 id="t_setup">Setup</h2> <h2 id="t_setup">Setup</h2>
<div id="message" class="message"></div> <div id="message" class="message"></div>
+28 -3
View File
@@ -49,6 +49,20 @@ import {
handlePublicDownloadAttachment, handlePublicDownloadAttachment,
} from './handlers/attachments'; } from './handlers/attachments';
function getNwIconSvg(): string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="NW icon"><rect x="4" y="4" width="88" height="88" rx="20" fill="#111418"/><text x="48" y="60" text-anchor="middle" font-size="36" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-weight="800" letter-spacing="0.5" fill="#FFFFFF">NW</text></svg>`;
}
function handleNwFavicon(): Response {
return new Response(getNwIconSvg(), {
status: 200,
headers: {
'Content-Type': 'image/svg+xml; charset=utf-8',
'Cache-Control': 'public, max-age=604800',
},
});
}
// Icons handler - proxy to Bitwarden's official icon service // Icons handler - proxy to Bitwarden's official icon service
async function handleGetIcon(request: Request, env: Env, hostname: string): Promise<Response> { async function handleGetIcon(request: Request, env: Env, hostname: string): Promise<Response> {
try { try {
@@ -105,9 +119,20 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return handleDisableSetup(request, env); return handleDisableSetup(request, env);
} }
// Favicon - return empty // Browser/devtools probe endpoint
if (path === '/favicon.ico') { if (path === '/.well-known/appspecific/com.chrome.devtools.json' && method === 'GET') {
return new Response(null, { status: 204 }); return new Response('{}', {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store',
},
});
}
// Favicon
if ((path === '/favicon.ico' || path === '/favicon.svg') && method === 'GET') {
return handleNwFavicon();
} }
// Icon endpoint - proxy to Bitwarden's icon service (no auth required) // Icon endpoint - proxy to Bitwarden's icon service (no auth required)