@shortlink-org/portolan 0.2.2 → 0.2.4
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 +7 -2
- package/cli/portolan.mjs +36 -6
- package/package.json +4 -4
- package/plugins/README.md +5 -0
- package/plugins/extract-python-kafka/README.md +6 -0
- package/plugins/extract-python-kafka/extract.py +2 -2
- package/plugins/extract-python-kafka/extract_test.py +17 -1
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +12 -1
- package/plugins/pyplugin/kafka.py +74 -3
- package/scripts/builtin-plugins.mjs +3 -2
- package/scripts/catalog-sources.mjs +2 -1
- package/scripts/catalog-sources.test.mjs +11 -1
- package/scripts/delivery-presets.mjs +207 -24
- package/scripts/diff.mjs +5 -1
- package/scripts/gen-likec4.mjs +78 -20
- package/scripts/gen-likec4.test.mjs +25 -2
- package/scripts/local-api.mjs +26 -377
- package/scripts/local-api.test.mjs +50 -0
- package/scripts/local-discovery.mjs +378 -0
- package/scripts/manifest.mjs +42 -1
- package/scripts/manifest.test.mjs +24 -1
- package/scripts/run-builtin.mjs +4 -2
- package/scripts/schema.mjs +4 -0
- package/scripts/site-docs.mjs +2 -2
- package/src/app/Breadcrumbs.test.ts +4 -0
- package/src/app/Breadcrumbs.tsx +1 -0
- package/src/app/CatalogApp.tsx +4 -4
- package/src/app/Sidebar.tsx +13 -730
- package/src/app/SidebarFlowSections.tsx +251 -0
- package/src/app/SidebarFooter.tsx +160 -0
- package/src/app/SidebarTree.tsx +322 -0
- package/src/catalog-index.ts +485 -0
- package/src/catalog-model.ts +1360 -0
- package/src/catalog-stores.test.ts +17 -0
- package/src/catalog-validation.ts +1611 -0
- package/src/catalog.test.ts +29 -2
- package/src/catalog.ts +6 -3306
- package/src/components/ChannelRows.messagepack.test.tsx +28 -0
- package/src/components/ChannelRows.test.tsx +54 -0
- package/src/components/ChannelRows.tsx +57 -10
- package/src/components/LifecycleDiagram.tsx +28 -12
- package/src/components/{PageHeader.test.ts → PageHeader.test.tsx} +9 -10
- package/src/components/ProblemRow.tsx +7 -0
- package/src/components/SourcePreview.tsx +1 -1
- package/src/components/WhatLinksHere.tsx +6 -4
- package/src/enrich.test.ts +4 -5
- package/src/flow/StepDetail.tsx +98 -54
- package/src/flow/answers.test.ts +18 -1
- package/src/flow/answers.ts +37 -8
- package/src/index.css +0 -17
- package/src/landing/LandingPage.tsx +4 -4
- package/src/lib/all-problems.ts +1 -1
- package/src/lib/backlinks.test.ts +16 -1
- package/src/lib/backlinks.ts +20 -0
- package/src/lib/catalog-diff.test.ts +18 -0
- package/src/lib/catalog-diff.ts +19 -1
- package/src/lib/derive.ts +2 -0
- package/src/lib/kafka-ui.test.ts +87 -0
- package/src/lib/kafka-ui.ts +105 -0
- package/src/lib/local-api.ts +19 -7
- package/src/lib/motion.test.ts +4 -2
- package/src/lib/motion.tsx +5 -4
- package/src/lib/proto-problems.test.ts +170 -3
- package/src/lib/proto-problems.ts +176 -4
- package/src/lib/wire-problems.test.ts +21 -0
- package/src/lib/wire-problems.ts +62 -1
- package/src/likec4/FlowView.tsx +2 -6
- package/src/likec4/flow-edges.test.ts +64 -1
- package/src/likec4/flow-edges.ts +43 -7
- package/src/likec4/view-index.ts +8 -2
- package/src/merge.test.ts +69 -0
- package/src/merge.ts +52 -2
- package/src/pages/CatalogFailure.tsx +2 -2
- package/src/pages/Settings.tsx +12 -129
- package/src/pages/settings/AboutSettings.tsx +129 -0
- package/src/pages/settings/DeliverySettings.tsx +71 -15
- package/src/pages/settings/IntegrationsSettings.tsx +117 -0
- package/src/routes.test.ts +2 -0
- package/src/routes.ts +2 -1
- package/src/selection/DetailPanel.tsx +46 -1
- package/src/selection/pages.test.ts +14 -1
- package/src/selection/pages.ts +9 -3
- package/vite.config.ts +3 -1
package/src/likec4/flow-edges.ts
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
// id across, so the two are paired by position.
|
|
4
4
|
//
|
|
5
5
|
// That is sound because it is the same walk twice: the generator emits steps in
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// abandoned rather than guessed at, and the diagram simply stops highlighting.
|
|
6
|
+
// catalog order and this module inserts the same contract-response edges in the
|
|
7
|
+
// same places. If the two lists ever differ in length the pairing is abandoned
|
|
8
|
+
// rather than guessed at, and the diagram simply stops highlighting.
|
|
10
9
|
|
|
11
10
|
import type { Flow } from "../catalog";
|
|
12
11
|
import { walkSteps } from "../catalog";
|
|
@@ -15,13 +14,16 @@ import { hiddenStepIds } from "../flow/cross-context";
|
|
|
15
14
|
export interface EdgeStepPairing {
|
|
16
15
|
/** LikeC4 edge id -> catalog step id */
|
|
17
16
|
stepOf: Map<string, string>;
|
|
18
|
-
/** catalog step id -> LikeC4 edge id */
|
|
17
|
+
/** catalog step id -> its primary LikeC4 edge id */
|
|
19
18
|
edgeOf: Map<string, string>;
|
|
19
|
+
/** catalog step id -> every edge it draws, including a contract response */
|
|
20
|
+
edgesOf: Map<string, string[]>;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const EMPTY_PAIRING: EdgeStepPairing = {
|
|
23
24
|
stepOf: new Map(),
|
|
24
25
|
edgeOf: new Map(),
|
|
26
|
+
edgesOf: new Map(),
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export function pairEdgesToSteps(
|
|
@@ -31,13 +33,17 @@ export function pairEdgesToSteps(
|
|
|
31
33
|
if (edgeIds.length !== stepIds.length) return EMPTY_PAIRING;
|
|
32
34
|
const stepOf = new Map<string, string>();
|
|
33
35
|
const edgeOf = new Map<string, string>();
|
|
36
|
+
const edgesOf = new Map<string, string[]>();
|
|
34
37
|
edgeIds.forEach((edgeId, i) => {
|
|
35
38
|
const stepId = stepIds[i];
|
|
36
39
|
if (stepId === undefined) return;
|
|
37
40
|
stepOf.set(edgeId, stepId);
|
|
38
|
-
edgeOf.set(stepId, edgeId);
|
|
41
|
+
if (!edgeOf.has(stepId)) edgeOf.set(stepId, edgeId);
|
|
42
|
+
const edges = edgesOf.get(stepId) ?? [];
|
|
43
|
+
edges.push(edgeId);
|
|
44
|
+
edgesOf.set(stepId, edges);
|
|
39
45
|
});
|
|
40
|
-
return { stepOf, edgeOf };
|
|
46
|
+
return { stepOf, edgeOf, edgesOf };
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/**
|
|
@@ -87,3 +93,33 @@ export function drawnStepIds(flow: Flow, crossOnly: boolean): string[] {
|
|
|
87
93
|
const hidden = hiddenStepIds(flow);
|
|
88
94
|
return steps.filter((s) => !hidden.has(s.id)).map((s) => s.id);
|
|
89
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* One step id per generated edge, including contract responses synthesized by
|
|
99
|
+
* the LikeC4 generator. Nested RPCs return immediately; the actor request that
|
|
100
|
+
* opened the flow returns after its final step.
|
|
101
|
+
*/
|
|
102
|
+
export function drawnEdgeStepIds(
|
|
103
|
+
flow: Flow,
|
|
104
|
+
crossOnly: boolean,
|
|
105
|
+
responseStepIds: ReadonlySet<string>,
|
|
106
|
+
): string[] {
|
|
107
|
+
const steps = walkSteps(flow.steps);
|
|
108
|
+
const actorIds = new Set(
|
|
109
|
+
flow.participants.filter((p) => p.kind === "actor").map((p) => p.id),
|
|
110
|
+
);
|
|
111
|
+
const deferred = steps.find(
|
|
112
|
+
(step) =>
|
|
113
|
+
step.kind === "rpc" &&
|
|
114
|
+
actorIds.has(step.from) &&
|
|
115
|
+
responseStepIds.has(step.id),
|
|
116
|
+
)?.id;
|
|
117
|
+
const drawn = drawnStepIds(flow, crossOnly);
|
|
118
|
+
const out: string[] = [];
|
|
119
|
+
for (const stepId of drawn) {
|
|
120
|
+
out.push(stepId);
|
|
121
|
+
if (stepId !== deferred && responseStepIds.has(stepId)) out.push(stepId);
|
|
122
|
+
}
|
|
123
|
+
if (deferred && drawn.includes(deferred)) out.push(deferred);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
package/src/likec4/view-index.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { pickViewBounds } from "likec4/react";
|
|
9
9
|
import { likec4model } from "./generated";
|
|
10
10
|
import type { Flow } from "../catalog";
|
|
11
|
-
import {
|
|
11
|
+
import { index } from "../data";
|
|
12
|
+
import { flowAnswers } from "../flow/answers";
|
|
13
|
+
import { drawnEdgeStepIds, pairEdgesToSteps } from "./flow-edges";
|
|
12
14
|
import type { EdgeStepPairing } from "./flow-edges";
|
|
13
15
|
import { EMPTY_PAIRING } from "./flow-edges";
|
|
14
16
|
import { flowCrossViewId, flowViewId } from "./ids";
|
|
@@ -70,8 +72,12 @@ export function flowPairing(flow: Flow, crossOnly: boolean): EdgeStepPairing {
|
|
|
70
72
|
const viewId = crossOnly ? flowCrossViewId(flow) : flowViewId(flow);
|
|
71
73
|
const cached = pairings.get(viewId);
|
|
72
74
|
if (cached) return cached;
|
|
75
|
+
const responseStepIds = new Set(flowAnswers(index, flow).keys());
|
|
73
76
|
const pairing = shapeOf(viewId).edgeIds.length
|
|
74
|
-
? pairEdgesToSteps(
|
|
77
|
+
? pairEdgesToSteps(
|
|
78
|
+
shapeOf(viewId).edgeIds,
|
|
79
|
+
drawnEdgeStepIds(flow, crossOnly, responseStepIds),
|
|
80
|
+
)
|
|
75
81
|
: EMPTY_PAIRING;
|
|
76
82
|
pairings.set(viewId, pairing);
|
|
77
83
|
return pairing;
|
package/src/merge.test.ts
CHANGED
|
@@ -181,6 +181,52 @@ describe("mergeCatalogs", () => {
|
|
|
181
181
|
expect(service?.provides.map((p) => p.id)).toEqual(["auth.v1.Users"]);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
+
it("retains and unions vendored proto descriptors across service fragments", () => {
|
|
185
|
+
const first = context("shop", ["shop.oms"]);
|
|
186
|
+
first.services[0]!.copies = [
|
|
187
|
+
{
|
|
188
|
+
id: "pricing.v1.Pricing",
|
|
189
|
+
source: "pricing.proto",
|
|
190
|
+
methods: [{ name: "GetQuote" }],
|
|
191
|
+
enums: [
|
|
192
|
+
{
|
|
193
|
+
name: "Status",
|
|
194
|
+
values: [{ name: "STATUS_UNSPECIFIED", number: 0 }],
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
const second = context("shop", ["shop.oms"]);
|
|
200
|
+
second.services[0]!.copies = [
|
|
201
|
+
{
|
|
202
|
+
id: "pricing.v1.Pricing",
|
|
203
|
+
source: "pricing.proto",
|
|
204
|
+
methods: [{ name: "WatchPrices" }],
|
|
205
|
+
enums: [
|
|
206
|
+
{
|
|
207
|
+
name: "Status",
|
|
208
|
+
values: [{ name: "STATUS_READY", number: 1 }],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const merged = mergeCatalogs([
|
|
215
|
+
source("a.json", { contexts: [first] }),
|
|
216
|
+
source("b.json", { contexts: [second] }),
|
|
217
|
+
]);
|
|
218
|
+
const copy = merged.catalog.contexts[0]?.services[0]?.copies?.[0];
|
|
219
|
+
|
|
220
|
+
expect(copy?.methods.map((method) => method.name)).toEqual([
|
|
221
|
+
"GetQuote",
|
|
222
|
+
"WatchPrices",
|
|
223
|
+
]);
|
|
224
|
+
expect(copy?.enums?.[0]?.values.map((value) => value.name)).toEqual([
|
|
225
|
+
"STATUS_UNSPECIFIED",
|
|
226
|
+
"STATUS_READY",
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
|
|
184
230
|
it("unions a service's commands by the line to type", () => {
|
|
185
231
|
const domain = context("shop", ["shop.cart"]);
|
|
186
232
|
domain.services = domain.services.map((s) => ({
|
|
@@ -711,6 +757,29 @@ describe("mergeCatalogs: schema modules", () => {
|
|
|
711
757
|
// came from.
|
|
712
758
|
expect(channels?.[0]?.source).toBe("bus/asyncapi.yaml");
|
|
713
759
|
});
|
|
760
|
+
|
|
761
|
+
it("keeps a message encoding and reports sources that disagree about it", () => {
|
|
762
|
+
const first = context("shop", ["shop.cart"]);
|
|
763
|
+
first.services[0]!.channels = [{
|
|
764
|
+
address: "shop.cart.basket",
|
|
765
|
+
messages: [{ name: "cart.BasketCreated", direction: "send", encoding: "msgpack", contentType: "application/msgpack" }],
|
|
766
|
+
}];
|
|
767
|
+
const second = context("shop", ["shop.cart"]);
|
|
768
|
+
second.services[0]!.channels = [{
|
|
769
|
+
address: "shop.cart.basket",
|
|
770
|
+
messages: [{ name: "cart.BasketCreated", direction: "send", encoding: "json", contentType: "application/json" }],
|
|
771
|
+
}];
|
|
772
|
+
|
|
773
|
+
const merged = mergeCatalogs([
|
|
774
|
+
source("a-msgpack.json", { contexts: [first] }),
|
|
775
|
+
source("b-json.json", { contexts: [second] }),
|
|
776
|
+
]);
|
|
777
|
+
const message = merged.catalog.contexts[0]!.services[0]!.channels![0]!.messages[0]!;
|
|
778
|
+
expect(message.encoding).toBe("msgpack");
|
|
779
|
+
expect(message.contentType).toBe("application/msgpack");
|
|
780
|
+
expect(merged.conflicts.map((conflict) => conflict.message).join(" ")).toMatch(/encoding json.*msgpack/);
|
|
781
|
+
expect(merged.conflicts.map((conflict) => conflict.message).join(" ")).toMatch(/contentType application\/json.*application\/msgpack/);
|
|
782
|
+
});
|
|
714
783
|
});
|
|
715
784
|
|
|
716
785
|
describe("a second source that has seen the flow run", () => {
|
package/src/merge.ts
CHANGED
|
@@ -442,6 +442,7 @@ function foldMaps(catalog: Catalog, stores: Store[]): Store[] {
|
|
|
442
442
|
if (!aggregate) return table;
|
|
443
443
|
return {
|
|
444
444
|
...table,
|
|
445
|
+
...(table.accesses ? { accesses: table.accesses.map((access) => ({ ...access })) } : {}),
|
|
445
446
|
columns: table.columns.map((column) => {
|
|
446
447
|
if (!column.maps) return column;
|
|
447
448
|
const maps = fold(aggregate, column.maps);
|
|
@@ -473,8 +474,11 @@ function mergeService(
|
|
|
473
474
|
if (!existing) {
|
|
474
475
|
services.set(incoming.id, {
|
|
475
476
|
...incoming,
|
|
476
|
-
provides:
|
|
477
|
+
provides: incoming.provides.map(copyInterface),
|
|
477
478
|
consumes: [...incoming.consumes],
|
|
479
|
+
...(incoming.copies
|
|
480
|
+
? { copies: incoming.copies.map(copyInterface) }
|
|
481
|
+
: {}),
|
|
478
482
|
aggregates: incoming.aggregates.map(copyAggregate),
|
|
479
483
|
...(incoming.stores ? { stores: [...incoming.stores] } : {}),
|
|
480
484
|
...(incoming.modules ? { modules: [...incoming.modules] } : {}),
|
|
@@ -534,6 +538,11 @@ function mergeService(
|
|
|
534
538
|
|
|
535
539
|
mergeInterfaces(existing.provides, incoming.provides);
|
|
536
540
|
appendNew(existing.consumes, incoming.consumes, (c) => c.id, raise);
|
|
541
|
+
if (incoming.copies?.length) {
|
|
542
|
+
const copies = existing.copies ?? [];
|
|
543
|
+
mergeInterfaces(copies, incoming.copies);
|
|
544
|
+
existing.copies = copies;
|
|
545
|
+
}
|
|
537
546
|
mergeAggregates(existing.aggregates, incoming.aggregates);
|
|
538
547
|
|
|
539
548
|
if (incoming.stores?.length) {
|
|
@@ -600,6 +609,21 @@ function mergeService(
|
|
|
600
609
|
mine.messages,
|
|
601
610
|
theirs.messages,
|
|
602
611
|
(m) => `${m.direction} ${m.name}`,
|
|
612
|
+
(held, offered) => {
|
|
613
|
+
for (const field of ["encoding", "contentType"] as const) {
|
|
614
|
+
if (!held[field] && offered[field]) {
|
|
615
|
+
held[field] = offered[field];
|
|
616
|
+
} else if (held[field] && offered[field] && held[field] !== offered[field]) {
|
|
617
|
+
conflicts.push({
|
|
618
|
+
path,
|
|
619
|
+
where: incoming.id,
|
|
620
|
+
message: `message "${held.name}" on channel "${mine.address}" of service "${incoming.id}" has ${field} ${offered[field]} here and ${held[field]} in ${owner}; the first one is used`,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return undefined;
|
|
626
|
+
},
|
|
603
627
|
);
|
|
604
628
|
|
|
605
629
|
// Mutated in place: the channel that was here keeps its prose and its
|
|
@@ -612,7 +636,7 @@ function mergeService(
|
|
|
612
636
|
}
|
|
613
637
|
|
|
614
638
|
function copyChannel(channel: Channel): Channel {
|
|
615
|
-
return { ...channel, messages:
|
|
639
|
+
return { ...channel, messages: channel.messages.map((message) => ({ ...message })) };
|
|
616
640
|
}
|
|
617
641
|
|
|
618
642
|
/**
|
|
@@ -713,6 +737,24 @@ function mergeInterfaces(target: RpcService[], incoming: RpcService[]): void {
|
|
|
713
737
|
}
|
|
714
738
|
}
|
|
715
739
|
if (messages.length > 0) mine.messages = messages;
|
|
740
|
+
|
|
741
|
+
const enums = mine.enums ?? [];
|
|
742
|
+
for (const item of theirs.enums ?? []) {
|
|
743
|
+
const existing = enums.find((candidate) => candidate.name === item.name);
|
|
744
|
+
if (!existing) {
|
|
745
|
+
enums.push({
|
|
746
|
+
...item,
|
|
747
|
+
values: item.values.map((value) => ({ ...value })),
|
|
748
|
+
});
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
appendNew(
|
|
752
|
+
existing.values,
|
|
753
|
+
item.values.map((value) => ({ ...value })),
|
|
754
|
+
(value) => value.name,
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
if (enums.length > 0) mine.enums = enums;
|
|
716
758
|
}
|
|
717
759
|
}
|
|
718
760
|
|
|
@@ -736,6 +778,14 @@ function copyInterface(provided: RpcService): RpcService {
|
|
|
736
778
|
})),
|
|
737
779
|
}
|
|
738
780
|
: {}),
|
|
781
|
+
...(provided.enums
|
|
782
|
+
? {
|
|
783
|
+
enums: provided.enums.map((item) => ({
|
|
784
|
+
...item,
|
|
785
|
+
values: item.values.map((value) => ({ ...value })),
|
|
786
|
+
})),
|
|
787
|
+
}
|
|
788
|
+
: {}),
|
|
739
789
|
};
|
|
740
790
|
}
|
|
741
791
|
|
|
@@ -26,8 +26,8 @@ const HINTS: { test: RegExp; hint: string }[] = [
|
|
|
26
26
|
hint: "A step names a lane the flow never declared. Add the service to the flow's `participants`, or point the step at one that is already there.",
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
test: /resolves to neither an Event nor
|
|
30
|
-
hint: "Either the ref is stale — the event was renamed or removed — or the step really does point at something outside the catalog, in which case its status belongs as `unresolved`.",
|
|
29
|
+
test: /resolves to neither an Event, an RpcCall nor a method/,
|
|
30
|
+
hint: "Either the ref is stale — the event or RPC method was renamed or removed — or the step really does point at something outside the catalog, in which case its status belongs as `unresolved`.",
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
test: /must have id/,
|
package/src/pages/Settings.tsx
CHANGED
|
@@ -3,13 +3,10 @@ import { createContext, useCallback, useContext, useEffect, useState } from "rea
|
|
|
3
3
|
import { Link, Navigate, NavLink, Route, Routes, useLocation } from "react-router";
|
|
4
4
|
import {
|
|
5
5
|
ArrowLeft,
|
|
6
|
-
ArrowRight,
|
|
7
|
-
BookOpen,
|
|
8
6
|
Box,
|
|
9
7
|
Check,
|
|
10
8
|
ChevronDown,
|
|
11
9
|
CircleAlert,
|
|
12
|
-
CircleDot,
|
|
13
10
|
FolderGit2,
|
|
14
11
|
GitBranch,
|
|
15
12
|
KeyRound,
|
|
@@ -21,7 +18,6 @@ import {
|
|
|
21
18
|
Trash2,
|
|
22
19
|
X,
|
|
23
20
|
} from "lucide-react";
|
|
24
|
-
import type { Variants } from "motion/react";
|
|
25
21
|
import { catalog, catalogSources } from "../data";
|
|
26
22
|
import { useToastStore } from "../app/toast";
|
|
27
23
|
import { absoluteTime, plural, relativeTime } from "../lib/format";
|
|
@@ -59,15 +55,10 @@ import { Modal } from "../components/Overlay";
|
|
|
59
55
|
import { MachineDocs } from "../components/MachineDocs";
|
|
60
56
|
import { DeliverySettings } from "./settings/DeliverySettings";
|
|
61
57
|
import { PreferencesSettings } from "./settings/PreferencesSettings";
|
|
58
|
+
import { AboutSettings } from "./settings/AboutSettings";
|
|
59
|
+
import { IntegrationsSettings } from "./settings/IntegrationsSettings";
|
|
62
60
|
import { CatEmptyState, CatIllustration } from "../components/CatIllustration";
|
|
63
61
|
import { CommitLink } from "../components/CommitLink";
|
|
64
|
-
import { m, transitions } from "../lib/motion";
|
|
65
|
-
import {
|
|
66
|
-
PRODUCT_ISSUES,
|
|
67
|
-
PRODUCT_LICENSE,
|
|
68
|
-
PRODUCT_README,
|
|
69
|
-
PRODUCT_REPOSITORY,
|
|
70
|
-
} from "../lib/product";
|
|
71
62
|
|
|
72
63
|
type Health = "healthy" | "changed" | "failed" | "unchecked";
|
|
73
64
|
type ProjectSource = ProjectDraft["source"];
|
|
@@ -964,6 +955,7 @@ const SETTINGS_LINKS = [
|
|
|
964
955
|
["Projects", paths.settingsProjects()],
|
|
965
956
|
["Pipeline", paths.settingsPipeline()],
|
|
966
957
|
["Delivery", paths.settingsDelivery()],
|
|
958
|
+
["Integrations", paths.settingsIntegrations()],
|
|
967
959
|
["Preferences", paths.settingsPreferences()],
|
|
968
960
|
["About", paths.settingsAbout()],
|
|
969
961
|
] as const;
|
|
@@ -1056,6 +1048,10 @@ function OverviewSettings({ local, onGenerate }: { local: boolean; onGenerate: (
|
|
|
1056
1048
|
<div className="font-semibold text-ink">Delivery</div>
|
|
1057
1049
|
<p className="mt-1 text-muted">Install review checks and static catalog publishing for GitHub or GitLab.</p>
|
|
1058
1050
|
</Link>
|
|
1051
|
+
<Link to={paths.settingsIntegrations()} className="card">
|
|
1052
|
+
<div className="font-semibold text-ink">Integrations</div>
|
|
1053
|
+
<p className="mt-1 text-muted">Connect operational tools such as Kafka UI to the catalog.</p>
|
|
1054
|
+
</Link>
|
|
1059
1055
|
<Link to={paths.settingsPreferences()} className="card">
|
|
1060
1056
|
<div className="font-semibold text-ink">Preferences</div>
|
|
1061
1057
|
<p className="mt-1 text-muted">Theme, row density, source editor and Ask the catalog.</p>
|
|
@@ -1117,135 +1113,21 @@ function PipelineSettings() {
|
|
|
1117
1113
|
);
|
|
1118
1114
|
}
|
|
1119
1115
|
|
|
1120
|
-
const ABOUT_CARD_MOTION: Variants = {
|
|
1121
|
-
hidden: { opacity: 0, y: 8 },
|
|
1122
|
-
shown: {
|
|
1123
|
-
opacity: 1,
|
|
1124
|
-
y: 0,
|
|
1125
|
-
transition: { ...transitions.page, staggerChildren: 0.05 },
|
|
1126
|
-
},
|
|
1127
|
-
hover: {},
|
|
1128
|
-
};
|
|
1129
|
-
|
|
1130
|
-
const ABOUT_COPY_MOTION: Variants = {
|
|
1131
|
-
hidden: { opacity: 0, y: 6 },
|
|
1132
|
-
shown: { opacity: 1, y: 0, transition: transitions.panel },
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
|
-
const ABOUT_CAT_MOTION: Variants = {
|
|
1136
|
-
hidden: { opacity: 0, x: 18, rotate: 3 },
|
|
1137
|
-
shown: {
|
|
1138
|
-
opacity: 1,
|
|
1139
|
-
x: 0,
|
|
1140
|
-
rotate: 0,
|
|
1141
|
-
transition: transitions.narrative,
|
|
1142
|
-
},
|
|
1143
|
-
hover: {
|
|
1144
|
-
y: -7,
|
|
1145
|
-
rotate: -2,
|
|
1146
|
-
scale: 1.035,
|
|
1147
|
-
transition: transitions.settle,
|
|
1148
|
-
},
|
|
1149
|
-
};
|
|
1150
|
-
|
|
1151
|
-
function AboutSettings() {
|
|
1152
|
-
return (
|
|
1153
|
-
<section>
|
|
1154
|
-
<div className="grid gap-grid lg:grid-cols-[minmax(0,1.25fr)_minmax(17rem,0.75fr)]">
|
|
1155
|
-
<m.div
|
|
1156
|
-
data-about-card
|
|
1157
|
-
className="card grid items-center gap-4 sm:grid-cols-[minmax(0,1fr)_180px]"
|
|
1158
|
-
variants={ABOUT_CARD_MOTION}
|
|
1159
|
-
initial="hidden"
|
|
1160
|
-
animate="shown"
|
|
1161
|
-
whileHover="hover"
|
|
1162
|
-
>
|
|
1163
|
-
<m.div
|
|
1164
|
-
className="flex flex-col items-start"
|
|
1165
|
-
variants={ABOUT_COPY_MOTION}
|
|
1166
|
-
>
|
|
1167
|
-
<p className="max-w-prose text-muted">
|
|
1168
|
-
Architecture from code and evidence. Product source,
|
|
1169
|
-
documentation, releases and issue tracking live in the public
|
|
1170
|
-
GitHub repository.
|
|
1171
|
-
</p>
|
|
1172
|
-
<div className="mt-5 flex flex-wrap gap-2">
|
|
1173
|
-
<Link to={paths.landing()} className="product-primary">
|
|
1174
|
-
Product page <ArrowRight size={15} aria-hidden />
|
|
1175
|
-
</Link>
|
|
1176
|
-
<a
|
|
1177
|
-
href={PRODUCT_REPOSITORY}
|
|
1178
|
-
target="_blank"
|
|
1179
|
-
rel="noreferrer"
|
|
1180
|
-
className="tbtn text-ink"
|
|
1181
|
-
>
|
|
1182
|
-
<GitBranch size={15} aria-hidden /> View product on GitHub
|
|
1183
|
-
</a>
|
|
1184
|
-
</div>
|
|
1185
|
-
</m.div>
|
|
1186
|
-
<m.div
|
|
1187
|
-
data-about-cat
|
|
1188
|
-
className="h-44 w-full justify-self-center sm:h-52"
|
|
1189
|
-
variants={ABOUT_CAT_MOTION}
|
|
1190
|
-
>
|
|
1191
|
-
<CatIllustration scene="about" className="h-full w-full" />
|
|
1192
|
-
</m.div>
|
|
1193
|
-
</m.div>
|
|
1194
|
-
<m.div
|
|
1195
|
-
className="card"
|
|
1196
|
-
initial={{ opacity: 0, y: 8 }}
|
|
1197
|
-
animate={{ opacity: 1, y: 0 }}
|
|
1198
|
-
transition={{ ...transitions.page, delay: 0.06 }}
|
|
1199
|
-
>
|
|
1200
|
-
<div className="label mb-2">resources</div>
|
|
1201
|
-
<div className="divide-y divide-line">
|
|
1202
|
-
<a
|
|
1203
|
-
href={PRODUCT_README}
|
|
1204
|
-
target="_blank"
|
|
1205
|
-
rel="noreferrer"
|
|
1206
|
-
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
1207
|
-
>
|
|
1208
|
-
<BookOpen size={15} aria-hidden className="shrink-0" />
|
|
1209
|
-
Documentation
|
|
1210
|
-
</a>
|
|
1211
|
-
<a
|
|
1212
|
-
href={PRODUCT_ISSUES}
|
|
1213
|
-
target="_blank"
|
|
1214
|
-
rel="noreferrer"
|
|
1215
|
-
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
1216
|
-
>
|
|
1217
|
-
<CircleDot size={15} aria-hidden className="shrink-0" />
|
|
1218
|
-
Issues and feedback
|
|
1219
|
-
</a>
|
|
1220
|
-
<a
|
|
1221
|
-
href={PRODUCT_LICENSE}
|
|
1222
|
-
target="_blank"
|
|
1223
|
-
rel="noreferrer"
|
|
1224
|
-
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
1225
|
-
>
|
|
1226
|
-
<ShieldCheck size={15} aria-hidden className="shrink-0" />
|
|
1227
|
-
MIT License
|
|
1228
|
-
</a>
|
|
1229
|
-
</div>
|
|
1230
|
-
</m.div>
|
|
1231
|
-
</div>
|
|
1232
|
-
</section>
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
1116
|
|
|
1236
1117
|
function SettingsContent({ local, onAdd, onRemove, onGenerate }: { local: boolean; onAdd: (source: ProjectSource) => void; onRemove: (project: SetupProject) => void; onGenerate: () => void }) {
|
|
1237
1118
|
const pathname = useLocation().pathname.replace(/\/$/, "");
|
|
1238
1119
|
const overview = pathname === paths.settings();
|
|
1239
1120
|
const about = pathname === paths.settingsAbout();
|
|
1121
|
+
const browserIntegration = pathname === paths.settingsIntegrations();
|
|
1240
1122
|
return (
|
|
1241
1123
|
<div className="h-full overflow-y-auto p-gutter">
|
|
1242
1124
|
<div className="max-w-table">
|
|
1243
1125
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
1244
1126
|
<div>
|
|
1245
1127
|
<div className="flex items-center gap-2"><h1 className="text-lg font-semibold">Settings</h1>{local ? <span className="chip status-verified">local mode</span> : null}</div>
|
|
1246
|
-
<p className="mt-1 max-w-prose text-muted">Configure projects, extraction, delivery automation and local preferences. {local ? "This local session can write reviewed changes." : "Build configuration is read-only here."}</p>
|
|
1128
|
+
<p className="mt-1 max-w-prose text-muted">Configure projects, extraction, delivery automation, integrations and local preferences. {local ? "This local session can write reviewed changes." : "Build configuration is read-only here."}</p>
|
|
1247
1129
|
</div>
|
|
1248
|
-
{local && !overview && !about ? <button type="button" className="product-primary" onClick={onGenerate}><Play size={15} /> Preview generated diff</button> : null}
|
|
1130
|
+
{local && !overview && !about && !browserIntegration ? <button type="button" className="product-primary" onClick={onGenerate}><Play size={15} /> Preview generated diff</button> : null}
|
|
1249
1131
|
</div>
|
|
1250
1132
|
<SettingsNav />
|
|
1251
1133
|
<div className="mt-section">
|
|
@@ -1254,12 +1136,13 @@ function SettingsContent({ local, onAdd, onRemove, onGenerate }: { local: boolea
|
|
|
1254
1136
|
<Route path="projects" element={<ProjectsSettings local={local} onAdd={onAdd} onRemove={onRemove} />} />
|
|
1255
1137
|
<Route path="pipeline" element={<PipelineSettings />} />
|
|
1256
1138
|
<Route path="delivery" element={<section><SectionTitle right={local ? "preview before writing" : "local mode required"}>Delivery presets</SectionTitle><DeliverySettings local={local} /></section>} />
|
|
1139
|
+
<Route path="integrations" element={<IntegrationsSettings />} />
|
|
1257
1140
|
<Route path="preferences" element={<PreferencesSettings />} />
|
|
1258
1141
|
<Route path="about" element={<AboutSettings />} />
|
|
1259
1142
|
<Route path="*" element={<Navigate to={paths.settings()} replace />} />
|
|
1260
1143
|
</Routes>
|
|
1261
1144
|
</div>
|
|
1262
|
-
{!about ? <div className="mono mt-section flex items-center gap-2 pb-section text-muted"><Box size={14} aria-hidden />{local ? "Changes are written only after preview; generated files remain reviewable in git." : "Configuration is embedded at build time; changing it requires a new catalog build."}</div> : null}
|
|
1145
|
+
{!about ? <div className="mono mt-section flex items-center gap-2 pb-section text-muted"><Box size={14} aria-hidden />{browserIntegration ? "Integration settings stay in this browser and do not change the generated catalog." : local ? "Changes are written only after preview; generated files remain reviewable in git." : "Configuration is embedded at build time; changing it requires a new catalog build."}</div> : null}
|
|
1263
1146
|
</div>
|
|
1264
1147
|
</div>
|
|
1265
1148
|
);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ArrowRight, BookOpen, CircleDot, GitBranch, ShieldCheck } from "lucide-react";
|
|
2
|
+
import type { Variants } from "motion/react";
|
|
3
|
+
import { Link } from "react-router";
|
|
4
|
+
|
|
5
|
+
import { CatIllustration } from "../../components/CatIllustration";
|
|
6
|
+
import { m, transitions } from "../../lib/motion";
|
|
7
|
+
import {
|
|
8
|
+
PRODUCT_ISSUES,
|
|
9
|
+
PRODUCT_LICENSE,
|
|
10
|
+
PRODUCT_README,
|
|
11
|
+
PRODUCT_REPOSITORY,
|
|
12
|
+
} from "../../lib/product";
|
|
13
|
+
import { paths } from "../../routes";
|
|
14
|
+
|
|
15
|
+
const ABOUT_CARD_MOTION: Variants = {
|
|
16
|
+
hidden: { opacity: 0, y: 8 },
|
|
17
|
+
shown: {
|
|
18
|
+
opacity: 1,
|
|
19
|
+
y: 0,
|
|
20
|
+
transition: { ...transitions.page, staggerChildren: 0.05 },
|
|
21
|
+
},
|
|
22
|
+
hover: {},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ABOUT_COPY_MOTION: Variants = {
|
|
26
|
+
hidden: { opacity: 0, y: 6 },
|
|
27
|
+
shown: { opacity: 1, y: 0, transition: transitions.panel },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ABOUT_CAT_MOTION: Variants = {
|
|
31
|
+
hidden: { opacity: 0, x: 18, rotate: 3 },
|
|
32
|
+
shown: {
|
|
33
|
+
opacity: 1,
|
|
34
|
+
x: 0,
|
|
35
|
+
rotate: 0,
|
|
36
|
+
transition: transitions.narrative,
|
|
37
|
+
},
|
|
38
|
+
hover: {
|
|
39
|
+
y: -7,
|
|
40
|
+
rotate: -2,
|
|
41
|
+
scale: 1.035,
|
|
42
|
+
transition: transitions.settle,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function AboutSettings() {
|
|
47
|
+
return (
|
|
48
|
+
<section>
|
|
49
|
+
<div className="grid gap-grid lg:grid-cols-[minmax(0,1.25fr)_minmax(17rem,0.75fr)]">
|
|
50
|
+
<m.div
|
|
51
|
+
data-about-card
|
|
52
|
+
className="card grid items-center gap-4 sm:grid-cols-[minmax(0,1fr)_180px]"
|
|
53
|
+
variants={ABOUT_CARD_MOTION}
|
|
54
|
+
initial="hidden"
|
|
55
|
+
animate="shown"
|
|
56
|
+
whileHover="hover"
|
|
57
|
+
>
|
|
58
|
+
<m.div
|
|
59
|
+
className="flex flex-col items-start"
|
|
60
|
+
variants={ABOUT_COPY_MOTION}
|
|
61
|
+
>
|
|
62
|
+
<p className="max-w-prose text-muted">
|
|
63
|
+
Architecture from code and evidence. Product source,
|
|
64
|
+
documentation, releases and issue tracking live in the public
|
|
65
|
+
GitHub repository.
|
|
66
|
+
</p>
|
|
67
|
+
<div className="mt-5 flex flex-wrap gap-2">
|
|
68
|
+
<Link to={paths.landing()} className="product-primary">
|
|
69
|
+
Product page <ArrowRight size={15} aria-hidden />
|
|
70
|
+
</Link>
|
|
71
|
+
<a
|
|
72
|
+
href={PRODUCT_REPOSITORY}
|
|
73
|
+
target="_blank"
|
|
74
|
+
rel="noreferrer"
|
|
75
|
+
className="tbtn text-ink"
|
|
76
|
+
>
|
|
77
|
+
<GitBranch size={15} aria-hidden /> View product on GitHub
|
|
78
|
+
</a>
|
|
79
|
+
</div>
|
|
80
|
+
</m.div>
|
|
81
|
+
<m.div
|
|
82
|
+
data-about-cat
|
|
83
|
+
className="h-44 w-full justify-self-center sm:h-52"
|
|
84
|
+
variants={ABOUT_CAT_MOTION}
|
|
85
|
+
>
|
|
86
|
+
<CatIllustration scene="about" className="h-full w-full" />
|
|
87
|
+
</m.div>
|
|
88
|
+
</m.div>
|
|
89
|
+
<m.div
|
|
90
|
+
className="card"
|
|
91
|
+
initial={{ opacity: 0, y: 8 }}
|
|
92
|
+
animate={{ opacity: 1, y: 0 }}
|
|
93
|
+
transition={{ ...transitions.page, delay: 0.06 }}
|
|
94
|
+
>
|
|
95
|
+
<div className="label mb-2">resources</div>
|
|
96
|
+
<div className="divide-y divide-line">
|
|
97
|
+
<a
|
|
98
|
+
href={PRODUCT_README}
|
|
99
|
+
target="_blank"
|
|
100
|
+
rel="noreferrer"
|
|
101
|
+
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
102
|
+
>
|
|
103
|
+
<BookOpen size={15} aria-hidden className="shrink-0" />
|
|
104
|
+
Documentation
|
|
105
|
+
</a>
|
|
106
|
+
<a
|
|
107
|
+
href={PRODUCT_ISSUES}
|
|
108
|
+
target="_blank"
|
|
109
|
+
rel="noreferrer"
|
|
110
|
+
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
111
|
+
>
|
|
112
|
+
<CircleDot size={15} aria-hidden className="shrink-0" />
|
|
113
|
+
Issues and feedback
|
|
114
|
+
</a>
|
|
115
|
+
<a
|
|
116
|
+
href={PRODUCT_LICENSE}
|
|
117
|
+
target="_blank"
|
|
118
|
+
rel="noreferrer"
|
|
119
|
+
className="flex items-center gap-2 py-2.5 text-muted transition-colors hover:text-ink"
|
|
120
|
+
>
|
|
121
|
+
<ShieldCheck size={15} aria-hidden className="shrink-0" />
|
|
122
|
+
MIT License
|
|
123
|
+
</a>
|
|
124
|
+
</div>
|
|
125
|
+
</m.div>
|
|
126
|
+
</div>
|
|
127
|
+
</section>
|
|
128
|
+
);
|
|
129
|
+
}
|