ankka-console 0.12.0 → 0.14.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.
Binary file
@@ -68,9 +68,69 @@ export declare const installationSecretsSchema: z.ZodObject<{
68
68
  auditLog: z.ZodDefault<z.ZodString>;
69
69
  }, z.core.$strip>;
70
70
  export type InstallationSecrets = z.infer<typeof installationSecretsSchema>;
71
+ /** One model the installation's model server declares: its state, and its digest once served (feature 053). */
72
+ export declare const modelLineSchema: z.ZodObject<{
73
+ name: z.ZodString;
74
+ state: z.ZodString;
75
+ digest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
76
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
77
+ }, z.core.$strip>;
78
+ export type ModelLine = z.infer<typeof modelLineSchema>;
79
+ /** One cluster's model server, read live through its door. */
80
+ export declare const clusterModelsSchema: z.ZodObject<{
81
+ cluster: z.ZodString;
82
+ server: z.ZodString;
83
+ ready: z.ZodBoolean;
84
+ models: z.ZodDefault<z.ZodArray<z.ZodObject<{
85
+ name: z.ZodString;
86
+ state: z.ZodString;
87
+ digest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
88
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
89
+ }, z.core.$strip>>>;
90
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
91
+ }, z.core.$strip>;
92
+ export type ClusterModels = z.infer<typeof clusterModelsSchema>;
93
+ export declare const installationModelsSchema: z.ZodObject<{
94
+ clusters: z.ZodArray<z.ZodObject<{
95
+ cluster: z.ZodString;
96
+ server: z.ZodString;
97
+ ready: z.ZodBoolean;
98
+ models: z.ZodDefault<z.ZodArray<z.ZodObject<{
99
+ name: z.ZodString;
100
+ state: z.ZodString;
101
+ digest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
102
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
103
+ }, z.core.$strip>>>;
104
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
105
+ }, z.core.$strip>>;
106
+ }, z.core.$strip>;
107
+ export type InstallationModels = z.infer<typeof installationModelsSchema>;
108
+ /**
109
+ * The installation's broker (spec 057): how many nodes, absent when the control plane was not told,
110
+ * and the copies a topic is given by default, with how many must hold a message.
111
+ */
112
+ export declare const installationBrokerSchema: z.ZodObject<{
113
+ nodes: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | undefined, number | null | undefined>>;
114
+ defaultCopies: z.ZodNumber;
115
+ minInSync: z.ZodNumber;
116
+ }, z.core.$strip>;
117
+ export type InstallationBroker = z.infer<typeof installationBrokerSchema>;
118
+ /** The longest retention a topic may be given (spec 057): a duration, or unbounded. */
119
+ export declare const installationRetentionSchema: z.ZodObject<{
120
+ longest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
121
+ unbounded: z.ZodDefault<z.ZodBoolean>;
122
+ }, z.core.$strip>;
123
+ export type InstallationRetention = z.infer<typeof installationRetentionSchema>;
124
+ /** Erasure and its keyring (spec 057, built by spec 042); absent until the platform has erasure. */
125
+ export declare const installationErasureSchema: z.ZodObject<{
126
+ enabled: z.ZodBoolean;
127
+ keyringInstances: z.ZodNumber;
128
+ }, z.core.$strip>;
129
+ export type InstallationErasure = z.infer<typeof installationErasureSchema>;
71
130
  /**
72
131
  * `GET /installation`: the platform's version, and its cloud when it names a provider; and its
73
- * backups (feature 041); and where it keeps its secrets (feature 038).
132
+ * backups (feature 041); where it keeps its secrets (feature 038); its model server (feature 053);
133
+ * and its broker, longest retention and erasure (spec 057), each absent when not stated.
74
134
  */
75
135
  export declare const installationSchema: z.ZodObject<{
76
136
  platformVersion: z.ZodString;
@@ -211,6 +271,79 @@ export declare const installationSchema: z.ZodObject<{
211
271
  recordRetention: string;
212
272
  auditLog: string;
213
273
  } | null | undefined>>;
274
+ models: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodObject<{
275
+ clusters: z.ZodArray<z.ZodObject<{
276
+ cluster: z.ZodString;
277
+ server: z.ZodString;
278
+ ready: z.ZodBoolean;
279
+ models: z.ZodDefault<z.ZodArray<z.ZodObject<{
280
+ name: z.ZodString;
281
+ state: z.ZodString;
282
+ digest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
283
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
284
+ }, z.core.$strip>>>;
285
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
286
+ }, z.core.$strip>>;
287
+ }, z.core.$strip>>>, z.ZodTransform<{
288
+ clusters: {
289
+ cluster: string;
290
+ server: string;
291
+ ready: boolean;
292
+ models: {
293
+ name: string;
294
+ state: string;
295
+ digest: string | undefined;
296
+ detail: string | undefined;
297
+ }[];
298
+ detail: string | undefined;
299
+ }[];
300
+ } | undefined, {
301
+ clusters: {
302
+ cluster: string;
303
+ server: string;
304
+ ready: boolean;
305
+ models: {
306
+ name: string;
307
+ state: string;
308
+ digest: string | undefined;
309
+ detail: string | undefined;
310
+ }[];
311
+ detail: string | undefined;
312
+ }[];
313
+ } | null | undefined>>;
314
+ broker: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodObject<{
315
+ nodes: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | undefined, number | null | undefined>>;
316
+ defaultCopies: z.ZodNumber;
317
+ minInSync: z.ZodNumber;
318
+ }, z.core.$strip>>>, z.ZodTransform<{
319
+ nodes: number | undefined;
320
+ defaultCopies: number;
321
+ minInSync: number;
322
+ } | undefined, {
323
+ nodes: number | undefined;
324
+ defaultCopies: number;
325
+ minInSync: number;
326
+ } | null | undefined>>;
327
+ retention: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodObject<{
328
+ longest: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
329
+ unbounded: z.ZodDefault<z.ZodBoolean>;
330
+ }, z.core.$strip>>>, z.ZodTransform<{
331
+ longest: string | undefined;
332
+ unbounded: boolean;
333
+ } | undefined, {
334
+ longest: string | undefined;
335
+ unbounded: boolean;
336
+ } | null | undefined>>;
337
+ erasure: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodObject<{
338
+ enabled: z.ZodBoolean;
339
+ keyringInstances: z.ZodNumber;
340
+ }, z.core.$strip>>>, z.ZodTransform<{
341
+ enabled: boolean;
342
+ keyringInstances: number;
343
+ } | undefined, {
344
+ enabled: boolean;
345
+ keyringInstances: number;
346
+ } | null | undefined>>;
214
347
  }, z.core.$strip>;
215
348
  export type Installation = z.infer<typeof installationSchema>;
