机架管理前后端(rack/):FastAPI + SQLite + Vue3,经统一门户上线

- 后端 prrack:MQTT(phonerail-backend @ 127.0.0.1:1884)↔ 遥测入库 / 事件增量同步 / WebSocket 推送;
  命令白名单 + 重传同 seq;审计记门户 X-Auth-User(只绑 127.0.0.1,main.py 拒绝非回环)。
- 自动恢复:只看电流(平线 / 过低,服务器够不到手机 adb),先长按电源再断电重启,
  每小时限次后停手;默认逐台关闭。adb 探活接口 recovery.Prober 预留。
- 前端:总览 / 板子页(五路操作、电流电压曲线)/ 事件 / 操作记录 / 自动恢复;手机宽度可用。
- 部署 deploy/deploy.sh:服务器 Python 3.8 上先跑测试再替换;systemd phonerail-rack,每日备份。
- 门户应用 id 16、key rack(phonerail 已被需求文档站占用),授权 admin + staff。

测试 26 例(本机 3.14 / 服务器 3.8 均过)。线上板 b81f3fc9c8bc 在线,WS 经门户 101 + 1 条/s 遥测。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-27 12:33:28 +08:00
共同撰写人 Claude Opus 5.5
父节点 a32fa06c43
当前提交 26c04ce878
共修改 34 个文件,包含 4031 行新增和 0 行删除
+52
查看文件
@@ -0,0 +1,52 @@
<template>
<div class="shell">
<header class="top">
<router-link to="/" class="brand">PhoneRail 机架</router-link>
<nav>
<router-link to="/">总览</router-link>
<router-link to="/events">事件</router-link>
<router-link to="/audit">操作记录</router-link>
<router-link to="/recovery">自动恢复</router-link>
</nav>
<div class="conn">
<span :class="['dot', state.ws ? 'up' : 'down']" :title="state.ws ? '已连上服务器' : '与服务器断开,重连中'" />
<span v-if="!state.ws" class="warn">断开</span>
<span v-else-if="!state.mqtt" class="warn" title="后端连不上 MQTT broker:所有板子都会显示通信中断">broker 断开</span>
<span class="user">{{ state.user }}</span>
</div>
</header>
<main><router-view /></main>
</div>
</template>
<script setup>
import { state } from './store'
</script>
<style>
:root { --bg: #f1f5f9; --card: #fff; --line: #e2e8f0; --muted: #64748b; }
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); font-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; color: #0f172a; }
.shell { min-height: 100vh; }
.top { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: 16px;
padding: 0 16px; height: 52px; background: #0f172a; color: #fff; }
.top a { color: #cbd5e1; text-decoration: none; }
.top .brand { color: #fff; font-weight: 600; white-space: nowrap; }
.top nav { display: flex; gap: 14px; overflow-x: auto; flex: 1; min-width: 0; }
.top nav a { white-space: nowrap; padding: 4px 0; }
.top nav a.router-link-exact-active { color: #fff; border-bottom: 2px solid #38bdf8; }
.conn { display: flex; align-items: center; gap: 8px; font-size: 13px; white-space: nowrap; }
.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.dot.up { background: #22c55e; } .dot.down { background: #ef4444; }
.conn .warn { color: #fca5a5; }
.conn .user { color: #94a3b8; }
main { padding: 16px; max-width: 1400px; margin: 0 auto; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 14px; }
.muted { color: var(--muted); font-size: 13px; }
@media (max-width: 640px) {
.top { gap: 10px; padding: 0 10px; }
.top .brand { display: none; }
.conn .user { display: none; }
main { padding: 10px; }
}
</style>