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
31 lines
2.0 KiB
JavaScript
31 lines
2.0 KiB
JavaScript
import { qs } from "./dom.js";
|
|
|
|
export function renderTrend(events = []) {
|
|
const days = Array.from({ length: 7 }, (_, index) => {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() - (6 - index));
|
|
return {
|
|
key: date.toISOString().slice(0, 10),
|
|
label: `${date.getMonth() + 1}/${date.getDate()}`,
|
|
fire: 0,
|
|
smoke: 0,
|
|
};
|
|
});
|
|
events.forEach((event) => {
|
|
const classes = event.classes || [];
|
|
const day = days.find((item) => item.key === String(event.created_at).slice(0, 10));
|
|
if (!day) return;
|
|
if (classes.includes("fire")) day.fire += 1;
|
|
if (classes.includes("smoke")) day.smoke += 1;
|
|
});
|
|
const width = 720;
|
|
const height = 225;
|
|
const padding = { left: 34, right: 12, top: 14, bottom: 28 };
|
|
const maxValue = Math.max(3, ...days.flatMap((day) => [day.fire, day.smoke]));
|
|
const x = (index) => padding.left + index * ((width - padding.left - padding.right) / 6);
|
|
const y = (value) => height - padding.bottom - value * ((height - padding.top - padding.bottom) / maxValue);
|
|
const path = (key) => days.map((day, index) => `${index ? "L" : "M"}${x(index)},${y(day[key])}`).join(" ");
|
|
const gridValues = [0, Math.ceil(maxValue / 2), maxValue];
|
|
qs("#trendChart").innerHTML = `<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none"><title>最近七天火焰与烟雾告警趋势</title>${gridValues.map((value) => `<line class="chart-grid" x1="${padding.left}" x2="${width - padding.right}" y1="${y(value)}" y2="${y(value)}"></line><text class="chart-axis-label" x="4" y="${y(value) + 3}">${value}</text>`).join("")}<path class="chart-fire" d="${path("fire")}"></path><path class="chart-smoke" d="${path("smoke")}"></path>${days.map((day, index) => `<circle class="chart-point-fire" cx="${x(index)}" cy="${y(day.fire)}" r="3"></circle><circle class="chart-point-smoke" cx="${x(index)}" cy="${y(day.smoke)}" r="3"></circle><text class="chart-axis-label" text-anchor="middle" x="${x(index)}" y="${height - 8}">${day.label}</text>`).join("")}</svg>`;
|
|
}
|