@vertebr/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConfigForm.d.ts +9 -0
- package/dist/ConfigForm.d.ts.map +1 -0
- package/dist/ConfigForm.js +87 -0
- package/dist/EnvSelect.d.ts +9 -0
- package/dist/EnvSelect.d.ts.map +1 -0
- package/dist/EnvSelect.js +12 -0
- package/dist/MetricsBoard.d.ts +10 -0
- package/dist/MetricsBoard.d.ts.map +1 -0
- package/dist/MetricsBoard.js +4 -0
- package/dist/ResourceTable.d.ts +23 -0
- package/dist/ResourceTable.d.ts.map +1 -0
- package/dist/ResourceTable.js +112 -0
- package/dist/VertebrAdmin.d.ts +17 -0
- package/dist/VertebrAdmin.d.ts.map +1 -0
- package/dist/VertebrAdmin.js +269 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +65 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/styles.css +997 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/useVertebrControls.d.ts +34 -0
- package/dist/useVertebrControls.d.ts.map +1 -0
- package/dist/useVertebrControls.js +67 -0
- package/package.json +54 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldSchema } from "./types";
|
|
2
|
+
type ConfigFormProps = {
|
|
3
|
+
schema: Record<string, FieldSchema>;
|
|
4
|
+
value: Record<string, unknown>;
|
|
5
|
+
onSave: (value: Record<string, unknown>) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
export declare function ConfigForm({ schema, value, onSave }: ConfigFormProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ConfigForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../src/ConfigForm.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D,CAAC;AAgCF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,eAAe,+BA4HpE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useForm, Controller } from "react-hook-form";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
|
+
import * as Label from "@radix-ui/react-label";
|
|
7
|
+
import * as Switch from "@radix-ui/react-switch";
|
|
8
|
+
function buildZodSchema(schema) {
|
|
9
|
+
const shape = {};
|
|
10
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
11
|
+
let zodField;
|
|
12
|
+
switch (field.type) {
|
|
13
|
+
case "number":
|
|
14
|
+
zodField = z.number();
|
|
15
|
+
if (field.min != null)
|
|
16
|
+
zodField = zodField.min(field.min);
|
|
17
|
+
if (field.max != null)
|
|
18
|
+
zodField = zodField.max(field.max);
|
|
19
|
+
break;
|
|
20
|
+
case "boolean":
|
|
21
|
+
zodField = z.boolean();
|
|
22
|
+
break;
|
|
23
|
+
default:
|
|
24
|
+
zodField = z.string();
|
|
25
|
+
}
|
|
26
|
+
if (!field.required) {
|
|
27
|
+
zodField = zodField.optional();
|
|
28
|
+
}
|
|
29
|
+
shape[key] = zodField;
|
|
30
|
+
}
|
|
31
|
+
return z.object(shape);
|
|
32
|
+
}
|
|
33
|
+
function spanFor(field) {
|
|
34
|
+
if (field.type === "boolean")
|
|
35
|
+
return "full";
|
|
36
|
+
if (field.type === "richtext")
|
|
37
|
+
return "wide";
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export function ConfigForm({ schema, value, onSave }) {
|
|
41
|
+
const zodSchema = useMemo(() => buildZodSchema(schema), [schema]);
|
|
42
|
+
const [status, setStatus] = useState(null);
|
|
43
|
+
const [tone, setTone] = useState();
|
|
44
|
+
const form = useForm({
|
|
45
|
+
resolver: zodResolver(zodSchema),
|
|
46
|
+
defaultValues: value,
|
|
47
|
+
});
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
form.reset(value);
|
|
50
|
+
}, [value, form]);
|
|
51
|
+
const onSubmit = form.handleSubmit(async (data) => {
|
|
52
|
+
setStatus("Saving…");
|
|
53
|
+
setTone(undefined);
|
|
54
|
+
try {
|
|
55
|
+
await onSave(data);
|
|
56
|
+
setStatus("Saved");
|
|
57
|
+
setTone("ok");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
setStatus(error instanceof Error ? error.message : "Save failed");
|
|
61
|
+
setTone("danger");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const isDirty = form.formState.isDirty;
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const onKeyDown = (event) => {
|
|
67
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
void onSubmit();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener("keydown", onKeyDown);
|
|
73
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
74
|
+
}, [onSubmit]);
|
|
75
|
+
return (_jsxs("form", { className: "vertebr-form", onSubmit: onSubmit, children: [_jsx("div", { className: "vertebr-form-fields", children: Object.entries(schema).map(([key, field]) => {
|
|
76
|
+
if (field.type === "boolean") {
|
|
77
|
+
return (_jsxs("div", { className: "vertebr-switch-row", children: [_jsx(Label.Root, { htmlFor: key, children: field.label ?? key }), _jsx(Controller, { control: form.control, name: key, render: ({ field: controllerField }) => (_jsx(Switch.Root, { className: "vertebr-switch", id: key, checked: Boolean(controllerField.value), onCheckedChange: controllerField.onChange, children: _jsx(Switch.Thumb, { className: "vertebr-switch-thumb" }) })) })] }, key));
|
|
78
|
+
}
|
|
79
|
+
return (_jsxs("div", { className: "vertebr-field", "data-span": spanFor(field), children: [_jsx(Label.Root, { htmlFor: key, children: field.label ?? key }), field.type === "richtext" ? (_jsx("textarea", { id: key, rows: 3, readOnly: field.readOnly, ...form.register(key) })) : (_jsx("input", { id: key, type: field.type === "number" ? "number" : "text", readOnly: field.readOnly, ...form.register(key, {
|
|
80
|
+
valueAsNumber: field.type === "number",
|
|
81
|
+
}) }))] }, key));
|
|
82
|
+
}) }), _jsxs("div", { className: "vertebr-actions", children: [_jsx("button", { className: "vertebr-btn vertebr-btn-primary", type: "submit", disabled: form.formState.isSubmitting || !isDirty, children: form.formState.isSubmitting ? "Saving…" : "Save changes" }), isDirty ? (_jsx("button", { className: "vertebr-btn", type: "button", onClick: () => {
|
|
83
|
+
form.reset(value);
|
|
84
|
+
setStatus(null);
|
|
85
|
+
setTone(undefined);
|
|
86
|
+
}, children: "Discard" })) : null, isDirty ? (_jsx("span", { className: "vertebr-dirty", children: "Unsaved" })) : status ? (_jsx("span", { className: "vertebr-status", "data-tone": tone, children: status })) : null, _jsx("span", { className: "vertebr-actions-spacer" }), _jsx("span", { className: "vertebr-kbd", children: "\u2318S" })] })] }));
|
|
87
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AdminEnvironment } from "./types";
|
|
2
|
+
type EnvSelectProps = {
|
|
3
|
+
environments: AdminEnvironment[];
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (next: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function EnvSelect({ environments, value, onChange }: EnvSelectProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=EnvSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnvSelect.d.ts","sourceRoot":"","sources":["../src/EnvSelect.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,KAAK,cAAc,GAAG;IACpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,+BAqD1E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as Select from "@radix-ui/react-select";
|
|
3
|
+
export function EnvSelect({ environments, value, onChange }) {
|
|
4
|
+
const active = environments.find((env) => env.id === value);
|
|
5
|
+
return (_jsxs(Select.Root, { value: value, onValueChange: onChange, children: [_jsxs(Select.Trigger, { className: "vertebr-select-trigger", "aria-label": "Environment", "data-tone": active?.tone ?? "neutral", children: [_jsxs("span", { className: "vertebr-select-value", children: [_jsx("span", { className: "vertebr-select-dot", "data-tone": active?.tone ?? "neutral" }), _jsx(Select.Value, { placeholder: "Select environment" })] }), _jsx(Select.Icon, { className: "vertebr-select-icon", children: _jsx(Chevron, {}) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { className: "vertebr-select-content", position: "popper", sideOffset: 6, children: _jsx(Select.Viewport, { className: "vertebr-select-viewport", children: environments.map((env) => (_jsxs(Select.Item, { value: env.id, className: "vertebr-select-item", "data-tone": env.tone ?? "neutral", children: [_jsxs("span", { className: "vertebr-select-value", children: [_jsx("span", { className: "vertebr-select-dot", "data-tone": env.tone ?? "neutral" }), _jsx(Select.ItemText, { children: env.label })] }), env.auth?.required ? (_jsx("span", { className: "vertebr-env-lock", children: "auth" })) : null, _jsx(Select.ItemIndicator, { className: "vertebr-select-check", children: _jsx(Check, {}) })] }, env.id))) }) }) })] }));
|
|
6
|
+
}
|
|
7
|
+
function Chevron() {
|
|
8
|
+
return (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: _jsx("path", { d: "M2.5 4.5 6 8l3.5-3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
9
|
+
}
|
|
10
|
+
function Check() {
|
|
11
|
+
return (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: _jsx("path", { d: "M2.5 6.2 4.8 8.5 9.5 3.8", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MetricCard } from "./types";
|
|
2
|
+
type MetricsBoardProps = {
|
|
3
|
+
label?: string;
|
|
4
|
+
metrics: MetricCard[];
|
|
5
|
+
onRefresh?: () => void;
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function MetricsBoard({ label, metrics, onRefresh, loading, }: MetricsBoardProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=MetricsBoard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetricsBoard.d.ts","sourceRoot":"","sources":["../src/MetricsBoard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,KAAK,iBAAiB,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAkB,EAClB,OAAO,EACP,SAAS,EACT,OAAO,GACR,EAAE,iBAAiB,+BAqCnB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function MetricsBoard({ label = "Overview", metrics, onRefresh, loading, }) {
|
|
3
|
+
return (_jsxs("div", { className: "vertebr-metrics", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] }), metrics.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: "No metrics registered yet." })) : (_jsx("div", { className: "vertebr-metrics-grid", children: metrics.map((metric) => (_jsxs("article", { className: "vertebr-metric-card", "data-tone": metric.tone ?? "neutral", children: [_jsx("p", { className: "vertebr-metric-label", children: metric.label }), _jsx("p", { className: "vertebr-metric-value", children: metric.value }), metric.hint ? (_jsx("p", { className: "vertebr-metric-hint", children: metric.hint })) : null] }, metric.id))) }))] }));
|
|
4
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FieldSchema, ResourceActionMeta } from "./types";
|
|
2
|
+
type ResourcePanelProps = {
|
|
3
|
+
schema: Record<string, FieldSchema>;
|
|
4
|
+
items: Record<string, unknown>[];
|
|
5
|
+
permissions?: {
|
|
6
|
+
create?: boolean;
|
|
7
|
+
update?: boolean;
|
|
8
|
+
delete?: boolean;
|
|
9
|
+
};
|
|
10
|
+
actions?: Record<string, ResourceActionMeta>;
|
|
11
|
+
rowIdKeys?: string[];
|
|
12
|
+
onUpdate?: (rowId: string, value: Record<string, unknown>) => Promise<void>;
|
|
13
|
+
onDelete?: (rowId: string) => Promise<void>;
|
|
14
|
+
onAction?: (rowId: string, actionId: string) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export declare function ResourcePanel({ schema, items, permissions, actions, rowIdKeys, onUpdate, onDelete, onAction, }: ResourcePanelProps): import("react").JSX.Element;
|
|
17
|
+
/** @deprecated Prefer ResourcePanel */
|
|
18
|
+
export declare function ResourceTable(props: {
|
|
19
|
+
schema: Record<string, FieldSchema>;
|
|
20
|
+
items: Record<string, unknown>[];
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=ResourceTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../src/ResourceTable.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE/D,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D,CAAC;AAmCF,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAY,EACZ,SAA0C,EAC1C,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,kBAAkB,+BAqKpB;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAClC,+BAEA"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
|
|
4
|
+
import { ConfigForm } from "./ConfigForm";
|
|
5
|
+
function formatCell(value) {
|
|
6
|
+
if (value == null)
|
|
7
|
+
return "—";
|
|
8
|
+
if (typeof value === "boolean")
|
|
9
|
+
return value ? "Yes" : "No";
|
|
10
|
+
if (typeof value === "object") {
|
|
11
|
+
if (value &&
|
|
12
|
+
typeof value === "object" &&
|
|
13
|
+
"toDate" in value &&
|
|
14
|
+
typeof value.toDate === "function") {
|
|
15
|
+
try {
|
|
16
|
+
return value.toDate().toLocaleString();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// fall through
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
function resolveRowId(row, keys) {
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
const value = row[key];
|
|
29
|
+
if (typeof value === "string" && value.trim())
|
|
30
|
+
return value;
|
|
31
|
+
if (typeof value === "number")
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export function ResourcePanel({ schema, items, permissions, actions = {}, rowIdKeys = ["id", "slug", "uid", "email"], onUpdate, onDelete, onAction, }) {
|
|
37
|
+
const [editing, setEditing] = useState(null);
|
|
38
|
+
const [busyAction, setBusyAction] = useState(null);
|
|
39
|
+
const canUpdate = Boolean(permissions?.update && onUpdate);
|
|
40
|
+
const canDelete = Boolean(permissions?.delete && onDelete);
|
|
41
|
+
const actionEntries = Object.entries(actions);
|
|
42
|
+
const columns = useMemo(() => {
|
|
43
|
+
const keys = Object.keys(schema);
|
|
44
|
+
const defs = keys.length === 0
|
|
45
|
+
? [
|
|
46
|
+
{
|
|
47
|
+
id: "raw",
|
|
48
|
+
header: "Item",
|
|
49
|
+
cell: ({ row }) => formatCell(row.original),
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
: keys.map((key) => ({
|
|
53
|
+
accessorKey: key,
|
|
54
|
+
header: schema[key]?.label ?? key,
|
|
55
|
+
cell: ({ getValue }) => formatCell(getValue()),
|
|
56
|
+
}));
|
|
57
|
+
if (canUpdate || canDelete || actionEntries.length > 0) {
|
|
58
|
+
defs.push({
|
|
59
|
+
id: "_actions",
|
|
60
|
+
header: "",
|
|
61
|
+
cell: ({ row }) => {
|
|
62
|
+
const rowId = resolveRowId(row.original, rowIdKeys);
|
|
63
|
+
if (!rowId)
|
|
64
|
+
return null;
|
|
65
|
+
return (_jsxs("div", { className: "vertebr-row-actions", children: [canUpdate ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: () => setEditing(row.original), children: "Edit" })) : null, actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn", disabled: busyAction === `${rowId}:${actionId}`, onClick: () => {
|
|
66
|
+
if (!onAction)
|
|
67
|
+
return;
|
|
68
|
+
setBusyAction(`${rowId}:${actionId}`);
|
|
69
|
+
void onAction(rowId, actionId).finally(() => setBusyAction(null));
|
|
70
|
+
}, children: meta.label }, actionId))), canDelete ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: () => {
|
|
71
|
+
if (window.confirm("Delete this row? This cannot be undone.")) {
|
|
72
|
+
void onDelete?.(rowId);
|
|
73
|
+
}
|
|
74
|
+
}, children: "Delete" })) : null] }));
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return defs;
|
|
79
|
+
}, [
|
|
80
|
+
schema,
|
|
81
|
+
canUpdate,
|
|
82
|
+
canDelete,
|
|
83
|
+
actionEntries,
|
|
84
|
+
busyAction,
|
|
85
|
+
onAction,
|
|
86
|
+
onDelete,
|
|
87
|
+
rowIdKeys,
|
|
88
|
+
]);
|
|
89
|
+
const table = useReactTable({
|
|
90
|
+
data: items,
|
|
91
|
+
columns,
|
|
92
|
+
getCoreRowModel: getCoreRowModel(),
|
|
93
|
+
});
|
|
94
|
+
const editingId = editing ? resolveRowId(editing, rowIdKeys) : null;
|
|
95
|
+
if (editing && editingId && onUpdate) {
|
|
96
|
+
const editableSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !field.readOnly));
|
|
97
|
+
return (_jsxs("div", { className: "vertebr-resource-edit", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsxs("h2", { children: ["Edit ", editingId] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: () => setEditing(null), children: "Back" })] }), _jsx(ConfigForm, { schema: editableSchema, value: editing, onSave: async (next) => {
|
|
98
|
+
await onUpdate(editingId, next);
|
|
99
|
+
setEditing(null);
|
|
100
|
+
} })] }));
|
|
101
|
+
}
|
|
102
|
+
if (items.length === 0) {
|
|
103
|
+
return _jsx("div", { className: "vertebr-empty", children: "No rows yet." });
|
|
104
|
+
}
|
|
105
|
+
return (_jsx("div", { className: "vertebr-table-wrap", children: _jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => (_jsx("th", { children: header.isPlaceholder
|
|
106
|
+
? null
|
|
107
|
+
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { children: row.getVisibleCells().map((cell) => (_jsx("td", { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }) }));
|
|
108
|
+
}
|
|
109
|
+
/** @deprecated Prefer ResourcePanel */
|
|
110
|
+
export function ResourceTable(props) {
|
|
111
|
+
return _jsx(ResourcePanel, { ...props });
|
|
112
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type VertebrAdminProps = {
|
|
2
|
+
/** Base URL for Vertebr API, e.g. `/admin/api` */
|
|
3
|
+
apiBase?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
/** localStorage key for remembering the selected environment */
|
|
6
|
+
envStorageKey?: string;
|
|
7
|
+
/** localStorage key for the access token */
|
|
8
|
+
tokenStorageKey?: string;
|
|
9
|
+
/** Optional host-provided token getter (Firebase ID token, etc.) */
|
|
10
|
+
getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
11
|
+
/** Signed-in identity shown in the dashboard. */
|
|
12
|
+
authUserLabel?: string;
|
|
13
|
+
/** Host-provided sign-out action. */
|
|
14
|
+
onSignOut?: () => void | Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=VertebrAdmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../src/VertebrAdmin.tsx"],"names":[],"mappings":"AAcA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtF,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAgCF,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAgdnB"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { createVertebrClient } from "./client";
|
|
4
|
+
import { ConfigForm } from "./ConfigForm";
|
|
5
|
+
import { EnvSelect } from "./EnvSelect";
|
|
6
|
+
import { MetricsBoard } from "./MetricsBoard";
|
|
7
|
+
import { ResourcePanel } from "./ResourceTable";
|
|
8
|
+
function readStored(key) {
|
|
9
|
+
try {
|
|
10
|
+
return localStorage.getItem(key);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function writeStored(key, value) {
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem(key, value);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// ignore
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function clearStored(key) {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.removeItem(key);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", getAccessToken, authUserLabel, onSignOut, }) {
|
|
33
|
+
const [environment, setEnvironment] = useState(() => readStored(envStorageKey) ?? "");
|
|
34
|
+
const [token, setToken] = useState(() => readStored(tokenStorageKey) ?? "");
|
|
35
|
+
const [tokenDraft, setTokenDraft] = useState(token);
|
|
36
|
+
const getEnvironment = useCallback(() => environment, [environment]);
|
|
37
|
+
const client = useMemo(() => createVertebrClient({
|
|
38
|
+
apiBase,
|
|
39
|
+
getEnvironment,
|
|
40
|
+
getAccessToken: getAccessToken ?? (() => token || null),
|
|
41
|
+
}), [apiBase, getEnvironment, getAccessToken, token]);
|
|
42
|
+
const [bootstrap, setBootstrap] = useState(null);
|
|
43
|
+
const [catalog, setCatalog] = useState(null);
|
|
44
|
+
const [selection, setSelection] = useState({ kind: "overview" });
|
|
45
|
+
const [resourceData, setResourceData] = useState(null);
|
|
46
|
+
const [configData, setConfigData] = useState(null);
|
|
47
|
+
const [metricsData, setMetricsData] = useState(null);
|
|
48
|
+
const [metricsLoading, setMetricsLoading] = useState(false);
|
|
49
|
+
const [error, setError] = useState(null);
|
|
50
|
+
const [loading, setLoading] = useState(true);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
let cancelled = false;
|
|
53
|
+
(async () => {
|
|
54
|
+
try {
|
|
55
|
+
const next = await client.getBootstrap();
|
|
56
|
+
if (cancelled)
|
|
57
|
+
return;
|
|
58
|
+
setBootstrap(next);
|
|
59
|
+
const known = next.environments.map((env) => env.id);
|
|
60
|
+
const preferred = (environment && known.includes(environment) && environment) ||
|
|
61
|
+
next.defaultEnvironment;
|
|
62
|
+
if (preferred !== environment) {
|
|
63
|
+
setEnvironment(preferred);
|
|
64
|
+
writeStored(envStorageKey, preferred);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (!cancelled) {
|
|
69
|
+
setError(err instanceof Error ? err.message : "Failed to load bootstrap");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
return () => {
|
|
74
|
+
cancelled = true;
|
|
75
|
+
};
|
|
76
|
+
}, [client, environment, envStorageKey]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!bootstrap || !environment)
|
|
79
|
+
return;
|
|
80
|
+
let cancelled = false;
|
|
81
|
+
(async () => {
|
|
82
|
+
try {
|
|
83
|
+
setLoading(true);
|
|
84
|
+
setError(null);
|
|
85
|
+
const next = await client.getCatalog();
|
|
86
|
+
if (cancelled)
|
|
87
|
+
return;
|
|
88
|
+
setCatalog(next);
|
|
89
|
+
setSelection((current) => {
|
|
90
|
+
if (current?.kind === "overview")
|
|
91
|
+
return current;
|
|
92
|
+
if (current?.kind === "resource") {
|
|
93
|
+
if (next.resources.some((r) => r.id === current.id))
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
96
|
+
if (current?.kind === "config") {
|
|
97
|
+
if (next.configs.some((c) => c.id === current.id))
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
if (next.metrics)
|
|
101
|
+
return { kind: "overview" };
|
|
102
|
+
const firstResource = next.resources[0];
|
|
103
|
+
if (firstResource)
|
|
104
|
+
return { kind: "resource", id: firstResource.id };
|
|
105
|
+
const firstConfig = next.configs[0];
|
|
106
|
+
if (firstConfig)
|
|
107
|
+
return { kind: "config", id: firstConfig.id };
|
|
108
|
+
return { kind: "overview" };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (!cancelled) {
|
|
113
|
+
setCatalog(null);
|
|
114
|
+
setError(err instanceof Error ? err.message : "Failed to load catalog");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
if (!cancelled)
|
|
119
|
+
setLoading(false);
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
return () => {
|
|
123
|
+
cancelled = true;
|
|
124
|
+
};
|
|
125
|
+
}, [client, bootstrap, environment]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!selection || !catalog)
|
|
128
|
+
return;
|
|
129
|
+
let cancelled = false;
|
|
130
|
+
(async () => {
|
|
131
|
+
try {
|
|
132
|
+
setError(null);
|
|
133
|
+
if (selection.kind === "overview") {
|
|
134
|
+
setMetricsLoading(true);
|
|
135
|
+
const data = await client.getMetrics();
|
|
136
|
+
if (!cancelled) {
|
|
137
|
+
setMetricsData(data);
|
|
138
|
+
setResourceData(null);
|
|
139
|
+
setConfigData(null);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (selection.kind === "resource") {
|
|
143
|
+
const data = await client.listResource(selection.id);
|
|
144
|
+
if (!cancelled) {
|
|
145
|
+
setResourceData(data);
|
|
146
|
+
setConfigData(null);
|
|
147
|
+
setMetricsData(null);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const data = await client.getConfig(selection.id);
|
|
152
|
+
if (!cancelled) {
|
|
153
|
+
setConfigData(data);
|
|
154
|
+
setResourceData(null);
|
|
155
|
+
setMetricsData(null);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (!cancelled) {
|
|
161
|
+
setError(err instanceof Error ? err.message : "Failed to load panel");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
if (!cancelled)
|
|
166
|
+
setMetricsLoading(false);
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
return () => {
|
|
170
|
+
cancelled = true;
|
|
171
|
+
};
|
|
172
|
+
}, [client, selection, catalog, environment]);
|
|
173
|
+
const refreshResource = useCallback(async () => {
|
|
174
|
+
if (selection?.kind !== "resource")
|
|
175
|
+
return;
|
|
176
|
+
const data = await client.listResource(selection.id);
|
|
177
|
+
setResourceData(data);
|
|
178
|
+
}, [client, selection]);
|
|
179
|
+
const refreshMetrics = useCallback(async () => {
|
|
180
|
+
setMetricsLoading(true);
|
|
181
|
+
try {
|
|
182
|
+
const data = await client.getMetrics();
|
|
183
|
+
setMetricsData(data);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setMetricsLoading(false);
|
|
187
|
+
}
|
|
188
|
+
}, [client]);
|
|
189
|
+
const envList = catalog?.environments ?? bootstrap?.environments ?? [];
|
|
190
|
+
const activeEnv = envList.find((env) => env.id === environment);
|
|
191
|
+
const authRequired = Boolean(activeEnv?.auth?.required);
|
|
192
|
+
const switchEnvironment = (nextEnv) => {
|
|
193
|
+
if (nextEnv === environment)
|
|
194
|
+
return;
|
|
195
|
+
const meta = envList.find((env) => env.id === nextEnv);
|
|
196
|
+
if (meta?.tone === "danger") {
|
|
197
|
+
const ok = window.confirm(`Switch to ${meta.label}? Writes will affect this environment.`);
|
|
198
|
+
if (!ok)
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
writeStored(envStorageKey, nextEnv);
|
|
202
|
+
setEnvironment(nextEnv);
|
|
203
|
+
setResourceData(null);
|
|
204
|
+
setConfigData(null);
|
|
205
|
+
};
|
|
206
|
+
const saveToken = () => {
|
|
207
|
+
const next = tokenDraft.trim();
|
|
208
|
+
setToken(next);
|
|
209
|
+
if (next)
|
|
210
|
+
writeStored(tokenStorageKey, next);
|
|
211
|
+
else
|
|
212
|
+
clearStored(tokenStorageKey);
|
|
213
|
+
};
|
|
214
|
+
const clearToken = () => {
|
|
215
|
+
setToken("");
|
|
216
|
+
setTokenDraft("");
|
|
217
|
+
clearStored(tokenStorageKey);
|
|
218
|
+
};
|
|
219
|
+
if (!bootstrap && loading) {
|
|
220
|
+
return _jsx("div", { className: "vertebr-loading", children: "Loading Vertebr\u2026" });
|
|
221
|
+
}
|
|
222
|
+
if (error && !bootstrap) {
|
|
223
|
+
return _jsx("div", { className: "vertebr-error", children: error });
|
|
224
|
+
}
|
|
225
|
+
const activeLabel = selection?.kind === "overview"
|
|
226
|
+
? (catalog?.metrics?.label ?? "Overview")
|
|
227
|
+
: selection?.kind === "resource"
|
|
228
|
+
? catalog?.resources.find((r) => r.id === selection.id)?.label
|
|
229
|
+
: selection?.kind === "config"
|
|
230
|
+
? catalog?.configs.find((c) => c.id === selection.id)?.label
|
|
231
|
+
: null;
|
|
232
|
+
return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsxs("aside", { className: "vertebr-sidebar", children: [_jsxs("div", { className: "vertebr-sidebar-top", children: [_jsxs("div", { className: "vertebr-brand", children: [_jsx("strong", { children: bootstrap?.brand ?? catalog?.brand ?? "Vertebr" }), _jsx("span", { children: title })] }), envList.length > 0 ? (_jsxs("div", { className: "vertebr-env", children: [_jsx("div", { className: "vertebr-nav-label", children: "Environment" }), _jsx(EnvSelect, { environments: envList, value: environment, onChange: switchEnvironment })] })) : null, _jsxs("nav", { className: "vertebr-nav", children: [catalog?.metrics ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Analytics" }), _jsx("button", { type: "button", "data-active": selection?.kind === "overview", onClick: () => setSelection({ kind: "overview" }), children: catalog.metrics.label })] })) : null, catalog && catalog.resources.length > 0 ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Resources" }), catalog.resources.map((resource) => (_jsx("button", { type: "button", "data-active": selection?.kind === "resource" &&
|
|
233
|
+
selection.id === resource.id, onClick: () => setSelection({ kind: "resource", id: resource.id }), children: resource.label }, resource.id)))] })) : null, catalog && catalog.configs.length > 0 ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Config" }), catalog.configs.map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" && selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null] })] }), _jsxs("div", { className: "vertebr-sidebar-bottom", children: [getAccessToken && authUserLabel ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Signed in" }), _jsx("p", { className: "vertebr-auth-hint", children: authUserLabel }), onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: () => void onSignOut(), children: "Sign out" })) : null] })) : null, !getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Access" }), _jsx("p", { className: "vertebr-auth-hint", children: authRequired
|
|
234
|
+
? `This environment requires auth${activeEnv?.auth?.claimKey
|
|
235
|
+
? ` (${activeEnv.auth.claimKey})`
|
|
236
|
+
: ""}.`
|
|
237
|
+
: "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => setTokenDraft(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: saveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: clearToken, children: "Clear" })) : null] })] })) : null] })] }), _jsxs("main", { className: "vertebr-main", children: [_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel ?? (catalog ? "Dashboard" : "Sign in") }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: !catalog && authRequired
|
|
238
|
+
? "Provide an access token to load this environment."
|
|
239
|
+
: selection?.kind === "overview"
|
|
240
|
+
? "Live metrics for the selected environment."
|
|
241
|
+
: selection?.kind === "resource"
|
|
242
|
+
? "Manage records scoped to the selected environment."
|
|
243
|
+
: selection?.kind === "config"
|
|
244
|
+
? "Owner-managed config for this environment."
|
|
245
|
+
: "Select a panel." })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }), _jsx("div", { className: "vertebr-content", children: catalog ? (_jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, metrics: metricsData?.metrics ?? [], loading: metricsLoading, onRefresh: () => void refreshMetrics() })) : null, selection?.kind === "resource" && resourceData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: resourceData.items.length })] }), _jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, permissions: resourceData.permissions, actions: resourceData.actions, onUpdate: resourceData.permissions.update
|
|
246
|
+
? async (rowId, value) => {
|
|
247
|
+
await client.updateResource(resourceData.id, rowId, value);
|
|
248
|
+
await refreshResource();
|
|
249
|
+
}
|
|
250
|
+
: undefined, onDelete: resourceData.permissions.delete
|
|
251
|
+
? async (rowId) => {
|
|
252
|
+
await client.deleteResource(resourceData.id, rowId);
|
|
253
|
+
await refreshResource();
|
|
254
|
+
}
|
|
255
|
+
: undefined, onAction: async (rowId, actionId) => {
|
|
256
|
+
await client.runResourceAction(resourceData.id, rowId, actionId);
|
|
257
|
+
await refreshResource();
|
|
258
|
+
} })] })) : null, selection?.kind === "config" && configData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Settings" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] }), _jsx(ConfigForm, { schema: configData.schema, value: configData.value, onSave: async (next) => {
|
|
259
|
+
const saved = await client.setConfig(configData.id, next);
|
|
260
|
+
setConfigData({
|
|
261
|
+
...configData,
|
|
262
|
+
value: saved.value,
|
|
263
|
+
});
|
|
264
|
+
} })] })) : null] })) : (_jsx("section", { className: "vertebr-panel", children: _jsx("div", { className: "vertebr-empty", children: authRequired
|
|
265
|
+
? "Authenticated access required for this environment."
|
|
266
|
+
: loading
|
|
267
|
+
? "Loading…"
|
|
268
|
+
: "Unable to load catalog." }) })) })] })] }));
|
|
269
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BootstrapResponse, CatalogResponse, ConfigResponse, MetricsResponse, ResourceListResponse } from "./types";
|
|
2
|
+
export declare const VERTEBR_ENV_HEADER = "x-vertebr-env";
|
|
3
|
+
export type VertebrClientOptions = {
|
|
4
|
+
apiBase: string;
|
|
5
|
+
getEnvironment: () => string;
|
|
6
|
+
getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
7
|
+
};
|
|
8
|
+
export type VertebrClient = {
|
|
9
|
+
getBootstrap: () => Promise<BootstrapResponse>;
|
|
10
|
+
getCatalog: () => Promise<CatalogResponse>;
|
|
11
|
+
getMetrics: () => Promise<MetricsResponse>;
|
|
12
|
+
listResource: (id: string) => Promise<ResourceListResponse>;
|
|
13
|
+
createResource: (id: string, value: Record<string, unknown>) => Promise<{
|
|
14
|
+
ok: boolean;
|
|
15
|
+
item: Record<string, unknown>;
|
|
16
|
+
}>;
|
|
17
|
+
updateResource: (id: string, rowId: string, value: Record<string, unknown>) => Promise<{
|
|
18
|
+
ok: boolean;
|
|
19
|
+
item: Record<string, unknown>;
|
|
20
|
+
}>;
|
|
21
|
+
deleteResource: (id: string, rowId: string) => Promise<{
|
|
22
|
+
ok: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
runResourceAction: (id: string, rowId: string, actionId: string, input?: Record<string, unknown>) => Promise<{
|
|
25
|
+
ok: boolean;
|
|
26
|
+
result?: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
getConfig: (id: string) => Promise<ConfigResponse>;
|
|
29
|
+
setConfig: (id: string, value: Record<string, unknown>) => Promise<ConfigResponse>;
|
|
30
|
+
};
|
|
31
|
+
export declare function createVertebrClient(apiBaseOrOptions: string | VertebrClientOptions, getEnvironment?: () => string): VertebrClient;
|
|
32
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,eAAe,EACf,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3C,UAAU,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3C,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC5D,cAAc,EAAE,CACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC7D,cAAc,EAAE,CACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC7D,cAAc,EAAE,CACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9B,iBAAiB,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChD,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACnD,SAAS,EAAE,CACT,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,GAAG,oBAAoB,EAC/C,cAAc,CAAC,EAAE,MAAM,MAAM,GAC5B,aAAa,CA4Ff"}
|