@braidlabs/console 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -10
- package/package.json +10 -4
- package/src/app/auth.d.ts +46 -0
- package/src/app/auth.js +80 -0
- package/src/app/auth.js.map +1 -0
- package/src/app/console-app.d.ts +8 -6
- package/src/app/console-app.js +91 -20
- package/src/app/console-app.js.map +1 -1
- package/src/app/main.js +25 -2
- package/src/app/main.js.map +1 -1
- package/src/app/routes.d.ts +14 -0
- package/src/app/routes.js +23 -0
- package/src/app/routes.js.map +1 -0
- package/src/index.d.ts +15 -2
- package/src/index.js +10 -2
- package/src/index.js.map +1 -1
- package/src/lib/client.d.ts +52 -1
- package/src/lib/client.js +48 -0
- package/src/lib/client.js.map +1 -1
- package/src/lib/draft.d.ts +1 -1
- package/src/lib/draft.js +2 -2
- package/src/lib/draft.js.map +1 -1
- package/src/lib/empty-state.d.ts +15 -0
- package/src/lib/empty-state.js +38 -0
- package/src/lib/empty-state.js.map +1 -0
- package/src/lib/history-panel.d.ts +19 -0
- package/src/lib/history-panel.js +85 -0
- package/src/lib/history-panel.js.map +1 -0
- package/src/lib/ownership.d.ts +15 -0
- package/src/lib/ownership.js +21 -0
- package/src/lib/ownership.js.map +1 -0
- package/src/lib/query.d.ts +47 -0
- package/src/lib/query.js +121 -0
- package/src/lib/query.js.map +1 -0
- package/src/lib/registry-console.d.ts +13 -2
- package/src/lib/registry-console.js +15 -11
- package/src/lib/registry-console.js.map +1 -1
- package/src/lib/registry-editor.js +20 -7
- package/src/lib/registry-editor.js.map +1 -1
- package/src/lib/shell-codegen.d.ts +28 -0
- package/src/lib/shell-codegen.js +316 -0
- package/src/lib/shell-codegen.js.map +1 -0
- package/src/lib/shells-panel.d.ts +43 -0
- package/src/lib/shells-panel.js +307 -0
- package/src/lib/shells-panel.js.map +1 -0
- package/src/lib/styles.d.ts +1 -1
- package/src/lib/styles.js +72 -4
- package/src/lib/styles.js.map +1 -1
- package/src/lib/tag-chip.d.ts +8 -0
- package/src/lib/tag-chip.js +12 -0
- package/src/lib/tag-chip.js.map +1 -0
- package/src/lib/tags.d.ts +10 -0
- package/src/lib/tags.js +53 -0
- package/src/lib/tags.js.map +1 -0
- package/src/lib/topology-graph.d.ts +5 -2
- package/src/lib/topology-graph.js +17 -8
- package/src/lib/topology-graph.js.map +1 -1
- package/src/lib/topology.d.ts +18 -3
- package/src/lib/topology.js +42 -3
- package/src/lib/topology.js.map +1 -1
package/src/lib/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DiscoveryEntry, FragmentManifest } from '@braidlabs/gateway';
|
|
2
|
-
import type { DescriptorNote, RegistryDiff, RegistryFinding, SnapshotRef } from '@braidlabs/registry';
|
|
2
|
+
import type { DescriptorNote, RegistryDiff, RegistryFinding, RegistryPermissions, ShellManifest, SnapshotRef } from '@braidlabs/registry';
|
|
3
3
|
/**
|
|
4
4
|
* How the console reaches a gateway.
|
|
5
5
|
*
|
|
@@ -54,6 +54,9 @@ export interface HeadState {
|
|
|
54
54
|
id: string;
|
|
55
55
|
createdAt: string;
|
|
56
56
|
manifests: FragmentManifest[];
|
|
57
|
+
/** Absent when the snapshot has no shells, and from servers that predate them. */
|
|
58
|
+
shells?: ShellManifest[];
|
|
59
|
+
labels?: Record<string, string>;
|
|
57
60
|
} | null;
|
|
58
61
|
}
|
|
59
62
|
export interface PublishOutcome {
|
|
@@ -66,6 +69,11 @@ export interface PublishOutcome {
|
|
|
66
69
|
descriptorNotes: DescriptorNote[];
|
|
67
70
|
pinned: boolean;
|
|
68
71
|
diff?: RegistryDiff;
|
|
72
|
+
/** Set when the registry proposes changes as pull requests (GitOps) rather than publishing them. */
|
|
73
|
+
pullRequest?: {
|
|
74
|
+
url: string;
|
|
75
|
+
number: number;
|
|
76
|
+
};
|
|
69
77
|
}
|
|
70
78
|
/** The currently pinned snapshot, or `{ id: null }` when nothing has been published. */
|
|
71
79
|
export declare function fetchHead(api?: ConsoleApi, signal?: AbortSignal): Promise<HeadState>;
|
|
@@ -81,9 +89,52 @@ export declare function listSnapshots(api?: ConsoleApi, signal?: AbortSignal): P
|
|
|
81
89
|
*/
|
|
82
90
|
export declare function publishSnapshot(api: ConsoleApi, body: {
|
|
83
91
|
manifests: FragmentManifest[];
|
|
92
|
+
/** Omit to keep HEAD's shells; `[]` removes them. */
|
|
93
|
+
shells?: ShellManifest[];
|
|
84
94
|
labels?: Record<string, string>;
|
|
85
95
|
pin?: boolean;
|
|
96
|
+
/** The id the draft started from; the server refuses with 409 if HEAD has moved since. */
|
|
97
|
+
baseId?: string | null;
|
|
86
98
|
}): Promise<PublishOutcome>;
|
|
99
|
+
/**
|
|
100
|
+
* What the signed-in caller may do, so the UI offers only what will work. The server still
|
|
101
|
+
* decides every request; this is advice for drawing buttons.
|
|
102
|
+
*
|
|
103
|
+
* A server too old to have the endpoint answers 404, and that reads as "everything allowed": the
|
|
104
|
+
* server will refuse what it refuses, which is the behavior such a server always had.
|
|
105
|
+
*/
|
|
106
|
+
export declare function fetchPermissions(api?: ConsoleApi, signal?: AbortSignal): Promise<RegistryPermissions>;
|
|
107
|
+
/** An environment as `GET /environments` describes it. */
|
|
108
|
+
export interface EnvironmentState {
|
|
109
|
+
name: string;
|
|
110
|
+
from: string | null;
|
|
111
|
+
approvals: number;
|
|
112
|
+
id: string | null;
|
|
113
|
+
pending: {
|
|
114
|
+
id: string;
|
|
115
|
+
base: string | null;
|
|
116
|
+
requestedBy: string;
|
|
117
|
+
requestedAt: string;
|
|
118
|
+
} | null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The registry's environments. A server with none — or too old to have them — answers 404, which
|
|
122
|
+
* reads as an empty list: there is nothing to promote to.
|
|
123
|
+
*/
|
|
124
|
+
export declare function fetchEnvironments(api?: ConsoleApi, signal?: AbortSignal): Promise<EnvironmentState[]>;
|
|
125
|
+
/** Promotes a snapshot into an environment; `pending` when the environment needs approval. */
|
|
126
|
+
export declare function promoteSnapshot(api: ConsoleApi, environment: string, id: string): Promise<{
|
|
127
|
+
pending?: unknown;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Approves the promotion the approver was shown. The server refuses (409) if what is pending is
|
|
131
|
+
* not that one any more, so an approval cannot land on a request swapped in after review.
|
|
132
|
+
*/
|
|
133
|
+
export declare function approvePromotion(api: ConsoleApi, environment: string, shown: {
|
|
134
|
+
id: string;
|
|
135
|
+
requestedAt: string;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
export declare function rejectPromotion(api: ConsoleApi, environment: string): Promise<void>;
|
|
87
138
|
/** Re-points HEAD. Rollback is this and nothing else. */
|
|
88
139
|
export declare function pinSnapshot(api: ConsoleApi, id: string): Promise<void>;
|
|
89
140
|
export declare class RegistryApiError extends Error {
|
package/src/lib/client.js
CHANGED
|
@@ -71,6 +71,54 @@ export async function listSnapshots(api = {}, signal) {
|
|
|
71
71
|
export async function publishSnapshot(api, body) {
|
|
72
72
|
return apiRequest(api, '/snapshots', { method: 'POST', body: JSON.stringify(body) });
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* What the signed-in caller may do, so the UI offers only what will work. The server still
|
|
76
|
+
* decides every request; this is advice for drawing buttons.
|
|
77
|
+
*
|
|
78
|
+
* A server too old to have the endpoint answers 404, and that reads as "everything allowed": the
|
|
79
|
+
* server will refuse what it refuses, which is the behavior such a server always had.
|
|
80
|
+
*/
|
|
81
|
+
export async function fetchPermissions(api = {}, signal) {
|
|
82
|
+
try {
|
|
83
|
+
return await apiRequest(api, '/permissions', { method: 'GET' }, signal);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof RegistryApiError && error.status === 404)
|
|
87
|
+
return { read: true, publish: true, pin: true, actor: null, tags: null };
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The registry's environments. A server with none — or too old to have them — answers 404, which
|
|
93
|
+
* reads as an empty list: there is nothing to promote to.
|
|
94
|
+
*/
|
|
95
|
+
export async function fetchEnvironments(api = {}, signal) {
|
|
96
|
+
try {
|
|
97
|
+
return (await apiRequest(api, '/environments', { method: 'GET' }, signal)).items;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error instanceof RegistryApiError && error.status === 404)
|
|
101
|
+
return [];
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Promotes a snapshot into an environment; `pending` when the environment needs approval. */
|
|
106
|
+
export async function promoteSnapshot(api, environment, id) {
|
|
107
|
+
return apiRequest(api, `/environments/${encodeURIComponent(environment)}/promote`, { method: 'POST', body: JSON.stringify({ id }) });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Approves the promotion the approver was shown. The server refuses (409) if what is pending is
|
|
111
|
+
* not that one any more, so an approval cannot land on a request swapped in after review.
|
|
112
|
+
*/
|
|
113
|
+
export async function approvePromotion(api, environment, shown) {
|
|
114
|
+
await apiRequest(api, `/environments/${encodeURIComponent(environment)}/approve`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
body: JSON.stringify({ id: shown.id, requestedAt: shown.requestedAt }),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export async function rejectPromotion(api, environment) {
|
|
120
|
+
await apiRequest(api, `/environments/${encodeURIComponent(environment)}/reject`, { method: 'POST', body: '{}' });
|
|
121
|
+
}
|
|
74
122
|
/** Re-points HEAD. Rollback is this and nothing else. */
|
|
75
123
|
export async function pinSnapshot(api, id) {
|
|
76
124
|
await apiRequest(api, '/head', { method: 'POST', body: JSON.stringify({ id }) });
|
package/src/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/client.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAU1D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB,EAAE,EAAE,MAAoB;IAC5E,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,IAAI,sBAAsB,CAAC;IAEzD,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,IAA+B,CAAC;IAEpC,GAAG,CAAC;QACF,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,SAAS,IAAI,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;YAClC,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;YACvE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAErE,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAkB,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,CAAC;QACV,uDAAuD;IACzD,CAAC,QAAQ,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,GAAG,IAAI,EAAE;IAEjE,OAAO;QACL,OAAO;QACP,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,OAAO,CAAC,MAAM;QACpC,UAAU,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI;QACrC,eAAe,EAAE,IAAI,EAAE,eAAe,IAAI,SAAS;KACpD,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC;IACA;IAFX,YACW,GAAW,EACX,MAAc;QAEvB,KAAK,CACH,MAAM,KAAK,GAAG;YACZ,CAAC,CAAC,kBAAkB,GAAG,oGAAoG;YAC3H,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;gBAChC,CAAC,CAAC,0CAA0C,GAAG,GAAG;gBAClD,CAAC,CAAC,2BAA2B,GAAG,qBAAqB,MAAM,GAAG,CACnE,CAAC;QATO,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAQ;QASvB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,KAAK,UAAU,cAAc,CAAC,GAAe;IAC3C,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAwBxD,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAkB,EAAE,EAAE,MAAoB;IACxE,OAAO,UAAU,CAAY,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB,EAAE,EAAE,MAAoB;IAC5E,OAAO,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAe,EACf,IAQC;IAED,OAAO,UAAU,CAAiB,GAAG,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAkB,EAAE,EAAE,MAAoB;IAC/E,IAAI,CAAC;QACH,OAAO,MAAM,UAAU,CAAsB,GAAG,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,gBAAgB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACxI,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAWD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAkB,EAAE,EAAE,MAAoB;IAChF,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,UAAU,CAAgC,GAAG,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAClH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,gBAAgB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,EAAE,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAe,EAAE,WAAmB,EAAE,EAAU;IACpF,OAAO,UAAU,CAAC,GAAG,EAAE,iBAAiB,kBAAkB,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;AACvI,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAe,EAAE,WAAmB,EAAE,KAA0C;IACrH,MAAM,UAAU,CAAC,GAAG,EAAE,iBAAiB,kBAAkB,CAAC,WAAW,CAAC,UAAU,EAAE;QAChF,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;KACvE,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAe,EAAE,WAAmB;IACxE,MAAM,UAAU,CAAC,GAAG,EAAE,iBAAiB,kBAAkB,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACnH,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAe,EAAE,EAAU;IAC3D,MAAM,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAG9B;IACA;IACA;IAJX,YACE,OAAe,EACN,MAAc,EACd,WAA8B,EAAE,EAChC,kBAAoC,EAAE;QAE/C,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAwB;QAChC,oBAAe,GAAf,eAAe,CAAuB;QAG/C,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,KAAK,UAAU,UAAU,CAAI,GAAe,EAAE,KAAa,EAAE,IAAiB,EAAE,MAAoB;IAClG,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,OAAO,IAAI,gBAAgB,GAAG,KAAK,EAAE,CAAC;IAEhE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;QAClC,GAAG,IAAI;QACP,OAAO,EAAE;YACP,MAAM,EAAE,kBAAkB;YAC1B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;SAC/B;QACD,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAA4B,CAAC;IAElF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,gBAAgB,CACxB,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,QAAQ,CAAC,MAAM,SAAS,GAAG,EAAE,EACnF,QAAQ,CAAC,MAAM,EACd,IAAI,CAAC,QAA8B,IAAI,EAAE,EACzC,IAAI,CAAC,eAAoC,IAAI,EAAE,CACjD,CAAC;IACJ,CAAC;IAED,OAAO,IAAS,CAAC;AACnB,CAAC"}
|
package/src/lib/draft.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface DraftStatus {
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function draftStatus(draft: Draft): DraftStatus;
|
|
38
38
|
export declare function updateFragment(draft: Draft, index: number, patch: Partial<FragmentManifest>): Draft;
|
|
39
|
-
export declare function addFragment(draft: Draft): Draft;
|
|
39
|
+
export declare function addFragment(draft: Draft, tags?: string[]): Draft;
|
|
40
40
|
export declare function removeFragment(draft: Draft, index: number): Draft;
|
|
41
41
|
/** Discards every edit, returning to the snapshot the draft branched from. */
|
|
42
42
|
export declare function resetDraft(draft: Draft): Draft;
|
package/src/lib/draft.js
CHANGED
|
@@ -23,10 +23,10 @@ export function updateFragment(draft, index, patch) {
|
|
|
23
23
|
const manifests = draft.manifests.map((manifest, position) => position === index ? prune({ ...manifest, ...patch }) : manifest);
|
|
24
24
|
return { ...draft, manifests };
|
|
25
25
|
}
|
|
26
|
-
export function addFragment(draft) {
|
|
26
|
+
export function addFragment(draft, tags) {
|
|
27
27
|
return {
|
|
28
28
|
...draft,
|
|
29
|
-
manifests: [...draft.manifests, { id: uniqueId(draft.manifests), endpoint: '' }],
|
|
29
|
+
manifests: [...draft.manifests, { id: uniqueId(draft.manifests), endpoint: '', ...(tags ? { tags } : {}) }],
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export function removeFragment(draft, index) {
|
package/src/lib/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/draft.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAA2C,MAAM,qBAAqB,CAAC;AAqBhH,MAAM,UAAU,WAAW,CAAC,IAAiC,EAAE,SAAwB,IAAI;IACzF,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;AAC/D,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAEzD,OAAO;QACL,QAAQ;QACR,IAAI;QACJ,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;QACjE,KAAK,EAAE,IAAI,CAAC,SAAS;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,KAAa,EAAE,KAAgC;IAC1F,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAC3D,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CACjE,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY;
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/draft.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAA2C,MAAM,qBAAqB,CAAC;AAqBhH,MAAM,UAAU,WAAW,CAAC,IAAiC,EAAE,SAAwB,IAAI;IACzF,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;AAC/D,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAEzD,OAAO;QACL,QAAQ;QACR,IAAI;QACJ,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;QACjE,KAAK,EAAE,IAAI,CAAC,SAAS;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,KAAa,EAAE,KAAgC;IAC1F,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAC3D,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CACjE,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,IAAe;IACvD,OAAO;QACL,GAAG,KAAK;QACR,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;KAC5G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,KAAa;IACxD,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,KAAK,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,QAA0B;IACvC,MAAM,MAAM,GAA4B,EAAE,GAAG,QAAQ,EAAE,CAAC;IAExD,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GACT,KAAK,KAAK,SAAS;YACnB,KAAK,KAAK,EAAE;YACZ,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;YAC5C,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;QAC5D,6FAA6F;QAC7F,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,UAAU;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,MAAqC,CAAC;AAC/C,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAoC;IAC7D,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,QAAQ,CAAC,SAAsC;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,SAAS,GAAG,cAAc,CAAC;IAC/B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,SAAS,GAAG,gBAAgB,MAAM,EAAE,EAAE,CAAC;IACpE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,KAAK,CAAC,SAAsC;IACnD,gGAAgG;IAChG,mFAAmF;IACnF,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to say when there is nothing to show — which is usually a setup step, not a failure.
|
|
3
|
+
*
|
|
4
|
+
* A 404 from the write API means it is not mounted, the common and safe case: the console is
|
|
5
|
+
* read-only until someone decides otherwise. A 401 or 403 means the caller is not allowed, which
|
|
6
|
+
* is the server doing its job. Neither deserves a red error box, and both deserve the next step.
|
|
7
|
+
*/
|
|
8
|
+
export type EmptyKind = 'missing-api' | 'signed-out' | 'forbidden' | 'empty';
|
|
9
|
+
export declare function emptyKindOf(error: unknown): EmptyKind | null;
|
|
10
|
+
export declare function EmptyState({ kind, subject, onCreate, onRetry, }: {
|
|
11
|
+
kind: EmptyKind;
|
|
12
|
+
subject?: string;
|
|
13
|
+
onCreate?: () => void;
|
|
14
|
+
onRetry?: () => void;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RegistryApiError } from './client.js';
|
|
3
|
+
export function emptyKindOf(error) {
|
|
4
|
+
if (!(error instanceof RegistryApiError))
|
|
5
|
+
return null;
|
|
6
|
+
if (error.status === 404)
|
|
7
|
+
return 'missing-api';
|
|
8
|
+
if (error.status === 401)
|
|
9
|
+
return 'signed-out';
|
|
10
|
+
if (error.status === 403)
|
|
11
|
+
return 'forbidden';
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const MOUNT_SNIPPET = `import { createRegistryApi } from '@braidlabs/registry';
|
|
15
|
+
import { fileSnapshotStore } from '@braidlabs/registry/node';
|
|
16
|
+
import { createOidcAuthorizer } from '@braidlabs/registry/oidc';
|
|
17
|
+
|
|
18
|
+
const registryApi = createRegistryApi({
|
|
19
|
+
store: fileSnapshotStore({ directory: '/var/braid/registry' }),
|
|
20
|
+
...createOidcAuthorizer({
|
|
21
|
+
issuer: 'https://example.okta.com/oauth2/default',
|
|
22
|
+
audience: 'api://braid-console',
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
// Beside the gateway: answer registryApi.handle(request) first — null means "not an API route".`;
|
|
26
|
+
export function EmptyState({ kind, subject = 'shells', onCreate, onRetry, }) {
|
|
27
|
+
switch (kind) {
|
|
28
|
+
case 'missing-api':
|
|
29
|
+
return (_jsxs("div", { className: "braid-console__emptystate", role: "status", children: [_jsx("h3", { children: "The registry API is not mounted" }), _jsxs("p", { children: ["Viewing works without it. Editing ", subject, " needs the write API, behind your identity provider. Until it is mounted this console is read-only, which is safe."] }), _jsx("pre", { className: "braid-console__code", children: MOUNT_SNIPPET }), onRetry && (_jsx("button", { type: "button", className: "braid-console__ghost", onClick: onRetry, children: "Check again" }))] }));
|
|
30
|
+
case 'signed-out':
|
|
31
|
+
return (_jsxs("div", { className: "braid-console__emptystate", role: "status", children: [_jsxs("h3", { children: ["Sign in to see ", subject] }), _jsx("p", { children: "The registry API needs a valid token. Your session may have expired. Sign in again to continue." }), onRetry && (_jsx("button", { type: "button", className: "braid-console__ghost", onClick: onRetry, children: "Try again" }))] }));
|
|
32
|
+
case 'forbidden':
|
|
33
|
+
return (_jsxs("div", { className: "braid-console__emptystate", role: "status", children: [_jsxs("h3", { children: ["Your role cannot read ", subject] }), _jsxs("p", { children: ["You are signed in, but none of your groups or scopes grants ", _jsx("code", { children: "read" }), ". Ask whoever runs the registry to add you to a group in its role map."] })] }));
|
|
34
|
+
case 'empty':
|
|
35
|
+
return (_jsxs("div", { className: "braid-console__emptystate", role: "status", children: [_jsxs("h3", { children: ["No ", subject, " yet"] }), _jsx("p", { children: "A shell is a page as data: its routes, a grid layout typed as ASCII art, and the fragment in each area. Publishing mints a new snapshot. The previous one stays, so you can always go back." }), onCreate && (_jsx("button", { type: "button", className: "braid-console__retry", onClick: onCreate, children: "Create your first shell" }))] }));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=empty-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/empty-state.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAW/C,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,CAAC,KAAK,YAAY,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,aAAa,CAAC;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,YAAY,CAAC;IAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,WAAW,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,aAAa,GAAG;;;;;;;;;;;iGAW2E,CAAC;AAElG,MAAM,UAAU,UAAU,CAAC,EACzB,IAAI,EACJ,OAAO,GAAG,QAAQ,EAClB,QAAQ,EACR,OAAO,GAMR;IACC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,OAAO,CACL,eAAK,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAC,QAAQ,aACtD,2DAAwC,EACxC,8DACqC,OAAO,0HAExC,EACJ,cAAK,SAAS,EAAC,qBAAqB,YAAE,aAAa,GAAO,EACzD,OAAO,IAAI,CACV,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,EAAC,OAAO,EAAE,OAAO,4BAE9D,CACV,IACG,CACP,CAAC;QACJ,KAAK,YAAY;YACf,OAAO,CACL,eAAK,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAC,QAAQ,aACtD,4CAAoB,OAAO,IAAM,EACjC,0HAAsG,EACrG,OAAO,IAAI,CACV,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,EAAC,OAAO,EAAE,OAAO,0BAE9D,CACV,IACG,CACP,CAAC;QACJ,KAAK,WAAW;YACd,OAAO,CACL,eAAK,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAC,QAAQ,aACtD,mDAA2B,OAAO,IAAM,EACxC,wFAC8D,kCAAiB,8EAE3E,IACA,CACP,CAAC;QACJ,KAAK,OAAO;YACV,OAAO,CACL,eAAK,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAC,QAAQ,aACtD,gCAAQ,OAAO,YAAU,EACzB,sNAGI,EACH,QAAQ,IAAI,CACX,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,EAAC,OAAO,EAAE,QAAQ,wCAE/D,CACV,IACG,CACP,CAAC;IACN,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ConsoleApi } from './client.js';
|
|
2
|
+
export interface HistoryPanelProps {
|
|
3
|
+
api?: ConsoleApi;
|
|
4
|
+
theme?: 'light' | 'dark';
|
|
5
|
+
className?: string;
|
|
6
|
+
/** Called after HEAD moves, for hosts that want to refresh other views. */
|
|
7
|
+
onPinned?: (id: string) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Published snapshots, newest first, and who published each.
|
|
11
|
+
*
|
|
12
|
+
* **This is rollback.** Snapshots are immutable, so going back is pinning an earlier id — there is
|
|
13
|
+
* no undo log to replay and nothing to reconstruct. Deleting a shell or a fragment is equally safe
|
|
14
|
+
* for the same reason: the snapshot before the delete still has it.
|
|
15
|
+
*
|
|
16
|
+
* `publishedBy` is shown only when the server stamped it from a verified identity; a client-claimed
|
|
17
|
+
* label would be a note, not a record, and the server's `identify` hook is what makes it the latter.
|
|
18
|
+
*/
|
|
19
|
+
export declare function HistoryPanel({ api, theme, className, onPinned }: HistoryPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { approvePromotion, fetchEnvironments, fetchPermissions, listSnapshots, pinSnapshot, promoteSnapshot, rejectPromotion, } from './client.js';
|
|
4
|
+
import { EmptyState, emptyKindOf } from './empty-state.js';
|
|
5
|
+
import { ensureStyles } from './styles.js';
|
|
6
|
+
/**
|
|
7
|
+
* Published snapshots, newest first, and who published each.
|
|
8
|
+
*
|
|
9
|
+
* **This is rollback.** Snapshots are immutable, so going back is pinning an earlier id — there is
|
|
10
|
+
* no undo log to replay and nothing to reconstruct. Deleting a shell or a fragment is equally safe
|
|
11
|
+
* for the same reason: the snapshot before the delete still has it.
|
|
12
|
+
*
|
|
13
|
+
* `publishedBy` is shown only when the server stamped it from a verified identity; a client-claimed
|
|
14
|
+
* label would be a note, not a record, and the server's `identify` hook is what makes it the latter.
|
|
15
|
+
*/
|
|
16
|
+
export function HistoryPanel({ api, theme, className, onPinned }) {
|
|
17
|
+
const [state, setState] = useState({ status: 'loading' });
|
|
18
|
+
const [pinning, setPinning] = useState(null);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
21
|
+
const apiKey = `${api?.baseUrl ?? ''}|${api?.apiPath ?? ''}`;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
ensureStyles();
|
|
24
|
+
}, []);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
let current = true;
|
|
28
|
+
setState({ status: 'loading' });
|
|
29
|
+
Promise.all([listSnapshots(api, controller.signal), fetchPermissions(api, controller.signal), fetchEnvironments(api, controller.signal)]).then(([{ items, head }, permissions, environments]) => current && setState({ status: 'ready', items, head, permissions, environments }), (reason) => {
|
|
30
|
+
if (!current || controller.signal.aborted)
|
|
31
|
+
return;
|
|
32
|
+
const kind = emptyKindOf(reason);
|
|
33
|
+
setState(kind ? { status: 'empty', kind } : { status: 'failed', error: reason instanceof Error ? reason : new Error(String(reason)) });
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
current = false;
|
|
37
|
+
controller.abort();
|
|
38
|
+
};
|
|
39
|
+
// apiKey stands in for the api object, which is usually a fresh literal each render
|
|
40
|
+
}, [apiKey, reloadToken]);
|
|
41
|
+
const reload = useCallback(() => setReloadToken((token) => token + 1), []);
|
|
42
|
+
/** Runs one write at a time, reporting its error in the panel and reloading on success. */
|
|
43
|
+
const act = async (key, write) => {
|
|
44
|
+
setPinning(key);
|
|
45
|
+
setError(null);
|
|
46
|
+
try {
|
|
47
|
+
await write();
|
|
48
|
+
reload();
|
|
49
|
+
}
|
|
50
|
+
catch (reason) {
|
|
51
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setPinning(null);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const pin = (id) => act(id, async () => {
|
|
58
|
+
await pinSnapshot(api ?? {}, id);
|
|
59
|
+
onPinned?.(id);
|
|
60
|
+
});
|
|
61
|
+
return (_jsx("div", { className: `braid-console${className ? ` ${className}` : ''}`, ...(theme ? { 'data-theme': theme } : {}), children: state.status === 'loading' ? (_jsx("p", { className: "braid-console__empty", children: "Loading history\u2026" })) : state.status === 'empty' ? (_jsx(EmptyState, { kind: state.kind, subject: "history", onRetry: reload })) : state.status === 'failed' ? (_jsxs("div", { className: "braid-console__error", role: "alert", children: [_jsx("h3", { children: "Could not load history" }), _jsx("p", { children: state.error.message }), _jsx("button", { className: "braid-console__retry", type: "button", onClick: reload, children: "Try again" })] })) : (_jsxs("div", { className: "braid-console__shell", children: [_jsxs("div", { className: "braid-console__header", children: [_jsx("h2", { className: "braid-console__title", children: "History" }), _jsx("span", { className: "braid-console__count", children: state.items.length })] }), _jsx("p", { className: "braid-console__notice braid-console__notice--quiet", children: "To roll back, pin an earlier snapshot. A gateway that follows HEAD with a refresh interval serves the new pin within that interval; one without it picks the pin up on its next restart or deploy." }), error && (_jsx("p", { className: "braid-console__barstate--error", role: "alert", children: error })), state.environments
|
|
62
|
+
.filter((environment) => environment.pending)
|
|
63
|
+
.map((environment) => {
|
|
64
|
+
const pending = environment.pending;
|
|
65
|
+
const rights = state.permissions.environments?.[environment.name];
|
|
66
|
+
const isRequester = state.permissions.actor !== null && state.permissions.actor === pending.requestedBy;
|
|
67
|
+
return (_jsxs("div", { className: "braid-console__notice", role: "status", children: [_jsx("strong", { children: pending.requestedBy }), " asked to promote", ' ', _jsx("span", { className: "braid-console__mono", children: pending.id }), " to ", _jsx("strong", { children: environment.name }), ".", ' ', rights?.approve && !isRequester && (_jsx("button", { type: "button", className: "braid-console__ghost", disabled: pinning !== null, onClick: () => void act(`approve:${environment.name}`, () => approvePromotion(api ?? {}, environment.name, pending)), children: "Approve" })), (rights?.approve || isRequester) && (_jsx("button", { type: "button", className: "braid-console__ghost", disabled: pinning !== null, onClick: () => void act(`reject:${environment.name}`, () => rejectPromotion(api ?? {}, environment.name)), children: isRequester ? 'Withdraw' : 'Reject' })), isRequester && !rights?.approve && _jsx("span", { className: "braid-console__desc", children: " Waiting for someone else to approve." })] }, environment.name));
|
|
68
|
+
}), state.items.length === 0 ? (_jsx("p", { className: "braid-console__empty", children: "Nothing has been published yet." })) : (_jsxs("table", { className: "braid-console__table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Snapshot" }), _jsx("th", { scope: "col", children: "Published" }), _jsx("th", { scope: "col", children: "By" }), _jsx("th", { scope: "col", children: "Fragments" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "braid-console__sronly", children: "Actions" }) })] }) }), _jsx("tbody", { children: state.items.map((item) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("span", { className: "braid-console__mono", children: item.id }), item.id === state.head && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "braid-console__badge braid-console__badge--loadable", children: "HEAD" })] })), state.environments
|
|
69
|
+
.filter((environment) => environment.id === item.id)
|
|
70
|
+
.map((environment) => (_jsxs("span", { children: [' ', _jsx("span", { className: "braid-console__badge", children: environment.name })] }, environment.name)))] }), _jsx("td", { children: _jsx("time", { dateTime: item.createdAt, children: new Date(item.createdAt).toLocaleString() }) }), _jsx("td", { children: item.labels?.['publishedBy'] ?? _jsx("span", { className: "braid-console__desc", children: "not recorded" }) }), _jsx("td", { children: item.fragmentCount }), _jsxs("td", { children: [promotableTo(item.id, state).map((environment) => (_jsx("button", { type: "button", className: "braid-console__ghost", disabled: pinning !== null, onClick: () => void act(`promote:${environment.name}:${item.id}`, () => promoteSnapshot(api ?? {}, environment.name, item.id)), "aria-label": `Promote ${item.id} to ${environment.name}`, children: pinning === `promote:${environment.name}:${item.id}` ? 'Promoting…' : `Promote to ${environment.name}` }, environment.name))), item.id !== state.head && state.permissions.pin && (_jsx("button", { type: "button", className: "braid-console__ghost", disabled: pinning !== null, onClick: () => void pin(item.id), "aria-label": `Pin ${item.id} (roll back to it)`, children: pinning === item.id ? 'Pinning…' : 'Pin' }))] })] }, item.id))) })] }))] })) }));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The environments a snapshot can be promoted into from this row: the caller may promote there, it
|
|
74
|
+
* is not already there, nothing is waiting for approval there, and — for an environment with a
|
|
75
|
+
* source — it is what the source holds. (A rollback to an earlier one is the server's to allow;
|
|
76
|
+
* the row offers the forward path.)
|
|
77
|
+
*/
|
|
78
|
+
function promotableTo(id, state) {
|
|
79
|
+
const refOf = (name) => state.environments.find((environment) => environment.name === name)?.id ?? null;
|
|
80
|
+
return state.environments.filter((environment) => state.permissions.environments?.[environment.name]?.promote &&
|
|
81
|
+
environment.id !== id &&
|
|
82
|
+
!environment.pending &&
|
|
83
|
+
(environment.from ? refOf(environment.from) === id : true));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=history-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-panel.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/history-panel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OwnedTags } from '@braidlabs/registry';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the caller may change an item with these tags, by the registry's per-tag ownership:
|
|
4
|
+
* they must own one of its tags, and an untagged item needs `*`. `null` means the registry has no
|
|
5
|
+
* tag ownership, so publish rights are all there is.
|
|
6
|
+
*
|
|
7
|
+
* The server decides every request; this only keeps the console from offering what it would
|
|
8
|
+
* refuse.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canChange(tags: readonly string[] | undefined, owned: OwnedTags | null | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The tags a new item starts with, so a caller who owns only some tags can still edit what they
|
|
13
|
+
* just created — an untagged item would need `*`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function startingTags(owned: OwnedTags | null | undefined): string[] | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the caller may change an item with these tags, by the registry's per-tag ownership:
|
|
3
|
+
* they must own one of its tags, and an untagged item needs `*`. `null` means the registry has no
|
|
4
|
+
* tag ownership, so publish rights are all there is.
|
|
5
|
+
*
|
|
6
|
+
* The server decides every request; this only keeps the console from offering what it would
|
|
7
|
+
* refuse.
|
|
8
|
+
*/
|
|
9
|
+
export function canChange(tags, owned) {
|
|
10
|
+
if (owned == null || owned === '*')
|
|
11
|
+
return true;
|
|
12
|
+
return (tags ?? []).some((tag) => owned.includes(tag));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The tags a new item starts with, so a caller who owns only some tags can still edit what they
|
|
16
|
+
* just created — an untagged item would need `*`.
|
|
17
|
+
*/
|
|
18
|
+
export function startingTags(owned) {
|
|
19
|
+
return owned == null || owned === '*' || owned.length === 0 ? undefined : [owned[0]];
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/ownership.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAmC,EAAE,KAAmC;IAChG,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmC;IAC9D,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DiscoveryEntry } from '@braidlabs/gateway';
|
|
2
|
+
import type { ShellManifest } from '@braidlabs/registry';
|
|
3
|
+
/**
|
|
4
|
+
* A small query language over shells and fragments.
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* tag:finance -is:gated route:/billing/123 "customer reviews"
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Terms are ANDed; `-` negates one; quotes keep spaces in a value. Anything that is not a known
|
|
11
|
+
* `key:value` is free text, matched as one phrase across everything a row shows — which is exactly
|
|
12
|
+
* what the old filter box did, so a query with no keys behaves as it always has.
|
|
13
|
+
*/
|
|
14
|
+
export declare const QUERY_KEYS: readonly ["tag", "type", "framework", "route", "adapter", "is"];
|
|
15
|
+
export type QueryKey = (typeof QUERY_KEYS)[number];
|
|
16
|
+
export interface QueryTerm {
|
|
17
|
+
key: QueryKey;
|
|
18
|
+
value: string;
|
|
19
|
+
negate: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ParsedQuery {
|
|
22
|
+
terms: QueryTerm[];
|
|
23
|
+
/** Free text, lowercased and matched as one phrase; empty when there is none. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Words prefixed with `-` and no key, lowercased: a row containing any of them is excluded. */
|
|
26
|
+
exclude: string[];
|
|
27
|
+
}
|
|
28
|
+
/** What a row shows, normalized so shells and fragments can be matched by one function. */
|
|
29
|
+
export interface SearchItem {
|
|
30
|
+
type: 'shell' | 'fragment';
|
|
31
|
+
id: string;
|
|
32
|
+
tags: readonly string[];
|
|
33
|
+
/** Fragments: pierce patterns. Shells: route paths. */
|
|
34
|
+
routes: readonly string[];
|
|
35
|
+
adapter?: string;
|
|
36
|
+
framework?: string;
|
|
37
|
+
/** Listed but not loadable by this caller. Fragments only. */
|
|
38
|
+
gated?: boolean;
|
|
39
|
+
/** Everything free text searches. */
|
|
40
|
+
fields: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function parseQuery(raw: string): ParsedQuery;
|
|
43
|
+
export declare function matchQuery(item: SearchItem, query: ParsedQuery): boolean;
|
|
44
|
+
export declare function fragmentItem(entry: DiscoveryEntry): SearchItem;
|
|
45
|
+
export declare function shellItem(shell: ShellManifest): SearchItem;
|
|
46
|
+
/** Filters anything with a {@link SearchItem} view by a query string. */
|
|
47
|
+
export declare function filterBy<T>(items: readonly T[], query: string, view: (item: T) => SearchItem): T[];
|
package/src/lib/query.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small query language over shells and fragments.
|
|
3
|
+
*
|
|
4
|
+
* ```
|
|
5
|
+
* tag:finance -is:gated route:/billing/123 "customer reviews"
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* Terms are ANDed; `-` negates one; quotes keep spaces in a value. Anything that is not a known
|
|
9
|
+
* `key:value` is free text, matched as one phrase across everything a row shows — which is exactly
|
|
10
|
+
* what the old filter box did, so a query with no keys behaves as it always has.
|
|
11
|
+
*/
|
|
12
|
+
export const QUERY_KEYS = ['tag', 'type', 'framework', 'route', 'adapter', 'is'];
|
|
13
|
+
const TOKEN = /(-?)(?:([a-zA-Z]+):)?(?:"([^"]*)"|(\S+))/g;
|
|
14
|
+
const DANGLING_KEY = new RegExp(`(^|\\s)-?(?:${QUERY_KEYS.join('|')}):(?=\\s|$)`, 'gi');
|
|
15
|
+
export function parseQuery(raw) {
|
|
16
|
+
const terms = [];
|
|
17
|
+
const text = [];
|
|
18
|
+
const exclude = [];
|
|
19
|
+
// `tag:` with nothing after it is a query being typed, not a filter that matches nothing.
|
|
20
|
+
const input = raw.replace(DANGLING_KEY, '$1');
|
|
21
|
+
let structured = input !== raw;
|
|
22
|
+
for (const match of input.matchAll(TOKEN)) {
|
|
23
|
+
const [raw, minus, key, quoted, bare] = match;
|
|
24
|
+
const value = quoted ?? bare ?? '';
|
|
25
|
+
const known = key && QUERY_KEYS.includes(key.toLowerCase());
|
|
26
|
+
if (known) {
|
|
27
|
+
structured = true;
|
|
28
|
+
terms.push({ key: key.toLowerCase(), value, negate: minus === '-' });
|
|
29
|
+
}
|
|
30
|
+
else if (minus === '-' && !key && value) {
|
|
31
|
+
structured = true;
|
|
32
|
+
exclude.push(value.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (quoted !== undefined)
|
|
36
|
+
structured = true;
|
|
37
|
+
text.push(key ? raw.replace(/^-/, '') : value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// With no structure at all, keep the query's own spacing: the old filter matched the trimmed
|
|
41
|
+
// query as typed, and "unchanged" should mean unchanged.
|
|
42
|
+
const phrase = structured ? text.join(' ') : input.trim();
|
|
43
|
+
return { terms, text: phrase.toLowerCase(), exclude };
|
|
44
|
+
}
|
|
45
|
+
export function matchQuery(item, query) {
|
|
46
|
+
const fields = item.fields.map((field) => field.toLowerCase());
|
|
47
|
+
if (query.text && !fields.some((field) => field.includes(query.text)))
|
|
48
|
+
return false;
|
|
49
|
+
if (query.exclude.some((word) => fields.some((field) => field.includes(word))))
|
|
50
|
+
return false;
|
|
51
|
+
return query.terms.every((term) => matchTerm(item, term) !== term.negate);
|
|
52
|
+
}
|
|
53
|
+
function matchTerm(item, { key, value }) {
|
|
54
|
+
const needle = value.toLowerCase();
|
|
55
|
+
switch (key) {
|
|
56
|
+
case 'tag':
|
|
57
|
+
return item.tags.some((tag) => tag.toLowerCase() === needle);
|
|
58
|
+
case 'type':
|
|
59
|
+
return item.type === needle;
|
|
60
|
+
case 'framework':
|
|
61
|
+
return item.framework?.toLowerCase() === needle;
|
|
62
|
+
case 'adapter':
|
|
63
|
+
return item.adapter?.toLowerCase() === needle;
|
|
64
|
+
case 'route':
|
|
65
|
+
return item.routes.some((pattern) => routeMatches(pattern, value));
|
|
66
|
+
case 'is':
|
|
67
|
+
if (needle === 'gated')
|
|
68
|
+
return item.gated === true;
|
|
69
|
+
if (needle === 'unrouted')
|
|
70
|
+
return item.routes.length === 0;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `route:` matches a pattern written the same way, or a concrete path the pattern would serve —
|
|
76
|
+
* `route:/billing/123` finds what pierces `/billing/*`, which is the question an operator with a
|
|
77
|
+
* URL in hand is asking.
|
|
78
|
+
*/
|
|
79
|
+
function routeMatches(pattern, value) {
|
|
80
|
+
if (pattern.toLowerCase() === value.toLowerCase())
|
|
81
|
+
return true;
|
|
82
|
+
if (typeof URLPattern === 'undefined' || !value.startsWith('/'))
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
return new URLPattern({ pathname: pattern }).test({ pathname: value });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function fragmentItem(entry) {
|
|
92
|
+
return {
|
|
93
|
+
type: 'fragment',
|
|
94
|
+
id: entry.id,
|
|
95
|
+
tags: entry.tags ?? [],
|
|
96
|
+
routes: entry.pierce ?? [],
|
|
97
|
+
adapter: entry.adapter,
|
|
98
|
+
gated: !entry.loadable,
|
|
99
|
+
fields: [entry.id, entry.title, entry.description, entry.adapter, ...(entry.tags ?? []), ...(entry.pierce ?? [])].filter((field) => Boolean(field)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function shellItem(shell) {
|
|
103
|
+
const routes = shell.routes.map((route) => route.path);
|
|
104
|
+
const slotted = shell.routes.flatMap((route) => Object.values(route.slots ?? {}));
|
|
105
|
+
return {
|
|
106
|
+
type: 'shell',
|
|
107
|
+
id: shell.id,
|
|
108
|
+
tags: shell.tags ?? [],
|
|
109
|
+
routes,
|
|
110
|
+
framework: shell.framework,
|
|
111
|
+
fields: [shell.id, shell.title, shell.description, shell.framework, ...(shell.tags ?? []), ...routes, ...slotted].filter((field) => Boolean(field)),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Filters anything with a {@link SearchItem} view by a query string. */
|
|
115
|
+
export function filterBy(items, query, view) {
|
|
116
|
+
const parsed = parseQuery(query);
|
|
117
|
+
if (!parsed.text && parsed.terms.length === 0 && parsed.exclude.length === 0)
|
|
118
|
+
return [...items];
|
|
119
|
+
return items.filter((item) => matchQuery(view(item), parsed));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/query.ts"],"names":[],"mappings":"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"}
|