216
349
  export declare const quotaSchema: z.ZodObject<{
@@ -389,6 +522,21 @@ export declare const dnsRecordSchema: z.ZodObject<{
389
522
  value: z.ZodString;
390
523
  }, z.core.$strip>;
391
524
  export type DnsRecord = z.infer<typeof dnsRecordSchema>;
525
+ /**
526
+ * One named bucket of a service, as the operator reported it (feature 055): kept ones are those the
527
+ * descriptor no longer names, which the platform never deletes.
528
+ */
529
+ export declare const bucketReportSchema: z.ZodObject<{
530
+ name: z.ZodString;
531
+ phase: z.ZodString;
532
+ store: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
533
+ bucket: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
534
+ exposure: z.ZodDefault<z.ZodString>;
535
+ publicAddress: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
536
+ kept: z.ZodDefault<z.ZodBoolean>;
537
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
538
+ }, z.core.$strip>;
539
+ export type BucketReport = z.infer<typeof bucketReportSchema>;
392
540
  /** One custom hostname of a service: where it stands, and the record that points it at the installation. */
393
541
  export declare const customHostnameSchema: z.ZodObject<{
394
542
  hostname: z.ZodString;
@@ -622,6 +770,34 @@ export declare const serviceStatusSchema: z.ZodObject<{
622
770
  bucketLocation: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
623
771
  softDeleteDays: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | undefined, number | null | undefined>>;
624
772
  storageMove: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
773
+ buckets: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
774
+ name: z.ZodString;
775
+ phase: z.ZodString;
776
+ store: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
777
+ bucket: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
778
+ exposure: z.ZodDefault<z.ZodString>;
779
+ publicAddress: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
780
+ kept: z.ZodDefault<z.ZodBoolean>;
781
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
782
+ }, z.core.$strip>>>>, z.ZodTransform<{
783
+ name: string;
784
+ phase: string;
785
+ store: string | undefined;
786
+ bucket: string | undefined;
787
+ exposure: string;
788
+ publicAddress: string | undefined;
789
+ kept: boolean;
790
+ detail: string | undefined;
791
+ }[] | undefined, {
792
+ name: string;
793
+ phase: string;
794
+ store: string | undefined;
795
+ bucket: string | undefined;
796
+ exposure: string;
797
+ publicAddress: string | undefined;
798
+ kept: boolean;
799
+ detail: string | undefined;
800
+ }[] | null | undefined>>;
625
801
  databaseCluster: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
626
802
  grants: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
627
803
  crossProjectTopics: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodLazy<z.ZodObject<{
@@ -939,6 +1115,34 @@ export declare const rolledBackSchema: z.ZodObject<{
939
1115
  bucketLocation: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
940
1116
  softDeleteDays: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | undefined, number | null | undefined>>;
941
1117
  storageMove: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
1118
+ buckets: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
1119
+ name: z.ZodString;
1120
+ phase: z.ZodString;
1121
+ store: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
1122
+ bucket: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
1123
+ exposure: z.ZodDefault<z.ZodString>;
1124
+ publicAddress: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
1125
+ kept: z.ZodDefault<z.ZodBoolean>;
1126
+ detail: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
1127
+ }, z.core.$strip>>>>, z.ZodTransform<{
1128
+ name: string;
1129
+ phase: string;
1130
+ store: string | undefined;
1131
+ bucket: string | undefined;
1132
+ exposure: string;
1133
+ publicAddress: string | undefined;
1134
+ kept: boolean;
1135
+ detail: string | undefined;
1136
+ }[] | undefined, {
1137
+ name: string;
1138
+ phase: string;
1139
+ store: string | undefined;
1140
+ bucket: string | undefined;
1141
+ exposure: string;
1142
+ publicAddress: string | undefined;
1143
+ kept: boolean;
1144
+ detail: string | undefined;
1145
+ }[] | null | undefined>>;
942
1146
  databaseCluster: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
943
1147
  grants: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
944
1148
  crossProjectTopics: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodLazy<z.ZodObject<{
@@ -57,9 +57,47 @@ export const installationSecretsSchema = z.object({
57
57
  recordRetention: z.string(),
58
58
  auditLog: z.string().default("unknown"),
59
59
  });
60
+ /** One model the installation's model server declares: its state, and its digest once served (feature 053). */
61
+ export const modelLineSchema = z.object({
62
+ name: z.string(),
63
+ state: z.string(),
64
+ digest: optional(z.string()),
65
+ detail: optional(z.string()),
66
+ });
67
+ /** One cluster's model server, read live through its door. */
68
+ export const clusterModelsSchema = z.object({
69
+ cluster: z.string(),
70
+ server: z.string(),
71
+ ready: z.boolean(),
72
+ models: z.array(modelLineSchema).default([]),
73
+ detail: optional(z.string()),
74
+ });
75
+ export const installationModelsSchema = z.object({
76
+ clusters: z.array(clusterModelsSchema),
77
+ });
78
+ /**
79
+ * The installation's broker (spec 057): how many nodes, absent when the control plane was not told,
80
+ * and the copies a topic is given by default, with how many must hold a message.
81
+ */
82
+ export const installationBrokerSchema = z.object({
83
+ nodes: optional(z.number().int()),
84
+ defaultCopies: z.number().int(),
85
+ minInSync: z.number().int(),
86
+ });
87
+ /** The longest retention a topic may be given (spec 057): a duration, or unbounded. */
88
+ export const installationRetentionSchema = z.object({
89
+ longest: optional(z.string()),
90
+ unbounded: z.boolean().default(false),
91
+ });
92
+ /** Erasure and its keyring (spec 057, built by spec 042); absent until the platform has erasure. */
93
+ export const installationErasureSchema = z.object({
94
+ enabled: z.boolean(),
95
+ keyringInstances: z.number().int(),
96
+ });
60
97
  /**
61
98
  * `GET /installation`: the platform's version, and its cloud when it names a provider; and its
62
- * backups (feature 041); and where it keeps its secrets (feature 038).
99
+ * backups (feature 041); where it keeps its secrets (feature 038); its model server (feature 053);
100
+ * and its broker, longest retention and erasure (spec 057), each absent when not stated.
63
101
  */
64
102
  export const installationSchema = z.object({
65
103
  platformVersion: z.string(),
@@ -67,6 +105,10 @@ export const installationSchema = z.object({
67
105
  // Lazily: its schema is with the other backup schemas, below.
68
106
  backups: optional(z.lazy(() => installationStatusSchema)),
69
107
  secrets: optional(installationSecretsSchema),
108
+ models: optional(installationModelsSchema),
109
+ broker: optional(installationBrokerSchema),
110
+ retention: optional(installationRetentionSchema),
111
+ erasure: optional(installationErasureSchema),
70
112
  });
71
113
  export const quotaSchema = z.object({
72
114
  projects: optional(z.number().int()),
@@ -140,6 +182,24 @@ export const dnsRecordSchema = z.object({
140
182
  kind: z.string(),
141
183
  value: z.string(),
142
184
  });
185
+ /**
186
+ * One named bucket of a service, as the operator reported it (feature 055): kept ones are those the
187
+ * descriptor no longer names, which the platform never deletes.
188
+ */
189
+ export const bucketReportSchema = z.object({
190
+ name: z.string(),
191
+ phase: z.string(),
192
+ /** `garage` or `gcs`; absent for a supplied bucket. */
193
+ store: optional(z.string()),
194
+ /** Its name in the store. */
195
+ bucket: optional(z.string()),
196
+ /** `private`, `signed`, `public` or `supplied`. */
197
+ exposure: z.string().default("private"),
198
+ /** Where a public or signed bucket is on the internet. */
199
+ publicAddress: optional(z.string()),
200
+ kept: z.boolean().default(false),
201
+ detail: optional(z.string()),
202
+ });
143
203
  /** One custom hostname of a service: where it stands, and the record that points it at the installation. */
144
204
  export const customHostnameSchema = z.object({
145
205
  hostname: z.string(),
@@ -190,6 +250,8 @@ export const serviceStatusSchema = z.object({
190
250
  softDeleteDays: optional(z.number().int()),
191
251
  /** Where a move of the bucket from Garage is, as a phrase; its detail is in `detail`. */
192
252
  storageMove: optional(z.string()),
253
+ /** Each named bucket, in the descriptor's order, then the kept ones (feature 055); absent when none. */
254
+ buckets: optional(z.array(bucketReportSchema)),
193
255
  /** The project database cluster the service is on, when a member switched it to a restore. */
194
256
  databaseCluster: optional(z.string()),
195
257
  /** `mounted` when the service's instances read their project's grants (feature 040). */
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The front page: who you are, as the control plane sees you, and the organizations you can see —
3
3
  * yours with your role in each, or every one of them for a platform administrator — and what the
4
- * installation is: its version, and its cloud when it names one (feature 044).
4
+ * installation is: its version, and its cloud when it names one (feature 044), and its broker and
5
+ * longest topic retention (spec 057).
5
6
  */
6
7
  import { type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "react-router";
7
8
  import { ConsoleErrorBoundary } from "../ui/errors.tsx";
@@ -76,6 +77,33 @@ export declare function loader({ context }: LoaderFunctionArgs): Promise<{
76
77
  recordRetention: string;
77
78
  auditLog: string;
78
79
  } | undefined;
80
+ models: {
81
+ clusters: {
82
+ cluster: string;
83
+ server: string;
84
+ ready: boolean;
85
+ models: {
86
+ name: string;
87
+ state: string;
88
+ digest: string | undefined;
89
+ detail: string | undefined;
90
+ }[];
91
+ detail: string | undefined;
92
+ }[];
93
+ } | undefined;
94
+ broker: {
95
+ nodes: number | undefined;
96
+ defaultCopies: number;
97
+ minInSync: number;
98
+ } | undefined;
99
+ retention: {
100
+ longest: string | undefined;
101
+ unbounded: boolean;
102
+ } | undefined;
103
+ erasure: {
104
+ enabled: boolean;
105
+ keyringInstances: number;
106
+ } | undefined;
79
107
  };
80
108
  hold: {
81
109
  held: boolean;
@@ -2,7 +2,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  /**
3
3
  * The front page: who you are, as the control plane sees you, and the organizations you can see —
4
4
  * yours with your role in each, or every one of them for a platform administrator — and what the
5
- * installation is: its version, and its cloud when it names one (feature 044).
5
+ * installation is: its version, and its cloud when it names one (feature 044), and its broker and
6
+ * longest topic retention (spec 057).
6
7
  */
7
8
  import { redirect, useLoaderData } from "react-router";
8
9
  import { act, guard, pageData, text, useConsoleContext, withShell } from "../context.js";
@@ -50,7 +51,18 @@ export default function Front() {
50
51
  const hostActions = useHostActions("organization.create");
51
52
  const { shows } = useConsole();
52
53
  return (_jsxs(Page, { inspector: hostActions ? (_jsx("div", { className: "ac-ops", children: _jsx(HostActions, { operation: "organization.create" }) })) : null, children: [_jsx("h1", { children: "Organizations" }), _jsxs("p", { className: "ac-lede", children: ["Signed in as ", _jsx("strong", { children: whoami.name ?? whoami.email ?? whoami.subject }), whoami.email && whoami.name ? ` (${whoami.email})` : null, whoami.platformAdmin ? ", a platform administrator: you see every organization in this installation." : "."] }), hold?.held ? (_jsxs("section", { className: "ac-card", "aria-labelledby": "hold-title", "data-held": "yes", children: [_jsx("h2", { id: "hold-title", children: "The control plane is held" }), _jsxs("p", { children: ["Its database was restored to ", hold.targetTime ? when(hold.targetTime) : "an earlier moment", ", and it changes nothing in the cluster until a platform administrator releases it."] }), hold.services.length > 0 ? (_jsx("ul", { className: "ac-topics", "aria-label": "Services that differ", children: hold.services.map((d) => (_jsxs("li", { "data-differs": `${d.project}/${d.service}`, children: [d.project, "/", d.service, ": recorded ", d.recordedImage ?? "nothing", ", the cluster runs ", d.clusterImage ?? "nothing"] }, `${d.project}/${d.service}`))) })) : null, hold.unknownProjects.length > 0 ? _jsxs("p", { children: ["Projects the database does not know: ", hold.unknownProjects.join(", "), "."] }) : null, whoami.platformAdmin && shows("installation.release") ? (_jsx(ConsoleForm, { intent: "release", children: _jsx(Submit, { intent: "release", children: "Release the control plane" }) })) : null, _jsx(Refused, { intent: "release" })] })) : hold?.releasedAt ? (_jsxs("p", { "data-held": "released", children: ["The control plane was released after its database's restore, ", when(hold.releasedAt), hold.releasedBy ? ` by ${hold.releasedBy}` : "", "."] })) : null, backups ? (_jsx("p", { className: "ac-hint", "data-backups": backups.backupTarget, children: backups.notBackedUp ??
53
- `Backups are kept at least ${backups.retentionDays} days${backups.sharesFailureDomain ? ", in the cluster they back up" : ", with a copy outside the cluster"}.` })) : null, organizations.length === 0 ? (_jsxs("p", { className: "ac-empty", children: ["You are not a member of any organization yet. Create one, or ask an owner to invite ", whoami.email ?? "you", "."] })) : (_jsx("div", { className: "ac-card ac-table-wrap", children: _jsxs("table", { className: "ac-table", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Organizations you can see" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Name" }), _jsx("th", { scope: "col", children: "Id" }), _jsx("th", { scope: "col", children: "Your role" }), _jsx("th", { scope: "col", className: "ac-num", children: "Projects" }), _jsx("th", { scope: "col", children: "State" })] }) }), _jsx("tbody", { children: organizations.map((o) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(ConsoleLink, { to: `organizations/${encodeURIComponent(o.id)}`, children: o.name }) }), _jsx("td", { children: o.id }), _jsx("td", { children: o.role ? roleWords[o.role] : "Administrator (not a member)" }), _jsx("td", { className: "ac-num", children: o.projects }), _jsx("td", { children: o.disabled ? "Disabled" : "Active" })] }, o.id))) })] }) })), _jsxs("section", { className: "ac-card", "aria-labelledby": "installation", children: [_jsx("h2", { id: "installation", children: "This installation" }), _jsxs("dl", { className: "ac-facts", children: [_jsx("dt", { children: "Platform version" }), _jsx("dd", { children: installation.platformVersion }), _jsx("dt", { children: "Cloud" }), _jsx("dd", { children: cloud ? `${cloud.provider}, account ${cloud.account}` : "None: everything is served by the installation itself" }), cloud ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Location" }), _jsx("dd", { children: cloud.location })] })) : null, cloud?.kmsKey ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Wrapping key" }), _jsx("dd", { children: cloud.kmsKey })] })) : null, installation.secrets ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Secrets kept in" }), _jsx("dd", { children: secretBackendWords[installation.secrets.backend] ?? installation.secrets.backend }), _jsx("dt", { children: "Record of reads kept for" }), _jsx("dd", { children: installation.secrets.recordRetention }), _jsx("dt", { children: "Access log" }), _jsx("dd", { children: installation.secrets.auditLog })] })) : null] })] })] }));
54
+ `Backups are kept at least ${backups.retentionDays} days${backups.sharesFailureDomain ? ", in the cluster they back up" : ", with a copy outside the cluster"}.` })) : null, organizations.length === 0 ? (_jsxs("p", { className: "ac-empty", children: ["You are not a member of any organization yet. Create one, or ask an owner to invite ", whoami.email ?? "you", "."] })) : (_jsx("div", { className: "ac-card ac-table-wrap", children: _jsxs("table", { className: "ac-table", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Organizations you can see" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Name" }), _jsx("th", { scope: "col", children: "Id" }), _jsx("th", { scope: "col", children: "Your role" }), _jsx("th", { scope: "col", className: "ac-num", children: "Projects" }), _jsx("th", { scope: "col", children: "State" })] }) }), _jsx("tbody", { children: organizations.map((o) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(ConsoleLink, { to: `organizations/${encodeURIComponent(o.id)}`, children: o.name }) }), _jsx("td", { children: o.id }), _jsx("td", { children: o.role ? roleWords[o.role] : "Administrator (not a member)" }), _jsx("td", { className: "ac-num", children: o.projects }), _jsx("td", { children: o.disabled ? "Disabled" : "Active" })] }, o.id))) })] }) })), _jsxs("section", { className: "ac-card", "aria-labelledby": "installation", children: [_jsx("h2", { id: "installation", children: "This installation" }), _jsxs("dl", { className: "ac-facts", children: [_jsx("dt", { children: "Platform version" }), _jsx("dd", { children: installation.platformVersion }), _jsx("dt", { children: "Cloud" }), _jsx("dd", { children: cloud ? `${cloud.provider}, account ${cloud.account}` : "None: everything is served by the installation itself" }), cloud ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Location" }), _jsx("dd", { children: cloud.location })] })) : null, cloud?.kmsKey ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Wrapping key" }), _jsx("dd", { children: cloud.kmsKey })] })) : null, installation.secrets ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Secrets kept in" }), _jsx("dd", { children: secretBackendWords[installation.secrets.backend] ?? installation.secrets.backend }), _jsx("dt", { children: "Record of reads kept for" }), _jsx("dd", { children: installation.secrets.recordRetention }), _jsx("dt", { children: "Access log" }), _jsx("dd", { children: installation.secrets.auditLog })] })) : null, installation.broker ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Broker" }), _jsx("dd", { children: brokerWords(installation.broker) })] })) : null, installation.retention ? (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Longest topic retention" }), _jsx("dd", { children: retentionWords(installation.retention) })] })) : null] })] })] }));
55
+ }
56
+ /** The installation's broker (spec 057), in words; a node count the control plane was not told is said so. */
57
+ function brokerWords(broker) {
58
+ const nodes = broker.nodes === undefined ? "nodes not stated" : `${broker.nodes} ${broker.nodes === 1 ? "node" : "nodes"}`;
59
+ return `${nodes}; a topic gets ${broker.defaultCopies} ${broker.defaultCopies === 1 ? "copy" : "copies"}, ${broker.minInSync} in sync, by default`;
60
+ }
61
+ /** The longest retention a topic may be given (spec 057), in words. */
62
+ function retentionWords(retention) {
63
+ if (retention.longest !== undefined)
64
+ return retention.longest;
65
+ return retention.unbounded ? "Unbounded" : "Not stated";
54
66
  }
