mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: update setup pages and router to enhance UI and favicon handling
This commit is contained in:
+86
-70
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user