- Add `rem_local: bool` to UnitRuntime; set true when any equipment's
REM signal is false with good quality
- Engine check_fault_comm: stop auto-control and fire AutoControlStopped
when any equipment switches to local mode
- Block start-auto when rem_local (backend + error message)
- Frontend: disable Start Auto button in units/ops views when rem_local
- Frontend: disable equipment Start/Stop buttons in config view when
unit's rem_local is true
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reuses the existing API.md drawer for README; switching between
docs reloads content and updates the drawer title. Backend serves
README.md via /api/docs/readme-md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
loadPoints() relies on state.equipmentMap to display bound equipment names.
Running it in parallel with loadEquipments() caused a race condition where
points rendered before the map was populated, showing all as "Unbound".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signal indicators are now wider pill badges (40×20px rect) with the role
label (REM/RUN/FLT) embedded inside, replacing the 10px dot+label rows.
Equipment Start/Stop buttons are disabled when:
- auto control is active
- REM = 0 (device in local mode, not accepting remote commands)
- FLT = 1 (fault active)
Button state reacts in real time to WS signal updates via a per-equipment
syncBtns closure registered in state.opsUnitSyncFns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevent starting unit auto control while fault_locked or manual_ack_required,
enforcing that faults must be manually acknowledged before resuming automation.
Also disable the Start Auto button in the frontend with descriptive tooltips.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Events were only loaded when switching to config view, but the
system events panel lives in ops view, leaving it empty on refresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ops view now reads equipment+role_points from state.units (returned by
unit list API) instead of state.equipments, eliminating the loadEquipments()
call on bootstrap.
Config data (sources, equipments, events, points) is deferred until the
user first switches to config view. On WS reconnect, loadEquipments is
only refreshed when config view is active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a disconnect/reconnect, re-fetch units (runtimes) and equipments
(monitor data) so the UI reflects current server state without requiring
a page reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Equipment list response now includes signal-role points with monitor data,
so the ops view can render signal dots directly from state.equipments
without fetching /api/unit/:id/detail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
startOps() was calling loadAllEquipmentCards() redundantly — the
units-loaded event listener already calls it after loadUnits()
completes. This caused two parallel requests to /api/unit/:id/detail
for every unit on page load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the standalone GET /api/unit/runtimes endpoint in favour of
embedding runtime directly in existing responses:
- GET /api/unit → each item now includes `runtime` field
- GET /api/unit/:id → returns UnitWithRuntime
- GET /api/unit/:id/detail → UnitDetail now includes `runtime`
runtime is null when the engine has not yet initialised the unit.
Frontend loadUnits() reads the embedded runtime field to populate
state.runtimes — one request instead of two.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: state.runtimes was empty after refresh because the engine
only pushes UnitRuntimeChanged on state transitions — if the engine
is mid-wait-phase, no push occurs and badges show OFFLINE.
Fix: add GET /api/unit/runtimes batch endpoint (returns all known
runtimes as { unit_id: UnitRuntime }) and call it in parallel with
the unit list fetch inside loadUnits(), so runtime badges are correct
immediately after page load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ops.js: remove unused `formatValue` import
- logs.js: remove `export` from internal-only `appendLog`
- state.js: fix stale comment ({ valueEl, qualityEl } → { dotEl })
- docs: rewrite both plan docs in Chinese; update dual-view-web plan to
reflect actual implementation (sigDotClass dots, loadAllEquipmentCards,
syncEquipmentButtonsForUnit, batch auto buttons in startOps)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Engine now spawns one async task per enabled unit (supervised every 10s)
- wait_phase uses sleep_until + select! for precise timing; 500ms fault-tick
runs inside each phase so fault/comm is still checked promptly
- WS UnitRuntimeChanged pushed only on state transitions, not every tick
- ControlRuntimeStore gains notify_unit/get_or_create_notify for instant
wake-up when handlers change auto_enabled or fault_locked
- UnitRuntime: remove last_tick_at, current_run/stop/distributor_elapsed_sec;
add display_acc_sec (snapshot at transition, avoids mid-cycle jitter)
- accumulated_run_sec now increments by exact run_time_sec*1000 per cycle
- unit.state_changed events no longer written to DB (too frequent)
- Frontend: show display_acc_sec instead of accumulated_run_sec
- styles: event-card flex-shrink:0 fixes text overlap under flex column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .api-drawer (1100px) so content area is ~880px instead of ~540px
- Replace scrollIntoView with apiDocContent.scrollBy to avoid scrolling
the window and collapsing the drawer layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bulk load+re-render with scroll-based pagination: load 10 items
on init, append next page when scrolling near the bottom. prependEvent
now inserts directly into DOM instead of rebuilding from state.events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When dismiss() was called on a persistent+shaking toast, the .shake CSS
rule (declared after .hiding) overrode toast-out animation. If shake had
already finished, no animationend fired and the element was never removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- POST /api/control/unit/batch-start-auto — starts auto on all enabled
units that are not fault/comm locked and not already running auto
- POST /api/control/unit/batch-stop-auto — stops auto on all units
Frontend (ops view):
- Add "全部启动" / "全部停止" buttons in the unit sidebar header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Green dot + "已连接" when socket is open; red dot + "连接断开,重连中…"
on close/error. Reconnect timer (2s) already in place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- validator: reject equipment start/stop when unit auto_enabled
- engine: fix stop_time_sec==0 causing infinite Stopped state (never starts)
- engine: call simulate_run_feedback after auto commands when SIMULATE_PLC=true
- command: extract simulate_run_feedback to shared module (was private in handler)
- web: disable Start/Stop buttons when unit auto is active; sync on WS runtime update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 运维/配置 tab switch; grid-ops / grid-config layout classes
- New ops-panel: unit sidebar + equipment card grid (REM/RUN/FLT signals)
- All equipment cards shown by default; unit click acts as filter
- Signal cells seed from point_monitor cache on render, then update via WS PointNewValue
- New log-stream-panel: SSE realtime log stream, active only in config view
- Backend: get_unit_detail now includes point_monitor (current value) in each point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents browser from caching JS/CSS modules, so frontend changes take
effect immediately on page refresh without needing hard refresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove SSE log stream (EventSource /api/logs/stream) and logView panel
- System events panel now occupies the full bottom-middle panel
- Each event renders as a single flex row: level badge, type, message, timestamp
- Remove logSource from state, logView from dom, startLogs from app bootstrap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a unit is selected in the sidebar, the create-equipment modal now
pre-fills the unit dropdown with that unit. Previously it always reset
to empty, so newly created equipment got unit_id=null and was hidden by
the unit filter after save.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add showToast() utility in api.js and a matching toast stylesheet.
apiFetch now automatically shows a toast for any 400+ response before
re-throwing, so callers can still .catch() for additional handling.
Toasts stack at the bottom-right, auto-dismiss after 4s, and support
error/warning/success/info levels via a left-border colour accent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>