55
67
  /** The installation's secret store (feature 038), in words; an unknown backend is shown as it is named. */
56
68
  const secretBackendWords = {
@@ -87,6 +87,16 @@ export declare function loader({ request, params, context }: LoaderFunctionArgs)
87
87
  bucketLocation: string | undefined;
88
88
  softDeleteDays: number | undefined;
89
89
  storageMove: string | undefined;
90
+ buckets: {
91
+ name: string;
92
+ phase: string;
93
+ store: string | undefined;
94
+ bucket: string | undefined;
95
+ exposure: string;
96
+ publicAddress: string | undefined;
97
+ kept: boolean;
98
+ detail: string | undefined;
99
+ }[] | undefined;
90
100
  databaseCluster: string | undefined;
91
101
  grants: string | undefined;
92
102
  crossProjectTopics: {
@@ -120,6 +120,16 @@ export declare function loader({ params, context }: LoaderFunctionArgs): Promise
120
120
  bucketLocation: string | undefined;
121
121
  softDeleteDays: number | undefined;
122
122
  storageMove: string | undefined;
123
+ buckets: {
124
+ name: string;
125
+ phase: string;
126
+ store: string | undefined;
127
+ bucket: string | undefined;
128
+ exposure: string;
129
+ publicAddress: string | undefined;
130
+ kept: boolean;
131
+ detail: string | undefined;
132
+ }[] | undefined;
123
133
  databaseCluster: string | undefined;
124
134
  grants: string | undefined;
125
135
  crossProjectTopics: {
@@ -77,6 +77,6 @@ const words = {
77
77
  export default function ServiceHistory() {
78
78
  const { name, projectId, history } = useLoaderData();
79
79
  const offered = rollbackTargets(history);
80
- return (_jsxs(Page, { children: [_jsxs("div", { className: "ac-head", children: [_jsxs("h1", { children: ["History of ", name] }), _jsx(ServiceSections, { projectId: projectId, name: name, current: "history" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "history", children: [_jsx("h2", { id: "history", children: "History" }), history.length === 0 ? (_jsx("p", { className: "ac-empty", children: "Nothing recorded yet." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "What" }), _jsx("th", { scope: "col", className: "ac-num", children: "Generation" }), _jsx("th", { scope: "col", children: "Image" }), _jsx("th", { scope: "col", children: "Digest" }), _jsx("th", { scope: "col", children: "Who" }), _jsx("th", { scope: "col", children: "When" }), _jsx("th", { scope: "col", "aria-label": "Roll back" })] }) }), _jsx("tbody", { children: history.map((h, i) => (_jsxs("tr", { children: [_jsx("td", { children: h.rolledBackTo !== undefined ? `Rolled back to generation ${h.rolledBackTo}` : (words[h.kind] ?? h.kind) }), _jsx("td", { className: "ac-num", children: h.generation }), _jsx("td", { children: h.image ?? "—" }), _jsx("td", { children: h.digest ? (_jsx(ConsoleLink, { to: `projects/${encodeURIComponent(projectId)}/services/apply?name=${encodeURIComponent(name)}&generation=${h.generation}`, "aria-label": `Generation ${h.generation}'s descriptor`, children: _jsx("code", { title: h.digest, children: h.digest.slice(0, 12) }) })) : ("—") }), _jsxs("td", { children: [h.actor ? (h.actor.display ?? h.actor.subject) : "—", h.actor?.administrative ? " (as administrator)" : ""] }), _jsx("td", { children: when(h.at) }), _jsx("td", { children: offered.has(h.generation) ? _jsx(RollBack, { generation: h.generation, image: h.image, entity: { name, projectId, entry: h } }) : null })] }, i))) })] }) })), _jsx(Refused, { intent: "rollback" })] })] }));
80
+ return (_jsxs(Page, { children: [_jsxs("div", { className: "ac-head", children: [_jsxs("h1", { children: ["History of ", name] }), _jsx(ServiceSections, { projectId: projectId, name: name, current: "history" })] }), _jsxs("section", { className: "ac-card", "aria-labelledby": "history", children: [_jsx("h2", { id: "history", children: "History" }), history.length === 0 ? (_jsx("p", { className: "ac-empty", children: "Nothing recorded yet." })) : (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "What" }), _jsx("th", { scope: "col", className: "ac-num", children: "Generation" }), _jsx("th", { scope: "col", children: "Image" }), _jsx("th", { scope: "col", children: "Digest" }), _jsx("th", { scope: "col", children: "Who" }), _jsx("th", { scope: "col", children: "When" }), _jsx("th", { scope: "col", "aria-label": "Roll back" })] }) }), _jsx("tbody", { children: history.map((h, i) => (_jsxs("tr", { children: [_jsx("td", { children: h.rolledBackTo !== undefined ? `Rolled back to generation ${h.rolledBackTo}` : (words[h.kind] ?? h.kind) }), _jsx("td", { className: "ac-num", children: h.generation }), _jsx("td", { children: h.image ?? "—" }), _jsx("td", { children: h.digest ? (_jsx(ConsoleLink, { to: `projects/${encodeURIComponent(projectId)}/services/apply?name=${encodeURIComponent(name)}&generation=${h.generation}`, "aria-label": `${h.digest.slice(0, 12)}, generation ${h.generation}'s descriptor`, children: _jsx("code", { title: h.digest, children: h.digest.slice(0, 12) }) })) : ("—") }), _jsxs("td", { children: [h.actor ? (h.actor.display ?? h.actor.subject) : "—", h.actor?.administrative ? " (as administrator)" : ""] }), _jsx("td", { children: when(h.at) }), _jsx("td", { children: offered.has(h.generation) ? _jsx(RollBack, { generation: h.generation, image: h.image, entity: { name, projectId, entry: h } }) : null })] }, i))) })] }) })), _jsx(Refused, { intent: "rollback" })] })] }));
81
81
  }
