First Push
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>控制面板 - Cal Display</title>
|
||||
<link rel="stylesheet" href="/static/css/control.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<header>
|
||||
<h1>Cal Display 控制面板 <span id="current-user-badge" style="font-size:13px;font-weight:400;color:var(--text-dim);margin-left:8px;"></span></h1>
|
||||
<nav id="tabs">
|
||||
<button class="tab active" data-tab="calendar">日历</button>
|
||||
<button class="tab" data-tab="weather">天气</button>
|
||||
<button class="tab" data-tab="alarms">闹钟</button>
|
||||
<button class="tab" data-tab="pomodoro">番茄钟</button>
|
||||
<button class="tab" data-tab="users" id="users-tab" style="display:none;">用户管理</button>
|
||||
<button class="tab" data-tab="settings">设置</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Calendar Tab -->
|
||||
<section id="tab-calendar" class="tab-content active">
|
||||
<!-- Groups -->
|
||||
<div class="section">
|
||||
<h3>日历分组</h3>
|
||||
<div id="group-list"></div>
|
||||
<form id="group-form" class="inline-form">
|
||||
<input type="text" id="group-name" placeholder="分组名称" required>
|
||||
<input type="color" id="group-color" value="#4A90D9">
|
||||
<button type="submit">添加分组</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Upload ICS -->
|
||||
<div class="section">
|
||||
<h3>上传 ICS 文件</h3>
|
||||
<form id="upload-form" class="inline-form">
|
||||
<select id="upload-group"></select>
|
||||
<input type="text" id="upload-name" placeholder="来源名称" required>
|
||||
<input type="file" id="upload-file" accept=".ics" required>
|
||||
<button type="submit">上传</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Subscribe ICS -->
|
||||
<div class="section">
|
||||
<h3>订阅 ICS 链接</h3>
|
||||
<form id="subscribe-form" class="inline-form">
|
||||
<select id="subscribe-group"></select>
|
||||
<input type="text" id="subscribe-name" placeholder="来源名称" required>
|
||||
<input type="url" id="subscribe-url" placeholder="https://example.com/calendar.ics" required>
|
||||
<button type="submit">订阅</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Reminders -->
|
||||
<div class="section">
|
||||
<h3>提醒设置</h3>
|
||||
<form id="reminder-form" class="inline-form">
|
||||
<select id="reminder-group"></select>
|
||||
<input type="number" id="reminder-minutes" value="15" min="0" max="1440" style="width:70px">
|
||||
<label>分钟前</label>
|
||||
<label><input type="checkbox" id="reminder-enabled" checked> 启用</label>
|
||||
<button type="submit">保存</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Manual Add Event -->
|
||||
<div class="section">
|
||||
<h3>手动添加日程</h3>
|
||||
<form id="event-form" class="inline-form">
|
||||
<select id="event-group"></select>
|
||||
<input type="text" id="event-summary" placeholder="日程标题" required>
|
||||
<input type="datetime-local" id="event-dtstart" required>
|
||||
<input type="datetime-local" id="event-dtend">
|
||||
<input type="text" id="event-location" placeholder="地点 (可选)">
|
||||
<button type="submit">添加</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Group Events -->
|
||||
<div class="section">
|
||||
<h3>分组日程管理</h3>
|
||||
<div class="inline-form" style="margin-bottom:12px;">
|
||||
<select id="browse-group"></select>
|
||||
<button type="button" id="browse-events-btn">查看日程</button>
|
||||
</div>
|
||||
<div id="group-events-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Weather Tab -->
|
||||
<section id="tab-weather" class="tab-content">
|
||||
<!-- JWT Key Management -->
|
||||
<div class="section">
|
||||
<h3>JWT 密钥管理(和风天气)</h3>
|
||||
<p style="font-size:13px;color:var(--text-dim);margin-bottom:12px;">
|
||||
生成本地 Ed25519 密钥对,将公钥复制到
|
||||
<a href="https://console.qweather.com" target="_blank" style="color:var(--accent);">和风天气控制台</a>
|
||||
→ 凭据管理 → 添加凭据。
|
||||
</p>
|
||||
<div class="form-row">
|
||||
<label>公钥(复制到和风控制台)</label>
|
||||
<textarea id="weather-public-key" rows="4" readonly
|
||||
style="width:100%;font-family:monospace;font-size:12px;resize:none;"></textarea>
|
||||
</div>
|
||||
<button type="button" id="generate-keys-btn">生成密钥对</button>
|
||||
<span id="key-gen-status" style="margin-left:10px;font-size:13px;color:var(--text-dim);"></span>
|
||||
</div>
|
||||
|
||||
<!-- City Search -->
|
||||
<div class="section">
|
||||
<h3>城市搜索</h3>
|
||||
<div class="inline-form">
|
||||
<input type="text" id="weather-city-search" placeholder="输入城市名搜索…" style="min-width:200px;">
|
||||
<button type="button" id="city-search-btn">搜索</button>
|
||||
</div>
|
||||
<div id="city-results" style="margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- QWeather Config -->
|
||||
<div class="section">
|
||||
<h3>和风天气配置</h3>
|
||||
<form id="weather-form">
|
||||
<!-- JWT fields(推荐;未填时自动回退到 API Key) -->
|
||||
<div id="jwt-fields">
|
||||
<div class="form-row">
|
||||
<label>Credential ID (kid)</label>
|
||||
<input type="text" id="weather-kid" placeholder="从和风控制台获取">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Project ID (sub)</label>
|
||||
<input type="text" id="weather-sub" placeholder="从和风控制台获取">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>专属 API Host(必填!)</label>
|
||||
<input type="text" id="weather-host" placeholder="如 abc1234.def.qweatherapi.com">
|
||||
<div style="font-size:12px;color:var(--accent-red);margin-top:4px;">
|
||||
⚠️ 共享域名已弃用!登录 <a href="https://console.qweather.com" target="_blank" style="color:var(--accent);">和风控制台</a>
|
||||
→ 产品管理 → 你的项目 → API Host,复制完整域名填入此处
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Key(可选):和风旧版 API Key,JWT 配置完整时自动优先 JWT -->
|
||||
<div id="legacy-key-field">
|
||||
<div class="form-row">
|
||||
<label>API Key(可选)</label>
|
||||
<input type="text" id="weather-apikey" placeholder="输入和风 API Key(未配置 JWT 时使用)">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Common fields -->
|
||||
<div class="form-row">
|
||||
<label>城市名称</label>
|
||||
<input type="text" id="weather-city" placeholder="Beijing">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>纬度</label>
|
||||
<input type="number" id="weather-lat" step="0.0001" placeholder="39.9042">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>经度</label>
|
||||
<input type="number" id="weather-lon" step="0.0001" placeholder="116.4074">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>单位</label>
|
||||
<select id="weather-units">
|
||||
<option value="metric">摄氏度 (metric)</option>
|
||||
<option value="imperial">华氏度 (imperial)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">保存配置</button>
|
||||
<button type="button" id="weather-refresh" class="btn-secondary">立即刷新天气</button>
|
||||
<span id="weather-save-status" style="margin-left:10px;font-size:13px;color:var(--text-dim);"></span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Current weather preview -->
|
||||
<div class="section">
|
||||
<h3>当前天气预览</h3>
|
||||
<div id="weather-preview" style="font-size:14px;color:var(--text-dim);">加载中...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Alarms Tab -->
|
||||
<section id="tab-alarms" class="tab-content">
|
||||
<!-- Alarms -->
|
||||
<div class="section">
|
||||
<h3>闹钟列表</h3>
|
||||
<div id="alarm-list"></div>
|
||||
<form id="alarm-form" class="inline-form">
|
||||
<input type="text" id="alarm-label" placeholder="标签">
|
||||
<input type="time" id="alarm-time" required>
|
||||
<select id="alarm-sound">
|
||||
<option value="default">默认铃声</option>
|
||||
</select>
|
||||
<label><input type="checkbox" id="alarm-once"> 仅一次</label>
|
||||
<div class="day-picker" id="alarm-days-picker">
|
||||
<label><input type="checkbox" value="1" checked>一</label>
|
||||
<label><input type="checkbox" value="2" checked>二</label>
|
||||
<label><input type="checkbox" value="3" checked>三</label>
|
||||
<label><input type="checkbox" value="4" checked>四</label>
|
||||
<label><input type="checkbox" value="5" checked>五</label>
|
||||
<label><input type="checkbox" value="6" checked>六</label>
|
||||
<label><input type="checkbox" value="7" checked>日</label>
|
||||
</div>
|
||||
<button type="submit">添加闹钟</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Ringtone Upload -->
|
||||
<div class="section">
|
||||
<h3>自定义铃声</h3>
|
||||
<form id="ringtone-form" class="inline-form">
|
||||
<input type="file" id="ringtone-file" accept=".mp3,.wav,.ogg" required>
|
||||
<button type="submit">上传铃声</button>
|
||||
</form>
|
||||
<div id="ringtone-list" style="margin-top:8px;font-size:13px;color:var(--text-dim);"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pomodoro Tab -->
|
||||
<section id="tab-pomodoro" class="tab-content">
|
||||
<div class="section">
|
||||
<h3>番茄钟设置</h3>
|
||||
<div id="pomodoro-status" style="margin-bottom:12px;font-size:14px;color:var(--text-dim);"></div>
|
||||
<form id="pomodoro-form" class="inline-form">
|
||||
<label>工作 <input type="number" id="pomodoro-work" value="25" min="1" max="120" style="width:70px"> 分钟</label>
|
||||
<label>休息 <input type="number" id="pomodoro-break" value="5" min="1" max="60" style="width:70px"> 分钟</label>
|
||||
<label>长休息 <input type="number" id="pomodoro-long-break" value="15" min="1" max="120" style="width:70px"> 分钟</label>
|
||||
<label>每 <input type="number" id="pomodoro-cycles-before-long" value="4" min="1" max="20" style="width:60px"> 组后长休息</label>
|
||||
<label><input type="text" id="pomodoro-work-label" placeholder="工作中" style="width:80px"> 工作标签</label>
|
||||
<label><input type="text" id="pomodoro-break-label" placeholder="休息中" style="width:80px"> 休息标签</label>
|
||||
<label><input type="checkbox" id="pomodoro-display-controls" checked> 允许显示端控制</label>
|
||||
<button type="submit">保存设置</button>
|
||||
</form>
|
||||
<div style="margin-top:12px;display:flex;gap:8px;">
|
||||
<button type="button" id="pomodoro-start-btn">开始</button>
|
||||
<button type="button" id="pomodoro-pause-btn" class="btn-secondary">暂停</button>
|
||||
<button type="button" id="pomodoro-skip-btn" class="btn-secondary">跳过</button>
|
||||
<button type="button" id="pomodoro-stop-btn" class="btn-danger">停止</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Users Tab (admin only) -->
|
||||
<section id="tab-users" class="tab-content">
|
||||
<div class="section">
|
||||
<h3>用户管理</h3>
|
||||
<p style="font-size:13px;color:var(--text-dim);margin-bottom:12px;">
|
||||
仅管理员可用。注册/编辑/删除用户均为高危操作,每次操作前需二次验证管理员身份。
|
||||
新注册用户的全部数据相互隔离,且不拥有用户管理权限。
|
||||
</p>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap;">
|
||||
<button type="button" id="user-add-admin" class="btn-primary">👨💼 管理员代注册</button>
|
||||
<button type="button" id="user-add-temp" class="btn-secondary">🕐 创建临时账户(用户填写信息模式)</button>
|
||||
<button type="button" id="user-refresh-btn" class="btn-secondary">刷新列表</button>
|
||||
</div>
|
||||
<div id="pending-requests" style="margin-bottom:16px;"></div>
|
||||
<div id="user-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<section id="tab-settings" class="tab-content">
|
||||
<div class="section">
|
||||
<h3>主题模式</h3>
|
||||
<p style="font-size:13px;color:var(--text-dim);margin-bottom:12px;">
|
||||
跟随账户独立设置,显示端实时同步生效。
|
||||
<strong>自动</strong> 模式按你天气设置中的城市经纬度计算日出日落,白天浅色、夜晚深色。
|
||||
</p>
|
||||
<div class="inline-form">
|
||||
<select id="theme-mode">
|
||||
<option value="dark">深色模式</option>
|
||||
<option value="light">浅色模式</option>
|
||||
<option value="auto">自动(日出日落)</option>
|
||||
</select>
|
||||
<button type="button" id="theme-save-btn">保存主题</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>显示端模式</h3> <form id="mode-form" class="inline-form">
|
||||
<select id="display-mode">
|
||||
<option value="default">默认页面</option>
|
||||
<option value="custom">自定义 HTML</option>
|
||||
</select>
|
||||
<button type="submit">切换</button>
|
||||
</form>
|
||||
<div id="html-editor-wrap" style="margin-top:12px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
||||
<label style="font-size:13px;color:var(--text-dim);">当前编辑: <strong id="html-editor-label">HTML 0</strong></label>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<button type="button" class="small-btn html-tab-btn active" data-idx="0">HTML 0</button>
|
||||
<button type="button" class="small-btn html-tab-btn" data-idx="1">HTML 1</button>
|
||||
<button type="button" class="small-btn html-tab-btn" data-idx="2">HTML 2</button>
|
||||
<button type="button" class="small-btn html-tab-btn" data-idx="3">HTML 3</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="custom-html-editor" rows="8" style="width:100%;padding:10px;border:1px solid var(--border);border-radius:6px;font-family:monospace;font-size:13px;resize:vertical;" placeholder="在此输入 HTML..."></textarea>
|
||||
<div style="margin-top:8px;display:flex;gap:8px;">
|
||||
<button type="button" id="save-html-btn">保存当前模板</button>
|
||||
<button type="button" id="apply-html-btn" class="btn-secondary">使用当前 HTML</button>
|
||||
<button type="button" id="refresh-display-btn" class="btn-secondary">强制刷新显示端</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:8px;display:flex;gap:8px;">
|
||||
<button type="button" id="kick-all-btn" class="btn-danger">踢掉所有显示端</button>
|
||||
</div>
|
||||
<div id="mode-msg" style="margin-top:8px;font-size:13px;color:var(--text-dim);"></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>时区设置</h3>
|
||||
<div style="font-size:13px;color:var(--text-dim);margin-bottom:10px;">
|
||||
所有本地时间将根据此设置显示。使用 <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" target="_blank" style="color:var(--accent);">IANA 时区名称</a> 或 UTC+X 格式。
|
||||
</div>
|
||||
<form id="timezone-form" class="inline-form">
|
||||
<input type="text" id="timezone-input" value="Asia/Shanghai" style="min-width:220px;" placeholder="Asia/Shanghai">
|
||||
<button type="submit">保存时区</button>
|
||||
<span id="timezone-msg" style="margin-left:10px;font-size:13px;color:var(--text-dim);"></span>
|
||||
</form>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>手动推送提醒</h3>
|
||||
<form id="notify-form" class="inline-form">
|
||||
<input type="text" id="notify-title" placeholder="标题" value="提醒" required>
|
||||
<input type="text" id="notify-message" placeholder="内容" required>
|
||||
<label><input type="checkbox" id="notify-sound" checked> 播放声音</label>
|
||||
<button type="submit">推送</button>
|
||||
</form>
|
||||
<div id="notify-msg" style="margin-top:8px;font-size:13px;color:var(--text-dim);"></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>双因素认证 (MFA)</h3>
|
||||
<div id="mfa-status" style="font-size:13px;color:var(--text-dim);margin-bottom:12px;">加载中...</div>
|
||||
|
||||
<!-- Passkey -->
|
||||
<div style="margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:8px;">
|
||||
<h4 style="margin-bottom:8px;font-size:14px;">🔑 Passkey (设备密钥)</h4>
|
||||
<p style="font-size:13px;color:var(--text-dim);margin-bottom:8px;">
|
||||
使用指纹、面容或安全密钥快速登录。
|
||||
</p>
|
||||
<button type="button" id="mfa-register-passkey" class="btn-secondary">注册 Passkey</button>
|
||||
<button type="button" id="mfa-remove-passkey" class="btn-danger" style="display:none;">移除 Passkey</button>
|
||||
<div id="mfa-passkey-status" style="font-size:13px;color:var(--text-dim);margin-top:6px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- TOTP -->
|
||||
<div style="margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:8px;">
|
||||
<h4 style="margin-bottom:8px;font-size:14px;">📱 TOTP (身份验证器)</h4>
|
||||
<p style="font-size:13px;color:var(--text-dim);margin-bottom:8px;">
|
||||
使用 Google Authenticator、Microsoft Authenticator 等应用。
|
||||
</p>
|
||||
<button type="button" id="mfa-setup-totp" class="btn-secondary">设置 TOTP</button>
|
||||
<button type="button" id="mfa-remove-totp" class="btn-danger" style="display:none;">停用 TOTP</button>
|
||||
<div id="mfa-totp-area" style="display:none;margin-top:12px;padding:12px;background:#fafafa;border-radius:8px;">
|
||||
<div id="mfa-totp-qr" style="text-align:center;"></div>
|
||||
<div id="mfa-totp-secret" style="font-family:monospace;font-size:12px;text-align:center;margin:8px 0;"></div>
|
||||
<div class="inline-form">
|
||||
<input type="text" id="mfa-totp-code" placeholder="6 位验证码" maxlength="6" inputmode="numeric" style="width:140px;">
|
||||
<button type="button" id="mfa-totp-verify-btn">验证</button>
|
||||
</div>
|
||||
<div id="mfa-totp-msg" style="font-size:13px;margin-top:6px;"></div>
|
||||
</div>
|
||||
<div id="mfa-totp-status" style="font-size:13px;color:var(--text-dim);margin-top:6px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Recovery codes -->
|
||||
<div id="mfa-recovery-section" style="display:none;margin-bottom:16px;padding:14px;background:#fff8e1;border:1px solid #ffe082;border-radius:8px;">
|
||||
<h4 style="font-size:14px;color:#e65100;margin-bottom:8px;">🔐 恢复码</h4>
|
||||
<p style="font-size:13px;color:#e65100;margin-bottom:8px;">每个恢复码仅可使用一次。请妥善保管。</p>
|
||||
<div id="mfa-recovery-codes" style="font-family:monospace;font-size:13px;line-height:2;"></div>
|
||||
<div style="margin-top:8px;display:flex;gap:8px;">
|
||||
<button type="button" id="mfa-copy-recovery" class="small-btn">复制</button>
|
||||
<button type="button" id="mfa-regenerate-recovery" class="small-btn">重新生成</button>
|
||||
</div>
|
||||
<div id="mfa-recovery-msg" style="font-size:13px;color:var(--accent-red);margin-top:6px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>退出登录</h3>
|
||||
<button type="button" id="logout-btn" class="btn-danger">退出登录</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/theme.js"></script>
|
||||
<script src="/static/js/passkey_bridge.js"></script>
|
||||
<script src="/static/js/control.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user