UX Spec

Manage Device Access by Site

Amy TangAug 11 2026
Open Spec Changes
manage-device-access-role-improvement
formalize-rebac-permission-model
Prototype
manage-device-access-role-improvement-demo.html(edit a user's device access across the Site tree)

Visual basis: vortex-1.0 — verify behavior / copy / structure, not pixel style.

Context

Why we're building this

VORTEX grants device access per user, per device — an Admin sets which roles a user holds on the org's devices. Two structural shifts change how: device grouping expands from a single-level camera group to a Sites tree (a Site with up to four Area levels), and every device now belongs to a Site.

So access is managed against that Site tree: check the sites or areas a user should keep, set a role, and review before submitting.

What value it brings

Manage device access by Site — from whole org to a single device.

  • Batch — the whole org, or selected sites / areas
  • Site-first by design — the Site / area is the primary unit; single-device access sits one level down, toward VORTEX's future site permissions
  • Safe — every change, including removals, is reviewable before submit
Section 01

對話框結構與進入點

Manage Device Access by Site 是一個模態對話框,讓管理者(Admin / Owner)設定單一使用者對整個組織裝置的存取角色。固定尺寸 1116 × 747,分三塊。

進入點與編輯模型 Entry & edit model

  • 從哪進:Portal › Users › 某使用者的 detail page › Manage device access → 開啟此對話框。
  • 誰是唯讀、誰是編輯:外層 user detail page 是唯讀瀏覽該使用者目前的存取;這個對話框是唯一的編輯介面
  • 開啟時:預載該使用者目前的有效存取——自己直屬裝置保有存取的節點 pre-check(兩態 per-node,見 Section 02)。對話框顯示的值與外層頁面一致。
  • 送出時:只送出這次 session 的 diff(變更集),不是整份重寫。
mda-has-value
主 mode(Has value)— 左 sidemenu 站點樹(兩態 checkbox、選取計數在搜尋下方) + 右角色面板(標題列為裝置類型 Tab,列名為 breadcrumb);開啟時預載現況,右面板僅列勾選節點(Figma key screen)

三個區塊 Regions

區塊 內容
Header 70px 標題「Manage Device Access by Site」 + 右上關閉
Body 607px 四種 mode 之一(見下);padding 8px 40px、雙欄間距 40px
Footer 70px 右下「Save changes」 + 「Cancel」(各 150px);drill-in mode 隱藏 footer

Body 的四種 mode Four modes

Mode 觸發 版面
主 mode 開啟預設 左 280px sidemenu + 右 716px 面板
All sites 批次 點 sidemenu 上「All sites」的 checkbox——未勾或半選 → 勾選皆進批次(標準三態循環,見 Section 04) 左 sidemenu 不變 + 右單一 batch row(見 Section 04)
Drill-in 點右面板某列的 chevron > 整個 body 換成單欄裝置清單(見 Section 05)
Review Changes 點右面板上的「N changes」 整個 body 換成單欄 diff 清單(見 Section 07)

兩種起始狀態 Initial states

  • Empty(從未設定過):右面板顯示空狀態「No Device Access」 + 「Select sites from the side menu to manage device access.」。裝置類型 Tab 列在 Empty 下仍然保留——空狀態插圖置中於 Tab 列以下的區域,不會連 Tab 一起被取代(見 Section 03)。明確設為 No Access 算「已設定」——它讓對話框以 Has value 起始態開啟(初始狀態偵測),但該節點本身依兩態推導為未勾、不在右面板列出(見 Section 02)。
  • Has value(已有設定):sidemenu 依各節點自身直屬裝置是否保有存取 pre-check(兩態 per-node),每列顯示目前角色,與外層頁面一致。
mda-empty
起始 Empty — 從未設定過的使用者,右面板顯示 No Device Access;裝置類型 Tab 列仍保留(Figma key screen)

貫穿全對話框的原則 Key principles

  • checkbox = 該 site / area「自己的直屬裝置」是否保有存取(兩態 per-node、隨裝置狀態即時推導,見 Section 02),不是「我正在編輯誰」,也不代表 subtree 聚合。
  • 站點層 dropdown 沒有 No Access;移除存取靠 sidemenu uncheck(見 Section 03)。No Access 只在裝置層(drill-in)才可選。
  • 這三種破壞性編輯不用 confirm 擋:uncheck 保有存取的 site、drill-in 把直屬裝置全設 No Access、Multiple roles 壓平——但一律記進 diff、送出前可檢視(見 Section 07)。會即時跳 modal 的例外:「撤銷會牽動 linked 裝置」的 link-confirm(見 Section 06),以及 All sites 批次的套用 confirm 與取消時一律出現的 Reset confirm(見 Section 04)。
  • 變更清單必須能重建最終狀態:讀 Review Changes 的人,從已儲存狀態出發套用清單上的每一筆(範圍重疊時取最具體的一筆),應該得出真正會被送出的結果。這是清單的驗收條件——粗粒度的一筆(例如 All sites 重設)之後若有更細的例外,那個例外一定要單獨列出(見 Section 07)。
  • 組裝了既有 pattern:sidemenu 站點樹沿用 Design Pattern: Tree View;search / filter 慣例沿用 Design Pattern: Filter Combo

驗收條件 QA · Given-When-Then

  • Given 從 user detail page 開啟,Then 對話框以編輯模式開啟並預載該使用者目前存取;外層頁面維持唯讀。
  • Given 使用者已有設定,Then 自身直屬裝置保有存取的節點 pre-check(兩態,無半選;祖先不因子孫連動),各列角色與外層一致。
  • Given 使用者從未設定,Then 顯示 Empty 空狀態「No Device Access」。
  • Given 某使用者僅有「明確設為 No Access」的節點,Then 對話框以 Has value 起始態開啟(不顯示 Empty 空狀態),該節點 checkbox 為未勾、不在右面板列出。

TODO / 未解問題

前一版列的兩項待決已於 2026-08-11 裁定,均改寫為結論、不再是未解問題:drill-in 沒有裝置類型 Tab 是確定的設計(見 Section 05),「勾選但未指派角色」不加送出前提醒(見 Section 08)。

前一版列的兩項 Figma 待補已於 2026-08-10 更新的 key screens 中解決:批次 mode 不再需要「All sites: 」標題前綴(右面板標題整體移除,見 Section 04),且樹為部分勾選時 All sites 列已正確畫成半選。

Section 02

側邊站點樹

左側 280px 的 sidemenu,是選取「要管理哪些 site / area」的地方。由上到下:搜尋、選取計數、All sites 列、站點樹(含 checkbox)。

本節聚焦 sidemenu 的規則;整體畫面見 Section 01 的主 mode 截圖。站點樹本身沿用 Design Pattern: Tree View

搜尋 Search

沿用 Filter Combo 的搜尋規則:

  • 輸入 2 個字以上、300ms debounce 觸發過濾。
  • 命中節點與其所有祖先顯示且可選;無關分支隱藏。
  • 命中文字 highlight(primary 底 + 白字)。
  • 無結果:顯示「No Matches Found / Please try adjusting filters or use different keywords.」。
  • 清除搜尋 → 還原搜尋前的展開狀態。

選取計數 Selection count(40px)

搜尋框與 All sites 列之間有一行選取計數,primary/default 色、14px Semibold:

  • 格式 N sites, M areas selected——其一為 0 則略去該段(只有 site 時為 2 sites selected),單複數依數量(1 site / 1 area)。
  • 零選取顯示 0 selected,並改為 text/03(不再是強調色)。
  • 計數算的是 sidemenu 上勾選的節點數(level 1 = sites、level 2–5 = areas),不是「有權限的節點數」——剛勾選還沒配角色的節點也算在內。
  • 與裝置類型 Tab 無關(不依類型過濾)。

這一行在 v1.x 位於右面板標題列;現在移到 sidemenu,因為它描述的是左邊的選取結果,而右面板標題列改放裝置類型 Tab(見 Section 03)。格式與 Design Pattern: Filter Combo 的 site 計數同一套規則,含 selected 後綴。

All sites 列 All sites row

  • checkbox(未勾 / 勾選 / 半選 三態):進入 All sites 批次 mode 的唯一入口(見 Section 04)。未勾或半選 → 點 → 勾選進入批次;勾選 → 點 → 未勾一律先跳 Reset confirm(見 Section 04)。
  • 列名稱不可點——避免與 checkbox 語意衝突(checkbox 同時管 mode 與 selection,label 不參與)。
  • 右側雙箭頭按鈕 = Collapse all(收合到 level 1);再點一次還原全展開。
  • 批次 mode 中,整列有選中底色作為「正在批次 mode」的視覺指示——樣式沿用站點樹節點的 Active 慣例(surface/04 底 + 8px 圓角,見 Design Pattern: Tree View)。

站點樹 checkbox 變體 Tree with checkbox

在 Tree View 既有規格上,每個節點於 chevron 後加一個 20×20 checkbox:

  • checkbox:只動 selection,不影響展開。在批次 mode 中也照常 toggle(不是特例)——selection 因此不再是「全選」,批次 mode 隨之自動退出(見 Section 04)。
  • chevron:只 toggle 展開,不動 selection。
  • 列名稱兩種 mode 都是 no-op(selection 已由 checkbox 表達、展開由 chevron 表達)。
  • 每一列都顯示 chevron、都可展開——包含當下沒有子項的 Area。這不是本對話框的特例:Design Pattern: Tree View 的箭頭規則是依節點種類而非依當下有無子項,而 Site / Area 是容器種類(比照資料夾邏輯),所以一律有箭頭;真正沒有箭頭的是 View / Floorplans sidemenu 裡的裝置節點。規則以該 deck 為權威,此處不重列。
  • 展開一個沒有子項的 Area 時不顯示任何列,展開後直接是下一個同層節點。這同樣不是本對話框的特例:空提示行在 Design Pattern: Tree Viewprofile 層級的差異——site tree(本對話框與 Site Management 主樹)不顯示,View / Floorplans sidemenu 才顯示 No cameras / No floor plans。規則以該 deck 的 Section 01 邊界表為權威。
  • 預設全部展開到最深層(含 Empty 與 Has value 起始態)。

checkbox 兩態的意義 Two-state, per-node

站點樹 checkbox 採兩態 per-node(與 Design Pattern: Filter Combo 的 Sites 選取同一套規則;2026-07-16 定案)。每個 checkbox 只回答一個問題:這個節點自己的直屬裝置是否保有存取——沒有半選,也沒有任何父子聚合語意。

站點樹節點不再有半選。整個 sidemenu 唯一保留三態的是 All sites 列——它是批次 mode 的開關 + 全組織存取的摘要,不屬於樹的選取元件(見 Section 04)。

狀態 意義
勾選 該節點自己的直屬裝置至少一台保有存取(任一非 No Access 角色);或剛勾選、角色待指派(右面板顯示「Select role」)
未勾 該節點直屬裝置全部 No Access,或該節點沒有直屬裝置。不代表任何 subtree 資訊——子孫各自獨立判斷

操作行為:

操作 結果
未勾 → 點 → 勾選 該節點 + 整 subtree 全部勾選(cascade;各列角色待指派)
勾選 → 點 → 未勾 該節點 + 整 subtree 全部設為 No Access(對稱 cascade,進 diff)
子層獨立勾 / 取消 只影響自己;祖先永不自動連動(勾滿全部子層,父層也不會自動打勾)

checkbox 即時反映裝置狀態 Live derivation

checkbox 顯示 = 「即時推導」或「勾選待配」二者之一,隨裝置資料即時更新:

  • drill-in 把某節點的直屬裝置全部改為 No Access → 該節點自動變未勾,右面板該列消失。這只是顯示層推導,不產生額外的 diff(裝置層變更已各自記錄)。
  • 之後任何操作(含 Review Changes 改回角色)讓任一直屬裝置恢復存取 → 自動變回勾選。
  • 只清一部分(還有裝置保有存取)→ 維持勾選,該列聚合顯示「Multiple roles」(見 Section 03)。
  • 「剛勾選、還沒配角色」的節點維持勾選直到被 uncheck;重開對話框時依裝置現況重新推導。

diff 何時產生 When a diff is recorded

  • 未勾 → 勾選:不直接算 diff。因為還沒選具體角色——右面板該列 dropdown 顯示「Select role」placeholder;等使用者在右面板 / drill-in 選了角色才算 1 筆 diff。
  • 勾選 → 未勾:每個裝置類型至多 1 筆 diff,條件是該 subtree 的初始(已儲存)狀態中,該類型存在非 No Access 裝置——剛勾選還沒配角色就 uncheck、或初始即全 No Access 的 subtree,不記 diff(沒有東西被撤)。subtree cascade 不獨立列。
  • uncheck 會一併清算 subtree 內本次 session 已記錄的節點 / 裝置變更(它們已被 uncheck 取代)——診斷原則:diff 帳本永遠與畫面狀態一致,不會出現互相抵銷的兩筆。
  • 若 subtree 內裝置連動到其他 site / area 的裝置且會一併失去存取 → 額外跳 link-confirm modal(見 Section 06)。
  • uncheck 之後再對範圍內的節點賦權,一定會另記一筆——即使選的角色剛好就是已儲存的原值。因為那筆 uncheck 的範圍涵蓋整個 subtree,若不另記,讀變更清單的人會以為該節點也跟著變成 No Access。反過來,已被涵蓋的移除不會重複記:粗粒度那筆已經表達了同一個轉換(見 Section 07)。

驗收條件 QA · Given-When-Then

  • Given 勾選一個原本未勾的節點,Then 該節點與 subtree 加入 selection,但不改任何角色、不記 diff;右面板該列顯示「Select role」。
  • Given 勾滿某父層的全部子層,Then 父層 checkbox 維持原狀不自動打勾(per-node,祖先永不連動)。
  • Given uncheck 一個保有存取的 site,Then 整 subtree 設為 No Access 並記進 diff(不跳 blocking confirm;若牽動 linked 裝置才跳 link-confirm);本次 session 在該 subtree 內已記的變更一併清算。
  • Given 勾選節點、配了角色後再 uncheck 它(初始為 Empty),Then N changes 歸零、Save changes 停用(淨變化為零)。
  • Given drill-in 把某節點直屬裝置全部設為 No Access 後返回,Then 該節點 checkbox 自動變未勾、右面板該列消失,且沒有額外的 uncheck diff。
  • Given 搜尋輸入 2 字以上,Then 命中節點與祖先顯示、highlight,無關分支隱藏。
  • Given 勾選 2 個 site 與 10 個 area,Then 計數列顯示 2 sites, 10 areas selected;全部取消後顯示 0 selected
  • Given 一個沒有子項的 Area,Then 它仍顯示 chevron 且可點(規則以 Tree View deck 為準);展開後不顯示任何列,直接接續下一個同層節點。
  • Given uncheck 一個 site 後,再把該 site 底下某個 area 的角色設回它原本已儲存的角色,Then 變更清單仍會列出這一筆——記帳基準是 uncheck 之後的有效狀態(No Access),不是原始儲存值。

前一版此處掛著「uncheck site 後把 area 設回原角色仍要記帳」這一項。它已實作,並由回歸套件的 effective-baseline 情境釘住,因此移到上方驗收條件,不再是未解問題。

Section 03

右側面板(主 mode)

右側 716px 是設定角色的地方。由上到下:標題列、批次列、以及每個被選節點各一列。

標題列 Header row(40px)

標題列沒有標題文字,只有兩個元素:

  • 裝置類型 Tab(Camera / Other devices,見下節)。
  • N changes — 只在有變更(N > 0)時顯示,點下去進 Review Changes(見 Section 07);單數為 1 change

v1.x 的計數 N sites, M areas 原本在此列左側,現已移到 sidemenu(見 Section 02)——它描述的是左邊的選取結果。搬移後也加回了 selected 後綴,與 Filter Combo 的共用格式一致;先前「此處不帶後綴」的差異不再存在。

Empty 狀態下這一列仍然保留——空狀態插圖置中於 Tab 列以下的區域,不會連 Tab 一起被取代(見下方「Empty 狀態」)。

裝置類型 Tab Device type tabs

視覺與互動沿用 filter-combo 原型的 tab item 元件(樣式逐字取自該原型,非本節自訂)。需注意:Design Pattern: Filter Combo 這份 deck 目前沒有 tab item 的規格章節——該元件的權威定義在 filter-combo 的原型與其 design doc,不在 deck 裡。本節不另定義樣式:

  • 兩個固定 Tab:Camera(預設)/ Other devices直接點擊即切換,不展開任何選單。
  • 後者是一個 UI 分組標籤,不是裝置種類;hover 出 tooltip 說明它包含哪些:Other devices include NVR, Bridge, Network Speaker, PoE Switch, and VSS.(與 Review Changes 的 Device type 欄共用同一份文案,見 Section 07)。
  • 切換時,每列 dropdown 重新計算「該節點直屬該類型裝置的聚合角色」;列本身不變(顯示哪些列由 selection 決定,與類型無關)。
  • sidemenu 的選取計數與 Tab 無關(不依類型過濾)。

v1.x 這裡是標題列右側的 filter chip,且 chip 上顯示逐字列舉的 NVR/Bridge/Speaker/PoE/VSS。改為 Tab 後,列舉字串只出現在 tooltip 裡,並改用 glossary 的完整名稱(Network SpeakerPoE Switch)。

mda-camera-batch
主 mode,`Camera` Tab — 站點層角色 dropdown 與批次列(Figma key screen)
mda-other-batch
主 mode,`Other devices` Tab — 各列聚合依 Other 類型重算,角色選項僅 Viewer / Supervisor(Figma key screen)

批次列 Batch row(60px)

  • 「Apply to all selected」 + 一個 200×32 dropdown。
  • 選一個角色 → 套用到目前面板顯示的所有 site / area 列直屬的該類型裝置(每列各記 1 筆 diff)。
  • 範圍有限(只限顯示中的 sites),所以不需要 confirm modal——這點與 All sites 全 org 批次不同(見 Section 04)。

每個 site / area 列 Site/Area row(60px)

元件 規格
名稱 完整路徑 breadcrumb(Site > … > 該層),套用 Design Pattern: Sites Breadcrumbs 的漸進式截斷規則,見下節
Dropdown 200×32,顯示該節點直屬該類型裝置的聚合角色
Chevron > 進入 drill-in(見 Section 05)

名稱為 breadcrumb Full path, progressively truncated

列名顯示完整路徑而非只有該層名稱。理由:不同分支常有同名節點(Floor 1Lobby),而這一列就是實際指派權限的地方——挑錯節點的代價最高。sidemenu 雖然呈現階層,但只對游標所在的那一個節點有效,不是正在編輯的這一列。

  • 截斷規則、優先序、折疊符的畫法一律以 Sites Breadcrumbs 為準(該 deck 是權威),此處只記錄套用範圍與差異。
  • 連續被折疊的層合併成單一折疊符,不是每層一個。
  • hover breadcrumb 上的任一處(含各層、分隔符 >、片段之間的空隙,以及該區域右側的留白)→ 顯示完整路徑 tooltip;內容一律是整條路徑,不會因游標落在哪一段而不同。完全沒有被縮寫時不顯示(顯示與畫面相同的內容只是噪音)。
  • Review Changes 內有兩處刻意不套 breadcrumb(都維持只顯示最末層 + hover 看路徑):Site / Area 欄(與 Device type 欄共用寬度)與 device 群組的 sub-header(見 Section 07)。

上面這條 hover 規則已是 Sites Breadcrumbs 的共用規則(該 deck Section 01),不是本對話框的放寬,所以此處不再另行說明差異。

顯示哪些列 = sidemenu 上所有勾選節點,展平、按 sidemenu 順序排列(site 與 area 穿插,不分組)。drill-in 把某列的直屬裝置全部設為 No Access 時,該節點自動變未勾、該列隨之消失(見 Section 02 的即時推導)。

dropdown 的角色選項 Role options

filter 選項
Camera Viewer / Supervisor / 各 Custom role(N 個,01 / 02 / 03… 僅示意)
Other devices Viewer / Supervisor

站點層沒有 No Access——移除存取靠 sidemenu uncheck(見 Section 02);No Access 只在裝置層(drill-in)才可選。只有 Camera 有 Custom role。

mda-row-dropdown
站點層角色 dropdown 展開 — Viewer / Supervisor / Custom role(Camera),沒有 No Access

三種特殊顯示 Aggregate display

  • Select role(placeholder,灰字):該節點直屬該類型裝置全為 No Access(典型情境:未勾 → 勾選 後、使用者還沒選角色)。站點層 dropdown 永不顯示「No Access」字樣——移除存取靠 sidemenu uncheck。
  • Multiple roles:該節點直屬該類型裝置的角色不一致時的聚合顯示值,包含「部分裝置 No Access + 部分有角色」的混合——節點內部有存取缺口時,在此層就看得到。選一個具體值會把該節點直屬裝置壓平成新角色,不遞迴子層 area,記 1 筆 diff。
  • (em-dash,disabled):該節點直屬沒有該類型裝置(null 聚合,別於 No Access 與 Multiple roles)——dropdown 停用、不可開;但 chevron 仍可 drill-in。批次列在此情況同樣停用。 hover 停用的 dropdown 會說明原因:Camera Tab 下為 No cameras here.Other devices Tab 下為 No other devices here.。停用狀態只表達「不能用」,不表達「為什麼」,所以補上這句;文案與 drill-in 的 Area header 共用同一份實作(見 Section 05)。 批次列刻意不做這個 tooltip——它停用的原因是「目前顯示的所有列都沒有該類型裝置」,語意與單一節點不同,而且要描述它得引進「selected sites」這個新的文案概念;發生率也低。這是刻意的取捨,不是遺漏。
mda-n-changes
有變更時 — 標題列右側出現可點的變更計數,進入 Review Changes;Save changes 轉為可用(Figma key screen)

Empty 狀態 Empty

sidemenu 全 未勾 時,右面板換成空狀態:「No Device Access」 + 「Select sites from the side menu to manage device access.」。

Tab 列在此狀態下仍然保留——只有 Tab 列以下的區域被空狀態取代,插圖與文字垂直居中於該區域。理由:Tab 是這個面板的持續控制項,不是內容的一部分;把它一起藏掉會讓使用者以為類型切換也不見了。此時 sidemenu 的計數列顯示 0 selected(見 Section 02)。

驗收條件 QA · Given-When-Then

  • Given 開啟站點層 dropdown,Then 選項為 Viewer / Supervisor( + Camera 才有 Custom role),沒有 No Access
  • Given 某節點直屬裝置角色不一致(含部分 No Access 的混合),Then 顯示 Multiple roles;選具體值只壓平該節點直屬裝置,不動子層 area。
  • Given 剛勾選、尚未配角色的節點,Then 該列 dropdown 顯示「Select role」placeholder,永不顯示「No Access」。
  • Given 某節點沒有該類型裝置,Then dropdown 為 disabled,但仍可 drill-in;hover 該 dropdown 顯示 No cameras here.(Camera Tab)或 No other devices here.(Other devices Tab)。
  • Given hover 批次列的停用 dropdown,Then 不顯示 tooltip(刻意不做,見上)。
  • Given 切換裝置類型 Tab,Then 每列 dropdown 依新類型重算 enabled / disabled 與聚合角色,顯示哪些列不變。
  • Given hover Other devices Tab,Then 顯示 Other devices include NVR, Bridge, Network Speaker, PoE Switch, and VSS.
  • Given sidemenu 沒有任何勾選,Then 右面板顯示空狀態,但裝置類型 Tab 列仍在、仍可切換。
  • Given 某列的節點位於深層路徑,Then 列名顯示完整路徑 breadcrumb 並依可用寬度漸進式截斷;hover 該 breadcrumb 任一處都顯示完整路徑。
  • Given 某列的路徑完整顯示得下(沒有任何縮寫),Then hover 不顯示 tooltip。
  • Given 用批次列選角色,Then 套用到顯示中所有列的直屬該類型裝置(各記 1 筆),不跳 confirm。

TODO / 未解問題

  • 「Multiple roles」作為系統保留字、不可被設為 Custom role name 的規則,RD 目前尚未實作;列為未來 TODO,實作後再補回描述與驗收條件。
Section 04

All sites 批次

點 sidemenu「All sites」的 checkbox(未勾或半選 → 勾選,標準三態循環)→ 右面板換成單一批次列,一次套用一個角色到全組織該類型裝置。進入本身無破壞性——不動任何角色(循環說明見下方「循環為什麼是 半選 → 勾選 → 未勾」)。

mda-all-sites
All sites 批次 mode — 標題列只有 Tab 與變更計數(不再有標題文字),單一「Apply to all sites」列,一次套用一個角色到全組織(prototype)

進入批次 mode Enter

  • 進入 = 點 All sites checkbox 從未勾或半選 → 勾選:sidemenu 全部變 勾選、右面板換成單一批次列(列名為 Apply to all sites)。此步驟不改任何角色——「覆蓋全 org」的守門在套用時(見下節),不在入口。
  • 右面板沒有標題文字:批次 mode 的識別改由三件事共同表達——sidemenu 的 All sites 列有選中底色、sidemenu 計數列顯示全 org 的數字、批次列名為 Apply to all sites(主 mode 是 Apply to all selected)。
  • 單一列的 dropdown 顯示全 org 該類型裝置的聚合角色(全同→該角色、不一致→Multiple roles、全為 No Access→「Select role」placeholder、沒裝置→ disabled)。

v1.x 進入時會快照當前狀態(角色 / 來源 / 勾選 / 變更清單 / 前一個 mode),退出可完整還原。快照機制已整組移除——它是一個看不見的隱藏狀態,同一個點擊會依「有沒有快照」產生兩種完全不同的結果(靜默還原 vs 全部清空),使用者無法預測自己會得到哪一種。移除後每個狀態的點擊結果只有一種。v1.x 的標題 All sites: N sites, M areas 也隨右面板標題整體移除。

套用角色必須 confirm Apply needs confirmation

因為會覆蓋整個組織的該類型裝置,選角色時先跳 confirm modal(bounded 的主 mode 批次列則不需要):

元素 內容
標題 Apply role to all Cameras?(Camera)/ 對應 Other 類型的標題
內容 說明此舉會覆蓋全 org 該類型裝置、取代先前 per-site / per-device 設定;並提示送出前可逐筆檢視調整
主鈕 Apply to all
次鈕 Cancel
  • 確認 → 套用到全 org 該類型裝置,記 1 筆 diff(All sites → 角色)。
  • 取消 → 回 All sites mode、不套用。
  • Empty(該類型全是 No Access)或該類型無裝置 → 不跳 confirm(沒東西可覆蓋);後者不記 diff。
mda-all-sites-confirm
「Apply role to all Cameras?」— 進入 All sites 批次後選 Camera 角色,套用前的覆蓋全 org 確認(prototype)

取消 All sites:一律先確認 Uncheck always confirms

點 All sites checkbox 勾選 → 未勾時,只要當下組織內還有任何裝置保有存取,一律Reset access for all sites? confirm——不再依「有沒有快照」分岔成兩種結果。

元素 內容
標題 Reset access for all sites?
內容第 1 句 Every device in the organization will be set to No Access. This clears all per-site and per-device settings.
內容第 2 句(只在已有變更時出現) This also discards your N unsaved changes.——N 為 1 時省略數字(your unsaved change),N 為 0 時整句不顯示;數字與標題列的變更計數同一個值
內容第 3 句 You can review the reset before saving.
主鈕 Reset access
次鈕 Cancel
  • 確認後的結果可以送出:全 org 設為 No Access,並記進變更清單——至多 2 筆(Camera / Other 各一筆),依已儲存狀態判斷該類型是否需要記帳(初始即全 No Access 的類型記 0 筆)。Site / Area 欄顯示 All sites,且為唯讀(見 Section 07)。本次 session 先前記的每一筆都被這次重設取代而清算。
  • 例外(靜默):當下全組織已經沒有任何存取 → 沒有東西可撤,不跳 confirm,直接清掉勾選、回主 mode。
  • 取消:什麼都不動——維持勾選、留在批次 mode。
  • 重設不會觸發 link-confirm:整個組織都被清空,不存在「範圍外還有連動裝置」的情形(見 Section 06)。
  • 從 All sites mode 進 Review Changes 再按 back → 回到 All sites mode(不是主 mode)。

為什麼保留這個 confirm。 本對話框的原則是破壞性編輯交由變更清單呈現、不用 modal 阻擋(見 Section 01),但這個動作是例外——它的第一步會清算整份變更清單,而它自己寫入的兩筆在 Review Changes 是唯讀的。也就是說,「變更清單會兜住」這個前提在此不成立,清單本身就是它的犧牲品;使用者事後唯一的退路是 Cancel 放棄整個 session。與「進入批次 mode 不需要確認」並不矛盾:進入完全不動角色,兩者的後果差距懸殊。

批次 mode 中點站點列不再是特例。 點 sidemenu 任一站點的 checkbox 就是照常 toggle 該節點(照常走移除的記帳與 link-confirm 守門);selection 因此不再是「全選」,批次 mode 隨之自動退出。v1.x 把它當成「退出手勢」是為了讓快照還原能運作,快照移除後理由消失,剩下的只是一個隱藏特例(點了 checkbox 卻沒反應)。

mda-reset-modal
「Reset access for all sites?」— 取消 All sites(勾選 → 未勾)一律先確認,確認後全 org 設為 No Access 並記進 diff、可送出(prototype)

循環為什麼是 半選 → 勾選 → 未勾 Design rationale

All sites 列是整個 sidemenu 唯一的三態 checkbox(站點樹節點自 2026-07-16 起為兩態,見 Section 02)——它是批次 mode 的開關 + 全組織存取狀態的摘要(半選 = 組織內部分裝置保有存取),不是樹的選取元件。點擊循環為 半選勾選未勾:半選與未勾點下去都是「進批次 mode」,勾選點下去是「取消」。

它其實是兩態控制項 + 一個推導出來的第三態。 半選 不是一個可以被選到的目標值,而是子項狀態的摘要;它沒有自己被儲存的值(Site 本身不持有權限)。因此點 半選 的結果與點 未勾 相同——都是往「全給」走。這也符合原生 checkbox 的行為:一次啟動一定會清掉 indeterminate,不可能停在那個狀態。

規範層只定義三個狀態的語意與無障礙表述,沒有規定點擊後往哪走;業界確實分成兩派(部分設計系統點半選是全選,少數是全不選)。這裡選「全給」的理由:

  • 這是狀態編輯器,不是表格選取。 選取型情境(表格列、篩選下拉)的勾選是暫時的,最常見的下一步是「清掉重來」;這裡的勾選狀態就是資料本身,點父層是為了做一次批次賦值。
  • 控制項的語意方向朝 on。 列名讀起來是「這個範圍的所有裝置」;看到一個沒被勾滿的框,直覺是「把它勾滿」。要求使用者理解「點一個未滿的框會讓它更空」在權限介面裡是反直覺的。
  • 兩個結果的可達性不對稱。 選全給:半選 →(1 下)→ 全給 →(2 下)→ 全清,中間那一步看得到全勾狀態,路徑自我解釋。選全清:想全給的人會先經歷一次「我的東西不見了」。同樣兩下,但錯誤成本落在不同的人身上,而權限管理者更常做的是「整包開給他」。
  • 變更清單已經先擋住風險。 送出前什麼都還沒寫入,所以進入 + 全給只是一筆待確認的變更,不是破壞性動作。

v1.x 的兩項論述已不適用。 一是「進入無破壞性是因為有快照可還原」——快照移除後,理由變成更單純的「進入不動任何角色」,不需要快照也成立。二是「直接 Reset 全 org 的入口消失」這個已知取捨——現在 勾選 → 未勾 就是那個入口(一律跳 Reset confirm),不需要在批次 mode 內另加 secondary 按鈕。

驗收條件 QA · Given-When-Then

  • Given 點 All sites checkbox 未勾 → 勾選,Then 全 site 變 勾選、右面板換成單一 Apply to all sites 列,且不改任何角色、不記任何變更
  • Given All sites 為半選(has-value 現況)時點它,Then 直接進入批次 mode,不跳任何 modal。
  • Given 批次 mode 中,Then 右面板標題列沒有標題文字,只有裝置類型 Tab 與變更計數;sidemenu 的 All sites 列有選中底色、計數列顯示全 org 數字、批次列名為 Apply to all sites
  • Given 在 All sites 選角色且該類型有非 No Access 裝置,Then 跳「Apply role to all …?」confirm;確認後全 org 套用並記 1 筆「All sites → 角色」。
  • Given 點 All sites checkbox 勾選 → 未勾 且組織內仍有裝置保有存取,Then 一律跳「Reset access for all sites?」——不論本次 session 是否套過角色。
  • Given 已記錄 3 筆變更時觸發該 confirm,Then 內容出現「會一併捨棄已記錄的 3 筆變更」那一句,且數字與標題列的變更計數相同;尚無變更時該句不出現。
  • Given 確認 Reset access,Then 全 org 設為 No Access,並在變更清單記至多 2 筆(All sites,Camera / Other 各一)、Save changes 可用;先前記的變更被清算。
  • Given 已儲存狀態原本就毫無存取時點 勾選 → 未勾,Then 不跳 confirm,直接清掉勾選回主 mode。
  • Given 在 Reset confirm 按 Cancel,Then 什麼都不變——維持勾選、留在批次 mode。
  • Given 批次 mode 中點 sidemenu 任一站點的 checkbox,Then 該節點照常 toggle(照常走移除記帳與 link-confirm 守門),並因 selection 不再是全選而自動退出批次 mode。
  • Given 確認 Reset access 之後,再把某個 site 的角色設成它原本已儲存的角色,Then 變更清單仍會列出這一筆(否則清單會讀成該 site 也變成 No Access)。
  • Given 從 All sites mode 進 Review Changes 再 back,Then 回到 All sites mode。
Section 05

Drill-in 單一裝置

點右面板某列的 chevron > → 整個 body 換成該節點的單一裝置清單,可逐台設定角色。drill-in 期間 footer(Save / Cancel)隱藏,變更累計回主 mode 統一送出。

Header(40px) Header

  • 左:back < 回主 mode(drill-in 只能從主 mode 的列 chevron 進入;All sites 批次的單一列沒有 chevron)。
  • 中:標題為 Configure device access for "<節點名稱>"——說明「正在做什麼、對哪一個節點」,引號內是該節點自己的名稱(不含路徑)。名稱過長時只有引號內的名稱截斷,前綴敘述文字不縮;hover 名稱出完整名稱。
  • 右:240×32「Search devices」搜尋框(300ms debounce;此清單 1 個字即觸發——與 Filter Combo / sidemenu 的 ≥2 字不同,以實作為準)。

路徑不放在標題,而是放在下方的 Area header 列——避免同一畫面把路徑印兩次。

Area header 列(60px) Area header

  • 左:完整路徑 breadcrumb,規則、視覺、tooltip 與主 mode 的列名完全相同(共用同一個實作,見 Section 03);差別只有可用寬度,而且差距很大——drill-in 收掉整個左側 sidemenu、單欄佔滿 body,該列從 716px 變成約 1036px,加上此處沒有 chevron 欄(省下 32px 按鈕 + 16px 間距),breadcrumb 可用寬度約從 420px 變成 790px。因此同一個節點在 drill-in 常常完整顯示,在主 mode 卻已被折疊 —— 這是寬度驅動的正常結果,不是兩套規則。
  • 右:200×32 dropdown,顯示該節點直屬該類型裝置的聚合角色。
  • 改 dropdown = 套用到該節點直屬該類型的全部裝置(不遞迴子層;不受裝置搜尋過濾影響)。
  • 節點無該類型裝置時 dropdown 為 disabled,hover 出與主 mode 相同的說明:No cameras here. / No other devices here.(共用同一份實作,見 Section 03)。

裝置列(60px) Device row

元件 顯示條件 說明
裝置 icon 永遠 依裝置類型(見下)
裝置名稱 永遠
N tag Camera 連動到任一 NVR(不限同節點——實作只檢查該 Camera 有無連動的 NVR,不比對兩者是否在同一個 site / area;design doc 的表格曾寫「同節點」,與實作不符) 直角小標;hover 顯示連動的 NVR 名
PREM tag Camera 且實體屬性為 premium outline pill + info icon;點 info icon 出說明(支援 Attribute search / Re-search)
B tag Speaker / PoE Switch 連動到 Bridge 直角小標;hover 顯示連動的 Bridge 名
Auto-granted tag NVR / Bridge 因連動被自動賦權(來源 = auto) 藍底 pill;hover 顯示觸發它的來源裝置(見 Section 06)
Dropdown 永遠 200×32,選項見下

裝置類型 icon(9 類 + 容錯):Camera(default / fisheye / PTZ / MA 多sensor)、NVR、Bridge、Network Speaker、PoE Switch、VSS,以及 Unknown 容錯。

dropdown 選項(裝置層才有 No Access) Options

裝置類別 選項
Camera 系 No Access / Viewer / Supervisor / 各 Custom role(N 個,01 / 02 / 03… 僅示意)
Other 系 No Access / Viewer / Supervisor

Custom role 只能套到 Camera 系。No Access 只在此裝置層出現(站點層沒有)。

mda-drill-camera
Drill-in — Camera 裝置清單,逐台角色 dropdown(含 No Access),可見 N / PREM tag(prototype)
mda-drill-other
Drill-in — Other devices 清單,NVR 的 Auto-granted tag(prototype)

空狀態 Empty states

drill-in 可進入任何節點(即使沒裝置);依情況顯示對應空狀態(共用 64×64 插圖):

情境 標題 說明
該節點任何類型都沒裝置 No Devices in This Site or Area Go back to pick another site, or add devices to this one first.
有 Other 但沒 Camera(filter = Camera) No Cameras Here Switch the device type filter to see what's on this site.
有 Camera 但沒 Other(filter = Other devices) No Devices Here Switch the device type filter to see what's on this site.
搜尋無結果 No Matches Found Please try adjusting filters or use different keywords.

drill-in 空節點不算 diff。

drill-in 沒有裝置類型 Tab,這是確定的設計。 類型 Tab 只在主 mode 的標題列;drill-in header 只有 back / 標題 / 搜尋。drill-in 的職責是「把這一個節點看完」,在其中切換類型等於換掉觀察對象,會讓 back 要返回哪裡變得不明確。因此空狀態文案引導的「切換類型」是要使用者先返回主 mode 再切。OpenSpec 原本要求在 drill-in 空狀態也能直接切,以本節為準。

全清會即時反映到 sidemenu Clearing all devices

把該節點的直屬裝置全部設為 No Access → sidemenu 該節點 checkbox 自動變未勾、返回主 mode 後該列消失;恢復任一裝置則自動變回勾選。這是顯示層即時推導,不產生額外 diff——完整規則見 Section 02「checkbox 即時反映裝置狀態」,此處不重列。

大量裝置的載入 Infinite scroll

裝置多(60+)的節點採分批載入:第一批 50 筆,捲到接近底部自動載下一批 50 筆;切 filter / 改搜尋 / 進不同節點都重置回第一批。載入時清單末端顯示置中的 48×48 loading(無文字)。

驗收條件 QA · Given-When-Then

  • Given 點某列 chevron >,Then 進入該節點的裝置清單,footer 隱藏。
  • Given 改一台裝置的角色,Then 只有該台變更並記 1 筆 diff。
  • Given 裝置層 dropdown,Then 有 No Access 選項(站點層沒有);Custom role 只出現在 Camera 系。
  • Given 進入沒有該類型裝置的節點,Then 顯示對應空狀態,且不記 diff。
  • Given 在 drill-in 的空狀態,Then 沒有裝置類型 Tab 可切;要換類型必須先返回主 mode(確定的設計,見上方註記)。
  • Given 進入任一節點,Then 標題為 Configure device access for "<節點名稱>"(引號內不含路徑),路徑顯示在下方的 Area header 列。
  • Given 該節點位於深層路徑,Then Area header 的 breadcrumb 規則與主 mode 列名一致;hover 任一處顯示完整路徑。
  • Given 該節點沒有該類型裝置,Then Area header 的 dropdown 為 disabled 且 hover 顯示 No cameras here. / No other devices here.
  • Given 節點裝置超過一批,Then 捲動載入下一批 50 筆,末端顯示 loading。
Section 06

連動與確認

有些裝置彼此連動:Camera ↔ NVRSpeaker / PoE Switch ↔ Bridge。改一邊的存取會牽動另一邊。原則是正向自動、反向確認;實際會遇到三種連動確認狀況

正向自動賦權(不打斷) Forward auto-grant

  • 把 Camera(或 Speaker / PoE)從 No Access 改成其他角色時,若連動的 parent(NVR / Bridge)當下是 No Access 且來源非手動 → parent 自動變 Viewer、來源標 auto、顯示 Auto-granted tag(見 Section 05)。
  • 全部連動的 child 都回 No Access → auto 的 parent 也跟著回 No Access。
  • 使用者手動改 parent → 來源變 manual,脫離 auto。
  • 正向不當下打斷;自動賦權在背景累積,送出前才統一確認(狀況三)。

狀況一:操作當下的撤銷確認 Link-confirm · device trigger

drill-in 把某裝置改成 No Access、且會連帶讓 linked 裝置失去存取時,當下立刻跳確認。

觸發 連動對象
NVR → No Access 連動的 Cameras
Bridge → No Access 連動的 Speaker / PoE Switch
Camera → No Access(且它是某 NVR 最後一個保有存取的來源) 該 NVR(來源非 manual)
Speaker / PoE → No Access(同上邏輯) 該 Bridge(來源非 manual)
  • 標題:Remove access from "<裝置名>"?;內文:Setting "<裝置>" to No Access also removes access from its linked <類型>:,逐列列出 裝置名(類型) + 舊角色 → No Access;hover 出完整路徑。
  • CTA:Remove access(主)/ Cancel(次)。
  • 確認 → 觸發裝置與所列 linked 裝置都設 No Access,各記 1 筆 diff;取消 → 全 revert、無 diff。
mda-link-confirm
狀況一 — 操作當下的撤銷確認「Remove access from …?」,列出連動失去存取的裝置(prototype)

狀況二:移除整個 site 的連動確認 Link-confirm · site trigger

從 sidemenu uncheck 一個 site / area,若其 subtree 連動到其他站點的裝置且會一併失去存取 → 跳同一個 link-confirm,但為 site 觸發版本。

  • 標題:Remove access from "<site 名>"?;內文:Removing access from "<site>" also removes access from these linked devices in other sites:,逐列列出跨站受影響裝置 + 路徑。
  • CTA:Remove access / Cancel。行為同狀況一(確認一併撤銷並記 diff;取消不動)。

All sites 的重設不走這條路。 取消 All sites checkbox 而觸發的 Reset access for all sites?(見 Section 04)不會再跳 link-confirm——它把整個組織都設為 No Access,因此不存在「範圍外還有連動裝置」的情形,沒有 outside 可言。這一點要與「uncheck 單一 site / area」區分清楚:後者的範圍有邊界,邊界外的連動裝置正是 link-confirm 要提醒的事。

mda-link-confirm-site
狀況二 — uncheck 一個 site 時的跨站連動確認「… removes access from these linked devices in other sites」(prototype)

狀況三:送出前的正向連動彙總 Confirm Summary at save

按「Save changes」時,若這次 session 有因正向連動而自動賦權(來源從初始變成 auto)的 NVR / Bridge,先跳彙總 modal 統一確認。

元素 內容
標題 Confirm Device Access Changes
區塊 The following will update automatically based on linked devices:(1 筆與多筆共用同一句,刻意不做單複數分支
每列 裝置名(NVR / Bridge) + No Access → 角色 + Triggered by: 觸發來源裝置;hover 出路徑
CTA Confirm changes(主)/ Cancel(次)

反向撤銷已在狀況一 / 二當下確認過,不在此重複列出

mda-confirm-summary
狀況三 — 送出前彙總「Confirm Device Access Changes」,列出正向連動自動更新的 NVR / Bridge(prototype)

邊界:VSS Out of scope

VSS 的連動在此版不實作警示——現有情境尚未出現需要 VSS 警示的商業條件;VSS 在 drill-in 只當一般裝置顯示,不觸發 link tag 或 link-confirm。未來商業條件符合時再規劃。

驗收條件 QA · Given-When-Then

  • Given 把某 Camera 從 No Access 改為有角色、連動 NVR 當下 No Access 且非手動,Then NVR 自動變 Viewer、標 Auto-granted,不當下跳 modal
  • (狀況一)Given drill-in 把裝置改為 No Access 且牽動 linked 裝置,Then 跳「Remove access from "<裝置>"?」列出受影響裝置;確認一併 No Access 並各記 diff,取消全 revert。
  • (狀況二)Given uncheck 的 site subtree 連動到外部站點裝置,Then 跳「Remove access from ""?」列出跨站受影響裝置。
  • Given 確認 All sites 的「Reset access for all sites?」,Then 跳 link-confirm(整個組織都被清空,沒有範圍外的連動裝置)。
  • Given Confirm Summary 只有 1 台自動賦權裝置,Then 標題與多筆時完全相同,不出現任何單複數差異。
  • (狀況三)Given 正向賦權產生 auto NVR / Bridge,Then 按 Save 跳「Confirm Device Access Changes」列出這些自動更新,CTA 為 Confirm changes;反向撤銷不重列。
  • Given VSS 參與連動,Then 此版不顯示警示(一般裝置處理)。

連動裝置不保證同站。 實測:把 East WingCamera-001 設為 Viewer 後,被自動賦權的 NVR 不在該站點底下 —— 切到 Other devices Tab 進同一個節點,底下的 NVR 全是 No Access。所以 Auto-granted 標記會出現在另一個節點的裝置列上,要找它得先從 Confirm Summary 反查裝置位置。這與 Section 05 的 N tag 規則一致(只檢查有無連動的 NVR,不比對是否同節點)。

Section 07

Review Changes

點右面板標題列的「N changes」→ 整個 body 換成本次 session 的變更清單。每一筆變更(含移除)都列出來,送出前一覽無遺。

mda-review
Review Changes — 兩段式 diff 清單(Site / Area 4 欄表 + Device 群組),Old → New,可重選(Figma key screen)

清單上方另有一行提示:「When a site and the devices inside it are set to different roles, the site row will appear as Multiple roles after saving.」(說明站點與其內部裝置角色不一致時,送出後該站點列會呈現 Multiple roles。)

版面 Layout

  • Header(40px):back <(回進入前的 mode) + 標題「N changes」(單數「1 change」)。
  • Footer 顯示(Save changes / Cancel)——與 drill-in 不同,Review 沿用主 mode 的 footer。

兩段排序 Two sections

清單分兩段,各段內依變更加入順序:

  1. Site / Area 等級變更——4 欄表格:Site / AreaDevice typeOld roleNew role
  2. Device 等級變更——依「裝置所屬節點」群組,每群一個 sub-header(40px) + 該節點下的裝置列。

同節點的裝置變更會聚在同一個 sub-header 下,即使操作時在不同節點間穿插。

欄位與呈現 Columns

  • device 群組的 sub-header:與下面的 Site / Area 欄一樣只顯示最末層 + hover 看路徑(游標變 help),同樣刻意不採 breadcrumb。這一節內因此有兩處是截斷規則的例外。
  • Site / Area:只顯示該節點最末層名稱;有路徑時 hover 出完整 Root > … > Self tooltip(游標變 help)。刻意不採漸進式截斷——這是與主 mode 列名的有意差異(主 mode 已改為完整路徑 breadcrumb,見 Section 03):此欄與 Device type 欄共用寬度,放 breadcrumb 會被截到失去意義,而 hover 的完整路徑已經提供了同樣的資訊。All sites 重設的兩筆在此欄顯示 All sites
  • Device type:顯示 CameraOther devices(短標籤,不再逐字列舉裝置種類)。此欄刻意不加 hover 說明——Other devices 包含哪五種裝置已由主 mode 的 Tab tooltip 說明(見 Section 03),使用者是先在那裡選了類型才會走到這裡;同一份說明在下游再出現一次是多餘的。
  • Old role:舊角色(灰字 + 刪除線) + 箭頭 icon。
  • New role:200×32 dropdown,可直接重選,立即套回對應範圍。

diff 顆粒度 Diff granularity

每筆只在「使用者真正動到的最高層級」記一次:

操作 diff
sidemenu uncheck 一個 site(勾選 → 未勾) 至多 2 筆(Camera / Other 各一筆,僅該類型的 subtree 初始狀態含非 No Access 裝置時成立;剛勾選未配角色、或初始即全 No Access → 0 筆);整 subtree cascade 不列,且 subtree 內本次 session 已記的變更一併清算(見 Section 02)
右面板改一列 dropdown 1 筆(該節點 → 新角色,對應該 filter)
drill-in 改一台裝置 1 筆(裝置層)
All sites 批次改角色 1 筆(All sites)
All sites 重設(取消 All sites checkbox 並確認) 至多 2 筆(Camera / Other 各一,Site / Area 欄顯示 All sites);依已儲存狀態判斷該類型是否需要記帳,初始即全 No Access 的類型記 0 筆;先前記的每一筆都被這次重設取代而清算;唯讀(見下)
drill-in 全清觸發的 checkbox 自動未勾 0 筆(顯示層推導,裝置變更已各自成列,見 Section 02)
自動賦權的 NVR / Bridge(來源 auto) 0 筆——不進 diff、不出現在 Review;於送出前的「Confirm Device Access Changes」彙總 modal 統一列出(見 Section 06)

在 Review 內重選 Re-select in review

  • 站點列(node)、裝置列(device)、All sites 列的 New role dropdown 可直接改,立即套回原本 scope。裝置列改成 No Access 時,與 drill-in 相同,會經過 link-confirm 守門(牽動 linked 裝置時先跳 modal,見 Section 06)。
  • uncheck 列(… → No Access)為唯讀——在 Review 直接重配會遺失被 cascade 撤權的子孫紀錄;要回復,回主 mode 重新勾選該節點再配角色。All sites 重設的兩筆同樣唯讀(它們就是「uncheck 整棵樹」,沿用同一條規則)。
  • 改回最初的舊角色 → 該筆自動從清單移除(revert)。這裡的「最初」指的是扣掉範圍更大的變更之後的基準,不是無條件的已儲存值:若已經有一筆涵蓋此節點的粗粒度移除(祖先的 uncheck 或 All sites 重設),該節點的基準就是 No Access,因此把它設成原本已儲存的角色仍然算一筆變更(見下節)。
  • Revert 的先天限制:Old role 是「Multiple roles」的筆(組織 / 節點初始角色不一致)無法用「改回原值」移除——取消這類變更要用 Cancel 放棄整個 session。

清單必須能重建最終狀態 The list is the contract

這是這份清單的驗收條件,也是 QA 最該拿來驗的一條:從已儲存狀態出發,套用清單上的每一筆(範圍重疊時取最具體的那一筆),得出的結果必須等於實際會被送出的狀態。 換句話說,讀清單的人能重建出真正的最終狀態。

「最具體的那一筆」由細到粗是:某一台裝置 → 該裝置所屬節點 → 該節點與各層祖先的移除 → 整個組織。同範圍不會同時存在兩筆(對節點明確賦值會取代該節點自己的移除那筆;移除一個 subtree 會清算 subtree 內的細粒度筆數)。

由此推出兩條看起來反直覺、但必要的規則:

  • 粗粒度移除之後,對範圍內節點賦權一定會另記一筆——即使選的角色剛好就是已儲存的原值。若省略,清單會讀成「該節點也跟著變成 No Access」,與實際相反。這一筆的 Old role 會顯示 No Access(粗粒度那筆造成的狀態),不是已儲存的角色。
  • 已被涵蓋的移除不會重複記——粗粒度那一筆已經表達了同一個轉換,再記一次等於同一件事報兩遍。

因此清單可能出現「粗粒度預設 + 細粒度例外」的組合,例如先一筆 All sites → No Access,後面接一筆 某 Area:No Access → Supervisor排序保證粗粒度那筆一定在例外之前(見上方「兩段排序」:各段內依變更加入順序,而例外必然是在粗粒度那筆已存在的前提下才被記錄)——這條順序不能改成依名稱之類的排法,否則讀者會先看到例外、再看到涵蓋它的預設,得出相反的結論。

淨變化為零時清單為空:若所有編輯繞了一圈,最後沒有任何裝置與已儲存狀態不同,清單會清空、Save changes 停用——即使過程中曾經記過粗粒度與例外的組合。

Back 行為 Back

進入 Review 時記住前一個 mode(主 / All sites),按 back 還原到該 mode——特別是從 All sites mode 進來,back 要回到 All sites mode,不會跳成「主 mode 但 sidemenu 全 勾選」的混合狀態。

驗收條件 QA · Given-When-Then

  • Given uncheck 一個含 Camera 與 Other 的 site(兩類型初始皆保有存取),Then Review 列 2 筆(Device type 各為 Camera 與 Other),皆 舊角色 → No Access
  • Given 移除某站點存取使它從可見清單消失,Then 該變更仍出現在 Review(舊角色 → No Access)。
  • Given 在 Review 把某筆 New role 改回最初值,Then 該筆自動從清單移除、N changes 遞減。
  • Given 一筆 uncheck 列(… → No Access),Then 其 New role dropdown 為唯讀不可改。
  • Given 在 Review 把某台裝置改成 No Access 且會牽動 linked 裝置,Then 跳與 drill-in 相同的 link-confirm modal。
  • Given 從 All sites mode 進 Review 再 back,Then 回到 All sites mode。
  • Given N changes = 0,Then 不顯示 N changes 入口(不會進到空的 Review)。
  • Given 確認 All sites 重設,Then 清單出現至多 2 筆、Site / Area 欄為 All sites、Device type 各為 CameraOther devices,且兩筆的 New role dropdown 唯讀
  • Given 已有一筆涵蓋某節點的粗粒度移除,再把該節點設成它原本已儲存的角色,Then 清單仍列出這一筆,且該筆 Old role 顯示 No Access
  • Given 粗粒度移除已在清單上,再 uncheck 該範圍內的某個節點,Then 不新增重複的一筆。
  • Given 從已儲存狀態出發套用清單上每一筆(範圍重疊取最具體者),Then 得到的每個裝置角色與實際將送出的狀態完全一致。
  • Given 所有編輯的淨效果為零(沒有任何裝置與已儲存狀態不同),Then 清單為空、Save changes 停用。
Section 08

送出與取消

Footer 有「Save changes」與「Cancel」。送出只送 diff;有未存變更時關閉會先確認。

Save changes Submit

條件 行為
啟用 有變更(N changes > 0)
停用 沒有變更(N changes = 0),灰色
點下去(無自動賦權) 直接送出 diff
點下去(有自動賦權的 NVR / Bridge) 先跳 Confirm Summary(見 Section 06),確認後才送
  • 成功 → 關閉對話框、外層 user detail 頁刷新為新值,並顯示成功 toast(說明變更何時生效)。
  • 失敗 → 顯示紅色 toast,對話框保持開啟、變更保留。

勾選但沒配角色的節點不會被儲存 Checked without a role

只勾選一個節點、沒有在右面板或 drill-in 指派角色 → 不記任何變更,因此送出時也不會被儲存。重開對話框後該節點會依裝置現況重新推導(沒有任何裝置保有存取 → 未勾),sidemenu 的選取計數也隨之下降。

這是自我一致的行為:勾選只是「我要管理這幾個節點」,不是授權;計數算的是勾選節點數而非有權限的節點數(見 Section 02),所以計數下降並沒有隱瞞什麼。使用者仍有可能把它讀成工作遺失,特別是同時有些節點配了角色、有些沒配的混合情況(完全沒配角色時本來就沒有東西可送出)。

不加送出前提醒(2026-08-11 已決定)。 評估過的作法是把 Confirm Summary 的觸發條件從「有自動賦權」擴充為「有自動賦權有勾選未配角色的節點」——該 modal 目前只在有自動賦權時才跳。決定不做:為一個不造成資料損失的狀態多插一道 modal,代價高於收益。沒配角色本來就沒有東西可儲存,使用者要的變更一筆都不會遺失。

Cancel / 關閉 / ESC Dismiss

情境 行為
N changes = 0 直接關閉
N changes > 0 跳 Discard 確認

Discard modal:

元素 內容
標題 Discard changes?
內容 提示未存的存取變更將遺失
主鈕 Discard(捨棄並關閉)
次鈕 Keep editing(留在對話框)
mda-discard
「Discard changes?」— 有未存變更時 Cancel / 關閉 / ESC 會先確認(prototype)

驗收條件 QA · Given-When-Then

  • Given 沒有任何變更,Then Save changes 停用。
  • Given 有變更並送出成功,Then 外層頁面刷新為新值,顯示成功 toast(含生效時間)。
  • Given 送出失敗,Then 顯示失敗 toast、對話框保留、變更不遺失。
  • Given 有未存變更時按 Cancel / 關閉 / ESC,Then 跳「Discard changes?」;Discard 捨棄關閉、Keep editing 留下。
  • Given 送出時 diff 含自動賦權 NVR / Bridge,Then 先跳 Confirm Summary(見 Section 06)。
  • Given 只勾選節點、沒有指派任何角色,Then Save changes 停用(沒有變更可送)。
  • Given 一部分節點配了角色、一部分只勾選未配,Then 送出後只有配了角色的節點被儲存;重開對話框時未配角色的節點不再勾選、選取計數隨之下降。