82
82
  export const ErrorBoundary = ConsoleErrorBoundary;
@@ -87,6 +87,16 @@ export declare function loader({ params, context }: LoaderFunctionArgs): Promise
87
87
  bucketLocation: string | undefined;
88
88
  softDeleteDays: number | undefined;
89
89
  storageMove: string | undefined;
90
+ buckets: {
91
+ name: string;
92
+ phase: string;
93
+ store: string | undefined;
94
+ bucket: string | undefined;
95
+ exposure: string;
96
+ publicAddress: string | undefined;
97
+ kept: boolean;
98
+ detail: string | undefined;
99
+ }[] | undefined;
90
100
  databaseCluster: string | undefined;
91
101
  grants: string | undefined;
92
102
  crossProjectTopics: {
@@ -92,6 +92,16 @@ export declare function loader({ params, context }: LoaderFunctionArgs): Promise
92
92
  bucketLocation: string | undefined;
93
93
  softDeleteDays: number | undefined;
94
94
  storageMove: string | undefined;
95
+ buckets: {
96
+ name: string;
97
+ phase: string;
98
+ store: string | undefined;
99
+ bucket: string | undefined;
100
+ exposure: string;
101
+ publicAddress: string | undefined;
102
+ kept: boolean;
103
+ detail: string | undefined;
104
+ }[] | undefined;
95
105
  databaseCluster: string | undefined;
96
106
  grants: string | undefined;
97
107
  crossProjectTopics: {
@@ -12,6 +12,7 @@ import { Select } from "../ui/primitives/select.js";
12
12
  import { Page, SectionTitle, ServiceSections } from "../ui/shell.js";
13
13
  import { Shape } from "../ui/shape.js";
14
14
  import { runsAs } from "../ui/hosting.js";
15
+ import { bucketRows } from "../ui/buckets.js";
15
16
  import { Refused, useRefusal } from "../ui/refused.js";
16
17
  import { Lifecycle } from "../ui/status.js";
17
18
  import { useServiceStream } from "../ui/use-stream.js";
@@ -132,6 +133,6 @@ export default function Service() {
132
133
  const { status: s, state } = useServiceStream(p.id, data.service.name, data.service);
133
134
  const deleteRefused = useRefusal("delete") !== undefined;
134
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" })] })] })] }));
135
- 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.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 })] }));
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 })] }));
136
137
  }
