Skip to content

Commit cf07c40

Browse files
authored
feat: run the orchestration UI shell on the live control API (#1328)
* feat(ui): add the orchestration UI prototype as a SvelteKit app Port the prototype pages, components and styles into ui/ as a static single-page app, with neutral example owners and data. * fix(ui): resolve the prototype's type-check errors Wire the lineage hover handlers to the current event names, declare the run gantt element refs as state and add Node types for the generated tsconfig. * feat(ui): add a typed API client layer with a mock implementation Pages and the shell read through an ApiClient interface shaped like the server API, with changed-signal subscriptions for live refetches. A mock client provides neutral example data, a coalescing warehouse sync and per-target trigger state. * feat(ui): add the orchestration shell with location and target switchers The sidebar follows the new navigation with capability-gated admin items and live counts. The top bar shows location / target, connection state, snapshot age and warehouse sync freshness with a coalescing refresh, above a platform-alert banner. Pages without content yet render a not-built state. * feat(ui): read prototype pages through the API client Overview, Runs, Jobs, Triggers, Checks and Connections load from the client and refetch on changed signals. Run statuses use the orchestration vocabulary, trigger state shows the code default, and gated actions are disabled without the matching capability. * feat(ui): add platform and admin API types and mock client * feat(ui): gate the warehouse refresh behind warehouse.sync * feat(ui): build the Alerts page with platform health and event history * feat(ui): build Locations and rework Connections into warehouse and providers * feat(ui): build the Maintenance page from synced migration and janitor state * feat(ui): build Admin pages and read-only Settings * feat(ui): rebuild the runs list, queue, timeline and run detail Runs list with the design's statuses and kinds, trigger and tick links, requested-by, node counts and nullable cost, with group parents expanding to their child runs. A Queue tab shows why each queued run waits (write claims, full concurrency groups, priority, upstream jobs) and its overlap outcome. The timeline projects schedule and calendar ticks only and marks covered and skipped ticks. Run detail keeps the Gantt, status rail and tabs, and now shows nodes with skipped-because-of across jobs (kept in the graph), Recall events and scheduler facts, output, per-statement SQL, liveness, cancel progress, provenance and invocations. Cancel, Rerun and Rerun from failure are capability-gated. The mock client gains an operate store that derives node outcomes, events and run status from run records, and a small scheduler loop so claims, cancels and launches progress like they do on the server. * feat(ui): rebuild the run panel from the CLI command table The panel's verbs and flags are generated from the CLI's command table by scripts/extract-command-table.mjs (npm run gen:command-table; --check fails when the generated file is stale, for CI). Selectors autocomplete from the snapshot's nodes. Before launch the server check shows the resolved selection, the write set, run.adhoc coverage (refused as a whole, listing out-of-scope nodes), whether run.rewrite_history is needed, and the claims the run would queue behind. The target is locked to the target switcher, and launching opens the queued run. * feat(ui): show jobs and job groups with their code declarations One list for jobs and groups with a kind filter and no create button. Detail shows the read-only declaration and its source file, the resolved nodes, settings, overlap, on_failure, concurrency groups with current holders, priority, targets and owner; for groups, the members in their lineage-derived order with after edges and critical nodes. Attached triggers can be paused or enabled (an audited state fact), recent runs are listed, and Launch takes an optional cursor window or full refresh gated by run.rewrite_history. * feat(ui): show triggers and sensors with tick logs and evaluations The list covers cron and calendar schedules, built-in job, node and platform-event sensors and custom @sensors, with running or paused state against the code default, reset to code default, and bulk pause or enable recorded as audited facts with an optional reason. Detail shows the tick log (requested, skipped, covered by run X, failed, deferred, late and catch-up), next due times, the calendar's stored horizon with its low-horizon warning, and for sensors the condition, evaluations (requested, completed, failed, timed_out, abandoned), the cursor with a reset gated by sensor.reset_cursor, run requests by run key, a code-changed-since-last-evaluation notice and the override history. * fix(ui): explain queued group children waiting for their parent run A child run whose group run hasn't been claimed now says so, instead of reading as waiting for an upstream job. The README documents regenerating the run panel's command-table extract. * feat(ui): split run kind into what runs and why it was requested Runs now carry kind (job, group, adhoc) and origin (manual, schedule, sensor, rerun). Group children are job runs that inherit the parent's origin. The run list gains an origin filter and labels show both. * refactor(ui): drop the deferred tick outcome Tick outcomes are requested, skipped, covered and failed. The tick that could not be evaluated while the code location was unavailable is now a failed tick. * fix(ui): point the overdue tick alert at an overdue schedule The tick_overdue event named activity_microbatch, which ticks normally. A new hourly segments_hourly schedule has its last tick 72 minutes ago and no evaluation since, so its next due time is in the past and the triggers list shows it overdue with a watchdog warning. Platform fixtures that still named the removed nightly_build job now point at existing jobs and nodes. * feat(ui): disable users and create service accounts Users admin gains Disable (revokes the account's sessions, confirmed first) and New service account (name and description; it can then be issued API tokens). Both need user.manage and are audited by the mock. The mock refuses disabling yourself and removing your own last admin assignment, recording the refusal as a denied audit entry, and the roles page flags that assignment with a stronger warning. * refactor(ui): delete the superseded connection list API The reworked Connections page reads the connections overview, so the unused listConnections endpoint, the old Connection type and its fixtures go. Mock principals are imported from principals.ts directly. * feat(ui): re-enable disabled accounts * feat(ui): rebuild the overview on a BUILD data layer Add the BUILD block to the API client (overview, catalog nodes, column lineage, checks, evidence and the snapshot change feed) with wire types in types/build.ts and a mock that derives build state from the run history, builds outside the scheduler and the change feed. The overview now shows typed attention items (failed and partial runs, stale nodes split into changed and behind with a gated Rebuild, late sources, platform events, unresponsive runs, blocked queue entries, low calendar horizons and pending role approvals), metrics with partial runs counted separately and 24-hour cost against a rolling baseline where the adapter reports cost, build status by group, and recent snapshots with their git author. * feat(ui): show latest check results with history and audited evidence Checks list the latest audit result per check with severity, run scope phase, evaluation mode, outcome and violations or measured value, plus a strip of recent outcomes. Each check has a page with its history, the executed SQL (view.sql) and stored evidence rows (view.evidence, audited). Run checks opens the run panel on sqb audit over the visible checks' nodes. * feat(ui): back lineage and catalog with build state from the API Lineage and Catalog now read catalog nodes from the client instead of inline prototype data. Last outcome and stale reasons (changed, changed upstream, behind) are shown separately, build age is labelled Last built, and drift and SQL compare against the definitions stored with each fingerprint in this target; builds outside the scheduler show their fingerprint run and time. Nodes carry an owner list that can be filtered. The catalog page gains Drift, Runs, Checks, Warehouse (object, declared against actual columns, row statistics and microbatch coverage) and History (renames, former names and old-name views) tabs. Graph selections and the toolbar filters feed the run panel, and the large-graph canvas is kept. * feat(ui): show the snapshot change feed per deploy Changes lists each published snapshot with its generation, git revision, author and publisher: nodes added, removed, changed or renamed with their column changes, jobs, triggers and sensors, roles with the nodes they now cover, and environment fingerprint changes. Entries link to the affected nodes and to the runs that followed, and show per-run cost changes by job where the adapter reports cost. * feat(ui): replace Alerts with a Health page and drop notification channels Alerting is done by sensors, and where their messages go is an ordinary provider used by the sensor's code, so the separate channel concept is gone: no Channels panels, types, client method or mock data. A chat_webhook provider shows the pattern, with the platform-event sensor as its user. The Alerts page becomes Health under Platform. It shows the scheduler lease, server replicas, the heartbeat and the read-only server-side platform-event sink, followed by the platform event history. Each event links to the trigger, job, run or location it affects and to the sensors that reacted to it, with how each handled it. The banner, overview attention items and nav badge now point at /health. * feat(ui): redesign the runs list with status tabs, URL filters and bulk actions * feat(ui): redesign run detail with header actions, failures and a filtering timeline * feat(ui): fold server health and problem history into Locations * feat(ui): give providers their own pages apart from warehouse connections * feat(ui): split people from service accounts and drop personal API tokens * feat(ui): rewrite page copy in plain language * fix(ui): match check wording to how audits run * fix(ui): use the shared checkbox style on the runs list * feat(ui): deploy each location against exactly one target A location is now one project deployed against one target, so staging and dev become their own locations. The sidebar keeps a single location switcher that reads "name · target · adapter", the target switcher and the ?target= deep link are gone, and the data layer scopes everything by location only. The location page drops the Targets, Warehouse sync and processes tables and the side rail: the header shows the deployed version, a one-line process summary (listing processes only when none or several run), the warehouse connection and a collapsible Details; access tokens move into the page. * feat(ui): report warehouse sync failures as location problems Adds the warehouse_sync_failed platform event kind, labelled "Warehouse sync failing", so a sync that can't read one of SQLBuild's tables shows up in the location's Problems and the banner instead of a per-table sync table. The staging example location demonstrates it. * fix(ui): show open location problems by default and hide tokens without access * feat(ui): model custom roles as a base role plus additions Roles declare the built-in role they extend and their additions. Assignments carry their source (manual, SSO group, policy file or built-in), denials carry a decision ID, and approvals carry the definition diff and who gains what. Adds a shared permission catalog with a plain name and risk for every action, used by the Requires hints. * feat(ui): explain access with the chain, nearest miss and denial IDs One access checker for Settings, person pages and a Check access page. It accepts a denial ID from a permission error, shows the assignment, role, grant and scope behind a yes, and for a no the nearest miss and whether a pending change would allow it. * feat(ui): make People the default admin page with per-person access Users becomes People: role per location with source chips and filters for role, location, source and risky actions. Each person's page lists their roles (derived ones locked), an effective-access grid whose marks open the reason, recent activity and disable or enable. Giving a role is limited to roles you hold. * feat(ui): list service accounts in a table with an account page The same shape as People plus owner, token count and last use. Each account's page has its tokens, roles, effective access and recent activity. * feat(ui): split Roles into roles, SSO mappings and approvals Roles show base plus additions, where they are defined and who has them; each role's page explains its additions with risk tags, what its selectors match today and their growth, and can compare with the base role. SSO mappings are read-only. Approvals show who gains what, the definition diff and narrowing already applied, need a comment, and block approving your own commit. * feat(ui): show plain action names and denial IDs in the audit log Actions read in plain words with the code on hover. Denied rows show their copyable decision ID and a Check this link to the access checker. * fix(ui): check approval refusals as the role permission behind them * fix(ui): label config-assigned roles as server config and the default role plainly * feat(ui): match check, maintenance, coverage and sync reads to the control API A check's SQL and execution error come from a separate audited view.sql read, loaded on request, because driver errors quote SQL. Evidence cells can be booleans and columns are the rows' sorted keys. A move discovered from the warehouse has no origin model, so pages show its origin relation. Microbatch coverage bounds can be integer or timestamp text, the frontier can be missing, and the panel says coverage is tracked only for concurrent microbatch models, with known gaps meaning holes between completed partitions. The warehouse sync runs on the location's schedule, so the shell shows when it last synced and the on-demand refresh and its permission are gone. * build(ui): sync the lockfile with package.json npm ci refused the lockfile because two optional build dependencies were missing from it. Regenerated with npm install; the locked @lucide/svelte stays at 1.17.0, which type-checks cleanly. * feat(ui): drop role approvals and service accounts The control server has neither: roles defined in code apply when their snapshot is published, and people sign in with single sign-on, a password or sqb login. The approvals tab, its banner, attention item, nav badge and change-feed tag go, along with the service-account screens, API tokens, the token.manage permission and the service-account principal kind. Mock CI and reporting actors become ordinary people. * wip: person bootstrap read and person-authorized change feed Checkpoint, not ready for review. The grant decision is now a pure function shared by authorize_person (which still audits denials) and an unaudited capability read. GET /api/control/bootstrap lists the locations a person can view; the person change feed needs View on every node of the target, rechecks the session and grants before each notice, and drops per-run notices. Validated: targeted orchestration tests on SQLite and PostgreSQL, clippy, fensu, and the TypeScript client check and test. No UI changes yet. * fix: name no nodes in refused listings A refused location listing named the nodes the person could not view and read differently for a location that does not exist. Listing refusals now carry one fixed message with the decision ID, identical for unassigned and unknown locations. The audit event keeps the missing nodes. * wip: live shell UI on the control API Checkpoint, not ready for review. The UI talks to the server unless ?mock or VITE_SQB_MOCK=1 selects the mock: password sign-in with return to the requested page, sign-out, the bootstrap's locations in the switcher, the selected location's change feed with live, reconnecting and offline states, and navigation entries hidden when the server has no endpoint for them. Validated: svelte-check, and a browser walk-through against sqb server with a SQLite store: login rejection and success, location switch, server outage and recovery, sign-out, and a person with no roles. No UI unit tests yet. * fix: name no nodes in authorization denials Every refusal from person authorization now shows fixed text per action and its decision ID; the audit event keeps the missing nodes. Unknown jobs and triggers are refused only after the caller is authorized for the target, so an unknown object reads the same as a forbidden one. * test(ui): unit-test the change feed, navigation gating and sign-in redirect The sign-in redirect now resolves next against a fixed origin, so backslash and control-character variants of //host are refused too. * ci: type-check and unit-test the UI when it changes * ci: run the UI job when the verify workflow changes * fix(ui): stop retrying a refused change feed and dot-segment redirects Before a retry the feed now asks the feed URL itself and stops on 401 or 403. The bootstrap lists a location when any node is viewable, so a partial viewer retried the refused feed forever, auditing each refusal. The sign-in redirect also refuses paths that resolve to //host. * ci: run the UI job when the generated control client changes
1 parent 04e98cb commit cf07c40

