@burdenoff/microfe-devportal 2026.623.5 → 2026.623.7
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/dist/devportal/DevportalRoutes.js +123 -108
- package/dist/devportal/DevportalRoutes.js.map +1 -1
- package/dist/devportal/components/SectionNav.js +71 -5
- package/dist/devportal/components/SectionNav.js.map +1 -0
- package/dist/devportal/components/index.js +1 -1
- package/dist/devportal/hooks/useGraphQL.js +1 -1
- package/dist/devportal/pages/AdminReviewPage.js +39 -39
- package/dist/devportal/pages/AnalyticsDashboardPage.js +54 -54
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +53 -53
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +9 -9
- package/dist/devportal/pages/ApplicationDetailPage.js +462 -462
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationVersionsPage.js +120 -120
- package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +73 -73
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +20 -20
- package/dist/devportal/pages/CreateApplicationPage.js +32 -32
- package/dist/devportal/pages/CreateOAuthClientPage.js +18 -18
- package/dist/devportal/pages/CreateOrganizationPage.js +12 -12
- package/dist/devportal/pages/CreateWebhookPage.js +23 -23
- package/dist/devportal/pages/DashboardPage.js +23 -23
- package/dist/devportal/pages/DelegationsPage.js +22 -22
- package/dist/devportal/pages/EditApplicationPage.js +283 -283
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.js +9 -9
- package/dist/devportal/pages/OAuthClientDetailPage.js +15 -15
- package/dist/devportal/pages/OAuthClientsPage.js +14 -14
- package/dist/devportal/pages/OrganizationDetailPage.js +84 -84
- package/dist/devportal/pages/OrganizationMembersPage.js +260 -260
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationsPage.js +16 -16
- package/dist/devportal/pages/PublishPage.js +16 -16
- package/dist/devportal/pages/PublisherProfilePage.js +18 -18
- package/dist/devportal/pages/RevenueDashboardPage.js +29 -29
- package/dist/devportal/pages/SettingsPage.js +17 -17
- package/dist/devportal/pages/SetupPage.js +99 -99
- package/dist/devportal/pages/SetupPage.js.map +1 -1
- package/dist/devportal/pages/WebhookDetailPage.js +143 -143
- package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
- package/dist/devportal/pages/WebhooksPage.js +133 -133
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { useDevportal as e } from "../providers/DevportalProvider.js";
|
|
2
2
|
import { cn as t } from "../utils/cn.js";
|
|
3
3
|
import { EmptyState as n } from "../components/EmptyState.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { useCurrentUserAppsQuery as r, useMyApiKeysQuery as ee, useRevokeApiKeyAuthMutation as te, useRotateApiKeyAuthMutation as ne } from "../../generated/global-operations.js";
|
|
5
|
+
import { getErrorMessage as re } from "../hooks/useGraphQL.js";
|
|
6
|
+
import { PageLayout as i } from "../components/PageLayout.js";
|
|
7
|
+
import { ActiveIndicator as ie } from "../components/StatusBadge.js";
|
|
6
8
|
import { SelectionCheckbox as a } from "../components/SelectionCheckbox.js";
|
|
7
|
-
import { NewSecretAlert as
|
|
8
|
-
import { DataTable as
|
|
9
|
+
import { NewSecretAlert as ae } from "../components/SecretDisplay.js";
|
|
10
|
+
import { DataTable as oe } from "../components/DataTable.js";
|
|
9
11
|
import { ConfirmDialog as o } from "../components/ConfirmDialog.js";
|
|
10
|
-
import {
|
|
11
|
-
import { PermissionGuard as c } from "../components/PermissionGuard.js";
|
|
12
|
-
import { getErrorMessage as ae } from "../hooks/useGraphQL.js";
|
|
12
|
+
import { PermissionGuard as se } from "../components/PermissionGuard.js";
|
|
13
13
|
import "../components/index.js";
|
|
14
|
-
import { DEVPORTAL_PERMISSIONS as
|
|
14
|
+
import { DEVPORTAL_PERMISSIONS as s } from "../constants/permissions.js";
|
|
15
15
|
import "../hooks/index.js";
|
|
16
|
-
import { useMemo as
|
|
17
|
-
import { useI18n as
|
|
18
|
-
import { AlertCircle as
|
|
16
|
+
import { useMemo as c, useState as l } from "react";
|
|
17
|
+
import { useI18n as ce } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
18
|
+
import { AlertCircle as le, AlertTriangle as ue, Ban as u, Clock as de, Key as d, Loader2 as fe, Plus as f, RotateCw as p, Search as pe, Shield as me } from "lucide-react";
|
|
19
19
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
20
20
|
//#region src/devportal/pages/ApiKeysPage.tsx
|
|
21
21
|
function he(e) {
|
|
@@ -36,14 +36,14 @@ var g = (e) => {
|
|
|
36
36
|
let t = Math.ceil((new Date(e).getTime() - Date.now()) / (1e3 * 60 * 60 * 24));
|
|
37
37
|
return t <= 30 && t > 0;
|
|
38
38
|
}, _ = (e) => e ? new Date(e).getTime() < Date.now() : !1, v = () => {
|
|
39
|
-
let { navigate: v, basePath:
|
|
40
|
-
let n =
|
|
39
|
+
let { navigate: v, basePath: ge, activeOrgId: _e } = e(), { t: ve } = ce(), y = (e, t) => {
|
|
40
|
+
let n = ve(e);
|
|
41
41
|
return n === e ? t : n;
|
|
42
|
-
}, [
|
|
42
|
+
}, [b, x] = l(""), [S, ye] = l("all"), [C, w] = l(null), [T, E] = l(null), [D, O] = l(null), [k, A] = l(/* @__PURE__ */ new Set()), [j, M] = l(null), [N, P] = l(!1), { data: F, loading: be, error: I, refetch: L } = ee({ fetchPolicy: "cache-and-network" }), { data: R } = r({ fetchPolicy: "cache-first" }), z = c(() => {
|
|
43
43
|
let e = R?.currentUserApps?.apps;
|
|
44
44
|
return e?.length ? new Set(e.map((e) => e.id)) : null;
|
|
45
|
-
}, [R?.currentUserApps?.apps]), [B, { loading: V }] =
|
|
46
|
-
v?.(`${
|
|
45
|
+
}, [R?.currentUserApps?.apps]), [B, { loading: V }] = te(), [xe, { loading: H }] = ne(), U = c(() => F?.myApiKeys?.apiKeys.map(he) ?? [], [F?.myApiKeys?.apiKeys]), W = (e) => {
|
|
46
|
+
v?.(`${ge}${e}`);
|
|
47
47
|
}, Se = async () => {
|
|
48
48
|
if (C) try {
|
|
49
49
|
await B({ variables: { keyId: C.id } }), w(null), await L();
|
|
@@ -54,9 +54,9 @@ var g = (e) => {
|
|
|
54
54
|
e && O(e), E(null), await L();
|
|
55
55
|
} catch {}
|
|
56
56
|
}, G = U.filter((e) => {
|
|
57
|
-
let t = !z || z.has(e.appId), n = !
|
|
57
|
+
let t = !z || z.has(e.appId), n = !b || e.name.toLowerCase().includes(b.toLowerCase()) || e.prefix.toLowerCase().includes(b.toLowerCase()) || (e.appName?.toLowerCase().includes(b.toLowerCase()) ?? !1), r = S === "all" || S === "active" && !_(e.expiresAt) || S === "expired" && _(e.expiresAt);
|
|
58
58
|
return t && n && r;
|
|
59
|
-
}), K =
|
|
59
|
+
}), K = c(() => U.filter((e) => k.has(e.id)), [U, k]), q = c(() => K.filter((e) => !_(e.expiresAt)), [K]), J = G.filter((e) => !_(e.expiresAt)), Y = J.length > 0 && J.every((e) => k.has(e.id)), X = J.some((e) => k.has(e.id)) && !Y, we = (e, t) => {
|
|
60
60
|
A((n) => {
|
|
61
61
|
let r = new Set(n);
|
|
62
62
|
return t ? r.add(e) : r.delete(e), r;
|
|
@@ -69,36 +69,36 @@ var g = (e) => {
|
|
|
69
69
|
}), n;
|
|
70
70
|
});
|
|
71
71
|
}, Q = () => A(/* @__PURE__ */ new Set());
|
|
72
|
-
if (be && !F) return /* @__PURE__ */ m(
|
|
73
|
-
title:
|
|
74
|
-
description:
|
|
72
|
+
if (be && !F) return /* @__PURE__ */ m(i, {
|
|
73
|
+
title: y("devportal.apiKeys", "API Keys"),
|
|
74
|
+
description: y("devportal.manageApiKeys", "Manage your API keys for programmatic access"),
|
|
75
75
|
children: /* @__PURE__ */ h("div", {
|
|
76
76
|
className: "flex items-center justify-center py-12",
|
|
77
|
-
children: [/* @__PURE__ */ m(
|
|
77
|
+
children: [/* @__PURE__ */ m(fe, { className: "h-8 w-8 animate-spin text-muted-foreground" }), /* @__PURE__ */ m("span", {
|
|
78
78
|
className: "ml-2 text-muted-foreground",
|
|
79
|
-
children:
|
|
79
|
+
children: y("devportal.loadingApiKeys", "Loading API keys...")
|
|
80
80
|
})]
|
|
81
81
|
})
|
|
82
82
|
});
|
|
83
|
-
if (I) return /* @__PURE__ */ m(
|
|
84
|
-
title:
|
|
85
|
-
description:
|
|
83
|
+
if (I) return /* @__PURE__ */ m(i, {
|
|
84
|
+
title: y("devportal.apiKeys", "API Keys"),
|
|
85
|
+
description: y("devportal.manageApiKeys", "Manage your API keys for programmatic access"),
|
|
86
86
|
children: /* @__PURE__ */ h("div", {
|
|
87
87
|
className: "rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center",
|
|
88
88
|
children: [
|
|
89
|
-
/* @__PURE__ */ m(
|
|
89
|
+
/* @__PURE__ */ m(le, { className: "mx-auto h-8 w-8 text-destructive" }),
|
|
90
90
|
/* @__PURE__ */ m("h3", {
|
|
91
91
|
className: "mt-2 font-medium text-destructive",
|
|
92
|
-
children:
|
|
92
|
+
children: y("devportal.failedToLoadApiKeys", "Failed to load API keys")
|
|
93
93
|
}),
|
|
94
94
|
/* @__PURE__ */ m("p", {
|
|
95
95
|
className: "mt-1 text-sm text-muted-foreground",
|
|
96
|
-
children:
|
|
96
|
+
children: re(I)
|
|
97
97
|
}),
|
|
98
98
|
/* @__PURE__ */ m("button", {
|
|
99
99
|
onClick: () => L(),
|
|
100
100
|
className: "mt-4 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90",
|
|
101
|
-
children:
|
|
101
|
+
children: y("devportal.tryAgain", "Try Again")
|
|
102
102
|
})
|
|
103
103
|
]
|
|
104
104
|
})
|
|
@@ -130,7 +130,7 @@ var g = (e) => {
|
|
|
130
130
|
className: "flex items-center gap-3",
|
|
131
131
|
children: [/* @__PURE__ */ m("div", {
|
|
132
132
|
className: t("flex h-10 w-10 items-center justify-center rounded-lg", _(e.expiresAt) ? "bg-status-error-bg-subtle text-status-error-text" : "bg-primary/10 text-primary"),
|
|
133
|
-
children: /* @__PURE__ */ m(
|
|
133
|
+
children: /* @__PURE__ */ m(d, { size: 20 })
|
|
134
134
|
}), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("div", {
|
|
135
135
|
className: "flex items-center gap-2",
|
|
136
136
|
children: [
|
|
@@ -196,7 +196,7 @@ var g = (e) => {
|
|
|
196
196
|
header: "Status",
|
|
197
197
|
render: (e) => /* @__PURE__ */ h("div", {
|
|
198
198
|
className: "flex items-center gap-2",
|
|
199
|
-
children: [/* @__PURE__ */ m(
|
|
199
|
+
children: [/* @__PURE__ */ m(ie, { active: !_(e.expiresAt) }), /* @__PURE__ */ m("span", {
|
|
200
200
|
className: "text-sm",
|
|
201
201
|
children: _(e.expiresAt) ? "Expired" : "Active"
|
|
202
202
|
})]
|
|
@@ -219,7 +219,7 @@ var g = (e) => {
|
|
|
219
219
|
t.stopPropagation(), E(e);
|
|
220
220
|
},
|
|
221
221
|
className: t("inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors", "text-status-info-text hover:bg-status-info-bg-subtle", "disabled:opacity-50 disabled:cursor-not-allowed"),
|
|
222
|
-
children: [/* @__PURE__ */ m(
|
|
222
|
+
children: [/* @__PURE__ */ m(p, { size: 14 }), /* @__PURE__ */ m("span", {
|
|
223
223
|
className: "sr-only sm:not-sr-only",
|
|
224
224
|
children: "Rotate"
|
|
225
225
|
})]
|
|
@@ -231,7 +231,7 @@ var g = (e) => {
|
|
|
231
231
|
t.stopPropagation(), w(e);
|
|
232
232
|
},
|
|
233
233
|
className: t("inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors", "text-status-error-text hover:bg-status-error-bg-subtle", "disabled:opacity-50 disabled:cursor-not-allowed"),
|
|
234
|
-
children: [/* @__PURE__ */ m(
|
|
234
|
+
children: [/* @__PURE__ */ m(u, { size: 14 }), /* @__PURE__ */ m("span", {
|
|
235
235
|
className: "sr-only sm:not-sr-only",
|
|
236
236
|
children: "Revoke"
|
|
237
237
|
})]
|
|
@@ -244,18 +244,18 @@ var g = (e) => {
|
|
|
244
244
|
expiringSoon: G.filter((e) => g(e.expiresAt)).length,
|
|
245
245
|
expired: G.filter((e) => _(e.expiresAt)).length
|
|
246
246
|
};
|
|
247
|
-
return /* @__PURE__ */ m(
|
|
248
|
-
permission:
|
|
249
|
-
children: /* @__PURE__ */ h(
|
|
250
|
-
title:
|
|
251
|
-
description:
|
|
247
|
+
return /* @__PURE__ */ m(se, {
|
|
248
|
+
permission: s.APP_READ,
|
|
249
|
+
children: /* @__PURE__ */ h(i, {
|
|
250
|
+
title: y("devportal.apiKeys", "API Keys"),
|
|
251
|
+
description: y("devportal.manageApiKeys", "Manage your API keys for programmatic access"),
|
|
252
252
|
actions: /* @__PURE__ */ h("button", {
|
|
253
253
|
onClick: () => W("/api-keys/new"),
|
|
254
254
|
className: "inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
255
|
-
children: [/* @__PURE__ */ m(
|
|
255
|
+
children: [/* @__PURE__ */ m(f, { size: 16 }), y("devportal.generateApiKey", "Generate API Key")]
|
|
256
256
|
}),
|
|
257
257
|
children: [
|
|
258
|
-
D && /* @__PURE__ */ m(
|
|
258
|
+
D && /* @__PURE__ */ m(ae, {
|
|
259
259
|
title: "API Key Rotated",
|
|
260
260
|
secret: D,
|
|
261
261
|
description: "The previous secret has been invalidated. Copy the new secret below — it will not be shown again.",
|
|
@@ -284,8 +284,8 @@ var g = (e) => {
|
|
|
284
284
|
}), /* @__PURE__ */ m("input", {
|
|
285
285
|
type: "text",
|
|
286
286
|
placeholder: "Search by name, prefix, or app...",
|
|
287
|
-
value:
|
|
288
|
-
onChange: (e) =>
|
|
287
|
+
value: b,
|
|
288
|
+
onChange: (e) => x(e.target.value),
|
|
289
289
|
className: "w-full rounded-md border bg-background pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
290
290
|
})]
|
|
291
291
|
}), /* @__PURE__ */ h("select", {
|
|
@@ -316,7 +316,7 @@ var g = (e) => {
|
|
|
316
316
|
className: "rounded-lg border bg-card p-4",
|
|
317
317
|
children: [/* @__PURE__ */ h("div", {
|
|
318
318
|
className: "flex items-center gap-2 text-muted-foreground",
|
|
319
|
-
children: [/* @__PURE__ */ m(
|
|
319
|
+
children: [/* @__PURE__ */ m(d, { size: 16 }), /* @__PURE__ */ m("span", {
|
|
320
320
|
className: "text-sm",
|
|
321
321
|
children: "Total Keys"
|
|
322
322
|
})]
|
|
@@ -342,7 +342,7 @@ var g = (e) => {
|
|
|
342
342
|
className: "rounded-lg border bg-card p-4",
|
|
343
343
|
children: [/* @__PURE__ */ h("div", {
|
|
344
344
|
className: "flex items-center gap-2 text-status-warning-text",
|
|
345
|
-
children: [/* @__PURE__ */ m(
|
|
345
|
+
children: [/* @__PURE__ */ m(de, { size: 16 }), /* @__PURE__ */ m("span", {
|
|
346
346
|
className: "text-sm",
|
|
347
347
|
children: "Expiring Soon"
|
|
348
348
|
})]
|
|
@@ -355,7 +355,7 @@ var g = (e) => {
|
|
|
355
355
|
className: "rounded-lg border bg-card p-4",
|
|
356
356
|
children: [/* @__PURE__ */ h("div", {
|
|
357
357
|
className: "flex items-center gap-2 text-status-error-text",
|
|
358
|
-
children: [/* @__PURE__ */ m(
|
|
358
|
+
children: [/* @__PURE__ */ m(ue, { size: 16 }), /* @__PURE__ */ m("span", {
|
|
359
359
|
className: "text-sm",
|
|
360
360
|
children: "Expired"
|
|
361
361
|
})]
|
|
@@ -408,7 +408,7 @@ var g = (e) => {
|
|
|
408
408
|
disabled: q.length === 0,
|
|
409
409
|
className: "inline-flex items-center gap-2 rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50",
|
|
410
410
|
children: [
|
|
411
|
-
/* @__PURE__ */ m(
|
|
411
|
+
/* @__PURE__ */ m(u, { size: 16 }),
|
|
412
412
|
"Revoke Selected",
|
|
413
413
|
q.length > 0 ? ` (${q.length})` : ""
|
|
414
414
|
]
|
|
@@ -416,18 +416,18 @@ var g = (e) => {
|
|
|
416
416
|
})]
|
|
417
417
|
})
|
|
418
418
|
}),
|
|
419
|
-
G.length > 0 ? /* @__PURE__ */ m(
|
|
419
|
+
G.length > 0 ? /* @__PURE__ */ m(oe, {
|
|
420
420
|
data: G,
|
|
421
421
|
columns: Te,
|
|
422
422
|
keyField: "id"
|
|
423
423
|
}) : /* @__PURE__ */ m(n, {
|
|
424
|
-
icon: /* @__PURE__ */ m(
|
|
425
|
-
title:
|
|
426
|
-
description:
|
|
427
|
-
action: !
|
|
424
|
+
icon: /* @__PURE__ */ m(d, { size: 32 }),
|
|
425
|
+
title: y("devportal.noApiKeysFound", "No API keys found"),
|
|
426
|
+
description: b || S !== "all" ? y("devportal.tryAdjustingFilters", "Try adjusting your filters") : y("devportal.generateFirstApiKey", "Generate your first API key to start making API calls"),
|
|
427
|
+
action: !b && S === "all" ? /* @__PURE__ */ h("button", {
|
|
428
428
|
onClick: () => W("/api-keys/new"),
|
|
429
429
|
className: "inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90",
|
|
430
|
-
children: [/* @__PURE__ */ m(
|
|
430
|
+
children: [/* @__PURE__ */ m(f, { size: 16 }), y("devportal.generateApiKey", "Generate API Key")]
|
|
431
431
|
}) : void 0
|
|
432
432
|
}),
|
|
433
433
|
/* @__PURE__ */ m(o, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeysPage.js","names":[],"sources":["../../../src/devportal/pages/ApiKeysPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Key,\n Shield,\n Clock,\n AlertTriangle,\n Loader2,\n AlertCircle,\n RotateCw,\n Ban,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApiKey } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useMyApiKeysQuery,\n useCurrentUserAppsQuery,\n useRevokeApiKeyAuthMutation,\n useRotateApiKeyAuthMutation,\n getErrorMessage,\n} from \"../hooks\";\n\n/**\n * Map GraphQL APIKey to local AuthApiKey type\n */\nfunction mapApiKey(key: {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string | null;\n createdAt: string;\n expiresAt?: string | null;\n lastUsedAt?: string | null;\n}): AuthApiKey {\n return {\n id: key.id,\n name: key.name,\n prefix: key.prefix,\n scopes: key.scopes,\n appId: key.appId,\n appName: key.appName ?? undefined,\n createdAt: key.createdAt,\n expiresAt: key.expiresAt ?? undefined,\n lastUsedAt: key.lastUsedAt ?? undefined,\n };\n}\n\nconst isExpiringSoon = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n const daysUntilExpiry = Math.ceil(\n (new Date(expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24),\n );\n return daysUntilExpiry <= 30 && daysUntilExpiry > 0;\n};\n\nconst isExpired = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n return new Date(expiresAt).getTime() < Date.now();\n};\n\n/**\n * API Keys listing page\n *\n * Displays all API keys for the current user from auth-svc.\n * Previously showed devportal-svc ApiKeys per application;\n * now shows all user-owned API keys centrally.\n * Supports revoking and rotating API keys.\n */\nexport const ApiKeysPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<\n \"all\" | \"active\" | \"expired\"\n >(\"all\");\n\n // Revoke / Rotate state\n const [revokeTarget, setRevokeTarget] = useState<AuthApiKey | null>(null);\n const [rotateTarget, setRotateTarget] = useState<AuthApiKey | null>(null);\n const [newSecret, setNewSecret] = useState<string | null>(null);\n const [selectedKeyIds, setSelectedKeyIds] = useState<Set<string>>(new Set());\n const [bulkFeedback, setBulkFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n const [showBulkRevokeDialog, setShowBulkRevokeDialog] = useState(false);\n\n // Fetch API keys from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useMyApiKeysQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch OAuth apps to map appId → filter; key.appId is an OAuth client UUID\n const { data: oauthAppsData } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-first\",\n });\n const orgAppIds = useMemo(() => {\n const apps = oauthAppsData?.currentUserApps?.apps;\n if (!apps?.length) return null;\n return new Set(apps.map((a: { id: string }) => a.id));\n }, [oauthAppsData?.currentUserApps?.apps]);\n\n // Mutations\n const [revokeApiKey, { loading: revokeLoading }] =\n useRevokeApiKeyAuthMutation();\n const [rotateApiKey, { loading: rotateLoading }] =\n useRotateApiKeyAuthMutation();\n\n const apiKeys: AuthApiKey[] = useMemo(\n () => data?.myApiKeys?.apiKeys.map(mapApiKey) ?? [],\n [data?.myApiKeys?.apiKeys],\n );\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Revoke handler\n const handleRevoke = async () => {\n if (!revokeTarget) return;\n try {\n await revokeApiKey({\n variables: { keyId: revokeTarget.id },\n });\n setRevokeTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Rotate handler\n const handleRotate = async () => {\n if (!rotateTarget) return;\n try {\n const result = await rotateApiKey({\n variables: { keyId: rotateTarget.id },\n });\n const secret = result.data?.rotateAPIKey.apiSecret;\n if (secret) {\n setNewSecret(secret);\n }\n setRotateTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Filter keys: org scope + search + status\n const filteredKeys = apiKeys.filter((key: AuthApiKey) => {\n const matchesOrg = !orgAppIds || orgAppIds.has(key.appId);\n const matchesSearch =\n !searchQuery ||\n key.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n key.prefix.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (key.appName?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false);\n const matchesStatus =\n statusFilter === \"all\" ||\n (statusFilter === \"active\" && !isExpired(key.expiresAt)) ||\n (statusFilter === \"expired\" && isExpired(key.expiresAt));\n return matchesOrg && matchesSearch && matchesStatus;\n });\n\n const selectedKeys = useMemo(\n () => apiKeys.filter((key) => selectedKeyIds.has(key.id)),\n [apiKeys, selectedKeyIds],\n );\n\n const selectedActiveKeys = useMemo(\n () => selectedKeys.filter((key) => !isExpired(key.expiresAt)),\n [selectedKeys],\n );\n\n const allVisibleSelectableKeys = filteredKeys.filter(\n (key) => !isExpired(key.expiresAt),\n );\n const allVisibleSelected =\n allVisibleSelectableKeys.length > 0 &&\n allVisibleSelectableKeys.every((key) => selectedKeyIds.has(key.id));\n const partiallySelected =\n allVisibleSelectableKeys.some((key) => selectedKeyIds.has(key.id)) &&\n !allVisibleSelected;\n\n const toggleKeySelection = (keyId: string, checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(keyId);\n } else {\n next.delete(keyId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleKeys = (checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n allVisibleSelectableKeys.forEach((key) => {\n if (checked) {\n next.add(key.id);\n } else {\n next.delete(key.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedKeyIds(new Set());\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-muted-foreground\">\n {tr(\"devportal.loadingApiKeys\", \"Loading API keys...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-destructive\" />\n <h3 className=\"mt-2 font-medium text-destructive\">\n {tr(\"devportal.failedToLoadApiKeys\", \"Failed to load API keys\")}\n </h3>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n ),\n className: \"w-12\",\n render: (key: AuthApiKey) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedKeyIds.has(key.id)}\n disabled={isExpired(key.expiresAt)}\n onChange={(checked) => toggleKeySelection(key.id, checked)}\n ariaLabel={`Select API key ${key.name}`}\n />\n </div>\n ),\n },\n {\n key: \"name\",\n header: \"API Key\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n isExpired(key.expiresAt)\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : \"bg-primary/10 text-primary\",\n )}\n >\n <Key size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{key.name}</p>\n {isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text\">\n Expired\n </span>\n )}\n {isExpiringSoon(key.expiresAt) && !isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text\">\n Expiring Soon\n </span>\n )}\n </div>\n {key.appName && (\n <p className=\"text-xs text-muted-foreground\">\n App: {key.appName}\n </p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: \"prefix\",\n header: \"Key Prefix\",\n render: (key: AuthApiKey) => (\n <code className=\"text-sm text-muted-foreground\">{key.prefix}...</code>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (key: AuthApiKey) => (\n <div className=\"flex flex-wrap gap-1\">\n {key.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-muted px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {key.scopes.length > 3 && (\n <span className=\"text-xs text-muted-foreground\">\n +{key.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"lastUsed\",\n header: \"Last Used\",\n render: (key: AuthApiKey) => (\n <span className=\"text-sm text-muted-foreground\">\n {key.lastUsedAt\n ? new Date(key.lastUsedAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"expires\",\n header: \"Expires\",\n render: (key: AuthApiKey) => (\n <span\n className={cn(\n \"text-sm\",\n isExpired(key.expiresAt)\n ? \"text-status-error-text\"\n : isExpiringSoon(key.expiresAt)\n ? \"text-status-warning-text\"\n : \"text-muted-foreground\",\n )}\n >\n {key.expiresAt\n ? new Date(key.expiresAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={!isExpired(key.expiresAt)} />\n <span className=\"text-sm\">\n {isExpired(key.expiresAt) ? \"Expired\" : \"Active\"}\n </span>\n </div>\n ),\n },\n {\n key: \"actions\",\n header: \"Actions\",\n className: \"w-28\",\n render: (key: AuthApiKey) => {\n if (isExpired(key.expiresAt)) {\n return (\n <span className=\"text-xs text-muted-foreground\">No actions</span>\n );\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n title=\"Rotate secret\"\n disabled={rotateLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRotateTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-info-text hover:bg-status-info-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <RotateCw size={14} />\n <span className=\"sr-only sm:not-sr-only\">Rotate</span>\n </button>\n <button\n type=\"button\"\n title=\"Revoke key\"\n disabled={revokeLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRevokeTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-error-text hover:bg-status-error-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Ban size={14} />\n <span className=\"sr-only sm:not-sr-only\">Revoke</span>\n </button>\n </div>\n );\n },\n },\n ];\n\n // Stats scoped to the filtered set (org context)\n const stats = {\n total: filteredKeys.length,\n active: filteredKeys.filter((k) => !isExpired(k.expiresAt)).length,\n expiringSoon: filteredKeys.filter((k) => isExpiringSoon(k.expiresAt))\n .length,\n expired: filteredKeys.filter((k) => isExpired(k.expiresAt)).length,\n };\n\n const handleBulkRevoke = async () => {\n if (selectedActiveKeys.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedActiveKeys.map((key) =>\n revokeApiKey({ variables: { keyId: key.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setBulkFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: `Revoked ${succeeded} API key${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`,\n });\n setShowBulkRevokeDialog(false);\n clearSelection();\n await refetch();\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n }\n >\n {/* New secret alert — shown after a successful rotation */}\n {newSecret && (\n <NewSecretAlert\n title=\"API Key Rotated\"\n secret={newSecret}\n description=\"The previous secret has been invalidated. Copy the new secret below — it will not be shown again.\"\n onDismiss={() => setNewSecret(null)}\n />\n )}\n\n {bulkFeedback && (\n <div\n className={cn(\n \"rounded-lg border px-4 py-3 text-sm\",\n bulkFeedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{bulkFeedback.message}</span>\n <button\n onClick={() => setBulkFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name, prefix, or app...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-background pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as \"all\" | \"active\" | \"expired\")\n }\n className=\"rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"expired\">Expired</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Key size={16} />\n <span className=\"text-sm\">Total Keys</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.total}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.active}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <Clock size={16} />\n <span className=\"text-sm\">Expiring Soon</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.expiringSoon}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <AlertTriangle size={16} />\n <span className=\"text-sm\">Expired</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.expired}</p>\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-muted-foreground\">\n {filteredKeys.length} API key{filteredKeys.length !== 1 ? \"s\" : \"\"}{\" \"}\n found\n </p>\n\n {filteredKeys.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n <span className=\"text-sm font-medium\">\n Select all active keys\n </span>\n </div>\n {selectedKeyIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-muted px-3 py-1 text-sm font-medium text-muted-foreground\">\n {selectedKeyIds.size} selected\n </span>\n <button\n onClick={() => setShowBulkRevokeDialog(true)}\n disabled={selectedActiveKeys.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Ban size={16} />\n Revoke Selected\n {selectedActiveKeys.length > 0\n ? ` (${selectedActiveKeys.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Keys Table */}\n {filteredKeys.length > 0 ? (\n <DataTable data={filteredKeys} columns={columns} keyField=\"id\" />\n ) : (\n <EmptyState\n icon={<Key size={32} />}\n title={tr(\"devportal.noApiKeysFound\", \"No API keys found\")}\n description={\n searchQuery || statusFilter !== \"all\"\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.generateFirstApiKey\",\n \"Generate your first API key to start making API calls\",\n )\n }\n action={\n !searchQuery && statusFilter === \"all\" ? (\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRevokeTarget(null);\n }}\n onConfirm={handleRevoke}\n title=\"Revoke API Key\"\n description={`Are you sure you want to revoke \"${revokeTarget?.name ?? \"\"}\"? This action cannot be undone. Any application or service using this key will immediately lose access.`}\n confirmLabel=\"Revoke Key\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n\n {/* Rotate confirmation dialog */}\n <ConfirmDialog\n open={rotateTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRotateTarget(null);\n }}\n onConfirm={handleRotate}\n title=\"Rotate API Key Secret\"\n description={`This will generate a new secret for \"${rotateTarget?.name ?? \"\"}\" and immediately invalidate the current one. Make sure to update all services using this key before the old secret stops working.`}\n confirmLabel=\"Rotate Secret\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n loading={rotateLoading}\n />\n\n <ConfirmDialog\n open={showBulkRevokeDialog}\n onOpenChange={setShowBulkRevokeDialog}\n onConfirm={handleBulkRevoke}\n title=\"Revoke selected API keys\"\n description={`Revoke ${selectedActiveKeys.length} selected API key${selectedActiveKeys.length === 1 ? \"\" : \"s\"}? Any service using these keys will immediately lose access.`}\n confirmLabel=\"Revoke Keys\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ApiKeysPage.js","names":[],"sources":["../../../src/devportal/pages/ApiKeysPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Key,\n Shield,\n Clock,\n AlertTriangle,\n Loader2,\n AlertCircle,\n RotateCw,\n Ban,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApiKey } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useMyApiKeysQuery,\n useCurrentUserAppsQuery,\n useRevokeApiKeyAuthMutation,\n useRotateApiKeyAuthMutation,\n getErrorMessage,\n} from \"../hooks\";\n\n/**\n * Map GraphQL APIKey to local AuthApiKey type\n */\nfunction mapApiKey(key: {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string | null;\n createdAt: string;\n expiresAt?: string | null;\n lastUsedAt?: string | null;\n}): AuthApiKey {\n return {\n id: key.id,\n name: key.name,\n prefix: key.prefix,\n scopes: key.scopes,\n appId: key.appId,\n appName: key.appName ?? undefined,\n createdAt: key.createdAt,\n expiresAt: key.expiresAt ?? undefined,\n lastUsedAt: key.lastUsedAt ?? undefined,\n };\n}\n\nconst isExpiringSoon = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n const daysUntilExpiry = Math.ceil(\n (new Date(expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24),\n );\n return daysUntilExpiry <= 30 && daysUntilExpiry > 0;\n};\n\nconst isExpired = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n return new Date(expiresAt).getTime() < Date.now();\n};\n\n/**\n * API Keys listing page\n *\n * Displays all API keys for the current user from auth-svc.\n * Previously showed devportal-svc ApiKeys per application;\n * now shows all user-owned API keys centrally.\n * Supports revoking and rotating API keys.\n */\nexport const ApiKeysPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<\n \"all\" | \"active\" | \"expired\"\n >(\"all\");\n\n // Revoke / Rotate state\n const [revokeTarget, setRevokeTarget] = useState<AuthApiKey | null>(null);\n const [rotateTarget, setRotateTarget] = useState<AuthApiKey | null>(null);\n const [newSecret, setNewSecret] = useState<string | null>(null);\n const [selectedKeyIds, setSelectedKeyIds] = useState<Set<string>>(new Set());\n const [bulkFeedback, setBulkFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n const [showBulkRevokeDialog, setShowBulkRevokeDialog] = useState(false);\n\n // Fetch API keys from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useMyApiKeysQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch OAuth apps to map appId → filter; key.appId is an OAuth client UUID\n const { data: oauthAppsData } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-first\",\n });\n const orgAppIds = useMemo(() => {\n const apps = oauthAppsData?.currentUserApps?.apps;\n if (!apps?.length) return null;\n return new Set(apps.map((a: { id: string }) => a.id));\n }, [oauthAppsData?.currentUserApps?.apps]);\n\n // Mutations\n const [revokeApiKey, { loading: revokeLoading }] =\n useRevokeApiKeyAuthMutation();\n const [rotateApiKey, { loading: rotateLoading }] =\n useRotateApiKeyAuthMutation();\n\n const apiKeys: AuthApiKey[] = useMemo(\n () => data?.myApiKeys?.apiKeys.map(mapApiKey) ?? [],\n [data?.myApiKeys?.apiKeys],\n );\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Revoke handler\n const handleRevoke = async () => {\n if (!revokeTarget) return;\n try {\n await revokeApiKey({\n variables: { keyId: revokeTarget.id },\n });\n setRevokeTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Rotate handler\n const handleRotate = async () => {\n if (!rotateTarget) return;\n try {\n const result = await rotateApiKey({\n variables: { keyId: rotateTarget.id },\n });\n const secret = result.data?.rotateAPIKey.apiSecret;\n if (secret) {\n setNewSecret(secret);\n }\n setRotateTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Filter keys: org scope + search + status\n const filteredKeys = apiKeys.filter((key: AuthApiKey) => {\n const matchesOrg = !orgAppIds || orgAppIds.has(key.appId);\n const matchesSearch =\n !searchQuery ||\n key.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n key.prefix.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (key.appName?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false);\n const matchesStatus =\n statusFilter === \"all\" ||\n (statusFilter === \"active\" && !isExpired(key.expiresAt)) ||\n (statusFilter === \"expired\" && isExpired(key.expiresAt));\n return matchesOrg && matchesSearch && matchesStatus;\n });\n\n const selectedKeys = useMemo(\n () => apiKeys.filter((key) => selectedKeyIds.has(key.id)),\n [apiKeys, selectedKeyIds],\n );\n\n const selectedActiveKeys = useMemo(\n () => selectedKeys.filter((key) => !isExpired(key.expiresAt)),\n [selectedKeys],\n );\n\n const allVisibleSelectableKeys = filteredKeys.filter(\n (key) => !isExpired(key.expiresAt),\n );\n const allVisibleSelected =\n allVisibleSelectableKeys.length > 0 &&\n allVisibleSelectableKeys.every((key) => selectedKeyIds.has(key.id));\n const partiallySelected =\n allVisibleSelectableKeys.some((key) => selectedKeyIds.has(key.id)) &&\n !allVisibleSelected;\n\n const toggleKeySelection = (keyId: string, checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(keyId);\n } else {\n next.delete(keyId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleKeys = (checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n allVisibleSelectableKeys.forEach((key) => {\n if (checked) {\n next.add(key.id);\n } else {\n next.delete(key.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedKeyIds(new Set());\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-muted-foreground\">\n {tr(\"devportal.loadingApiKeys\", \"Loading API keys...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-destructive\" />\n <h3 className=\"mt-2 font-medium text-destructive\">\n {tr(\"devportal.failedToLoadApiKeys\", \"Failed to load API keys\")}\n </h3>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n ),\n className: \"w-12\",\n render: (key: AuthApiKey) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedKeyIds.has(key.id)}\n disabled={isExpired(key.expiresAt)}\n onChange={(checked) => toggleKeySelection(key.id, checked)}\n ariaLabel={`Select API key ${key.name}`}\n />\n </div>\n ),\n },\n {\n key: \"name\",\n header: \"API Key\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n isExpired(key.expiresAt)\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : \"bg-primary/10 text-primary\",\n )}\n >\n <Key size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{key.name}</p>\n {isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text\">\n Expired\n </span>\n )}\n {isExpiringSoon(key.expiresAt) && !isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text\">\n Expiring Soon\n </span>\n )}\n </div>\n {key.appName && (\n <p className=\"text-xs text-muted-foreground\">\n App: {key.appName}\n </p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: \"prefix\",\n header: \"Key Prefix\",\n render: (key: AuthApiKey) => (\n <code className=\"text-sm text-muted-foreground\">{key.prefix}...</code>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (key: AuthApiKey) => (\n <div className=\"flex flex-wrap gap-1\">\n {key.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-muted px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {key.scopes.length > 3 && (\n <span className=\"text-xs text-muted-foreground\">\n +{key.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"lastUsed\",\n header: \"Last Used\",\n render: (key: AuthApiKey) => (\n <span className=\"text-sm text-muted-foreground\">\n {key.lastUsedAt\n ? new Date(key.lastUsedAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"expires\",\n header: \"Expires\",\n render: (key: AuthApiKey) => (\n <span\n className={cn(\n \"text-sm\",\n isExpired(key.expiresAt)\n ? \"text-status-error-text\"\n : isExpiringSoon(key.expiresAt)\n ? \"text-status-warning-text\"\n : \"text-muted-foreground\",\n )}\n >\n {key.expiresAt\n ? new Date(key.expiresAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={!isExpired(key.expiresAt)} />\n <span className=\"text-sm\">\n {isExpired(key.expiresAt) ? \"Expired\" : \"Active\"}\n </span>\n </div>\n ),\n },\n {\n key: \"actions\",\n header: \"Actions\",\n className: \"w-28\",\n render: (key: AuthApiKey) => {\n if (isExpired(key.expiresAt)) {\n return (\n <span className=\"text-xs text-muted-foreground\">No actions</span>\n );\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n title=\"Rotate secret\"\n disabled={rotateLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRotateTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-info-text hover:bg-status-info-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <RotateCw size={14} />\n <span className=\"sr-only sm:not-sr-only\">Rotate</span>\n </button>\n <button\n type=\"button\"\n title=\"Revoke key\"\n disabled={revokeLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRevokeTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-error-text hover:bg-status-error-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Ban size={14} />\n <span className=\"sr-only sm:not-sr-only\">Revoke</span>\n </button>\n </div>\n );\n },\n },\n ];\n\n // Stats scoped to the filtered set (org context)\n const stats = {\n total: filteredKeys.length,\n active: filteredKeys.filter((k) => !isExpired(k.expiresAt)).length,\n expiringSoon: filteredKeys.filter((k) => isExpiringSoon(k.expiresAt))\n .length,\n expired: filteredKeys.filter((k) => isExpired(k.expiresAt)).length,\n };\n\n const handleBulkRevoke = async () => {\n if (selectedActiveKeys.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedActiveKeys.map((key) =>\n revokeApiKey({ variables: { keyId: key.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setBulkFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: `Revoked ${succeeded} API key${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`,\n });\n setShowBulkRevokeDialog(false);\n clearSelection();\n await refetch();\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n }\n >\n {/* New secret alert — shown after a successful rotation */}\n {newSecret && (\n <NewSecretAlert\n title=\"API Key Rotated\"\n secret={newSecret}\n description=\"The previous secret has been invalidated. Copy the new secret below — it will not be shown again.\"\n onDismiss={() => setNewSecret(null)}\n />\n )}\n\n {bulkFeedback && (\n <div\n className={cn(\n \"rounded-lg border px-4 py-3 text-sm\",\n bulkFeedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{bulkFeedback.message}</span>\n <button\n onClick={() => setBulkFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name, prefix, or app...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-background pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as \"all\" | \"active\" | \"expired\")\n }\n className=\"rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"expired\">Expired</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Key size={16} />\n <span className=\"text-sm\">Total Keys</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.total}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.active}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <Clock size={16} />\n <span className=\"text-sm\">Expiring Soon</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.expiringSoon}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <AlertTriangle size={16} />\n <span className=\"text-sm\">Expired</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.expired}</p>\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-muted-foreground\">\n {filteredKeys.length} API key{filteredKeys.length !== 1 ? \"s\" : \"\"}{\" \"}\n found\n </p>\n\n {filteredKeys.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n <span className=\"text-sm font-medium\">\n Select all active keys\n </span>\n </div>\n {selectedKeyIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-muted px-3 py-1 text-sm font-medium text-muted-foreground\">\n {selectedKeyIds.size} selected\n </span>\n <button\n onClick={() => setShowBulkRevokeDialog(true)}\n disabled={selectedActiveKeys.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Ban size={16} />\n Revoke Selected\n {selectedActiveKeys.length > 0\n ? ` (${selectedActiveKeys.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Keys Table */}\n {filteredKeys.length > 0 ? (\n <DataTable data={filteredKeys} columns={columns} keyField=\"id\" />\n ) : (\n <EmptyState\n icon={<Key size={32} />}\n title={tr(\"devportal.noApiKeysFound\", \"No API keys found\")}\n description={\n searchQuery || statusFilter !== \"all\"\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.generateFirstApiKey\",\n \"Generate your first API key to start making API calls\",\n )\n }\n action={\n !searchQuery && statusFilter === \"all\" ? (\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRevokeTarget(null);\n }}\n onConfirm={handleRevoke}\n title=\"Revoke API Key\"\n description={`Are you sure you want to revoke \"${revokeTarget?.name ?? \"\"}\"? This action cannot be undone. Any application or service using this key will immediately lose access.`}\n confirmLabel=\"Revoke Key\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n\n {/* Rotate confirmation dialog */}\n <ConfirmDialog\n open={rotateTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRotateTarget(null);\n }}\n onConfirm={handleRotate}\n title=\"Rotate API Key Secret\"\n description={`This will generate a new secret for \"${rotateTarget?.name ?? \"\"}\" and immediately invalidate the current one. Make sure to update all services using this key before the old secret stops working.`}\n confirmLabel=\"Rotate Secret\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n loading={rotateLoading}\n />\n\n <ConfirmDialog\n open={showBulkRevokeDialog}\n onOpenChange={setShowBulkRevokeDialog}\n onConfirm={handleBulkRevoke}\n title=\"Revoke selected API keys\"\n description={`Revoke ${selectedActiveKeys.length} selected API key${selectedActiveKeys.length === 1 ? \"\" : \"s\"}? Any service using these keys will immediately lose access.`}\n confirmLabel=\"Revoke Keys\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useDevportal as e } from "../providers/DevportalProvider.js";
|
|
2
2
|
import { EmptyState as t } from "../components/EmptyState.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { useGetApplicationAuditLogsQuery as n } from "../../generated/global-operations.js";
|
|
4
|
+
import { getErrorMessage as r } from "../hooks/useGraphQL.js";
|
|
5
|
+
import { PageLayout as i, PageSection as a } from "../components/PageLayout.js";
|
|
6
6
|
import "../hooks/index.js";
|
|
7
7
|
import o from "../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/format.js";
|
|
8
8
|
import { useEffect as s, useMemo as c, useState as l } from "react";
|
|
@@ -19,7 +19,7 @@ var b = 25, x = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), S = (e) => e.repl
|
|
|
19
19
|
return "";
|
|
20
20
|
}
|
|
21
21
|
}, w = () => {
|
|
22
|
-
let { t: o } = u(), { appId: w } = _(), { navigate: T, basePath: E, currentUser: D } = e(), O = (e) => D?.id === e && D.email ? D.email : e, [k, A] = l("all"), [j, M] = l(""), [N, P] = l(1), { data: F, loading: I, error: L, refetch: R } =
|
|
22
|
+
let { t: o } = u(), { appId: w } = _(), { navigate: T, basePath: E, currentUser: D } = e(), O = (e) => D?.id === e && D.email ? D.email : e, [k, A] = l("all"), [j, M] = l(""), [N, P] = l(1), { data: F, loading: I, error: L, refetch: R } = n({
|
|
23
23
|
variables: {
|
|
24
24
|
applicationId: w || "",
|
|
25
25
|
limit: b,
|
|
@@ -48,7 +48,7 @@ var b = 25, x = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), S = (e) => e.repl
|
|
|
48
48
|
V,
|
|
49
49
|
j
|
|
50
50
|
]);
|
|
51
|
-
return I && !F ? /* @__PURE__ */ v(
|
|
51
|
+
return I && !F ? /* @__PURE__ */ v(i, {
|
|
52
52
|
title: "Application Activity",
|
|
53
53
|
description: "Loading application audit history",
|
|
54
54
|
backLink: w ? `/apps/${w}` : "/apps",
|
|
@@ -57,7 +57,7 @@ var b = 25, x = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), S = (e) => e.repl
|
|
|
57
57
|
className: "flex items-center justify-center py-16 text-muted-foreground",
|
|
58
58
|
children: /* @__PURE__ */ v(h, { className: "h-8 w-8 animate-spin" })
|
|
59
59
|
})
|
|
60
|
-
}) : L || !z ? /* @__PURE__ */ v(
|
|
60
|
+
}) : L || !z ? /* @__PURE__ */ v(i, {
|
|
61
61
|
title: "Application Activity",
|
|
62
62
|
description: "Review audit events for this application",
|
|
63
63
|
backLink: w ? `/apps/${w}` : "/apps",
|
|
@@ -68,9 +68,9 @@ var b = 25, x = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), S = (e) => e.repl
|
|
|
68
68
|
className: "text-status-error-text"
|
|
69
69
|
}),
|
|
70
70
|
title: "Unable to load activity",
|
|
71
|
-
description: L ?
|
|
71
|
+
description: L ? r(L) : "Application not found"
|
|
72
72
|
})
|
|
73
|
-
}) : /* @__PURE__ */ y(
|
|
73
|
+
}) : /* @__PURE__ */ y(i, {
|
|
74
74
|
title: "Application Activity",
|
|
75
75
|
description: `Audit events for ${z.name}`,
|
|
76
76
|
backLink: `/apps/${z.id}`,
|
|
@@ -133,7 +133,7 @@ var b = 25, x = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), S = (e) => e.repl
|
|
|
133
133
|
})
|
|
134
134
|
]
|
|
135
135
|
}),
|
|
136
|
-
/* @__PURE__ */ v(
|
|
136
|
+
/* @__PURE__ */ v(a, { children: U.length > 0 ? /* @__PURE__ */ v("div", {
|
|
137
137
|
className: "space-y-3 rounded-lg border bg-card p-4",
|
|
138
138
|
children: U.map((e) => /* @__PURE__ */ y("article", {
|
|
139
139
|
className: "rounded-lg border bg-background p-4",
|