@lunora/studio 0.0.0 → 1.0.0-alpha.1

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 (81) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1402 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,229 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useLunora } from '@lunora/react';
3
+ import { E as EmptyState } from './empty-state-DY_oe0k6.js';
4
+ import { u as useAdminSpec, S as Skeleton, R as ReferenceView } from './reference-view-BCKIoai7.js';
5
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
6
+ import { jsxDEV } from 'react/jsx-dev-runtime';
7
+
8
+ const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
9
+ const jsonSchemaOf = (media) => media?.content?.["application/json"]?.schema;
10
+ const buildResponses = (operation, document) => Object.entries(operation.responses ?? {}).map(([status, raw]) => {
11
+ const name = raw.$ref?.replace("#/components/responses/", "");
12
+ const resolved = name === void 0 ? raw : document.components?.responses?.[name] ?? raw;
13
+ return {
14
+ description: resolved.description,
15
+ schema: jsonSchemaOf(resolved),
16
+ status
17
+ };
18
+ });
19
+ const flattenOperation = (pathKey, httpPath, method, operation, document) => {
20
+ const requestSchema = jsonSchemaOf(operation.requestBody);
21
+ const functionPathConst = requestSchema?.properties?.["functionPath"]?.const;
22
+ const functionPath = typeof functionPathConst === "string" ? functionPathConst : void 0;
23
+ const argsSchema = functionPath === void 0 ? requestSchema : requestSchema?.properties?.["args"];
24
+ const operationId = operation.operationId ?? `${method} ${httpPath}`;
25
+ return {
26
+ argsSchema,
27
+ description: operation.description,
28
+ functionPath,
29
+ httpPath,
30
+ key: pathKey,
31
+ kind: operation["x-lunora-function-kind"],
32
+ method: method.toUpperCase(),
33
+ operationId,
34
+ requestSchema,
35
+ responses: buildResponses(operation, document),
36
+ summary: operation.summary ?? operationId,
37
+ tags: operation.tags ?? [],
38
+ title: operation.summary ?? operationId
39
+ };
40
+ };
41
+ const collectOperations = (document) => {
42
+ const operations = [];
43
+ for (const [pathKey, pathItem] of Object.entries(document.paths ?? {})) {
44
+ const httpPath = pathKey.includes("#") ? pathKey.slice(0, pathKey.indexOf("#")) : pathKey;
45
+ for (const method of HTTP_METHODS) {
46
+ const operation = pathItem[method];
47
+ if (operation !== void 0) {
48
+ operations.push(flattenOperation(pathKey, httpPath, method, operation, document));
49
+ }
50
+ }
51
+ }
52
+ return operations;
53
+ };
54
+ const parseOpenApi = (raw) => {
55
+ const document = raw ?? {};
56
+ const operations = collectOperations(document);
57
+ const tagMeta = new Map((document.tags ?? []).map((tag) => [tag.name, tag.description]));
58
+ const seenTags = /* @__PURE__ */ new Set();
59
+ const groups = [];
60
+ const pushGroup = (name) => {
61
+ if (seenTags.has(name)) {
62
+ return;
63
+ }
64
+ seenTags.add(name);
65
+ const members = operations.filter((operation) => (operation.tags[0] ?? "") === name);
66
+ if (members.length > 0) {
67
+ groups.push({
68
+ description: tagMeta.get(name),
69
+ name,
70
+ operations: members
71
+ });
72
+ }
73
+ };
74
+ for (const tag of document.tags ?? []) {
75
+ pushGroup(tag.name);
76
+ }
77
+ for (const operation of operations) {
78
+ pushGroup(operation.tags[0] ?? "");
79
+ }
80
+ return {
81
+ groups,
82
+ operationByKey: new Map(operations.map((operation) => [operation.key, operation])),
83
+ server: document.servers?.[0]?.url,
84
+ title: document.info?.title ?? "API reference",
85
+ version: document.info?.version
86
+ };
87
+ };
88
+
89
+ const isEmptySpec = (spec) => {
90
+ const {
91
+ paths
92
+ } = spec;
93
+ return paths === void 0 || typeof paths === "object" && paths !== null && Object.keys(paths).length === 0;
94
+ };
95
+ const classifySpec = (spec) => {
96
+ const document = spec;
97
+ return isEmptySpec(document) ? {
98
+ kind: "empty"
99
+ } : {
100
+ kind: "ready",
101
+ spec: document
102
+ };
103
+ };
104
+ const ApiReferencePanel = (t0) => {
105
+ const $ = c(23);
106
+ const {
107
+ spec: inlineSpec
108
+ } = t0;
109
+ const t = useT();
110
+ const client = useLunora();
111
+ let t1;
112
+ if ($[0] !== client) {
113
+ t1 = () => client.fetchOpenApi();
114
+ $[0] = client;
115
+ $[1] = t1;
116
+ } else {
117
+ t1 = $[1];
118
+ }
119
+ const fetchOpenApi = t1;
120
+ const state = useAdminSpec(inlineSpec, fetchOpenApi, classifySpec);
121
+ let t2;
122
+ if ($[2] !== state.kind || $[3] !== state.spec) {
123
+ t2 = state.kind === "ready" ? parseOpenApi(state.spec) : void 0;
124
+ $[2] = state.kind;
125
+ $[3] = state.spec;
126
+ $[4] = t2;
127
+ } else {
128
+ t2 = $[4];
129
+ }
130
+ const model = t2;
131
+ if (state.kind === "loading") {
132
+ let t32;
133
+ if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
134
+ t32 = /* @__PURE__ */ jsxDEV("div", {
135
+ className: "flex flex-col gap-4",
136
+ "data-testid": "api-reference-loading",
137
+ children: [/* @__PURE__ */ jsxDEV(Skeleton, {
138
+ className: "h-8 w-48"
139
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Skeleton, {
140
+ className: "h-64 w-full"
141
+ }, void 0, false)]
142
+ }, void 0, true);
143
+ $[5] = t32;
144
+ } else {
145
+ t32 = $[5];
146
+ }
147
+ return t32;
148
+ }
149
+ if (state.kind === "error") {
150
+ let t32;
151
+ if ($[6] !== state.message || $[7] !== t) {
152
+ t32 = t("Couldn't load the OpenAPI spec: {message}", {
153
+ message: state.message
154
+ });
155
+ $[6] = state.message;
156
+ $[7] = t;
157
+ $[8] = t32;
158
+ } else {
159
+ t32 = $[8];
160
+ }
161
+ let t4;
162
+ if ($[9] !== t) {
163
+ t4 = t("API reference unavailable");
164
+ $[9] = t;
165
+ $[10] = t4;
166
+ } else {
167
+ t4 = $[10];
168
+ }
169
+ let t5;
170
+ if ($[11] !== t32 || $[12] !== t4) {
171
+ t5 = /* @__PURE__ */ jsxDEV(EmptyState, {
172
+ description: t32,
173
+ testId: "api-reference-error",
174
+ title: t4
175
+ }, void 0, false);
176
+ $[11] = t32;
177
+ $[12] = t4;
178
+ $[13] = t5;
179
+ } else {
180
+ t5 = $[13];
181
+ }
182
+ return t5;
183
+ }
184
+ if (state.kind === "empty" || model === void 0) {
185
+ let t32;
186
+ if ($[14] !== t) {
187
+ t32 = t("Run `lunora codegen` and wire `_generated/openapi.json` to the worker to render the API reference here.");
188
+ $[14] = t;
189
+ $[15] = t32;
190
+ } else {
191
+ t32 = $[15];
192
+ }
193
+ let t4;
194
+ if ($[16] !== t) {
195
+ t4 = t("No OpenAPI spec configured");
196
+ $[16] = t;
197
+ $[17] = t4;
198
+ } else {
199
+ t4 = $[17];
200
+ }
201
+ let t5;
202
+ if ($[18] !== t32 || $[19] !== t4) {
203
+ t5 = /* @__PURE__ */ jsxDEV(EmptyState, {
204
+ description: t32,
205
+ testId: "api-reference-empty",
206
+ title: t4
207
+ }, void 0, false);
208
+ $[18] = t32;
209
+ $[19] = t4;
210
+ $[20] = t5;
211
+ } else {
212
+ t5 = $[20];
213
+ }
214
+ return t5;
215
+ }
216
+ let t3;
217
+ if ($[21] !== model) {
218
+ t3 = /* @__PURE__ */ jsxDEV(ReferenceView, {
219
+ model
220
+ }, void 0, false);
221
+ $[21] = model;
222
+ $[22] = t3;
223
+ } else {
224
+ t3 = $[22];
225
+ }
226
+ return t3;
227
+ };
228
+
229
+ export { ApiReferencePanel as default };
@@ -0,0 +1,251 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useState } from 'react';
3
+ import { B as Button } from './button-BhsN2uZH.js';
4
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
5
+ import ApiDocsPanel from './ApiDocsPanel-DpRjJhG5.js';
6
+ import ApiReferencePanel from './ApiReferencePanel-DMIUp-kK.js';
7
+ import OpenRpcReferencePanel from './OpenRpcReferencePanel-j2p3HB0s.js';
8
+ import { jsxDEV } from 'react/jsx-dev-runtime';
9
+
10
+ const VIEW_KEYS = ["reference", "snippets"];
11
+ const FORMAT_KEYS = ["openapi", "openrpc"];
12
+ const ApiTab = (t0) => {
13
+ const $ = c(51);
14
+ const {
15
+ functions,
16
+ initialShardKey,
17
+ openApiSpec,
18
+ openRpcSpec
19
+ } = t0;
20
+ const t = useT();
21
+ const [view, setView] = useState("reference");
22
+ const [format, setFormat] = useState("openapi");
23
+ let t1;
24
+ if ($[0] !== t) {
25
+ t1 = t("Reference");
26
+ $[0] = t;
27
+ $[1] = t1;
28
+ } else {
29
+ t1 = $[1];
30
+ }
31
+ let t2;
32
+ if ($[2] !== t) {
33
+ t2 = t("Snippets");
34
+ $[2] = t;
35
+ $[3] = t2;
36
+ } else {
37
+ t2 = $[3];
38
+ }
39
+ let t3;
40
+ if ($[4] !== t1 || $[5] !== t2) {
41
+ t3 = {
42
+ reference: t1,
43
+ snippets: t2
44
+ };
45
+ $[4] = t1;
46
+ $[5] = t2;
47
+ $[6] = t3;
48
+ } else {
49
+ t3 = $[6];
50
+ }
51
+ const viewLabel = t3;
52
+ let t4;
53
+ if ($[7] !== t) {
54
+ t4 = t("OpenAPI");
55
+ $[7] = t;
56
+ $[8] = t4;
57
+ } else {
58
+ t4 = $[8];
59
+ }
60
+ let t5;
61
+ if ($[9] !== t) {
62
+ t5 = t("OpenRPC");
63
+ $[9] = t;
64
+ $[10] = t5;
65
+ } else {
66
+ t5 = $[10];
67
+ }
68
+ let t6;
69
+ if ($[11] !== t4 || $[12] !== t5) {
70
+ t6 = {
71
+ openapi: t4,
72
+ openrpc: t5
73
+ };
74
+ $[11] = t4;
75
+ $[12] = t5;
76
+ $[13] = t6;
77
+ } else {
78
+ t6 = $[13];
79
+ }
80
+ const formatLabel = t6;
81
+ let t7;
82
+ if ($[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
83
+ t7 = (event) => {
84
+ setView(event.currentTarget.dataset.view);
85
+ };
86
+ $[14] = t7;
87
+ } else {
88
+ t7 = $[14];
89
+ }
90
+ const selectView = t7;
91
+ let t8;
92
+ if ($[15] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
93
+ t8 = (event_0) => {
94
+ setFormat(event_0.currentTarget.dataset.format);
95
+ };
96
+ $[15] = t8;
97
+ } else {
98
+ t8 = $[15];
99
+ }
100
+ const selectFormat = t8;
101
+ let t9;
102
+ if ($[16] !== t) {
103
+ t9 = t("API view");
104
+ $[16] = t;
105
+ $[17] = t9;
106
+ } else {
107
+ t9 = $[17];
108
+ }
109
+ let t10;
110
+ if ($[18] !== view || $[19] !== viewLabel) {
111
+ t10 = VIEW_KEYS.map((key) => /* @__PURE__ */ jsxDEV(Button, {
112
+ "aria-selected": view === key,
113
+ "data-testid": `api-view-${key}`,
114
+ "data-view": key,
115
+ onClick: selectView,
116
+ role: "tab",
117
+ size: "sm",
118
+ type: "button",
119
+ variant: view === key ? "default" : "outline",
120
+ children: viewLabel[key]
121
+ }, key, false));
122
+ $[18] = view;
123
+ $[19] = viewLabel;
124
+ $[20] = t10;
125
+ } else {
126
+ t10 = $[20];
127
+ }
128
+ let t11;
129
+ if ($[21] !== t10 || $[22] !== t9) {
130
+ t11 = /* @__PURE__ */ jsxDEV("div", {
131
+ "aria-label": t9,
132
+ className: "flex gap-1.5",
133
+ "data-testid": "api-view-toggle",
134
+ role: "tablist",
135
+ children: t10
136
+ }, void 0, false);
137
+ $[21] = t10;
138
+ $[22] = t9;
139
+ $[23] = t11;
140
+ } else {
141
+ t11 = $[23];
142
+ }
143
+ let t12;
144
+ if ($[24] !== format || $[25] !== formatLabel || $[26] !== t || $[27] !== view) {
145
+ t12 = view === "reference" && /* @__PURE__ */ jsxDEV("div", {
146
+ "aria-label": t("API spec format"),
147
+ className: "flex gap-1.5",
148
+ "data-testid": "api-format-toggle",
149
+ role: "tablist",
150
+ children: FORMAT_KEYS.map((key_0) => /* @__PURE__ */ jsxDEV(Button, {
151
+ "aria-selected": format === key_0,
152
+ "data-format": key_0,
153
+ "data-testid": `api-format-${key_0}`,
154
+ onClick: selectFormat,
155
+ role: "tab",
156
+ size: "sm",
157
+ type: "button",
158
+ variant: format === key_0 ? "default" : "outline",
159
+ children: formatLabel[key_0]
160
+ }, key_0, false))
161
+ }, void 0, false);
162
+ $[24] = format;
163
+ $[25] = formatLabel;
164
+ $[26] = t;
165
+ $[27] = view;
166
+ $[28] = t12;
167
+ } else {
168
+ t12 = $[28];
169
+ }
170
+ let t13;
171
+ if ($[29] !== t11 || $[30] !== t12) {
172
+ t13 = /* @__PURE__ */ jsxDEV("div", {
173
+ className: "flex shrink-0 flex-wrap items-center gap-3 border-b border-border px-6 py-3",
174
+ children: [t11, t12]
175
+ }, void 0, true);
176
+ $[29] = t11;
177
+ $[30] = t12;
178
+ $[31] = t13;
179
+ } else {
180
+ t13 = $[31];
181
+ }
182
+ let t14;
183
+ if ($[32] !== functions || $[33] !== initialShardKey || $[34] !== view) {
184
+ t14 = view === "snippets" && /* @__PURE__ */ jsxDEV("div", {
185
+ className: "min-h-0 flex-1 overflow-y-auto p-6",
186
+ children: /* @__PURE__ */ jsxDEV(ApiDocsPanel, {
187
+ functions,
188
+ initialShardKey
189
+ }, void 0, false)
190
+ }, void 0, false);
191
+ $[32] = functions;
192
+ $[33] = initialShardKey;
193
+ $[34] = view;
194
+ $[35] = t14;
195
+ } else {
196
+ t14 = $[35];
197
+ }
198
+ let t15;
199
+ if ($[36] !== format || $[37] !== openApiSpec || $[38] !== view) {
200
+ t15 = view === "reference" && format === "openapi" && /* @__PURE__ */ jsxDEV(ApiReferencePanel, {
201
+ spec: openApiSpec
202
+ }, void 0, false);
203
+ $[36] = format;
204
+ $[37] = openApiSpec;
205
+ $[38] = view;
206
+ $[39] = t15;
207
+ } else {
208
+ t15 = $[39];
209
+ }
210
+ let t16;
211
+ if ($[40] !== format || $[41] !== openRpcSpec || $[42] !== view) {
212
+ t16 = view === "reference" && format === "openrpc" && /* @__PURE__ */ jsxDEV(OpenRpcReferencePanel, {
213
+ spec: openRpcSpec
214
+ }, void 0, false);
215
+ $[40] = format;
216
+ $[41] = openRpcSpec;
217
+ $[42] = view;
218
+ $[43] = t16;
219
+ } else {
220
+ t16 = $[43];
221
+ }
222
+ let t17;
223
+ if ($[44] !== t14 || $[45] !== t15 || $[46] !== t16) {
224
+ t17 = /* @__PURE__ */ jsxDEV("div", {
225
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
226
+ children: [t14, t15, t16]
227
+ }, void 0, true);
228
+ $[44] = t14;
229
+ $[45] = t15;
230
+ $[46] = t16;
231
+ $[47] = t17;
232
+ } else {
233
+ t17 = $[47];
234
+ }
235
+ let t18;
236
+ if ($[48] !== t13 || $[49] !== t17) {
237
+ t18 = /* @__PURE__ */ jsxDEV("div", {
238
+ className: "flex min-h-0 flex-1 flex-col",
239
+ "data-testid": "lunora-api-tab",
240
+ children: [t13, t17]
241
+ }, void 0, true);
242
+ $[48] = t13;
243
+ $[49] = t17;
244
+ $[50] = t18;
245
+ } else {
246
+ t18 = $[50];
247
+ }
248
+ return t18;
249
+ };
250
+
251
+ export { ApiTab as default };
@@ -0,0 +1,212 @@
1
+ import { useLunora } from '@lunora/react';
2
+ import { useVirtualizer, observeElementRect } from '@tanstack/react-virtual';
3
+ import { useState, useMemo, useRef } from 'react';
4
+ import { L as LiveError } from './live-status-bPff1O7Y.js';
5
+ import { ShardInput } from './ShardInput-DNCsT1KW.js';
6
+ import { B as Badge } from './badge-B2PKA1-5.js';
7
+ import { C as Card, a as CardContent } from './card-DURq3ElK.js';
8
+ import { E as EmptyState } from './empty-state-DY_oe0k6.js';
9
+ import { I as Input } from './input-XH4r1Pt1.js';
10
+ import { T as Table, a as TableHeader, b as TableRow, c as TableHead, d as TableBody, e as TableCell } from './table-_RzNvy3R.js';
11
+ import { u as useLiveAdmin } from './use-live-admin-D1h1Fzsd.js';
12
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
13
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
14
+ import { d as formatTimestamp, c as callOptions, e as errorMessage, a as adminRef } from './internal-BBZYexre.js';
15
+ import { u as useLiveShardSeed } from './use-live-shard-seed-B74RYcOy.js';
16
+ import { jsxDEV } from 'react/jsx-dev-runtime';
17
+
18
+ const GET_AUDIT_LOG = adminRef(ADMIN_FUNCTIONS.getAuditLog);
19
+ const DETAIL_MAX = 80;
20
+ const formatDetail = (detail) => {
21
+ if (detail === void 0) {
22
+ return "";
23
+ }
24
+ const json = JSON.stringify(detail);
25
+ return json.length > DETAIL_MAX ? `${json.slice(0, DETAIL_MAX)}…` : json;
26
+ };
27
+ const ROW_HEIGHT = 41;
28
+ const SCROLL_HEIGHT = 480;
29
+ const COLUMN_COUNT = 5;
30
+ const observeViewportRect = (instance, callback) => observeElementRect(instance, (rect) => {
31
+ callback(rect.height > 0 ? rect : {
32
+ height: SCROLL_HEIGHT,
33
+ width: rect.width
34
+ });
35
+ });
36
+ const AuditPanel = ({
37
+ initialShardKey
38
+ }) => {
39
+ const client = useLunora();
40
+ const t = useT();
41
+ const [shardKey, setShardKey] = useState(initialShardKey ?? "");
42
+ const [entries, setEntries] = useState([]);
43
+ const [error, setError] = useState(null);
44
+ const [search, setSearch] = useState("");
45
+ const [liveError, setLiveError] = useState(void 0);
46
+ const refresh = async (shard) => {
47
+ setError(null);
48
+ try {
49
+ const result = await client.query(GET_AUDIT_LOG, {}, callOptions(shard));
50
+ setEntries(result.entries);
51
+ } catch (error_) {
52
+ setEntries([]);
53
+ setError(errorMessage(error_));
54
+ throw error_;
55
+ }
56
+ };
57
+ const committedShard = useLiveShardSeed(shardKey, refresh);
58
+ useLiveAdmin(ADMIN_FUNCTIONS.getAuditLog, {}, committedShard ?? "", (result_0) => {
59
+ setError(null);
60
+ setLiveError(void 0);
61
+ setEntries(result_0.entries);
62
+ }, committedShard !== void 0, setLiveError);
63
+ const filtered = useMemo(() => {
64
+ const needle = search.trim().toLowerCase();
65
+ if (needle === "") {
66
+ return entries;
67
+ }
68
+ return entries.filter((entry) => entry.op.toLowerCase().includes(needle) || (entry.table ?? "").toLowerCase().includes(needle) || (entry.id ?? "").toLowerCase().includes(needle));
69
+ }, [entries, search]);
70
+ const scrollRef = useRef(null);
71
+ const virtualizer = useVirtualizer({
72
+ count: filtered.length,
73
+ estimateSize: () => ROW_HEIGHT,
74
+ getScrollElement: () => scrollRef.current,
75
+ initialRect: {
76
+ height: SCROLL_HEIGHT,
77
+ width: 800
78
+ },
79
+ observeElementRect: observeViewportRect,
80
+ overscan: 8
81
+ });
82
+ const virtualRows = virtualizer.getVirtualItems();
83
+ const totalSize = virtualizer.getTotalSize();
84
+ const paddingTop = virtualRows[0]?.start ?? 0;
85
+ const paddingBottom = totalSize - (virtualRows.at(-1)?.end ?? 0);
86
+ const topSpacerStyle = {
87
+ height: paddingTop
88
+ };
89
+ const bottomSpacerStyle = {
90
+ height: paddingBottom
91
+ };
92
+ const onSearchChange = (event) => {
93
+ setSearch(event.target.value);
94
+ };
95
+ return /* @__PURE__ */ jsxDEV("div", {
96
+ className: "flex flex-col gap-4",
97
+ "data-testid": "lunora-audit",
98
+ children: [/* @__PURE__ */ jsxDEV("div", {
99
+ className: "flex flex-wrap items-center gap-2",
100
+ children: [/* @__PURE__ */ jsxDEV(ShardInput, {
101
+ onChange: setShardKey,
102
+ testId: "au-shard-input",
103
+ value: shardKey
104
+ }, void 0, false), /* @__PURE__ */ jsxDEV(LiveError, {
105
+ message: liveError,
106
+ prefix: "au"
107
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Input, {
108
+ "aria-label": t("Filter audit log"),
109
+ className: "h-8 w-48",
110
+ "data-testid": "au-search",
111
+ onChange: onSearchChange,
112
+ placeholder: t("filter op, table, id"),
113
+ value: search
114
+ }, void 0, false)]
115
+ }, void 0, true), error !== null && /* @__PURE__ */ jsxDEV("p", {
116
+ className: "text-sm text-destructive",
117
+ "data-testid": "au-error",
118
+ role: "alert",
119
+ children: error
120
+ }, void 0, false), error === null && filtered.length === 0 && /* @__PURE__ */ jsxDEV(EmptyState, {
121
+ description: t("State-changing admin operations are recorded here."),
122
+ icon: /* @__PURE__ */ jsxDEV("svg", {
123
+ "aria-hidden": "true",
124
+ fill: "none",
125
+ stroke: "currentColor",
126
+ strokeLinecap: "round",
127
+ strokeLinejoin: "round",
128
+ strokeWidth: 1.6,
129
+ viewBox: "0 0 24 24",
130
+ children: /* @__PURE__ */ jsxDEV("path", {
131
+ d: "M8 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2h-2M9 3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V3Zm-1 9h8m-8 4h5"
132
+ }, void 0, false)
133
+ }, void 0, false),
134
+ testId: "au-empty",
135
+ title: t("No audit entries.")
136
+ }, void 0, false), filtered.length > 0 && /* @__PURE__ */ jsxDEV(Card, {
137
+ className: "overflow-hidden py-0",
138
+ children: /* @__PURE__ */ jsxDEV(CardContent, {
139
+ className: "max-h-[30rem] overflow-auto px-0",
140
+ ref: scrollRef,
141
+ children: /* @__PURE__ */ jsxDEV(Table, {
142
+ "data-testid": "au-table",
143
+ children: [/* @__PURE__ */ jsxDEV(TableHeader, {
144
+ children: /* @__PURE__ */ jsxDEV(TableRow, {
145
+ children: [/* @__PURE__ */ jsxDEV(TableHead, {
146
+ children: t("time")
147
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
148
+ children: t("op")
149
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
150
+ children: t("table")
151
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
152
+ children: t("id")
153
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
154
+ children: t("detail")
155
+ }, void 0, false)]
156
+ }, void 0, true)
157
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableBody, {
158
+ children: [paddingTop > 0 && /* @__PURE__ */ jsxDEV("tr", {
159
+ "aria-hidden": "true",
160
+ children: /* @__PURE__ */ jsxDEV("td", {
161
+ colSpan: COLUMN_COUNT,
162
+ style: topSpacerStyle
163
+ }, void 0, false)
164
+ }, void 0, false), virtualRows.map((virtualRow) => {
165
+ const entry_0 = filtered[virtualRow.index];
166
+ const detail = formatDetail(entry_0.detail);
167
+ return /* @__PURE__ */ jsxDEV(TableRow, {
168
+ "data-testid": "au-row",
169
+ children: [/* @__PURE__ */ jsxDEV(TableCell, {
170
+ className: "text-muted-foreground tabular-nums",
171
+ children: formatTimestamp(entry_0.ts, "—")
172
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
173
+ children: /* @__PURE__ */ jsxDEV(Badge, {
174
+ variant: "secondary",
175
+ children: entry_0.op
176
+ }, void 0, false)
177
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
178
+ className: "font-medium",
179
+ children: entry_0.table ?? /* @__PURE__ */ jsxDEV("span", {
180
+ className: "text-muted-foreground",
181
+ children: "—"
182
+ }, void 0, false)
183
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
184
+ className: "max-w-[20ch] truncate font-mono text-xs",
185
+ children: entry_0.id ?? /* @__PURE__ */ jsxDEV("span", {
186
+ className: "text-muted-foreground",
187
+ children: "—"
188
+ }, void 0, false)
189
+ }, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
190
+ className: "max-w-[32ch] truncate font-mono text-xs text-muted-foreground",
191
+ title: detail || void 0,
192
+ children: detail || /* @__PURE__ */ jsxDEV("span", {
193
+ className: "text-muted-foreground",
194
+ children: "—"
195
+ }, void 0, false)
196
+ }, void 0, false)]
197
+ }, entry_0.seq, true);
198
+ }), paddingBottom > 0 && /* @__PURE__ */ jsxDEV("tr", {
199
+ "aria-hidden": "true",
200
+ children: /* @__PURE__ */ jsxDEV("td", {
201
+ colSpan: COLUMN_COUNT,
202
+ style: bottomSpacerStyle
203
+ }, void 0, false)
204
+ }, void 0, false)]
205
+ }, void 0, true)]
206
+ }, void 0, true)
207
+ }, void 0, false)
208
+ }, void 0, false)]
209
+ }, void 0, true);
210
+ };
211
+
212
+ export { AuditPanel };