@burdenoff/microfe-cosmicintersection 2026.525.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.
Files changed (120) hide show
  1. package/dist/cosmicintersection/CosmicIntersectionRoot.js +38 -113
  2. package/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
  3. package/dist/cosmicintersection/CosmicIntersectionRoutes.js +110 -44
  4. package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
  5. package/dist/cosmicintersection/api/cosmicApi.js +918 -0
  6. package/dist/cosmicintersection/api/cosmicApi.js.map +1 -0
  7. package/dist/cosmicintersection/components/ActionMenu.js +106 -0
  8. package/dist/cosmicintersection/components/ActionMenu.js.map +1 -0
  9. package/dist/cosmicintersection/components/ActivityFeedPanel.js +68 -0
  10. package/dist/cosmicintersection/components/ActivityFeedPanel.js.map +1 -0
  11. package/dist/cosmicintersection/components/DiscussionPanel.js +57 -0
  12. package/dist/cosmicintersection/components/DiscussionPanel.js.map +1 -0
  13. package/dist/cosmicintersection/components/EmptyState.js +35 -0
  14. package/dist/cosmicintersection/components/EmptyState.js.map +1 -0
  15. package/dist/cosmicintersection/components/EntityFormShell.js +57 -0
  16. package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -0
  17. package/dist/cosmicintersection/components/EntityTagsField.js +77 -0
  18. package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -0
  19. package/dist/cosmicintersection/components/ErrorState.js +40 -0
  20. package/dist/cosmicintersection/components/ErrorState.js.map +1 -0
  21. package/dist/cosmicintersection/components/FileUploadField.js +146 -0
  22. package/dist/cosmicintersection/components/FileUploadField.js.map +1 -0
  23. package/dist/cosmicintersection/components/LoadingState.js +23 -0
  24. package/dist/cosmicintersection/components/LoadingState.js.map +1 -0
  25. package/dist/cosmicintersection/components/PageLayout.js +46 -0
  26. package/dist/cosmicintersection/components/PageLayout.js.map +1 -0
  27. package/dist/cosmicintersection/components/Pagination.js +65 -0
  28. package/dist/cosmicintersection/components/Pagination.js.map +1 -0
  29. package/dist/cosmicintersection/components/SafeLink.js +35 -0
  30. package/dist/cosmicintersection/components/SafeLink.js.map +1 -0
  31. package/dist/cosmicintersection/components/StatusBadge.js +53 -0
  32. package/dist/cosmicintersection/components/StatusBadge.js.map +1 -0
  33. package/dist/cosmicintersection/components/index.js +13 -0
  34. package/dist/cosmicintersection/context/CosmicIntersectionContext.js +46 -0
  35. package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -0
  36. package/dist/cosmicintersection/index.js +0 -2
  37. package/dist/cosmicintersection/lib/mutationResult.js +12 -0
  38. package/dist/cosmicintersection/lib/mutationResult.js.map +1 -0
  39. package/dist/cosmicintersection/lib/toast.js +36 -0
  40. package/dist/cosmicintersection/lib/toast.js.map +1 -0
  41. package/dist/cosmicintersection/pages/access/AccessPage.js +347 -0
  42. package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -0
  43. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +484 -0
  44. package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -0
  45. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +177 -0
  46. package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -0
  47. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js +143 -0
  48. package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -0
  49. package/dist/cosmicintersection/pages/collections/CollectionsPage.js +112 -0
  50. package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -0
  51. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +246 -0
  52. package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -0
  53. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +480 -0
  54. package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -0
  55. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js +218 -0
  56. package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -0
  57. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +252 -0
  58. package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -0
  59. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +402 -0
  60. package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -0
  61. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js +313 -0
  62. package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -0
  63. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +203 -0
  64. package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -0
  65. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +244 -0
  66. package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -0
  67. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +210 -0
  68. package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -0
  69. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +193 -0
  70. package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -0
  71. package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js +184 -0
  72. package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -0
  73. package/dist/cosmicintersection/pages/releases/ReleasesPage.js +418 -0
  74. package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -0
  75. package/dist/cosmicintersection/pages/runs/RunDetailPage.js +380 -0
  76. package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -0
  77. package/dist/cosmicintersection/pages/runs/RunsPage.js +165 -0
  78. package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -0
  79. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js +205 -0
  80. package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -0
  81. package/dist/cosmicintersection/pages/sources/SourceFormPage.js +229 -0
  82. package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -0
  83. package/dist/cosmicintersection/pages/sources/SourcesPage.js +223 -0
  84. package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -0
  85. package/dist/cosmicintersection/utils/cn.js +10 -0
  86. package/dist/cosmicintersection/utils/cn.js.map +1 -0
  87. package/dist/index.js +2 -4
  88. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  89. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  90. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  91. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  92. package/package.json +2 -1
  93. package/dist/cosmicintersection/pages/AiDiscoveryPage.js +0 -363
  94. package/dist/cosmicintersection/pages/AiDiscoveryPage.js.map +0 -1
  95. package/dist/cosmicintersection/pages/DashboardPage.js +0 -306
  96. package/dist/cosmicintersection/pages/DashboardPage.js.map +0 -1
  97. package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js +0 -287
  98. package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js.map +0 -1
  99. package/dist/cosmicintersection/pages/SpaceScienceDataPage.js +0 -385
  100. package/dist/cosmicintersection/pages/SpaceScienceDataPage.js.map +0 -1
  101. package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js +0 -420
  102. package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js.map +0 -1
  103. package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js +0 -309
  104. package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js.map +0 -1
  105. package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js +0 -361
  106. package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js.map +0 -1
  107. package/dist/cosmicintersection/pages/SpaceSciencePage.js +0 -194
  108. package/dist/cosmicintersection/pages/SpaceSciencePage.js.map +0 -1
  109. package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js +0 -290
  110. package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js.map +0 -1
  111. package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js +0 -279
  112. package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js.map +0 -1
  113. package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js +0 -27
  114. package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js.map +0 -1
  115. package/dist/cosmicintersection/store/cosmicintersectionStore.js +0 -478
  116. package/dist/cosmicintersection/store/cosmicintersectionStore.js.map +0 -1
  117. package/dist/cosmicintersection/utils/i18n.js +0 -15
  118. package/dist/cosmicintersection/utils/i18n.js.map +0 -1
  119. package/dist/cosmicintersection/utils/navigation.js +0 -8
  120. package/dist/cosmicintersection/utils/navigation.js.map +0 -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":";;;;;;;;;;;;;;;;AAwCA,SAAS,EAAe,GAA+B;AACrD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAO,IAAI,KAAK,EAAM;AAC5B,QAAO,OAAO,MAAM,EAAK,SAAS,CAAC,GAAG,MAAM,EAAK,gBAAgB;;AAGnE,SAAS,EAAW,GAA+B;AACjD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAS,IAAI,KAAK,EAAM;AAI9B,QAHI,OAAO,MAAM,EAAO,SAAS,CAAC,GACzB,MAEF,EAAO,mBAAmB,KAAA,GAAW;EAAE,MAAM;EAAW,OAAO;EAAS,KAAK;EAAW,CAAC;;AAGlG,SAAS,EAAS,EAAE,UAAO,YAAoD;AAC7E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GAAG,WAAU;aAAmE;GAAU,CAAA,EAC1F,kBAAC,OAAD;GAAK,WAAU;aAAkC;GAAY,CAAA,CACzD;;;AAIV,SAAwB,IAAmB;CACzC,IAAM,EAAE,QAAK,OAAO,GAA2B,EACzC,EAAE,gBAAa,GAAuB,EAEtC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAkB,EAAG,EACzD,IAAS,GAAM,aAAa,MAE5B,CAAC,GAAY,EAAE,SAAS,OAAa,GAAgC,EACrE,CAAC,KAAiB,GAA6B,EAE/C,IAAmB,YAAY;AAC9B,QAGY,MAAM,EAAW,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC,EACtD,MAAM,wBACjB,EAAO,QAAQ,wBAAwB,IAAI,EAAO,KAAK,sBAAsB,EAC7E,MAAM,GAAS;IAIb,IAAgB,YAAY;AAChC,MAAI,CAAC,EACH;EAEF,IAAM,IAAW,MAAM,EAAc,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC;AACtE,MAAI,EAAS,MACX,OAAU,MAAM,EAAS,MAAM,QAAQ;AAEzC,MAAI,CAAC,EAAS,MAAM,iBAElB,OADA,EAAO,MAAM,4BAA4B,yCAAyC,EACxE,MAAM,wBAAwB;AAG1C,EADA,EAAO,QAAQ,kBAAkB,EACjC,EAAS,UAAU;;AAGrB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD,EAAc,OAAM,mBAAoB,CAAA;EAC7B,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD;GAAY,SAAQ;GAAiC,eAAe,KAAK,GAAS;GAAI,CAAA;EAC3E,CAAA;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAClC,kBAAC,GAAD;GACE,MAAM;GACN,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,MAAK;IAAS,eAAe,EAAS,UAAU;cAAE;IAEjD,CAAA;GAEX,CAAA;EACS,CAAA;CAIjB,IAAM,IAAW,EAAO,YAAY,EAAE;AAsDtC,QACE,kBAAC,GAAD;EACE,OAAO,EAAO;EACd,aAAa,EAAO,eAAe,KAAA;EACnC,UAAS;EACT,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,eAAe,EAAS,WAAW,EAAO,GAAG,OAAO;cAHtD,CAKE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,OAEtB;OACT,kBAAC,GAAD;IACE,WAAW,eAAe,EAAO;IACjC,YAAY,EAAO;IACnB,gBAAgB,GAAe;IAC/B,aAAY;IACZ,YAAY,CACV;KACE,KAAK;KACL,OAAO,IAAU,oBAAoB;KACrC,MAAM;KACN,UAAU;KACV,gBAAgB,KAAK,GAAkB;KACxC,CACF;IACD,CAAA,CACE;;YAGR,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAO,QAAU,CAAA,EACtC,kBAAC,GAAD;MAAO,SAAQ;gBAAa,EAAgB,EAAO,KAAK;MAAS,CAAA,CAC7D;;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,OAAM;OACN,OACE,EAAO,WACL,kBAAC,GAAD;QACE,MAAM,EAAO;QACb,WAAU;QACV,mBAAkB;kBAHpB,CAKE,kBAAC,GAAD;SAAU,WAAU;SAAsC,eAAY;SAAS,CAAA,EAC/E,kBAAC,QAAD;SAAM,WAAU;mBAAY,EAAO;SAAgB,CAAA,CAC1C;YAEX,kBAAC,QAAD;QAAM,WAAU;kBAAsB;QAAc,CAAA;OAGxD,CAAA;MACF,kBAAC,GAAD;OAAU,OAAM;OAAc,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,aAAa,EAAQ,CAAA;OAAI,CAAA;MAC3F,kBAAC,GAAD;OAAU,OAAM;OAAU,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,UAAU,EAAQ,CAAA;OAAI,CAAA;MACpF,kBAAC,GAAD;OAAU,OAAM;OAAU,OAAO,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAO,UAAU,EAAQ,CAAA;OAAI,CAAA;MAChF;;IAEL,EAAO,KAAK,SAAS,IACpB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAO,KAAK,KAAK,MAChB,kBAAC,GAAD;MAAiB,SAAQ;MAAU,WAAU;gBAC1C;MACK,EAFI,EAEJ,CACR;KACE,CAAA,GACJ;IAGJ,kBAAC,WAAD;KAAS,WAAU;eAAnB,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAA8B,CAAA,EA9HxF,EAAS,WAAW,IAEpB,kBAAC,GAAD;MACE,MAAM;MACN,OAAM;MACN,aAAY;MACZ,CAAA,GAIJ,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;OACE,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA;OAC3B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;OAC7B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;OAC7B,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA;OAC3B,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA;OACrB,EAAA,CAAA,EACC,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;OAEE,WAAU;OACV,eAAe,EAAS,YAAY,EAAQ,KAAK;iBAHnD;QAKE,kBAAC,GAAD;SAAW,WAAU;mBAAiC,EAAQ;SAAiB,CAAA;QAC/E,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,EAC7B,CAAA;QACZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,aAAe,CAAA,EAClC,CAAA;QACZ,kBAAC,GAAD;SAAW,WAAU;mBAClB,OAAO,EAAQ,SAAS,GAAG,IACxB,OAAO,EAAQ,SAAS,CAAC,gBAAgB,GACzC;SACM,CAAA;QACZ,kBAAC,GAAD;SAAW,WAAU;mBAClB,EAAW,EAAQ,UAAU;SACpB,CAAA;QACH;SAnBJ,EAAQ,GAmBJ,CACX,EACQ,CAAA,CACN,EAAA,CAAA;MACJ,CAAA,CAiFM;;IACN;;EACK,CAAA"}