137
138
  export const ErrorBoundary = ConsoleErrorBoundary;
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}}
@@ -204,6 +204,16 @@ interface TopicCheckRecord {
204
204
  stated?: string;
205
205
  state: string;
206
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
+ }
207
217
  interface Service {
208
218
  name: string;
209
219
  projectId: string;
@@ -273,6 +283,8 @@ interface Service {
273
283
  bucketLocation?: string;
274
284
  /** Where a move of its bucket from Garage is, as the control plane phrases it. */
275
285
  storageMove?: string;
286
+ /** Its named buckets as the operator would report them (feature 055), kept ones last. */
287
+ buckets?: BucketRecord[];
276
288
  /** What the instances report when asked for the topology; generated from the service when unset. */
277
289
  topology?: FakeTopology;
278
290
  }
@@ -445,6 +457,8 @@ export interface FakeSeed {
445
457
  /** The store its bucket is in, `garage` or `gcs`. */
446
458
  objectStore?: string;
447
459
  bucketLocation?: string;
460
+ /** Its named buckets as the operator reports them (feature 055). */
461
+ buckets?: BucketRecord[];
448
462
  }[];
449
463
  /** Project secrets, by name and entry: a value each, never listed. */
450
464
  secrets?: {
@@ -165,6 +165,25 @@ export async function fakeControlPlane(options = {}) {
165
165
  s.provisionObjectStorage = d.service?.provisionObjectStorage ?? false;
166
166
  s.exposeObjectStorage = d.service?.exposeObjectStorage ?? false;
167
167
  s.ownObjectStore = (d.service?.env ?? []).some((e) => e.name.startsWith("ANKKA_S3_"));
168
+ // Named buckets (feature 055): one report each, and one the descriptor stopped naming is kept.
169
+ const named = (d.service?.buckets ?? []).map((b) => {
170
+ if (b.supplied)
171
+ return { name: b.name, phase: "Supplied", bucket: b.supplied.bucket, exposure: "supplied" };
172
+ const exposure = b.exposure ?? "private";
173
+ const bucket = `${s.projectId}.${s.name}.${b.name}`;
174
+ return {
175
+ name: b.name,
176
+ phase: "Provisioned",
177
+ store: "garage",
178
+ bucket,
179
+ exposure,
180
+ ...(exposure === "private" ? {} : { publicAddress: `https://storage.${options.baseDomain ?? "example.test"}/${bucket}` }),
181
+ };
182
+ });
183
+ const dropped = (s.buckets ?? [])
184
+ .filter((b) => b.phase !== "Supplied" && !named.some((n) => n.name === b.name))
185
+ .map((b) => ({ ...b, phase: "Kept", kept: true }));
186
+ s.buckets = [...named, ...dropped];
168
187
  if (s.provisionObjectStorage && !s.objectStore) {
169
188
  s.objectStore = objectStore;
170
189
  if (objectStore === "gcs")
@@ -222,6 +241,7 @@ export async function fakeControlPlane(options = {}) {
222
241
  bucketLocation: s.provisionObjectStorage && s.objectStore === "gcs" ? (s.bucketLocation ?? null) : null,
223
242
  softDeleteDays: s.provisionObjectStorage && s.objectStore === "gcs" ? 7 : null,
224
243
  storageMove: s.storageMove ?? null,
244
+ ...(s.buckets && s.buckets.length > 0 ? { buckets: s.buckets } : {}),
225
245
  customHostnames: (s.customHostnames ?? []).map((hostname) => ({
226
246
  hostname,
227
247
  // The fake has no authority to wait for: an exposed service's hostnames serve at once.
@@ -363,6 +383,10 @@ export async function fakeControlPlane(options = {}) {
363
383
  ...(cloud ? { cloud: { ...where, ...(kmsKey && owns ? { kmsKey } : {}) } } : {}),
364
384
  // Feature 038: where secrets are kept, as an installation that has said nothing answers.
365
385
  secrets: { backend: "postgres", recordRetention: "365d", auditLog: "unknown" },
386
+ // Spec 057: a one-node broker and no bound on retention, as the shipped settings say; no
387
+ // erasure, which the platform does not have yet.
388
+ broker: { nodes: 1, defaultCopies: 1, minInSync: 1 },
389
+ retention: { unbounded: true },
366
390
  // Feature 041: where the backups go, as an installation with Garage and no copy says it.
367
391
  backups: {
368
392
  backupTarget: "object-store",
@@ -1827,6 +1851,7 @@ export async function fakeControlPlane(options = {}) {
1827
1851
  ownObjectStore: s.ownObjectStore,
1828
1852
  objectStore: s.objectStore,
1829
1853
  bucketLocation: s.bucketLocation,
1854
+ buckets: s.buckets,
1830
1855
  });
1831
1856
  }
1832
1857
  for (const sec of seed.secrets ?? []) {
@@ -0,0 +1,14 @@
1
+ import type { BucketReport } from "../client/schemas.ts";
2
+ /** One row of a service's named buckets, as the service page shows it (feature 055). */
3
+ export interface BucketRow {
4
+ name: string;
5
+ /** The exposure, `supplied` for a bucket the developer made, or `kept` for one no longer named. */
6
+ exposure: string;
7
+ /** Its name in the store, when reported. */
8
+ bucket: string | null;
9
+ /** Where it is on the internet, as a link, when it is. */
10
+ address: string | null;
11
+ /** Said of a bucket the descriptor no longer names, which is kept rather than deleted. */
12
+ note: string | null;
13
+ }
14
+ export declare function bucketRows(buckets: readonly BucketReport[] | null | undefined): BucketRow[];
@@ -0,0 +1,9 @@
1
+ export function bucketRows(buckets) {
2
+ return (buckets ?? []).map((b) => ({
3
+ name: b.name,
4
+ exposure: b.kept ? "kept" : b.phase === "Supplied" || b.exposure === "supplied" ? "supplied" : b.exposure,
5
+ bucket: b.bucket ?? null,
6
+ address: b.kept ? null : (b.publicAddress ?? null),
7
+ note: b.kept ? "kept, no longer named" : null,
8
+ }));
9
+ }
package/dist/ui/shell.js CHANGED
@@ -15,8 +15,12 @@ export function Shell({ children, className, theme = "dark" }) {
15
15
  export function Backdrop() {
16
16
  return _jsx("div", { className: "ac-backdrop", "aria-hidden": "true" });
17
17
  }
18
+ /**
19
+ * The mark: the duck, as a mask over the current colour, so it follows `--ac-color-ink` and a host's
20
+ * override of it in either theme with one asset. The build copies the image beside the stylesheet.
21
+ */
18
22
  function Mark() {
19
- return (_jsxs("svg", { viewBox: "0 0 36 36", width: "32", height: "32", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M8 24c0-7 5-12 10-12s10 5 10 12", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("path", { d: "M14 24h8", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("circle", { cx: "18", cy: "9", r: "2.2", fill: "var(--ac-color-amber)" })] }));
23
+ return _jsx("span", { className: "ac-mark-image", "aria-hidden": "true" });
20
24
  }
21
25
  const initials = (name) => name
22
26
  .split(/[\s@.]+/)
@@ -38,10 +38,10 @@ export function Graph({ shown, differs, selected, onSelect, }) {
38
38
  .filter(Boolean)
39
39
  .join(" ");
40
40
  const kind = kindInWords(n.kind) + (n.platform ? ", platform" : "") + (differs.has(n.id) ? ", not on every instance" : "");
41
- return (_jsxs("g", { className: classes, transform: `translate(${x} ${y})`, tabIndex: 0, role: "button", "aria-label": `${n.label}: ${kind}`, onClick: () => onSelect(n.id), onKeyDown: (e) => {
41
+ return (_jsxs("g", { className: classes, transform: `translate(${x} ${y})`, tabIndex: 0, role: "button", onClick: () => onSelect(n.id), onKeyDown: (e) => {
42
42
  if (e.key === "Enter" || e.key === " ")
43
43
  onSelect(n.id);
44
- }, children: [_jsx("rect", { width: GRID.width, height: GRID.height, rx: 12, filter: "url(#ac-topology-shadow)" }), _jsx("text", { x: 10, y: 16, className: "ac-topology-name", children: clip(n.label, 22) }), _jsx("text", { x: 10, y: 30, className: "ac-topology-kind", children: clip(kind, 28) })] }, n.id));
44
+ }, children: [_jsx("title", { children: `${n.label}: ${kind}` }), _jsx("rect", { width: GRID.width, height: GRID.height, rx: 12, filter: "url(#ac-topology-shadow)" }), _jsx("text", { x: 10, y: 16, className: "ac-topology-name", "aria-hidden": "true", children: clip(n.label, 22) }), _jsx("text", { x: 10, y: 30, className: "ac-topology-kind", "aria-hidden": "true", children: clip(kind, 28) })] }, n.id));
45
45
  };
46
46
  return (_jsxs("svg", { className: "ac-topology-graph", viewBox: `0 0 ${width} ${height}`, width: width, height: height, role: "group", "aria-label": describe(shown), children: [_jsx("defs", { children: _jsx("filter", { id: "ac-topology-shadow", x: "-20%", y: "-30%", width: "140%", height: "180%", children: _jsx("feDropShadow", { dx: "0", dy: "6", stdDeviation: "8", floodColor: "#05090b", floodOpacity: "0.28" }) }) }), shown.declared.map((e) => {
47
47
  const p = path(e.from, e.to, 0);
@@ -3,6 +3,14 @@
3
3
  * read. Handled and unanswered are separate columns, because they are separate facts.
4
4
  */
5
5
  import type { Pair, View } from "./layout.ts";
6
+ /**
7
+ * A handler as the table names it: a tool, an agent's or an MCP endpoint's, is marked with a tool
8
+ * glyph and the word, so an MCP endpoint's tools read apart from its routes, to a screen reader too.
9
+ */
10
+ export declare function handlerInWords(h: {
11
+ name: string;
12
+ type: string;
13
+ }): string;
6
14
  export declare function duration(pair: Pair): string;
7
15
  export declare function Table({ shown, differs }: {
8
16
  shown: View;
@@ -5,10 +5,17 @@ function nameOf(shown, id) {
5
5
  function kindInWords(kind) {
6
6
  return kind.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
7
7
  }
8
+ /**
9
+ * A handler as the table names it: a tool, an agent's or an MCP endpoint's, is marked with a tool
10
+ * glyph and the word, so an MCP endpoint's tools read apart from its routes, to a screen reader too.
11
+ */
12
+ export function handlerInWords(h) {
13
+ return h.type === "tool" ? `🔧 ${h.name} (tool)` : h.name;
14
+ }
8
15
  export function duration(pair) {
9
16
  const d = pair.durationMillis;
10
17
  return d ? `~${d.p50} / ~${d.p99} / ~${d.max} ms` : "";
11
18
  }
12
19
  export function Table({ shown, differs }) {
13
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Components", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Components" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Name" }), _jsx("th", { scope: "col", children: "Kind" }), _jsx("th", { scope: "col", children: "Handlers" })] }) }), _jsx("tbody", { children: shown.nodes.map((n) => (_jsxs("tr", { "data-node": n.id, children: [_jsx("td", { children: n.label }), _jsxs("td", { children: [kindInWords(n.kind), n.platform ? ", platform" : "", differs.has(n.id) ? `, only on ${differs.get(n.id).join(", ")}` : ""] }), _jsx("td", { children: n.handlers.map((h) => h.name).join(", ") })] }, n.id))) })] }) }), shown.declared.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Declared connections", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Declared connections" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "To" }), _jsx("th", { scope: "col", children: "As" })] }) }), _jsx("tbody", { children: shown.declared.map((e) => (_jsxs("tr", { children: [_jsx("td", { children: nameOf(shown, e.from) }), _jsx("td", { children: nameOf(shown, e.to) }), _jsx("td", { children: e.kind })] }, `${e.from}>${e.to}:${e.kind}`))) })] }) })) : null, shown.calls.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Observed calls", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Observed calls" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "To" }), _jsx("th", { scope: "col", children: "Handler to handler" }), _jsx("th", { scope: "col", className: "ac-num", children: "Ok" }), _jsx("th", { scope: "col", className: "ac-num", children: "Refused" }), _jsx("th", { scope: "col", className: "ac-num", children: "Failed" }), _jsx("th", { scope: "col", className: "ac-num", children: "Timed out" }), _jsx("th", { scope: "col", className: "ac-num", children: "Undelivered" }), _jsx("th", { scope: "col", children: "p50 / p99 / max" })] }) }), _jsx("tbody", { children: shown.calls.flatMap((c) => c.pairs.map((p) => (_jsxs("tr", { "data-pair": `${p.caller} -> ${p.callee}`, children: [_jsx("td", { children: nameOf(shown, c.from) }), _jsx("td", { children: nameOf(shown, c.to) }), _jsxs("td", { children: [p.caller, " \u2192 ", p.callee, p.streaming ? " (stream)" : ""] }), _jsx("td", { className: "ac-num", children: p.handled?.ok ?? 0 }), _jsx("td", { className: "ac-num", children: p.handled?.refused ?? 0 }), _jsx("td", { className: "ac-num", children: p.handled?.failed ?? 0 }), _jsx("td", { className: "ac-num", children: p.unanswered?.timedOut ?? 0 }), _jsx("td", { className: "ac-num", children: p.unanswered?.undelivered ?? 0 }), _jsx("td", { children: duration(p) })] }, `${c.from}>${c.to}:${p.caller}>${p.callee}`)))) })] }) })) : null] }));
20
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Components", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Components" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Name" }), _jsx("th", { scope: "col", children: "Kind" }), _jsx("th", { scope: "col", children: "Handlers" })] }) }), _jsx("tbody", { children: shown.nodes.map((n) => (_jsxs("tr", { "data-node": n.id, children: [_jsx("td", { children: n.label }), _jsxs("td", { children: [kindInWords(n.kind), n.platform ? ", platform" : "", differs.has(n.id) ? `, only on ${differs.get(n.id).join(", ")}` : ""] }), _jsx("td", { children: n.handlers.map(handlerInWords).join(", ") })] }, n.id))) })] }) }), shown.declared.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Declared connections", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Declared connections" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "To" }), _jsx("th", { scope: "col", children: "As" })] }) }), _jsx("tbody", { children: shown.declared.map((e) => (_jsxs("tr", { children: [_jsx("td", { children: nameOf(shown, e.from) }), _jsx("td", { children: nameOf(shown, e.to) }), _jsx("td", { children: e.kind })] }, `${e.from}>${e.to}:${e.kind}`))) })] }) })) : null, shown.calls.length > 0 ? (_jsx("div", { className: "ac-table-wrap", children: _jsxs("table", { className: "ac-table", "aria-label": "Observed calls", children: [_jsx("caption", { className: "ac-visually-hidden", children: "Observed calls" }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "From" }), _jsx("th", { scope: "col", children: "To" }), _jsx("th", { scope: "col", children: "Handler to handler" }), _jsx("th", { scope: "col", className: "ac-num", children: "Ok" }), _jsx("th", { scope: "col", className: "ac-num", children: "Refused" }), _jsx("th", { scope: "col", className: "ac-num", children: "Failed" }), _jsx("th", { scope: "col", className: "ac-num", children: "Timed out" }), _jsx("th", { scope: "col", className: "ac-num", children: "Undelivered" }), _jsx("th", { scope: "col", children: "p50 / p99 / max" })] }) }), _jsx("tbody", { children: shown.calls.flatMap((c) => c.pairs.map((p) => (_jsxs("tr", { "data-pair": `${p.caller} -> ${p.callee}`, children: [_jsx("td", { children: nameOf(shown, c.from) }), _jsx("td", { children: nameOf(shown, c.to) }), _jsxs("td", { children: [p.caller, " \u2192 ", p.callee, p.streaming ? " (stream)" : ""] }), _jsx("td", { className: "ac-num", children: p.handled?.ok ?? 0 }), _jsx("td", { className: "ac-num", children: p.handled?.refused ?? 0 }), _jsx("td", { className: "ac-num", children: p.handled?.failed ?? 0 }), _jsx("td", { className: "ac-num", children: p.unanswered?.timedOut ?? 0 }), _jsx("td", { className: "ac-num", children: p.unanswered?.undelivered ?? 0 }), _jsx("td", { children: duration(p) })] }, `${c.from}>${c.to}:${p.caller}>${p.callee}`)))) })] }) })) : null] }));
14
21
  }
@@ -85,6 +85,16 @@ export declare function useProjectStream(projectId: string, initial: ServiceStat
85
85
  bucketLocation: string | undefined;
86
86
  softDeleteDays: number | undefined;
87
87
  storageMove: string | undefined;
88
+ buckets: {
89
+ name: string;
90
+ phase: string;
91
+ store: string | undefined;
92
+ bucket: string | undefined;
93
+ exposure: string;
94
+ publicAddress: string | undefined;
95
+ kept: boolean;
96
+ detail: string | undefined;
97
+ }[] | undefined;
88
98
  databaseCluster: string | undefined;
89
99
  grants: string | undefined;
90
100
  crossProjectTopics: {
@@ -208,6 +218,16 @@ export declare function useServiceStream(projectId: string, name: string, initia
208
218
  bucketLocation: string | undefined;
209
219
  softDeleteDays: number | undefined;
210
220
  storageMove: string | undefined;
221
+ buckets: {
222
+ name: string;
223
+ phase: string;
224
+ store: string | undefined;
225
+ bucket: string | undefined;
226
+ exposure: string;
227
+ publicAddress: string | undefined;
228
+ kept: boolean;
229
+ detail: string | undefined;
230
+ }[] | undefined;
211
231
  databaseCluster: string | undefined;
212
232
  grants: string | undefined;
213
233
  crossProjectTopics: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ankka-console",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "The ankka console as a package: the control plane client, sign-in and sessions, and the pages as routes a React Router host mounts.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -52,13 +52,13 @@
52
52
  "dependencies": {
53
53
  "class-variance-authority": "^0.7.1",
54
54
  "clsx": "^2.1.1",
55
- "jose": "^6.2.12",
56
- "lucide-react": "^1.52.0",
55
+ "jose": "^6.2.13",
56
+ "lucide-react": "^1.61.0",
57
57
  "openid-client": "^6.8.8",
58
- "zod": "^4.6.5"
58
+ "zod": "^4.6.7"
59
59
  },
60
60
  "peerDependencies": {
61
- "@react-router/dev": "^8.4.0",
61
+ "@react-router/dev": "^8.5.0",
62
62
  "@react-router/node": "^8.4.0",
63
63
  "react": "^19.2.7",
64
64
  "react-dom": "^19.2.7",
@@ -71,17 +71,17 @@
71
71
  },
72
72
  "devDependencies": {
73
73
  "@fontsource-variable/inter": "^5.3.0",
74
- "@react-router/dev": "^8.4.0",
74
+ "@react-router/dev": "^8.5.0",
75
75
  "@react-router/node": "^8.4.0",
76
76
  "@tailwindcss/cli": "^4.3.3",
77
- "@types/node": "^26.6.3",
77
+ "@types/node": "^26.6.5",
78
78
  "@types/react": "^19.3.0",
79
79
  "@types/react-dom": "^19.3.0",
80
- "react": "^19.3.0",
81
- "react-dom": "^19.3.0",
80
+ "react": "^19.3.1",
81
+ "react-dom": "^19.3.1",
82
82
  "react-router": "^8.4.0",
83
83
  "tailwindcss": "^4.3.3",
84
84
  "typescript": "^7.0.2",
85
- "vite": "^8.3.1"
85
+ "vite": "^8.3.5"
86
86
  }
87
87
  }