ankka-console 0.15.0 → 0.16.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/dist/client/control-plane.d.ts +19 -1
- package/dist/client/control-plane.js +38 -1
- package/dist/client/schemas.d.ts +216 -0
- package/dist/client/schemas.js +93 -0
- package/dist/extensions/types.d.ts +1 -1
- package/dist/extensions/types.js +5 -0
- package/dist/routes/erasure.certificate.d.ts +7 -0
- package/dist/routes/erasure.certificate.js +12 -0
- package/dist/routes/erasure.d.ts +53 -0
- package/dist/routes/erasure.js +74 -0
- package/dist/routes/project.d.ts +27 -1
- package/dist/routes/project.js +30 -5
- package/dist/routes.js +2 -0
- package/dist/styles.css +1 -1
- package/dist/testing/fake-control-plane.d.ts +50 -0
- package/dist/testing/fake-control-plane.js +220 -3
- package/dist/ui/erasures.d.ts +9 -0
- package/dist/ui/erasures.js +16 -0
- package/package.json +8 -8
package/dist/routes/project.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A project: its services, kept current while the page is open, its registry credential, its
|
|
3
3
|
* project secrets — by name and entry, never a value — the topics it declares on the broker, the
|
|
4
|
-
* grants it makes to other projects' services and to machines,
|
|
4
|
+
* grants it makes to other projects' services and to machines, what other projects grant it, and
|
|
5
|
+
* its erasure requests with where each stands.
|
|
5
6
|
*/
|
|
6
7
|
import { type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "react-router";
|
|
7
8
|
import { ConsoleErrorBoundary } from "../ui/errors.tsx";
|
|
@@ -403,6 +404,31 @@ export declare function loader({ params, context }: LoaderFunctionArgs): Promise
|
|
|
403
404
|
copies: number | undefined;
|
|
404
405
|
} | undefined;
|
|
405
406
|
}[];
|
|
407
|
+
erasures: {
|
|
408
|
+
id: string;
|
|
409
|
+
subject: string;
|
|
410
|
+
state: string;
|
|
411
|
+
correlation: string | undefined;
|
|
412
|
+
notBefore: string | undefined;
|
|
413
|
+
askedBy: string | undefined;
|
|
414
|
+
askedAt: string | undefined;
|
|
415
|
+
appliedAt: string | undefined;
|
|
416
|
+
finalAt: string | undefined;
|
|
417
|
+
project: string | undefined;
|
|
418
|
+
completions: {
|
|
419
|
+
service: string;
|
|
420
|
+
completedAt: string | undefined;
|
|
421
|
+
rowsRedacted: number;
|
|
422
|
+
tokensDropped: number;
|
|
423
|
+
sessionsEnded: number;
|
|
424
|
+
handler: {
|
|
425
|
+
objectsErased: number;
|
|
426
|
+
finality: string;
|
|
427
|
+
error: string | undefined;
|
|
428
|
+
} | undefined;
|
|
429
|
+
}[];
|
|
430
|
+
}[];
|
|
431
|
+
erasuresMore: boolean;
|
|
406
432
|
panels: import("../extensions/render.tsx").LoadedPanels;
|
|
407
433
|
}>;
|
|
408
434
|
export declare function action({ request, params, context }: ActionFunctionArgs): Promise<import("react-router").UNSAFE_DataWithResponseInit<import("../context.ts").ActionRefusal> | import("react-router").UNSAFE_DataWithResponseInit<{
|
package/dist/routes/project.js
CHANGED
|
@@ -2,7 +2,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
/**
|
|
3
3
|
* A project: its services, kept current while the page is open, its registry credential, its
|
|
4
4
|
* project secrets — by name and entry, never a value — the topics it declares on the broker, the
|
|
5
|
-
* grants it makes to other projects' services and to machines,
|
|
5
|
+
* grants it makes to other projects' services and to machines, what other projects grant it, and
|
|
6
|
+
* its erasure requests with where each stands.
|
|
6
7
|
*/
|
|
7
8
|
import { data, redirect, useActionData, useLoaderData } from "react-router";
|
|
8
9
|
import { ControlPlaneError } from "../client/errors.js";
|
|
@@ -14,13 +15,14 @@ import { Refused, useRefusal } from "../ui/refused.js";
|
|
|
14
15
|
import { Lifecycle } from "../ui/status.js";
|
|
15
16
|
import { useProjectStream } from "../ui/use-stream.js";
|
|
16
17
|
import { targetText } from "../ui/grants.js";
|
|
18
|
+
import { notBeforeOf } from "../ui/erasures.js";
|
|
17
19
|
import { HostActions, loadPanels, Panels } from "../extensions/render.js";
|
|
18
20
|
export const meta = ({ loaderData }) => [{ title: `${loaderData?.project.name ?? "Project"} · ankka` }];
|
|
19
21
|
export async function loader({ params, context }) {
|
|
20
22
|
const ctx = useConsoleContext(context);
|
|
21
23
|
const id = params.projectId;
|
|
22
24
|
return guard(ctx, async () => {
|
|
23
|
-
const [project, services, secrets, topics, brokers, grants, received, page, backups, rehearsals, restores, history, setting] = await Promise.all([
|
|
25
|
+
const [project, services, secrets, topics, brokers, grants, received, page, backups, rehearsals, restores, history, setting, erasureList] = await Promise.all([
|
|
24
26
|
ctx.client.getProject(id),
|
|
25
27
|
ctx.client.listServices(id),
|
|
26
28
|
ctx.client.listProjectSecrets(id),
|
|
@@ -36,7 +38,15 @@ export async function loader({ params, context }) {
|
|
|
36
38
|
ctx.client.listRestores(id).catch(() => []),
|
|
37
39
|
ctx.client.projectHistory(id).catch(() => []),
|
|
38
40
|
ctx.client.projectDatabase(id).catch(() => undefined),
|
|
41
|
+
// Feature 042: a control plane that cannot list them leaves the section empty rather than failing the page.
|
|
42
|
+
ctx.client.listErasures(id).catch(() => []),
|
|
39
43
|
]);
|
|
44
|
+
// An applied request shows each service's completion, which only the request itself carries.
|
|
45
|
+
const completed = await Promise.all(erasureList
|
|
46
|
+
.filter((e) => e.state === "applied")
|
|
47
|
+
.slice(0, ErasuresShown)
|
|
48
|
+
.map((e) => ctx.client.getErasure(id, e.id).catch(() => undefined)));
|
|
49
|
+
const erasures = erasureList.slice(0, ErasuresShown).map((e) => ({ ...e, completions: completed.find((v) => v?.id === e.id)?.completions ?? [] }));
|
|
40
50
|
// A verified restore says what the broker holds past its moment, asked as it is read.
|
|
41
51
|
const reports = await Promise.all(restores.filter((r) => r.phase === "Verified" || r.phase === "InUse").map((r) => ctx.client.getRestore(id, r.name).catch(() => r)));
|
|
42
52
|
const organization = await ctx.client.getOrganization(project.organizationId);
|
|
@@ -56,10 +66,14 @@ export async function loader({ params, context }) {
|
|
|
56
66
|
setting,
|
|
57
67
|
grants,
|
|
58
68
|
received,
|
|
69
|
+
erasures,
|
|
70
|
+
erasuresMore: erasureList.length > ErasuresShown,
|
|
59
71
|
panels: await loadPanels(ctx, "project", project),
|
|
60
72
|
};
|
|
61
73
|
});
|
|
62
74
|
}
|
|
75
|
+
/** How many erasure requests the project's page lists, newest first; the CLI lists them all. */
|
|
76
|
+
const ErasuresShown = 50;
|
|
63
77
|
export async function action({ request, params, context }) {
|
|
64
78
|
const ctx = useConsoleContext(context);
|
|
65
79
|
const id = params.projectId;
|
|
@@ -172,6 +186,16 @@ export async function action({ request, params, context }) {
|
|
|
172
186
|
case "grant-make":
|
|
173
187
|
await ctx.client.makeGrant(id, { grantee: text(form, "grantee"), target: grantTarget(form) });
|
|
174
188
|
return redirect(self);
|
|
189
|
+
case "erasure-ask": {
|
|
190
|
+
// The request's own page, by id: the listing is a projection and may not have it yet.
|
|
191
|
+
const asked = await ctx.client.askErasure(id, {
|
|
192
|
+
subject: text(form, "erasureSubject"),
|
|
193
|
+
notBefore: notBeforeOf(text(form, "erasureNotBefore")),
|
|
194
|
+
reason: text(form, "erasureReason") || undefined,
|
|
195
|
+
correlation: text(form, "erasureCorrelation") || undefined,
|
|
196
|
+
});
|
|
197
|
+
return redirect(ctx.href(`projects/${encodeURIComponent(id)}/erasures/${encodeURIComponent(asked.id)}`));
|
|
198
|
+
}
|
|
175
199
|
case "grant-end":
|
|
176
200
|
// A pending grant is withdrawn and an accepted one revoked: the control plane decides which.
|
|
177
201
|
await ctx.client.endGrant(id, text(form, "grantId"));
|
|
@@ -204,7 +228,8 @@ function grantTarget(form) {
|
|
|
204
228
|
/** A live grant is ended by withdrawing it while it waits for an answer, by revoking it once accepted. */
|
|
205
229
|
const endVerb = (state) => (state === "pending" ? "Withdraw" : "Revoke");
|
|
206
230
|
export default function Project() {
|
|
207
|
-
const { project: p, organization: o, services: initial, secrets, topics, brokers, grants, received, backups, rehearsals, restores, history, setting, panels } = useLoaderData();
|
|
231
|
+
const { project: p, organization: o, services: initial, secrets, topics, brokers, grants, received, backups, rehearsals, restores, history, setting, erasures, erasuresMore, panels } = useLoaderData();
|
|
232
|
+
const erasureRefusal = useRefusal("erasure-ask");
|
|
208
233
|
const databaseRefusal = useRefusal("database");
|
|
209
234
|
// One history: what was done to a topic names it, and is shown with the topics.
|
|
210
235
|
const topicHistory = history.filter((h) => h.topic !== undefined);
|
|
@@ -226,9 +251,9 @@ export default function Project() {
|
|
|
226
251
|
const path = `projects/${encodeURIComponent(p.id)}`;
|
|
227
252
|
const inspector = (_jsxs(_Fragment, { children: [_jsx("div", { className: "ac-ops", children: _jsx(HostActions, { operation: "service.apply", entity: p }) }), _jsxs("section", { className: "ac-form", "aria-labelledby": "registry-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "registry-title", children: "Registry" }) }), shows("registry.set") ? (_jsxs("details", { className: "ac-more", open: registryRefusal !== undefined || undefined, children: [_jsx("summary", { children: p.registry ? "Replace the registry credential" : "Set a registry credential" }), _jsxs(ConsoleForm, { intent: "registry-set", className: "ac-form", children: [_jsx(Field, { label: "Registry server", name: "server", required: true, placeholder: "ghcr.io", defaultValue: registryRefusal?.values.server ?? p.registry?.server }), _jsx(Field, { label: "Username", name: "username", required: true, autoComplete: "off", defaultValue: registryRefusal?.values.username ?? p.registry?.username }), _jsx(Field, { label: "Password or token", name: "password", type: "password", required: true, autoComplete: "new-password", hint: "Sent once to the platform, which keeps it only in the cluster. It is never shown again." }), _jsx(Refused, { intent: "registry-set" }), _jsx("div", { children: _jsx(Submit, { intent: "registry-set", children: "Save credential" }) })] })] })) : null, p.registry && shows("registry.clear") ? (_jsxs(ConsoleForm, { intent: "registry-clear", children: [_jsx(Submit, { intent: "registry-clear", children: "Clear credential" }), _jsx(Refused, { intent: "registry-clear" })] })) : null] }), shows("project-secret.set") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "secret-set-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "secret-set-title", children: "Project secrets" }) }), _jsxs("details", { className: "ac-more", open: secretRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Set a project secret entry" }), _jsxs(ConsoleForm, { intent: "secret-set", className: "ac-form", children: [_jsx(Field, { label: "Project secret", name: "secretName", required: true, placeholder: "checkout", defaultValue: secretRefusal?.values.secretName }), _jsx(Field, { label: "Entry", name: "secretEntry", required: true, placeholder: "STRIPE_KEY", autoComplete: "off", defaultValue: secretRefusal?.values.secretEntry }), _jsx(Field, { label: "Value", name: "secretValue", type: "password", required: true, autoComplete: "new-password", hint: "Sent once to the platform, which keeps it only in the cluster. It is never shown again." }), _jsx(Refused, { intent: "secret-set" }), _jsx("div", { children: _jsx(Submit, { intent: "secret-set", children: "Save entry" }) })] })] })] })) : null, shows("project-topic.set") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "topic-set-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "topic-set-title", children: "Topics" }) }), _jsxs("details", { className: "ac-more", open: topicRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Declare a topic" }), _jsxs(ConsoleForm, { intent: "topic-set", className: "ac-form", children: [_jsx(Field, { label: "Topic", name: "topicName", required: true, placeholder: "transactions", defaultValue: topicRefusal?.values.topicName }), _jsx(Field, { label: "Partitions", name: "topicPartitions", type: "number", defaultValue: topicRefusal?.values.topicPartitions ?? "3", hint: "Needed for a new topic. A topic can be given more partitions later, never fewer." }), _jsx(Field, { label: "Retention", name: "topicRetention", placeholder: "90d", autoComplete: "off", defaultValue: topicRefusal?.values.topicRetention, hint: "How long the topic keeps a message: 90d, 36h, or everything. Empty: the installation's default, or what the topic has." }), _jsx(Field, { label: "Retention size", name: "topicRetentionSize", placeholder: "50GiB", autoComplete: "off", defaultValue: topicRefusal?.values.topicRetentionSize, hint: "How much each partition keeps: 50GiB, or none." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "topicCleanup", children: "Cleanup policy" }), _jsxs("select", { id: "topicCleanup", name: "topicCleanup", defaultValue: topicRefusal?.values.topicCleanup ?? "", children: [_jsx("option", { value: "", children: "Unchanged, or the installation's default" }), _jsx("option", { value: "delete", children: "delete" }), _jsx("option", { value: "compact", children: "compact" }), _jsx("option", { value: "compact,delete", children: "compact,delete" })] })] }), _jsx(Field, { label: "Tombstone window", name: "topicTombstoneWindow", placeholder: "1d", autoComplete: "off", defaultValue: topicRefusal?.values.topicTombstoneWindow, hint: "How long a compacted topic keeps a deletion." }), _jsx(Field, { label: "Minimum compaction lag", name: "topicMinCompactionLag", placeholder: "0s", autoComplete: "off", defaultValue: topicRefusal?.values.topicMinCompactionLag }), _jsx(Field, { label: "Maximum compaction lag", name: "topicMaxCompactionLag", placeholder: "none", autoComplete: "off", defaultValue: topicRefusal?.values.topicMaxCompactionLag }), _jsx(Field, { label: "Copies", name: "topicCopies", type: "number", defaultValue: topicRefusal?.values.topicCopies, hint: "How many copies the broker keeps; fixed once the topic is declared." }), _jsx(Field, { label: "Minimum in-sync copies", name: "topicMinInSync", type: "number", defaultValue: topicRefusal?.values.topicMinInSync, hint: "How many copies must hold a message before it is acknowledged; fixed once declared." }), _jsxs("div", { className: "ac-field", children: [_jsxs("label", { htmlFor: "topicCompacted", children: [_jsx("input", { id: "topicCompacted", name: "topicCompacted", type: "checkbox", defaultChecked: topicRefusal?.values.topicCompacted === "on" }), " Compacted"] }), _jsx("p", { className: "ac-hint", id: "topicCompacted-hint", children: "The broker keeps the last message under each key, as a graph's delta topic needs." })] }), _jsx(Field, { label: "Contract", name: "topicContract", placeholder: "order.v1", autoComplete: "off", defaultValue: topicRefusal?.values.topicContract, hint: "What the topic carries, which every side must state. Optional, with its schema." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "topicSchema", children: "Schema" }), _jsx("textarea", { id: "topicSchema", name: "topicSchema", spellCheck: false, placeholder: '{"type": "object"}', defaultValue: topicRefusal?.values.topicSchema, "aria-describedby": "topicSchema-hint" }), _jsx("p", { className: "ac-hint", id: "topicSchema-hint", children: "The contract's JSON Schema document, held by the project for members to build against." })] }), removal === undefined ? _jsx(Refused, { intent: "topic-set" }) : null, _jsx("div", { children: _jsx(Submit, { intent: "topic-set", children: "Declare topic" }) })] }), removal !== undefined && topicRefusal ? (_jsxs("div", { className: "ac-card", role: "alertdialog", "aria-labelledby": "topic-removal-title", "data-removal-dialog": true, children: [_jsx("h3", { id: "topic-removal-title", children: "This declaration removes messages" }), _jsxs("p", { children: ["It removes ", removal, ", and they are gone. Only an owner of the organization may send it."] }), _jsxs(ConsoleForm, { intent: "topic-set", className: "ac-inline", children: [Object.entries(topicRefusal.values)
|
|
228
253
|
.filter(([k]) => k !== "intent" && k !== "topicRemoves")
|
|
229
|
-
.map(([k, v]) => (_jsx("input", { type: "hidden", name: k, value: v }, k))), _jsx("input", { type: "hidden", name: "topicRemoves", value: removal }), _jsx(Submit, { intent: "topic-set", children: "Remove them" })] })] })) : null] })] })) : null, shows("project-broker.set") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "broker-set-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "broker-set-title", children: "Brokers" }) }), _jsxs("details", { className: "ac-more", open: brokerRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Declare a broker" }), _jsxs(ConsoleForm, { intent: "broker-set", className: "ac-form", children: [_jsx(Field, { label: "Broker", name: "brokerName", required: true, placeholder: "legacy", defaultValue: brokerRefusal?.values.brokerName, hint: "The name a component gives for a topic that lives there." }), _jsx(Field, { label: "Bootstrap", name: "brokerBootstrap", required: true, placeholder: "kafka.legacy:9094", autoComplete: "off", defaultValue: brokerRefusal?.values.brokerBootstrap, hint: "host:port, several separated by commas." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "brokerShape", children: "Shape" }), _jsxs("select", { id: "brokerShape", name: "brokerShape", defaultValue: brokerRefusal?.values.brokerShape ?? "sasl", "aria-describedby": "brokerShape-hint", children: [_jsx("option", { value: "sasl", children: "sasl" }), _jsx("option", { value: "certificate", children: "certificate" })] }), _jsx("p", { className: "ac-hint", id: "brokerShape-hint", children: "sasl: the secret holds ca.crt, username and password. certificate: ca.crt, tls.crt and tls.key. Both over TLS." })] }), _jsx(Field, { label: "Credential secret", name: "brokerSecret", required: true, placeholder: "legacy-credential", autoComplete: "off", list: "broker-secrets", defaultValue: brokerRefusal?.values.brokerSecret, hint: "The project secret holding the credential, mounted for the platform's program alone." }), _jsx("datalist", { id: "broker-secrets", children: secrets.map((s) => (_jsx("option", { value: s.name }, s.name))) }), _jsx(Refused, { intent: "broker-set" }), _jsx("div", { children: _jsx(Submit, { intent: "broker-set", children: "Declare broker" }) })] })] })] })) : null, shows("project-location.set") || shows("project-location.clear") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "location-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "location-title", children: "Bucket location" }) }), shows("project-location.set") ? (_jsxs("details", { className: "ac-more", open: locationRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Choose where new buckets are made" }), _jsxs(ConsoleForm, { intent: "location-set", className: "ac-form", children: [_jsx(Field, { label: "Location", name: "location", required: true, placeholder: "europe-west6", autoComplete: "off", defaultValue: locationRefusal?.values.location, hint: "Where the project's new buckets in Google Cloud Storage are made, in the installation's own words. A bucket's location is fixed when it is made, so this moves no bucket." }), _jsx(Refused, { intent: "location-set" }), _jsx("div", { children: _jsx(Submit, { intent: "location-set", children: "Set location" }) })] })] })) : null, shows("project-location.clear") ? (_jsxs(ConsoleForm, { intent: "location-clear", children: [_jsx(Submit, { intent: "location-clear", children: "Use the installation's location" }), _jsx(Refused, { intent: "location-clear" })] })) : null] })) : null, shows("grant.make") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "grant-make-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "grant-make-title", children: "Grants" }) }), _jsxs("details", { className: "ac-more", open: grantRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Make a grant" }), _jsxs(ConsoleForm, { intent: "grant-make", className: "ac-form", children: [_jsx(Field, { label: "Grantee", name: "grantee", required: true, placeholder: "service:payments/merchant", autoComplete: "off", defaultValue: grantRefusal?.values.grantee, hint: "service:<project>/<service>, or machine:<organization>/<name>. Another organization's grantee must accept first." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "grantKind", children: "Grant kind" }), _jsxs("select", { id: "grantKind", name: "grantKind", defaultValue: grantRefusal?.values.grantKind ?? "route", "aria-describedby": "grantKind-hint", children: [_jsx("option", { value: "route", children: "route" }), _jsx("option", { value: "method", children: "method" }), _jsx("option", { value: "topic", children: "topic" }), _jsx("option", { value: "erasure", children: "erasure" })] }), _jsx("p", { className: "ac-hint", id: "grantKind-hint", children: "route: a service, an HTTP method and a path. method: a service and a gRPC method. topic: a declared topic and a right. erasure: nothing more." })] }), _jsx(Field, { label: "Granted service", name: "grantService", placeholder: "wallet", autoComplete: "off", defaultValue: grantRefusal?.values.grantService }), _jsx(Field, { label: "Granted method", name: "grantMethod", placeholder: "POST", autoComplete: "off", defaultValue: grantRefusal?.values.grantMethod, hint: "An HTTP method for a route; Service/Method for a gRPC method." }), _jsx(Field, { label: "Granted path", name: "grantPath", placeholder: "/v1/wallets/{player}", autoComplete: "off", defaultValue: grantRefusal?.values.grantPath }), _jsx(Field, { label: "Granted topic", name: "grantTopic", list: "grant-topics", autoComplete: "off", defaultValue: grantRefusal?.values.grantTopic }), _jsx("datalist", { id: "grant-topics", children: topics.map((t) => (_jsx("option", { value: t.name }, t.name))) }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "grantRight", children: "Granted right" }), _jsxs("select", { id: "grantRight", name: "grantRight", defaultValue: grantRefusal?.values.grantRight ?? "consume", children: [_jsx("option", { value: "consume", children: "consume" }), _jsx("option", { value: "produce", children: "produce" })] })] }), _jsxs("div", { className: "ac-field", children: [_jsxs("label", { htmlFor: "grantDecrypt", children: [_jsx("input", { id: "grantDecrypt", name: "grantDecrypt", type: "checkbox", defaultChecked: grantRefusal?.values.grantDecrypt === "on" }), " Allow decryption"] }), _jsx("p", { className: "ac-hint", id: "grantDecrypt-hint", children: "Only with consume: the grantee may read what the topic's producers encrypted." })] }), _jsx(Refused, { intent: "grant-make" }), _jsx("div", { children: _jsx(Submit, { intent: "grant-make", children: "Make grant" }) })] })] }), _jsx("div", { className: "ac-ops", children: _jsx(HostActions, { operation: "grant.make", entity: p }) })] })) : null, shows("project.rename") || shows("project.delete") ? (_jsxs("details", { className: "ac-more", open: renameRefusal !== undefined || deleteRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Rename or delete" }), shows("project.rename") ? (_jsxs(ConsoleForm, { intent: "rename", className: "ac-inline", children: [_jsx(Field, { label: "New name", name: "name", required: true, defaultValue: renameRefusal?.values.name ?? p.name }), _jsx(Submit, { intent: "rename", children: "Rename" })] })) : null, _jsx(Refused, { intent: "rename" }), shows("project.delete") ? (_jsxs("div", { className: "ac-danger-zone", children: [_jsxs("p", { children: ["Deleting ", p.name, " is permanent, and its id can never be used again. It must have no services."] }), _jsx(ConsoleForm, { intent: "delete", children: _jsx(Submit, { intent: "delete", danger: true, children: "Delete project" }) }), _jsx(Refused, { intent: "delete" })] })) : null] })) : null] }));
|
|
254
|
+
.map(([k, v]) => (_jsx("input", { type: "hidden", name: k, value: v }, k))), _jsx("input", { type: "hidden", name: "topicRemoves", value: removal }), _jsx(Submit, { intent: "topic-set", children: "Remove them" })] })] })) : null] })] })) : null, shows("project-broker.set") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "broker-set-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "broker-set-title", children: "Brokers" }) }), _jsxs("details", { className: "ac-more", open: brokerRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Declare a broker" }), _jsxs(ConsoleForm, { intent: "broker-set", className: "ac-form", children: [_jsx(Field, { label: "Broker", name: "brokerName", required: true, placeholder: "legacy", defaultValue: brokerRefusal?.values.brokerName, hint: "The name a component gives for a topic that lives there." }), _jsx(Field, { label: "Bootstrap", name: "brokerBootstrap", required: true, placeholder: "kafka.legacy:9094", autoComplete: "off", defaultValue: brokerRefusal?.values.brokerBootstrap, hint: "host:port, several separated by commas." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "brokerShape", children: "Shape" }), _jsxs("select", { id: "brokerShape", name: "brokerShape", defaultValue: brokerRefusal?.values.brokerShape ?? "sasl", "aria-describedby": "brokerShape-hint", children: [_jsx("option", { value: "sasl", children: "sasl" }), _jsx("option", { value: "certificate", children: "certificate" })] }), _jsx("p", { className: "ac-hint", id: "brokerShape-hint", children: "sasl: the secret holds ca.crt, username and password. certificate: ca.crt, tls.crt and tls.key. Both over TLS." })] }), _jsx(Field, { label: "Credential secret", name: "brokerSecret", required: true, placeholder: "legacy-credential", autoComplete: "off", list: "broker-secrets", defaultValue: brokerRefusal?.values.brokerSecret, hint: "The project secret holding the credential, mounted for the platform's program alone." }), _jsx("datalist", { id: "broker-secrets", children: secrets.map((s) => (_jsx("option", { value: s.name }, s.name))) }), _jsx(Refused, { intent: "broker-set" }), _jsx("div", { children: _jsx(Submit, { intent: "broker-set", children: "Declare broker" }) })] })] })] })) : null, shows("project-location.set") || shows("project-location.clear") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "location-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "location-title", children: "Bucket location" }) }), shows("project-location.set") ? (_jsxs("details", { className: "ac-more", open: locationRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Choose where new buckets are made" }), _jsxs(ConsoleForm, { intent: "location-set", className: "ac-form", children: [_jsx(Field, { label: "Location", name: "location", required: true, placeholder: "europe-west6", autoComplete: "off", defaultValue: locationRefusal?.values.location, hint: "Where the project's new buckets in Google Cloud Storage are made, in the installation's own words. A bucket's location is fixed when it is made, so this moves no bucket." }), _jsx(Refused, { intent: "location-set" }), _jsx("div", { children: _jsx(Submit, { intent: "location-set", children: "Set location" }) })] })] })) : null, shows("project-location.clear") ? (_jsxs(ConsoleForm, { intent: "location-clear", children: [_jsx(Submit, { intent: "location-clear", children: "Use the installation's location" }), _jsx(Refused, { intent: "location-clear" })] })) : null] })) : null, shows("grant.make") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "grant-make-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "grant-make-title", children: "Grants" }) }), _jsxs("details", { className: "ac-more", open: grantRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Make a grant" }), _jsxs(ConsoleForm, { intent: "grant-make", className: "ac-form", children: [_jsx(Field, { label: "Grantee", name: "grantee", required: true, placeholder: "service:payments/merchant", autoComplete: "off", defaultValue: grantRefusal?.values.grantee, hint: "service:<project>/<service>, or machine:<organization>/<name>. Another organization's grantee must accept first." }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "grantKind", children: "Grant kind" }), _jsxs("select", { id: "grantKind", name: "grantKind", defaultValue: grantRefusal?.values.grantKind ?? "route", "aria-describedby": "grantKind-hint", children: [_jsx("option", { value: "route", children: "route" }), _jsx("option", { value: "method", children: "method" }), _jsx("option", { value: "topic", children: "topic" }), _jsx("option", { value: "erasure", children: "erasure" })] }), _jsx("p", { className: "ac-hint", id: "grantKind-hint", children: "route: a service, an HTTP method and a path. method: a service and a gRPC method. topic: a declared topic and a right. erasure: nothing more." })] }), _jsx(Field, { label: "Granted service", name: "grantService", placeholder: "wallet", autoComplete: "off", defaultValue: grantRefusal?.values.grantService }), _jsx(Field, { label: "Granted method", name: "grantMethod", placeholder: "POST", autoComplete: "off", defaultValue: grantRefusal?.values.grantMethod, hint: "An HTTP method for a route; Service/Method for a gRPC method." }), _jsx(Field, { label: "Granted path", name: "grantPath", placeholder: "/v1/wallets/{player}", autoComplete: "off", defaultValue: grantRefusal?.values.grantPath }), _jsx(Field, { label: "Granted topic", name: "grantTopic", list: "grant-topics", autoComplete: "off", defaultValue: grantRefusal?.values.grantTopic }), _jsx("datalist", { id: "grant-topics", children: topics.map((t) => (_jsx("option", { value: t.name }, t.name))) }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "grantRight", children: "Granted right" }), _jsxs("select", { id: "grantRight", name: "grantRight", defaultValue: grantRefusal?.values.grantRight ?? "consume", children: [_jsx("option", { value: "consume", children: "consume" }), _jsx("option", { value: "produce", children: "produce" })] })] }), _jsxs("div", { className: "ac-field", children: [_jsxs("label", { htmlFor: "grantDecrypt", children: [_jsx("input", { id: "grantDecrypt", name: "grantDecrypt", type: "checkbox", defaultChecked: grantRefusal?.values.grantDecrypt === "on" }), " Allow decryption"] }), _jsx("p", { className: "ac-hint", id: "grantDecrypt-hint", children: "Only with consume: the grantee may read what the topic's producers encrypted." })] }), _jsx(Refused, { intent: "grant-make" }), _jsx("div", { children: _jsx(Submit, { intent: "grant-make", children: "Make grant" }) })] })] }), _jsx("div", { className: "ac-ops", children: _jsx(HostActions, { operation: "grant.make", entity: p }) })] })) : null, shows("erasure.ask") ? (_jsxs("section", { className: "ac-form", "aria-labelledby": "erasure-ask-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "erasure-ask-title", children: "Erasure" }) }), _jsxs("details", { className: "ac-more", open: erasureRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Ask for an erasure" }), _jsxs(ConsoleForm, { intent: "erasure-ask", className: "ac-form", children: [_jsx(Field, { label: "Data subject", name: "erasureSubject", required: true, placeholder: "player/8c1f", autoComplete: "off", defaultValue: erasureRefusal?.values.erasureSubject, hint: "Who the personal data is about, as the project's services name them: letters, digits, '.', '_', '-', '/' and ':'." }), _jsx(Field, { label: "Not-before date", name: "erasureNotBefore", type: "date", defaultValue: erasureRefusal?.values.erasureNotBefore, hint: "Leave empty to apply it at once. A hold keeps it until the start of that day, UTC." }), _jsx(Field, { label: "Reason", name: "erasureReason", autoComplete: "off", defaultValue: erasureRefusal?.values.erasureReason, hint: "Why it is held; at most 500 characters." }), _jsx(Field, { label: "Correlation", name: "erasureCorrelation", autoComplete: "off", defaultValue: erasureRefusal?.values.erasureCorrelation, hint: "An id of your own to find it by, such as a ticket." }), _jsx(Refused, { intent: "erasure-ask" }), _jsx("div", { children: _jsx(Submit, { intent: "erasure-ask", children: "Ask for an erasure" }) })] })] })] })) : null, shows("project.rename") || shows("project.delete") ? (_jsxs("details", { className: "ac-more", open: renameRefusal !== undefined || deleteRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Rename or delete" }), shows("project.rename") ? (_jsxs(ConsoleForm, { intent: "rename", className: "ac-inline", children: [_jsx(Field, { label: "New name", name: "name", required: true, defaultValue: renameRefusal?.values.name ?? p.name }), _jsx(Submit, { intent: "rename", children: "Rename" })] })) : null, _jsx(Refused, { intent: "rename" }), shows("project.delete") ? (_jsxs("div", { className: "ac-danger-zone", children: [_jsxs("p", { children: ["Deleting ", p.name, " is permanent, and its id can never be used again. It must have no services."] }), _jsx(ConsoleForm, { intent: "delete", children: _jsx(Submit, { intent: "delete", danger: true, children: "Delete project" }) }), _jsx(Refused, { intent: "delete" })] })) : null] })) : null] }));
|
|
230
255
|
return (_jsxs(Page, { inspector: inspector, children: [_jsx("h1", { children: p.name }), _jsxs("dl", { className: "ac-card ac-facts", children: [_jsx("dt", { children: "Id" }), _jsx("dd", { children: p.id }), _jsx("dt", { children: "Organization" }), _jsx("dd", { children: o.name }), _jsx("dt", { children: "Registry" }), _jsx("dd", { children: p.registry ? (_jsxs(_Fragment, { children: ["Private images are pulled from ", _jsx("strong", { children: p.registry.server }), " as ", _jsx("strong", { children: p.registry.username }), ", set ", when(p.registry.setAt), p.registry.setBy ? ` by ${p.registry.setBy}` : "", "."] })) : ("Services in this project pull only public images. Set a credential to pull private ones.") })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "services", children: [_jsx("h2", { id: "services", children: "Services" }), state === "live" ? _jsx("p", { className: "ac-live", children: "Updating as the platform reports" }) : null, services.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No services yet. Apply a descriptor to deploy one." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "services", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Service" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", className: "ac-num", children: "Instances" }), _jsx("th", { scope: "col", children: "Image" }), _jsx("th", { scope: "col", className: "ac-num", children: "Generation" }), _jsx("th", { scope: "col", children: "Address" })] }) }), _jsx("tbody", { children: services.map((s) => (_jsxs("tr", { "data-service": s.name, children: [_jsx("td", { children: _jsx(ConsoleLink, { to: `${path}/services/${encodeURIComponent(s.name)}`, children: s.name }) }), _jsx("td", { children: _jsx(Lifecycle, { lifecycle: s.lifecycle, confirmed: s.confirmed }) }), _jsxs("td", { className: "ac-num", children: [s.readyInstances, " of ", s.desiredInstances] }), _jsx("td", { children: s.image }), _jsx("td", { className: "ac-num", children: s.generation }), _jsxs("td", { children: [s.hostname ? _jsx("a", { href: s.hostname, children: s.hostname.replace(/^https:\/\//, "") }) : s.exposed ? "Exposed, no address yet" : "Not exposed", s.customHostnames.map((h) => (_jsxs("span", { className: "ac-hint", "data-custom-hostname": h.hostname, children: [", ", h.hostname, h.state === "serving" ? "" : ` (${h.state})`] }, h.hostname)))] })] }, s.name))) })] }) }))] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "secrets", children: [_jsx("h2", { id: "secrets", children: "Project secrets" }), secrets.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No project secrets. A descriptor's variable can take an entry of one by secretKeyRef." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "secrets", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Secret" }), _jsx("th", { scope: "col", children: "Entry" }), _jsx("th", { scope: "col", children: "Set" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: secrets.flatMap((s) => s.entries.map((entry) => (_jsxs("tr", { "data-secret": s.name, "data-entry": entry, children: [_jsx("td", { children: s.name }), _jsx("td", { children: entry }), _jsxs("td", { children: [when(s.setAt), s.setBy ? ` by ${s.setBy}` : ""] }), _jsx("td", { children: shows("project-secret.unset") ? (_jsxs(ConsoleForm, { intent: "secret-unset", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "secretName", value: s.name }), _jsx("input", { type: "hidden", name: "secretEntry", value: entry }), _jsx(Submit, { intent: "secret-unset", children: `Remove ${entry}` })] })) : null })] }, `${s.name}/${entry}`)))) })] }) })), _jsx(Refused, { intent: "secret-unset" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "topics", children: [_jsx("h2", { id: "topics", children: "Topics" }), topics.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No topics declared. Every service of the project uses a declared topic by its name." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "topics", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Topic" }), _jsx("th", { scope: "col", className: "ac-num", children: "Partitions" }), _jsx("th", { scope: "col", children: "Retention" }), _jsx("th", { scope: "col", children: "Size" }), _jsx("th", { scope: "col", children: "Cleanup" }), _jsx("th", { scope: "col", children: "Copies" }), _jsx("th", { scope: "col", children: "Compacted" }), _jsx("th", { scope: "col", children: "Contract" }), _jsx("th", { scope: "col", children: "Broker" }), _jsx("th", { scope: "col", children: "Checks" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: topics.map((t) => (_jsxs("tr", { "data-topic": t.name, children: [_jsx("td", { children: _jsx("code", { children: t.name }) }), _jsx("td", { className: "ac-num", children: t.partitions }), ["retention", "retentionSize", "cleanup"].map((field) => (_jsxs("td", { "data-setting": field, "data-defaulted": t.settings?.defaulted.includes(field) ? "yes" : "no", children: [t.settings ? t.settings[field] : "—", t.settings?.defaulted.includes(field) ? _jsx("span", { className: "ac-hint", children: " default" }) : null] }, field))), _jsx("td", { "data-setting": "copies", children: t.settings?.copies !== undefined && t.settings?.copies !== null
|
|
231
256
|
? `${t.settings.copies}, ${t.settings.minInSync ?? 1} in sync`
|
|
232
|
-
: `the broker's${t.copiesHeld ? ` (${t.copiesHeld})` : ""}` }), _jsx("td", { "data-compacted": t.compacted ? "yes" : "no", children: t.compacted ? "yes" : "no" }), _jsx("td", { children: t.contract ? (_jsxs(_Fragment, { children: [_jsx("code", { children: t.contract.name }), " ", _jsx("span", { className: "ac-hint", children: t.contract.fingerprint.slice(0, 15) })] })) : ("none") }), _jsxs("td", { children: [t.phase ?? "Nothing reported yet", t.detail ? ` — ${t.detail}` : ""] }), _jsx("td", { children: t.checks.length === 0 ? (t.contract ? "no side seen yet" : "—") : (_jsx("ul", { className: "ac-topics", children: t.checks.map((c) => (_jsxs("li", { "data-check": c.state, children: [c.service, " ", c.direction, ": ", c.state, c.state === "mismatch" ? ` (${c.stated ?? "none"})` : ""] }, `${c.service}/${c.component}/${c.direction}`))) })) }), _jsxs("td", { children: [t.contract ? (_jsxs(ConsoleForm, { intent: "topic-schema", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "topicName", value: t.name }), _jsx(Submit, { intent: "topic-schema", children: `Show schema of ${t.name}` })] })) : null, shows("project-topic.unset") ? (_jsxs(ConsoleForm, { intent: "topic-unset", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "topicName", value: t.name }), _jsx(Submit, { intent: "topic-unset", children: `Stop declaring ${t.name}` })] })) : null] })] }, t.name))) })] }) })), shownSchema ? (_jsxs("div", { className: "ac-card", "data-schema": shownSchema.topic, children: [_jsxs("h3", { children: ["Schema of ", _jsx("code", { children: shownSchema.topic })] }), _jsx("pre", { children: JSON.stringify(shownSchema.schema, null, 2) })] })) : null, _jsx(Refused, { intent: "topic-unset" }), _jsx(Refused, { intent: "topic-schema" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "project-history", children: [_jsx("h2", { id: "project-history", children: "Topic history" }), topicHistory.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No topic has been declared, changed or removed yet." })) : (_jsx("ul", { className: "ac-topics", children: topicHistory.map((h, i) => (_jsxs("li", { "data-history": h.kind, "data-topic": h.topic, children: [h.at ? `${h.at} ` : "", _jsx("code", { children: h.topic }), " ", h.kind.replace("topic-", ""), " by ", h.by ?? "the platform", h.changes.length > 0 ? `: ${h.changes.map((c) => `${c.setting} ${c.from} → ${c.to}`).join("; ")}` : ""] }, i))) }))] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "brokers", children: [_jsx("h2", { id: "brokers", children: "Brokers" }), brokers.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No brokers declared beside the installation's. A component may name one for a topic that lives elsewhere." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "brokers", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Broker" }), _jsx("th", { scope: "col", children: "Bootstrap" }), _jsx("th", { scope: "col", children: "Shape" }), _jsx("th", { scope: "col", children: "Secret" }), _jsx("th", { scope: "col", children: "Declared" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: brokers.map((b) => (_jsxs("tr", { "data-broker": b.name, children: [_jsx("td", { children: _jsx("code", { children: b.name }) }), _jsx("td", { children: b.bootstrap }), _jsx("td", { children: b.shape }), _jsx("td", { children: b.secret }), _jsx("td", { children: b.declaredAt ? when(b.declaredAt) : "—" }), _jsx("td", { children: shows("project-broker.unset") ? (_jsxs(ConsoleForm, { intent: "broker-unset", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "brokerName", value: b.name }), _jsx(Submit, { intent: "broker-unset", children: `Stop declaring ${b.name}` })] })) : null })] }, b.name))) })] }) })), _jsx(Refused, { intent: "broker-unset" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "database-title", id: "database", children: [_jsx("h2", { id: "database-title", children: "Database" }), !backups ? (_jsx("p", { className: "ac-empty", children: "The control plane could not say how the project's database is backed up." })) : (_jsxs(_Fragment, { children: [_jsxs("p", { "data-backed-up": backups.backedUp ? "yes" : "no", children: [backups.backedUp ? "Backed up." : "Not backed up.", backups.detail ? ` ${backups.detail}` : ""] }), backups.database ? (_jsxs("p", { children: [backups.database.readyInstances, " of ", backups.database.instances, " instances ready", backups.database.primary ? `, primary ${backups.database.primary}` : "", backups.database.synchronous ? ", synchronous" : "", backups.database.writesWaitingOn ? ` — ${backups.database.writesWaitingOn}` : ""] })) : null, backups.lines.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Lines of history", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Line" }), _jsx("th", { scope: "col", children: "Backups" }), _jsx("th", { scope: "col", children: "Last base backup" }), _jsx("th", { scope: "col", children: "Restorable from" }), _jsx("th", { scope: "col", children: "To" })] }) }), _jsx("tbody", { children: backups.lines.map((l) => (_jsxs("tr", { "data-line": l.line, children: [_jsx("td", { children: _jsx("code", { children: l.line }) }), _jsxs("td", { children: [l.phase, l.failing ? ` — ${l.failing}` : ""] }), _jsx("td", { children: l.lastBaseBackup ? when(l.lastBaseBackup) : "—" }), _jsx("td", { children: l.firstRestorable ? when(l.firstRestorable) : "—" }), _jsx("td", { children: l.lastRestorable ? when(l.lastRestorable) : "—" })] }, l.line))) })] }) })) : null, backups.clusters.length > 0 ? (_jsx("ul", { className: "ac-topics", "aria-label": "Database clusters", children: backups.clusters.map((c) => (_jsxs("li", { "data-cluster": c.name, "data-phase": c.phase, children: [_jsx("code", { children: c.name }), " (", c.phase, c.leftAt ? `, left ${when(c.leftAt)}` : "", c.since ? `, since ${when(c.since)}` : "", "): ", c.services.length === 0 ? "no service" : c.services.join(", ")] }, c.name))) })) : null, restores.map((r) => (_jsxs("div", { className: "ac-card", id: `restore-${r.name}`, "data-restore": r.name, "data-phase": r.phase, children: [_jsxs("h3", { children: ["Restore ", _jsx("code", { children: r.name }), " of ", r.line, " at ", when(r.moment), ": ", r.phase] }), r.detail ? _jsx("p", { children: r.detail }) : null, r.services.length > 0 ? (_jsx("ul", { className: "ac-topics", children: r.services.map((v) => (_jsxs("li", { "data-verified": v.name, children: [v.name, ": ", v.present ? `${v.journalRows} journal rows, highest sequence ${v.highestSequence}` : "no database at the moment", v.changedSecrets.length > 0 ? `; secrets changed since: ${v.changedSecrets.join(", ")}` : "", v.present && (r.phase === "Verified" || r.phase === "InUse") && shows("service.switch") ? (_jsxs(ConsoleForm, { intent: "switch", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "service", value: v.name }), _jsx("input", { type: "hidden", name: "cluster", value: r.name }), _jsx(Submit, { intent: "switch", children: `Switch ${v.name} to ${r.name}` })] })) : null] }, v.name))) })) : null, r.broker.length > 0 ? (_jsx("ul", { className: "ac-topics", "aria-label": `What ${r.name} cannot take back`, children: r.broker.map((d) => (_jsxs("li", { "data-diverged": d.topic, children: [_jsx("code", { children: d.topic }), d.group ? ` read by ${d.group}: ${d.read ?? 0} of` : ":", " ", d.after, " messages newer than the moment"] }, `${d.topic}/${d.group ?? ""}`))) })) : null, r.note ? _jsx("p", { className: "ac-hint", children: r.note }) : null] }, r.name))), _jsx(Refused, { intent: "switch" })] })), setting ? (_jsxs("p", { "data-setting": `${setting.replicas}`, children: ["Asks for ", setting.replicas === 0 ? "a primary alone" : `${setting.replicas} replica${setting.replicas === 1 ? "" : "s"}`, setting.synchronous ? ", synchronous" : "", setting.retentionDays ? `, backups kept ${setting.retentionDays} days` : "", setting.rehearse ? `, rehearsed ${setting.rehearse}` : "", "."] })) : null, shows("project.database") ? (_jsxs("details", { className: "ac-more", open: databaseRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Replicas, retention and rehearsals" }), _jsxs(ConsoleForm, { intent: "database", className: "ac-form", children: [_jsx(Field, { label: "Replicas", name: "replicas", type: "number", defaultValue: databaseRefusal?.values.replicas ?? String(setting?.replicas ?? 0), hint: "Beside the primary, 0 to 4; one takes its place if it is lost." }), _jsxs("div", { className: "ac-field", children: [_jsxs("label", { htmlFor: "synchronous", children: [_jsx("input", { id: "synchronous", name: "synchronous", type: "checkbox", defaultChecked: setting?.synchronous }), " Synchronous"] }), _jsx("p", { className: "ac-hint", children: "Every write waits until a replica holds it, so none is lost with the primary." })] }), _jsx(Field, { label: "Keep backups for (days)", name: "retentionDays", type: "number", defaultValue: databaseRefusal?.values.retentionDays ?? (setting?.retentionDays ? String(setting.retentionDays) : "") }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "rehearse", children: "Rehearse a restore" }), _jsxs("select", { id: "rehearse", name: "rehearse", defaultValue: setting?.rehearse ?? "", children: [_jsx("option", { value: "", children: "never" }), _jsx("option", { value: "daily", children: "daily" }), _jsx("option", { value: "weekly", children: "weekly" })] })] }), _jsx(Refused, { intent: "database" }), _jsx("div", { children: _jsx(Submit, { intent: "database", children: "Save" }) })] })] })) : null, shows("project.backup-credential") ? (_jsxs(ConsoleForm, { intent: "reissue", className: "ac-inline", children: [_jsx(Submit, { intent: "reissue", children: "Issue the backup credential again" }), _jsx(Refused, { intent: "reissue" })] })) : null, shows("project.restore") ? (_jsxs("details", { className: "ac-more", children: [_jsx("summary", { children: "Restore to a moment" }), _jsxs(ConsoleForm, { intent: "restore", className: "ac-form", children: [_jsx(Field, { label: "Moment", name: "moment", type: "datetime-local", required: true, hint: "A new cluster beside the current one; nothing in the current database changes. Owners only." }), _jsx(Field, { label: "Line", name: "line", placeholder: "ankka-db", hint: "Only when the project's services are on more than one cluster." }), _jsx(Refused, { intent: "restore" }), _jsx("div", { children: _jsx(Submit, { intent: "restore", children: "Restore" }) })] })] })) : null] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "rehearsals-title", id: "rehearsals", children: [_jsx("h2", { id: "rehearsals-title", children: "Rehearsals" }), rehearsals.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No rehearsals yet. A rehearsal restores into a namespace of its own, checks it, times it and removes it." })) : (_jsx("ul", { className: "ac-topics", children: rehearsals.map((r) => (_jsxs("li", { "data-rehearsal": r.name, "data-outcome": r.outcome, children: [_jsx("code", { children: r.name }), " to ", when(r.moment), ": ", r.outcome, r.elapsedSeconds !== undefined ? `, in ${r.elapsedSeconds}s` : "", r.requestedBy ? `, asked by ${r.requestedBy}` : ", on the schedule", r.detail ? ` — ${r.detail}` : ""] }, r.name))) })), shows("project.rehearse") ? (_jsx(ConsoleForm, { intent: "rehearse", className: "ac-inline", children: _jsx(Submit, { intent: "rehearse", children: "Rehearse a restore" }) })) : null, _jsx(Refused, { intent: "rehearse" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "database-history", children: [_jsx("h2", { id: "database-history", children: "Database history" }), databaseHistory.length === 0 ? (_jsx("p", { className: "ac-empty", children: "Nothing has been done to the project's database yet." })) : (_jsx("ul", { className: "ac-topics", children: databaseHistory.map((h, i) => (_jsxs("li", { "data-history": h.kind, children: [h.kind, h.by ? ` by ${h.by}` : "", h.at ? `, ${when(h.at)}` : "", h.detail ? `: ${h.detail}` : ""] }, i))) }))] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "grants", children: [_jsx("h2", { id: "grants", children: "Grants" }), grants.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No grants. A grant lets a service of another project, or a machine, reach one route, method or topic of this project." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "grants", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Grantee" }), _jsx("th", { scope: "col", children: "Target" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", children: "Effect" }), _jsx("th", { scope: "col", children: "Granted" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: grants.map((g) => (_jsxs("tr", { "data-grant": g.id, "data-state": g.state, children: [_jsx("td", { children: _jsx("code", { children: g.grantee }) }), _jsx("td", { children: targetText(g.target) }), _jsx("td", { children: g.state }), _jsx("td", { "data-effect": true, children: g.effect }), _jsxs("td", { children: [g.granted.at ? when(g.granted.at) : "—", g.granted.by ? ` by ${g.granted.by}` : "", g.ended ? (_jsxs("span", { className: "ac-hint", children: [" ", "\u00B7 ended ", g.ended.at ? when(g.ended.at) : "", g.ended.by ? ` by ${g.ended.by}` : ""] })) : null] }), _jsx("td", { children: shows("grant.end") && (g.state === "pending" || g.state === "accepted") ? (_jsxs(ConsoleForm, { intent: "grant-end", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "grantId", value: g.id }), _jsx(Submit, { intent: "grant-end", label: `${endVerb(g.state)} the grant to ${g.grantee} of ${targetText(g.target)}`, children: endVerb(g.state) })] })) : null })] }, g.id))) })] }) })), _jsx(Refused, { intent: "grant-end" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "received", children: [_jsx("h2", { id: "received", children: "Received" }), received.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No other project grants this project's services anything." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "received", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "Grantee" }), _jsx("th", { scope: "col", children: "Target" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", children: "Changes" })] }) }), _jsx("tbody", { children: received.map((r) => (_jsxs("tr", { "data-received": r.id, "data-state": r.state, children: [_jsxs("td", { children: [r.grantingProject, " ", _jsxs("span", { className: "ac-hint", children: ["of ", r.grantingOrganization] })] }), _jsx("td", { children: _jsx("code", { children: r.grantee }) }), _jsxs("td", { children: [targetText(r.target), r.topic ? (_jsxs("span", { className: "ac-hint", children: [" ", "\u00B7 ", r.topic.partitions, " partitions", r.topic.compacted ? ", compacted" : "", r.topic.retention ? `, kept ${r.topic.retention}` : ""] })) : null] }), _jsx("td", { children: r.state }), _jsx("td", { children: _jsx("ul", { className: "ac-topics", children: r.changes.map((c, i) => (_jsxs("li", { children: [c.change, c.at ? ` ${when(c.at)}` : "", c.by ? ` by ${c.by}` : ""] }, `${c.change}-${i}`))) }) })] }, r.id))) })] }) }))] }), _jsx(Panels, { kind: "project", entity: p, loaded: panels })] }));
|
|
257
|
+
: `the broker's${t.copiesHeld ? ` (${t.copiesHeld})` : ""}` }), _jsx("td", { "data-compacted": t.compacted ? "yes" : "no", children: t.compacted ? "yes" : "no" }), _jsx("td", { children: t.contract ? (_jsxs(_Fragment, { children: [_jsx("code", { children: t.contract.name }), " ", _jsx("span", { className: "ac-hint", children: t.contract.fingerprint.slice(0, 15) })] })) : ("none") }), _jsxs("td", { children: [t.phase ?? "Nothing reported yet", t.detail ? ` — ${t.detail}` : ""] }), _jsx("td", { children: t.checks.length === 0 ? (t.contract ? "no side seen yet" : "—") : (_jsx("ul", { className: "ac-topics", children: t.checks.map((c) => (_jsxs("li", { "data-check": c.state, children: [c.service, " ", c.direction, ": ", c.state, c.state === "mismatch" ? ` (${c.stated ?? "none"})` : ""] }, `${c.service}/${c.component}/${c.direction}`))) })) }), _jsxs("td", { children: [t.contract ? (_jsxs(ConsoleForm, { intent: "topic-schema", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "topicName", value: t.name }), _jsx(Submit, { intent: "topic-schema", children: `Show schema of ${t.name}` })] })) : null, shows("project-topic.unset") ? (_jsxs(ConsoleForm, { intent: "topic-unset", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "topicName", value: t.name }), _jsx(Submit, { intent: "topic-unset", children: `Stop declaring ${t.name}` })] })) : null] })] }, t.name))) })] }) })), shownSchema ? (_jsxs("div", { className: "ac-card", "data-schema": shownSchema.topic, children: [_jsxs("h3", { children: ["Schema of ", _jsx("code", { children: shownSchema.topic })] }), _jsx("pre", { children: JSON.stringify(shownSchema.schema, null, 2) })] })) : null, _jsx(Refused, { intent: "topic-unset" }), _jsx(Refused, { intent: "topic-schema" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "project-history", children: [_jsx("h2", { id: "project-history", children: "Topic history" }), topicHistory.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No topic has been declared, changed or removed yet." })) : (_jsx("ul", { className: "ac-topics", children: topicHistory.map((h, i) => (_jsxs("li", { "data-history": h.kind, "data-topic": h.topic, children: [h.at ? `${h.at} ` : "", _jsx("code", { children: h.topic }), " ", h.kind.replace("topic-", ""), " by ", h.by ?? "the platform", h.changes.length > 0 ? `: ${h.changes.map((c) => `${c.setting} ${c.from} → ${c.to}`).join("; ")}` : ""] }, i))) }))] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "brokers", children: [_jsx("h2", { id: "brokers", children: "Brokers" }), brokers.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No brokers declared beside the installation's. A component may name one for a topic that lives elsewhere." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "brokers", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Broker" }), _jsx("th", { scope: "col", children: "Bootstrap" }), _jsx("th", { scope: "col", children: "Shape" }), _jsx("th", { scope: "col", children: "Secret" }), _jsx("th", { scope: "col", children: "Declared" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: brokers.map((b) => (_jsxs("tr", { "data-broker": b.name, children: [_jsx("td", { children: _jsx("code", { children: b.name }) }), _jsx("td", { children: b.bootstrap }), _jsx("td", { children: b.shape }), _jsx("td", { children: b.secret }), _jsx("td", { children: b.declaredAt ? when(b.declaredAt) : "—" }), _jsx("td", { children: shows("project-broker.unset") ? (_jsxs(ConsoleForm, { intent: "broker-unset", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "brokerName", value: b.name }), _jsx(Submit, { intent: "broker-unset", children: `Stop declaring ${b.name}` })] })) : null })] }, b.name))) })] }) })), _jsx(Refused, { intent: "broker-unset" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "database-title", id: "database", children: [_jsx("h2", { id: "database-title", children: "Database" }), !backups ? (_jsx("p", { className: "ac-empty", children: "The control plane could not say how the project's database is backed up." })) : (_jsxs(_Fragment, { children: [_jsxs("p", { "data-backed-up": backups.backedUp ? "yes" : "no", children: [backups.backedUp ? "Backed up." : "Not backed up.", backups.detail ? ` ${backups.detail}` : ""] }), backups.database ? (_jsxs("p", { children: [backups.database.readyInstances, " of ", backups.database.instances, " instances ready", backups.database.primary ? `, primary ${backups.database.primary}` : "", backups.database.synchronous ? ", synchronous" : "", backups.database.writesWaitingOn ? ` — ${backups.database.writesWaitingOn}` : ""] })) : null, backups.lines.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Lines of history", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Line" }), _jsx("th", { scope: "col", children: "Backups" }), _jsx("th", { scope: "col", children: "Last base backup" }), _jsx("th", { scope: "col", children: "Restorable from" }), _jsx("th", { scope: "col", children: "To" })] }) }), _jsx("tbody", { children: backups.lines.map((l) => (_jsxs("tr", { "data-line": l.line, children: [_jsx("td", { children: _jsx("code", { children: l.line }) }), _jsxs("td", { children: [l.phase, l.failing ? ` — ${l.failing}` : ""] }), _jsx("td", { children: l.lastBaseBackup ? when(l.lastBaseBackup) : "—" }), _jsx("td", { children: l.firstRestorable ? when(l.firstRestorable) : "—" }), _jsx("td", { children: l.lastRestorable ? when(l.lastRestorable) : "—" })] }, l.line))) })] }) })) : null, backups.clusters.length > 0 ? (_jsx("ul", { className: "ac-topics", "aria-label": "Database clusters", children: backups.clusters.map((c) => (_jsxs("li", { "data-cluster": c.name, "data-phase": c.phase, children: [_jsx("code", { children: c.name }), " (", c.phase, c.leftAt ? `, left ${when(c.leftAt)}` : "", c.since ? `, since ${when(c.since)}` : "", "): ", c.services.length === 0 ? "no service" : c.services.join(", ")] }, c.name))) })) : null, restores.map((r) => (_jsxs("div", { className: "ac-card", id: `restore-${r.name}`, "data-restore": r.name, "data-phase": r.phase, children: [_jsxs("h3", { children: ["Restore ", _jsx("code", { children: r.name }), " of ", r.line, " at ", when(r.moment), ": ", r.phase] }), r.detail ? _jsx("p", { children: r.detail }) : null, r.services.length > 0 ? (_jsx("ul", { className: "ac-topics", children: r.services.map((v) => (_jsxs("li", { "data-verified": v.name, children: [v.name, ": ", v.present ? `${v.journalRows} journal rows, highest sequence ${v.highestSequence}` : "no database at the moment", v.changedSecrets.length > 0 ? `; secrets changed since: ${v.changedSecrets.join(", ")}` : "", v.present && (r.phase === "Verified" || r.phase === "InUse") && shows("service.switch") ? (_jsxs(ConsoleForm, { intent: "switch", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "service", value: v.name }), _jsx("input", { type: "hidden", name: "cluster", value: r.name }), _jsx(Submit, { intent: "switch", children: `Switch ${v.name} to ${r.name}` })] })) : null] }, v.name))) })) : null, r.broker.length > 0 ? (_jsx("ul", { className: "ac-topics", "aria-label": `What ${r.name} cannot take back`, children: r.broker.map((d) => (_jsxs("li", { "data-diverged": d.topic, children: [_jsx("code", { children: d.topic }), d.group ? ` read by ${d.group}: ${d.read ?? 0} of` : ":", " ", d.after, " messages newer than the moment"] }, `${d.topic}/${d.group ?? ""}`))) })) : null, r.note ? _jsx("p", { className: "ac-hint", children: r.note }) : null] }, r.name))), _jsx(Refused, { intent: "switch" })] })), setting ? (_jsxs("p", { "data-setting": `${setting.replicas}`, children: ["Asks for ", setting.replicas === 0 ? "a primary alone" : `${setting.replicas} replica${setting.replicas === 1 ? "" : "s"}`, setting.synchronous ? ", synchronous" : "", setting.retentionDays ? `, backups kept ${setting.retentionDays} days` : "", setting.rehearse ? `, rehearsed ${setting.rehearse}` : "", "."] })) : null, shows("project.database") ? (_jsxs("details", { className: "ac-more", open: databaseRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Replicas, retention and rehearsals" }), _jsxs(ConsoleForm, { intent: "database", className: "ac-form", children: [_jsx(Field, { label: "Replicas", name: "replicas", type: "number", defaultValue: databaseRefusal?.values.replicas ?? String(setting?.replicas ?? 0), hint: "Beside the primary, 0 to 4; one takes its place if it is lost." }), _jsxs("div", { className: "ac-field", children: [_jsxs("label", { htmlFor: "synchronous", children: [_jsx("input", { id: "synchronous", name: "synchronous", type: "checkbox", defaultChecked: setting?.synchronous }), " Synchronous"] }), _jsx("p", { className: "ac-hint", children: "Every write waits until a replica holds it, so none is lost with the primary." })] }), _jsx(Field, { label: "Keep backups for (days)", name: "retentionDays", type: "number", defaultValue: databaseRefusal?.values.retentionDays ?? (setting?.retentionDays ? String(setting.retentionDays) : "") }), _jsxs("div", { className: "ac-field", children: [_jsx("label", { htmlFor: "rehearse", children: "Rehearse a restore" }), _jsxs("select", { id: "rehearse", name: "rehearse", defaultValue: setting?.rehearse ?? "", children: [_jsx("option", { value: "", children: "never" }), _jsx("option", { value: "daily", children: "daily" }), _jsx("option", { value: "weekly", children: "weekly" })] })] }), _jsx(Refused, { intent: "database" }), _jsx("div", { children: _jsx(Submit, { intent: "database", children: "Save" }) })] })] })) : null, shows("project.backup-credential") ? (_jsxs(ConsoleForm, { intent: "reissue", className: "ac-inline", children: [_jsx(Submit, { intent: "reissue", children: "Issue the backup credential again" }), _jsx(Refused, { intent: "reissue" })] })) : null, shows("project.restore") ? (_jsxs("details", { className: "ac-more", children: [_jsx("summary", { children: "Restore to a moment" }), _jsxs(ConsoleForm, { intent: "restore", className: "ac-form", children: [_jsx(Field, { label: "Moment", name: "moment", type: "datetime-local", required: true, hint: "A new cluster beside the current one; nothing in the current database changes. Owners only." }), _jsx(Field, { label: "Line", name: "line", placeholder: "ankka-db", hint: "Only when the project's services are on more than one cluster." }), _jsx(Refused, { intent: "restore" }), _jsx("div", { children: _jsx(Submit, { intent: "restore", children: "Restore" }) })] })] })) : null] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "rehearsals-title", id: "rehearsals", children: [_jsx("h2", { id: "rehearsals-title", children: "Rehearsals" }), rehearsals.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No rehearsals yet. A rehearsal restores into a namespace of its own, checks it, times it and removes it." })) : (_jsx("ul", { className: "ac-topics", children: rehearsals.map((r) => (_jsxs("li", { "data-rehearsal": r.name, "data-outcome": r.outcome, children: [_jsx("code", { children: r.name }), " to ", when(r.moment), ": ", r.outcome, r.elapsedSeconds !== undefined ? `, in ${r.elapsedSeconds}s` : "", r.requestedBy ? `, asked by ${r.requestedBy}` : ", on the schedule", r.detail ? ` — ${r.detail}` : ""] }, r.name))) })), shows("project.rehearse") ? (_jsx(ConsoleForm, { intent: "rehearse", className: "ac-inline", children: _jsx(Submit, { intent: "rehearse", children: "Rehearse a restore" }) })) : null, _jsx(Refused, { intent: "rehearse" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "database-history", children: [_jsx("h2", { id: "database-history", children: "Database history" }), databaseHistory.length === 0 ? (_jsx("p", { className: "ac-empty", children: "Nothing has been done to the project's database yet." })) : (_jsx("ul", { className: "ac-topics", children: databaseHistory.map((h, i) => (_jsxs("li", { "data-history": h.kind, children: [h.kind, h.by ? ` by ${h.by}` : "", h.at ? `, ${when(h.at)}` : "", h.detail ? `: ${h.detail}` : ""] }, i))) }))] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "erasures", id: "erasure-requests", children: [_jsx("h2", { id: "erasures", children: "Erasures" }), erasures.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No erasure requests. An erasure request makes a data subject's personal data unreadable in every service of the project." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "erasures", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Data subject" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", children: "Not-before date" }), _jsx("th", { scope: "col", children: "Asked" }), _jsx("th", { scope: "col", children: "Completion" })] }) }), _jsx("tbody", { children: erasures.map((e) => (_jsxs("tr", { "data-erasure": e.id, "data-subject": e.subject, "data-state": e.state, children: [_jsx("td", { children: _jsx(ConsoleLink, { to: `${path}/erasures/${encodeURIComponent(e.id)}`, children: _jsx("code", { children: e.subject }) }) }), _jsx("td", { children: e.state }), _jsx("td", { "data-not-before": true, children: e.notBefore ? when(e.notBefore) : "—" }), _jsxs("td", { children: [e.askedAt ? when(e.askedAt) : "—", e.askedBy ? ` by ${e.askedBy}` : ""] }), _jsx("td", { children: e.completions.length === 0 ? ("—") : (_jsx("ul", { className: "ac-topics", children: e.completions.map((c) => (_jsxs("li", { "data-completion": c.service, children: [c.service, ": ", c.completedAt ? `completed ${when(c.completedAt)}` : "not completed"] }, c.service))) })) })] }, e.id))) })] }) })), erasuresMore ? _jsxs("p", { className: "ac-hint", children: ["The newest ", ErasuresShown, " are shown; ", _jsx("code", { children: "ankka projects erasures list" }), " lists them all."] }) : null] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "grants", children: [_jsx("h2", { id: "grants", children: "Grants" }), grants.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No grants. A grant lets a service of another project, or a machine, reach one route, method or topic of this project." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "grants", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Grantee" }), _jsx("th", { scope: "col", children: "Target" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", children: "Effect" }), _jsx("th", { scope: "col", children: "Granted" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "ac-visually-hidden", children: "Actions" }) })] }) }), _jsx("tbody", { children: grants.map((g) => (_jsxs("tr", { "data-grant": g.id, "data-state": g.state, children: [_jsx("td", { children: _jsx("code", { children: g.grantee }) }), _jsx("td", { children: targetText(g.target) }), _jsx("td", { children: g.state }), _jsx("td", { "data-effect": true, children: g.effect }), _jsxs("td", { children: [g.granted.at ? when(g.granted.at) : "—", g.granted.by ? ` by ${g.granted.by}` : "", g.ended ? (_jsxs("span", { className: "ac-hint", children: [" ", "\u00B7 ended ", g.ended.at ? when(g.ended.at) : "", g.ended.by ? ` by ${g.ended.by}` : ""] })) : null] }), _jsx("td", { children: shows("grant.end") && (g.state === "pending" || g.state === "accepted") ? (_jsxs(ConsoleForm, { intent: "grant-end", className: "ac-inline", children: [_jsx("input", { type: "hidden", name: "grantId", value: g.id }), _jsx(Submit, { intent: "grant-end", label: `${endVerb(g.state)} the grant to ${g.grantee} of ${targetText(g.target)}`, children: endVerb(g.state) })] })) : null })] }, g.id))) })] }) })), _jsx(Refused, { intent: "grant-end" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "received", children: [_jsx("h2", { id: "received", children: "Received" }), received.length === 0 ? (_jsx("p", { className: "ac-empty", children: "No other project grants this project's services anything." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-describedby": "received", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "Grantee" }), _jsx("th", { scope: "col", children: "Target" }), _jsx("th", { scope: "col", children: "State" }), _jsx("th", { scope: "col", children: "Changes" })] }) }), _jsx("tbody", { children: received.map((r) => (_jsxs("tr", { "data-received": r.id, "data-state": r.state, children: [_jsxs("td", { children: [r.grantingProject, " ", _jsxs("span", { className: "ac-hint", children: ["of ", r.grantingOrganization] })] }), _jsx("td", { children: _jsx("code", { children: r.grantee }) }), _jsxs("td", { children: [targetText(r.target), r.topic ? (_jsxs("span", { className: "ac-hint", children: [" ", "\u00B7 ", r.topic.partitions, " partitions", r.topic.compacted ? ", compacted" : "", r.topic.retention ? `, kept ${r.topic.retention}` : ""] })) : null] }), _jsx("td", { children: r.state }), _jsx("td", { children: _jsx("ul", { className: "ac-topics", children: r.changes.map((c, i) => (_jsxs("li", { children: [c.change, c.at ? ` ${when(c.at)}` : "", c.by ? ` by ${c.by}` : ""] }, `${c.change}-${i}`))) }) })] }, r.id))) })] }) }))] }), _jsx(Panels, { kind: "project", entity: p, loaded: panels })] }));
|
|
233
258
|
}
|
|
234
259
|
export const ErrorBoundary = ConsoleErrorBoundary;
|
package/dist/routes.js
CHANGED
|
@@ -37,6 +37,8 @@ export function consoleRoutes(options = {}) {
|
|
|
37
37
|
page("organizations/:organizationId/machines", "machines"),
|
|
38
38
|
page("organizations/:organizationId/projects/new", "project-new"),
|
|
39
39
|
page("projects/:projectId", "project"),
|
|
40
|
+
page("projects/:projectId/erasures/:requestId", "erasure"),
|
|
41
|
+
resource("projects/:projectId/erasures/:requestId/certificate", "erasure.certificate"),
|
|
40
42
|
page("projects/:projectId/services/apply", "service-apply"),
|
|
41
43
|
page("projects/:projectId/services/:name", "service"),
|
|
42
44
|
page("projects/:projectId/services/:name/logs", "logs"),
|
package/dist/styles.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! tailwindcss v4.3.
|
|
1
|
+
/*! tailwindcss v4.3.4 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer theme{:root,:host{--ac-spacing:.25rem;--ac-color-mesh-base:#111f26;--ac-color-mesh-slate:#1e2e3a;--ac-color-glow:#d4805857;--ac-color-peach:#deaa8429;--ac-color-dot:#ffffff14;--ac-color-glass:#ffffff0d;--ac-color-glass-strong:#ffffff17;--ac-color-glass-line:#fff3;--ac-color-tile:#ffffff0f;--ac-color-tile-line:#ffffff24;--ac-color-node:#ffffff1a;--ac-color-node-line:#ffffff61;--ac-color-inset:#0003;--ac-color-edge:#ffffffb3;--ac-color-ink:#f5f8f7;--ac-color-ink-2:#dfe8e6;--ac-color-ink-3:#ccd9d6;--ac-color-amber:#f0b83c;--ac-color-amber-ink:#13343b;--ac-color-green:#8fe0ad;--ac-color-red:#ffc4bc;--ac-color-red-wash:#ff8a8029;--ac-shadow-part:0 18px 50px #050f1247;--ac-radius-panel:20px;--ac-radius-card:14px;--ac-radius-control:10px;--ac-radius-node:18px;--ac-font-sans:"Inter Variable", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--ac-font-mono:ui-monospace, "SF Mono", Menlo, Consolas, monospace;--ac-size-rail:56px;--ac-size-panel:232px;--ac-size-inspector:300px;--ac-size-bar:64px}:root:has(.ac-light){--ac-color-mesh-base:#e6eeee;--ac-color-mesh-slate:#cfe0dc;--ac-color-glow:#e0a5264d;--ac-color-peach:#f1d5c2cc;--ac-color-dot:#13343b1a;--ac-color-glass:#ffffff80;--ac-color-glass-strong:#ffffffd9;--ac-color-glass-line:#13343b24;--ac-color-tile:#ffffff80;--ac-color-tile-line:#13343b1a;--ac-color-node:#ffffff9e;--ac-color-node-line:#13343b38;--ac-color-inset:#13343b0f;--ac-color-edge:#13343b8c;--ac-color-ink:#13343b;--ac-color-ink-2:#33504f;--ac-color-ink-3:#4a6465;--ac-color-amber:#e0a526;--ac-color-amber-ink:#13343b;--ac-color-green:#1f6b3a;--ac-color-red:#a8231b;--ac-color-red-wash:#fbe9e7;--ac-shadow-part:0 18px 50px #13343b1a}}@layer base{.ac-root{color-scheme:dark;isolation:isolate;min-height:100vh;color:var(--ac-color-ink);font-family:var(--ac-font-sans);font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;font-size:.875rem;line-height:1.5;position:relative}.ac-root.ac-light{color-scheme:light}.ac-root *,.ac-root :before,.ac-root :after{box-sizing:border-box}.ac-root h1,.ac-root h2,.ac-root h3,.ac-root p,.ac-root dl,.ac-root dd,.ac-root figure{margin:0}.ac-root h1{letter-spacing:-.01em;text-wrap:balance;font-size:1.5rem;font-weight:600;line-height:1.2}.ac-root h2{font-size:.9375rem;font-weight:600}.ac-root h3{font-size:.875rem;font-weight:600}.ac-root p{max-width:72ch}.ac-root a{color:inherit;-webkit-text-decoration-color:var(--ac-color-ink-3);-webkit-text-decoration-color:var(--ac-color-ink-3);text-decoration-color:var(--ac-color-ink-3);text-underline-offset:.2em}.ac-root a:hover{-webkit-text-decoration-color:var(--ac-color-amber);-webkit-text-decoration-color:var(--ac-color-amber);text-decoration-color:var(--ac-color-amber);text-decoration-thickness:2px}.ac-root :focus-visible{outline:3px solid var(--ac-color-amber);outline-offset:2px;border-radius:4px}.ac-root code,.ac-root pre{font-family:var(--ac-font-mono)}.ac-root input,.ac-root select,.ac-root textarea{font:inherit;color:var(--ac-color-ink);background:var(--ac-color-inset);border:1px solid var(--ac-color-glass-line);border-radius:var(--ac-radius-control);width:100%;padding:.5rem .75rem}.ac-root input::placeholder,.ac-root textarea::placeholder{color:var(--ac-color-ink-3)}.ac-root textarea{font-family:var(--ac-font-mono);min-height:16rem;font-size:.8125rem}.ac-root input[type=checkbox],.ac-root input[type=radio]{width:auto;accent-color:var(--ac-color-amber)}.ac-root input[type=file]{padding:.4rem}.ac-root [aria-invalid=true]{border-color:var(--ac-color-red)}}@layer components{.ac-backdrop{z-index:-1;pointer-events:none;background:radial-gradient(var(--ac-color-dot) 1px, transparent 1.3px) 0 0 / 16px 16px, radial-gradient(42% 46% at 56% 52%, var(--ac-color-glow) 0%, transparent 72%), radial-gradient(40% 50% at 90% 88%, var(--ac-color-peach) 0%, transparent 65%), radial-gradient(55% 60% at 8% 10%, var(--ac-color-mesh-slate) 0%, transparent 70%), linear-gradient(160deg, var(--ac-color-mesh-base), var(--ac-color-mesh-slate));position:fixed;inset:0}.ac-rail,.ac-bar,.ac-listing,.ac-inspector{background:var(--ac-color-glass);-webkit-backdrop-filter:blur(20px)saturate(150%);backdrop-filter:blur(20px)saturate(150%);border:1px solid var(--ac-color-glass-line);border-radius:var(--ac-radius-panel)}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.ac-rail,.ac-bar,.ac-listing,.ac-inspector,.ac-card,.ac-panel,.ac-part{background:linear-gradient(var(--ac-color-glass), var(--ac-color-glass)), var(--ac-color-mesh-base)}}.ac-shell{--ac-shell-gap:12px;--ac-shell-rail:0px;--ac-shell-listing:0px;--ac-shell-inspector:0px;grid-template-columns:var(--ac-shell-rail) var(--ac-shell-listing) minmax(0, 1fr) var(--ac-shell-inspector);column-gap:0;row-gap:var(--ac-shell-gap);padding:var(--ac-shell-gap);grid-template-rows:auto 1fr;align-items:start;display:grid}.ac-shell:has(>.ac-rail){--ac-shell-rail:calc(var(--ac-size-rail) + var(--ac-shell-gap))}.ac-shell:has(>.ac-listing){--ac-shell-listing:calc(var(--ac-size-panel) + var(--ac-shell-gap))}.ac-shell:has(.ac-inspector){--ac-shell-inspector:calc(var(--ac-size-inspector) + var(--ac-shell-gap))}.ac-rail{top:var(--ac-shell-gap);max-height:calc(100vh - 2 * var(--ac-shell-gap));grid-area:1/1/3;margin-inline-end:var(--ac-shell-gap);position:sticky}.ac-bar{grid-area:1/2/auto/-1}.ac-listing{top:calc(var(--ac-size-bar) + 2 * var(--ac-shell-gap));max-height:calc(100vh - var(--ac-size-bar) - 3 * var(--ac-shell-gap));grid-area:2/2;margin-inline-end:var(--ac-shell-gap);position:sticky}.ac-page{display:contents}.ac-body{flex-direction:column;grid-area:2/3;gap:16px;min-width:0;padding-block-end:24px;display:flex}.ac-inspector{top:calc(var(--ac-size-bar) + 2 * var(--ac-shell-gap));max-height:calc(100vh - var(--ac-size-bar) - 3 * var(--ac-shell-gap));flex-direction:column;grid-area:2/4;gap:14px;margin-inline-start:var(--ac-shell-gap);padding:14px;display:flex;position:sticky;overflow:auto}.ac-rail{flex-direction:column;align-items:center;gap:6px;padding:10px 0;display:flex}.ac-rail-areas{flex-direction:column;align-items:center;gap:6px;margin:0;padding:0;list-style:none;display:flex}.ac-rail-foot{margin-top:auto}.ac-mark{width:40px;height:40px;color:var(--ac-color-ink);place-items:center;margin-bottom:8px;display:grid}.ac-mark-image{background-color:currentColor;width:32px;height:32px;-webkit-mask:url(./ankka-mark.png) 50%/contain no-repeat;mask:url(./ankka-mark.png) 50%/contain no-repeat}.ac-rail-item{width:40px;height:40px;color:var(--ac-color-ink-2);cursor:pointer;background:0 0;border:0;border-radius:12px;place-items:center;padding:0;display:grid}.ac-rail-item svg{width:20px;height:20px}a.ac-rail-item:hover,button.ac-rail-item:hover{background:var(--ac-color-glass-strong);color:var(--ac-color-ink)}.ac-rail-item[aria-current]{background:var(--ac-color-amber);color:var(--ac-color-amber-ink)}.ac-rail-item[aria-disabled=true]{color:var(--ac-color-ink-3);opacity:.5;cursor:default}.ac-bar{min-height:var(--ac-size-bar);grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:16px;padding:8px 16px;display:grid}.ac-bar-start,.ac-bar-end{align-items:center;gap:16px;min-width:0;display:flex}.ac-bar-end{justify-content:flex-end}.ac-wordmark{letter-spacing:-.01em;white-space:nowrap;font-size:1rem;font-weight:600}.ac-root a.ac-wordmark{text-decoration:none}.ac-wordmark span{color:var(--ac-color-ink-2);font-weight:400}.ac-person{background:var(--ac-color-glass-strong);border:1px solid var(--ac-color-glass-line);border-radius:50%;flex:none;place-items:center;width:32px;height:32px;font-size:.75rem;font-weight:600;display:grid}.ac-crumbs ol{color:var(--ac-color-ink-2);white-space:nowrap;align-items:center;gap:8px;margin:0;padding:0;list-style:none;display:flex}.ac-crumbs li+li:before{content:"/";color:var(--ac-color-ink-3);margin-right:8px}.ac-root .ac-crumbs a{text-decoration:none}.ac-root .ac-crumbs a:hover{color:var(--ac-color-ink);text-decoration:underline;-webkit-text-decoration-color:var(--ac-color-amber);-webkit-text-decoration-color:var(--ac-color-amber);text-decoration-color:var(--ac-color-amber)}.ac-crumbs [aria-current=page]{color:var(--ac-color-ink);font-weight:500}.ac-listing{flex-direction:column;gap:12px;min-height:0;padding:14px;display:flex}.ac-listing-items{gap:4px;min-height:0;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.ac-listing-items>li:has(>[aria-current=page]){z-index:1;position:sticky;top:0;bottom:0}.ac-listing-item{border-radius:var(--ac-radius-control);background:var(--ac-color-tile);border:1px solid #0000;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:2px 8px;padding:8px 10px;display:grid}.ac-root a.ac-listing-item{text-decoration:none}.ac-listing-item:hover{background:var(--ac-color-glass-strong)}.ac-listing-item[aria-current=page]{background:linear-gradient(var(--ac-color-glass-strong), var(--ac-color-glass-strong)), var(--ac-color-mesh-base);border-color:var(--ac-color-glass-line)}.ac-listing-name{text-overflow:ellipsis;white-space:nowrap;font-weight:500;overflow:hidden}.ac-listing-count{color:var(--ac-color-ink-2);grid-area:1/2/3;font-size:.75rem}.ac-section-title{letter-spacing:.08em;text-transform:uppercase;color:var(--ac-color-ink-3);justify-content:space-between;align-items:center;margin:0;font-size:.6875rem;font-weight:600;display:flex}.ac-seg{background:var(--ac-color-inset);border-radius:999px;grid-auto-columns:1fr;grid-auto-flow:column;padding:3px;display:inline-grid}.ac-seg a{text-align:center;color:var(--ac-color-ink-2);white-space:nowrap;border-radius:999px;padding:6px 14px;font-weight:500}.ac-root .ac-seg a{text-decoration:none}.ac-seg a[aria-current=page]{background:var(--ac-color-glass-strong);color:var(--ac-color-ink);box-shadow:0 1px 2px #0003}.ac-head{flex-wrap:wrap;align-items:center;gap:12px 16px;padding:4px 6px 0;display:flex}.ac-head .ac-seg{margin-left:auto}.ac-lede,.ac-hint,.ac-empty{color:var(--ac-color-ink-2)}.ac-hint{font-size:.8125rem}.ac-card,.ac-panel{background:var(--ac-color-tile);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--ac-color-tile-line);border-radius:var(--ac-radius-card);flex-direction:column;gap:12px;min-width:0;padding:16px;display:flex}.ac-panel-failed{color:var(--ac-color-red)}.ac-table-wrap{overflow-x:auto}.ac-table{border-collapse:collapse;width:100%}.ac-table th,.ac-table td{text-align:left;border-top:1px solid var(--ac-color-tile-line);vertical-align:top;padding:9px 12px 9px 0}.ac-table thead th{color:var(--ac-color-ink-2);border-top:0;padding-top:0;font-size:.8125rem;font-weight:500}.ac-table .ac-num{text-align:right}.ac-facts{grid-template-columns:minmax(8rem,max-content) minmax(0,1fr);column-gap:24px;display:grid}.ac-facts dt,.ac-facts dd{border-top:1px solid var(--ac-color-tile-line);padding:9px 0}.ac-facts dt{color:var(--ac-color-ink-2)}.ac-facts dd{overflow-wrap:anywhere}.ac-facts>div{display:contents}.ac-status{white-space:nowrap;align-items:baseline;gap:6px;font-weight:500;display:inline-flex}.ac-status-mark{letter-spacing:-.15em;font-size:.75em}.ac-status-ready .ac-status-mark{color:var(--ac-color-green)}.ac-status-moving .ac-status-mark,.ac-status-partial .ac-status-mark{color:var(--ac-color-amber)}.ac-status-trouble{color:var(--ac-color-red)}.ac-status-stopped .ac-status-mark,.ac-status-idle .ac-status-mark{color:var(--ac-color-ink-3)}.ac-status-note{color:var(--ac-color-ink-2);font-weight:400}.ac-pill{background:var(--ac-color-glass-strong);border:1px solid var(--ac-color-glass-line);border-radius:999px;padding:4px 10px 4px 8px}.ac-live{color:var(--ac-color-ink-2);align-items:center;gap:8px;font-size:.8125rem;display:inline-flex}.ac-live:before{content:"";background:var(--ac-color-amber);border-radius:50%;flex:none;width:8px;height:8px}@media (prefers-reduced-motion:no-preference){.ac-live:before{animation:2.4s ease-out infinite ac-ripple}}.ac-field{gap:4px;min-width:0;max-width:32rem;display:grid}.ac-field label{font-weight:500}.ac-field-error{color:var(--ac-color-red)}.ac-form{gap:16px;display:grid}.ac-inline{flex-wrap:wrap;align-items:end;gap:8px;display:flex}.ac-inline .ac-field{flex:12rem}.ac-inspector .ac-inline{flex-direction:column;align-items:stretch}.ac-inspector .ac-inline .ac-field{flex:none}.ac-inspector .ac-field{max-width:none}.ac-switch{cursor:pointer;align-items:center;gap:10px;display:inline-flex}.ac-root .ac-switch input{opacity:0;width:1px;height:1px;position:absolute}.ac-switch-track{background:var(--ac-color-inset);border:1px solid var(--ac-color-glass-line);border-radius:999px;flex:none;width:40px;height:24px;position:relative}.ac-switch-track:after{content:"";background:var(--ac-color-ink);border-radius:50%;width:18px;height:18px;position:absolute;top:2px;left:2px}.ac-switch input:checked+.ac-switch-track{background:var(--ac-color-amber);border-color:var(--ac-color-amber)}.ac-switch input:checked+.ac-switch-track:after{background:var(--ac-color-amber-ink);transform:translate(16px)}.ac-switch input:focus-visible+.ac-switch-track{outline:3px solid var(--ac-color-amber);outline-offset:2px}@media (prefers-reduced-motion:no-preference){.ac-switch-track,.ac-switch-track:after{transition:background .15s,transform .15s}}.ac-button{font:inherit;border:1px solid var(--ac-color-glass-line);background:var(--ac-color-glass-strong);color:var(--ac-color-ink);cursor:pointer;white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;gap:8px;padding:7px 14px;font-weight:500;display:inline-flex}.ac-root a.ac-button{color:var(--ac-color-ink);text-decoration:none}.ac-button svg{width:16px;height:16px}.ac-button:hover{background:var(--ac-color-glass-line)}.ac-button-primary,.ac-root a.ac-button-primary{background:var(--ac-color-amber);border-color:var(--ac-color-amber);color:var(--ac-color-amber-ink)}.ac-button-primary:hover{background:var(--ac-color-amber)}@supports (color:color-mix(in lab, red, red)){.ac-button-primary:hover{background:color-mix(in srgb, var(--ac-color-amber) 88%, white)}}.ac-button-danger,.ac-root a.ac-button-danger{background:var(--ac-color-red);border-color:var(--ac-color-red);color:var(--ac-color-mesh-base)}.ac-button-danger:hover{background:var(--ac-color-red)}@supports (color:color-mix(in lab, red, red)){.ac-button-danger:hover{background:color-mix(in srgb, var(--ac-color-red) 88%, white)}}.ac-button-quiet{background:0 0}.ac-button-small{padding:4px 10px;font-size:.8125rem}.ac-button[disabled]{opacity:.55;cursor:progress}.ac-actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.ac-actions form{display:inline}.ac-ops{gap:8px;display:grid}.ac-ops>form,.ac-ops>.ac-button,.ac-ops>form>.ac-button{width:100%}.ac-more{border-top:1px solid var(--ac-color-glass-line);padding-top:12px}.ac-more summary{cursor:pointer;font-weight:500}.ac-more[open]>*+*{margin-top:12px}.ac-danger-zone{border-left:3px solid var(--ac-color-red);gap:10px;padding:8px 0 8px 12px;display:grid}.ac-danger-zone p{color:var(--ac-color-ink-2);font-size:.8125rem}.ac-refusal{border-left:3px solid var(--ac-color-red);background:var(--ac-color-red-wash);border-radius:0 var(--ac-radius-control) var(--ac-radius-control) 0;padding:10px 14px}.ac-refusal ul{margin:6px 0 0;padding-left:1.2rem}.ac-notice{border-left:3px solid var(--ac-color-amber);background:var(--ac-color-tile);border-radius:0 var(--ac-radius-control) var(--ac-radius-control) 0;padding:10px 14px}.ac-secret{font-family:var(--ac-font-mono);background:var(--ac-color-inset);border:1px dashed var(--ac-color-ink-3);border-radius:var(--ac-radius-control);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all;padding:10px;display:block}.ac-log{font-family:var(--ac-font-mono);background:var(--ac-color-inset);border:1px solid var(--ac-color-tile-line);border-radius:var(--ac-radius-control);white-space:pre-wrap;overflow-wrap:anywhere;max-height:32rem;margin:0;padding:12px;font-size:.8125rem;overflow:auto}.ac-mounts{gap:4px;margin:0;padding:0;list-style:none;display:grid}.ac-visually-hidden{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.ac-shape{border-radius:var(--ac-radius-panel);border:1px solid var(--ac-color-glass-line);padding:28px 20px 20px}.ac-tree{justify-items:center;max-width:720px;margin:0 auto;display:grid}.ac-row{justify-items:center;width:100%;display:grid}.ac-row-many{gap:24px}.ac-part{background:var(--ac-color-node);-webkit-backdrop-filter:blur(18px)saturate(140%);backdrop-filter:blur(18px)saturate(140%);border:1px solid var(--ac-color-node-line);border-radius:var(--ac-radius-node);width:100%;max-width:300px;box-shadow:var(--ac-shadow-part);grid-template-columns:auto minmax(0,1fr);align-items:center;gap:2px 12px;padding:16px 16px 14px;display:grid;position:relative}.ac-part-self{box-shadow:var(--ac-shadow-part), 0 0 0 2px var(--ac-color-amber), 0 0 24px var(--ac-color-amber)}@supports (color:color-mix(in lab, red, red)){.ac-part-self{box-shadow:var(--ac-shadow-part), 0 0 0 2px var(--ac-color-amber), 0 0 24px color-mix(in srgb, var(--ac-color-amber) 45%, transparent)}}.ac-part-icon{width:40px;height:40px;color:var(--ac-color-ink);grid-row:1/3}.ac-part-icon svg{width:40px;height:40px}.ac-part-title,.ac-part-sub{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.ac-part-title{font-weight:500}.ac-part-sub{color:var(--ac-color-ink-2);font-size:.8125rem}.ac-part-inset{background:var(--ac-color-inset);border-radius:10px;grid-column:1/3;justify-content:space-between;align-items:center;gap:8px;margin-top:10px;padding:7px 12px;font-size:.8125rem;display:flex}.ac-part-up:before,.ac-part-down:after{content:"";background:var(--ac-color-node);border:1px solid var(--ac-color-node-line);width:12px;height:7px;position:absolute;left:50%;transform:translate(-50%)}.ac-part-down:after{border-top:0;border-radius:0 0 7px 7px;bottom:-7px}.ac-part-up:before{border-bottom:0;border-radius:7px 7px 0 0;top:-7px}.ac-part-tab{background:var(--ac-color-node);border:1px solid var(--ac-color-node-line);border-bottom:0;border-radius:12px 12px 0 0;gap:2px;padding:2px 8px 6px;display:flex;position:absolute;top:-27px;left:50%;transform:translate(-50%)}.ac-part-tab form{display:contents}.ac-tab-control{width:24px;height:20px;color:var(--ac-color-ink);cursor:pointer;background:0 0;border:0;border-radius:6px;place-items:center;padding:0;display:grid}.ac-tab-control:hover{background:var(--ac-color-glass-strong)}.ac-tab-control svg{width:14px;height:14px}.ac-part-tabbed{margin-top:27px}.ac-part-tabbed.ac-part-up:before{display:none}.ac-edge{width:100%;height:48px;color:var(--ac-color-edge);display:block;overflow:visible}.ac-edge path{fill:none;stroke:currentColor;stroke-width:1.5px;vector-effect:non-scaling-stroke}.ac-topology{align-items:flex-start;gap:16px;display:flex}.ac-topology-picture{border-radius:var(--ac-radius-panel);border:1px solid var(--ac-color-glass-line);flex:1;min-width:0;padding:8px;overflow-x:auto}.ac-topology-detail{flex:0 0 18rem}.ac-topology-node rect{fill:var(--ac-color-node);stroke:var(--ac-color-node-line);stroke-width:1px}.ac-topology-node{cursor:pointer}.ac-topology-node:focus-visible rect,.ac-topology-selected rect{stroke:var(--ac-color-amber);stroke-width:2px}.ac-topology-node:focus{outline:none}.ac-topology-outside rect,.ac-topology-differs rect{stroke-dasharray:4 3}.ac-topology-platform rect{fill:var(--ac-color-inset)}.ac-topology-name{fill:var(--ac-color-ink);font-size:12px;font-weight:600}.ac-topology-kind{fill:var(--ac-color-ink-2);font-size:11px}.ac-topology-declared{fill:none;stroke:var(--ac-color-edge);stroke-width:1.25px}.ac-topology-call{fill:none;stroke:var(--ac-color-ink);stroke-dasharray:5 4}.ac-topology-warning{stroke:var(--ac-color-red)}.ac-topology-mark{fill:var(--ac-color-red);font-size:14px;font-weight:700}@media (forced-colors:active){.ac-rail,.ac-bar,.ac-listing,.ac-inspector,.ac-card,.ac-panel,.ac-part,.ac-listing-item{border:1px solid canvastext}.ac-button,.ac-rail-item,.ac-seg a{border:1px solid buttontext}.ac-rail-item[aria-current],.ac-seg a[aria-current=page],.ac-listing-item[aria-current=page]{outline:2px solid highlight}}@media (max-width:1180px){.ac-shell,.ac-shell:has(>.ac-listing),.ac-shell:has(.ac-inspector){grid-template-columns:var(--ac-shell-rail) minmax(0, 1fr);grid-template-rows:none;grid-auto-rows:auto}.ac-rail{grid-row:1/span 5}.ac-bar,.ac-listing,.ac-body,.ac-inspector{grid-area:auto/2;max-height:none;margin-inline:0;position:static}.ac-listing-items{grid-template-columns:repeat(auto-fill,minmax(200px,1fr));max-height:18rem}}@media (max-width:720px){.ac-shell,.ac-shell:has(>.ac-rail),.ac-shell:has(>.ac-listing),.ac-shell:has(.ac-inspector){grid-template-columns:minmax(0,1fr)}.ac-rail{flex-flow:wrap;grid-area:auto/1;justify-content:flex-start;max-height:none;margin:0;padding:8px 10px;position:static}.ac-rail-areas{flex-flow:wrap}.ac-mark{margin:0 6px 0 0}.ac-rail-foot{margin-top:0;margin-left:auto}.ac-bar,.ac-listing,.ac-body,.ac-inspector{grid-column:1}.ac-bar{grid-template-columns:minmax(0,1fr) auto}.ac-bar-start{overflow:hidden}.ac-wordmark span{display:none}.ac-crumbs{grid-area:2/1/auto/-1}.ac-crumbs ol{white-space:normal;flex-wrap:wrap;row-gap:2px}.ac-head .ac-seg{width:100%;margin-left:0}.ac-row-many{gap:20px;grid-template-columns:minmax(0,1fr)!important}.ac-edge-split{display:none}.ac-facts{grid-template-columns:minmax(0,1fr)}.ac-facts dd{border-top:0;padding-top:0}.ac-topology{flex-direction:column}.ac-topology-detail{flex-basis:auto;width:100%}}}@layer utilities;@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./fonts/inter-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./fonts/inter-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@keyframes ac-ripple{0%{box-shadow:0 0 0 0 color-mix(in srgb, var(--ac-color-amber) 60%, transparent)}to{box-shadow:0 0 0 .6rem #0000}}
|
|
@@ -119,8 +119,43 @@ interface Project {
|
|
|
119
119
|
};
|
|
120
120
|
/** The changes to its topics, newest first. */
|
|
121
121
|
history?: ProjectHistoryRecord[];
|
|
122
|
+
/** Feature 042: its erasure requests, oldest first. */
|
|
123
|
+
erasures?: ErasureRecord[];
|
|
122
124
|
hidden: boolean;
|
|
123
125
|
}
|
|
126
|
+
/** An erasure request as the control plane's entity holds it (feature 042). */
|
|
127
|
+
interface ErasureRecord {
|
|
128
|
+
id: string;
|
|
129
|
+
project: string;
|
|
130
|
+
subject: string;
|
|
131
|
+
state: "held" | "applying" | "applied" | "withdrawn";
|
|
132
|
+
askedBy?: string;
|
|
133
|
+
askedAt: string;
|
|
134
|
+
notBefore?: string;
|
|
135
|
+
reason?: string;
|
|
136
|
+
correlation?: string;
|
|
137
|
+
lapsedAt?: string;
|
|
138
|
+
holdOverride?: {
|
|
139
|
+
owner: string;
|
|
140
|
+
reason: string;
|
|
141
|
+
at: string;
|
|
142
|
+
};
|
|
143
|
+
erasureSeq?: number;
|
|
144
|
+
destroyedAt?: string;
|
|
145
|
+
appliedAt?: string;
|
|
146
|
+
completions: {
|
|
147
|
+
service: string;
|
|
148
|
+
completedAt?: string;
|
|
149
|
+
rowsRedacted: number;
|
|
150
|
+
tokensDropped: number;
|
|
151
|
+
sessionsEnded: number;
|
|
152
|
+
}[];
|
|
153
|
+
finalAt?: string;
|
|
154
|
+
lastFailure?: {
|
|
155
|
+
reason: string;
|
|
156
|
+
at?: string;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
124
159
|
interface ProjectHistoryRecord {
|
|
125
160
|
kind: string;
|
|
126
161
|
topic: string;
|
|
@@ -503,6 +538,21 @@ export interface FakeSeed {
|
|
|
503
538
|
name: string;
|
|
504
539
|
byteRates?: Machine["byteRates"];
|
|
505
540
|
}[];
|
|
541
|
+
/** Feature 042: whether the installation's erasure is on (`ANKKA_ERASURE`); off until a suite says so. */
|
|
542
|
+
erasure?: boolean;
|
|
543
|
+
/**
|
|
544
|
+
* Erasure requests, as if asked by `askedBy` (`seed` otherwise): `held` until a not-before date a
|
|
545
|
+
* month away unless one is given, or `applied` with a completion from every service of the project.
|
|
546
|
+
*/
|
|
547
|
+
erasures?: {
|
|
548
|
+
projectId: string;
|
|
549
|
+
subject: string;
|
|
550
|
+
state: "held" | "applied" | "withdrawn";
|
|
551
|
+
notBefore?: string;
|
|
552
|
+
reason?: string;
|
|
553
|
+
correlation?: string;
|
|
554
|
+
askedBy?: string;
|
|
555
|
+
}[];
|
|
506
556
|
}
|
|
507
557
|
export declare function fakeControlPlane(options?: FakeControlPlaneOptions): Promise<FakeControlPlane>;
|
|
508
558
|
export {};
|