Service Accounts UI - #2042
Conversation
❌ Code HealthIntroduces 48 Unused files
64 Unused exports
|
* Rollup subscriptions by prefix for tabular display * Add recipients section to subscription dialogs * Adjust state selectors in SubscriberSection component * Adjust how AlertTypeSelector updates client state * Consolidate CSS border styles for subscriber accordion components * Move accordion expand arrow from right to left * Move delete button from DialogActions to accordion * Move delete button outside Summary component * Placeholder: enable single subscription deletion * Initialize mutable subscription metadata state on dialog open * Enable subscription email editing * Display placeholder in accordion summary for email when empty * Move single subscription delete button into SubscriberInfo directory * Add CTA to subscribers section * Enable the creation of a templated subscription in dialog * Remove net new subscriptions from client state * Disable email field and add button when email conflicts or absence detected * Display error icon when email conflicts or absence detected * Tweak accordion border radius logic * Remove logging statement * Display templated subscription first in list * Enable single subscription operations * Enable creating subscriptions for nested prefixes * Correct dialog closure logic * Adjust dialog closure logic * Enable the deletion of all subscriptions for a given prefix * Store email when tab and enter keys are pressed * Do not display error icon in summary when email empty * Update delete button label * Filter out deleted subscriptions in metadata memo in SubscriberSection * Thicken accordion border for templated subscriptions * Correct typo in getSubscriptionIndex * Add simple empty subscriber message to dialog * Initialize global prefix settings in store * Move alert type field and selector components to child directory * Move email field and selector components to child directory * Move SubscriberInfo directory into SubscriberSection directory * Add global settings section to dialog * Set data movement stalled global setting in dialog * Prevent existing global settings from being removed * Unset data movement stalled global setting in dialog * Remove commented code fragment from Rows * Update dialog section header styling * Add todo comment in useInitializeAlertConfig * Remove diminished chip styling for alert type chips * Replace instance of depreciated renderTags prop in AlertTypeSelector * Rename memoized values variable in AlertTypeSelector * Present system alerts in alert type selector menu * Replace instance of depreciated InputProps prop in AlertTypeSelector * Upsert alert config * Remove multiple prefix support * Display a loading state for DataMovementSetting * Use formatted catalog prefix to index subscription metadata on prefix change * Tightly couple global prefix setting state to prefix input * Move email error state into MutableAlertSubscription * Remove and replace AlertTypeFieldProps with instance of SubscriptionDependentProps * Place email field in error state when duplicate email detected * Display error text when duplicate email detected * Replace alert type selector with checklist * Rename AlertTypeSelector to AlertTypeList * Sort alert type options in state * Update selected state of alert type list items * Disable multi-subscription delete button for new prefixes * Remove the default right margin from FormControlLabel in AlertTypeList * Prevent system alert types from being unchecked in list * Change alert type list item styling on hover * Expand LogRocket coverage in useModifyAlertMetadata * Expand server error coverage * Set emailErrorsExist when duplicate email detected * Update input type annotation for UpdateAlertConfigMutation * Keep MutableAlertSubscription local to types file * Allow alert types to be set when email undefined * Add handling for the absence of EffectiveAlertConfig.condition * Mark GlobalSettingConfig.condition as an optional property * Remove eager promise resolution in the absence of subscriptions * Implement workaround for alertConfigs GraphQL API * Add hyper-safeguard to useModifyAlertMetadata * Remove AlertConfigs context provider * Update fallback value for merged subscription data * Remove stale existing alert subscriptions * Placeholder: Evaluate inherited prefix settings * Display only alert_subscriptions data in client table * Disable save button when subscriber array empty * Populate staleSubscriptionQueries array * Display five chips in subscriber summary * Relax email validation * Prevent insertion of alert_configs row with empty config * Set loading to false in useModifyAlertMetadata early exit condition * Clarify empty config gaurd in useModifyAlertMetadata * Use the subscription id to locate subscription in setSingleAlertType * Prevent mutation of GraphQL data in store * Add subscription array safegaurd to Rows * Correct typo in variable name in useEvaluateGlobalPrefixSettings * Ensure setSingleAlertType generates subscriptions with passed id when applicable * Remove attempt to specify operation in error message * Cancel debounced prefix in useInitializeAlertConfig on unmount * Remove needless condition when displaying warning icon in Summary * Codegen output * Restrict the tenant portion of PrefixField to selected tenant * Ignore delete emails in useEvaluateSubscriptionIneligibility checks * Convert settings variable to a constant in useEvaluateGlobalPrefixSettings * Limit use of new PrefixedName property * Store active subscriptions in const for ease of reference * Factor the enabled flag when evaluating global prefix settings * Remove unused EditButton prop * Skip subscriptions with empty emails when identifying duplicates * Store a copy of an initializeGlobalPrefixSettings array, input arg * Add temporary alertConfigs pagination workaround * Send non-effective alert config in updateAlertConfig request * Narrow scope of setGlobalPrefixSettings * Remove alert config condition property to unset setting * Do not display dialog content if init queries fail * Remove unused markSubscriptionForDeletion param * Codegen output * Placeholder: replace instances of gql with graphql * Broaden standard config type annotation * Replace instance of PrefixedName with LeavesAutocomplete * Use selectedTenant to prefill prefix field * Prevent clearing of selected tenant in prefix field * Reset subscriber list when prefix changes * Prevent clearing of selected tenant slash in prefix field * Hide DeleteButton in configure notificiation methods dialog * Disable (most) interactive components for prefixes with existing subscriptions in create flow * Remove outdated type * Display data movement stalled field loading state when prefix mis-formatted * Debounce email input to enable semi-early state updates * Placeholder: integrate new and modified alert config endpoints * Placeholder: store reference to explicit server config * Preserve effective config state * Make effective config state immutable * Remove useEvaluateGlobalPrefixSettings hook * Display effective alert config query error * Remove unused interface from src/api/types.ts * Use subscribed email as a fallback for disabled email field * Decrease severity of existing prefix message * Prevent alert config queries when prefix invalid * Extend debouced email wait period * Append a forward slash to debounced prefix * Preserve templated subscriptions when prefix does not have existing subscribers * Allow server errors within useInitializeAlertConfig to override client state * Remove visual indications of hover and click on system alert checkboxes * Transfer native padding from ListItem to FormControlLabel --------- Co-authored-by: Kiahna Tucker <ktucker@Kiahnas-MacBook-Air.local>
* ui(theme): trade the card shadow for a hairline, and give light mode a third surface
The card shadow stacked three layers, one of them casting upward — light from
two directions at once. It was also doing redundant work in light mode, where a
white card already separates from the page, and none at all in dark, where a
10%-black shadow on a near-black page is invisible. CardWrapper now draws a
`palette.divider` hairline: the same border the stat cards inside it already
used, so the product stops nesting bordered cards inside shadowed ones.
Light mode was also a surface short. Dark has three — canvas 900, panel 800,
cards a 5% white wash — while light ran canvas 100, panel *white*, cards
*white*: the panel had taken the value the cards needed, so a card and the
surface under it were the same colour. Light now steps the same ramp, 200 to
100 to white. Dark is byte-identical.
`defaultBoxShadow` and `opaqueLightModeBorder` had no other consumer and are
gone.
* ui(layout): stop sizing the page panel's padding off the sidebar
PageContainer set `px: navigationOpen ? 1 : { xs: 1, md: 5 }` — 8px with the
sidebar out, 40px with it collapsed — on the reasoning that a wider pane wants
more breathing room. But the header panel above it is fixed at `px: 2`, and the
two are welded into one card by a shared corner radius, so collapsing the
sidebar stepped every page's content 32px right of its own title.
It only ever looked right expanded by coincidence: on the details page, 8px of
panel padding plus that page's own 8px margin happened to equal the header's 16.
Pages without that margin, `/captures` among them, were already 8px out in the
other direction.
Now a constant 16px, matching the header. Someone collapsing the sidebar is
asking for width; taking 80px of it back as padding argued with them.
* ui(tabs): filled pills, and stop reserving space for absent scroll buttons
A filled pill for the selected tab instead of MUI's sliding underline. The
underline needs a rule beneath the row to sit against, and that rule was one of
three stacked across the top of a details page; a background says "you are
here" without needing anything drawn under it.
`scrollButtons="auto"` disables the arrows when there is nothing to scroll to
but still lays them out — 40px reserved each side. The left one is disabled
whenever the row is scrolled fully left, which is its resting state, so it was
silently indenting tab rows past their own page's left edge with no overflow
anywhere in sight. Disabled buttons are now taken out of flow.
The alerts badge follows: at 20px and `top: -4` it started 6px above the 34px
pill and had its top sliced off by the three `overflow: hidden` MUI puts on the
tab, the scroller and the root. Unclipping is not available — the scroller's
overflow is what makes the row scrollable — so it is 16px, sits inside, and
takes `errorMain` with explicit white text. White measures 4.92:1 there, which
clears the 4.5 this 10px text needs, but the theme's `contrastThreshold` is 5,
so `getContrastText` would reject it by 0.08 and return dark text at 3.94.
The details shell loses its stacked dividers and its own horizontal margin, and
puts the tab panel in the same Stack as the chrome above it so the gap under the
tabs is the same 16px as the gap over them.
* ui(layout): align list and admin pages to the page's own left edge
Twelve ad-hoc horizontal insets, none of them agreeing with each other or with
the page title in the header bar.
The three list pages wrapped their content in a Toolbar without
`disableGutters` — MUI's default 24px — and then a Box with another 16px, so
"New Capture" and the table sat 30px right of "Sources". Every other Toolbar in
the codebase already passes `disableGutters`.
Admin was worse: `StandAloneTableTitle` carried `m: 2`, the access-grants table
`ml: 2`, the prefix-alerts table a hardcoded `margin: '0 16px'`, and Settings,
Api, Billing and both RefreshToken sections a `p: 2` or `mx: 2` each. Section
headings, descriptions, buttons and tables each landed somewhere different.
All of it is vertical-only now, so nothing moved up or down. Centred table
empty states and anything inside a CardWrapper stay where they were — cards
align to the card, not to the page.
* ui: resize the usage chart with its container, and stop toggles shouting
DataByHourGraph listened only for `window.resize`, but the sidebar resizes the
container without resizing the window, so the canvas kept its old width and
overflowed. A ResizeObserver on the container fixes it, and the card is allowed
to shrink below the canvas's natural width.
MUI uppercases ToggleButton labels. The theme opts MuiButton and MuiTab out of
that but not MuiToggleButton, so "Data"/"Docs" and "Private"/"Public" — already
sentence case in the language file — arrived as DATA/DOCS and PRIVATE/PUBLIC,
shouting beside headings that do not. Scoped to these two rather than the theme;
four other toggle groups would change with it.
* ui: simplify after review
* chore(ui): prettier
Formatting only. Fallout from the simplify pass: two blank lines left where a
constant was removed, and a quoted object key that no longer needs quoting now
that its quoted siblings are gone.
* ui(layout): restore the header rule, and inset content evenly from it
Review asked for the divider between the page header and the content panel,
which the shadow-to-hairline change had removed. The shared corner radius
welds the two Papers into one surface, so without it the header runs straight
into the content.
Restoring it exposed the vertical half of a bug this branch already fixed
horizontally. The content panel used pt: header ? 1 : 2, an 8px top against
its own 16px left, which had nothing to measure against while the rule was
absent. It is now p: 2 on all four sides.
The three list pages were never at 8px: MUI's Toolbar carries a 56/64px
min-height meant for an app bar, and centring a 42px button in it added ~11px.
That is why they looked right when Welcome and Admin looked wrong. Overriding
the min-height puts every page at a flat 16px below the rule. The same Toolbar
was already giving these pages 24px of unwanted gutter, fixed earlier here.
* ui: address review — theme-level toggle case, light-only card border, collapsed-nav padding
Three changes from review on #2048.
Move textTransform: 'none' from OutlinedToggleButton to a MuiToggleButton
override in the theme, beside MuiButton and MuiTab. This also picks up four
toggles OutlinedToggleButton does not wrap: Details/Spec, Details/Logs/Status,
FieldActionButton and DataPlanes/DialogFields/ToggleField. The icon-only
ToggleButtons in the data preview carry no text, so the property is inert there.
Draw the CardWrapper border in light mode only. Dark mode already separates the
card from the panel by tone, with a 5% white wash over grey[800], so a border
there does the same work twice. Light mode has no wash — white on grey[100] is a
narrower step and the border is what makes the edge legible.
Restore the wider content-panel inset when the sidebar is collapsed. The header
panel stays at 16px, so in the collapsed state content starts 24px right of the
page title. That is the requested behaviour, not an oversight; giving the header
panel the same value would remove the offset. With the sidebar open both are
16px and the offset is 0.
Add a "Time Behind" row to the materialization details card, beside the data backlog: the gap between the newest document published to a source collection and the newest one the task has processed. The two timestamps live in different stats rows. A binding's progress is `taskStats.materialize[<collection>].lastSourcePublishedAt` on the materialization's own row, while how far the collection has advanced is `statsSummary.lastPublishedAt` on the collection's row, written by whichever capture or derivation produces it. Pairing them requires a second query, which resolves once the first has named the bindings. The gap is computed per collection and the task-level figure is the largest of them, since a task is only as caught up as its furthest-behind binding. Comparing the oldest `lastSourcePublishedAt` against anything would instead report a lag for collections that are simply quiet: a binding that has read everything its collection holds looks old while being perfectly current. The two timestamps are written by different tasks at different moments, so a binding can sit fractionally past its collection's recorded frontier; anything at or beyond it counts as caught up. `lastPublishedAt` is only recorded by a capture running on the V2 runtime, which is a stricter condition than the backlog's — a materialization can be on V2 while the captures feeding it are not. Where nothing can be compared the row is left out rather than shown empty. Durations are spelled out to two units, so a long lag reads as "1 day, 6 hours" instead of trailing minutes and seconds that add nothing at that scale.
Marketing flagged this as a problem in the GTM configuration for approved events: GTM reports connect-src violations against https://ad.doubleclick.net, so the tag cannot fire. The existing doubleclick entries cover the other directives (stats.g.doubleclick.net in connect-src, td.doubleclick.net in frame-src, *.g.doubleclick.net in script-src), but nothing permitted XHR/beacon traffic to ad.doubleclick.net. Kept inline on the existing connect-src line, per the warning in this file that newlines in the CSP block previously broke prod.
The admin area's four pages were reached through a tab bar rendered at the top of each page. They now appear as sub-items of the Admin entry in the sidebar, so the sidebar shows where you are within the section instead of the page repeating its own navigation. While the sidebar is expanded, the sub-items list beneath the active section as indented labels without icons, and the section header drops its selected background so only the current page reads as selected. On the collapsed rail there is no room for the labels, so hovering or focusing the section opens them in a flyout beside the rail; the flyout closes on a short delay to let the pointer reach it, and ArrowRight moves focus into it for keyboard users. Billing remains gated on hasAnyAccess, as it was in the tab bar. The access grants page title is now "Account Access" to match its nav label, which the tab bar had already been using. NavigationTabProps is no longer exported now that the deleted admin tabs were its only outside consumer, and NavLink stays local to NavItems since the sidebar reaches it through the wrapper.
Service accounts are non-human identities that hold their own grants and API keys, so automation can authenticate as a durable principal with a scoped capability set. This adds the admin UI for managing them, replacing the Admin > API tab. Admin > Service accounts (`/admin/serviceAccounts`): - List of accounts as cards, with an empty state that offers both direct and guided creation - Details view (`/admin/serviceAccounts/details`) with an API keys section and a grants section, each supporting create and revoke/remove - CreateDialog for the full account creation flow (name, capability selection, prefix grants, key lifetime), plus CreateApiKeyDialog and GrantDialog for adding to an existing account - SecretRevealModal shows a newly minted key once; the value is deliberately not cached - ExpiryWarning and UsageIndicator surface key lifetime and usage at a glance - Accounts get a stable monogram avatar color derived from their name (`src/utils/stableColor.ts`), and generated account names come from an alliterative word list (`src/utils/alliterate.ts`) GraphQL (`src/api/gql/serviceAccounts.ts`) covers the serviceAccounts query and the createServiceAccount, createApiKey, revokeApiKey, revokeAllApiKeys, addServiceAccountGrant and removeServiceAccountGrant mutations, with URQL cache entries for ServiceAccount, ServiceAccountApiKey, UserGrant, and a null-keyed CreateApiKeyResult so the one-time secret stays uncached. The personal refresh-token UI moves out of the admin area to `/settings/personalTokens`, reachable from the user menu, and the flowctl auth flow gets its own page at `/flowctl/accessToken`.
List: cap the account cards at 300px wide (`repeat(auto-fill, minmax(240px, 400px))` for the main grid, `minmax(200px, 300px)` for the "No access" compacts) so they keep a consistent size instead of stretching to fill the row. CreateDialog: the full-name preview's color is derived from the name, so it changed on every keystroke. The text still updates immediately, but the color now settles 300ms after typing stops and crossfades over a second. Adds `useDebouncedValue`. Details: show the selected account's leaf name as a trailing breadcrumb segment in the content header. `TopBar` gains a `headerDetail` field, set through `usePageTitle` and rendered by `PageContainer` after a "/" separator; it holds a resolved string rather than a message id, since entity names aren't translatable. The details page reads the name from the URL param so the breadcrumb is populated on first render rather than when the query lands, and the header segment is pluralized to "Service Accounts" so the trail names the parent section. Also adds "mercurial" to the alliterative name library.
9334471 to
d1c8ac7
Compare
The schema no longer exposes `ServiceAccountsFilter`, so the query cannot narrow the list to one organization. The list now shows every account the caller may query, across organizations. Delete `useValidatedSelectedTenant`. It existed only to gate this query on a confirmed tenant. Regenerate the GraphQL types against the current schema.
Replaces the CLI - API admin page with a Service Accounts section: a list of service accounts, a create flow, and a details page for managing API keys and grants.
Stacked on #2041.