@stigmer/react 3.32.0 → 3.34.0
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/api-key/ApiKeyListPanel.js +2 -4
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/internal/LastUsedLabel.d.ts +20 -0
- package/internal/LastUsedLabel.d.ts.map +1 -0
- package/internal/LastUsedLabel.js +23 -0
- package/internal/LastUsedLabel.js.map +1 -0
- package/licenses/license-format.d.ts +15 -3
- package/licenses/license-format.d.ts.map +1 -1
- package/licenses/license-format.js +1 -1
- package/licenses/license-format.js.map +1 -1
- package/package.json +5 -5
- package/platform-client/PlatformClientDetailPanel.d.ts +13 -3
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +21 -8
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.d.ts +19 -3
- package/platform-client/PlatformClientListPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientListPanel.js +11 -7
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/copy.d.ts +14 -0
- package/platform-client/copy.d.ts.map +1 -0
- package/platform-client/copy.js +14 -0
- package/platform-client/copy.js.map +1 -0
- package/settings/PlatformClientsSection.d.ts +7 -0
- package/settings/PlatformClientsSection.d.ts.map +1 -1
- package/settings/PlatformClientsSection.js +18 -2
- package/settings/PlatformClientsSection.js.map +1 -1
- package/src/api-key/ApiKeyListPanel.tsx +2 -6
- package/src/internal/LastUsedLabel.tsx +41 -0
- package/src/licenses/__tests__/license-format.test.ts +24 -4
- package/src/licenses/license-format.ts +19 -3
- package/src/platform-client/PlatformClientDetailPanel.tsx +41 -7
- package/src/platform-client/PlatformClientListPanel.tsx +55 -20
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +99 -6
- package/src/platform-client/__tests__/PlatformClientListPanel.test.tsx +140 -0
- package/src/platform-client/copy.ts +15 -0
- package/src/settings/PlatformClientsSection.tsx +23 -2
- package/src/settings/__tests__/PlatformClientsSection.test.tsx +92 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlatformClientListPanel.js","sourceRoot":"","sources":["../../src/platform-client/PlatformClientListPanel.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PlatformClientListPanel.js","sourceRoot":"","sources":["../../src/platform-client/PlatformClientListPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-client copy shared by more than one surface, so the settings
|
|
3
|
+
* section and anything else that lists an organization's platform clients
|
|
4
|
+
* say the same thing.
|
|
5
|
+
*
|
|
6
|
+
* A platform client is a credential: its creator and the organization's
|
|
7
|
+
* admins see it, and other members may not see it at all. An empty list is
|
|
8
|
+
* therefore not evidence that none exists, so a caller who may not create
|
|
9
|
+
* platform clients is told who manages them instead of being told that
|
|
10
|
+
* nothing is configured (stigmer/stigmer#1302).
|
|
11
|
+
*/
|
|
12
|
+
/** Shown in place of the empty state to a caller who may not create platform clients. */
|
|
13
|
+
export declare const PLATFORM_CLIENTS_MANAGED_BY_ADMINS = "Platform clients are managed by your organization's admins.";
|
|
14
|
+
//# sourceMappingURL=copy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../../src/platform-client/copy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,yFAAyF;AACzF,eAAO,MAAM,kCAAkC,gEACgB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-client copy shared by more than one surface, so the settings
|
|
3
|
+
* section and anything else that lists an organization's platform clients
|
|
4
|
+
* say the same thing.
|
|
5
|
+
*
|
|
6
|
+
* A platform client is a credential: its creator and the organization's
|
|
7
|
+
* admins see it, and other members may not see it at all. An empty list is
|
|
8
|
+
* therefore not evidence that none exists, so a caller who may not create
|
|
9
|
+
* platform clients is told who manages them instead of being told that
|
|
10
|
+
* nothing is configured (stigmer/stigmer#1302).
|
|
11
|
+
*/
|
|
12
|
+
/** Shown in place of the empty state to a caller who may not create platform clients. */
|
|
13
|
+
export const PLATFORM_CLIENTS_MANAGED_BY_ADMINS = "Platform clients are managed by your organization's admins.";
|
|
14
|
+
//# sourceMappingURL=copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.js","sourceRoot":"","sources":["../../src/platform-client/copy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,yFAAyF;AACzF,MAAM,CAAC,MAAM,kCAAkC,GAC7C,6DAA6D,CAAC"}
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* A server too old to say either way is offered the button, and its own
|
|
9
9
|
* answer to the create decides: the section never claims a limitation the
|
|
10
10
|
* server did not report. Until the server has answered, it offers neither.
|
|
11
|
+
*
|
|
12
|
+
* The button also needs the caller to hold `can_create_platform_client` on
|
|
13
|
+
* the organization, which the model gives its admins. The list shows only
|
|
14
|
+
* the clients the caller may view (each client's creator and the
|
|
15
|
+
* organization's admins), so a caller who may not create them is told that
|
|
16
|
+
* the organization's admins manage them rather than that none is
|
|
17
|
+
* configured.
|
|
11
18
|
*/
|
|
12
19
|
export declare function PlatformClientsSection(): import("react/jsx-runtime").JSX.Element;
|
|
13
20
|
//# sourceMappingURL=PlatformClientsSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlatformClientsSection.d.ts","sourceRoot":"","sources":["../../src/settings/PlatformClientsSection.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlatformClientsSection.d.ts","sourceRoot":"","sources":["../../src/settings/PlatformClientsSection.tsx"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,4CA8IrC"}
|
|
@@ -5,8 +5,10 @@ import { PlatformClientListPanel } from "../platform-client/PlatformClientListPa
|
|
|
5
5
|
import { CreatePlatformClientForm } from "../platform-client/CreatePlatformClientForm.js";
|
|
6
6
|
import { PlatformClientDetailPanel } from "../platform-client/PlatformClientDetailPanel.js";
|
|
7
7
|
import { PlatformClientSecretAlert } from "../platform-client/PlatformClientSecretAlert.js";
|
|
8
|
+
import { PLATFORM_CLIENTS_MANAGED_BY_ADMINS } from "../platform-client/copy.js";
|
|
9
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
8
10
|
import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
9
|
-
import { useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
11
|
+
import { useActiveOrgId, useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
10
12
|
import { useServerInfo } from "../server-info.js";
|
|
11
13
|
/**
|
|
12
14
|
* Settings section for creating and maintaining platform clients.
|
|
@@ -18,13 +20,27 @@ import { useServerInfo } from "../server-info.js";
|
|
|
18
20
|
* A server too old to say either way is offered the button, and its own
|
|
19
21
|
* answer to the create decides: the section never claims a limitation the
|
|
20
22
|
* server did not report. Until the server has answered, it offers neither.
|
|
23
|
+
*
|
|
24
|
+
* The button also needs the caller to hold `can_create_platform_client` on
|
|
25
|
+
* the organization, which the model gives its admins. The list shows only
|
|
26
|
+
* the clients the caller may view (each client's creator and the
|
|
27
|
+
* organization's admins), so a caller who may not create them is told that
|
|
28
|
+
* the organization's admins manage them rather than that none is
|
|
29
|
+
* configured.
|
|
21
30
|
*/
|
|
22
31
|
export function PlatformClientsSection() {
|
|
23
32
|
const headingId = useId();
|
|
24
33
|
const org = useActiveOrgSlug();
|
|
34
|
+
const orgId = useActiveOrgId();
|
|
25
35
|
const { serverInfo } = useServerInfo();
|
|
26
36
|
const trustsEveryRequest = serverInfo?.authenticationRequired === false;
|
|
27
37
|
const canMint = serverInfo !== null && !trustsEveryRequest;
|
|
38
|
+
const createCheck = useCheckPermission(orgId ? { kind: "organization", id: orgId } : null, "can_create_platform_client");
|
|
39
|
+
const canCreate = !createCheck.isLoading && createCheck.allowed;
|
|
40
|
+
const deniedCreate = !createCheck.isLoading && !createCheck.allowed;
|
|
41
|
+
const emptyState = deniedCreate
|
|
42
|
+
? PLATFORM_CLIENTS_MANAGED_BY_ADMINS
|
|
43
|
+
: undefined;
|
|
28
44
|
const [flow, setFlow] = useState({ phase: "idle" });
|
|
29
45
|
const listRefetchRef = useRef(null);
|
|
30
46
|
const handleRefetchRef = useCallback((refetch) => {
|
|
@@ -56,6 +72,6 @@ export function PlatformClientsSection() {
|
|
|
56
72
|
listRefetchRef.current?.();
|
|
57
73
|
setFlow({ phase: "idle" });
|
|
58
74
|
}, []);
|
|
59
|
-
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsxs("div", { className: "stg:mb-3 stg:flex stg:items-center stg:justify-between", children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "Platform Clients" }), canMint && org && flow.phase === "idle" && (_jsx("button", { type: "button", onClick: () => setFlow({ phase: "creating" }), className: "stg:text-primary stg:hover:text-foreground stg:text-xs stg:font-medium stg:transition-colors", children: "+ New platform client" }))] }), _jsx("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: "Platform clients let your backend mint Stigmer user tokens so you can embed Stigmer components in your application without requiring OIDC federation." }), trustsEveryRequest && (_jsxs(CloudFeatureNotice, { className: "stg:mb-4", children: ["This server trusts every request, so nothing would verify a platform client's tokens and it does not mint them. Configure an identity provider (", _jsx("code", { children: "STIGMER_OIDC_ISSUER" }), ") to create platform clients."] })), flow.phase === "creating" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(CreatePlatformClientForm, { org: org, onCreated: handleCreated, onCancel: () => setFlow({ phase: "idle" }) }) })) : flow.phase === "revealing" ? (_jsxs("div", { className: "stg:space-y-4", children: [_jsx(PlatformClientSecretAlert, { clientId: flow.clientId, clientSecret: flow.clientSecret, context: flow.context, onDismiss: () => setFlow({ phase: "idle" }) }), _jsx(PlatformClientListPanel, { org: org, onEdit: (pc) => setFlow({ phase: "editing", platformClient: pc }), onRefetchRef: handleRefetchRef })] })) : flow.phase === "editing" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(PlatformClientDetailPanel, { platformClient: flow.platformClient, onUpdated: handleUpdated, onSecretRotated: handleSecretRotated, onDeleted: handleDeleted, onBack: () => setFlow({ phase: "idle" }) }) })) : (_jsx(PlatformClientListPanel, { org: org, onEdit: (pc) => setFlow({ phase: "editing", platformClient: pc }), onRefetchRef: handleRefetchRef }))] }));
|
|
75
|
+
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsxs("div", { className: "stg:mb-3 stg:flex stg:items-center stg:justify-between", children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "Platform Clients" }), canMint && canCreate && org && flow.phase === "idle" && (_jsx("button", { type: "button", onClick: () => setFlow({ phase: "creating" }), className: "stg:text-primary stg:hover:text-foreground stg:text-xs stg:font-medium stg:transition-colors", children: "+ New platform client" }))] }), _jsx("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: "Platform clients let your backend mint Stigmer user tokens so you can embed Stigmer components in your application without requiring OIDC federation." }), trustsEveryRequest && (_jsxs(CloudFeatureNotice, { className: "stg:mb-4", children: ["This server trusts every request, so nothing would verify a platform client's tokens and it does not mint them. Configure an identity provider (", _jsx("code", { children: "STIGMER_OIDC_ISSUER" }), ") to create platform clients."] })), flow.phase === "creating" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(CreatePlatformClientForm, { org: org, onCreated: handleCreated, onCancel: () => setFlow({ phase: "idle" }) }) })) : flow.phase === "revealing" ? (_jsxs("div", { className: "stg:space-y-4", children: [_jsx(PlatformClientSecretAlert, { clientId: flow.clientId, clientSecret: flow.clientSecret, context: flow.context, onDismiss: () => setFlow({ phase: "idle" }) }), _jsx(PlatformClientListPanel, { org: org, onEdit: (pc) => setFlow({ phase: "editing", platformClient: pc }), onRefetchRef: handleRefetchRef, emptyState: emptyState })] })) : flow.phase === "editing" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(PlatformClientDetailPanel, { platformClient: flow.platformClient, onUpdated: handleUpdated, onSecretRotated: handleSecretRotated, onDeleted: handleDeleted, onBack: () => setFlow({ phase: "idle" }) }) })) : (_jsx(PlatformClientListPanel, { org: org, onEdit: (pc) => setFlow({ phase: "editing", platformClient: pc }), onRefetchRef: handleRefetchRef, emptyState: emptyState }))] }));
|
|
60
76
|
}
|
|
61
77
|
//# sourceMappingURL=PlatformClientsSection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlatformClientsSection.js","sourceRoot":"","sources":["../../src/settings/PlatformClientsSection.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"PlatformClientsSection.js","sourceRoot":"","sources":["../../src/settings/PlatformClientsSection.tsx"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../internal/tooltip.js"
|
|
|
9
9
|
import { useApiKeyList } from "./useApiKeyList.js";
|
|
10
10
|
import { useDeleteApiKey } from "./useDeleteApiKey.js";
|
|
11
11
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
12
|
-
import {
|
|
12
|
+
import { LastUsedLabel } from "../internal/LastUsedLabel.js";
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Public API
|
|
@@ -263,11 +263,7 @@ function ApiKeyRow({
|
|
|
263
263
|
? `Expires ${formatShortDate(timestampDate(expiresAt))}`
|
|
264
264
|
: "No expiry"}
|
|
265
265
|
</span>
|
|
266
|
-
<
|
|
267
|
-
{lastUsedAt
|
|
268
|
-
? formatRelativeTime(timestampDate(lastUsedAt), now)
|
|
269
|
-
: "Never used"}
|
|
270
|
-
</span>
|
|
266
|
+
<LastUsedLabel at={lastUsedAt} now={now} />
|
|
271
267
|
</div>
|
|
272
268
|
|
|
273
269
|
{/* Delete button */}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
4
|
+
import type { Timestamp } from "@bufbuild/protobuf/wkt";
|
|
5
|
+
import { formatRelativeTime } from "../activity/format-relative-time.js";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
7
|
+
|
|
8
|
+
/** Props for {@link LastUsedLabel}. */
|
|
9
|
+
export interface LastUsedLabelProps {
|
|
10
|
+
/** The credential's `status.last_used_at`; absent means it was never used. */
|
|
11
|
+
readonly at: Timestamp | undefined;
|
|
12
|
+
/** Frozen clock for tests; defaults to the wall clock. */
|
|
13
|
+
readonly now?: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* When a credential was last used, said one way on every surface that
|
|
18
|
+
* lists credentials (API keys, platform clients): "Used 5m" in the SDK's
|
|
19
|
+
* compact relative vocabulary, with the exact instant in a tooltip, or
|
|
20
|
+
* "Never used". The label names what the time is, because it sits beside
|
|
21
|
+
* other dates (created, expires) in the same row.
|
|
22
|
+
*
|
|
23
|
+
* The server records a use at most once a minute, so "Used now" means
|
|
24
|
+
* within the last minute or so.
|
|
25
|
+
*/
|
|
26
|
+
export function LastUsedLabel({ at, now }: LastUsedLabelProps) {
|
|
27
|
+
if (at === undefined) {
|
|
28
|
+
return <span>Never used</span>;
|
|
29
|
+
}
|
|
30
|
+
const date = timestampDate(at);
|
|
31
|
+
return (
|
|
32
|
+
<Tooltip>
|
|
33
|
+
<TooltipTrigger render={<time dateTime={date.toISOString()} />}>
|
|
34
|
+
{`Used ${formatRelativeTime(date, now)}`}
|
|
35
|
+
</TooltipTrigger>
|
|
36
|
+
<TooltipContent side="top">
|
|
37
|
+
{`Last used ${date.toISOString()}`}
|
|
38
|
+
</TooltipContent>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
// The licenses surface's display vocabulary: "covered through" steps back
|
|
2
|
-
// from the expiry instant, relative days count UTC days,
|
|
3
|
-
//
|
|
2
|
+
// from the expiry instant, relative days count UTC days, an absent limit
|
|
3
|
+
// reads as unlimited rather than blank, and the plan-only features are
|
|
4
|
+
// never offered on a license.
|
|
4
5
|
|
|
5
6
|
import { describe, it, expect } from "vitest";
|
|
6
7
|
import { create } from "@bufbuild/protobuf";
|
|
7
|
-
import { EntitlementsSchema } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
|
|
8
|
-
import {
|
|
8
|
+
import { EntitlementsSchema, Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
|
|
9
|
+
import {
|
|
10
|
+
coveredThroughDay,
|
|
11
|
+
entitlementParts,
|
|
12
|
+
formatDayFromToday,
|
|
13
|
+
GRANTABLE_FEATURES,
|
|
14
|
+
} from "../license-format";
|
|
15
|
+
|
|
16
|
+
describe("GRANTABLE_FEATURES", () => {
|
|
17
|
+
it("offers every license feature and never a plan-only one", () => {
|
|
18
|
+
expect(GRANTABLE_FEATURES).toEqual([
|
|
19
|
+
Feature.sso_enforcement,
|
|
20
|
+
Feature.platform_client,
|
|
21
|
+
Feature.byo_provider_keys,
|
|
22
|
+
Feature.channels,
|
|
23
|
+
Feature.sharing,
|
|
24
|
+
]);
|
|
25
|
+
expect(GRANTABLE_FEATURES).not.toContain(Feature.teams);
|
|
26
|
+
expect(GRANTABLE_FEATURES).not.toContain(Feature.managed_organizations);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
9
29
|
|
|
10
30
|
describe("coveredThroughDay", () => {
|
|
11
31
|
it("is the day before a midnight expiry and the same day for a mid-day one", () => {
|
|
@@ -24,8 +24,24 @@ import { LicenseTerm } from "@stigmer/protos/ai/stigmer/platform/v1/license_pb";
|
|
|
24
24
|
/** A term an operator can issue; the unspecified zero value is not one. */
|
|
25
25
|
export type IssuableTerm = LicenseTerm.trial | LicenseTerm.paid;
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Features a Cloud plan sells but a license never carries: they tier the
|
|
29
|
+
* hosted runtime, and an Enterprise deployment has them by edition, so
|
|
30
|
+
* offering one on a license would sell a capability the license does not
|
|
31
|
+
* control.
|
|
32
|
+
*/
|
|
33
|
+
export type PlanOnlyFeature = Feature.teams | Feature.managed_organizations;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A feature an operator can grant on a license: every feature but the
|
|
37
|
+
* unspecified zero value and the plan-only ones. Derived by exclusion, so
|
|
38
|
+
* a feature added to the contract lands here by default and must be given
|
|
39
|
+
* a label below, or be named plan-only above, before the package compiles.
|
|
40
|
+
*/
|
|
41
|
+
export type GrantableFeature = Exclude<
|
|
42
|
+
Feature,
|
|
43
|
+
Feature.feature_unspecified | PlanOnlyFeature
|
|
44
|
+
>;
|
|
29
45
|
|
|
30
46
|
/** Display labels for the issuable terms. */
|
|
31
47
|
export const TERM_LABELS: Readonly<Record<IssuableTerm, string>> = {
|
|
@@ -35,7 +51,7 @@ export const TERM_LABELS: Readonly<Record<IssuableTerm, string>> = {
|
|
|
35
51
|
|
|
36
52
|
/**
|
|
37
53
|
* Label and one-line explanation per grantable feature. Keyed by every
|
|
38
|
-
*
|
|
54
|
+
* grantable `Feature` value, so a feature added to the contract fails to
|
|
39
55
|
* compile here until it has words an operator can read.
|
|
40
56
|
*/
|
|
41
57
|
export const FEATURE_LABELS: Readonly<
|
|
@@ -11,6 +11,8 @@ import type { PlatformClientCreateResponse } from "@stigmer/protos/ai/stigmer/ia
|
|
|
11
11
|
import { useUpdatePlatformClient } from "./useUpdatePlatformClient.js";
|
|
12
12
|
import { useRotatePlatformClientSecret } from "./useRotatePlatformClientSecret.js";
|
|
13
13
|
import { useDeletePlatformClient } from "./useDeletePlatformClient.js";
|
|
14
|
+
import { formatRelativeTime } from "../activity/format-relative-time.js";
|
|
15
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
14
16
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
15
17
|
import { isPlatformClientExpired } from "./expiry.js";
|
|
16
18
|
|
|
@@ -33,14 +35,24 @@ export interface PlatformClientDetailPanelProps {
|
|
|
33
35
|
readonly onBack?: () => void;
|
|
34
36
|
/** Additional CSS class names for the root container. */
|
|
35
37
|
readonly className?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Reference instant for the "last used" relative stamp. Defaults to
|
|
40
|
+
* the live clock. **Deterministic hosts (documentation embeds, video
|
|
41
|
+
* export) must pass a frozen instant** — otherwise a depicted client's
|
|
42
|
+
* "2h ago" drifts with every replay.
|
|
43
|
+
*/
|
|
44
|
+
readonly now?: Date;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
48
|
* View and edit panel for an existing platform client.
|
|
40
49
|
*
|
|
41
50
|
* In **view mode**, displays all configuration fields in a structured
|
|
42
|
-
* label/value layout with
|
|
43
|
-
* actions
|
|
51
|
+
* label/value layout, with when the client last minted a token, and
|
|
52
|
+
* "Edit", "Rotate Secret", and "Delete" actions offered only to a caller
|
|
53
|
+
* the server allows: `can_edit` for Edit and Rotate, `can_delete` for
|
|
54
|
+
* Delete. The organization's admins may view a client they did not
|
|
55
|
+
* create, and see no actions on it.
|
|
44
56
|
*
|
|
45
57
|
* In **edit mode**, mutable spec fields become editable: JIT
|
|
46
58
|
* provisioning toggles, expiry, auto-grant role, and allowed
|
|
@@ -71,11 +83,18 @@ export function PlatformClientDetailPanel({
|
|
|
71
83
|
onDeleted,
|
|
72
84
|
onBack,
|
|
73
85
|
className,
|
|
86
|
+
now,
|
|
74
87
|
}: PlatformClientDetailPanelProps) {
|
|
75
88
|
const baseId = useId();
|
|
76
89
|
const spec = platformClient.spec;
|
|
77
90
|
const meta = platformClient.metadata;
|
|
78
91
|
|
|
92
|
+
const resource = meta?.id ? { kind: "platform_client", id: meta.id } : null;
|
|
93
|
+
const editCheck = useCheckPermission(resource, "can_edit");
|
|
94
|
+
const deleteCheck = useCheckPermission(resource, "can_delete");
|
|
95
|
+
const canEdit = !editCheck.isLoading && editCheck.allowed;
|
|
96
|
+
const canDelete = !deleteCheck.isLoading && deleteCheck.allowed;
|
|
97
|
+
|
|
79
98
|
const { update, isUpdating, error: updateError, clearError: clearUpdateError } =
|
|
80
99
|
useUpdatePlatformClient();
|
|
81
100
|
const { rotateSecret, isRotating, error: rotateError, clearError: clearRotateError } =
|
|
@@ -239,6 +258,7 @@ export function PlatformClientDetailPanel({
|
|
|
239
258
|
|
|
240
259
|
const createdAt = platformClient.status?.audit?.specAudit?.createdAt;
|
|
241
260
|
const updatedAt = platformClient.status?.audit?.specAudit?.updatedAt;
|
|
261
|
+
const lastUsedAt = platformClient.status?.lastUsedAt;
|
|
242
262
|
|
|
243
263
|
return (
|
|
244
264
|
<div className={cn("stg:space-y-4", className)}>
|
|
@@ -272,7 +292,7 @@ export function PlatformClientDetailPanel({
|
|
|
272
292
|
</div>
|
|
273
293
|
</div>
|
|
274
294
|
|
|
275
|
-
{mode === "view" && (
|
|
295
|
+
{mode === "view" && canEdit && (
|
|
276
296
|
<button
|
|
277
297
|
type="button"
|
|
278
298
|
onClick={enterEdit}
|
|
@@ -293,6 +313,8 @@ export function PlatformClientDetailPanel({
|
|
|
293
313
|
spec={spec}
|
|
294
314
|
createdAt={createdAt}
|
|
295
315
|
updatedAt={updatedAt}
|
|
316
|
+
lastUsedAt={lastUsedAt}
|
|
317
|
+
now={now}
|
|
296
318
|
/>
|
|
297
319
|
) : (
|
|
298
320
|
<form onSubmit={handleSave} className="stg:space-y-3">
|
|
@@ -499,13 +521,13 @@ export function PlatformClientDetailPanel({
|
|
|
499
521
|
</form>
|
|
500
522
|
)}
|
|
501
523
|
|
|
502
|
-
{/* Actions bar (view mode only) */}
|
|
503
|
-
{mode === "view" && (
|
|
524
|
+
{/* Actions bar (view mode only, and only the actions the caller may take) */}
|
|
525
|
+
{mode === "view" && (canEdit || canDelete) && (
|
|
504
526
|
<div className="stg:space-y-2 stg:pt-2">
|
|
505
527
|
<hr className="stg:border-border-muted" />
|
|
506
528
|
|
|
507
529
|
{/* Rotate Secret */}
|
|
508
|
-
{confirmingRotate ? (
|
|
530
|
+
{!canEdit ? null : confirmingRotate ? (
|
|
509
531
|
<div className="stg:flex stg:items-center stg:justify-between stg:rounded-md stg:border stg:border-warning/30 stg:bg-warning/5 stg:px-3 stg:py-2">
|
|
510
532
|
<p className="stg:text-xs stg:text-foreground">
|
|
511
533
|
Rotate secret? The current secret will be
|
|
@@ -570,7 +592,7 @@ export function PlatformClientDetailPanel({
|
|
|
570
592
|
)}
|
|
571
593
|
|
|
572
594
|
{/* Delete */}
|
|
573
|
-
{confirmingDelete ? (
|
|
595
|
+
{!canDelete ? null : confirmingDelete ? (
|
|
574
596
|
<div className="stg:flex stg:items-center stg:justify-between stg:rounded-md stg:border stg:border-destructive/30 stg:bg-destructive-subtle stg:px-3 stg:py-2">
|
|
575
597
|
<div className="stg:min-w-0 stg:flex-1">
|
|
576
598
|
<p className="stg:text-xs stg:text-foreground">
|
|
@@ -641,10 +663,14 @@ function ViewMode({
|
|
|
641
663
|
spec,
|
|
642
664
|
createdAt,
|
|
643
665
|
updatedAt,
|
|
666
|
+
lastUsedAt,
|
|
667
|
+
now,
|
|
644
668
|
}: {
|
|
645
669
|
spec: PlatformClient["spec"];
|
|
646
670
|
createdAt?: Timestamp;
|
|
647
671
|
updatedAt?: Timestamp;
|
|
672
|
+
lastUsedAt?: Timestamp;
|
|
673
|
+
now?: Date;
|
|
648
674
|
}) {
|
|
649
675
|
return (
|
|
650
676
|
<dl className="stg:space-y-2.5">
|
|
@@ -730,6 +756,14 @@ function ViewMode({
|
|
|
730
756
|
value={formatDate(timestampDate(updatedAt))}
|
|
731
757
|
/>
|
|
732
758
|
)}
|
|
759
|
+
<Field
|
|
760
|
+
label="Last used"
|
|
761
|
+
value={
|
|
762
|
+
lastUsedAt
|
|
763
|
+
? formatRelativeTime(timestampDate(lastUsedAt), now)
|
|
764
|
+
: "Never"
|
|
765
|
+
}
|
|
766
|
+
/>
|
|
733
767
|
</div>
|
|
734
768
|
</dl>
|
|
735
769
|
);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
4
5
|
import { cn } from "@stigmer/theme";
|
|
5
6
|
import { getUserMessage } from "@stigmer/sdk";
|
|
6
7
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
7
8
|
import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
|
|
9
|
+
import { PermissionGate } from "../iam-policy/PermissionGate.js";
|
|
10
|
+
import { LastUsedLabel } from "../internal/LastUsedLabel.js";
|
|
8
11
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../internal/tooltip.js";
|
|
9
12
|
import { usePlatformClientList } from "./usePlatformClientList.js";
|
|
10
13
|
import { useDeletePlatformClient } from "./useDeletePlatformClient.js";
|
|
@@ -19,8 +22,22 @@ export interface PlatformClientListPanelProps {
|
|
|
19
22
|
readonly onEdit?: (pc: PlatformClient) => void;
|
|
20
23
|
/** Expose the refetch function so parents can trigger a list refresh. */
|
|
21
24
|
readonly onRefetchRef?: (refetch: () => void) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Shown when the list is empty. Defaults to "No platform clients
|
|
27
|
+
* configured." The list holds only the clients the caller may view (each
|
|
28
|
+
* client's creator and the organization's admins), so a host that knows
|
|
29
|
+
* the caller may not manage platform clients should say who does instead.
|
|
30
|
+
*/
|
|
31
|
+
readonly emptyState?: ReactNode;
|
|
22
32
|
/** Additional CSS class names for the root container. */
|
|
23
33
|
readonly className?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Reference instant for the "last used" relative stamp. Defaults to
|
|
36
|
+
* the live clock. **Deterministic hosts (documentation embeds, video
|
|
37
|
+
* export) must pass a frozen instant** — otherwise a depicted client's
|
|
38
|
+
* "2h ago" drifts with every replay.
|
|
39
|
+
*/
|
|
40
|
+
readonly now?: Date;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
/**
|
|
@@ -28,8 +45,9 @@ export interface PlatformClientListPanelProps {
|
|
|
28
45
|
* organization with inline delete confirmation.
|
|
29
46
|
*
|
|
30
47
|
* Each row shows the client name, `client_id` (monospace), secret
|
|
31
|
-
* fingerprint, expiry status,
|
|
32
|
-
*
|
|
48
|
+
* fingerprint, expiry status, creation date and when the client last
|
|
49
|
+
* minted a token. Edit and delete are offered only to a caller the
|
|
50
|
+
* server allows (`can_edit`, `can_delete`); delete confirms inline.
|
|
33
51
|
*
|
|
34
52
|
* Platform clients are admin-level resources with small cardinality
|
|
35
53
|
* (typically 1–5 per org), so the list is rendered without pagination.
|
|
@@ -54,7 +72,9 @@ export function PlatformClientListPanel({
|
|
|
54
72
|
org,
|
|
55
73
|
onEdit,
|
|
56
74
|
onRefetchRef,
|
|
75
|
+
emptyState = "No platform clients configured.",
|
|
57
76
|
className,
|
|
77
|
+
now,
|
|
58
78
|
}: PlatformClientListPanelProps) {
|
|
59
79
|
const { platformClients, isLoading, error, refetch } =
|
|
60
80
|
usePlatformClientList(org);
|
|
@@ -97,7 +117,7 @@ export function PlatformClientListPanel({
|
|
|
97
117
|
className,
|
|
98
118
|
)}
|
|
99
119
|
>
|
|
100
|
-
|
|
120
|
+
{emptyState}
|
|
101
121
|
</p>
|
|
102
122
|
);
|
|
103
123
|
}
|
|
@@ -115,6 +135,7 @@ export function PlatformClientListPanel({
|
|
|
115
135
|
key={id}
|
|
116
136
|
platformClient={pc}
|
|
117
137
|
isConfirming={confirmingId === id}
|
|
138
|
+
now={now}
|
|
118
139
|
onEdit={onEdit ? () => onEdit(pc) : undefined}
|
|
119
140
|
onConfirmDelete={() => setConfirmingId(id)}
|
|
120
141
|
onCancelDelete={() => setConfirmingId(null)}
|
|
@@ -136,6 +157,7 @@ export function PlatformClientListPanel({
|
|
|
136
157
|
function PlatformClientRow({
|
|
137
158
|
platformClient,
|
|
138
159
|
isConfirming,
|
|
160
|
+
now,
|
|
139
161
|
onEdit,
|
|
140
162
|
onConfirmDelete,
|
|
141
163
|
onCancelDelete,
|
|
@@ -143,6 +165,7 @@ function PlatformClientRow({
|
|
|
143
165
|
}: {
|
|
144
166
|
platformClient: PlatformClient;
|
|
145
167
|
isConfirming: boolean;
|
|
168
|
+
now?: Date;
|
|
146
169
|
onEdit?: () => void;
|
|
147
170
|
onConfirmDelete: () => void;
|
|
148
171
|
onCancelDelete: () => void;
|
|
@@ -158,6 +181,7 @@ function PlatformClientRow({
|
|
|
158
181
|
const fingerprint = spec?.secretFingerprint ?? "";
|
|
159
182
|
const createdAt =
|
|
160
183
|
platformClient.status?.audit?.specAudit?.createdAt;
|
|
184
|
+
const lastUsedAt = platformClient.status?.lastUsedAt;
|
|
161
185
|
|
|
162
186
|
const handleDelete = useCallback(async () => {
|
|
163
187
|
try {
|
|
@@ -269,35 +293,46 @@ function PlatformClientRow({
|
|
|
269
293
|
</TooltipContent>
|
|
270
294
|
</Tooltip>
|
|
271
295
|
)}
|
|
296
|
+
<LastUsedLabel at={lastUsedAt} now={now} />
|
|
272
297
|
</div>
|
|
273
298
|
|
|
274
299
|
<div className="stg:flex stg:shrink-0 stg:items-center stg:gap-1">
|
|
275
300
|
{onEdit && (
|
|
301
|
+
<PermissionGate
|
|
302
|
+
resource={{ kind: "platform_client", id }}
|
|
303
|
+
relation="can_edit"
|
|
304
|
+
>
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
onClick={onEdit}
|
|
308
|
+
aria-label={`Edit ${name}`}
|
|
309
|
+
className={cn(
|
|
310
|
+
"stg:shrink-0 stg:rounded stg:p-1",
|
|
311
|
+
"stg:text-muted-foreground stg:hover:text-foreground stg:hover:bg-accent-hover",
|
|
312
|
+
"stg:transition-colors",
|
|
313
|
+
)}
|
|
314
|
+
>
|
|
315
|
+
<PencilIcon />
|
|
316
|
+
</button>
|
|
317
|
+
</PermissionGate>
|
|
318
|
+
)}
|
|
319
|
+
<PermissionGate
|
|
320
|
+
resource={{ kind: "platform_client", id }}
|
|
321
|
+
relation="can_delete"
|
|
322
|
+
>
|
|
276
323
|
<button
|
|
277
324
|
type="button"
|
|
278
|
-
onClick={
|
|
279
|
-
aria-label={`
|
|
325
|
+
onClick={onConfirmDelete}
|
|
326
|
+
aria-label={`Delete ${name}`}
|
|
280
327
|
className={cn(
|
|
281
328
|
"stg:shrink-0 stg:rounded stg:p-1",
|
|
282
|
-
"stg:text-muted-foreground stg:hover:text-
|
|
329
|
+
"stg:text-muted-foreground stg:hover:text-destructive stg:hover:bg-destructive-subtle",
|
|
283
330
|
"stg:transition-colors",
|
|
284
331
|
)}
|
|
285
332
|
>
|
|
286
|
-
<
|
|
333
|
+
<TrashIcon />
|
|
287
334
|
</button>
|
|
288
|
-
|
|
289
|
-
<button
|
|
290
|
-
type="button"
|
|
291
|
-
onClick={onConfirmDelete}
|
|
292
|
-
aria-label={`Delete ${name}`}
|
|
293
|
-
className={cn(
|
|
294
|
-
"stg:shrink-0 stg:rounded stg:p-1",
|
|
295
|
-
"stg:text-muted-foreground stg:hover:text-destructive stg:hover:bg-destructive-subtle",
|
|
296
|
-
"stg:transition-colors",
|
|
297
|
-
)}
|
|
298
|
-
>
|
|
299
|
-
<TrashIcon />
|
|
300
|
-
</button>
|
|
335
|
+
</PermissionGate>
|
|
301
336
|
</div>
|
|
302
337
|
</div>
|
|
303
338
|
);
|