ankka-console 0.11.0 → 0.13.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/ankka-mark.png +0 -0
- package/dist/client/control-plane.d.ts +63 -1
- package/dist/client/control-plane.js +142 -1
- package/dist/client/schemas.d.ts +1460 -2
- package/dist/client/schemas.js +462 -2
- package/dist/context.d.ts +1 -1
- package/dist/context.js +1 -1
- package/dist/extensions/types.d.ts +1 -1
- package/dist/extensions/types.js +22 -0
- package/dist/routes/front.d.ts +76 -2
- package/dist/routes/front.js +37 -8
- package/dist/routes/logs.d.ts +67 -0
- package/dist/routes/machines.d.ts +78 -0
- package/dist/routes/machines.js +78 -0
- package/dist/routes/organization.d.ts +29 -1
- package/dist/routes/organization.js +27 -5
- package/dist/routes/project.d.ts +285 -1
- package/dist/routes/project.js +107 -6
- package/dist/routes/service-history.d.ts +2 -0
- package/dist/routes/service-history.js +3 -0
- package/dist/routes/service-topology.d.ts +67 -0
- package/dist/routes/service.d.ts +67 -0
- package/dist/routes/service.js +64 -4
- package/dist/routes.js +1 -0
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +1 -0
- package/dist/styles.css +1 -1
- package/dist/testing/fake-control-plane.d.ts +193 -1
- package/dist/testing/fake-control-plane.js +840 -7
- package/dist/ui/buckets.d.ts +14 -0
- package/dist/ui/buckets.js +9 -0
- package/dist/ui/grants.d.ts +3 -0
- package/dist/ui/grants.js +13 -0
- package/dist/ui/shell.d.ts +1 -1
- package/dist/ui/shell.js +14 -3
- package/dist/ui/topology/Table.d.ts +8 -0
- package/dist/ui/topology/Table.js +8 -1
- package/dist/ui/use-stream.d.ts +134 -0
- package/package.json +1 -1
package/dist/routes/service.js
CHANGED
|
@@ -7,10 +7,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
7
7
|
import { redirect, useLoaderData } from "react-router";
|
|
8
8
|
import { act, applyPrimary, guard, pageData, projectShell, text, useConsoleContext } from "../context.js";
|
|
9
9
|
import { ConsoleErrorBoundary } from "../ui/errors.js";
|
|
10
|
-
import { ConsoleForm, Submit, useConsole } from "../ui/console.js";
|
|
10
|
+
import { ConsoleForm, Field, Submit, useConsole } from "../ui/console.js";
|
|
11
|
+
import { Select } from "../ui/primitives/select.js";
|
|
11
12
|
import { Page, SectionTitle, ServiceSections } from "../ui/shell.js";
|
|
12
13
|
import { Shape } from "../ui/shape.js";
|
|
13
14
|
import { runsAs } from "../ui/hosting.js";
|
|
15
|
+
import { bucketRows } from "../ui/buckets.js";
|
|
14
16
|
import { Refused, useRefusal } from "../ui/refused.js";
|
|
15
17
|
import { Lifecycle } from "../ui/status.js";
|
|
16
18
|
import { useServiceStream } from "../ui/use-stream.js";
|
|
@@ -37,6 +39,7 @@ export async function loader({ params, context }) {
|
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
const serviceOperations = ["pause", "resume", "restart", "expose", "unexpose"];
|
|
42
|
+
const storageOperations = ["storage-credential", "storage-settings"];
|
|
40
43
|
export async function action({ request, params, context }) {
|
|
41
44
|
const ctx = useConsoleContext(context);
|
|
42
45
|
const { projectId, name } = params;
|
|
@@ -47,12 +50,47 @@ export async function action({ request, params, context }) {
|
|
|
47
50
|
await ctx.client.deleteService(projectId, name);
|
|
48
51
|
return redirect(ctx.href(`projects/${encodeURIComponent(projectId)}`));
|
|
49
52
|
}
|
|
53
|
+
const self = ctx.href(`projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}`);
|
|
54
|
+
if (intent === "storage-credential") {
|
|
55
|
+
await ctx.client.reissueStorageCredential(projectId, name);
|
|
56
|
+
return redirect(self);
|
|
57
|
+
}
|
|
58
|
+
if (intent === "storage-settings") {
|
|
59
|
+
await ctx.client.reapplyStorageSettings(projectId, name);
|
|
60
|
+
return redirect(self);
|
|
61
|
+
}
|
|
62
|
+
if (intent === "storage-move") {
|
|
63
|
+
await ctx.client.moveStorage(projectId, name, text(form, "writePauseBound") || undefined);
|
|
64
|
+
return redirect(self);
|
|
65
|
+
}
|
|
66
|
+
if (intent === "hostname-add") {
|
|
67
|
+
await ctx.client.addHostname(projectId, name, text(form, "hostname").trim());
|
|
68
|
+
return redirect(self);
|
|
69
|
+
}
|
|
70
|
+
if (intent === "hostname-remove") {
|
|
71
|
+
await ctx.client.removeHostname(projectId, name, text(form, "hostname"));
|
|
72
|
+
return redirect(self);
|
|
73
|
+
}
|
|
50
74
|
if (!serviceOperations.includes(intent))
|
|
51
75
|
throw new Response(`unknown operation '${intent}'`, { status: 400 });
|
|
52
76
|
await ctx.client.serviceOperation(projectId, name, intent);
|
|
53
|
-
return redirect(
|
|
77
|
+
return redirect(self);
|
|
54
78
|
});
|
|
55
79
|
}
|
|
80
|
+
/** The store a bucket is in, as the operator reported it. */
|
|
81
|
+
const stores = { garage: "Garage", gcs: "Google Cloud Storage" };
|
|
82
|
+
/**
|
|
83
|
+
* What members can do about a bucket the platform made: issue its credential again, reapply the
|
|
84
|
+
* installation's settings to one in Google Cloud Storage, or move one from Garage there. Whether the
|
|
85
|
+
* installation can move at all is the control plane's to say, and its refusal is shown here.
|
|
86
|
+
*/
|
|
87
|
+
function Storage({ s }) {
|
|
88
|
+
const { shows } = useConsole();
|
|
89
|
+
const moveRefusal = useRefusal("storage-move");
|
|
90
|
+
if (!s.bucket)
|
|
91
|
+
return null;
|
|
92
|
+
return (_jsxs("section", { className: "ac-ops", "aria-labelledby": "storage-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "storage-title", children: "Object storage" }) }), _jsx(Operation, { intent: "storage-credential", label: "Issue credential again", operation: "service.storage-credential", entity: s }), s.objectStore === "gcs" ? (_jsx(Operation, { intent: "storage-settings", label: "Reapply bucket settings", operation: "service.storage-settings", entity: s })) : shows("service.storage-move") ? (_jsxs("details", { className: "ac-more", open: moveRefusal !== undefined || undefined, children: [_jsx("summary", { children: "Move to Google Cloud Storage" }), _jsx("p", { children: "Every object is copied while the service goes on writing; then its writes are paused, what changed is copied, every object is checked on both sides, and the service is replaced onto its new bucket. The bucket in Garage is kept." }), _jsxs(ConsoleForm, { intent: "storage-move", className: "ac-form", children: [_jsx(Field, { label: "Write pause bound", name: "writePauseBound", placeholder: "10m", autoComplete: "off", defaultValue: moveRefusal?.values.writePauseBound, hint: "How long writes may be paused, from 1m to 24h. A pause that reaches it fails the move and gives the writes back." }), _jsx(Refused, { intent: "storage-move" }), _jsx("div", { children: _jsx(Submit, { intent: "storage-move", children: "Move bucket" }) })] })] })) : null, _jsx(HostActions, { operation: "service.storage-move", entity: s }), storageOperations.map((op) => (_jsx(Refused, { intent: op }, op)))] }));
|
|
93
|
+
}
|
|
56
94
|
/** A service's bucket, that it has a store of its own, or none — and a phrase while it waits or failed. */
|
|
57
95
|
function objectStorage(s) {
|
|
58
96
|
if (s.objectStorage && /waiting|failed/.test(s.objectStorage))
|
|
@@ -67,12 +105,34 @@ function Operation({ intent, label, operation, danger, entity }) {
|
|
|
67
105
|
const { shows } = useConsole();
|
|
68
106
|
return (_jsxs(_Fragment, { children: [shows(operation) ? (_jsx(ConsoleForm, { intent: intent, children: _jsx(Submit, { intent: intent, danger: danger, children: label }) })) : null, _jsx(HostActions, { operation: operation, entity: entity })] }));
|
|
69
107
|
}
|
|
108
|
+
function record(r) {
|
|
109
|
+
return `${r.kind} ${r.name} → ${r.value}`;
|
|
110
|
+
}
|
|
111
|
+
function hostnameStands(h) {
|
|
112
|
+
return h.reason ? `${h.state}: ${h.reason}` : h.state;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The names a service answers at: the one the platform derives, then each custom hostname with where it
|
|
116
|
+
* stands and the record its owner creates, and the proof record the project's names need. Adding and
|
|
117
|
+
* removing are forms; the control plane's refusal is shown as it says it.
|
|
118
|
+
*/
|
|
119
|
+
function Hostnames({ service: s }) {
|
|
120
|
+
return (_jsxs("section", { className: "ac-card", "aria-labelledby": "hostnames", "data-hostnames": true, children: [_jsx("h2", { id: "hostnames", children: "Hostnames" }), _jsxs("dl", { className: "ac-facts", children: [_jsx("dt", { children: "Address" }), _jsx("dd", { children: s.hostname ? _jsx("a", { href: s.hostname, children: s.hostname }) : s.exposed ? "Exposed; the platform has no address for it yet" : "Not exposed" }), s.customHostnames.map((h) => (_jsxs("div", { "data-custom-hostname": h.hostname, "data-state": h.state, children: [_jsx("dt", { children: h.hostname }), _jsxs("dd", { children: [_jsx("span", { children: hostnameStands(h) }), h.record ? (_jsxs("span", { className: "ac-hint", "data-record": true, children: [" ", "Create ", record(h.record)] })) : null, h.note ? _jsxs("span", { className: "ac-hint", children: [" ", h.note] }) : null] })] }, h.hostname))), s.proofRecord ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Proof record" }), _jsx("dd", { children: _jsxs("code", { "data-proof-record": true, children: [s.proofRecord.kind, " ", s.proofRecord.name, " \"", s.proofRecord.value, "\""] }) })] })) : null] })] }));
|
|
121
|
+
}
|
|
122
|
+
/** Adding and removing a custom hostname, in the inspector with the service's other operations. */
|
|
123
|
+
function HostnameOperations({ service: s }) {
|
|
124
|
+
const { shows } = useConsole();
|
|
125
|
+
const addRefusal = useRefusal("hostname-add");
|
|
126
|
+
if (!shows("service-hostname.add") && !shows("service-hostname.remove"))
|
|
127
|
+
return null;
|
|
128
|
+
return (_jsxs("section", { className: "ac-form", "aria-labelledby": "hostname-operations", "data-hostname-operations": true, children: [_jsx(SectionTitle, { children: _jsx("span", { id: "hostname-operations", children: "Custom hostnames" }) }), shows("service-hostname.add") ? (_jsxs(ConsoleForm, { intent: "hostname-add", className: "ac-inline", children: [_jsx(Field, { label: "Add a custom hostname", name: "hostname", required: true, autoComplete: "off", placeholder: "app.example.com", defaultValue: addRefusal?.values.hostname }), _jsx(Submit, { intent: "hostname-add", primary: true, children: "Add" })] })) : null, _jsx(Refused, { intent: "hostname-add" }), shows("service-hostname.remove") && s.customHostnames.length > 0 ? (_jsxs(ConsoleForm, { intent: "hostname-remove", className: "ac-inline", children: [_jsx(Select, { label: "Remove a custom hostname", id: "hostname-remove", name: "hostname", children: s.customHostnames.map((h) => (_jsx("option", { value: h.hostname, children: h.hostname }, h.hostname))) }), _jsx(Submit, { intent: "hostname-remove", danger: true, children: "Remove" })] })) : null, _jsx(Refused, { intent: "hostname-remove" })] }));
|
|
129
|
+
}
|
|
70
130
|
export default function Service() {
|
|
71
131
|
const data = useLoaderData();
|
|
72
132
|
const { project: p, panels } = data;
|
|
73
133
|
const { status: s, state } = useServiceStream(p.id, data.service.name, data.service);
|
|
74
134
|
const deleteRefused = useRefusal("delete") !== undefined;
|
|
75
|
-
const inspector = (_jsxs(_Fragment, { children: [_jsxs("section", { className: "ac-ops", "aria-labelledby": "operations-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "operations-title", children: "Operations" }) }), s.paused ? _jsx(Operation, { intent: "resume", label: "Resume", operation: "service.resume", entity: s }) : _jsx(Operation, { intent: "pause", label: "Pause", operation: "service.pause", entity: s }), _jsx(Operation, { intent: "restart", label: "Restart", operation: "service.restart", entity: s }), s.exposed ? _jsx(Operation, { intent: "unexpose", label: "Unexpose", operation: "service.unexpose", entity: s }) : _jsx(Operation, { intent: "expose", label: "Expose", operation: "service.expose", entity: s })] }), serviceOperations.map((op) => (_jsx(Refused, { intent: op }, op))), _jsxs("details", { className: "ac-more", open: deleteRefused || undefined, children: [_jsx("summary", { children: "Delete" }), _jsxs("div", { className: "ac-danger-zone", children: [_jsxs("p", { children: ["Deleting ", s.name, " stops it and removes it from the project. Its database is kept, and so is its bucket if it has one: applying a descriptor with this name again brings the service back with its data."] }), _jsx(Operation, { intent: "delete", label: "Delete service", operation: "service.delete", danger: true, entity: s }), _jsx(Refused, { intent: "delete" })] })] })] }));
|
|
76
|
-
return (_jsxs(Page, { inspector: inspector, children: [_jsxs("div", { className: "ac-head", children: [_jsx("h1", { children: s.name }), _jsxs("p", { "aria-live": "polite", className: "ac-actions ac-state-line", children: [_jsx("span", { className: "ac-pill", children: _jsx(Lifecycle, { lifecycle: s.lifecycle, confirmed: s.confirmed }) }), state === "live" ? _jsx("span", { className: "ac-live", children: " Updating as the platform reports" }) : null] }), _jsx(ServiceSections, { projectId: p.id, name: s.name, current: "overview" })] }), s.detail ? _jsx("p", { className: "ac-notice", children: s.detail }) : null, _jsx(Shape, { service: s }), _jsxs("section", { className: "ac-card", "aria-labelledby": "reported", children: [_jsx("h2", { id: "reported", children: "Reported by the cluster" }), _jsxs("dl", { className: "ac-facts", children: [_jsx("dt", { children: "Instances" }), _jsxs("dd", { children: [s.readyInstances, " ready of ", s.desiredInstances] }), _jsx("dt", { children: "Image" }), _jsx("dd", { children: s.image }), _jsx("dt", { children: "Generation" }), _jsx("dd", { children: s.generation }), _jsx("dt", { children: "
|
|
135
|
+
const inspector = (_jsxs(_Fragment, { children: [_jsxs("section", { className: "ac-ops", "aria-labelledby": "operations-title", children: [_jsx(SectionTitle, { children: _jsx("span", { id: "operations-title", children: "Operations" }) }), s.paused ? _jsx(Operation, { intent: "resume", label: "Resume", operation: "service.resume", entity: s }) : _jsx(Operation, { intent: "pause", label: "Pause", operation: "service.pause", entity: s }), _jsx(Operation, { intent: "restart", label: "Restart", operation: "service.restart", entity: s }), s.exposed ? _jsx(Operation, { intent: "unexpose", label: "Unexpose", operation: "service.unexpose", entity: s }) : _jsx(Operation, { intent: "expose", label: "Expose", operation: "service.expose", entity: s })] }), _jsx(HostnameOperations, { service: s }), serviceOperations.map((op) => (_jsx(Refused, { intent: op }, op))), _jsx(Storage, { s: s }), _jsxs("details", { className: "ac-more", open: deleteRefused || undefined, children: [_jsx("summary", { children: "Delete" }), _jsxs("div", { className: "ac-danger-zone", children: [_jsxs("p", { children: ["Deleting ", s.name, " stops it and removes it from the project. Its database is kept, and so is its bucket if it has one: applying a descriptor with this name again brings the service back with its data."] }), _jsx(Operation, { intent: "delete", label: "Delete service", operation: "service.delete", danger: true, entity: s }), _jsx(Refused, { intent: "delete" })] })] })] }));
|
|
136
|
+
return (_jsxs(Page, { inspector: inspector, children: [_jsxs("div", { className: "ac-head", children: [_jsx("h1", { children: s.name }), _jsxs("p", { "aria-live": "polite", className: "ac-actions ac-state-line", children: [_jsx("span", { className: "ac-pill", children: _jsx(Lifecycle, { lifecycle: s.lifecycle, confirmed: s.confirmed }) }), state === "live" ? _jsx("span", { className: "ac-live", children: " Updating as the platform reports" }) : null] }), _jsx(ServiceSections, { projectId: p.id, name: s.name, current: "overview" })] }), s.detail ? _jsx("p", { className: "ac-notice", children: s.detail }) : null, _jsx(Shape, { service: s }), _jsx(Hostnames, { service: s }), _jsxs("section", { className: "ac-card", "aria-labelledby": "reported", children: [_jsx("h2", { id: "reported", children: "Reported by the cluster" }), _jsxs("dl", { className: "ac-facts", children: [_jsx("dt", { children: "Instances" }), _jsxs("dd", { children: [s.readyInstances, " ready of ", s.desiredInstances] }), _jsx("dt", { children: "Image" }), _jsx("dd", { children: s.image }), _jsx("dt", { children: "Generation" }), _jsx("dd", { children: s.generation }), _jsx("dt", { children: "Database" }), _jsx("dd", { children: s.hosting === "web" ? "None" : (s.database ?? "Nothing reported yet") }), _jsx("dt", { children: "Object storage" }), _jsxs("dd", { children: [objectStorage(s), s.bucketAddress ? _jsxs("span", { className: "ac-hint", "data-bucket-address": true, children: [" ", s.bucketAddress] }) : null] }), s.objectStore ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Object store" }), _jsx("dd", { "data-object-store": s.objectStore, children: stores[s.objectStore] ?? s.objectStore })] })) : null, s.bucketLocation ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Bucket location" }), _jsx("dd", { children: s.bucketLocation })] })) : null, s.softDeleteDays !== undefined && s.softDeleteDays !== null ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Deleted objects" }), _jsxs("dd", { children: ["Recoverable for ", s.softDeleteDays, " days"] })] })) : null, s.storageMove ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Bucket move" }), _jsx("dd", { "data-storage-move": true, children: s.storageMove })] })) : null, s.buckets && s.buckets.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Named buckets" }), _jsx("dd", { children: _jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "data-buckets": true, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Name" }), _jsx("th", { scope: "col", children: "Exposure" }), _jsx("th", { scope: "col", children: "Bucket" }), _jsx("th", { scope: "col", children: "Address" })] }) }), _jsx("tbody", { children: bucketRows(s.buckets).map((b) => (_jsxs("tr", { "data-bucket": b.name, children: [_jsx("th", { scope: "row", children: b.name }), _jsx("td", { "data-bucket-exposure": true, children: b.exposure }), _jsx("td", { children: b.bucket ?? "" }), _jsxs("td", { children: [b.address ? _jsx("a", { href: b.address, children: b.address }) : null, b.note ? _jsx("span", { className: "ac-hint", "data-bucket-note": true, children: b.note }) : null] })] }, b.name))) })] }) }) })] })) : null, s.broker ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Broker" }), _jsx("dd", { children: s.broker })] })) : null, s.grants ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Grants" }), _jsx("dd", { "data-grants": true, children: s.grants })] })) : null, s.crossProjectTopics && s.crossProjectTopics.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Cross-project topics" }), _jsx("dd", { children: _jsx("ul", { className: "ac-topics", children: s.crossProjectTopics.map((t) => (_jsxs("li", { "data-cross-project-topic": `${t.project}/${t.topic}`, "data-granted": t.status === "granted" ? "yes" : "no", children: [_jsx("code", { children: `${t.project}/${t.topic}` }), " ", t.right, " ", t.status === "granted" ? t.status : _jsx("span", { className: "ac-notice", children: t.status })] }, `${t.project}/${t.topic}/${t.right}`))) }) })] })) : null, s.undeclaredTopics && s.undeclaredTopics.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Undeclared topics" }), _jsx("dd", { children: _jsx("ul", { className: "ac-topics", children: s.undeclaredTopics.map((t) => (_jsx("li", { "data-topic": t, children: _jsx("code", { children: t }) }, t))) }) })] })) : null, s.warnings && s.warnings.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Warnings" }), _jsx("dd", { children: _jsx("ul", { className: "ac-topics", children: s.warnings.map((w) => (_jsx("li", { "data-warning": w.kind, children: _jsx("span", { className: "ac-notice", children: w.message }) }, `${w.kind}/${w.component}/${w.topic}`))) }) })] })) : null, s.topicSources && s.topicSources.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Topic sources" }), _jsx("dd", { children: _jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "data-topic-sources": true, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Component" }), _jsx("th", { scope: "col", children: "Topic" }), _jsx("th", { scope: "col", children: "Group" }), _jsx("th", { scope: "col", className: "ac-num", children: "Version" }), _jsx("th", { scope: "col", className: "ac-num", children: "Lag" }), _jsx("th", { scope: "col", children: "Failing" }), _jsx("th", { scope: "col", children: "Retained" })] }) }), _jsx("tbody", { children: s.topicSources.map((t) => (_jsxs("tr", { "data-topic-source": t.component, "data-failing": t.failing ? "yes" : "no", children: [_jsx("td", { children: t.component }), _jsxs("td", { children: [_jsx("code", { children: t.broker ? `${t.topic}@${t.broker}` : t.topic }), t.contract ? ` as ${t.contract}` : ""] }), _jsx("td", { children: _jsx("code", { children: t.group }) }), _jsx("td", { className: "ac-num", children: t.version }), _jsx("td", { className: "ac-num", children: t.lag ?? "—" }), _jsx("td", { children: t.failing ? _jsx("span", { className: "ac-notice", children: t.failing }) : "—" }), _jsx("td", { "data-gap": t.gap ? (t.gap.compacted ? "compacted" : t.gap.gone ? "gone" : "everything") : "unknown", children: t.gap === undefined || t.gap === null ? ("—") : t.gap.compacted ? ("compacted") : !t.gap.gone ? ("everything") : (_jsxs("details", { children: [_jsx("summary", { children: "earlier messages gone" }), _jsx("ul", { className: "ac-topics", children: t.gap.partitions.map((p) => (_jsxs("li", { "data-partition": p.partition, children: ["p", p.partition, " from ", p.beginning, " (", p.earliestRetained ?? "holds nothing", ")"] }, p.partition))) })] })) })] }, t.component))) })] }) }) })] })) : null, s.topicChecks && s.topicChecks.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Topic checks" }), _jsx("dd", { children: _jsx("ul", { className: "ac-topics", children: s.topicChecks.map((c) => (_jsxs("li", { "data-check": c.state, children: [c.component, " ", c.direction, ": ", c.state, c.state === "mismatch" ? ` (${c.stated ?? "none"})` : ""] }, `${c.component}/${c.direction}`))) }) })] })) : null, _jsx("dt", { children: "Runs as" }), _jsx("dd", { children: runsAs(s) }), s.hosting === "web" ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Admits" }), _jsx("dd", { children: ["The internet", ...s.callers.map((c) => (c === "*" ? `Every service in ${p.name}` : c))].join(", ") }), _jsx("dt", { children: "Mounts" }), _jsx("dd", { children: s.mounts.length === 0 ? ("None") : (_jsx("ul", { className: "ac-mounts", children: s.mounts.map((m) => (_jsxs("li", { "data-mount": m.path, children: [_jsx("code", { children: m.path }), " \u2192 ", m.service, m.state && m.state !== "ok" ? ` (${m.state})` : ""] }, m.path))) })) })] })) : null, _jsx("dt", { children: "Stopped by" }), _jsx("dd", { children: s.suspended ? "Its organization is disabled" : s.paused ? "Its members paused it" : "Nobody" }), _jsx("dt", { children: "Report" }), _jsx("dd", { children: s.confirmed ? "Confirmed by the cluster" : "The last known state; the cluster has not confirmed it" })] })] }), _jsx(Panels, { kind: "service", entity: s, loaded: panels })] }));
|
|
77
137
|
}
|
|
78
138
|
export const ErrorBoundary = ConsoleErrorBoundary;
|
package/dist/routes.js
CHANGED
|
@@ -34,6 +34,7 @@ export function consoleRoutes(options = {}) {
|
|
|
34
34
|
page("organizations/:organizationId", "organization"),
|
|
35
35
|
page("organizations/:organizationId/members", "members"),
|
|
36
36
|
page("organizations/:organizationId/tokens", "tokens"),
|
|
37
|
+
page("organizations/:organizationId/machines", "machines"),
|
|
37
38
|
page("organizations/:organizationId/projects/new", "project-new"),
|
|
38
39
|
page("projects/:projectId", "project"),
|
|
39
40
|
page("projects/:projectId/services/apply", "service-apply"),
|
package/dist/runtime.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { SealedFlash, type SessionStore } from "./session/cookie-store.ts";
|
|
|
3
3
|
import { SessionTokenSource, type TokenSource } from "./session/token-source.ts";
|
|
4
4
|
import { type FetchLike } from "./server/transport.ts";
|
|
5
5
|
import type { ConsoleOptions } from "./options.ts";
|
|
6
|
-
import type { DeployTokenCreated } from "./client/schemas.ts";
|
|
6
|
+
import type { DeployTokenCreated, MachineRegistered } from "./client/schemas.ts";
|
|
7
7
|
export interface ConsoleRuntime {
|
|
8
8
|
options: ConsoleOptions;
|
|
9
9
|
mount: string;
|
|
@@ -16,6 +16,8 @@ export interface ConsoleRuntime {
|
|
|
16
16
|
controlPlaneFetch: FetchLike;
|
|
17
17
|
/** A deploy token's secret, carried across one redirect and shown once. */
|
|
18
18
|
tokenFlash: SealedFlash<DeployTokenCreated>;
|
|
19
|
+
/** A registered machine's client secret, carried across one redirect and shown once, as a token's is. */
|
|
20
|
+
machineFlash: SealedFlash<MachineRegistered>;
|
|
19
21
|
pendingLogin: SealedFlash<{
|
|
20
22
|
state: string;
|
|
21
23
|
nonce: string;
|
package/dist/runtime.js
CHANGED
|
@@ -71,6 +71,7 @@ export function createRuntime(options) {
|
|
|
71
71
|
sessionTokens,
|
|
72
72
|
controlPlaneFetch,
|
|
73
73
|
tokenFlash: new SealedFlash({ secret: secret ?? "unused", secure, name: "ankka_console_flash", maxAgeSeconds: 60 }),
|
|
74
|
+
machineFlash: new SealedFlash({ secret: secret ?? "unused", secure, name: "ankka_console_machine_flash", maxAgeSeconds: 60 }),
|
|
74
75
|
pendingLogin: new SealedFlash({ secret: secret ?? "unused", secure, name: "ankka_console_login", maxAgeSeconds: 600 }),
|
|
75
76
|
log: options.log ?? ((line) => process.stdout.write(JSON.stringify(line) + "\n")),
|
|
76
77
|
};
|
package/dist/styles.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
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-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}}
|
|
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}}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* A control plane in one process: every route the console uses, over in-memory state, with the
|
|
3
3
|
* control plane's rules as the console can observe them — who sees what, `404` rather than `403`
|
|
4
4
|
* for an outsider, owner-only operations, tombstoned ids, `409` for a non-empty delete, a disabled
|
|
5
|
-
* organization refusing changes, descriptor validation, a deploy token shown once
|
|
5
|
+
* organization refusing changes, descriptor validation, a deploy token shown once, grants made,
|
|
6
|
+
* listed and ended with their effect, and machines registered with a secret shown once.
|
|
6
7
|
*
|
|
7
8
|
* It answers from state rather than from a script, so it cannot run out quietly. What a test needs
|
|
8
9
|
* to steer is explicit: `script()` makes the next answer on one route a given status, `tick()` moves
|
|
@@ -32,6 +33,10 @@ export interface FakeControlPlaneOptions {
|
|
|
32
33
|
signupUrl?: string;
|
|
33
34
|
baseDomain?: string;
|
|
34
35
|
port?: number;
|
|
36
|
+
/** The broker's address outside the cluster; unset, the installation does not expose its broker and a machine's topic grant opens nothing. */
|
|
37
|
+
brokerBootstrap?: string;
|
|
38
|
+
/** The most bytes a second a machine may be given on either side; the control plane's default is 32 MiB. */
|
|
39
|
+
byteRateCeiling?: number;
|
|
35
40
|
}
|
|
36
41
|
type Role = "owner" | "member";
|
|
37
42
|
interface Member {
|
|
@@ -82,8 +87,55 @@ interface Project {
|
|
|
82
87
|
topics?: Map<string, TopicRecord>;
|
|
83
88
|
/** Declared brokers beside the installation's, by name. */
|
|
84
89
|
brokers?: Map<string, BrokerRecord>;
|
|
90
|
+
/** Where the project's new buckets in Google Cloud Storage are made (feature 039). */
|
|
91
|
+
location?: string;
|
|
92
|
+
/** Feature 041: restores asked for, in order, and rehearsals. Restores verify at once here. */
|
|
93
|
+
restores?: {
|
|
94
|
+
name: string;
|
|
95
|
+
line: string;
|
|
96
|
+
moment: string;
|
|
97
|
+
requestedBy?: string;
|
|
98
|
+
requestedAt: string;
|
|
99
|
+
}[];
|
|
100
|
+
rehearsals?: {
|
|
101
|
+
name: string;
|
|
102
|
+
line: string;
|
|
103
|
+
moment: string;
|
|
104
|
+
requestedBy?: string;
|
|
105
|
+
requestedAt: string;
|
|
106
|
+
}[];
|
|
107
|
+
databaseHistory?: {
|
|
108
|
+
kind: string;
|
|
109
|
+
by?: string;
|
|
110
|
+
at: string;
|
|
111
|
+
detail?: string;
|
|
112
|
+
}[];
|
|
113
|
+
credentialGeneration?: number;
|
|
114
|
+
database?: {
|
|
115
|
+
replicas: number;
|
|
116
|
+
synchronous: boolean;
|
|
117
|
+
retentionDays?: number;
|
|
118
|
+
rehearse?: string;
|
|
119
|
+
};
|
|
120
|
+
/** The changes to its topics, newest first. */
|
|
121
|
+
history?: ProjectHistoryRecord[];
|
|
85
122
|
hidden: boolean;
|
|
86
123
|
}
|
|
124
|
+
interface ProjectHistoryRecord {
|
|
125
|
+
kind: string;
|
|
126
|
+
topic: string;
|
|
127
|
+
actor?: {
|
|
128
|
+
subject: string;
|
|
129
|
+
display?: string;
|
|
130
|
+
administrative: boolean;
|
|
131
|
+
};
|
|
132
|
+
at?: string;
|
|
133
|
+
changes: {
|
|
134
|
+
setting: string;
|
|
135
|
+
from: string;
|
|
136
|
+
to: string;
|
|
137
|
+
}[];
|
|
138
|
+
}
|
|
87
139
|
interface BrokerRecord {
|
|
88
140
|
bootstrap: string;
|
|
89
141
|
shape: string;
|
|
@@ -102,7 +154,31 @@ interface TopicSourceRecord {
|
|
|
102
154
|
contract?: string;
|
|
103
155
|
lag?: number;
|
|
104
156
|
failing?: string;
|
|
157
|
+
/** What the broker still holds of the topic, per partition. */
|
|
158
|
+
gap?: {
|
|
159
|
+
partitions: {
|
|
160
|
+
partition: number;
|
|
161
|
+
beginning: number;
|
|
162
|
+
earliestRetained?: string;
|
|
163
|
+
}[];
|
|
164
|
+
compacted?: boolean;
|
|
165
|
+
gone?: boolean;
|
|
166
|
+
readAt?: string;
|
|
167
|
+
};
|
|
105
168
|
}
|
|
169
|
+
/** A topic's settings, in the words a member types, as the control plane keeps them. */
|
|
170
|
+
interface TopicSettingsRecord {
|
|
171
|
+
retention: string;
|
|
172
|
+
retentionSize: string;
|
|
173
|
+
cleanup: string;
|
|
174
|
+
tombstoneWindow: string;
|
|
175
|
+
minCompactionLag: string;
|
|
176
|
+
maxCompactionLag: string;
|
|
177
|
+
copies?: number;
|
|
178
|
+
minInSync?: number;
|
|
179
|
+
}
|
|
180
|
+
declare const SettingNames: readonly ["retention", "retentionSize", "cleanup", "tombstoneWindow", "minCompactionLag", "maxCompactionLag", "copies", "minInSync"];
|
|
181
|
+
type SettingName = (typeof SettingNames)[number];
|
|
106
182
|
interface TopicRecord {
|
|
107
183
|
partitions: number;
|
|
108
184
|
phase?: string;
|
|
@@ -113,6 +189,11 @@ interface TopicRecord {
|
|
|
113
189
|
fingerprint: string;
|
|
114
190
|
schema: unknown;
|
|
115
191
|
};
|
|
192
|
+
/** Every setting, and the ones the installation supplied; absent for a topic seeded without them. */
|
|
193
|
+
settings?: TopicSettingsRecord;
|
|
194
|
+
defaulted?: SettingName[];
|
|
195
|
+
/** How many copies the broker holds, as a seed says. */
|
|
196
|
+
copiesHeld?: number;
|
|
116
197
|
}
|
|
117
198
|
/** One side a running service takes on a declared topic with a contract, against it; `topic` is the fake's own, for the project's listing. */
|
|
118
199
|
interface TopicCheckRecord {
|
|
@@ -123,9 +204,21 @@ interface TopicCheckRecord {
|
|
|
123
204
|
stated?: string;
|
|
124
205
|
state: string;
|
|
125
206
|
}
|
|
207
|
+
/** A named bucket as the operator reports it (feature 055). */
|
|
208
|
+
export interface BucketRecord {
|
|
209
|
+
name: string;
|
|
210
|
+
phase: string;
|
|
211
|
+
store?: string;
|
|
212
|
+
bucket?: string;
|
|
213
|
+
exposure: string;
|
|
214
|
+
publicAddress?: string;
|
|
215
|
+
kept?: boolean;
|
|
216
|
+
}
|
|
126
217
|
interface Service {
|
|
127
218
|
name: string;
|
|
128
219
|
projectId: string;
|
|
220
|
+
/** Custom hostnames (feature 045), in the order added; the fake serves each at once. */
|
|
221
|
+
customHostnames?: string[];
|
|
129
222
|
lifecycle: string;
|
|
130
223
|
generation: number;
|
|
131
224
|
image: string;
|
|
@@ -136,6 +229,8 @@ interface Service {
|
|
|
136
229
|
paused: boolean;
|
|
137
230
|
/** What the cluster reports of its database; `null` until it has reported one. */
|
|
138
231
|
database: string | null;
|
|
232
|
+
/** The restore a member switched it to (feature 041); the project database when absent. */
|
|
233
|
+
databaseCluster?: string;
|
|
139
234
|
history: {
|
|
140
235
|
kind: string;
|
|
141
236
|
generation: number;
|
|
@@ -147,6 +242,7 @@ interface Service {
|
|
|
147
242
|
at: string;
|
|
148
243
|
image?: string;
|
|
149
244
|
digest?: string;
|
|
245
|
+
hostname?: string;
|
|
150
246
|
rolledBackTo?: number;
|
|
151
247
|
}[];
|
|
152
248
|
/** The descriptor in force, and the applied ones a rollback can return to, newest first, at most fifty. */
|
|
@@ -181,6 +277,14 @@ interface Service {
|
|
|
181
277
|
ownObjectStore?: boolean;
|
|
182
278
|
/** The descriptor asked that the bucket be reachable from the internet. */
|
|
183
279
|
exposeObjectStorage?: boolean;
|
|
280
|
+
/** The store the bucket is in, `garage` or `gcs`, as the operator reports it (feature 039). */
|
|
281
|
+
objectStore?: string;
|
|
282
|
+
/** Where its bucket in Google Cloud Storage is. */
|
|
283
|
+
bucketLocation?: string;
|
|
284
|
+
/** Where a move of its bucket from Garage is, as the control plane phrases it. */
|
|
285
|
+
storageMove?: string;
|
|
286
|
+
/** Its named buckets as the operator would report them (feature 055), kept ones last. */
|
|
287
|
+
buckets?: BucketRecord[];
|
|
184
288
|
/** What the instances report when asked for the topology; generated from the service when unset. */
|
|
185
289
|
topology?: FakeTopology;
|
|
186
290
|
}
|
|
@@ -216,6 +320,48 @@ interface Token {
|
|
|
216
320
|
expiresAt?: string;
|
|
217
321
|
revoked: boolean;
|
|
218
322
|
}
|
|
323
|
+
/** What one grant opens, flat as the control plane holds it (feature 040). */
|
|
324
|
+
export interface FakeGrantTarget {
|
|
325
|
+
kind: string;
|
|
326
|
+
service?: string;
|
|
327
|
+
method?: string;
|
|
328
|
+
path?: string;
|
|
329
|
+
topic?: string;
|
|
330
|
+
right?: string;
|
|
331
|
+
decrypt?: boolean;
|
|
332
|
+
}
|
|
333
|
+
interface GrantAct {
|
|
334
|
+
by?: string;
|
|
335
|
+
at: string;
|
|
336
|
+
}
|
|
337
|
+
/** A grant a project made; `changes` is what the grantee's side records of it. */
|
|
338
|
+
interface Grant {
|
|
339
|
+
id: string;
|
|
340
|
+
projectId: string;
|
|
341
|
+
grantee: string;
|
|
342
|
+
target: FakeGrantTarget;
|
|
343
|
+
state: string;
|
|
344
|
+
granted: GrantAct;
|
|
345
|
+
answered?: GrantAct;
|
|
346
|
+
ended?: GrantAct;
|
|
347
|
+
changes: {
|
|
348
|
+
change: string;
|
|
349
|
+
by?: string;
|
|
350
|
+
at: string;
|
|
351
|
+
}[];
|
|
352
|
+
}
|
|
353
|
+
interface Machine {
|
|
354
|
+
organizationId: string;
|
|
355
|
+
name: string;
|
|
356
|
+
registeredBy?: string;
|
|
357
|
+
registeredAt: string;
|
|
358
|
+
byteRates?: {
|
|
359
|
+
produceBytesPerSecond: number;
|
|
360
|
+
consumeBytesPerSecond: number;
|
|
361
|
+
requestPercentage: number;
|
|
362
|
+
};
|
|
363
|
+
hidden: boolean;
|
|
364
|
+
}
|
|
219
365
|
export interface FakeControlPlane {
|
|
220
366
|
url: string;
|
|
221
367
|
server: Server;
|
|
@@ -242,11 +388,36 @@ export interface FakeControlPlane {
|
|
|
242
388
|
services: Map<string, Service>;
|
|
243
389
|
tokens: Map<string, Token>;
|
|
244
390
|
tombstones: Set<string>;
|
|
391
|
+
/** Every project's grants, by id. */
|
|
392
|
+
grants: Map<string, Grant>;
|
|
393
|
+
/** Registered machines, by `<organization>/<name>`. */
|
|
394
|
+
machines: Map<string, Machine>;
|
|
245
395
|
};
|
|
246
396
|
seed(seed: FakeSeed): void;
|
|
247
397
|
close(): Promise<void>;
|
|
248
398
|
}
|
|
399
|
+
/** The installation's cloud, as `GET /installation` reports it (feature 044). */
|
|
400
|
+
export interface FakeCloud {
|
|
401
|
+
provider: string;
|
|
402
|
+
account: string;
|
|
403
|
+
location: string;
|
|
404
|
+
kmsKey?: string;
|
|
405
|
+
}
|
|
249
406
|
export interface FakeSeed {
|
|
407
|
+
/** The installation's cloud (feature 044); `null` for an installation that names none, as until seeded. */
|
|
408
|
+
cloud?: FakeCloud | null;
|
|
409
|
+
/** Where the installation keeps new buckets (feature 039); Garage until a suite says otherwise. */
|
|
410
|
+
objectStore?: "garage" | "gcs";
|
|
411
|
+
/** Feature 041: start held, as a control plane whose own database was restored. */
|
|
412
|
+
held?: {
|
|
413
|
+
targetTime: string;
|
|
414
|
+
services?: {
|
|
415
|
+
project: string;
|
|
416
|
+
service: string;
|
|
417
|
+
recordedImage?: string;
|
|
418
|
+
clusterImage?: string;
|
|
419
|
+
}[];
|
|
420
|
+
};
|
|
250
421
|
organizations?: {
|
|
251
422
|
id: string;
|
|
252
423
|
name: string;
|
|
@@ -283,6 +454,11 @@ export interface FakeSeed {
|
|
|
283
454
|
provisionObjectStorage?: boolean;
|
|
284
455
|
exposeObjectStorage?: boolean;
|
|
285
456
|
ownObjectStore?: boolean;
|
|
457
|
+
/** The store its bucket is in, `garage` or `gcs`. */
|
|
458
|
+
objectStore?: string;
|
|
459
|
+
bucketLocation?: string;
|
|
460
|
+
/** Its named buckets as the operator reports them (feature 055). */
|
|
461
|
+
buckets?: BucketRecord[];
|
|
286
462
|
}[];
|
|
287
463
|
/** Project secrets, by name and entry: a value each, never listed. */
|
|
288
464
|
secrets?: {
|
|
@@ -310,6 +486,22 @@ export interface FakeSeed {
|
|
|
310
486
|
name: string;
|
|
311
487
|
schema: unknown;
|
|
312
488
|
};
|
|
489
|
+
/** Its settings, each the installation's shipped default unless given; none for a topic from before settings. */
|
|
490
|
+
settings?: Partial<TopicSettingsRecord>;
|
|
491
|
+
}[];
|
|
492
|
+
/** Grants a project made, as if an owner had made them: accepted within its organization, pending to another, unless `state` says. */
|
|
493
|
+
grants?: {
|
|
494
|
+
projectId: string;
|
|
495
|
+
grantee: string;
|
|
496
|
+
target: FakeGrantTarget;
|
|
497
|
+
state?: string;
|
|
498
|
+
by?: string;
|
|
499
|
+
}[];
|
|
500
|
+
/** Machines registered on an organization. */
|
|
501
|
+
machines?: {
|
|
502
|
+
organizationId: string;
|
|
503
|
+
name: string;
|
|
504
|
+
byteRates?: Machine["byteRates"];
|
|
313
505
|
}[];
|
|
314
506
|
}
|
|
315
507
|
export declare function fakeControlPlane(options?: FakeControlPlaneOptions): Promise<FakeControlPlane>;
|