@@ -0,0 +1,229 @@
1
+ import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
2
+ import { notify as t } from "../../lib/toast.js";
3
+ import { useCosCreateSourceMutation as n, useCosSourceQuery as r, useCosUpdateSourceMutation as i } from "../../api/cosmicApi.js";
4
+ import a from "../../components/EntityFormShell.js";
5
+ import o from "../../components/EntityTagsField.js";
6
+ import { formatEnumLabel as s } from "../../components/StatusBadge.js";
7
+ import "../../components/index.js";
8
+ import { useEffect as c, useMemo as l, useState as u } from "react";
9
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { useParams as p } from "react-router-dom";
11
+ import { Input as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y, Textarea as b } from "@burdenoff/fe-libs/ui";
12
+ //#region src/cosmicintersection/pages/sources/SourceFormPage.tsx
13
+ var x = [
14
+ "TELESCOPE",
15
+ "SATELLITE",
16
+ "GROUND_SENSOR",
17
+ "ARCHIVE",
18
+ "API",
19
+ "STREAM",
20
+ "FILE_DROP",
21
+ "SIMULATION"
22
+ ], S = [
23
+ "CONFIGURING",
24
+ "CONNECTED",
25
+ "SYNCING",
26
+ "DEGRADED",
27
+ "ERROR",
28
+ "DISABLED"
29
+ ], C = {
30
+ name: "",
31
+ description: "",
32
+ type: "TELESCOPE",
33
+ status: "CONFIGURING",
34
+ endpoint: "",
35
+ credentialRef: "",
36
+ config: "",
37
+ tags: []
38
+ }, w = "block space-y-1.5 text-sm", T = "font-medium text-text-primary";
39
+ function E() {
40
+ let { id: E = "" } = p(), D = l(() => !!E, [E]), { navigate: O } = e(), { data: k, loading: A } = r(E), [j, { loading: M }] = n(), [N, { loading: P }] = i(), [F, I] = u(C), [L, R] = u(!1);
41
+ c(() => {
42
+ if (!D || L || !k?.cosSource) return;
43
+ let e = k.cosSource;
44
+ I({
45
+ name: e.name ?? "",
46
+ description: e.description ?? "",
47
+ type: e.type,
48
+ status: e.status,
49
+ endpoint: e.endpoint ?? "",
50
+ credentialRef: "",
51
+ config: e.config ? JSON.stringify(e.config, null, 2) : "",
52
+ tags: e.tags ?? []
53
+ }), R(!0);
54
+ }, [
55
+ D,
56
+ L,
57
+ k
58
+ ]);
59
+ let z = (e, t) => {
60
+ I((n) => ({
61
+ ...n,
62
+ [e]: t
63
+ }));
64
+ }, B = M || P, V = async (e) => {
65
+ if (e.preventDefault(), !F.name.trim()) {
66
+ t.error("Name is required", "Give this source a recognizable name.");
67
+ return;
68
+ }
69
+ let n;
70
+ if (F.config.trim()) try {
71
+ let e = JSON.parse(F.config);
72
+ if (typeof e != "object" || !e || Array.isArray(e)) throw Error("Config must be a JSON object.");
73
+ n = e;
74
+ } catch (e) {
75
+ t.error("Invalid config JSON", e instanceof Error ? e.message : "Please provide valid JSON.");
76
+ return;
77
+ }
78
+ let r = {
79
+ name: F.name.trim(),
80
+ type: F.type,
81
+ status: F.status,
82
+ description: F.description.trim() || void 0,
83
+ endpoint: F.endpoint.trim() || void 0,
84
+ credentialRef: F.credentialRef.trim() || void 0,
85
+ config: n,
86
+ tags: F.tags
87
+ };
88
+ if (D) {
89
+ (await N({ variables: {
90
+ id: E,
91
+ input: r
92
+ } })).data?.cosUpdateSource?.id && (t.success("Source updated"), O("sources"));
93
+ return;
94
+ }
95
+ (await j({ variables: { input: r } })).data?.cosCreateSource?.id && (t.success("Source created"), O("sources"));
96
+ };
97
+ return /* @__PURE__ */ f(a, {
98
+ title: D ? "Edit source" : "New data source",
99
+ description: "Instruments, archives and feeds that supply raw data into your federation.",
100
+ purpose: "A source describes where data comes from — a telescope, satellite feed, archive, API or file drop. Configure it once, then attach datasets that ingest from it and mark it synced as data arrives.",
101
+ backLabel: "Back to sources",
102
+ onBack: () => O("sources"),
103
+ onSubmit: (e) => void V(e),
104
+ submitLabel: D ? "Save source" : "Create source",
105
+ saving: B || D && A && !L,
106
+ children: [
107
+ /* @__PURE__ */ f("div", {
108
+ className: "grid grid-cols-1 gap-4 md:grid-cols-2",
109
+ children: [/* @__PURE__ */ f("label", {
110
+ className: w,
111
+ children: [/* @__PURE__ */ d("span", {
112
+ className: T,
113
+ children: "Name"
114
+ }), /* @__PURE__ */ d(m, {
115
+ value: F.name,
116
+ onChange: (e) => z("name", e.target.value),
117
+ placeholder: "e.g. VLA Radio Array",
118
+ required: !0
119
+ })]
120
+ }), /* @__PURE__ */ f("label", {
121
+ className: w,
122
+ children: [/* @__PURE__ */ d("span", {
123
+ className: T,
124
+ children: "Endpoint"
125
+ }), /* @__PURE__ */ d(m, {
126
+ value: F.endpoint,
127
+ onChange: (e) => z("endpoint", e.target.value),
128
+ placeholder: "https://archive.example.org/api"
129
+ })]
130
+ })]
131
+ }),
132
+ /* @__PURE__ */ f("label", {
133
+ className: w,
134
+ children: [/* @__PURE__ */ d("span", {
135
+ className: T,
136
+ children: "Description"
137
+ }), /* @__PURE__ */ d(b, {
138
+ value: F.description,
139
+ onChange: (e) => z("description", e.target.value),
140
+ rows: 3,
141
+ placeholder: "What this source provides and how it connects."
142
+ })]
143
+ }),
144
+ /* @__PURE__ */ f("div", {
145
+ className: "grid grid-cols-1 gap-4 md:grid-cols-2",
146
+ children: [/* @__PURE__ */ f("label", {
147
+ className: w,
148
+ children: [/* @__PURE__ */ d("span", {
149
+ className: T,
150
+ children: "Type"
151
+ }), /* @__PURE__ */ f(h, {
152
+ value: F.type,
153
+ onValueChange: (e) => z("type", e),
154
+ children: [/* @__PURE__ */ d(v, {
155
+ "aria-label": "Source type",
156
+ children: /* @__PURE__ */ d(y, {})
157
+ }), /* @__PURE__ */ d(g, { children: x.map((e) => /* @__PURE__ */ d(_, {
158
+ value: e,
159
+ children: s(e)
160
+ }, e)) })]
161
+ })]
162
+ }), /* @__PURE__ */ f("label", {
163
+ className: w,
164
+ children: [/* @__PURE__ */ d("span", {
165
+ className: T,
166
+ children: "Status"
167
+ }), /* @__PURE__ */ f(h, {
168
+ value: F.status,
169
+ onValueChange: (e) => z("status", e),
170
+ children: [/* @__PURE__ */ d(v, {
171
+ "aria-label": "Source status",
172
+ children: /* @__PURE__ */ d(y, {})
173
+ }), /* @__PURE__ */ d(g, { children: S.map((e) => /* @__PURE__ */ d(_, {
174
+ value: e,
175
+ children: s(e)
176
+ }, e)) })]
177
+ })]
178
+ })]
179
+ }),
180
+ /* @__PURE__ */ f("label", {
181
+ className: w,
182
+ children: [
183
+ /* @__PURE__ */ d("span", {
184
+ className: T,
185
+ children: "Credential reference"
186
+ }),
187
+ /* @__PURE__ */ d(m, {
188
+ value: F.credentialRef,
189
+ onChange: (e) => z("credentialRef", e.target.value),
190
+ placeholder: "Secret key or vault reference (write-only)"
191
+ }),
192
+ /* @__PURE__ */ d("span", {
193
+ className: "text-xs text-text-secondary",
194
+ children: "A pointer to a stored credential — never paste raw secrets here."
195
+ })
196
+ ]
197
+ }),
198
+ /* @__PURE__ */ f("label", {
199
+ className: w,
200
+ children: [
201
+ /* @__PURE__ */ d("span", {
202
+ className: T,
203
+ children: "Config (JSON)"
204
+ }),
205
+ /* @__PURE__ */ d(b, {
206
+ value: F.config,
207
+ onChange: (e) => z("config", e.target.value),
208
+ rows: 5,
209
+ className: "font-mono text-xs",
210
+ placeholder: "{ \"pollIntervalMinutes\": 30 }"
211
+ }),
212
+ /* @__PURE__ */ d("span", {
213
+ className: "text-xs text-text-secondary",
214
+ children: "Optional connection parameters as a JSON object."
215
+ })
216
+ ]
217
+ }),
218
+ /* @__PURE__ */ d(o, {
219
+ value: F.tags,
220
+ onChange: (e) => z("tags", e),
221
+ disabled: B
222
+ })
223
+ ]
224
+ });
225
+ }
226
+ //#endregion
227
+ export { E as default };
228
+
229
+ //# sourceMappingURL=SourceFormPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SourceFormPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/sources/SourceFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCreateSourceMutation,\n useCosSourceQuery,\n useCosUpdateSourceMutation,\n type CosSourceStatus,\n type CosSourceType,\n} from '../../api/cosmicApi';\nimport { EntityFormShell, EntityTagsField, formatEnumLabel } from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\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\ninterface SourceFormState {\n name: string;\n description: string;\n type: CosSourceType;\n status: CosSourceStatus;\n endpoint: string;\n credentialRef: string;\n config: string;\n tags: string[];\n}\n\nconst INITIAL_STATE: SourceFormState = {\n name: '',\n description: '',\n type: 'TELESCOPE',\n status: 'CONFIGURING',\n endpoint: '',\n credentialRef: '',\n config: '',\n tags: [],\n};\n\nconst fieldLabelClass = 'block space-y-1.5 text-sm';\nconst fieldSpanClass = 'font-medium text-text-primary';\n\nexport default function SourceFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useCosmicIntersection();\n\n const { data, loading: loadingSource } = useCosSourceQuery(id);\n const [createSource, { loading: creating }] = useCosCreateSourceMutation();\n const [updateSource, { loading: updating }] = useCosUpdateSourceMutation();\n\n const [form, setForm] = useState<SourceFormState>(INITIAL_STATE);\n const [hydrated, setHydrated] = useState(false);\n\n useEffect(() => {\n if (!isEdit || hydrated || !data?.cosSource) {\n return;\n }\n const source = data.cosSource;\n setForm({\n name: source.name ?? '',\n description: source.description ?? '',\n type: source.type,\n status: source.status,\n endpoint: source.endpoint ?? '',\n credentialRef: '',\n config: source.config ? JSON.stringify(source.config, null, 2) : '',\n tags: source.tags ?? [],\n });\n setHydrated(true);\n }, [isEdit, hydrated, data]);\n\n const onChange = <K extends keyof SourceFormState>(field: K, value: SourceFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.name.trim()) {\n notify.error('Name is required', 'Give this source a recognizable name.');\n return;\n }\n\n let config: Record<string, unknown> | undefined;\n if (form.config.trim()) {\n try {\n const parsed = JSON.parse(form.config) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Config must be a JSON object.');\n }\n config = parsed as Record<string, unknown>;\n } catch (parseError) {\n notify.error(\n 'Invalid config JSON',\n parseError instanceof Error ? parseError.message : 'Please provide valid JSON.'\n );\n return;\n }\n }\n\n const base = {\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n description: form.description.trim() || undefined,\n endpoint: form.endpoint.trim() || undefined,\n credentialRef: form.credentialRef.trim() || undefined,\n config,\n tags: form.tags,\n };\n\n if (isEdit) {\n const response = await updateSource({ variables: { id, input: base } });\n if (response.data?.cosUpdateSource?.id) {\n notify.success('Source updated');\n navigate('sources');\n }\n return;\n }\n\n const response = await createSource({ variables: { input: base } });\n if (response.data?.cosCreateSource?.id) {\n notify.success('Source created');\n navigate('sources');\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit source' : 'New data source'}\n description=\"Instruments, archives and feeds that supply raw data into your federation.\"\n purpose=\"A source describes where data comes from — a telescope, satellite feed, archive, API or file drop. Configure it once, then attach datasets that ingest from it and mark it synced as data arrives.\"\n backLabel=\"Back to sources\"\n onBack={() => navigate('sources')}\n onSubmit={(event) => void onSubmit(event)}\n submitLabel={isEdit ? 'Save source' : 'Create source'}\n saving={saving || (isEdit && loadingSource && !hydrated)}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Name</span>\n <Input\n value={form.name}\n onChange={(event) => onChange('name', event.target.value)}\n placeholder=\"e.g. VLA Radio Array\"\n required\n />\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Endpoint</span>\n <Input\n value={form.endpoint}\n onChange={(event) => onChange('endpoint', event.target.value)}\n placeholder=\"https://archive.example.org/api\"\n />\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Description</span>\n <Textarea\n value={form.description}\n onChange={(event) => onChange('description', event.target.value)}\n rows={3}\n placeholder=\"What this source provides and how it connects.\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Type</span>\n <Select\n value={form.type}\n onValueChange={(value) => onChange('type', value as CosSourceType)}\n >\n <SelectTrigger aria-label=\"Source type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {formatEnumLabel(type)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Status</span>\n <Select\n value={form.status}\n onValueChange={(value) => onChange('status', value as CosSourceStatus)}\n >\n <SelectTrigger aria-label=\"Source status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_STATUSES.map((status) => (\n <SelectItem key={status} value={status}>\n {formatEnumLabel(status)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Credential reference</span>\n <Input\n value={form.credentialRef}\n onChange={(event) => onChange('credentialRef', event.target.value)}\n placeholder=\"Secret key or vault reference (write-only)\"\n />\n <span className=\"text-xs text-text-secondary\">\n A pointer to a stored credential — never paste raw secrets here.\n </span>\n </label>\n\n <label className={fieldLabelClass}>\n <span className={fieldSpanClass}>Config (JSON)</span>\n <Textarea\n value={form.config}\n onChange={(event) => onChange('config', event.target.value)}\n rows={5}\n className=\"font-mono text-xs\"\n placeholder='{ \"pollIntervalMinutes\": 30 }'\n />\n <span className=\"text-xs text-text-secondary\">\n Optional connection parameters as a JSON object.\n </span>\n </label>\n\n <EntityTagsField\n value={form.tags}\n onChange={(tags) => onChange('tags', tags)}\n disabled={saving}\n />\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}