init: 从 /root/zikai 根目录迁入
把原本散落在 /root/zikai 的 FastAPI 服务整理到 server/ 子目录, 开启独立 git 与 venv。运行时数据(config.yaml / keys / uploads) 按 .gitignore 留在工作目录但不入仓。
This commit is contained in:
5
app/views/__init__.py
Normal file
5
app/views/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""HTML 视图(纯字符串模板)。"""
|
||||
|
||||
from . import system_status_html
|
||||
|
||||
__all__ = ["system_status_html"]
|
||||
134
app/views/system_status_html.py
Normal file
134
app/views/system_status_html.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""系统状态页面的 HTML 渲染。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from html import escape
|
||||
|
||||
from ..schemas.system import SystemStatus
|
||||
|
||||
|
||||
def _fmt_bytes(n: int) -> str:
|
||||
x = float(n)
|
||||
for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB"):
|
||||
if abs(x) < 1024.0 or unit == "PiB":
|
||||
return f"{int(x):,} B" if unit == "B" else f"{x:,.1f} {unit}"
|
||||
x /= 1024.0
|
||||
return f"{x:,.1f} PiB"
|
||||
|
||||
|
||||
def _fmt_duration(seconds: float) -> str:
|
||||
s = int(seconds)
|
||||
d, s = divmod(s, 86400)
|
||||
h, s = divmod(s, 3600)
|
||||
m, s = divmod(s, 60)
|
||||
parts: list[str] = []
|
||||
if d:
|
||||
parts.append(f"{d}天")
|
||||
if h or d:
|
||||
parts.append(f"{h}小时")
|
||||
if m or h or d:
|
||||
parts.append(f"{m}分")
|
||||
parts.append(f"{s}秒")
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
def _bar(pct: float) -> str:
|
||||
"""带颜色阈值的进度条:>85% 红,>60% 黄,否则绿。"""
|
||||
pct = max(0.0, min(100.0, pct))
|
||||
color = "#e53935" if pct >= 85 else "#fb8c00" if pct >= 60 else "#43a047"
|
||||
return (
|
||||
'<div class="bar">'
|
||||
f'<div class="fill" style="width:{pct:.1f}%;background:{color};"></div>'
|
||||
f'<span class="pct">{pct:.1f}%</span>'
|
||||
'</div>'
|
||||
)
|
||||
|
||||
|
||||
_CSS = """
|
||||
:root { color-scheme: light dark; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
max-width: 880px; margin: 2em auto; padding: 0 1em; line-height: 1.5; }
|
||||
h1 { margin-bottom: 0.1em; }
|
||||
.sub { color: #777; margin-top: 0; font-size: 0.95em; }
|
||||
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1em 1.2em;
|
||||
margin: 1em 0; background: rgba(0,0,0,0.02); }
|
||||
.card h2 { margin: 0 0 0.6em; font-size: 1.1em; }
|
||||
.row { display: grid; grid-template-columns: 160px 1fr; gap: 0.25em 1em;
|
||||
align-items: center; padding: 0.15em 0; }
|
||||
.row .k { color: #666; }
|
||||
.bar { position: relative; background: #e6e6e6; border-radius: 4px;
|
||||
height: 18px; width: 100%; overflow: hidden; }
|
||||
.bar .fill { height: 100%; transition: width 0.3s; }
|
||||
.bar .pct { position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
text-align: center; font-size: 12px; line-height: 18px;
|
||||
color: #fff; mix-blend-mode: difference; }
|
||||
table.disks { border-collapse: collapse; width: 100%; }
|
||||
table.disks th, table.disks td { padding: 0.4em 0.6em; text-align: left;
|
||||
border-bottom: 1px solid #eee; vertical-align: middle; }
|
||||
table.disks th { font-weight: 600; color: #555; font-size: 0.9em; }
|
||||
.foot { color: #888; font-size: 0.85em; margin-top: 2em; text-align: center; }
|
||||
a.json { font-size: 0.85em; color: #1565c0; text-decoration: none; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { background: #1a1a1a; color: #e0e0e0; }
|
||||
.card { background: rgba(255,255,255,0.04); border-color: #333; }
|
||||
.bar { background: #333; }
|
||||
table.disks th, table.disks td { border-bottom-color: #2a2a2a; }
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def render(status: SystemStatus) -> str:
|
||||
rows = "".join(
|
||||
"<tr>"
|
||||
f"<td>{escape(d.mountpoint)}</td>"
|
||||
f"<td>{escape(d.device)}</td>"
|
||||
f"<td>{escape(d.fstype)}</td>"
|
||||
f"<td>{_fmt_bytes(d.used)} / {_fmt_bytes(d.total)}</td>"
|
||||
f"<td>{_fmt_bytes(d.free)}</td>"
|
||||
f"<td style='min-width:160px'>{_bar(d.percent)}</td>"
|
||||
"</tr>"
|
||||
for d in status.disks
|
||||
)
|
||||
|
||||
return f"""<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>系统状态 — {escape(status.hostname)}</title>
|
||||
<style>{_CSS}</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>系统状态</h1>
|
||||
<p class="sub">主机 <b>{escape(status.hostname)}</b> · 运行 {_fmt_duration(status.uptime_seconds)}
|
||||
· <a class="json" href="?format=json">查看 JSON</a></p>
|
||||
|
||||
<div class="card">
|
||||
<h2>CPU</h2>
|
||||
<div class="row"><div class="k">逻辑核心数</div><div>{status.cpu_count}</div></div>
|
||||
<div class="row"><div class="k">使用率</div> <div>{_bar(status.cpu_percent)}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>内存</h2>
|
||||
<div class="row"><div class="k">总量</div> <div>{_fmt_bytes(status.memory.total)}</div></div>
|
||||
<div class="row"><div class="k">已用</div> <div>{_fmt_bytes(status.memory.used)}</div></div>
|
||||
<div class="row"><div class="k">可用</div> <div>{_fmt_bytes(status.memory.available)}</div></div>
|
||||
<div class="row"><div class="k">使用率</div><div>{_bar(status.memory.percent)}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>磁盘</h2>
|
||||
<table class="disks">
|
||||
<thead><tr>
|
||||
<th>挂载点</th><th>设备</th><th>文件系统</th>
|
||||
<th>已用 / 总量</th><th>剩余</th><th>使用率</th>
|
||||
</tr></thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="foot">zikai file service · 数据来源 psutil</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
Reference in New Issue
Block a user