feat(theme): 砂狼白子主题 —— 真实立绘/头像 + 自绘装饰
素材(第三方,仅个人自用): - 立绘/头像来自 SchaleDB(schaledb.com),学生 Id 10010 = 砂狼白子 - 版权归 NEXON Games / Yostar(蔚蓝档案),来源与公开部署提示见 README「主题」章节 自绘装饰(SVG/CSS,按公开设定还原): - 天蓝围巾飘带(顶栏底边)、公路车(左下角 + 加载车轮) - 光环水印:倾斜椭圆环 + 四方位带圆头短杆(对齐官方造型,此前误画为正圆+三角) - 倒十字发饰分组标识、迷你光环状态点 其他:主题切换(settings.theme / ?theme= / 顶栏按钮)、立绘位 settings.artwork 修复:dataFilter=uptimerobot 时平均延迟恒为「–」(改为纯外部来源时以其为唯一口径)
@@ -25,6 +25,7 @@
|
||||
- *接入*:把 UptimeRobot 已有的监控结果当数据源拉进来,与自建探测并列展示(异地视角),状态、延迟、90 天可用率、心率图全部映射自 UR 官方数据;UR 读取失败时保留上次数据,绝不把服务误判为离线
|
||||
- *输出*:提供 `/api/v2/getMonitors`,字段与状态码编码对齐 UptimeRobot v2,既有脚本/看板可直接消费
|
||||
- **滥用防护与信息泄露控制**:刷新接口仅内网/回环可触发、按来源冷却(429 + Retry-After)、可整体关闭;支持 `hideTargets` / `redactDetails` 与 `local.monitors.json` 私有配置分层,公开配置里可以只有服务名 —— 详见 [docs/SECURITY.md](docs/SECURITY.md)
|
||||
- **主题:砂狼白子**(默认启用,可一键切回默认主题):她的官方立绘与头像、青色光环(含"外圈先亮、内圈后亮"的呼吸动画)、天蓝围巾飘带顶栏、倒十字发饰、公路车轮加载态。素材来源与版权说明见 [主题](#主题砂狼白子)
|
||||
- **三种数据源自动解析**:边缘 API(实时)→ 静态快照 → 浏览器直连,也可用 `?source=` 强制指定
|
||||
|
||||
## 快速开始(本地)
|
||||
@@ -60,9 +61,15 @@ shiroko-monitor/
|
||||
│ ├── monitors.json # ★ 监控目标配置(改这里)
|
||||
│ ├── status.json # 探针输出的状态快照(面板读取)
|
||||
│ └── assets/
|
||||
│ ├── style.css # 浅色主题 + 暗色自适应
|
||||
│ ├── app.js # 数据源解析 / 渲染 / 浏览器直连探测
|
||||
│ └── favicon.svg
|
||||
│ ├── style.css # 基础样式(浅色 + 暗色自适应)
|
||||
│ ├── theme-shiroko.css # 砂狼白子主题(仅在 body[data-theme=shiroko] 下生效)
|
||||
│ ├── shiroko-portrait.webp # 立绘(SchaleDB 素材,见「主题」章节的版权说明)
|
||||
│ ├── shiroko-icon.webp # 方形头像(同上)
|
||||
│ ├── favicon-shiroko.png # 白子主题图标(由头像生成)
|
||||
│ ├── deco-scarf.svg # 自绘:天蓝围巾飘带
|
||||
│ ├── deco-bicycle.svg # 自绘:公路车
|
||||
│ ├── app.js # 数据源解析 / 渲染 / 浏览器直连探测 / 主题切换
|
||||
│ └── favicon.svg # 默认主题图标
|
||||
├── prober/node/ # Node 探针(本地 / CI 用)
|
||||
│ ├── index.js # 巡检循环 + 静态服务器 + /api/* + /api/v2/getMonitors + --once/--demo/--out/--config
|
||||
│ └── lib/
|
||||
@@ -98,6 +105,8 @@ shiroko-monitor/
|
||||
"title": "Shiroko Monitor", // 面板标题
|
||||
"description": "轻量级服务监控面板",
|
||||
"view": "table", // 默认视图:table | cards
|
||||
"theme": "shiroko", // shiroko(砂狼白子)| default
|
||||
"artwork": "", // 可选:立绘图片路径(相对 public/),如 assets/character.png
|
||||
"dataFilter": "both", // both | config | uptimerobot(详见「面板形态与数据源」)
|
||||
"refreshInterval": 30, // 面板检查数据的最大间隔(秒,最小 5);实际对齐服务端更新节奏
|
||||
"dataSource": "auto", // auto | api | static | browser
|
||||
@@ -284,6 +293,45 @@ UptimeRobot 的探测来自其多个节点,适合作为**异地视角**与自
|
||||
|
||||
效果:无论你什么时候打开页面,倒计时都显示「距服务端下一次更新还有多久」——刚更新完打开是 300s,过了 4 分钟打开就只剩 60s。静态快照与浏览器直连模式没有可对齐的服务端节奏,仍按 `refreshInterval` 轮询。
|
||||
|
||||
## 主题(砂狼白子)
|
||||
|
||||
面板名称叫 shiroko-monitor,主题自然取了《蔚蓝档案》的**砂狼白子**:她的光环、围巾、狼耳与配色都被做进了界面。
|
||||
|
||||
### 主题里的元素
|
||||
|
||||
| 位置 | 元素 | 取自设定 |
|
||||
| --- | --- | --- |
|
||||
| 左上 logo / 浏览器图标 | **她的头像**(真实素材)+ 青色光环环(呼吸式外扩动画) | 头像为白子的官方方形头像素材 |
|
||||
| 右下角 | **她的官方立绘**(透明底,约 56vh,顶部渐隐;宽屏下表格会自动让位,不遮挡数据) | 白子原版(阿拜多斯校服)立绘 |
|
||||
| 顶栏底边 | **天蓝色围巾飘带**:波浪形状 + 5 处褶皱纹路(自绘) | 天蓝色围巾是星野送给她的信物 |
|
||||
| 背景 | 天蓝渐变 + 斜向细排线 + 右下**光环水印**(自绘:倾斜椭圆环 + 四方位带圆头的短杆) | 白子的光环是倾斜的椭圆环,四个方位各有一条带圆头的短杆,整体形似全息瞄准镜准星 |
|
||||
| 状态点 | **迷你光环**(外圈 + 中心点,在线时缓慢脉冲外扩) | 光环是基沃托斯学生的标志 |
|
||||
| 分组标题 | **青色倒十字发饰**(旋转 45°,自绘) | 她左侧刘海上别的青色十字发夹 |
|
||||
| 刷新按钮加载态 | **公路车轮**(轮圈 + 辐条 + 花鼓)旋转 | 她最爱的公路车(Bianchi ARIA 原型) |
|
||||
| 左下角 | 公路车侧影(自绘) | 同上 |
|
||||
| 配色 | 天蓝 / 青 + 藏蓝 + 灰白;「正常」色改为青绿(`#12b0a2`) | 阿拜多斯藏蓝校服 + 天蓝围巾 + 灰白发色 |
|
||||
|
||||
### 素材来源与版权
|
||||
|
||||
| 类别 | 内容 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| **第三方素材**(真实立绘/头像) | `public/assets/shiroko-portrait.webp`(540×1024 原图缩至 460 宽)、`shiroko-icon.webp`(120×120)、`favicon-shiroko.png`(64×64) | [SchaleDB](https://schaledb.com)(社区数据库):`images/student/portrait/10010.webp`、`images/student/icon/10010.webp`;学生 Id `10010` = 砂狼白子(校服原版) |
|
||||
| 设定资料(光环形状、发饰、围巾、配色、车型) | 设计依据 | [萌娘百科 · 砂狼白子](https://mzh.moegirl.org.cn/%E7%A0%82%E7%8B%BC%E7%99%BD%E5%AD%90)、[Blue Archive Wiki](https://bluearchive.fandom.com/zh/wiki/%E7%A0%82%E7%8B%BC%E7%99%BD%E5%AD%90)、[百度百科](https://baike.baidu.com/item/%E7%A0%82%E7%8B%BC%E7%99%BD%E5%AD%90/62839164) |
|
||||
| 自绘装饰(SVG/CSS) | 围巾飘带 `deco-scarf.svg`、公路车 `deco-bicycle.svg`、光环水印、迷你光环状态点、倒十字图标、车轮加载图标 | 本仓库原创,按上述公开设定还原 |
|
||||
|
||||
> ⚠️ **版权提示**:立绘与头像素材版权归 **NEXON Games / Yostar(《蔚蓝档案》)** 所有,此处仅作个人自用面板的风格美化。如果要把本仓库或 GitHub Pages **公开发布**,建议删除 `public/assets/shiroko-portrait.webp`、`shiroko-icon.webp`、`favicon-shiroko.png` 并在配置里清空 `artwork`,或替换为你自己的图片(其余装饰均为自绘,可保留)。
|
||||
|
||||
### 切换与配置
|
||||
|
||||
```jsonc
|
||||
"theme": "shiroko", // shiroko | default(默认主题);也可用地址栏 ?theme=default 临时覆盖
|
||||
"artwork": "assets/shiroko-portrait.webp" // 立绘图片路径(相对 public/),留空则不显示
|
||||
```
|
||||
|
||||
优先级:`?theme=` → 浏览器上次选择(localStorage)→ `settings.theme`。顶栏右侧的光环图标按钮可一键切换。切回默认主题后,立绘、光环、斜纹、围巾飘带、公路车全部消失,回到最初的简洁样式。
|
||||
|
||||
**换成你自己的图**:把图片放进 `public/assets/`,再把 `artwork` 指向它即可(透明底 PNG/WebP 效果最好)。文件不存在时静默不显示,不会出现破图。
|
||||
|
||||
## 90 天可用率
|
||||
|
||||
探针每完成一次探测,就把它计入「当日分桶」:`monitors.<id>.daily["YYYY-MM-DD"] = { total, up }`,只保留最近 90 天(超出自动淘汰)。面板据此渲染 90 格色条与百分比。
|
||||
@@ -388,6 +436,13 @@ UptimeRobot 的探测来自其多个节点,适合作为**异地视角**与自
|
||||
- [x] 页脚改为展示「数据更新于 … + 下次检查 Ns」,tooltip 内含服务端更新节奏与快照生成时间
|
||||
- [x] `scripts/verify.cjs` 适配纯外部数据源部署(自动跳过不适用的探测类断言),并新增「倒计时对齐服务端」断言
|
||||
|
||||
### v0.7.0 已实现
|
||||
|
||||
- [x] **砂狼白子主题**(默认启用):官方立绘(右下角,宽屏自动为它让位)+ 头像 logo/favicon(光环环呼吸动画)+ 天蓝围巾飘带顶栏 + 倒十字发饰分组标识 + 公路车轮加载态 + 按官方造型还原的倾斜椭圆光环水印;配套青绿主色调
|
||||
- [x] 主题切换:`settings.theme` / `?theme=` / 顶栏光环按钮(localStorage 记忆),默认主题完整保留
|
||||
- [x] `settings.artwork` 立绘位,默认指向内置立绘;未配置或文件缺失时静默不显示
|
||||
- [x] 修复:`dataFilter=uptimerobot`(只展示外部数据源)时「平均延迟」恒为「–」——原规则一律排除 UR 延迟,改为「混合来源时排除、纯外部来源时以其为唯一口径」,服务端与面板同步调整
|
||||
|
||||
### 路线图(后续迭代)
|
||||
|
||||
- [ ] 通知渠道:Webhook / Server 酱 / Telegram / Bark / 邮件
|
||||
@@ -423,13 +478,15 @@ UPTIMEROBOT_API_KEY=ur... node scripts/verify-uptimerobot.cjs # UR 映
|
||||
|
||||
## 界面截图
|
||||
|
||||
| 表格(默认视图) | 卡片视图 |
|
||||
| 砂狼白子主题(默认,表格视图) | 默认主题 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
UptimeRobot 数据源(`?filter=uptimerobot`,截图中含真实监控名与域名,公开发布前可自行删除):`docs/screenshot-uptimerobot.png`
|
||||
| 卡片视图(白子主题) | 移动端 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
演示数据(90 天满档)与移动端:`docs/screenshot-demo.png`、`docs/screenshot-mobile.png`
|
||||
UptimeRobot 数据源(`?filter=uptimerobot`,截图中含真实监控名与域名,公开发布前可自行删除):`docs/screenshot-uptimerobot.png`;演示数据 90 天满档:`docs/screenshot-demo.png`
|
||||
|
||||
## 致谢与定位
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 321 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 295 KiB |
@@ -11,7 +11,7 @@
|
||||
|
||||
import { connect } from 'cloudflare:sockets';
|
||||
|
||||
const VERSION = '0.6.0';
|
||||
const VERSION = '0.7.0';
|
||||
const HISTORY_LIMIT = 48;
|
||||
const UPTIME_DAYS = 90;
|
||||
const USER_AGENT = 'shiroko-monitor/0.3 (lightweight uptime panel)';
|
||||
|
||||
@@ -35,7 +35,7 @@ const CONCURRENCY = 8;
|
||||
const WRITE_DEBOUNCE_MS = 300;
|
||||
const DEFAULT_INTERVAL_SEC = 60;
|
||||
const PORT = Number(process.env.PORT || 8787);
|
||||
const VERSION = '0.6.0';
|
||||
const VERSION = '0.7.0';
|
||||
const UR_DEFAULT_POLL_SEC = 300;
|
||||
const UR_MIN_REFRESH_GAP_MS = 60000;
|
||||
|
||||
@@ -348,6 +348,9 @@ function snapshot(runtime, demo = false) {
|
||||
const hideUrls = config.settings?.hideTargets === true;
|
||||
const includeConfig = filter !== 'uptimerobot';
|
||||
const includeExternal = filter !== 'config';
|
||||
// 混合来源时平均延迟只统计自建探测(UR 的延迟来自其探测节点,与本机视角不可比);
|
||||
// 若展示集合只有外部数据源,则 UR 延迟就是唯一口径,照常统计
|
||||
const latencyEligible = (isUr) => !isUr || filter === 'uptimerobot';
|
||||
const monitors = {};
|
||||
let up = 0;
|
||||
let down = 0;
|
||||
@@ -376,8 +379,7 @@ function snapshot(runtime, demo = false) {
|
||||
}
|
||||
if (r.status === 'up') {
|
||||
up++;
|
||||
// 平均延迟只统计自建探针:UR 的延迟来自其探测节点,与本机视角不可比
|
||||
if (!isUr && r.latency != null) {
|
||||
if (latencyEligible(isUr) && r.latency != null) {
|
||||
latencySum += r.latency;
|
||||
latencyCount++;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const VERSION = '0.6.0';
|
||||
const VERSION = '0.7.0';
|
||||
const HB_LIMIT = 48;
|
||||
const UPTIME_DAYS = 90;
|
||||
const BROWSER_CONCURRENCY = 6;
|
||||
@@ -15,6 +15,7 @@ const state = {
|
||||
source: null,
|
||||
filter: 'all',
|
||||
view: 'cards',
|
||||
theme: 'default',
|
||||
refreshTimer: null,
|
||||
nextRefreshAt: 0,
|
||||
loading: false,
|
||||
@@ -537,6 +538,8 @@ function localStats() {
|
||||
let latCount = 0;
|
||||
let uptimeUp = 0;
|
||||
let uptimeTotal = 0;
|
||||
// 与探针口径一致:混合来源时平均延迟只统计自建探测;若展示集合只有外部数据源,则以其延迟为唯一口径
|
||||
const hasProbe = monitors.some((m) => !isExternal(m));
|
||||
for (const m of monitors) {
|
||||
const st = state.status?.monitors?.[m.id];
|
||||
if (!st || st.status === 'pending') {
|
||||
@@ -545,8 +548,7 @@ function localStats() {
|
||||
}
|
||||
if (st.status === 'up') up++;
|
||||
else down++;
|
||||
// 与探针口径一致:平均延迟只统计自建探测目标
|
||||
if (!isExternal(m) && st.latency != null) {
|
||||
if (st.latency != null && (!isExternal(m) || !hasProbe)) {
|
||||
latSum += st.latency;
|
||||
latCount++;
|
||||
}
|
||||
@@ -666,7 +668,10 @@ function setLoading(on, text = '刷新中…') {
|
||||
state.loading = on;
|
||||
const btn = $('refresh-btn');
|
||||
btn.disabled = on;
|
||||
btn.textContent = on ? text : '刷新';
|
||||
// 车轮图标(砂狼白子骑行意象,主题里会转起来)
|
||||
btn.innerHTML = on
|
||||
? `<svg class="wheel" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.35"/><g stroke="currentColor" stroke-width="1.3"><line x1="12" y1="2" x2="12" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/><line x1="19.1" y1="4.9" x2="4.9" y2="19.1"/></g><circle cx="12" cy="12" r="2.2" fill="currentColor"/></svg><span>${escapeHtml(text)}</span>`
|
||||
: '刷新';
|
||||
}
|
||||
|
||||
async function refresh({ force = false } = {}) {
|
||||
@@ -748,9 +753,55 @@ function startAutoRefresh() {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* ---------- 主题 ---------- */
|
||||
|
||||
const THEMES = { default: '默认', shiroko: '砂狼白子' };
|
||||
|
||||
function resolveTheme() {
|
||||
const q = new URLSearchParams(location.search).get('theme');
|
||||
let saved = null;
|
||||
try {
|
||||
saved = localStorage.getItem('shiroko.theme');
|
||||
} catch { /* 隐私模式 */ }
|
||||
return [q, saved, state.config?.settings?.theme].find((t) => t && THEMES[t]) ?? 'default';
|
||||
}
|
||||
|
||||
// 角色立绘位:主题为白子且配置了 artwork 时才插入;文件不存在则静默移除(不留破图)
|
||||
function applyArtwork() {
|
||||
document.querySelector('.artwork')?.remove();
|
||||
const src = state.config?.settings?.artwork;
|
||||
if (state.theme !== 'shiroko' || !src) return;
|
||||
const img = document.createElement('img');
|
||||
img.className = 'artwork';
|
||||
img.alt = '';
|
||||
img.src = src;
|
||||
img.addEventListener('error', () => img.remove());
|
||||
document.body.appendChild(img);
|
||||
}
|
||||
|
||||
function applyTheme(theme, { persist = true } = {}) {
|
||||
state.theme = THEMES[theme] ? theme : 'default';
|
||||
if (state.theme === 'default') delete document.body.dataset.theme;
|
||||
else document.body.dataset.theme = state.theme;
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem('shiroko.theme', state.theme);
|
||||
} catch { /* 忽略 */ }
|
||||
}
|
||||
const icon = document.querySelector('link[rel="icon"]');
|
||||
if (icon) icon.setAttribute('href', state.theme === 'shiroko' ? 'assets/favicon-shiroko.png' : 'assets/favicon.svg');
|
||||
applyArtwork();
|
||||
const btn = $('theme-btn');
|
||||
if (btn) {
|
||||
btn.classList.toggle('active', state.theme === 'shiroko');
|
||||
btn.title = state.theme === 'shiroko' ? '当前主题:砂狼白子(点击切回默认)' : '当前主题:默认(点击切换到砂狼白子主题)';
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
// 只有手动点「刷新」才会触发一次重新检测;自动刷新是只读的
|
||||
$('refresh-btn').addEventListener('click', () => refresh({ force: true }));
|
||||
$('theme-btn')?.addEventListener('click', () => applyTheme(state.theme === 'shiroko' ? 'default' : 'shiroko'));
|
||||
}
|
||||
|
||||
/* ---------- 启动 ---------- */
|
||||
@@ -767,6 +818,7 @@ async function init() {
|
||||
$('site-desc').textContent = state.config.settings?.description || '';
|
||||
const versionEl = document.querySelector('.version');
|
||||
if (versionEl) versionEl.textContent = `shiroko-monitor v${VERSION}`;
|
||||
applyTheme(resolveTheme());
|
||||
await resolveSource();
|
||||
state.view = resolveView();
|
||||
bindEvents();
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 150" fill="none">
|
||||
<title>公路车(简笔画 · 自绘,白子的爱车意象)</title>
|
||||
<g stroke="#2ea8dc" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- 车轮 -->
|
||||
<circle cx="58" cy="96" r="42"/>
|
||||
<circle cx="58" cy="96" r="4" fill="#2ea8dc" stroke="none"/>
|
||||
<circle cx="202" cy="96" r="42"/>
|
||||
<circle cx="202" cy="96" r="4" fill="#2ea8dc" stroke="none"/>
|
||||
</g>
|
||||
<g stroke="#8fd3f2" stroke-width="1.6" stroke-linecap="round">
|
||||
<!-- 辐条 -->
|
||||
<line x1="58" y1="56" x2="58" y2="136"/><line x1="18" y1="96" x2="98" y2="96"/>
|
||||
<line x1="30" y1="68" x2="86" y2="124"/><line x1="86" y1="68" x2="30" y2="124"/>
|
||||
<line x1="202" y1="56" x2="202" y2="136"/><line x1="162" y1="96" x2="242" y2="96"/>
|
||||
<line x1="174" y1="68" x2="230" y2="124"/><line x1="230" y1="68" x2="174" y2="124"/>
|
||||
</g>
|
||||
<g stroke="#1c2f4d" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- 车架 -->
|
||||
<path d="M58 96 L110 96 L142 52 L202 96"/>
|
||||
<path d="M110 96 L142 52"/>
|
||||
<path d="M110 96 L146 100"/>
|
||||
<!-- 前叉 + 车把 -->
|
||||
<path d="M202 96 L176 46"/>
|
||||
<path d="M164 40 L190 46"/>
|
||||
<!-- 座管 + 车座 -->
|
||||
<path d="M142 52 L130 34"/>
|
||||
<path d="M118 32 L142 34"/>
|
||||
<!-- 曲柄 + 脚踏 -->
|
||||
<circle cx="146" cy="100" r="10"/>
|
||||
<path d="M146 100 L162 116"/>
|
||||
</g>
|
||||
<g stroke="#2ea8dc" stroke-width="3" stroke-linecap="round">
|
||||
<path d="M96 100 L104 100" opacity="0.9"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 60" fill="none" preserveAspectRatio="none">
|
||||
<title>天蓝色围巾飘带(自绘)</title>
|
||||
<defs>
|
||||
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#a9e9ff"/>
|
||||
<stop offset="0.45" stop-color="#2ea8dc"/>
|
||||
<stop offset="1" stop-color="#1c2f4d"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- 波浪形的围巾:上边缘平直贴合顶栏,下边缘起波 -->
|
||||
<path d="M0 6 H600 V22 C560 38 520 12 480 26 C440 40 400 14 360 28 C320 42 280 12 240 26 C200 40 160 14 120 28 C80 42 40 16 0 30 Z"
|
||||
fill="url(#sg)"/>
|
||||
<!-- 围巾的褶皱纹路 -->
|
||||
<g stroke="#ffffff" stroke-opacity="0.35" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M60 16 C58 22 58 26 60 30"/>
|
||||
<path d="M180 16 C178 22 178 26 180 30"/>
|
||||
<path d="M300 16 C298 22 298 26 300 30"/>
|
||||
<path d="M420 16 C418 22 418 26 420 30"/>
|
||||
<path d="M540 16 C538 22 538 26 540 30"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 75 KiB |
@@ -110,6 +110,8 @@ body {
|
||||
.stat-down b { color: var(--down); }
|
||||
|
||||
#refresh-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
@@ -123,6 +125,32 @@ body {
|
||||
#refresh-btn:hover { background: var(--bg); }
|
||||
#refresh-btn:disabled { opacity: 0.6; cursor: default; }
|
||||
|
||||
/* 加载态用的车轮图标(砂狼白子骑行意象) */
|
||||
.wheel { width: 14px; height: 14px; margin-right: 6px; animation: spin 0.9s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* 图标按钮(主题切换) */
|
||||
.icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.icon-btn svg { width: 16px; height: 16px; }
|
||||
.icon-btn:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.icon-btn.active { color: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
|
||||
|
||||
/* 光环仅在白子主题下显示 */
|
||||
.logo .halo { display: none; }
|
||||
|
||||
/* ---------- 视图切换 ---------- */
|
||||
.view-switch { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
||||
.view-btn {
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/* ============================================================
|
||||
砂狼白子主题(shiroko)
|
||||
真实素材(头像 / 立绘)来自社区数据库 SchaleDB,版权归 NEXON Games · Yostar;
|
||||
其余装饰为自绘几何图形,均按其公开设定还原:
|
||||
· 光环:倾斜的椭圆环 + 四方位带圆头的短杆(全息瞄准镜准星意象)
|
||||
· 发饰:青色倒十字(旋转 45°)
|
||||
· 围巾:天蓝色飘带(星野的信物)
|
||||
· 座驾:公路车
|
||||
仅在 body[data-theme="shiroko"] 下生效,不影响默认主题。
|
||||
============================================================ */
|
||||
|
||||
body[data-theme="shiroko"] {
|
||||
--bg: #eef7fd;
|
||||
--surface: #ffffff;
|
||||
--border: #d3e6f5;
|
||||
--text: #17253d;
|
||||
--text-2: #5d7b9a;
|
||||
--up: #12b0a2;
|
||||
--up-bg: #e4faf6;
|
||||
--down: #e0524e;
|
||||
--down-bg: #fdecec;
|
||||
--pending: #d99413;
|
||||
--pending-bg: #fff7e6;
|
||||
--accent: #1e9fd4;
|
||||
--uptime-ok: #12b0a2;
|
||||
--uptime-warn: #e5a52c;
|
||||
--uptime-bad: #e0524e;
|
||||
--uptime-none: #dbe8f3;
|
||||
--shadow: 0 1px 2px rgb(23 60 100 / 0.05), 0 10px 26px -20px rgb(23 60 100 / 0.35);
|
||||
--sh-sky: #8fe0ff;
|
||||
--sh-sky-deep: #2ea8dc;
|
||||
--sh-navy: #1c2f4d;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body[data-theme="shiroko"] {
|
||||
--bg: #0a1622;
|
||||
--surface: #101d2c;
|
||||
--border: #1d3247;
|
||||
--text: #e4eef7;
|
||||
--text-2: #8ba6bf;
|
||||
--up: #2fd4c2;
|
||||
--up-bg: #0d2a2a;
|
||||
--down: #f4736c;
|
||||
--down-bg: #2c1518;
|
||||
--pending: #e8b344;
|
||||
--pending-bg: #2a2113;
|
||||
--accent: #55c2ef;
|
||||
--uptime-ok: #2fd4c2;
|
||||
--uptime-none: #1a2b3d;
|
||||
--shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 背景:天蓝渐变 + 淡斜纹 ---------- */
|
||||
body[data-theme="shiroko"] {
|
||||
background-color: var(--bg);
|
||||
background-image:
|
||||
repeating-linear-gradient(135deg, rgb(46 168 220 / 4%) 0 1px, transparent 1px 9px),
|
||||
radial-gradient(120% 60% at 50% -20%, rgb(143 224 255 / 26%), transparent 70%),
|
||||
linear-gradient(180deg, rgb(255 255 255 / 70%), transparent 320px);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* 右下角:按官方造型还原的光环水印(倾斜椭圆环 + 四方位圆头短杆) */
|
||||
body[data-theme="shiroko"]::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
right: -70px;
|
||||
bottom: 40px;
|
||||
width: 460px;
|
||||
height: 260px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.55;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 130'><g stroke='%232ea8dc' stroke-opacity='0.11' fill='none' stroke-linecap='round'><ellipse cx='120' cy='64' rx='86' ry='38' stroke-width='11'/><ellipse cx='120' cy='70' rx='48' ry='21' stroke-width='6'/><g stroke-width='7'><line x1='22' y1='64' x2='6' y2='64'/><line x1='218' y1='64' x2='234' y2='64'/><line x1='120' y1='18' x2='120' y2='2'/><line x1='120' y1='110' x2='120' y2='126'/></g></g><g fill='%232ea8dc' fill-opacity='0.11'><circle cx='6' cy='64' r='4.6'/><circle cx='234' cy='64' r='4.6'/><circle cx='120' cy='2' r='4.6'/><circle cx='120' cy='126' r='4.6'/></g></svg>");
|
||||
}
|
||||
|
||||
/* 左下角:公路车(自绘) */
|
||||
body[data-theme="shiroko"]::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
left: 14px;
|
||||
bottom: 10px;
|
||||
width: 156px;
|
||||
height: 90px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.5;
|
||||
background: url("deco-bicycle.svg") no-repeat center / contain;
|
||||
}
|
||||
|
||||
/* ---------- 顶栏:天蓝到白 + 底部围巾飘带 ---------- */
|
||||
body[data-theme="shiroko"] .topbar {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f6fcff 100%);
|
||||
border-bottom: 0;
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
body[data-theme="shiroko"] .topbar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
background: url("deco-scarf.svg") repeat-x bottom left / 300px 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ---------- 品牌:真实头像 + 光环环 ---------- */
|
||||
body[data-theme="shiroko"] .logo {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
background: #eaf7ff url("shiroko-icon.webp") center / cover no-repeat;
|
||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--sh-sky-deep), 0 0 12px rgb(46 168 220 / 55%);
|
||||
animation: avatar-halo 3.4s ease-out infinite;
|
||||
}
|
||||
@keyframes avatar-halo {
|
||||
0% { box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--sh-sky-deep), 0 0 12px rgb(46 168 220 / 55%); }
|
||||
65% { box-shadow: 0 0 0 2px #fff, 0 0 0 8px rgb(46 168 220 / 0%), 0 0 16px rgb(46 168 220 / 30%); }
|
||||
100% { box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--sh-sky-deep), 0 0 12px rgb(46 168 220 / 55%); }
|
||||
}
|
||||
body[data-theme="shiroko"] .brand-text h1 {
|
||||
color: var(--sh-navy);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* 分组标题前的青色倒十字发饰(旋转 45°) */
|
||||
body[data-theme="shiroko"] .group-title::before,
|
||||
body[data-theme="shiroko"] .group-row td::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin-right: 7px;
|
||||
vertical-align: middle;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><g transform='rotate(45 10 10)' fill='%232ea8dc'><rect x='8.5' y='0.5' width='3' height='19' rx='1.4'/><rect x='0.5' y='8.5' width='19' height='3' rx='1.4'/></g><circle cx='10' cy='10' r='2.6' fill='%23bfeaff'/></svg>");
|
||||
}
|
||||
|
||||
/* ---------- 状态点 → 迷你光环 ---------- */
|
||||
body[data-theme="shiroko"] [data-status="up"] { --sh-state: var(--up); }
|
||||
body[data-theme="shiroko"] [data-status="down"] { --sh-state: var(--down); }
|
||||
body[data-theme="shiroko"] [data-status="pending"] { --sh-state: var(--pending); }
|
||||
body[data-theme="shiroko"] .dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: transparent;
|
||||
border: 2px solid var(--sh-state, var(--pending));
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
box-shadow: 0 0 7px -1px var(--sh-state, var(--pending));
|
||||
}
|
||||
body[data-theme="shiroko"] .dot::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 2.5px;
|
||||
border-radius: 50%;
|
||||
background: var(--sh-state, var(--pending));
|
||||
}
|
||||
body[data-theme="shiroko"] .card[data-status="up"] .dot { animation: halo-pulse 2.6s ease-out infinite; }
|
||||
@keyframes halo-pulse {
|
||||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 45%, transparent); }
|
||||
70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--up) 0%, transparent); }
|
||||
100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 0%, transparent); }
|
||||
}
|
||||
|
||||
/* ---------- 卡片 / 表格 / 交互 ---------- */
|
||||
body[data-theme="shiroko"] .card {
|
||||
border-left-width: 3px;
|
||||
background: linear-gradient(180deg, #ffffff, #fbfdff);
|
||||
}
|
||||
body[data-theme="shiroko"] .tab.active,
|
||||
body[data-theme="shiroko"] .view-btn.active {
|
||||
background: linear-gradient(135deg, var(--sh-sky-deep), #1b7fb4);
|
||||
box-shadow: 0 3px 10px -4px rgb(46 168 220 / 70%);
|
||||
}
|
||||
body[data-theme="shiroko"] #refresh-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
body[data-theme="shiroko"] .monitor-table th {
|
||||
background: linear-gradient(180deg, #f7fcff, #eef7fd);
|
||||
color: var(--sh-navy);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
body[data-theme="shiroko"] .monitor-table tbody tr:hover { background: rgb(46 168 220 / 6%); }
|
||||
body[data-theme="shiroko"] .group-row td {
|
||||
background: linear-gradient(90deg, #eaf6fd, #f7fcff);
|
||||
border-left: 3px solid var(--sh-sky-deep);
|
||||
color: var(--sh-navy);
|
||||
}
|
||||
body[data-theme="shiroko"] .table-wrap { box-shadow: var(--shadow); }
|
||||
body[data-theme="shiroko"] .uptime-cell.lv-none {
|
||||
background-image: repeating-linear-gradient(135deg, rgb(46 168 220 / 16%) 0 1px, transparent 1px 3px);
|
||||
background-color: var(--uptime-none);
|
||||
}
|
||||
|
||||
/* ---------- 刷新按钮的公路车轮加载态 ---------- */
|
||||
body[data-theme="shiroko"] #refresh-btn .wheel { animation: wheel-spin 0.9s linear infinite; }
|
||||
@keyframes wheel-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ---------- 角色立绘(settings.artwork,默认指向内置立绘) ---------- */
|
||||
body[data-theme="shiroko"] .artwork {
|
||||
position: fixed;
|
||||
right: 8px;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
height: min(56vh, 520px);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
filter: drop-shadow(0 8px 24px rgb(23 60 100 / 18%));
|
||||
mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 100%);
|
||||
-webkit-mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 100%);
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
body[data-theme="shiroko"] .artwork { display: none; }
|
||||
}
|
||||
/* 宽屏时为立绘留出空间,避免遮住表格右侧的可用率列 */
|
||||
@media (min-width: 1100px) {
|
||||
body[data-theme="shiroko"] .grid,
|
||||
body[data-theme="shiroko"] .footer { padding-right: 240px; }
|
||||
}
|
||||
|
||||
/* 内容层置于装饰之上 */
|
||||
body[data-theme="shiroko"] .grid,
|
||||
body[data-theme="shiroko"] .footer,
|
||||
body[data-theme="shiroko"] .group-tabs { position: relative; z-index: 1; }
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>Shiroko Monitor</title>
|
||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
<link rel="stylesheet" href="assets/theme-shiroko.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
@@ -25,6 +26,16 @@
|
||||
<div class="stat"><b id="stat-latency">–</b><label>平均延迟</label></div>
|
||||
<div class="stat"><b id="stat-uptime">–</b><label>90天可用率</label></div>
|
||||
<div class="view-switch" id="view-switch" role="group" aria-label="视图切换"></div>
|
||||
<button id="theme-btn" type="button" class="icon-btn" title="切换主题">
|
||||
<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||
<circle cx="50" cy="50" r="31" fill="none" stroke="currentColor" stroke-width="8"/>
|
||||
<circle cx="50" cy="50" r="15" fill="none" stroke="currentColor" stroke-width="4" opacity="0.7"/>
|
||||
<polygon points="46.4,4 53.6,4 51.2,15 48.8,15" fill="currentColor"/>
|
||||
<polygon points="46.4,96 53.6,96 51.2,85 48.8,85" fill="currentColor"/>
|
||||
<polygon points="4,46.4 4,53.6 15,51.2 15,48.8" fill="currentColor"/>
|
||||
<polygon points="96,46.4 96,53.6 85,51.2 85,48.8" fill="currentColor"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="refresh-btn" type="button">刷新</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -41,7 +52,7 @@
|
||||
<span id="ur-chip" class="footer-item warn-chip" hidden>UptimeRobot 同步异常</span>
|
||||
<span id="updated-at" class="footer-item">–</span>
|
||||
<span id="countdown" class="footer-item">–</span>
|
||||
<span class="footer-item version">shiroko-monitor v0.6.0</span>
|
||||
<span class="footer-item version">shiroko-monitor v0.7.0</span>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="assets/app.js"></script>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"title": "Shiroko Monitor",
|
||||
"description": "轻量级服务监控面板",
|
||||
"view": "table",
|
||||
"theme": "shiroko",
|
||||
"artwork": "assets/shiroko-portrait.webp",
|
||||
"dataFilter": "uptimerobot",
|
||||
"refreshInterval": 300,
|
||||
"dataSource": "auto",
|
||||
|
||||