@invect/user-auth 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -72
- package/dist/backend/index.cjs +410 -54
- package/dist/backend/index.cjs.map +1 -1
- package/dist/backend/index.d.cts +456 -0
- package/dist/backend/index.d.cts.map +1 -0
- package/dist/backend/index.d.mts +456 -0
- package/dist/backend/index.d.mts.map +1 -0
- package/dist/backend/index.d.ts +28 -18
- package/dist/backend/index.d.ts.map +1 -1
- package/dist/backend/index.mjs +408 -53
- package/dist/backend/index.mjs.map +1 -1
- package/dist/backend/plugin.d.ts +15 -15
- package/dist/backend/plugin.d.ts.map +1 -1
- package/dist/backend/types.d.ts +85 -9
- package/dist/backend/types.d.ts.map +1 -1
- package/dist/frontend/components/ApiKeysDialog.d.ts +17 -0
- package/dist/frontend/components/ApiKeysDialog.d.ts.map +1 -0
- package/dist/frontend/components/AuthenticatedInvect.d.ts +10 -10
- package/dist/frontend/components/SignInForm.d.ts.map +1 -1
- package/dist/frontend/components/SignInPage.d.ts.map +1 -1
- package/dist/frontend/components/UserManagement.d.ts.map +1 -1
- package/dist/frontend/index.cjs +434 -58
- package/dist/frontend/index.cjs.map +1 -1
- package/dist/frontend/index.d.cts +317 -0
- package/dist/frontend/index.d.cts.map +1 -0
- package/dist/frontend/index.d.mts +317 -0
- package/dist/frontend/index.d.mts.map +1 -0
- package/dist/frontend/index.d.ts +3 -1
- package/dist/frontend/index.d.ts.map +1 -1
- package/dist/frontend/index.mjs +418 -43
- package/dist/frontend/index.mjs.map +1 -1
- package/dist/frontend/plugins/authFrontendPlugin.d.ts +2 -2
- package/dist/frontend/plugins/authFrontendPlugin.d.ts.map +1 -1
- package/dist/shared/types.d.cts +49 -0
- package/dist/shared/types.d.cts.map +1 -0
- package/dist/shared/types.d.mts +49 -0
- package/dist/shared/types.d.mts.map +1 -0
- package/package.json +68 -66
package/dist/frontend/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ let react = require("react");
|
|
|
4
4
|
let _tanstack_react_query = require("@tanstack/react-query");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
let lucide_react = require("lucide-react");
|
|
7
|
-
let
|
|
7
|
+
let _invect_ui = require("@invect/ui");
|
|
8
8
|
let react_router = require("react-router");
|
|
9
9
|
//#region src/frontend/providers/AuthProvider.tsx
|
|
10
10
|
/**
|
|
@@ -232,14 +232,14 @@ function SignInForm({ onSuccess, className }) {
|
|
|
232
232
|
})]
|
|
233
233
|
}),
|
|
234
234
|
displayError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
235
|
-
className: "rounded-md border border-
|
|
235
|
+
className: "rounded-md border border-imp-destructive/30 bg-imp-destructive/10 px-3 py-2 text-sm text-imp-destructive",
|
|
236
236
|
children: displayError
|
|
237
237
|
}),
|
|
238
238
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
239
239
|
type: "submit",
|
|
240
240
|
disabled: isSigningIn,
|
|
241
|
-
className: "inline-flex h-9 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md bg-imp-
|
|
242
|
-
children: isSigningIn ? "Signing in…" : "
|
|
241
|
+
className: "inline-flex h-9 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md bg-imp-primary px-4 py-2 text-sm font-medium text-imp-primary-foreground shadow transition-colors hover:bg-imp-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:pointer-events-none disabled:opacity-50",
|
|
242
|
+
children: isSigningIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "w-4 h-4 animate-spin" }), "Signing in…"] }) : "Sign in"
|
|
243
243
|
})
|
|
244
244
|
]
|
|
245
245
|
})
|
|
@@ -256,16 +256,16 @@ function SignInForm({ onSuccess, className }) {
|
|
|
256
256
|
*/
|
|
257
257
|
function SignInPage({ onSuccess, onNavigateToSignUp, title = "Welcome back", subtitle = "Sign in to your account to continue" }) {
|
|
258
258
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
259
|
-
className: "flex
|
|
259
|
+
className: "flex items-center justify-center min-h-screen p-4 bg-imp-background text-imp-foreground",
|
|
260
260
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
261
|
-
className: "flex w-full max-w-sm
|
|
261
|
+
className: "flex flex-col w-full max-w-sm gap-6",
|
|
262
262
|
children: [
|
|
263
263
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
264
264
|
className: "flex flex-col items-center gap-2 text-center",
|
|
265
265
|
children: [
|
|
266
266
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
267
267
|
className: "flex items-center justify-center",
|
|
268
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImpLogo, { className: "
|
|
268
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImpLogo, { className: "w-auto h-24" })
|
|
269
269
|
}),
|
|
270
270
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
271
271
|
className: "text-xl font-bold",
|
|
@@ -279,24 +279,20 @@ function SignInPage({ onSuccess, onNavigateToSignUp, title = "Welcome back", sub
|
|
|
279
279
|
}),
|
|
280
280
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignInForm, { onSuccess }),
|
|
281
281
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
282
|
-
className: "relative text-
|
|
283
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "absolute inset-0 top-1/2 border-
|
|
284
|
-
className: "relative bg-imp-background
|
|
282
|
+
className: "relative text-sm text-center",
|
|
283
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "absolute inset-0 border-t top-1/2 border-imp-border" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
284
|
+
className: "relative px-2 bg-imp-background text-imp-muted-foreground",
|
|
285
285
|
children: "Admin-managed accounts"
|
|
286
286
|
})]
|
|
287
287
|
}),
|
|
288
288
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
289
|
-
className: "px-6 text-
|
|
290
|
-
children: [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
className: "font-medium underline underline-offset-4 hover:text-imp-foreground",
|
|
297
|
-
children: "Sign up"
|
|
298
|
-
})
|
|
299
|
-
]
|
|
289
|
+
className: "px-6 text-xs text-center text-imp-muted-foreground",
|
|
290
|
+
children: [onNavigateToSignUp ? " Or " : " Contact your admin if you need access.", onNavigateToSignUp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
291
|
+
type: "button",
|
|
292
|
+
onClick: onNavigateToSignUp,
|
|
293
|
+
className: "font-medium underline underline-offset-4 hover:text-imp-foreground",
|
|
294
|
+
children: "Sign up"
|
|
295
|
+
})]
|
|
300
296
|
})
|
|
301
297
|
]
|
|
302
298
|
})
|
|
@@ -401,6 +397,359 @@ function AuthGate({ children, fallback = null, loading = null }) {
|
|
|
401
397
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
|
|
402
398
|
}
|
|
403
399
|
//#endregion
|
|
400
|
+
//#region src/frontend/components/ApiKeysDialog.tsx
|
|
401
|
+
/**
|
|
402
|
+
* ApiKeysDialog — Admin dialog for managing API keys.
|
|
403
|
+
*
|
|
404
|
+
* Displays a list of API keys with the ability to:
|
|
405
|
+
* - Create new API keys (name, optional expiry)
|
|
406
|
+
* - Copy newly created keys
|
|
407
|
+
* - Delete existing keys
|
|
408
|
+
*
|
|
409
|
+
* Only functional when the auth plugin has API keys enabled.
|
|
410
|
+
*/
|
|
411
|
+
function formatDate$1(iso) {
|
|
412
|
+
try {
|
|
413
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
414
|
+
dateStyle: "medium",
|
|
415
|
+
timeStyle: "short"
|
|
416
|
+
}).format(new Date(iso));
|
|
417
|
+
} catch {
|
|
418
|
+
return iso;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function isExpired(expiresAt) {
|
|
422
|
+
if (!expiresAt) return false;
|
|
423
|
+
return new Date(expiresAt) < /* @__PURE__ */ new Date();
|
|
424
|
+
}
|
|
425
|
+
function ApiKeysDialog({ open, onOpenChange, apiBaseUrl }) {
|
|
426
|
+
const [apiKeys, setApiKeys] = (0, react.useState)([]);
|
|
427
|
+
const [isLoading, setIsLoading] = (0, react.useState)(false);
|
|
428
|
+
const [error, setError] = (0, react.useState)(null);
|
|
429
|
+
const [hasFetched, setHasFetched] = (0, react.useState)(false);
|
|
430
|
+
const [showCreateForm, setShowCreateForm] = (0, react.useState)(false);
|
|
431
|
+
const [newlyCreatedKey, setNewlyCreatedKey] = (0, react.useState)(null);
|
|
432
|
+
const [copiedKeyId, setCopiedKeyId] = (0, react.useState)(null);
|
|
433
|
+
const [pendingDeleteId, setPendingDeleteId] = (0, react.useState)(null);
|
|
434
|
+
const authApiBase = `${apiBaseUrl}/plugins/auth`;
|
|
435
|
+
const fetchApiKeys = (0, react.useCallback)(async () => {
|
|
436
|
+
setIsLoading(true);
|
|
437
|
+
setError(null);
|
|
438
|
+
try {
|
|
439
|
+
const res = await fetch(`${authApiBase}/api-keys`, { credentials: "include" });
|
|
440
|
+
if (!res.ok) {
|
|
441
|
+
const data = await res.json().catch(() => ({ error: "Failed to fetch API keys" }));
|
|
442
|
+
throw new Error(data.error || data.message || `HTTP ${res.status}`);
|
|
443
|
+
}
|
|
444
|
+
setApiKeys((await res.json()).apiKeys ?? []);
|
|
445
|
+
setHasFetched(true);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
setError(err instanceof Error ? err.message : "Failed to fetch API keys");
|
|
448
|
+
} finally {
|
|
449
|
+
setIsLoading(false);
|
|
450
|
+
}
|
|
451
|
+
}, [authApiBase]);
|
|
452
|
+
const deleteApiKey = (0, react.useCallback)(async (keyId) => {
|
|
453
|
+
try {
|
|
454
|
+
const res = await fetch(`${authApiBase}/api-keys/${keyId}`, {
|
|
455
|
+
method: "DELETE",
|
|
456
|
+
credentials: "include"
|
|
457
|
+
});
|
|
458
|
+
if (!res.ok) {
|
|
459
|
+
const data = await res.json().catch(() => ({}));
|
|
460
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
461
|
+
}
|
|
462
|
+
setApiKeys((prev) => prev.filter((k) => k.id !== keyId));
|
|
463
|
+
setPendingDeleteId(null);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
setError(err instanceof Error ? err.message : "Failed to delete API key");
|
|
466
|
+
setPendingDeleteId(null);
|
|
467
|
+
}
|
|
468
|
+
}, [authApiBase]);
|
|
469
|
+
const copyToClipboard = (0, react.useCallback)(async (text, id) => {
|
|
470
|
+
try {
|
|
471
|
+
await navigator.clipboard.writeText(text);
|
|
472
|
+
setCopiedKeyId(id);
|
|
473
|
+
setTimeout(() => setCopiedKeyId(null), 2e3);
|
|
474
|
+
} catch {
|
|
475
|
+
const textArea = document.createElement("textarea");
|
|
476
|
+
textArea.value = text;
|
|
477
|
+
textArea.style.position = "fixed";
|
|
478
|
+
textArea.style.opacity = "0";
|
|
479
|
+
document.body.appendChild(textArea);
|
|
480
|
+
textArea.select();
|
|
481
|
+
document.execCommand("copy");
|
|
482
|
+
document.body.removeChild(textArea);
|
|
483
|
+
setCopiedKeyId(id);
|
|
484
|
+
setTimeout(() => setCopiedKeyId(null), 2e3);
|
|
485
|
+
}
|
|
486
|
+
}, []);
|
|
487
|
+
if (open && !hasFetched && !isLoading) fetchApiKeys();
|
|
488
|
+
const handleOpenChange = (nextOpen) => {
|
|
489
|
+
if (!nextOpen) {
|
|
490
|
+
setShowCreateForm(false);
|
|
491
|
+
setNewlyCreatedKey(null);
|
|
492
|
+
setError(null);
|
|
493
|
+
setPendingDeleteId(null);
|
|
494
|
+
setHasFetched(false);
|
|
495
|
+
}
|
|
496
|
+
onOpenChange(nextOpen);
|
|
497
|
+
};
|
|
498
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.Dialog, {
|
|
499
|
+
open,
|
|
500
|
+
onOpenChange: handleOpenChange,
|
|
501
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DialogContent, {
|
|
502
|
+
className: "max-w-lg border-imp-border bg-imp-background text-imp-foreground sm:max-w-lg",
|
|
503
|
+
children: [
|
|
504
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DialogTitle, {
|
|
505
|
+
className: "flex items-center gap-2 text-sm font-semibold",
|
|
506
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Key, { className: "h-4 w-4" }), "API Keys"]
|
|
507
|
+
}) }),
|
|
508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
509
|
+
className: "space-y-3",
|
|
510
|
+
children: [
|
|
511
|
+
newlyCreatedKey && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
512
|
+
className: "rounded-md border border-green-500/30 bg-green-50 p-3 dark:bg-green-950/20",
|
|
513
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
514
|
+
className: "mb-1.5 text-xs font-medium text-green-700 dark:text-green-400",
|
|
515
|
+
children: "API key created! Copy it now — it won't be shown again."
|
|
516
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
517
|
+
className: "flex items-center gap-2",
|
|
518
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
519
|
+
className: "flex-1 rounded bg-green-100 px-2 py-1 text-xs font-mono text-green-800 dark:bg-green-900/30 dark:text-green-300 break-all",
|
|
520
|
+
children: newlyCreatedKey
|
|
521
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
522
|
+
type: "button",
|
|
523
|
+
onClick: () => copyToClipboard(newlyCreatedKey, "new-key"),
|
|
524
|
+
className: "shrink-0 rounded-md border border-green-300 p-1.5 text-green-700 hover:bg-green-100 dark:border-green-700 dark:text-green-400 dark:hover:bg-green-900/40",
|
|
525
|
+
children: copiedKeyId === "new-key" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Copy, { className: "h-3.5 w-3.5" })
|
|
526
|
+
})]
|
|
527
|
+
})]
|
|
528
|
+
}),
|
|
529
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
530
|
+
className: "rounded-md bg-red-50 p-2 text-xs text-red-600 dark:bg-red-950/20 dark:text-red-400",
|
|
531
|
+
children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
532
|
+
type: "button",
|
|
533
|
+
onClick: () => setError(null),
|
|
534
|
+
className: "ml-2 underline hover:no-underline",
|
|
535
|
+
children: "Dismiss"
|
|
536
|
+
})]
|
|
537
|
+
}),
|
|
538
|
+
!showCreateForm && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
539
|
+
type: "button",
|
|
540
|
+
onClick: () => setShowCreateForm(true),
|
|
541
|
+
className: "flex items-center gap-1.5 rounded-lg border border-imp-border px-3 py-1.5 text-xs font-medium text-imp-muted-foreground transition-colors hover:border-imp-primary/50 hover:text-imp-foreground",
|
|
542
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "h-3.5 w-3.5" }), " Create API Key"]
|
|
543
|
+
}),
|
|
544
|
+
showCreateForm && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateApiKeyForm, {
|
|
545
|
+
apiBaseUrl: authApiBase,
|
|
546
|
+
onCreated: (key, fullKey) => {
|
|
547
|
+
setApiKeys((prev) => [key, ...prev]);
|
|
548
|
+
setNewlyCreatedKey(fullKey);
|
|
549
|
+
setShowCreateForm(false);
|
|
550
|
+
},
|
|
551
|
+
onCancel: () => setShowCreateForm(false)
|
|
552
|
+
}),
|
|
553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
554
|
+
className: "max-h-[300px] space-y-1.5 overflow-y-auto",
|
|
555
|
+
children: [
|
|
556
|
+
isLoading && !hasFetched && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
557
|
+
className: "flex items-center justify-center py-8 text-xs text-imp-muted-foreground",
|
|
558
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Loading…"]
|
|
559
|
+
}),
|
|
560
|
+
hasFetched && apiKeys.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
561
|
+
className: "py-8 text-center text-xs text-imp-muted-foreground",
|
|
562
|
+
children: "No API keys yet. Create one to get started."
|
|
563
|
+
}),
|
|
564
|
+
apiKeys.map((key) => {
|
|
565
|
+
const expired = isExpired(key.expiresAt);
|
|
566
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
567
|
+
className: `flex items-center justify-between rounded-lg border px-3 py-2.5 ${expired ? "border-red-300/50 bg-red-50/50 dark:border-red-800/30 dark:bg-red-950/10" : "border-imp-border bg-imp-muted/10"}`,
|
|
568
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
569
|
+
className: "min-w-0 flex-1",
|
|
570
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
571
|
+
className: "flex items-center gap-2",
|
|
572
|
+
children: [
|
|
573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
574
|
+
className: "text-sm font-medium text-imp-foreground truncate",
|
|
575
|
+
children: key.name || "Unnamed key"
|
|
576
|
+
}),
|
|
577
|
+
expired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
578
|
+
className: "shrink-0 rounded-full bg-red-100 px-1.5 py-0.5 text-[10px] font-medium text-red-700 dark:bg-red-900/30 dark:text-red-400",
|
|
579
|
+
children: "Expired"
|
|
580
|
+
}),
|
|
581
|
+
key.enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
582
|
+
className: "shrink-0 rounded-full bg-yellow-100 px-1.5 py-0.5 text-[10px] font-medium text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400",
|
|
583
|
+
children: "Disabled"
|
|
584
|
+
})
|
|
585
|
+
]
|
|
586
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
587
|
+
className: "mt-0.5 flex items-center gap-2 text-[11px] text-imp-muted-foreground",
|
|
588
|
+
children: [
|
|
589
|
+
key.start && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
590
|
+
className: "font-mono",
|
|
591
|
+
children: [
|
|
592
|
+
key.prefix ? `${key.prefix}_` : "",
|
|
593
|
+
key.start,
|
|
594
|
+
"…"
|
|
595
|
+
]
|
|
596
|
+
}),
|
|
597
|
+
key.createdAt && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["Created ", formatDate$1(key.createdAt)] }),
|
|
598
|
+
key.expiresAt && !expired && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["Expires ", formatDate$1(key.expiresAt)] })
|
|
599
|
+
]
|
|
600
|
+
})]
|
|
601
|
+
}), pendingDeleteId === key.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
602
|
+
className: "flex items-center gap-1.5 shrink-0 ml-2",
|
|
603
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
604
|
+
type: "button",
|
|
605
|
+
onClick: () => deleteApiKey(key.id),
|
|
606
|
+
className: "rounded-md bg-red-600 px-2 py-1 text-[11px] font-medium text-white hover:bg-red-700",
|
|
607
|
+
children: "Confirm"
|
|
608
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
609
|
+
type: "button",
|
|
610
|
+
onClick: () => setPendingDeleteId(null),
|
|
611
|
+
className: "rounded-md border border-imp-border px-2 py-1 text-[11px] hover:bg-imp-muted",
|
|
612
|
+
children: "Cancel"
|
|
613
|
+
})]
|
|
614
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
615
|
+
type: "button",
|
|
616
|
+
onClick: () => setPendingDeleteId(key.id),
|
|
617
|
+
className: "shrink-0 ml-2 rounded-md p-1.5 text-imp-muted-foreground transition-colors hover:bg-imp-destructive/10 hover:text-imp-destructive",
|
|
618
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, { className: "h-3.5 w-3.5" })
|
|
619
|
+
})]
|
|
620
|
+
}, key.id);
|
|
621
|
+
})
|
|
622
|
+
]
|
|
623
|
+
})
|
|
624
|
+
]
|
|
625
|
+
}),
|
|
626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogFooter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
627
|
+
type: "button",
|
|
628
|
+
onClick: () => handleOpenChange(false),
|
|
629
|
+
className: "rounded-md border border-imp-border px-3 py-1.5 text-sm hover:bg-imp-muted",
|
|
630
|
+
children: "Close"
|
|
631
|
+
}) })
|
|
632
|
+
]
|
|
633
|
+
})
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function CreateApiKeyForm({ apiBaseUrl, onCreated, onCancel }) {
|
|
637
|
+
const [name, setName] = (0, react.useState)("");
|
|
638
|
+
const [expiresIn, setExpiresIn] = (0, react.useState)("");
|
|
639
|
+
const [isSubmitting, setIsSubmitting] = (0, react.useState)(false);
|
|
640
|
+
const [error, setError] = (0, react.useState)(null);
|
|
641
|
+
const EXPIRY_OPTIONS = [
|
|
642
|
+
{
|
|
643
|
+
value: "",
|
|
644
|
+
label: "No expiry"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
value: "86400",
|
|
648
|
+
label: "1 day"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
value: "604800",
|
|
652
|
+
label: "7 days"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
value: "2592000",
|
|
656
|
+
label: "30 days"
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
value: "7776000",
|
|
660
|
+
label: "90 days"
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
value: "31536000",
|
|
664
|
+
label: "1 year"
|
|
665
|
+
}
|
|
666
|
+
];
|
|
667
|
+
const handleSubmit = async (e) => {
|
|
668
|
+
e.preventDefault();
|
|
669
|
+
setError(null);
|
|
670
|
+
setIsSubmitting(true);
|
|
671
|
+
try {
|
|
672
|
+
const body = {};
|
|
673
|
+
if (name.trim()) body.name = name.trim();
|
|
674
|
+
if (expiresIn) body.expiresIn = parseInt(expiresIn, 10);
|
|
675
|
+
const res = await fetch(`${apiBaseUrl}/api-keys`, {
|
|
676
|
+
method: "POST",
|
|
677
|
+
credentials: "include",
|
|
678
|
+
headers: { "content-type": "application/json" },
|
|
679
|
+
body: JSON.stringify(body)
|
|
680
|
+
});
|
|
681
|
+
const data = await res.json();
|
|
682
|
+
if (!res.ok) throw new Error(data.error || data.message || `HTTP ${res.status}`);
|
|
683
|
+
const fullKey = data.key ?? data.apiKey?.key ?? "";
|
|
684
|
+
onCreated({
|
|
685
|
+
id: data.id ?? data.apiKey?.id ?? "",
|
|
686
|
+
name: data.name ?? data.apiKey?.name ?? (name.trim() || null),
|
|
687
|
+
start: data.start ?? data.apiKey?.start ?? null,
|
|
688
|
+
prefix: data.prefix ?? data.apiKey?.prefix ?? null,
|
|
689
|
+
enabled: data.enabled ?? data.apiKey?.enabled ?? true,
|
|
690
|
+
expiresAt: data.expiresAt ?? data.apiKey?.expiresAt ?? null,
|
|
691
|
+
createdAt: data.createdAt ?? data.apiKey?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
692
|
+
}, fullKey);
|
|
693
|
+
} catch (err) {
|
|
694
|
+
setError(err instanceof Error ? err.message : "Failed to create API key");
|
|
695
|
+
} finally {
|
|
696
|
+
setIsSubmitting(false);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
700
|
+
onSubmit: handleSubmit,
|
|
701
|
+
className: "space-y-3 rounded-lg border border-imp-border bg-imp-muted/10 p-3",
|
|
702
|
+
children: [
|
|
703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
704
|
+
className: "text-xs font-medium text-imp-foreground",
|
|
705
|
+
children: "Create API Key"
|
|
706
|
+
}),
|
|
707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
708
|
+
className: "grid grid-cols-2 gap-3",
|
|
709
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
710
|
+
className: "mb-1 block text-xs font-medium text-imp-foreground",
|
|
711
|
+
children: "Name"
|
|
712
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
713
|
+
type: "text",
|
|
714
|
+
value: name,
|
|
715
|
+
onChange: (e) => setName(e.target.value),
|
|
716
|
+
placeholder: "e.g. Production API",
|
|
717
|
+
className: "w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:border-imp-primary/50 focus:outline-none"
|
|
718
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
719
|
+
className: "mb-1 block text-xs font-medium text-imp-foreground",
|
|
720
|
+
children: "Expiry"
|
|
721
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
722
|
+
value: expiresIn,
|
|
723
|
+
onChange: (e) => setExpiresIn(e.target.value),
|
|
724
|
+
className: "w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm focus:border-imp-primary/50 focus:outline-none",
|
|
725
|
+
children: EXPIRY_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
726
|
+
value: option.value,
|
|
727
|
+
children: option.label
|
|
728
|
+
}, option.value))
|
|
729
|
+
})] })]
|
|
730
|
+
}),
|
|
731
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
732
|
+
className: "rounded-md bg-red-50 p-2 text-xs text-red-600 dark:bg-red-950/20 dark:text-red-400",
|
|
733
|
+
children: error
|
|
734
|
+
}),
|
|
735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
736
|
+
className: "flex justify-end gap-2",
|
|
737
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
738
|
+
type: "button",
|
|
739
|
+
onClick: onCancel,
|
|
740
|
+
className: "rounded-md border border-imp-border px-3 py-1.5 text-xs hover:bg-imp-muted",
|
|
741
|
+
children: "Cancel"
|
|
742
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
743
|
+
type: "submit",
|
|
744
|
+
disabled: isSubmitting,
|
|
745
|
+
className: "rounded-md bg-imp-primary px-3 py-1.5 text-xs font-semibold text-imp-primary-foreground hover:bg-imp-primary/90 disabled:opacity-50",
|
|
746
|
+
children: isSubmitting ? "Creating…" : "Create"
|
|
747
|
+
})]
|
|
748
|
+
})
|
|
749
|
+
]
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
//#endregion
|
|
404
753
|
//#region src/frontend/components/UserManagement.tsx
|
|
405
754
|
/**
|
|
406
755
|
* UserManagement — Admin panel for managing users.
|
|
@@ -473,7 +822,7 @@ function SortHeader({ label, field, sortField, sortDir, onSort, align = "left" }
|
|
|
473
822
|
function RoleDropdown({ value, userId, disabled, onChange }) {
|
|
474
823
|
const current = require_roles.isAuthAssignableRole(value) ? value : require_roles.AUTH_DEFAULT_ROLE;
|
|
475
824
|
const currentLabel = ASSIGNABLE_ROLE_OPTIONS.find((o) => o.value === current)?.label ?? current;
|
|
476
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
825
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DropdownMenuTrigger, {
|
|
477
826
|
asChild: true,
|
|
478
827
|
disabled,
|
|
479
828
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -481,16 +830,16 @@ function RoleDropdown({ value, userId, disabled, onChange }) {
|
|
|
481
830
|
className: `inline-flex w-28 items-center justify-between gap-1.5 rounded-full border px-2.5 py-0.5 text-sm font-medium transition-colors hover:bg-imp-muted disabled:cursor-not-allowed disabled:opacity-50 ${ROLE_BADGE_CLASSES}`,
|
|
482
831
|
children: [currentLabel, !disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "w-3 h-3 shrink-0" })]
|
|
483
832
|
})
|
|
484
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
833
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DropdownMenuContent, {
|
|
485
834
|
align: "start",
|
|
486
835
|
className: "w-56",
|
|
487
836
|
children: [
|
|
488
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
837
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DropdownMenuLabel, {
|
|
489
838
|
className: "text-xs",
|
|
490
839
|
children: "Set role"
|
|
491
840
|
}),
|
|
492
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
493
|
-
ASSIGNABLE_ROLE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DropdownMenuSeparator, {}),
|
|
842
|
+
ASSIGNABLE_ROLE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DropdownMenuItem, {
|
|
494
843
|
onSelect: () => onChange(userId, option.value),
|
|
495
844
|
className: `items-start gap-0 px-2 py-2 ${current === option.value ? "bg-accent text-accent-foreground" : ""}`,
|
|
496
845
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -519,6 +868,9 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
519
868
|
const [currentPage, setCurrentPage] = (0, react.useState)(1);
|
|
520
869
|
const [sortField, setSortField] = (0, react.useState)("name");
|
|
521
870
|
const [sortDir, setSortDir] = (0, react.useState)("asc");
|
|
871
|
+
const [apiKeysEnabled, setApiKeysEnabled] = (0, react.useState)(false);
|
|
872
|
+
const [showApiKeysDialog, setShowApiKeysDialog] = (0, react.useState)(false);
|
|
873
|
+
const [hasCheckedApiKeys, setHasCheckedApiKeys] = (0, react.useState)(false);
|
|
522
874
|
const PAGE_SIZE = 10;
|
|
523
875
|
const handleSort = (0, react.useCallback)((field) => {
|
|
524
876
|
setSortField((prev) => {
|
|
@@ -562,6 +914,14 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
562
914
|
const totalPages = Math.max(1, Math.ceil(filteredUsers.length / PAGE_SIZE));
|
|
563
915
|
const paginatedUsers = filteredUsers.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
|
|
564
916
|
const authApiBase = `${apiBaseUrl}/plugins/auth`;
|
|
917
|
+
const checkApiKeys = (0, react.useCallback)(async () => {
|
|
918
|
+
try {
|
|
919
|
+
const res = await fetch(`${authApiBase}/info`, { credentials: "include" });
|
|
920
|
+
if (res.ok) setApiKeysEnabled(!!(await res.json()).apiKeysEnabled);
|
|
921
|
+
} catch {} finally {
|
|
922
|
+
setHasCheckedApiKeys(true);
|
|
923
|
+
}
|
|
924
|
+
}, [authApiBase]);
|
|
565
925
|
const fetchUsers = (0, react.useCallback)(async () => {
|
|
566
926
|
setIsLoading(true);
|
|
567
927
|
setError(null);
|
|
@@ -618,29 +978,39 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
618
978
|
}, [authApiBase]);
|
|
619
979
|
if (!isAuthenticated || user?.role !== "admin") return null;
|
|
620
980
|
if (!hasFetched && !isLoading) fetchUsers();
|
|
981
|
+
if (!hasCheckedApiKeys) checkApiKeys();
|
|
621
982
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
622
983
|
className: `space-y-4 ${className ?? ""}`,
|
|
623
984
|
children: [
|
|
624
985
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
625
986
|
className: "flex items-center gap-2",
|
|
626
|
-
children: [
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
987
|
+
children: [
|
|
988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
989
|
+
className: "relative flex-1 max-w-sm",
|
|
990
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, { className: "absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 pointer-events-none text-imp-muted-foreground" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
991
|
+
type: "text",
|
|
992
|
+
value: searchQuery,
|
|
993
|
+
onChange: (e) => {
|
|
994
|
+
setSearchQuery(e.target.value);
|
|
995
|
+
setCurrentPage(1);
|
|
996
|
+
},
|
|
997
|
+
placeholder: "Search users…",
|
|
998
|
+
className: "w-full py-2 pr-3 text-sm border rounded-lg outline-none border-imp-border bg-transparent pl-9 placeholder:text-imp-muted-foreground focus:border-imp-primary/50"
|
|
999
|
+
})]
|
|
1000
|
+
}),
|
|
1001
|
+
apiKeysEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1002
|
+
type: "button",
|
|
1003
|
+
onClick: () => setShowApiKeysDialog(true),
|
|
1004
|
+
className: "flex items-center gap-1.5 rounded-lg border border-imp-border px-3 py-2 text-sm font-medium text-imp-muted-foreground transition-colors hover:border-imp-primary/50 hover:text-imp-foreground",
|
|
1005
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Key, { className: "w-4 h-4" }), " API Keys"]
|
|
1006
|
+
}),
|
|
1007
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1008
|
+
type: "button",
|
|
1009
|
+
onClick: () => setShowCreateDialog(true),
|
|
1010
|
+
className: "flex items-center gap-1.5 rounded-lg border border-imp-border px-3 py-2 text-sm font-medium text-imp-muted-foreground transition-colors hover:border-imp-primary/50 hover:text-imp-foreground",
|
|
1011
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.UserPlus, { className: "w-4 h-4" }), " Create User"]
|
|
1012
|
+
})
|
|
1013
|
+
]
|
|
644
1014
|
}),
|
|
645
1015
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
646
1016
|
className: "p-3 text-sm text-red-600 rounded-md bg-red-50 dark:bg-red-950/20 dark:text-red-400",
|
|
@@ -742,7 +1112,7 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
742
1112
|
}) : u.id !== user?.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
743
1113
|
type: "button",
|
|
744
1114
|
onClick: () => setPendingDeleteUser(u),
|
|
745
|
-
className: "rounded-md p-1.5 text-imp-muted-foreground opacity-0 transition-opacity hover:bg-
|
|
1115
|
+
className: "rounded-md p-1.5 text-imp-muted-foreground opacity-0 transition-opacity hover:bg-imp-destructive/10 hover:text-imp-destructive group-hover:opacity-100",
|
|
746
1116
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, { className: "w-4 h-4" })
|
|
747
1117
|
}) : null
|
|
748
1118
|
})
|
|
@@ -790,12 +1160,17 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
790
1160
|
]
|
|
791
1161
|
})]
|
|
792
1162
|
}),
|
|
793
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1163
|
+
apiKeysEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApiKeysDialog, {
|
|
1164
|
+
open: showApiKeysDialog,
|
|
1165
|
+
onOpenChange: setShowApiKeysDialog,
|
|
1166
|
+
apiBaseUrl
|
|
1167
|
+
}),
|
|
1168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.Dialog, {
|
|
794
1169
|
open: showCreateDialog,
|
|
795
1170
|
onOpenChange: (open) => !open && setShowCreateDialog(false),
|
|
796
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
1171
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DialogContent, {
|
|
797
1172
|
className: "max-w-md border-imp-border bg-imp-background text-imp-foreground sm:max-w-md",
|
|
798
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1173
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogTitle, {
|
|
799
1174
|
className: "text-sm font-semibold",
|
|
800
1175
|
children: "Create New User"
|
|
801
1176
|
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateUserForm, {
|
|
@@ -808,13 +1183,13 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
808
1183
|
})]
|
|
809
1184
|
})
|
|
810
1185
|
}),
|
|
811
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1186
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.Dialog, {
|
|
812
1187
|
open: pendingDeleteUser !== null,
|
|
813
1188
|
onOpenChange: (open) => !open && setPendingDeleteUser(null),
|
|
814
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
1189
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DialogContent, {
|
|
815
1190
|
className: "max-w-sm border-imp-border bg-imp-background text-imp-foreground sm:max-w-sm",
|
|
816
1191
|
children: [
|
|
817
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.DialogTitle, {
|
|
818
1193
|
className: "text-sm font-semibold",
|
|
819
1194
|
children: "Delete user"
|
|
820
1195
|
}) }),
|
|
@@ -830,7 +1205,7 @@ function UserManagement({ apiBaseUrl, className }) {
|
|
|
830
1205
|
"? This action cannot be undone."
|
|
831
1206
|
]
|
|
832
1207
|
}),
|
|
833
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
1208
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_invect_ui.DialogFooter, {
|
|
834
1209
|
className: "gap-2",
|
|
835
1210
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
836
1211
|
type: "button",
|
|
@@ -1032,7 +1407,7 @@ function LoadingSpinner() {
|
|
|
1032
1407
|
* plugin route contribution at '/users'.
|
|
1033
1408
|
*/
|
|
1034
1409
|
function UserManagementPage() {
|
|
1035
|
-
const api = (0,
|
|
1410
|
+
const api = (0, _invect_ui.useApiClient)();
|
|
1036
1411
|
const { user, isAuthenticated } = useAuth();
|
|
1037
1412
|
const apiBaseUrl = api.getBaseURL();
|
|
1038
1413
|
if (!isAuthenticated) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1042,7 +1417,7 @@ function UserManagementPage() {
|
|
|
1042
1417
|
children: "Please sign in to access this page."
|
|
1043
1418
|
})
|
|
1044
1419
|
});
|
|
1045
|
-
if (user?.role !== "admin") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1420
|
+
if (user?.role !== "admin") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.PageLayout, {
|
|
1046
1421
|
title: "User Management",
|
|
1047
1422
|
subtitle: "Manage users for your Invect instance.",
|
|
1048
1423
|
icon: lucide_react.Users,
|
|
@@ -1051,7 +1426,7 @@ function UserManagementPage() {
|
|
|
1051
1426
|
children: "Only administrators can manage users. Contact an admin for access."
|
|
1052
1427
|
})
|
|
1053
1428
|
});
|
|
1054
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1429
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.PageLayout, {
|
|
1055
1430
|
title: "User Management",
|
|
1056
1431
|
subtitle: "Create, manage, and remove users for your Invect instance.",
|
|
1057
1432
|
icon: lucide_react.Users,
|
|
@@ -1083,7 +1458,7 @@ function ProfilePage({ basePath }) {
|
|
|
1083
1458
|
});
|
|
1084
1459
|
const displayName = user.name ?? user.email ?? user.id;
|
|
1085
1460
|
const initials = displayName[0]?.toUpperCase() ?? "?";
|
|
1086
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1461
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_invect_ui.PageLayout, {
|
|
1087
1462
|
title: "Profile",
|
|
1088
1463
|
subtitle: "View your account details and manage your current session.",
|
|
1089
1464
|
icon: lucide_react.User,
|
|
@@ -1226,7 +1601,7 @@ function SidebarUserMenu({ collapsed = false, basePath = "" }) {
|
|
|
1226
1601
|
* AuthenticatedInvect already wraps the tree with it. This plugin
|
|
1227
1602
|
* only adds the user management UI.
|
|
1228
1603
|
*/
|
|
1229
|
-
const
|
|
1604
|
+
const authFrontend = {
|
|
1230
1605
|
id: "user-auth",
|
|
1231
1606
|
name: "User Authentication",
|
|
1232
1607
|
sidebar: [{
|
|
@@ -1246,6 +1621,7 @@ const authFrontendPlugin = {
|
|
|
1246
1621
|
}]
|
|
1247
1622
|
};
|
|
1248
1623
|
//#endregion
|
|
1624
|
+
exports.ApiKeysDialog = ApiKeysDialog;
|
|
1249
1625
|
exports.AuthGate = AuthGate;
|
|
1250
1626
|
exports.AuthProvider = AuthProvider;
|
|
1251
1627
|
exports.AuthenticatedInvect = AuthenticatedInvect;
|
|
@@ -1256,7 +1632,7 @@ exports.SignInPage = SignInPage;
|
|
|
1256
1632
|
exports.UserButton = UserButton;
|
|
1257
1633
|
exports.UserManagement = UserManagement;
|
|
1258
1634
|
exports.UserManagementPage = UserManagementPage;
|
|
1259
|
-
exports.
|
|
1635
|
+
exports.authFrontend = authFrontend;
|
|
1260
1636
|
exports.useAuth = useAuth;
|
|
1261
1637
|
|
|
1262
1638
|
//# sourceMappingURL=index.cjs.map
|