Full role-tab rollout: every customer module now has Customer/Internal/ Architect/Claude tabs, grounded per-module in real code. Backend serves 296 docs / 96 modules. Grounding surfaced ~20 real bugs across services (filed separately after verification). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.7 KiB
module, title, tab, order, audience
| module | title | tab | order | audience |
|---|---|---|---|---|
| devices.device-groups | Device Groups | Internal | 20 | internal |
Internal · ops/support/admin. Grounded in
hiveops-devicesbackend +AtmGroups.svelte(2026-07-01). Owner service: hiveops-devices (port 8096, DBhiveiq_devices).
What this module actually is
A CRUD screen over the atm_groups table plus its atm_group_members join table. Groups are named, colored collections of devices used for filtered device lists and bulk hardware-profile pushes. Deleting a group or dropping a device only touches the join table — no device is ever deleted.
Roles required
- Read (list groups, view a group): any authenticated JWT.
GET /api/atm-groupsandGET /api/atm-groups/{id}have no@PreAuthorize— they are readable by any logged-in user, scoped by institution (see below). - All mutations (create, update, delete, assign ATMs, add/remove single ATM, assign hardware profile):
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')"). A non-admin PUT/POST/DELETE returns 403.
Institution scoping (why a user "can't see a group")
AtmGroupService calls InstitutionContext.resolveScope() on every read/write:
- scope
null→ full access (MSP/BCOS admin) — sees all groups. - scope empty list → sees zero groups.
- scope non-empty → only groups whose
institution_keyis in scope (findAllWithAtmsByInstitutionKeyIn). - On create, if the caller is scoped to exactly one institution, the group's
institution_keyis forced to that key — the Institution dropdown selection in the form is ignored for single-institution users.
First thing to check when "my group vanished / I can't edit it": the caller's JWT institution scope vs the group's institution_key.
Admin-only actions on the screen
- + New Group →
POST /api/atm-groups. - Edit →
PUT /api/atm-groups/{id}(name/description/color/institution/membership; only non-null fields are applied). - Assign Devices →
PUT /api/atm-groups/{id}/atmswith a full[atmId, ...]list — this replaces the whole membership set, it is not additive. - Assign Profile →
POST /api/atm-groups/{id}/hardware-profile{ "profileKey": "..." }, then the frontend fires aPOST /fleet/tasks(CONFIG_UPDATE, patchatm.hardware.profile). - Delete →
DELETE /api/atm-groups/{id}.
Set Default / Clear Default — NOT server state
The Set Default / ✕ Clear buttons are purely client-side. The chosen group id is stored in browser localStorage under key hiveops_defaultGroupId (stores.ts). It is:
- per-browser, per-machine — never synced to the account or DB;
- used only to pre-filter the device list (passes
groupIdto/api/atms/paginated).
"My default group didn't follow me to another machine" is expected behaviour, not a bug. To reset it: clear site data / localStorage.
Common failure modes + fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| 409 / 500 on create or edit with terse message | atm_groups.name is UNIQUE globally (not per-institution). A duplicate name — even across institutions — violates the constraint. server.error.include-message=never hides the detail. |
Pick a unique name; check DB for the colliding row. |
| 403 on any save | Caller lacks MSP_ADMIN/BCOS_ADMIN. |
Use an admin JWT. |
| Group list empty for a real user | Institution scope is empty, or all groups are tied to institutions outside scope. | Check JWT institutionKey claim / scope. |
| "Assign Profile" says N devices but no config lands on ATMs | Two steps: profile save succeeded but the follow-up fleet task failed, or ATMs are offline/haven't polled. | Check /fleet/tasks for the queued CONFIG_UPDATE; verify agents are polling. |
| Assigning devices wiped others out | Assign Devices is a full replace, not add. |
Re-open, select the complete intended set. |
| Removed a device but it's "back" | Edit form / Assign panel was opened from stale data. | Reload groups (GET /api/atm-groups) and retry. |
| Group shows "All" institution but a scoped admin can still edit it | verifyGroupAccess only blocks when institution_key is non-null. Null (All) groups are editable by any in-scope admin. |
Intended today; note it before blaming data. |
Quick checks
# List groups (any authed JWT)
curl -s https://api.bcos.cloud/devices/api/atm-groups -H "Authorization: Bearer $JWT"
# DB: group + member counts
SELECT g.id, g.name, g.institution_key, count(m.atm_id) AS atms
FROM atm_groups g LEFT JOIN atm_group_members m ON m.group_id = g.id
GROUP BY g.id ORDER BY g.name;
See also [devices.device-groups] Overview (customer) and the Architect tab for data flow.