Skip to content

Commit fa494e0

Browse files
committed
Add the admin users console section
1 parent dc95822 commit fa494e0

21 files changed

Lines changed: 1830 additions & 7 deletions

apps/cloud/src/routeTree.gen.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Route as SetupMcpRouteImport } from './routes/bare/setup-mcp'
1313
import { Route as LoginRouteImport } from './routes/bare/login'
1414
import { Route as CreateOrgRouteImport } from './routes/bare/create-org'
1515
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index'
16+
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport } from './../../../packages/react/src/routes/users'
1617
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools'
1718
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits'
1819
import { Route as SecretsRouteImport } from './routes/app/secrets'
@@ -47,6 +48,12 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute =
4748
path: '/{-$orgSlug}/',
4849
getParentRoute: () => rootRouteImport,
4950
} as any)
51+
const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute =
52+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport.update({
53+
id: '/{-$orgSlug}/users',
54+
path: '/{-$orgSlug}/users',
55+
getParentRoute: () => rootRouteImport,
56+
} as any)
5057
const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute =
5158
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({
5259
id: '/{-$orgSlug}/tools',
@@ -132,6 +139,7 @@ export interface FileRoutesByFullPath {
132139
'/{-$orgSlug}/secrets': typeof SecretsRoute
133140
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
134141
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
142+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
135143
'/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
136144
'/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute
137145
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
@@ -150,6 +158,7 @@ export interface FileRoutesByTo {
150158
'/{-$orgSlug}/secrets': typeof SecretsRoute
151159
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
152160
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
161+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
153162
'/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
154163
'/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute
155164
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
@@ -169,6 +178,7 @@ export interface FileRoutesById {
169178
'/{-$orgSlug}/secrets': typeof SecretsRoute
170179
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
171180
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
181+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
172182
'/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
173183
'/{-$orgSlug}/billing_/plans': typeof Billing_DotplansRoute
174184
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
@@ -189,6 +199,7 @@ export interface FileRouteTypes {
189199
| '/{-$orgSlug}/secrets'
190200
| '/{-$orgSlug}/toolkits'
191201
| '/{-$orgSlug}/tools'
202+
| '/{-$orgSlug}/users'
192203
| '/{-$orgSlug}/'
193204
| '/{-$orgSlug}/billing/plans'
194205
| '/{-$orgSlug}/integrations/$namespace'
@@ -207,6 +218,7 @@ export interface FileRouteTypes {
207218
| '/{-$orgSlug}/secrets'
208219
| '/{-$orgSlug}/toolkits'
209220
| '/{-$orgSlug}/tools'
221+
| '/{-$orgSlug}/users'
210222
| '/{-$orgSlug}'
211223
| '/{-$orgSlug}/billing/plans'
212224
| '/{-$orgSlug}/integrations/$namespace'
@@ -225,6 +237,7 @@ export interface FileRouteTypes {
225237
| '/{-$orgSlug}/secrets'
226238
| '/{-$orgSlug}/toolkits'
227239
| '/{-$orgSlug}/tools'
240+
| '/{-$orgSlug}/users'
228241
| '/{-$orgSlug}/'
229242
| '/{-$orgSlug}/billing_/plans'
230243
| '/{-$orgSlug}/integrations/$namespace'
@@ -244,6 +257,7 @@ export interface RootRouteChildren {
244257
SecretsRoute: typeof SecretsRoute
245258
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
246259
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
260+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
247261
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
248262
Billing_DotplansRoute: typeof Billing_DotplansRoute
249263
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
@@ -281,6 +295,13 @@ declare module '@tanstack/react-router' {
281295
preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport
282296
parentRoute: typeof rootRouteImport
283297
}
298+
'/{-$orgSlug}/users': {
299+
id: '/{-$orgSlug}/users'
300+
path: '/{-$orgSlug}/users'
301+
fullPath: '/{-$orgSlug}/users'
302+
preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport
303+
parentRoute: typeof rootRouteImport
304+
}
284305
'/{-$orgSlug}/tools': {
285306
id: '/{-$orgSlug}/tools'
286307
path: '/{-$orgSlug}/tools'
@@ -397,6 +418,8 @@ const rootRouteChildren: RootRouteChildren = {
397418
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren,
398419
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute:
399420
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute,
421+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute:
422+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute,
400423
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute:
401424
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute,
402425
Billing_DotplansRoute: Billing_DotplansRoute,

apps/cloud/src/web/shell.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type React from "react";
22

33
import { Shell as SharedShell, defaultShellNavItems } from "@executor-js/react/multiplayer/shell";
4+
import { useAdminNavItems } from "@executor-js/react/multiplayer/use-admin-nav";
45
import { trackEvent } from "@executor-js/react/api/analytics";
56
import { AUTH_PATHS } from "../auth/api";
67
import { OrgMenuSlot } from "./components/org-menu-slot";
@@ -25,6 +26,12 @@ const navItems = [
2526
{ to: "/billing", label: "Billing" },
2627
];
2728

29+
// Sections only an admin of the org may open. The Users page reads the
30+
// tenant-wide admin plane, which cloud gates on an admin-role membership — so
31+
// a plain member is not shown a link that would only refuse them. The page
32+
// still renders its own denied state if one arrives by URL.
33+
const adminNavItems = [{ to: "/users", label: "Users" }];
34+
2835
// A top-level form POST, not fetch: the logout response 302s through the
2936
// WorkOS logout endpoint (ending the hosted AuthKit session) before landing
3037
// back home. A fetch would follow that chain invisibly inside the XHR and
@@ -39,10 +46,11 @@ const signOut = () => {
3946
};
4047

4148
export function Shell(props: { readonly content?: React.ReactNode }) {
49+
const items = useAdminNavItems(navItems, adminNavItems);
4250
return (
4351
<SharedShell
4452
onSignOut={signOut}
45-
navItems={navItems}
53+
navItems={items}
4654
orgMenuSlot={<OrgMenuSlot />}
4755
supportSlot={<SupportSlot />}
4856
content={props.content}

apps/host-cloudflare/tsr.routes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { consoleRoutes } from "@executor-js/react/console-routes";
1010
// this app only owns its root (the Cloudflare-Access shell). To add
1111
// app-specific routes, create web/routes/app and mount it inside the org
1212
// scope via `orgScoped: [physical("", "app")]` — the directory must exist.
13+
//
14+
// /users is excluded: this host mounts no admin-users routes (identity is
15+
// Cloudflare Access, which has no in-app role to gate on), so the page would
16+
// only ever render its denied state.
1317
export const routes = rootRoute("__root.tsx", [
14-
...consoleRoutes({ dir: "../../../../packages/react/src/routes" }),
18+
...consoleRoutes({ dir: "../../../../packages/react/src/routes", exclude: ["/users"] }),
1519
]);

apps/host-selfhost/web/routeTree.gen.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index'
13+
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport } from './../../../packages/react/src/routes/users'
1314
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools'
1415
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits'
1516
import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets'
@@ -29,6 +30,12 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute =
2930
path: '/{-$orgSlug}/',
3031
getParentRoute: () => rootRouteImport,
3132
} as any)
33+
const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute =
34+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport.update({
35+
id: '/{-$orgSlug}/users',
36+
path: '/{-$orgSlug}/users',
37+
getParentRoute: () => rootRouteImport,
38+
} as any)
3239
const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute =
3340
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({
3441
id: '/{-$orgSlug}/tools',
@@ -118,6 +125,7 @@ export interface FileRoutesByFullPath {
118125
'/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
119126
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
120127
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
128+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
121129
'/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
122130
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
123131
'/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
@@ -133,6 +141,7 @@ export interface FileRoutesByTo {
133141
'/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
134142
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
135143
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
144+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
136145
'/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
137146
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
138147
'/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
@@ -149,6 +158,7 @@ export interface FileRoutesById {
149158
'/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
150159
'/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
151160
'/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
161+
'/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
152162
'/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
153163
'/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
154164
'/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
@@ -166,6 +176,7 @@ export interface FileRouteTypes {
166176
| '/{-$orgSlug}/secrets'
167177
| '/{-$orgSlug}/toolkits'
168178
| '/{-$orgSlug}/tools'
179+
| '/{-$orgSlug}/users'
169180
| '/{-$orgSlug}/'
170181
| '/{-$orgSlug}/integrations/$namespace'
171182
| '/{-$orgSlug}/resume/$executionId'
@@ -181,6 +192,7 @@ export interface FileRouteTypes {
181192
| '/{-$orgSlug}/secrets'
182193
| '/{-$orgSlug}/toolkits'
183194
| '/{-$orgSlug}/tools'
195+
| '/{-$orgSlug}/users'
184196
| '/{-$orgSlug}'
185197
| '/{-$orgSlug}/integrations/$namespace'
186198
| '/{-$orgSlug}/resume/$executionId'
@@ -196,6 +208,7 @@ export interface FileRouteTypes {
196208
| '/{-$orgSlug}/secrets'
197209
| '/{-$orgSlug}/toolkits'
198210
| '/{-$orgSlug}/tools'
211+
| '/{-$orgSlug}/users'
199212
| '/{-$orgSlug}/'
200213
| '/{-$orgSlug}/integrations/$namespace'
201214
| '/{-$orgSlug}/resume/$executionId'
@@ -212,6 +225,7 @@ export interface RootRouteChildren {
212225
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
213226
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren
214227
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
228+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute
215229
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
216230
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
217231
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
@@ -228,6 +242,13 @@ declare module '@tanstack/react-router' {
228242
preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport
229243
parentRoute: typeof rootRouteImport
230244
}
245+
'/{-$orgSlug}/users': {
246+
id: '/{-$orgSlug}/users'
247+
path: '/{-$orgSlug}/users'
248+
fullPath: '/{-$orgSlug}/users'
249+
preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport
250+
parentRoute: typeof rootRouteImport
251+
}
231252
'/{-$orgSlug}/tools': {
232253
id: '/{-$orgSlug}/tools'
233254
path: '/{-$orgSlug}/tools'
@@ -342,6 +363,8 @@ const rootRouteChildren: RootRouteChildren = {
342363
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren,
343364
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute:
344365
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute,
366+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute:
367+
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute,
345368
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute:
346369
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute,
347370
DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute:

apps/host-selfhost/web/routes/__root.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "@executor-js/react/components/card";
1717
import { AuthProvider, useAuth } from "@executor-js/react/multiplayer/auth-context";
1818
import { Shell, defaultShellNavItems } from "@executor-js/react/multiplayer/shell";
19+
import { useAdminNavItems } from "@executor-js/react/multiplayer/use-admin-nav";
1920
import { plugins as clientPlugins } from "virtual:executor/plugins-client";
2021

2122
import { authClient } from "../auth-client";
@@ -46,6 +47,13 @@ const selfHostNavItems = [
4647
{ to: "/admin", label: "Admin" },
4748
];
4849

50+
// Sections only an owner/admin of the instance may open. Users reads the
51+
// tenant-wide admin plane, gated on a Better Auth owner/admin member, so a
52+
// plain member is not shown a link that would only refuse them. (The existing
53+
// /admin entry predates this and stays unconditional — it is this instance's
54+
// member/invite page, and its own notice covers a non-admin who opens it.)
55+
const selfHostAdminNavItems = [{ to: "/users", label: "Users" }];
56+
4957
const signOut = async () => {
5058
await authClient.signOut();
5159
window.location.href = "/";
@@ -150,12 +158,13 @@ function AuthGate({ children }: { children: ReactNode }) {
150158
function AuthenticatedApp() {
151159
const auth = useAuth();
152160
const organization = auth.status === "authenticated" ? (auth.organization ?? null) : null;
161+
const navItems = useAdminNavItems(selfHostNavItems, selfHostAdminNavItems);
153162

154163
// Single-org instance: a bare URL canonicalizes onto the instance org's
155164
// slug. There's only ever one org, so no other slug is reachable.
156165
const gated = (
157166
<>
158-
<Shell onSignOut={signOut} navItems={selfHostNavItems} />
167+
<Shell onSignOut={signOut} navItems={navItems} />
159168
<Toaster />
160169
</>
161170
);

0 commit comments

Comments
 (0)