机架管理前后端(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 行删除
+54
查看文件
@@ -0,0 +1,54 @@
// 显示用的小工具:单位换算、每一路的健康状态。
export const mA = ua => (ua == null ? '—' : (ua / 1000).toFixed(ua >= 100000 || ua <= -100000 ? 0 : 1))
export const V = mv => (mv == null ? '—' : (mv / 1000).toFixed(3))
export const C = mc => (mc == null ? '—' : (mc / 1000).toFixed(1))
export function ago(ts) {
if (!ts) return '从未'
const s = Math.round((Date.now() - ts) / 1000)
if (s < 60) return `${s} 秒前`
if (s < 3600) return `${Math.round(s / 60)} 分钟前`
if (s < 86400) return `${Math.round(s / 3600)} 小时前`
return `${Math.round(s / 86400)} 天前`
}
export function fmtTime(ts) {
if (!ts) return ''
const d = new Date(ts)
const p = n => String(n).padStart(2, '0')
return `${d.getMonth() + 1}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
}
// 执行器状态(firmware t_actuator_state_str)
export const ACT_ZH = {
idle: '空闲', 'hr-off': '断电中', 'hr-ramp': '上电中', 'hr-pg': '等 PG',
'key-hold': '按键中', 'edl-short': '进 EDL', 'edl-enum': '等枚举', cleanup: '收尾',
}
// 一路的健康状态 → { level, text }。level 决定颜色:
// ok 绿 / busy 蓝 / warn 橙 / bad 红 / off 深灰 / empty 浅灰 / lost 灰(通信中断)
export function railHealth(board, r) {
if (!board || board.stale || !board.online) return { level: 'lost', text: '通信中断' }
if (!r) return { level: 'lost', text: '无数据' }
if (!r.pr) return { level: 'empty', text: '空槽' }
if (r.st && r.st !== 'idle') return { level: 'busy', text: ACT_ZH[r.st] || r.st }
if (!r.en) return { level: 'off', text: '已断电' }
if (r.flt) return { level: 'bad', text: 'eFuse 故障' }
if (r.pg === false) return { level: 'bad', text: 'PG 丢失' }
if (r.mv == null || r.ua == null) return { level: 'warn', text: '读不到' }
return { level: 'ok', text: '运行' }
}
export const LEVEL_COLOR = {
ok: '#16a34a', busy: '#2563eb', warn: '#d97706', bad: '#dc2626',
off: '#475569', empty: '#cbd5e1', lost: '#94a3b8',
}
export const RECOVERY_ZH = {
idle: '正常', soft: '已长按电源', hard: '已断电重启', gave_up: '已放弃(需人工)',
}
export const REASON_ZH = {
flat: '电流平直(疑似卡死)', low: '电流过低(疑似关机)', recovered: '已恢复',
rate_limit: '一小时内动作过多', manual_reset: '人工复位',
}