@burdenoff/microfe-cosmicintersection 2026.704.1 → 2026.704.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js +31 -23
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js +131 -66
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
- package/dist/cosmicintersection/components/ActivityFeedPanel.js +68 -0
- package/dist/cosmicintersection/components/ActivityFeedPanel.js.map +1 -0
- package/dist/cosmicintersection/components/DiscussionPanel.js +57 -0
- package/dist/cosmicintersection/components/DiscussionPanel.js.map +1 -0
- package/dist/cosmicintersection/components/index.js +5 -3
- package/dist/cosmicintersection/pages/access/AccessPage.js +24 -17
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +12 -12
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +109 -96
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js +87 -61
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js +51 -42
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +5 -3
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +172 -152
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js +7 -7
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +205 -196
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js +211 -161
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js +113 -108
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js +86 -101
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js +205 -0
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/sources/SourcesPage.js +110 -104
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
|
|
2
|
+
import { notify as t } from "../../lib/toast.js";
|
|
3
|
+
import { useCosArchiveSourceMutation as n, useCosMarkSourceSyncedMutation as r, useCosSourceQuery as i } from "../../api/cosmicApi.js";
|
|
4
|
+
import a from "../../components/ActionMenu.js";
|
|
5
|
+
import o from "../../components/EmptyState.js";
|
|
6
|
+
import s from "../../components/ErrorState.js";
|
|
7
|
+
import c from "../../components/LoadingState.js";
|
|
8
|
+
import l from "../../components/SafeLink.js";
|
|
9
|
+
import u from "../../components/PageLayout.js";
|
|
10
|
+
import d, { formatEnumLabel as f } from "../../components/StatusBadge.js";
|
|
11
|
+
import "../../components/index.js";
|
|
12
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
13
|
+
import { useParams as h } from "react-router-dom";
|
|
14
|
+
import { Database as g, Download as _, Pencil as v, Radio as y, RefreshCw as b } from "lucide-react";
|
|
15
|
+
import { Badge as x, Button as S, Table as C, TableBody as w, TableCell as T, TableHead as E, TableHeader as D, TableRow as O } from "@burdenoff/fe-libs/ui";
|
|
16
|
+
//#region src/cosmicintersection/pages/sources/SourceDetailPage.tsx
|
|
17
|
+
function k(e) {
|
|
18
|
+
if (!e) return "—";
|
|
19
|
+
let t = new Date(e);
|
|
20
|
+
return Number.isNaN(t.getTime()) ? "—" : t.toLocaleString();
|
|
21
|
+
}
|
|
22
|
+
function A(e) {
|
|
23
|
+
if (!e) return "—";
|
|
24
|
+
let t = new Date(e);
|
|
25
|
+
return Number.isNaN(t.getTime()) ? "—" : t.toLocaleDateString(void 0, {
|
|
26
|
+
year: "numeric",
|
|
27
|
+
month: "short",
|
|
28
|
+
day: "numeric"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function j({ label: e, value: t }) {
|
|
32
|
+
return /* @__PURE__ */ m("div", {
|
|
33
|
+
className: "rounded-card border border-border-default bg-bg-elevated p-4",
|
|
34
|
+
children: [/* @__PURE__ */ p("p", {
|
|
35
|
+
className: "text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
36
|
+
children: e
|
|
37
|
+
}), /* @__PURE__ */ p("div", {
|
|
38
|
+
className: "mt-1 text-sm text-text-primary",
|
|
39
|
+
children: t
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function M() {
|
|
44
|
+
let { id: M = "" } = h(), { navigate: N } = e(), { data: P, loading: F, error: I, refetch: L } = i(M), R = P?.cosSource ?? null, [z, { loading: B }] = r(), [V] = n(), H = async () => {
|
|
45
|
+
R && (await z({ variables: { id: R.id } })).data?.cosMarkSourceSynced && (t.success("Source marked synced", `“${R.name}” is now up to date.`), await L());
|
|
46
|
+
}, U = async () => {
|
|
47
|
+
if (!R) return;
|
|
48
|
+
let e = await V({ variables: { id: R.id } });
|
|
49
|
+
if (e.error) throw Error(e.error.message);
|
|
50
|
+
if (!e.data?.cosArchiveSource) throw t.error("Could not archive source", "The server did not confirm the change."), Error("Archive not confirmed");
|
|
51
|
+
t.success("Source archived"), N("sources");
|
|
52
|
+
};
|
|
53
|
+
if (F) return /* @__PURE__ */ p(u, {
|
|
54
|
+
title: "Source",
|
|
55
|
+
backPath: "sources",
|
|
56
|
+
children: /* @__PURE__ */ p(c, { label: "Loading source…" })
|
|
57
|
+
});
|
|
58
|
+
if (I) return /* @__PURE__ */ p(u, {
|
|
59
|
+
title: "Source",
|
|
60
|
+
backPath: "sources",
|
|
61
|
+
children: /* @__PURE__ */ p(s, {
|
|
62
|
+
message: "We could not load this source.",
|
|
63
|
+
onRetry: () => void L()
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
if (!R) return /* @__PURE__ */ p(u, {
|
|
67
|
+
title: "Source",
|
|
68
|
+
backPath: "sources",
|
|
69
|
+
children: /* @__PURE__ */ p(o, {
|
|
70
|
+
icon: y,
|
|
71
|
+
title: "Source not found.",
|
|
72
|
+
description: "This source may have been removed or is outside your current scope.",
|
|
73
|
+
action: /* @__PURE__ */ p(S, {
|
|
74
|
+
type: "button",
|
|
75
|
+
onClick: () => N("sources"),
|
|
76
|
+
children: "Back to sources"
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
let W = R.datasets ?? [];
|
|
81
|
+
return /* @__PURE__ */ p(u, {
|
|
82
|
+
title: R.name,
|
|
83
|
+
description: R.description ?? void 0,
|
|
84
|
+
backPath: "sources",
|
|
85
|
+
actions: /* @__PURE__ */ m("div", {
|
|
86
|
+
className: "flex items-center gap-2",
|
|
87
|
+
children: [/* @__PURE__ */ m(S, {
|
|
88
|
+
type: "button",
|
|
89
|
+
variant: "outline",
|
|
90
|
+
onClick: () => N(`sources/${R.id}/edit`),
|
|
91
|
+
children: [/* @__PURE__ */ p(v, { className: "size-4" }), "Edit"]
|
|
92
|
+
}), /* @__PURE__ */ p(a, {
|
|
93
|
+
ariaLabel: `Actions for ${R.name}`,
|
|
94
|
+
entityName: R.name,
|
|
95
|
+
onDelete: () => U(),
|
|
96
|
+
deleteLabel: "Archive source",
|
|
97
|
+
extraItems: [{
|
|
98
|
+
key: "mark-synced",
|
|
99
|
+
label: B ? "Marking synced…" : "Mark synced",
|
|
100
|
+
icon: b,
|
|
101
|
+
disabled: B,
|
|
102
|
+
onSelect: () => void H()
|
|
103
|
+
}]
|
|
104
|
+
})]
|
|
105
|
+
}),
|
|
106
|
+
children: /* @__PURE__ */ m("div", {
|
|
107
|
+
className: "space-y-6",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ m("div", {
|
|
110
|
+
className: "flex flex-wrap items-center gap-2",
|
|
111
|
+
children: [/* @__PURE__ */ p(d, { status: R.status }), /* @__PURE__ */ p(x, {
|
|
112
|
+
variant: "secondary",
|
|
113
|
+
children: f(R.type)
|
|
114
|
+
})]
|
|
115
|
+
}),
|
|
116
|
+
/* @__PURE__ */ m("div", {
|
|
117
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ p(j, {
|
|
120
|
+
label: "Endpoint",
|
|
121
|
+
value: R.endpoint ? /* @__PURE__ */ m(l, {
|
|
122
|
+
href: R.endpoint,
|
|
123
|
+
className: "inline-flex items-center gap-1.5 truncate text-text-primary underline-offset-2 hover:underline",
|
|
124
|
+
fallbackClassName: "truncate text-text-secondary",
|
|
125
|
+
children: [/* @__PURE__ */ p(_, {
|
|
126
|
+
className: "size-4 shrink-0 text-text-secondary",
|
|
127
|
+
"aria-hidden": "true"
|
|
128
|
+
}), /* @__PURE__ */ p("span", {
|
|
129
|
+
className: "truncate",
|
|
130
|
+
children: R.endpoint
|
|
131
|
+
})]
|
|
132
|
+
}) : /* @__PURE__ */ p("span", {
|
|
133
|
+
className: "text-text-secondary",
|
|
134
|
+
children: "Not set"
|
|
135
|
+
})
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ p(j, {
|
|
138
|
+
label: "Last synced",
|
|
139
|
+
value: /* @__PURE__ */ p("span", { children: k(R.lastSyncedAt) })
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ p(j, {
|
|
142
|
+
label: "Created",
|
|
143
|
+
value: /* @__PURE__ */ p("span", { children: k(R.createdAt) })
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ p(j, {
|
|
146
|
+
label: "Updated",
|
|
147
|
+
value: /* @__PURE__ */ p("span", { children: k(R.updatedAt) })
|
|
148
|
+
})
|
|
149
|
+
]
|
|
150
|
+
}),
|
|
151
|
+
R.tags.length > 0 ? /* @__PURE__ */ p("div", {
|
|
152
|
+
className: "flex flex-wrap gap-1",
|
|
153
|
+
children: R.tags.map((e) => /* @__PURE__ */ p(x, {
|
|
154
|
+
variant: "outline",
|
|
155
|
+
className: "text-xs",
|
|
156
|
+
children: e
|
|
157
|
+
}, e))
|
|
158
|
+
}) : null,
|
|
159
|
+
/* @__PURE__ */ m("section", {
|
|
160
|
+
className: "space-y-3",
|
|
161
|
+
children: [/* @__PURE__ */ p("h2", {
|
|
162
|
+
className: "text-lg font-semibold text-text-primary",
|
|
163
|
+
children: "Datasets from this source"
|
|
164
|
+
}), W.length === 0 ? /* @__PURE__ */ p(o, {
|
|
165
|
+
icon: g,
|
|
166
|
+
title: "No datasets from this source yet",
|
|
167
|
+
description: "Datasets that ingest from this source will be listed here."
|
|
168
|
+
}) : /* @__PURE__ */ p("div", {
|
|
169
|
+
className: "overflow-hidden rounded-card border border-border-default bg-bg-elevated",
|
|
170
|
+
children: /* @__PURE__ */ m(C, { children: [/* @__PURE__ */ p(D, { children: /* @__PURE__ */ m(O, { children: [
|
|
171
|
+
/* @__PURE__ */ p(E, { children: "Name" }),
|
|
172
|
+
/* @__PURE__ */ p(E, { children: "Status" }),
|
|
173
|
+
/* @__PURE__ */ p(E, { children: "Access" }),
|
|
174
|
+
/* @__PURE__ */ p(E, { children: "Rows" }),
|
|
175
|
+
/* @__PURE__ */ p(E, { children: "Updated" })
|
|
176
|
+
] }) }), /* @__PURE__ */ p(w, { children: W.map((e) => /* @__PURE__ */ m(O, {
|
|
177
|
+
className: "cursor-pointer",
|
|
178
|
+
onClick: () => N(`datasets/${e.id}`),
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ p(T, {
|
|
181
|
+
className: "font-medium text-text-primary",
|
|
182
|
+
children: e.name
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ p(T, { children: /* @__PURE__ */ p(d, { status: e.status }) }),
|
|
185
|
+
/* @__PURE__ */ p(T, { children: /* @__PURE__ */ p(d, { status: e.accessLevel }) }),
|
|
186
|
+
/* @__PURE__ */ p(T, {
|
|
187
|
+
className: "tabular-nums text-sm text-text-secondary",
|
|
188
|
+
children: Number(e.rowCount) > 0 ? Number(e.rowCount).toLocaleString() : "—"
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ p(T, {
|
|
191
|
+
className: "whitespace-nowrap text-sm text-text-secondary",
|
|
192
|
+
children: A(e.updatedAt)
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
}, e.id)) })] })
|
|
196
|
+
})]
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
export { M as default };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=SourceDetailPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceDetailPage.tsx"],"sourcesContent":["/**\n * SourceDetailPage — full record for one data source.\n *\n * Mirrors CollectionDetailPage: a header with the source's metadata (type/status\n * badges, endpoint, last-synced, tags) over a table of the datasets ingested from\n * this source. Header actions edit the source, mark it synced\n * (`cosMarkSourceSynced`) or archive it (`cosArchiveSource`). Everything is read\n * from the backend — no fabricated data.\n */\nimport { Database, Download, Pencil, Radio, RefreshCw } from 'lucide-react';\nimport {\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourceQuery,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nfunction formatDateTime(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();\n}\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nfunction MetaTile({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"rounded-card border border-border-default bg-bg-elevated p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <div className=\"mt-1 text-sm text-text-primary\">{value}</div>\n </div>\n );\n}\n\nexport default function SourceDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n\n const { data, loading, error, refetch } = useCosSourceQuery(id);\n const source = data?.cosSource ?? null;\n\n const [markSynced, { loading: syncing }] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const handleMarkSynced = async () => {\n if (!source) {\n return;\n }\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async () => {\n if (!source) {\n return;\n }\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n navigate('sources');\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <LoadingState label=\"Loading source…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <ErrorState message=\"We could not load this source.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!source) {\n return (\n <PageLayout title=\"Source\" backPath=\"sources\">\n <EmptyState\n icon={Radio}\n title=\"Source not found.\"\n description=\"This source may have been removed or is outside your current scope.\"\n action={\n <Button type=\"button\" onClick={() => navigate('sources')}>\n Back to sources\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const datasets = source.datasets ?? [];\n\n const renderDatasets = () => {\n if (datasets.length === 0) {\n return (\n <EmptyState\n icon={Database}\n title=\"No datasets from this source yet\"\n description=\"Datasets that ingest from this source will be listed here.\"\n />\n );\n }\n return (\n <div className=\"overflow-hidden rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Rows</TableHead>\n <TableHead>Updated</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell className=\"font-medium text-text-primary\">{dataset.name}</TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.accessLevel} />\n </TableCell>\n <TableCell className=\"tabular-nums text-sm text-text-secondary\">\n {Number(dataset.rowCount) > 0\n ? Number(dataset.rowCount).toLocaleString()\n : '—'}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n );\n };\n\n return (\n <PageLayout\n title={source.name}\n description={source.description ?? undefined}\n backPath=\"sources\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`sources/${source.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onDelete={() => handleArchive()}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: syncing ? 'Marking synced…' : 'Mark synced',\n icon: RefreshCw,\n disabled: syncing,\n onSelect: () => void handleMarkSynced(),\n },\n ]}\n />\n </div>\n }\n >\n <div className=\"space-y-6\">\n {/* Status + key facts */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={source.status} />\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </div>\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <MetaTile\n label=\"Endpoint\"\n value={\n source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"inline-flex items-center gap-1.5 truncate text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"truncate text-text-secondary\"\n >\n <Download className=\"size-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"truncate\">{source.endpoint}</span>\n </SafeLink>\n ) : (\n <span className=\"text-text-secondary\">Not set</span>\n )\n }\n />\n <MetaTile label=\"Last synced\" value={<span>{formatDateTime(source.lastSyncedAt)}</span>} />\n <MetaTile label=\"Created\" value={<span>{formatDateTime(source.createdAt)}</span>} />\n <MetaTile label=\"Updated\" value={<span>{formatDateTime(source.updatedAt)}</span>} />\n </div>\n\n {source.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {source.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </div>\n ) : null}\n\n {/* Datasets */}\n <section className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Datasets from this source</h2>\n {renderDatasets()}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
|
|
2
2
|
import { notify as t } from "../../lib/toast.js";
|
|
3
3
|
import { useCosArchiveSourceMutation as n, useCosMarkSourceSyncedMutation as r, useCosSourcesQuery as i } from "../../api/cosmicApi.js";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import d from "../../components/
|
|
11
|
-
import
|
|
4
|
+
import a from "../../components/ActionMenu.js";
|
|
5
|
+
import o from "../../components/EmptyState.js";
|
|
6
|
+
import s from "../../components/ErrorState.js";
|
|
7
|
+
import c from "../../components/LoadingState.js";
|
|
8
|
+
import l from "../../components/SafeLink.js";
|
|
9
|
+
import u from "../../components/PageLayout.js";
|
|
10
|
+
import d, { usePagination as f } from "../../components/Pagination.js";
|
|
11
|
+
import p, { formatEnumLabel as m } from "../../components/StatusBadge.js";
|
|
12
12
|
import "../../components/index.js";
|
|
13
|
-
import { useMemo as
|
|
14
|
-
import { Fragment as
|
|
15
|
-
import { Plus as
|
|
13
|
+
import { useMemo as h, useState as g } from "react";
|
|
14
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
15
|
+
import { Plus as ee, Radio as b, RefreshCw as x } from "lucide-react";
|
|
16
16
|
import { Badge as S, Button as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O, Table as k, TableBody as A, TableCell as j, TableHead as M, TableHeader as N, TableRow as P } from "@burdenoff/fe-libs/ui";
|
|
17
17
|
//#region src/cosmicintersection/pages/sources/SourcesPage.tsx
|
|
18
18
|
var F = "ALL", I = [
|
|
@@ -42,7 +42,7 @@ function R(e) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
function z() {
|
|
45
|
-
let { navigate: z } = e(), [B, V] =
|
|
45
|
+
let { navigate: z } = e(), [B, V] = g(F), [H, U] = g(F), W = f({
|
|
46
46
|
pageSize: 20,
|
|
47
47
|
resetKey: `${B}|${H}`
|
|
48
48
|
}), { data: G, loading: K, error: q, refetch: J } = i({
|
|
@@ -52,152 +52,158 @@ function z() {
|
|
|
52
52
|
limit: W.limit,
|
|
53
53
|
offset: W.offset
|
|
54
54
|
}
|
|
55
|
-
}), [Y] = r(), [X] = n(), Z =
|
|
55
|
+
}), [Y] = r(), [X] = n(), Z = h(() => G?.cosListSources.items ?? [], [G]), Q = G?.cosListSources.pageInfo, te = async (e) => {
|
|
56
56
|
(await Y({ variables: { id: e.id } })).data?.cosMarkSourceSynced && (t.success("Source marked synced", `“${e.name}” is now up to date.`), await J());
|
|
57
57
|
}, ne = async (e) => {
|
|
58
58
|
let n = await X({ variables: { id: e.id } });
|
|
59
59
|
if (n.error) throw Error(n.error.message);
|
|
60
60
|
if (!n.data?.cosArchiveSource) throw t.error("Could not archive source", "The server did not confirm the change."), Error("Archive not confirmed");
|
|
61
61
|
t.success("Source archived"), await J();
|
|
62
|
-
}, $ = /* @__PURE__ */
|
|
62
|
+
}, $ = /* @__PURE__ */ y(C, {
|
|
63
63
|
type: "button",
|
|
64
64
|
onClick: () => z("sources/create"),
|
|
65
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ v(ee, { className: "size-4" }), "New source"]
|
|
66
66
|
});
|
|
67
|
-
return /* @__PURE__ */
|
|
67
|
+
return /* @__PURE__ */ v(u, {
|
|
68
68
|
title: "Data sources",
|
|
69
69
|
description: "Instruments, archives and feeds that supply raw data into your federation.",
|
|
70
70
|
actions: $,
|
|
71
|
-
children: /* @__PURE__ */
|
|
71
|
+
children: /* @__PURE__ */ y("div", {
|
|
72
72
|
className: "space-y-4",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ y("div", {
|
|
74
74
|
className: "flex flex-wrap items-center gap-3",
|
|
75
|
-
children: [/* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ v("div", {
|
|
76
76
|
className: "w-full max-w-[14rem]",
|
|
77
|
-
children: /* @__PURE__ */
|
|
77
|
+
children: /* @__PURE__ */ y(w, {
|
|
78
78
|
value: B,
|
|
79
79
|
onValueChange: V,
|
|
80
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ v(D, {
|
|
81
81
|
"aria-label": "Filter by type",
|
|
82
|
-
children: /* @__PURE__ */
|
|
83
|
-
}), /* @__PURE__ */
|
|
82
|
+
children: /* @__PURE__ */ v(O, { placeholder: "All types" })
|
|
83
|
+
}), /* @__PURE__ */ y(T, { children: [/* @__PURE__ */ v(E, {
|
|
84
84
|
value: F,
|
|
85
85
|
children: "All types"
|
|
86
|
-
}), I.map((e) => /* @__PURE__ */
|
|
86
|
+
}), I.map((e) => /* @__PURE__ */ v(E, {
|
|
87
87
|
value: e,
|
|
88
|
-
children:
|
|
88
|
+
children: m(e)
|
|
89
89
|
}, e))] })]
|
|
90
90
|
})
|
|
91
|
-
}), /* @__PURE__ */
|
|
91
|
+
}), /* @__PURE__ */ v("div", {
|
|
92
92
|
className: "w-full max-w-[14rem]",
|
|
93
|
-
children: /* @__PURE__ */
|
|
93
|
+
children: /* @__PURE__ */ y(w, {
|
|
94
94
|
value: H,
|
|
95
95
|
onValueChange: U,
|
|
96
|
-
children: [/* @__PURE__ */
|
|
96
|
+
children: [/* @__PURE__ */ v(D, {
|
|
97
97
|
"aria-label": "Filter by status",
|
|
98
|
-
children: /* @__PURE__ */
|
|
99
|
-
}), /* @__PURE__ */
|
|
98
|
+
children: /* @__PURE__ */ v(O, { placeholder: "All statuses" })
|
|
99
|
+
}), /* @__PURE__ */ y(T, { children: [/* @__PURE__ */ v(E, {
|
|
100
100
|
value: F,
|
|
101
101
|
children: "All statuses"
|
|
102
|
-
}), L.map((e) => /* @__PURE__ */
|
|
102
|
+
}), L.map((e) => /* @__PURE__ */ v(E, {
|
|
103
103
|
value: e,
|
|
104
|
-
children:
|
|
104
|
+
children: m(e)
|
|
105
105
|
}, e))] })]
|
|
106
106
|
})
|
|
107
107
|
})]
|
|
108
108
|
}), (() => {
|
|
109
|
-
if (K) return /* @__PURE__ */
|
|
109
|
+
if (K) return /* @__PURE__ */ v(c, {
|
|
110
110
|
variant: "rows",
|
|
111
111
|
rows: 6,
|
|
112
112
|
label: "Loading sources…"
|
|
113
113
|
});
|
|
114
|
-
if (q) return /* @__PURE__ */
|
|
114
|
+
if (q) return /* @__PURE__ */ v(s, {
|
|
115
115
|
message: q.message,
|
|
116
116
|
onRetry: () => void J()
|
|
117
117
|
});
|
|
118
118
|
if (Z.length === 0) {
|
|
119
119
|
let e = B !== F || H !== F;
|
|
120
|
-
return /* @__PURE__ */
|
|
120
|
+
return /* @__PURE__ */ v(o, {
|
|
121
121
|
icon: b,
|
|
122
122
|
title: e ? "No sources match these filters" : "No data sources yet",
|
|
123
123
|
description: e ? "Try clearing the type or status filter to see more sources." : "Connect a telescope, satellite feed, archive or API to start ingesting scientific data.",
|
|
124
124
|
action: e ? void 0 : $
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
-
return /* @__PURE__ */
|
|
127
|
+
return /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
|
|
128
128
|
className: "overflow-hidden rounded-card border border-border-default bg-bg-elevated",
|
|
129
|
-
children: /* @__PURE__ */
|
|
130
|
-
/* @__PURE__ */
|
|
131
|
-
/* @__PURE__ */
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
/* @__PURE__ */
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
-
/* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ y(k, { children: [/* @__PURE__ */ v(N, { children: /* @__PURE__ */ y(P, { children: [
|
|
130
|
+
/* @__PURE__ */ v(M, { children: "Name" }),
|
|
131
|
+
/* @__PURE__ */ v(M, { children: "Type" }),
|
|
132
|
+
/* @__PURE__ */ v(M, { children: "Status" }),
|
|
133
|
+
/* @__PURE__ */ v(M, { children: "Endpoint" }),
|
|
134
|
+
/* @__PURE__ */ v(M, { children: "Last synced" }),
|
|
135
|
+
/* @__PURE__ */ v(M, { children: "Tags" }),
|
|
136
|
+
/* @__PURE__ */ v(M, {
|
|
137
137
|
className: "text-right",
|
|
138
138
|
children: "Actions"
|
|
139
139
|
})
|
|
140
|
-
] }) }), /* @__PURE__ */
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
children:
|
|
156
|
-
|
|
157
|
-
className: "
|
|
158
|
-
|
|
159
|
-
children: e.endpoint
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
140
|
+
] }) }), /* @__PURE__ */ v(A, { children: Z.map((e) => /* @__PURE__ */ y(P, {
|
|
141
|
+
className: "cursor-pointer",
|
|
142
|
+
onClick: () => z(`sources/${e.id}`),
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ y(j, { children: [/* @__PURE__ */ v("div", {
|
|
145
|
+
className: "font-medium text-text-primary",
|
|
146
|
+
children: e.name
|
|
147
|
+
}), e.description ? /* @__PURE__ */ v("div", {
|
|
148
|
+
className: "mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary",
|
|
149
|
+
children: e.description
|
|
150
|
+
}) : null] }),
|
|
151
|
+
/* @__PURE__ */ v(j, { children: /* @__PURE__ */ v(S, {
|
|
152
|
+
variant: "secondary",
|
|
153
|
+
children: m(e.type)
|
|
154
|
+
}) }),
|
|
155
|
+
/* @__PURE__ */ v(j, { children: /* @__PURE__ */ v(p, { status: e.status }) }),
|
|
156
|
+
/* @__PURE__ */ v(j, {
|
|
157
|
+
className: "max-w-[16rem]",
|
|
158
|
+
onClick: (e) => e.stopPropagation(),
|
|
159
|
+
children: e.endpoint ? /* @__PURE__ */ v(l, {
|
|
160
|
+
href: e.endpoint,
|
|
161
|
+
className: "block truncate text-sm text-text-primary underline-offset-2 hover:underline",
|
|
162
|
+
fallbackClassName: "block truncate text-sm text-text-secondary",
|
|
163
|
+
children: e.endpoint
|
|
164
|
+
}) : /* @__PURE__ */ v("span", {
|
|
165
|
+
className: "text-sm text-text-secondary",
|
|
166
|
+
children: "—"
|
|
167
|
+
})
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ v(j, {
|
|
170
|
+
className: "whitespace-nowrap text-sm text-text-secondary",
|
|
171
|
+
children: R(e.lastSyncedAt)
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ v(j, { children: /* @__PURE__ */ y("div", {
|
|
174
|
+
className: "flex max-w-[12rem] flex-wrap gap-1",
|
|
175
|
+
children: [e.tags.length === 0 ? /* @__PURE__ */ v("span", {
|
|
176
|
+
className: "text-xs text-text-secondary",
|
|
177
|
+
children: "—"
|
|
178
|
+
}) : e.tags.slice(0, 3).map((e) => /* @__PURE__ */ v(S, {
|
|
179
|
+
variant: "outline",
|
|
180
|
+
className: "text-xs",
|
|
181
|
+
children: e
|
|
182
|
+
}, e)), e.tags.length > 3 ? /* @__PURE__ */ y("span", {
|
|
183
|
+
className: "text-xs text-text-secondary",
|
|
184
|
+
children: ["+", e.tags.length - 3]
|
|
185
|
+
}) : null]
|
|
186
|
+
}) }),
|
|
187
|
+
/* @__PURE__ */ v(j, {
|
|
188
|
+
className: "text-right",
|
|
189
|
+
children: /* @__PURE__ */ v(a, {
|
|
190
|
+
ariaLabel: `Actions for ${e.name}`,
|
|
191
|
+
entityName: e.name,
|
|
192
|
+
onView: () => z(`sources/${e.id}`),
|
|
193
|
+
onEdit: () => z(`sources/${e.id}/edit`),
|
|
194
|
+
onDelete: () => ne(e),
|
|
195
|
+
deleteLabel: "Archive source",
|
|
196
|
+
extraItems: [{
|
|
197
|
+
key: "mark-synced",
|
|
198
|
+
label: "Mark synced",
|
|
199
|
+
icon: x,
|
|
200
|
+
onSelect: () => void te(e)
|
|
201
|
+
}]
|
|
202
|
+
})
|
|
163
203
|
})
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
children: R(e.lastSyncedAt)
|
|
168
|
-
}),
|
|
169
|
-
/* @__PURE__ */ _(j, { children: /* @__PURE__ */ v("div", {
|
|
170
|
-
className: "flex max-w-[12rem] flex-wrap gap-1",
|
|
171
|
-
children: [e.tags.length === 0 ? /* @__PURE__ */ _("span", {
|
|
172
|
-
className: "text-xs text-text-secondary",
|
|
173
|
-
children: "—"
|
|
174
|
-
}) : e.tags.slice(0, 3).map((e) => /* @__PURE__ */ _(S, {
|
|
175
|
-
variant: "outline",
|
|
176
|
-
className: "text-xs",
|
|
177
|
-
children: e
|
|
178
|
-
}, e)), e.tags.length > 3 ? /* @__PURE__ */ v("span", {
|
|
179
|
-
className: "text-xs text-text-secondary",
|
|
180
|
-
children: ["+", e.tags.length - 3]
|
|
181
|
-
}) : null]
|
|
182
|
-
}) }),
|
|
183
|
-
/* @__PURE__ */ _(j, {
|
|
184
|
-
className: "text-right",
|
|
185
|
-
children: /* @__PURE__ */ _(ee, {
|
|
186
|
-
ariaLabel: `Actions for ${e.name}`,
|
|
187
|
-
entityName: e.name,
|
|
188
|
-
onEdit: () => z(`sources/${e.id}/edit`),
|
|
189
|
-
onDelete: () => ne(e),
|
|
190
|
-
deleteLabel: "Archive source",
|
|
191
|
-
extraItems: [{
|
|
192
|
-
key: "mark-synced",
|
|
193
|
-
label: "Mark synced",
|
|
194
|
-
icon: x,
|
|
195
|
-
onSelect: () => void te(e)
|
|
196
|
-
}]
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
] }, e.id)) })] })
|
|
200
|
-
}), /* @__PURE__ */ _(l, {
|
|
204
|
+
]
|
|
205
|
+
}, e.id)) })] })
|
|
206
|
+
}), /* @__PURE__ */ v(d, {
|
|
201
207
|
offset: W.offset,
|
|
202
208
|
limit: W.limit,
|
|
203
209
|
loadedCount: Z.length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourcesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Plus, RefreshCw, Radio } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourcesQuery,\n type CosSource,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function SourcesPage() {\n const { navigate } = useCosmicIntersection();\n const [typeFilter, setTypeFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: `${typeFilter}|${statusFilter}` });\n\n const { data, loading, error, refetch } = useCosSourcesQuery({\n type: typeFilter === ALL ? undefined : (typeFilter as CosSourceType),\n status: statusFilter === ALL ? undefined : (statusFilter as CosSourceStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [markSynced] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const sources = useMemo(() => data?.cosListSources.items ?? [], [data]);\n const pageInfo = data?.cosListSources.pageInfo;\n\n const handleMarkSynced = async (source: CosSource) => {\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async (source: CosSource) => {\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n await refetch();\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('sources/create')}>\n <Plus className=\"size-4\" />\n New source\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading sources…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (sources.length === 0) {\n const filtered = typeFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={Radio}\n title={filtered ? 'No sources match these filters' : 'No data sources yet'}\n description={\n filtered\n ? 'Try clearing the type or status filter to see more sources.'\n : 'Connect a telescope, satellite feed, archive or API to start ingesting scientific data.'\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n <div className=\"overflow-hidden rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Endpoint</TableHead>\n <TableHead>Last synced</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sources.map((source) => (\n <TableRow key={source.id}>\n <TableCell>\n <div className=\"font-medium text-text-primary\">{source.name}</div>\n {source.description ? (\n <div className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {source.description}\n </div>\n ) : null}\n </TableCell>\n <TableCell>\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </TableCell>\n <TableCell>\n <StatusBadge status={source.status} />\n </TableCell>\n <TableCell className=\"max-w-[16rem]\">\n {source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(source.lastSyncedAt)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {source.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n source.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))\n )}\n {source.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 3}\n </span>\n ) : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: 'Mark synced',\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={sources.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"sources\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Data sources\"\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={typeFilter} onValueChange={setTypeFilter}>\n <SelectTrigger aria-label=\"Filter by type\">\n <SelectValue placeholder=\"All types\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All types</SelectItem>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SourcesPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourcesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Plus, RefreshCw, Radio } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveSourceMutation,\n useCosMarkSourceSyncedMutation,\n useCosSourcesQuery,\n type CosSource,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n SafeLink,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst ALL = 'ALL';\n\nconst SOURCE_TYPES: CosSourceType[] = [\n 'TELESCOPE',\n 'SATELLITE',\n 'GROUND_SENSOR',\n 'ARCHIVE',\n 'API',\n 'STREAM',\n 'FILE_DROP',\n 'SIMULATION',\n];\n\nconst SOURCE_STATUSES: CosSourceStatus[] = [\n 'CONFIGURING',\n 'CONNECTED',\n 'SYNCING',\n 'DEGRADED',\n 'ERROR',\n 'DISABLED',\n];\n\nfunction formatDate(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return '—';\n }\n return parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\nexport default function SourcesPage() {\n const { navigate } = useCosmicIntersection();\n const [typeFilter, setTypeFilter] = useState<string>(ALL);\n const [statusFilter, setStatusFilter] = useState<string>(ALL);\n const pagination = usePagination({ pageSize: 20, resetKey: `${typeFilter}|${statusFilter}` });\n\n const { data, loading, error, refetch } = useCosSourcesQuery({\n type: typeFilter === ALL ? undefined : (typeFilter as CosSourceType),\n status: statusFilter === ALL ? undefined : (statusFilter as CosSourceStatus),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const [markSynced] = useCosMarkSourceSyncedMutation();\n const [archiveSource] = useCosArchiveSourceMutation();\n\n const sources = useMemo(() => data?.cosListSources.items ?? [], [data]);\n const pageInfo = data?.cosListSources.pageInfo;\n\n const handleMarkSynced = async (source: CosSource) => {\n const response = await markSynced({ variables: { id: source.id } });\n if (response.data?.cosMarkSourceSynced) {\n notify.success('Source marked synced', `“${source.name}” is now up to date.`);\n await refetch();\n }\n };\n\n const handleArchive = async (source: CosSource) => {\n const response = await archiveSource({ variables: { id: source.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveSource) {\n notify.error('Could not archive source', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Source archived');\n await refetch();\n };\n\n const newButton = (\n <Button type=\"button\" onClick={() => navigate('sources/create')}>\n <Plus className=\"size-4\" />\n New source\n </Button>\n );\n\n const renderBody = () => {\n if (loading) {\n return <LoadingState variant=\"rows\" rows={6} label=\"Loading sources…\" />;\n }\n if (error) {\n return <ErrorState message={error.message} onRetry={() => void refetch()} />;\n }\n if (sources.length === 0) {\n const filtered = typeFilter !== ALL || statusFilter !== ALL;\n return (\n <EmptyState\n icon={Radio}\n title={filtered ? 'No sources match these filters' : 'No data sources yet'}\n description={\n filtered\n ? 'Try clearing the type or status filter to see more sources.'\n : 'Connect a telescope, satellite feed, archive or API to start ingesting scientific data.'\n }\n action={filtered ? undefined : newButton}\n />\n );\n }\n\n return (\n <>\n <div className=\"overflow-hidden rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Endpoint</TableHead>\n <TableHead>Last synced</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sources.map((source) => (\n <TableRow\n key={source.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`sources/${source.id}`)}\n >\n <TableCell>\n <div className=\"font-medium text-text-primary\">{source.name}</div>\n {source.description ? (\n <div className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {source.description}\n </div>\n ) : null}\n </TableCell>\n <TableCell>\n <Badge variant=\"secondary\">{formatEnumLabel(source.type)}</Badge>\n </TableCell>\n <TableCell>\n <StatusBadge status={source.status} />\n </TableCell>\n <TableCell\n className=\"max-w-[16rem]\"\n onClick={(event) => event.stopPropagation()}\n >\n {source.endpoint ? (\n <SafeLink\n href={source.endpoint}\n className=\"block truncate text-sm text-text-primary underline-offset-2 hover:underline\"\n fallbackClassName=\"block truncate text-sm text-text-secondary\"\n >\n {source.endpoint}\n </SafeLink>\n ) : (\n <span className=\"text-sm text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(source.lastSyncedAt)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {source.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n source.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))\n )}\n {source.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{source.tags.length - 3}\n </span>\n ) : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${source.name}`}\n entityName={source.name}\n onView={() => navigate(`sources/${source.id}`)}\n onEdit={() => navigate(`sources/${source.id}/edit`)}\n onDelete={() => handleArchive(source)}\n deleteLabel=\"Archive source\"\n extraItems={[\n {\n key: 'mark-synced',\n label: 'Mark synced',\n icon: RefreshCw,\n onSelect: () => void handleMarkSynced(source),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={sources.length}\n hasMore={pageInfo?.hasMore ?? false}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"sources\"\n loading={loading}\n />\n </>\n );\n };\n\n return (\n <PageLayout\n title=\"Data sources\"\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n actions={newButton}\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-full max-w-[14rem]\">\n <Select value={typeFilter} onValueChange={setTypeFilter}>\n <SelectTrigger aria-label=\"Filter by type\">\n <SelectValue placeholder=\"All types\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All types</SelectItem>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"w-full max-w-[14rem]\">\n <Select value={statusFilter} onValueChange={setStatusFilter}>\n <SelectTrigger aria-label=\"Filter by status\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {renderBody()}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|