mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-10 01:15:55 +00:00
b6766572df
* feat(navigation): make Compact Launcher the default desktop style Compact Launcher replaces Smart bar as the default desktop navigation style; Classic bar is fully retired (rendering, settings option, and persisted-value migration to Compact). Quick-link capacity increases from 7 to 8, and the recommended default set becomes Home, Fleet, Resources, Security, Update, and Schedules. Quick-link seeding and Reset now use a settled, role-aware eligibility computation distinct from the existing current-context display filtering, so an operator's recommended defaults reflect their role rather than which node happens to be active, and a Reset produces the same result regardless of node context. Also fixes two issues surfaced while touching this code: a disabled quick-link control showing a duplicate tooltip (native title plus the Radix tooltip), and the Navigate launcher panel being unable to scroll at constrained viewport heights (now uses the shared ScrollArea component). The launcher hamburger icon animates into an X on open, respecting Reduced motion. * test(e2e): fix the launcher morph and panel scroll navigation assertions Three assertions in the new navigation specs were wrong against a real browser, all in the tests rather than the product: - The morph check read getComputedStyle().transform, which Tailwind v4 no longer writes: rotate-45 compiles to `rotate:45deg` and the translate utilities to the standalone `translate` property, so both the open and closed reads returned "none". It now snapshots translate and rotate alongside transform. The animation itself was always correct, since Tailwind's transition-transform covers translate, scale, and rotate. - The reduced-motion check assumed motion starts enabled. A fresh install defaults to the Calm visual style, which turns Reduced motion on, so the clamp was already active and the pre-toggle duration assertion could never hold. It now drives the toggle in both directions from a known state. - The panel scroll check asserted a specific overflow measurement, which depends on Radix having applied its available-height variable at read time and on how many destinations the account can reach. It now asserts the property the fix actually guarantees: the ScrollArea viewport owns vertical scrolling while the outer menu only clips, with no horizontal overflow and the panel inside the viewport. Verified by running the spec against live dev servers: 7 passed, twice. * fix(nav): make the Navigate launcher panel actually scroll with the mouse Live QA found that the panel only reached destinations below the fold by keyboard; a real mouse wheel did nothing. The Radix ScrollArea viewport is sized with h-full, and a percentage height only resolves against a containing block with a definite height. The popper content is height:auto clamped by max-height, which is not definite, so nothing sized from it is either, so the viewport fell back to its full content height with no internal overflow, no scrollbar, and no response to wheel input, while an ancestor's overflow-hidden silently clipped everything past the fold. Moves the available-height cap onto the viewport itself and moves the masthead inside the scroll region so the cap needs no masthead-height arithmetic. Verified live: the previous structure measured scrollHeight === clientHeight === 594 with zero wheel movement; the fix measures 646/357 with the wheel reaching the true maximum. Also hardens the panel's e2e coverage, which passed on the broken structure because it asserted only computed overflow properties and never drove a real scroll: adds a keyboard-reach assertion using End rather than ArrowDown (ArrowDown landing on the first item is stock roving focus and proves nothing about scrolling), a genuine mouse-wheel assertion that scrolls to the true bottom rather than assuming one gesture covers the range, and a companion test proving the cap tracks the popper's available height rather than a hardcoded pixel value. Confirmed the rewritten test fails on the previous structure and passes on the fix.
106 lines
7.0 KiB
Plaintext
106 lines
7.0 KiB
Plaintext
---
|
|
title: Give a Teammate Deploy Access to One Stack, Not the Whole Fleet
|
|
sidebarTitle: Scope deploy access to one stack
|
|
description: Create a teammate account that can deploy and restart one stack, without editing its compose file, touching any other stack, or reaching system settings.
|
|
---
|
|
|
|
Say a new on-call engineer needs to be able to restart `support-portal` at 2 AM without waking you up, but they have no business editing its compose file, deleting stacks, or managing users. This walks through creating that account, granting it a scoped Deployer permission on `support-portal` alone, and confirming from the teammate's own session that the access is exactly as narrow as intended: they can act on the one stack they were granted, and nothing else.
|
|
|
|
## Prerequisites
|
|
|
|
- The `admin` role. Creating accounts and granting scoped permissions is Admin-only, on every tier.
|
|
- A running stack to scope the permission to. This tutorial uses a small stack called `support-portal`:
|
|
|
|
```yaml
|
|
services:
|
|
web:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8097:80"
|
|
depends_on:
|
|
- db
|
|
db:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_PASSWORD: supportportal
|
|
```
|
|
|
|
- Available on Community and Admiral. Both tiers ship the same five built-in roles and the same additive stack and node scopes; nothing here requires a paid plan.
|
|
|
|
<Note>
|
|
Stack scopes are node-specific: granting Deployer on `support-portal` authorizes only that stack name on the node you pick in the next section. The same stack name on a different node needs its own grant. If your fleet has more than one node, keep this in mind when you choose the node in Step 2.
|
|
</Note>
|
|
|
|
<Steps>
|
|
<Step title="Create the teammate's account">
|
|
Open your avatar in the top-right corner, choose **Settings**, then under **Access** pick **Users**. Click **Add user**.
|
|
|
|
Fill in a **Username** (for example `jordan`), leave **Role** at its default of **Viewer** (the floor you'll add the scoped permission on top of), and set a **Password**. Leaving the global role at Viewer, rather than picking Deployer here, is the point: the account starts with read-only access everywhere, and the next step grants deploy rights on exactly one stack.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/new-user-form.png" alt="The New User form in Settings Users, with Username jordan, Role combobox defaulting to Viewer, and Password and Confirm Password fields filled." />
|
|
</Frame>
|
|
|
|
Click **Create user**. The table refreshes with the new row.
|
|
</Step>
|
|
<Step title="Grant a scoped Deployer permission on the stack">
|
|
Click the pencil icon on the new user's row to open **Edit User**. A **Scoped Permissions** box appears below the form.
|
|
|
|
Set **Role** to **Deployer**, **Resource Type** to **Stack**, pick the **Node** that hosts `support-portal`, then pick **support-portal** in the **Stack** field that unlocks once a node is selected.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/scoped-permission-form.png" alt="The Scoped Permissions form filled in: Role Deployer, Resource Type Stack, Node Local, Stack support-portal, with the Add button enabled." />
|
|
</Frame>
|
|
|
|
Click **Add**. A toast confirms the scope was added, and it appears as a row above the form.
|
|
</Step>
|
|
<Step title="Sign in as the teammate and confirm the boundary">
|
|
Log out and sign in as the account you just created. Open the `support-portal` stack: **Restart**, **Stop**, **Take down**, and **Update** all appear in the toolbar, matching the Deployer permission you granted.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/jordan-toolbar.png" alt="The support-portal stack as the teammate account, with Restart, Stop, Take down, and Update buttons in the toolbar." />
|
|
</Frame>
|
|
|
|
Open the `compose.yaml` tab. The file is visible (Viewer's global `stack:read` covers this), but there is no **Save & Deploy** button and no **Git Source** button, the way there would be for an account with `stack:edit`, only a close icon.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/jordan-compose-readonly.png" alt="The compose.yaml tab as the teammate account, showing the file contents with only a close icon in the toolbar and no Save or Git Source button." />
|
|
</Frame>
|
|
|
|
Click **Restart** to confirm the granted action actually works, then open a stack you did *not* grant a scope on. Its toolbar has no deploy actions at all: the scope really is limited to the one stack.
|
|
</Step>
|
|
</Steps>
|
|
|
|
## Verify it worked
|
|
|
|
Check from two places, since a single screen showing "it looks right" isn't proof the permission is actually enforced.
|
|
|
|
**The scope itself.** Sign back in as an admin, reopen the teammate's user record from **Settings · Users**, and confirm the Scoped Permissions box lists `deployer` on `stack: support-portal` for the node you picked.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/scope-added.png" alt="The Edit User panel's Scoped Permissions box showing one row: deployer on stack support-portal at Local, with a trash icon to remove it." />
|
|
</Frame>
|
|
|
|
**The audit log.** Open the navigation launcher and pick **Audit** (Smart bar: open **More** instead). Two entries confirm the setup, both attributed to your account: a **created user** entry (`POST /api/users`) and an **assigned role** entry (`POST /api/users/:id/roles`). If the teammate restarted the stack in Step 3, a third entry and a `manual` count on the dashboard's **Stack Restarts (7d)** card confirm the permission was exercised, not just granted.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/grant-scoped-stack-access/audit-log.png" alt="Audit log showing entries for assigning a role (POST /api/users/3/roles) and creating a user (POST /api/users), both attributed to the admin account." />
|
|
</Frame>
|
|
|
|
## If something goes wrong
|
|
|
|
**The teammate says they can't deploy the stack you scoped them to.** The two most common causes are both silent: the scope was added for the wrong node (stack scopes are node-specific, and a multi-node fleet's node picker is easy to skip past), or the stack name doesn't match exactly (names are case-sensitive, though the picker prevents typos since it lists real stacks). Reopen the user's **Edit User** panel and check the existing-scope row reads the resource and node you expect. If it doesn't, remove it with the trash icon and add it again with the correct node selected first. See [RBAC & User Management · Troubleshooting](/features/rbac#troubleshooting) for the other ways a scoped assignment can fail to apply.
|
|
|
|
## Related
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="RBAC & User Management" icon="user-shield" href="/features/rbac">
|
|
The full permission matrix, all five built-in roles, session security, and SSO auto-provisioning.
|
|
</Card>
|
|
<Card title="Set Up SSO with Custom OIDC" icon="key" href="/tutorials/set-up-sso">
|
|
Provision accounts automatically from your identity provider instead of creating them one at a time.
|
|
</Card>
|
|
</CardGroup>
|