All checks were successful
ui-regression / playwright-regression (push) Successful in 6m20s
docker-images / resolve-build-targets (push) Successful in 6s
docker-images / build-and-push (admin) (push) Successful in 25s
docker-images / build-and-push (backend) (push) Successful in 35s
docker-images / build-and-push (frontend) (push) Successful in 1m46s
docker-images / submit-indexnow (push) Successful in 15s
153 lines
5.9 KiB
Plaintext
153 lines
5.9 KiB
Plaintext
---
|
|
import '../styles/global.css'
|
|
|
|
import { api, DEFAULT_SITE_SETTINGS } from '../lib/api/client'
|
|
import { sanitizeMaintenanceReturnTo } from '../lib/maintenance'
|
|
|
|
const errorCode = Astro.url.searchParams.get('error')
|
|
const returnTo = sanitizeMaintenanceReturnTo(Astro.url.searchParams.get('returnTo'))
|
|
|
|
let siteSettings = DEFAULT_SITE_SETTINGS
|
|
|
|
try {
|
|
siteSettings = await api.getSiteSettings()
|
|
} catch (error) {
|
|
console.error('Failed to load site settings on maintenance page:', error)
|
|
}
|
|
|
|
const errorMessage =
|
|
errorCode === 'empty'
|
|
? '请先输入访问口令。'
|
|
: errorCode === 'invalid'
|
|
? '口令不正确,请重新输入。'
|
|
: errorCode === 'unavailable'
|
|
? '当前无法校验访问口令,请稍后再试。'
|
|
: ''
|
|
---
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
<link rel="icon" href="/favicon.ico" />
|
|
<title>{siteSettings.siteName} · 维护模式</title>
|
|
</head>
|
|
<body class="min-h-screen bg-[var(--bg)] text-[var(--text)]">
|
|
<main class="mx-auto flex min-h-screen w-full max-w-6xl items-center px-4 py-10 sm:px-6 lg:px-8">
|
|
<section class="terminal-toolbar-shell mx-auto w-full max-w-2xl overflow-hidden rounded-[2rem] p-0">
|
|
<div class="border-b border-[var(--border-color)] px-6 py-5 sm:px-8">
|
|
<div class="flex items-center gap-3">
|
|
<span class="flex h-12 w-12 items-center justify-center rounded-2xl border border-[var(--border-color)] bg-[var(--primary)]/10 text-xl font-semibold text-[var(--primary)]">
|
|
{siteSettings.siteShortName?.charAt(0) || siteSettings.siteName?.charAt(0) || 'T'}
|
|
</span>
|
|
<div class="min-w-0">
|
|
<p class="terminal-toolbar-label">MAINTENANCE ACCESS</p>
|
|
<h1 class="mt-1 text-2xl font-bold text-[var(--title-color)] sm:text-3xl">
|
|
{siteSettings.siteName} 正在维护
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-6 px-6 py-6 sm:px-8 sm:py-8">
|
|
<div class="rounded-3xl border border-[var(--border-color)] bg-[var(--terminal-bg)]/80 p-5">
|
|
<p class="text-sm leading-7 text-[var(--text-secondary)]">
|
|
当前前台内容暂时对外隐藏。你如果拿到了测试口令,可以直接输入进入站点继续浏览;没有口令的话,等我们开放后再访问即可。
|
|
</p>
|
|
{errorMessage && (
|
|
<div class="mt-4 rounded-2xl border border-[var(--danger)]/20 bg-[var(--danger)]/8 px-4 py-3 text-sm text-[var(--danger)]">
|
|
{errorMessage}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<form
|
|
method="post"
|
|
action="/api/maintenance/unlock"
|
|
class="space-y-4"
|
|
data-maintenance-unlock-form
|
|
>
|
|
<input type="hidden" name="returnTo" value={returnTo} />
|
|
|
|
<label class="block">
|
|
<span class="terminal-form-label">访问口令</span>
|
|
<input
|
|
type="password"
|
|
name="code"
|
|
autocomplete="current-password"
|
|
placeholder="请输入测试口令"
|
|
class="terminal-form-input"
|
|
/>
|
|
</label>
|
|
|
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
<button type="submit" class="terminal-action-button terminal-action-button-primary min-w-[10rem]">
|
|
进入站点
|
|
</button>
|
|
<p class="text-sm leading-6 text-[var(--text-tertiary)]">
|
|
口令修改后,旧的访问凭证会自动失效。
|
|
</p>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="rounded-3xl border border-dashed border-[var(--border-color)] bg-[var(--header-bg)]/40 px-5 py-4">
|
|
<p class="text-xs uppercase tracking-[0.22em] text-[var(--text-tertiary)]">
|
|
Return Target
|
|
</p>
|
|
<p class="mt-2 font-mono text-sm text-[var(--title-color)]">{returnTo}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<script>
|
|
const maintenanceForm = document.querySelector('[data-maintenance-unlock-form]');
|
|
|
|
if (maintenanceForm instanceof HTMLFormElement) {
|
|
maintenanceForm.addEventListener('submit', async (event) => {
|
|
event.preventDefault();
|
|
|
|
const submitButton = maintenanceForm.querySelector('button[type="submit"]');
|
|
if (submitButton instanceof HTMLButtonElement) {
|
|
submitButton.disabled = true;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData(maintenanceForm);
|
|
const response = await fetch(maintenanceForm.action, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify({
|
|
code: String(formData.get('code') || ''),
|
|
returnTo: String(formData.get('returnTo') || '/'),
|
|
}),
|
|
});
|
|
|
|
window.location.assign(response.url);
|
|
} catch (error) {
|
|
console.error('Failed to submit maintenance unlock form:', error);
|
|
const currentUrl = new URL(window.location.href);
|
|
const nextUrl = new URL('/maintenance', currentUrl.origin);
|
|
const returnToValue = new FormData(maintenanceForm).get('returnTo');
|
|
nextUrl.searchParams.set(
|
|
'returnTo',
|
|
String(returnToValue || currentUrl.searchParams.get('returnTo') || '/'),
|
|
);
|
|
nextUrl.searchParams.set('error', 'unavailable');
|
|
window.location.assign(nextUrl.toString());
|
|
} finally {
|
|
if (submitButton instanceof HTMLButtonElement) {
|
|
submitButton.disabled = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|