301 files changed

Lines changed: 36382 additions & 121 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/verify.yml‎

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
rules_performance: ${{ steps.scope.outputs.rules_performance }}
3030
wheels: ${{ steps.scope.outputs.wheels }}
3131
recovery: ${{ steps.scope.outputs.recovery }}
32+
ui: ${{ steps.scope.outputs.ui }}
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:
@@ -54,11 +55,12 @@ jobs:
5455
echo "compiler_differential=$8"
5556
echo "clickhouse=$9"
5657
echo "recovery=${10}"
58+
echo "ui=${11}"
5759
} >> "${GITHUB_OUTPUT}"
5860
}
5961
6062
if [[ "${EVENT_NAME}" != "pull_request" && "${EVENT_NAME}" != "merge_group" ]]; then
61-
write_scope true true true true true true true true true true
63+
write_scope true true true true true true true true true true true
6264
exit 0
6365
fi
6466
@@ -70,7 +72,7 @@ jobs:
7072
if [[ -z "${BASE_SHA}" || -z "${HEAD_SHA}" ]] || \
7173
! git diff --name-only -z "${BASE_SHA}...${HEAD_SHA}" > "${changed_paths}"; then
7274
echo "Could not diff ${BASE_SHA:-<none>}...${HEAD_SHA:-<none>}; running every suite" >&2
73-
write_scope true true true true true true true true true true
75+
write_scope true true true true true true true true true true true
7476
exit 0
7577
fi
7678
@@ -116,7 +118,7 @@ jobs:
116118
&& "${HEAD_REF}" == release-please--branches--* ]] && release_metadata_only; then
117119
echo "Release Please pull request changes only release metadata; skipping the suites" \
118120
| tee -a "${GITHUB_STEP_SUMMARY}"
119-
write_scope false false false false false false false false false false
121+
write_scope false false false false false false false false false false false
120122
exit 0
121123
fi
122124
@@ -129,6 +131,7 @@ jobs:
129131
wheels=false
130132
compiler_differential=false
131133
recovery=false
134+
ui=false
132135
broad=false
133136
changed=false
134137
# A change that only touches the website, root-level Markdown, the README demo GIFs or the
@@ -149,6 +152,13 @@ jobs:
149152
wheels=true
150153
;;
151154
esac
155+
case "${path}" in
156+
ui/*|clients/typescript/*|.github/workflows/verify.yml)
157+
# The web UI is not packaged; its own checks and unit tests cover it. It compiles
158+
# against the generated control client, so client changes run them too.
159+
ui=true
160+
;;
161+
esac
152162
case "${path}" in
153163
crates/sqlbuild-orchestration/*|crates/sqlbuild-process-ownership/*|crates/sqlbuild-cli/*|Cargo.toml|Cargo.lock|.github/workflows/verify.yml)
154164
recovery=true
@@ -201,6 +211,8 @@ jobs:
201211
;;
202212
tests/unit/*|tests/integration/*)
203213
;;
214+
ui/*)
215+
;;
204216
.github/workflows/verify.yml|Makefile)
205217
performance=true
206218
format_performance=true
@@ -215,7 +227,7 @@ jobs:
215227
done < "${changed_paths}"
216228
217229
if [[ "${changed}" != "true" ]]; then
218-
write_scope true true true true true true true true true true
230+
write_scope true true true true true true true true true true true
219231
exit 0
220232
fi
221233
@@ -241,7 +253,8 @@ jobs:
241253
"${wheels}" \
242254
"${compiler_differential}" \
243255
"${clickhouse}" \
244-
"${recovery}"
256+
"${recovery}" \
257+
"${ui}"
245258
246259
lockfile:
247260
name: Lockfile consistency
@@ -1110,6 +1123,28 @@ jobs:
11101123
- name: Verify packaged Compose deployment and operational recovery
11111124
run: make test-compose-packaged
11121125

1126+
ui:
1127+
name: UI checks and unit tests
1128+
needs: changes
1129+
if: needs.changes.outputs.ui == 'true'
1130+
runs-on: ubuntu-24.04
1131+
defaults:
1132+
run:
1133+
working-directory: ui
1134+
steps:
1135+
- uses: actions/checkout@v4
1136+
- uses: actions/setup-node@v4
1137+
with:
1138+
node-version: "22"
1139+
cache: npm
1140+
cache-dependency-path: ui/package-lock.json
1141+
- name: Install dependencies
1142+
run: npm ci
1143+
- name: Type-check
1144+
run: npm run check
1145+
- name: Unit tests
1146+
run: npm test
1147+
11131148
verify:
11141149
# The required Verify of a Release Please pull request is the commit status that the trusted
11151150
# release checks in release-please.yml post on the branch's current head, after the lockfile
@@ -1145,6 +1180,7 @@ jobs:
11451180
clickhouse,
11461181
wheels,
11471182
compose,
1183+
ui,
11481184
]
11491185
runs-on: ubuntu-24.04
11501186
steps:
@@ -1171,6 +1207,8 @@ jobs:
11711207
CLICKHOUSE_RESULT: ${{ needs.clickhouse.result }}
11721208
WHEELS_RESULT: ${{ needs.wheels.result }}
11731209
COMPOSE_RESULT: ${{ needs.compose.result }}
1210+
UI_SELECTED: ${{ needs.changes.outputs.ui }}
1211+
UI_RESULT: ${{ needs.ui.result }}
11741212
run: |
11751213
if [[ "${CHANGES_RESULT}" != "success" || "${LOCKFILE_RESULT}" != "success" ]]; then
11761214
echo "Required classification or lockfile checks did not pass" >&2
@@ -1209,4 +1247,9 @@ jobs:
12091247
exit 1
12101248
fi
12111249
done
1250+
if [[ "${UI_RESULT}" != "success" ]] && \
1251+
! [[ "${UI_SELECTED}" != "true" && "${UI_RESULT}" == "skipped" ]]; then
1252+
echo "Required UI checks did not pass: ${UI_RESULT}" >&2
1253+
exit 1
1254+
fi
12121255
echo "All path-relevant verification jobs passed"

‎clients/typescript/src/schema.d.ts‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ export interface paths {
194194
patch?: never;
195195
trace?: never;
196196
};
197+
"/api/control/bootstrap": {
198+
parameters: {
199+
query?: never;
200+
header?: never;
201+
path?: never;
202+
cookie?: never;
203+
};
204+
get: operations["readBootstrap"];
205+
put?: never;
206+
post?: never;
207+
delete?: never;
208+
options?: never;
209+
head?: never;
210+
patch?: never;
211+
trace?: never;
212+
};
197213
"/api/control/locations/{location_id}/targets/{target}/catalog": {
198214
parameters: {
199215
query?: never;
@@ -210,6 +226,22 @@ export interface paths {
210226
patch?: never;
211227
trace?: never;
212228
};
229+
"/api/control/locations/{location_id}/targets/{target}/changes": {
230+
parameters: {
231+
query?: never;
232+
header?: never;
233+
path?: never;
234+
cookie?: never;
235+
};
236+
get: operations["subscribeChanges"];
237+
put?: never;
238+
post?: never;
239+
delete?: never;
240+
options?: never;
241+
head?: never;
242+
patch?: never;
243+
trace?: never;
244+
};
213245
"/api/control/locations/{location_id}/targets/{target}/checks": {
214246
parameters: {
215247
query?: never;
@@ -822,6 +854,11 @@ export interface paths {
822854
export type webhooks = Record<string, never>;
823855
export interface components {
824856
schemas: {
857+
/**
858+
* @description Closed permission vocabulary; unknown actions fail deserialization.
859+
* @enum {string}
860+
*/
861+
Action: "view" | "view.sql" | "view.evidence" | "job.run" | "run.cancel" | "run.rerun" | "run.rewrite_history" | "trigger.toggle" | "sensor.reset_cursor" | "run.adhoc" | "connection.test" | "user.manage" | "role.assign" | "audit.view";
825862
/**
826863
* @description Administrator membership and person eligibility share the final-admin invariant.
827864
* @enum {string}
@@ -955,6 +992,11 @@ export interface components {
955992
/** Format: uuid */
956993
request_id: string;
957994
};
995+
/** @description What the UI needs first: how people sign in, and what this person may see and do. */
996+
ControlBootstrap: {
997+
access: components["schemas"]["PersonAccess"];
998+
login_mode: components["schemas"]["LoginMode"];
999+
};
9581000
/** @description One stretch of the cursor, as raw bound texts. */
9591001
CoverageBatch: {
9601002
end: string;
@@ -1057,6 +1099,14 @@ export interface components {
10571099
target: string;
10581100
window?: null | components["schemas"]["RunWindow"];
10591101
};
1102+
/** @description One location target the person can view, with the actions allowed on at least one of its nodes. */
1103+
LocationAccess: {
1104+
actions: components["schemas"]["Action"][];
1105+
/** Format: uuid */
1106+
location_id: string;
1107+
name: string;
1108+
target: string;
1109+
};
10601110
/** @description Locations report bounded facts, never connection strings or user exception text. */
10611111
LocationHealth: {
10621112
/** @enum {string} */
@@ -1166,6 +1216,11 @@ export interface components {
11661216
/** Format: uuid */
11671217
session_id: string;
11681218
};
1219+
/**
1220+
* @description How people sign in to this server.
1221+
* @enum {string}
1222+
*/
1223+
LoginMode: "password" | "trusted_proxy" | "oidc";
11691224
/** @description Recorded moves, their old-name view steps and janitor cleanups, newest moves first. */
11701225
MaintenanceHistory: {
11711226
/** @description Empty when the history is for one model; cleanups are not tied to models. */
@@ -1292,6 +1347,14 @@ export interface components {
12921347
csrf_token: string;
12931348
identity: components["schemas"]["SessionIdentity"];
12941349
};
1350+
/** @description The person's name, the actions allowed server-wide, and the location targets they can view. */
1351+
PersonAccess: {
1352+
actions: components["schemas"]["Action"][];
1353+
locations: components["schemas"]["LocationAccess"][];
1354+
name: string;
1355+
/** Format: uuid */
1356+
principal_id: string;
1357+
};
12951358
/** @description Observed physical evidence is not a live warehouse-existence guarantee. */
12961359
PhysicalEvidence: {
12971360
observation: components["schemas"]["CatalogObservation"];
@@ -2336,6 +2399,32 @@ export interface operations {
23362399
};
23372400
};
23382401
};
2402+
readBootstrap: {
2403+
parameters: {
2404+
query?: never;
2405+
header?: never;
2406+
path?: never;
2407+
cookie?: never;
2408+
};
2409+
requestBody?: never;
2410+
responses: {
2411+
200: {
2412+
headers: {
2413+
[name: string]: unknown;
2414+
};
2415+
content: {
2416+
"application/json": components["schemas"]["ControlBootstrap"];
2417+
};
2418+
};
2419+
/** @description Session rejected or person disabled */
2420+
401: {
2421+
headers: {
2422+
[name: string]: unknown;
2423+
};
2424+
content?: never;
2425+
};
2426+
};
2427+
};
23392428
readPersonCatalog: {
23402429
parameters: {
23412430
query: {
@@ -2389,6 +2478,43 @@ export interface operations {
23892478
};
23902479
};
23912480
};
2481+
subscribeChanges: {
2482+
parameters: {
2483+
query?: never;
2484+
header?: never;
2485+
path: {
2486+
location_id: string;
2487+
target: string;
2488+
};
2489+
cookie?: never;
2490+
};
2491+
requestBody?: never;
2492+
responses: {
2493+
/** @description changed events: all, runs, ticks, snapshot:<location>, node_status:<target>. Refetch REST on all/reconnect; no durable cursor. */
2494+
200: {
2495+
headers: {
2496+
[name: string]: unknown;
2497+
};
2498+
content: {
2499+
"text/event-stream": components["schemas"]["ChangeNotice"];
2500+
};
2501+
};
2502+
/** @description Session rejected or person disabled */
2503+
401: {
2504+
headers: {
2505+
[name: string]: unknown;
2506+
};
2507+
content?: never;
2508+
};
2509+
/** @description View does not cover every node of this target */
2510+
403: {
2511+
headers: {
2512+
[name: string]: unknown;
2513+
};
2514+
content?: never;
2515+
};
2516+
};
2517+
};
23922518
listPersonChecks: {
23932519
parameters: {
23942520
query?: never;

‎crates/sqlbuild-orchestration/src/api/_helpers/authentication/configure.rs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::net::SocketAddr;
55
use axum::{Extension, Router};
66

77
use crate::api::_helpers::authentication::{oidc, password_sessions, trusted_proxy};
8-
use crate::api::models::{ApiAuthentication, DevelopmentScope, GroupRole, HumanLogin};
8+
use crate::api::models::{ApiAuthentication, DevelopmentScope, GroupRole, HumanLogin, LoginMode};
99
use crate::store::errors::ControlError;
1010
use crate::store::models::ControlStore;
1111

@@ -28,6 +28,11 @@ pub(crate) async fn configure(
2828
HumanLogin::Oidc(settings) => settings.group_roles.clone(),
2929
_ => Vec::new(),
3030
};
31+
let login_mode = match &login {
32+
HumanLogin::Oidc(_) => LoginMode::Oidc,
33+
HumanLogin::TrustedProxy(_) => LoginMode::TrustedProxy,
34+
_ => LoginMode::Password,
35+
};
3136
let (development, routes) = match login {
3237
HumanLogin::None => (None, Router::new()),
3338
HumanLogin::Development(scope) => {
@@ -44,7 +49,9 @@ pub(crate) async fn configure(
4449
};
4550
Ok((
4651
development,
47-
routes.layer(Extension(default_role)),
52+
routes
53+
.layer(Extension(default_role))
54+
.layer(Extension(login_mode)),
4855
group_roles,
4956
))
5057
}

‎crates/sqlbuild-orchestration/src/api/_helpers/authentication/password_sessions.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ pub(crate) fn session_routes(store: &ControlStore) -> Router<ControlStore> {
4949
pub(crate) fn person_routes() -> Router<ControlStore> {
5050
Router::new()
5151
.route("/api/auth/cli/approve", post(super::cli_login::approve))
52+
.route(
53+
"/api/control/bootstrap",
54+
get(crate::api::_helpers::resources::person_bootstrap::read),
55+
)
56+
.route(
57+
"/api/control/locations/{location_id}/targets/{target}/changes",
58+
get(crate::api::_helpers::resources::changes::subscribe_person),
59+
)
5260
.route(
5361
"/api/control/audit",
5462
get(crate::api::_helpers::resources::audit::read),

0 commit comments

Comments
 (0)