Feature #1380
是由 陳國瑋 於 18 天 前更新
# Branch Admin 工程師工具(FE Implementation Spec)
## 1. 目標與範圍
在 Branch Admin 新增一個只供 Engineer 使用的「工程師工具」工作頁,讓工程師可以:
1. 對指定 CloudLink Connector 執行受控 Modbus 讀取與控制。
2. 由 Branch 向指定 Charge Point 發送 OCPP 1.6 CSMS Command。
3. 查詢每次操作的 request、response、錯誤、耗時與完整 event timeline。
4. 以 Fortune(華城)充電樁測試為主要情境,同時支援 CloudLink 與其他廠牌的 capability 顯示。
Backend 對應票為 #1381,目前已完成並部署至 Jimmy 與 A17。FE Jimmy。FE 不需要自行設計 Backend contract;本 Description、Backend OpenAPI 與附件 Plan 為實作依據。
## 2. 權限與導覽
- 新路由:`/engineer-tools`。
- 側邊欄名稱:「工程師工具」。
- 只有取得 Backend Engineer function/menu 的使用者顯示入口並可進入。
- 不要在 FE hardcode `ADMIN`、`power_user` 或只判斷 role name;這兩個既有帳號已由 Backend 加入 Engineer Role。
- FE 隱藏入口不是安全邊界,所有 API 仍由 Backend 驗證權限。
- 在既有 Connector 清單或詳情頁增加工具 icon,可 deep-link:
`/engineer-tools?connectorId={connectorId}&tab=ocpp`
- 進入 deep-link 後仍須重新向 API 取得 target 與 capability,不可直接信任 query string。
## 3. 頁面 Layout
沿用既有 dashboard shell,使用全寬工作區,不做 landing page,也不要把整個頁面包成多層 Card。
### Target Bar
頁面頂部固定顯示:
- Charge Point searchable select
- Connector searchable select
- online/offline status
- vendor
- model
- firmware version
- capability badge
規則:
- 先選 Charge Point,再載入該 Charge Point 的 catalog/capability。
- Connector 只在 connector-level command 或 Modbus command 時必填。
- 從 Connector deep-link 進入時,自動解析並預選其 Charge Point 與 Connector。
### Tabs
Target Bar 下方固定三個 tabs:
1. `Modbus`
2. `OCPP`
3. `操作紀錄`
Desktop:command list 與 form/result 使用左右兩欄。
Mobile:改為上下排列。
Command list、form、status panel 應有穩定尺寸,切換 command 或狀態時不得造成整頁寬度跳動或文字重疊。
## 4. Command Catalog 與 Capability
畫面能力唯一來源:
`GET /api/engineer/commands/catalog?chargePointId={chargePointId}`
回傳共 31 筆:19 個 OCPP commands + 12 個 Modbus operations。
每筆主要欄位:
- `protocol`:`OCPP` 或 `MODBUS`
- `command`
- `category`
- `targetLevel`:`CHARGE_POINT` 或 `CONNECTOR`
- `riskLevel`:`LOW`、`MEDIUM`、`HIGH`
- `writeOperation`
- `schema`:產生表單與前端預驗證使用的 JSON Schema
- `capability`:`VERIFIED`、`UNVERIFIED`、`UNSUPPORTED`
- `capabilityNote`
Capability UI:
- `VERIFIED`:正常啟用。
- `UNVERIFIED`:允許執行,但在送出按鈕附近顯示非阻斷警告。
- `UNSUPPORTED`:禁用執行按鈕並顯示 `capabilityNote`。
- 不要在 FE 重複維護 vendor allowlist 或自行把 `UNVERIFIED` 判定成 supported。
- FE 可以維護中文標籤、欄位順序與圖示,但 command key、schema、target、risk、capability 必須來自 API。
## 5. Modbus Tab
Modbus 只允許 CloudLink Connector。其他廠牌 catalog 會回 `UNSUPPORTED`,UI 必須禁用。
### 允許的 12 個 Operations
Read:
- `CONNECTION_TEST`
- `READ_SUPPLY_STATE`
- `READ_VOLTAGE`
- `READ_CURRENT`
- `READ_ACTIVE_POWER`
- `READ_APPARENT_POWER`
- `READ_POWER_FACTOR`
- `READ_TOTAL_ENERGY`
- `READ_BALANCE`
- `READ_METER_ID`
Write:
- `SET_SUPPLY_STATE`
- `SET_METER_ID`
UI 分組:
- 連線與狀態
- 電力讀值
- 控制
禁止提供:
- raw Modbus function code
- raw register address/value
- raw payload
- 清除歷史資料
- Browser 直接連 charge_point API
Read operation 的 `parameters` 必須是空 object。
`SET_SUPPLY_STATE`:
```json
{
"connectorId": "CP001-001",
"operation": "SET_SUPPLY_STATE",
"parameters": { "enabled": true }
}
```
`SET_METER_ID`:
```json
{
"connectorId": "CP001-001",
"operation": "SET_METER_ID",
"parameters": { "meterId": 1 }
}
```
`meterId` 範圍為 1–255。
文案使用「供電控制」;可以用次要說明標示底層由智慧電表 Test Mode 執行,但不要新增獨立 Test Mode command。
確認規則:
- Read command:直接執行,不顯示確認。
- `SET_SUPPLY_STATE`、`SET_METER_ID`:各顯示一次簡短確認。
- 不要求填寫「操作原因」。
- 不做額外第二次確認。
## 6. OCPP Tab
提供以下全部 19 個 OCPP 1.6 CSMS → Charge Point commands:
- `RemoteStartTransaction`
- `RemoteStopTransaction`
- `Reset`
- `ChangeAvailability`
- `UnlockConnector`
- `TriggerMessage`
- `DataTransfer`
- `GetConfiguration`
- `ChangeConfiguration`
- `ClearCache`
- `ReserveNow`
- `CancelReservation`
- `SetChargingProfile`
- `ClearChargingProfile`
- `GetCompositeSchedule`
- `GetDiagnostics`
- `UpdateFirmware`
- `GetLocalListVersion`
- `SendLocalList`
功能要求:
- command 依 catalog `category` 分組,並提供搜尋。
- 使用 segmented control 切換「表單」與「JSON」。
- 兩種模式共用同一份 payload state,切換模式不能丟失資料。
- 表單依 catalog `schema` 產生或映射。
- schema validation error 顯示到 JSON path/欄位。
- Backend 仍會執行同一份 schema validation,FE validation 只是提早提示。
- `Reset` 送出前顯示一次簡短確認。
- 其他 OCPP command 不增加確認。
- `GetDiagnostics`、`UpdateFirmware` 的 HTTP/HTTPS location 由 Engineer 輸入;FE 不需實作檔案上傳。
OCPP request:
```json
{
"chargePointId": "CP001",
"connectorId": null,
"command": "TriggerMessage",
"payload": {
"requestedMessage": "Heartbeat"
}
}
```
若 command 為 connector-level,送出 `connectorId`;Backend 會驗證該 Connector 是否屬於選定 Charge Point,並在需要時補入 OCPP numeric connectorId。
## 7. Backend API Contract
### 7.1 Catalog
`GET /api/engineer/commands/catalog?chargePointId={chargePointId}`
用途:取得 31-command catalog、schema、risk、target 與 capability。
### 7.2 Execute OCPP
`POST /api/engineer/ocpp/commands`
Body:
```json
{
"chargePointId": "CP001",
"connectorId": null,
"command": "TriggerMessage",
"payload": {
"requestedMessage": "Heartbeat"
}
}
```
### 7.3 Execute Modbus
`POST /api/engineer/modbus/commands`
Body:
```json
{
"connectorId": "CP001-001",
"operation": "READ_SUPPLY_STATE",
"parameters": {}
}
```
### 7.4 Command Detail
`GET /api/engineer/commands/{commandLogId}`
主要欄位:
- target snapshot:chargePointId、connectorId、connectorNo、vendor、model、firmwareVersion
- protocol、command、capability、operatorId
- correlationId、ocppUniqueId
- requestPayload、maskedRequestPayload、responsePayload
- state、protocolStatus
- errorCode、errorDescription、errorDetails
- createdAt、sentAt、respondedAt、completedAt、elapsedMs
- events
顯示規則:
- 預設顯示 `maskedRequestPayload`,不要預設展開 raw `requestPayload`。
- event timeline 依 `sequenceNo` 顯示。
- correlationId、ocppUniqueId、commandLogId 提供 copy icon。
- 長 JSON 與 ID 必須 wrap/scroll,不得撐破 drawer。
### 7.5 Command Search
`POST /api/engineer/commands/search`
Body 所有 filter 都可選:
```json
{
"from": null,
"to": null,
"protocol": null,
"command": null,
"chargePointId": null,
"connectorId": null,
"operatorId": null,
"state": null,
"correlationId": null,
"ocppUniqueId": null,
"page": 0,
"size": 20
}
```
規則:
- `page` 從 0 起算。
- `size` 最大 100。
- response 是 Spring Page,使用 `content`、`totalElements`、`totalPages`、`number`、`size`。
## 8. 202 Response 與 Polling
兩支 execute API 成功時回 HTTP 202:
```json
{
"commandLogId": "uuid",
"correlationId": "uuid",
"ocppUniqueId": "uuid",
"state": "SENT",
"capability": "UNVERIFIED",
"createdAt": "2026-07-14T22:43:17.151298"
}
```
核心規則:
- HTTP 202/`SENT` 只表示 Branch 已把 OCPP CALL 送入 WebSocket。
- 這不代表 Charge Point 已執行完成。
- FE 必須用 `commandLogId` 輪詢 detail API。
- 建議畫面可見時每 1 秒輪詢一次。
- 使用者離開頁面不會取消已送出的 command。
- 快速設備可能在 POST 回來前已完成;FE 要先檢查 create response 的 `state`,若已是 terminal 就不需等待。
Non-terminal states:
- `CREATED`
- `VALIDATED`
- `DISPATCHING`
- `SENT`
- `RUNNING`
Terminal states,收到後停止 polling:
- `COMPLETED`
- `REJECTED`
- `CALL_ERROR`
- `SEND_FAILED`
- `FAILED`
- `TIMEOUT`
- `STATUS_UNKNOWN`
`GetDiagnostics`、`UpdateFirmware` 可能先進入 `RUNNING`,後續 progress 由 detail events 顯示。
## 9. 操作紀錄 Tab
Filters:
- 時間範圍
- protocol
- command
- Charge Point
- Connector
- operator
- state/result
- correlationId
- ocppUniqueId
Table columns:
- 執行時間
- protocol/command
- target
- operator
- state/protocol status
- elapsed
- correlationId
點擊 row 開啟 detail drawer,顯示:
- target/device snapshot
- masked request/response JSON
- CALLERROR 或 local error
- command event timeline
- commandLogId、correlationId、ocppUniqueId
## 10. HTTP Error Handling
- `400`:payload/schema/target/Modbus parameters 錯誤。顯示 Backend message,schema error 儘量對應到欄位。
- `401`:未登入;沿用既有 refresh/sign-out middleware。
- `403`:沒有 Engineer function 權限。
- `404`:Charge Point、Connector 或 operation 不存在。
- `409`:同一 Charge Point 已有進行中的 `GetDiagnostics` 或 `UpdateFirmware`。
- `422`:capability 為 unsupported,或對非 CloudLink Connector 執行 Modbus。
- `503`:設備 offline 或 WebSocket dispatch 失敗。錯誤 message 會包含已建立的 `commandLogId`,UI 應提供前往 detail 的入口。
## 11. React Implementation Guidance
先在 `react/branch` 更新 Backend types:
```bash
# .env.local 的 NEXT_PUBLIC_API_URL 指向含 #1381 的 Branch API
yarn openapi:refresh
```
Backend OpenAPI tag:`[B135]工程師工具`。
必須:
- 使用既有 `lib/query-api/openapi.ts` 的 `$api`/`fetchClient`。
- 沿用既有 JWT middleware、React Query、React Hook Form、Zod、shadcn、Sonner/SweetAlert2 pattern。
- 不要另建未帶 auth/refresh middleware 的 API client。
- 不要手改 `lib/data/ems.json` 或 `lib/data/emsapi.ts`。
- 在 `config/menus.ts` 註冊 route/menu,但顯示權限仍以 Backend function/menu 為準。
建議 feature 結構:
```text
app/(dashboard)/engineer-tools/
├── page.tsx
├── components/
│ ├── target-bar.tsx
│ ├── command-list.tsx
│ ├── modbus-command-form.tsx
│ ├── ocpp-command-form.tsx
│ ├── command-status-panel.tsx
│ ├── operation-table.tsx
│ └── operation-detail-drawer.tsx
├── hooks/
│ └── engineer-command-hooks.ts
└── lib/
├── schema.ts
└── presentation.ts
```
這不是一般 CRUD,建議以 typed `$api.useQuery/useMutation` 建立專用 hooks,不必勉強套完整 CRUD factory。
## 12. 驗收條件
1. 只有取得 Engineer function 的使用者看得到並能進入工程師工具。
2. Charge Point/Connector target 可選取,deep-link 可正確預選。
3. Catalog 回傳的 31 commands 都能正確分組與顯示 capability。
4. Modbus 不提供任何 raw register/function/payload 入口。
5. 19 個 OCPP commands 都可由 schema 建立 payload,表單/JSON 模式切換不丟資料。
6. `UNVERIFIED` 可送出且有警告;`UNSUPPORTED` 禁用並顯示原因。
7. Command POST 後顯示 operation panel,正確輪詢至 terminal state。
8. `REJECTED`、`CALL_ERROR`、`SEND_FAILED`、`TIMEOUT` 不得顯示為成功。
9. 操作紀錄支援 server-side filter/pagination,detail drawer 顯示 masked payload 與完整 timeline。
10. Reset、供電控制、Meter ID 修改各只有一次簡短確認;不要求操作原因。
11. Desktop/mobile 無文字重疊、截斷或切換造成的 layout shift。
12. 完成後通過 `yarn lint` 與 `yarn build`。
## 13. Backend 現況與測試限制
Jimmy 已驗證:
- Engineer Role login
- 31-command catalog
- 真實 CP001 `READ_SUPPLY_STATE` Modbus E2E
- OCPP `TriggerMessage` Accepted/Rejected
- search/detail
- 未登入 401
- raw Modbus operation 400
Fortune(華城)設備在 Jimmy 2026-07-14 測試時離線;A17 於 2026-07-16 已通過 `TriggerMessage(Heartbeat)` 低風險實機 E2E。這只證明基礎 測試時離線,因此其 19 個 OCPP 通訊路徑可用,尚未完成 19-command capability matrix,因此 FE 仍必須將其 capability 呈現為 `UNVERIFIED` 與非阻斷警告,不能顯示為 目前維持 `UNVERIFIED`。FE 必須呈現非阻斷警告,不能顯示為 `VERIFIED`。
Backend E2E 報告:`test_report/20260714_001_redmine_1381_engineer_command_e2e.md`。
## 14. 附件與關聯票
- Backend:#1381(Resolved)
- 附件:`branch-admin-engineer-command-plan.md`
- 附件包含完整產品決策、資料表、OCPP schema、測試矩陣與後續 Fortune 實機驗證計畫。
若本 Description 與既有 Journal 有差異,以本 Description 與最新 Backend OpenAPI 為準;Journal 只作進度歷史,不作需求拼接來源。
返回