Backend:
- Persist alert events and robot delivery counters in SQLite (data/app.db,
YOLO_DB_PATH override); EventStore keeps its interface, DeliveryStatsStore
uses read/write-through UPSERT; in-memory fallback remains for standalone
AlertManager use
- Serve /api/detect from a sync endpoint and serialize YOLO inference with a
lock, so concurrent requests no longer block the event loop
- Unify single-image and session detection: requests without session_id
share the fixed single-image session and run full frame confirmation
- Store UTC Z-suffixed timestamps for sortable string comparison; close
executor and database on shutdown via FastAPI lifespan
Frontend:
- Split app.js into ES modules (main.js + modules/{dom,api,ui,charts,store}
+ views/{dashboard,inspection,events,robots,robotCards}), no build step
- Escape all server data interpolated into innerHTML; guard missing
event.classes; replace lazy element ID list with memoized qs()
- Remove hardcoded fake stats (trend badge, device donut segment)
Docs:
- Rewrite README: accurate weight policy (only trained best.pt committed,
no Git LFS), SQLite persistence, single-worker note, data asset inventory
- Align models/pretrained/README.md with actual files; add YOLO_DB_PATH to
.env.example; add persistence unit tests; ruff clean
212 lines
21 KiB
HTML
212 lines
21 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<meta name="theme-color" content="#0b1117" />
|
||
<title>燧安 · 火灾预防管理平台</title>
|
||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23153a39'/%3E%3Cpath d='M34 8c3 12-8 15-3 25 2-5 7-8 10-13 8 9 12 18 8 27-4 9-14 13-23 10C12 53 9 40 15 30c4-7 10-11 12-22 4 4 6 8 7 12z' fill='%2341d6c3'/%3E%3C/svg%3E" />
|
||
<link rel="stylesheet" href="./styles.css" />
|
||
</head>
|
||
<body>
|
||
<div class="platform-shell">
|
||
<aside class="sidebar" id="sidebar">
|
||
<div class="brand">
|
||
<div class="brand-mark" aria-hidden="true">燧</div>
|
||
<div>
|
||
<strong>燧安</strong>
|
||
<span>火灾预防管理平台</span>
|
||
</div>
|
||
</div>
|
||
<nav class="main-nav" aria-label="主导航">
|
||
<button class="nav-item is-active" type="button" data-view="overview"><span class="nav-icon">▦</span><span>系统总览</span></button>
|
||
<button class="nav-item" type="button" data-view="inspection"><span class="nav-icon">▶</span><span>视频巡检</span></button>
|
||
<button class="nav-item" type="button" data-view="events"><span class="nav-icon">!</span><span>告警中心</span><span class="nav-count" id="pendingNavCount">0</span></button>
|
||
<button class="nav-item" type="button" data-view="robots"><span class="nav-icon">⌁</span><span>消息机器人</span><span class="nav-count nav-count-cyan" id="robotNavCount">0</span></button>
|
||
<button class="nav-item" type="button" data-view="risks"><span class="nav-icon">◇</span><span>风险台账</span></button>
|
||
<button class="nav-item" type="button" data-view="settings"><span class="nav-icon">⚙</span><span>系统设置</span></button>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<div class="system-indicator"><span id="sidebarStatusDot" class="status-dot"></span><div><strong id="sidebarStatus">连接中</strong><span>智能视觉预警服务</span></div></div>
|
||
<span class="version">YOLO11S · v0.1.0</span>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="main-area">
|
||
<header class="topbar">
|
||
<div class="topbar-left">
|
||
<button id="menuButton" class="icon-button menu-button" type="button" aria-label="展开导航">☰</button>
|
||
<div>
|
||
<p class="breadcrumb">消防安全 / <span id="viewBreadcrumb">系统总览</span></p>
|
||
<h1 id="viewTitle">系统总览</h1>
|
||
</div>
|
||
</div>
|
||
<div class="topbar-actions">
|
||
<div class="clock-block"><span>当前时间</span><strong id="currentClock">--:--:--</strong></div>
|
||
<span id="globalStatus" class="status-chip status-checking">服务检查中</span>
|
||
<button class="icon-button" type="button" data-view-jump="events" aria-label="查看告警">♢<span id="notificationDot" class="notification-dot" hidden></span></button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="content-area">
|
||
<section class="view is-active" id="view-overview" data-view-panel="overview">
|
||
<div class="hero-strip command-hero">
|
||
<div>
|
||
<p class="section-kicker">FIRE PREVENTION COMMAND CENTER</p>
|
||
<h2>全域消防安全态势</h2>
|
||
<p id="overviewMessage">正在汇总模型、告警与通知通道状态。</p>
|
||
</div>
|
||
<div class="hero-actions"><span class="live-pill"><i></i>实时监测中</span><button class="primary-button" type="button" data-view-jump="inspection">开始视频巡检</button></div>
|
||
</div>
|
||
|
||
<div class="stat-grid">
|
||
<article class="stat-card"><div class="stat-head"><span>今日告警</span><span class="stat-symbol warning">!</span></div><strong id="todayEventCount">0</strong><small>系统产生的有效告警事件</small></article>
|
||
<article class="stat-card"><div class="stat-head"><span>待处置事件</span><span class="stat-symbol danger">●</span></div><strong id="pendingEventCount">0</strong><small>需要值班人员确认处理</small></article>
|
||
<article class="stat-card"><div class="stat-head"><span>模型状态</span><span class="stat-symbol success">✓</span></div><strong id="modelStatusValue" class="text-value">检查中</strong><small id="modelStatusDetail">正在读取权重状态</small></article>
|
||
<article class="stat-card"><div class="stat-head"><span>消息机器人</span><span class="stat-symbol info">⌁</span></div><strong id="onlineRobotCount">0</strong><small id="robotSummary">正在读取通知通道</small></article>
|
||
</div>
|
||
|
||
<div class="dashboard-command-grid">
|
||
<article class="panel safety-score-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">SAFETY INDEX</p><h3>综合安全指数</h3></div></div>
|
||
<div class="safety-score-body"><div id="safetyGauge" class="safety-gauge" style="--score: 86"><div><strong id="safetyScore">86</strong><span>安全</span></div></div><div class="score-breakdown"><div><span>机器人可用率</span><strong id="deviceOnlineRate">--</strong></div><div><span>事件闭环率</span><strong id="eventClosureRate">--</strong></div><div><span>通知覆盖率</span><strong id="notificationCoverage">--</strong></div></div></div>
|
||
</article>
|
||
<article class="panel message-flow-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">MESSAGE DELIVERY</p><h3>告警消息触达链路</h3></div><button class="text-button" type="button" data-view-jump="robots">管理机器人 →</button></div>
|
||
<div class="message-flow" aria-label="告警消息触达流程">
|
||
<div class="flow-node source"><span>01</span><strong>烟火识别</strong><small>YOLO 实时推理</small></div>
|
||
<i class="flow-connector"></i>
|
||
<div class="flow-node policy"><span>02</span><strong>告警确认</strong><small id="overviewPolicyText">连续帧策略</small></div>
|
||
<i class="flow-connector split"></i>
|
||
<div class="flow-destinations"><div class="flow-node channel"><span class="wechat-dot">微</span><strong>企业微信</strong><small id="overviewWechatState">检查中</small></div><div class="flow-node channel"><span class="feishu-dot">飞</span><strong>飞书</strong><small id="overviewFeishuState">检查中</small></div></div>
|
||
</div>
|
||
<div class="flow-foot"><span>检测结果只在达到确认阈值后发送</span><strong id="overviewDeliveryCount">累计成功 0 次</strong></div>
|
||
</article>
|
||
<article class="panel risk-donut-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">RISK MIX</p><h3>风险类型分布</h3></div><span class="micro-copy">近 7 日</span></div>
|
||
<div class="donut-layout"><div id="riskDonut" class="risk-donut"><div><strong id="riskTotal">0</strong><span>事件</span></div></div><div class="donut-legend"><div><span><i class="legend-fire"></i>火焰</span><strong id="fireRiskRatio">0%</strong></div><div><span><i class="legend-smoke"></i>烟雾</span><strong id="smokeRiskRatio">0%</strong></div></div></div>
|
||
</article>
|
||
</div>
|
||
|
||
<div class="overview-grid overview-grid-lower">
|
||
<article class="panel trend-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">ALERT TREND</p><h3>近期告警趋势</h3></div><div class="legend"><span><i class="legend-fire"></i>火焰</span><span><i class="legend-smoke"></i>烟雾</span></div></div>
|
||
<div id="trendChart" class="trend-chart" role="img" aria-label="近期火焰和烟雾告警趋势"></div>
|
||
</article>
|
||
<article class="panel readiness-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">SYSTEM READINESS</p><h3>系统就绪度</h3></div></div>
|
||
<div class="readiness-list">
|
||
<div class="readiness-item"><span>模型权重</span><strong id="readyWeights">检查中</strong></div>
|
||
<div class="readiness-item"><span>检测服务</span><strong id="readyApi">检查中</strong></div>
|
||
<div class="readiness-item"><span>企业微信</span><strong id="readyWechat">未配置</strong></div>
|
||
<div class="readiness-item"><span>飞书机器人</span><strong id="readyFeishu">未配置</strong></div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
|
||
<article class="panel recent-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">RECENT EVENTS</p><h3>最近告警事件</h3></div><button class="text-button" type="button" data-view-jump="events">查看全部 →</button></div>
|
||
<div id="recentEventList" class="event-list empty-list"><p>暂无告警事件。开始视频巡检后,满足连续帧条件的告警会显示在这里。</p></div>
|
||
</article>
|
||
</section>
|
||
|
||
<section class="view" id="view-inspection" data-view-panel="inspection">
|
||
<div class="page-intro"><div><p class="section-kicker">VIDEO INSPECTION</p><h2>智能视频巡检</h2><p>选择本地监控视频,系统将按设定间隔抽帧并进行烟雾、火焰识别。</p></div><span id="inspectionStatus" class="status-chip status-idle">等待视频</span></div>
|
||
<div class="inspection-grid">
|
||
<article class="panel video-panel">
|
||
<div class="video-toolbar"><div><strong id="sourceLabel">未选择视频</strong><span id="videoMeta">支持 MP4、WebM 等浏览器可播放格式</span></div><span id="videoClock">00:00 / 00:00</span></div>
|
||
<div class="media-stage">
|
||
<video id="videoPreview" controls muted playsinline hidden></video>
|
||
<div id="emptyState" class="empty-state"><div class="upload-emblem">+</div><strong>导入监控视频</strong><span>视频在浏览器本地播放,仅将抽取帧发送至检测服务</span></div>
|
||
<canvas id="overlayCanvas" aria-hidden="true"></canvas>
|
||
<div id="loadingState" class="loading-state" hidden><span class="spinner"></span>正在分析视频帧</div>
|
||
</div>
|
||
<div class="video-actions">
|
||
<label class="file-button"><input id="videoInput" type="file" accept="video/*" /><span>选择视频</span></label>
|
||
<button id="runDetectionButton" class="primary-button" type="button" disabled>开始连续检测</button>
|
||
<button id="clearButton" class="secondary-button" type="button">清除</button>
|
||
</div>
|
||
</article>
|
||
|
||
<div class="inspection-side">
|
||
<article class="panel detection-summary">
|
||
<div class="panel-header"><div><p class="section-kicker">LIVE RESULT</p><h3>实时检测摘要</h3></div><span id="lastUpdated" class="micro-copy">尚未检测</span></div>
|
||
<div class="detection-metrics">
|
||
<div><span>烟雾目标</span><strong id="smokeCount">0</strong></div>
|
||
<div><span>火焰目标</span><strong id="fireCount">0</strong></div>
|
||
<div><span>最高置信度</span><strong id="maxConfidence">--</strong></div>
|
||
<div><span>风险等级</span><strong id="riskStatus">待机</strong></div>
|
||
</div>
|
||
<div id="alertStatus" class="alert-status">机器人告警状态:检查中</div>
|
||
<div id="detectionList" class="detection-list"><p class="empty-copy">暂无检测结果</p></div>
|
||
</article>
|
||
<article class="panel inspection-settings">
|
||
<div class="panel-header"><div><p class="section-kicker">STRATEGY</p><h3>巡检策略</h3></div></div>
|
||
<label class="field-row"><span>检测间隔</span><select id="intervalSelect"><option value="500">0.5 秒</option><option value="1000" selected>1 秒</option><option value="2000">2 秒</option></select></label>
|
||
<div class="strategy-row"><span>连续确认帧</span><strong id="confirmFramesValue">3 帧</strong></div>
|
||
<div class="strategy-row"><span>告警冷却</span><strong id="cooldownValue">60 秒</strong></div>
|
||
<div class="strategy-note">连续检测到目标后才会生成告警事件,并按火焰、烟雾分别执行冷却。</div>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="view" id="view-events" data-view-panel="events">
|
||
<div class="page-intro"><div><p class="section-kicker">ALERT CENTER</p><h2>告警事件管理</h2><p>集中查看、确认和关闭视频巡检产生的火灾风险事件。</p></div><button id="refreshEventsButton" class="secondary-button" type="button">刷新事件</button></div>
|
||
<div class="event-stat-row"><div><span>全部事件</span><strong id="allEventsCount">0</strong></div><div><span>待处置</span><strong id="pendingEventsCount">0</strong></div><div><span>已确认</span><strong id="ackEventsCount">0</strong></div><div><span>已解决</span><strong id="resolvedEventsCount">0</strong></div></div>
|
||
<article class="panel event-table-panel">
|
||
<div class="table-toolbar"><div class="filter-group" role="group" aria-label="事件状态筛选"><button type="button" class="filter-button is-active" data-event-filter="">全部</button><button type="button" class="filter-button" data-event-filter="pending">待处置</button><button type="button" class="filter-button" data-event-filter="acknowledged">已确认</button><button type="button" class="filter-button" data-event-filter="resolved">已解决</button></div><span id="eventTableMeta">0 条记录</span></div>
|
||
<div class="table-wrap"><table class="event-table"><thead><tr><th>事件编号</th><th>时间</th><th>类型</th><th>置信度</th><th>通知通道</th><th>状态</th><th>操作</th></tr></thead><tbody id="eventTableBody"><tr><td colspan="7" class="empty-cell">暂无告警事件</td></tr></tbody></table></div>
|
||
</article>
|
||
</section>
|
||
|
||
<section class="view" id="view-robots" data-view-panel="robots">
|
||
<div class="page-intro robot-page-intro"><div><p class="section-kicker">MESSAGING ROBOT OPERATIONS</p><h2>消息机器人管理</h2><p>管理企业微信与飞书群机器人,监控告警投递状态并执行连接测试。</p></div><button id="refreshRobotsButton" class="secondary-button" type="button">刷新状态</button></div>
|
||
<div class="fleet-stat-grid">
|
||
<article class="fleet-stat"><span>支持平台</span><strong id="robotTotalCount">0</strong><small>企业微信与飞书</small></article>
|
||
<article class="fleet-stat"><span>已配置</span><strong id="robotConfiguredCount">0</strong><small>可接收告警消息</small></article>
|
||
<article class="fleet-stat"><span>成功投递</span><strong id="robotDeliveredCount">0</strong><small>累计(已持久化)</small></article>
|
||
<article class="fleet-stat"><span>发送失败</span><strong id="robotFailedCount">0</strong><small>需要检查网络或凭据</small></article>
|
||
</div>
|
||
<div class="robot-management-grid message-robot-layout">
|
||
<div id="robotCardGrid" class="message-robot-grid"><article class="panel robot-empty">正在读取机器人状态</article></div>
|
||
<aside class="panel robot-policy-panel">
|
||
<div class="panel-header"><div><p class="section-kicker">DELIVERY POLICY</p><h3>告警发送策略</h3></div><span class="status-chip status-online">自动执行</span></div>
|
||
<div class="policy-chain"><div><span>1</span><p><strong>连续帧确认</strong><small id="robotPolicyConfirm">--</small></p></div><div><span>2</span><p><strong>生成告警内容</strong><small>目标类型、置信度与检测时间</small></p></div><div><span>3</span><p><strong>并行发送</strong><small>向所有已配置机器人投递</small></p></div><div><span>4</span><p><strong>分类冷却</strong><small id="robotPolicyCooldown">--</small></p></div></div>
|
||
<div class="credential-note"><strong>凭据安全</strong><p>Webhook 与签名密钥只保存在服务端 <code>.env</code>,页面不会读取或展示敏感值。</p></div>
|
||
</aside>
|
||
</div>
|
||
<article class="panel robot-config-panel"><div class="panel-header"><div><p class="section-kicker">CONFIGURATION</p><h3>机器人接入</h3></div></div><div class="config-steps"><div><span>01</span><p><strong>创建群机器人</strong><small>在企业微信群或飞书群中添加自定义机器人。</small></p></div><div><span>02</span><p><strong>写入服务端配置</strong><small><code>WECHAT_WEBHOOK_URL</code> / <code>FEISHU_WEBHOOK_URL</code></small></p></div><div><span>03</span><p><strong>重启并测试</strong><small>重启服务后,在机器人卡片中发送测试消息。</small></p></div></div></article>
|
||
</section>
|
||
<section class="view" id="view-risks" data-view-panel="risks">
|
||
<div class="page-intro"><div><p class="section-kicker">RISK REGISTER</p><h2>风险台账</h2><p>从模型阈值、告警策略和处置状态评估当前风险治理情况。</p></div></div>
|
||
<div class="risk-grid">
|
||
<article class="panel risk-card"><span class="risk-level high">高风险</span><h3>未处置告警事件</h3><strong id="riskPendingValue">0</strong><p>待值班人员确认的火焰或烟雾事件。</p><button class="text-button" type="button" data-view-jump="events">进入告警中心 →</button></article>
|
||
<article class="panel risk-card"><span class="risk-level medium">策略风险</span><h3>告警通知覆盖</h3><strong id="riskChannelValue">0 / 2</strong><p>建议至少启用一个机器人通知通道。</p><button class="text-button" type="button" data-view-jump="robots">管理消息机器人 →</button></article>
|
||
<article class="panel risk-card"><span class="risk-level low">模型策略</span><h3>检测置信度阈值</h3><strong id="riskConfidenceValue">--</strong><p>阈值过低会增加误报,过高可能遗漏早期烟雾。</p><button class="text-button" type="button" data-view-jump="settings">查看系统参数 →</button></article>
|
||
</div>
|
||
<article class="panel checklist-panel"><div class="panel-header"><div><p class="section-kicker">PREVENTION CHECKLIST</p><h3>火灾预防工作清单</h3></div></div><div class="checklist"><label><input type="checkbox" />视频监控覆盖重点区域</label><label><input type="checkbox" />告警机器人可正常接收消息</label><label><input type="checkbox" />值班人员明确事件处置流程</label><label><input type="checkbox" />模型权重与阈值定期复核</label><label><input type="checkbox" />消防通道和设备状态已巡查</label><label><input type="checkbox" />告警事件完成闭环记录</label></div></article>
|
||
</section>
|
||
|
||
<section class="view" id="view-settings" data-view-panel="settings">
|
||
<div class="page-intro"><div><p class="section-kicker">SYSTEM SETTINGS</p><h2>系统设置</h2><p>查看当前生效的模型和告警参数。配置修改后需重启服务。</p></div></div>
|
||
<div class="settings-grid">
|
||
<article class="panel settings-card"><div class="panel-header"><div><p class="section-kicker">MODEL</p><h3>模型配置</h3></div></div><dl><div><dt>权重文件</dt><dd id="settingWeights">--</dd></div><div><dt>输入尺寸</dt><dd id="settingImageSize">--</dd></div><div><dt>置信度阈值</dt><dd id="settingConfidence">--</dd></div><div><dt>IOU 阈值</dt><dd id="settingIou">--</dd></div></dl></article>
|
||
<article class="panel settings-card"><div class="panel-header"><div><p class="section-kicker">ALERT POLICY</p><h3>告警策略</h3></div></div><dl><div><dt>连续确认</dt><dd id="settingConfirmFrames">--</dd></div><div><dt>分类冷却</dt><dd id="settingCooldown">--</dd></div><div><dt>事件存储</dt><dd>SQLite 持久化 · 最近 500 条</dd></div><div><dt>视频处理</dt><dd>浏览器本地抽帧</dd></div></dl></article>
|
||
</div>
|
||
<article class="panel env-panel"><div class="panel-header"><div><p class="section-kicker">ENVIRONMENT</p><h3>环境变量参考</h3></div></div><pre><code>YOLO_WEIGHTS=...
|
||
YOLO_DEVICE=0
|
||
YOLO_IMGSZ=768
|
||
YOLO_CONF=0.40
|
||
YOLO_IOU=0.45
|
||
ALERT_CONFIRM_FRAMES=3
|
||
ALERT_COOLDOWN_SECONDS=60</code></pre></article>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<div id="toastRegion" class="toast-region" aria-live="polite"></div>
|
||
<script src="./main.js" type="module"></script>
|
||
</body>
|
||
</html>
|