@crvouga/mockingbird-service-junction 1.7.0 → 1.8.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/CHANGELOG.md +19 -1
- package/dist/{chunk-WGRRZGPF.js → chunk-OW7EOZRN.js} +2 -2
- package/dist/{chunk-LUHSOIDO.js → chunk-WDRX5POF.js} +355 -22
- package/dist/chunk-WDRX5POF.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-LUHSOIDO.js.map +0 -7
- /package/dist/{chunk-WGRRZGPF.js.map → chunk-OW7EOZRN.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-junction
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.8.0 (2026-10-01)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- record SQL timelines and serve the admin explorer ([8ced17a](https://github.com/crvouga/mockingbird/commit/8ced17ac398e386ef4e9d7e01fc7744c34b95d07))
|
|
8
|
+
|
|
9
|
+
### Dependencies
|
|
10
|
+
|
|
11
|
+
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
+
|
|
13
|
+
## 1.7.1 (2026-09-30)
|
|
14
|
+
|
|
15
|
+
### Fixes and improvements
|
|
16
|
+
|
|
17
|
+
- keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
|
|
18
|
+
- size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
|
|
19
|
+
- keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
|
|
20
|
+
|
|
21
|
+
## 1.7.0 (2026-09-29)
|
|
4
22
|
|
|
5
23
|
### Features
|
|
6
24
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createRuntime,
|
|
4
4
|
defaultBillingTypesFrom,
|
|
5
5
|
pullCorpus
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WDRX5POF.js";
|
|
7
7
|
import {
|
|
8
8
|
defaultCorpus
|
|
9
9
|
} from "./chunk-C2UGIPPM.js";
|
|
@@ -537,4 +537,4 @@ export {
|
|
|
537
537
|
createServer2 as createServer,
|
|
538
538
|
serveTarget
|
|
539
539
|
};
|
|
540
|
-
//# sourceMappingURL=chunk-
|
|
540
|
+
//# sourceMappingURL=chunk-OW7EOZRN.js.map
|
|
@@ -1671,6 +1671,167 @@ function faultPresetList(value) {
|
|
|
1671
1671
|
})
|
|
1672
1672
|
};
|
|
1673
1673
|
}
|
|
1674
|
+
function mountSqlExplorer(root, api) {
|
|
1675
|
+
const cell = (value) => {
|
|
1676
|
+
if (value === null || value === void 0)
|
|
1677
|
+
return "";
|
|
1678
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1679
|
+
return String(value);
|
|
1680
|
+
}
|
|
1681
|
+
return JSON.stringify(value) ?? "";
|
|
1682
|
+
};
|
|
1683
|
+
root.replaceChildren();
|
|
1684
|
+
const layout = document.createElement("div");
|
|
1685
|
+
layout.className = "sql-layout";
|
|
1686
|
+
const tables = document.createElement("div");
|
|
1687
|
+
tables.className = "sql-tables";
|
|
1688
|
+
const stack = document.createElement("div");
|
|
1689
|
+
stack.className = "stack";
|
|
1690
|
+
const banner = document.createElement("p");
|
|
1691
|
+
banner.className = "banner";
|
|
1692
|
+
const grid = document.createElement("div");
|
|
1693
|
+
const input = document.createElement("textarea");
|
|
1694
|
+
input.className = "sql-input";
|
|
1695
|
+
input.spellcheck = false;
|
|
1696
|
+
input.placeholder = "SELECT * FROM ...";
|
|
1697
|
+
const actions = document.createElement("div");
|
|
1698
|
+
actions.className = "row-actions";
|
|
1699
|
+
const run = document.createElement("button");
|
|
1700
|
+
run.type = "button";
|
|
1701
|
+
run.textContent = "Run";
|
|
1702
|
+
actions.append(run);
|
|
1703
|
+
const result = document.createElement("pre");
|
|
1704
|
+
stack.append(banner, grid, input, actions, result);
|
|
1705
|
+
layout.append(tables, stack);
|
|
1706
|
+
root.append(layout);
|
|
1707
|
+
const showError = (error) => {
|
|
1708
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1709
|
+
banner.classList.add("show");
|
|
1710
|
+
};
|
|
1711
|
+
const clearError = () => {
|
|
1712
|
+
banner.textContent = "";
|
|
1713
|
+
banner.classList.remove("show");
|
|
1714
|
+
};
|
|
1715
|
+
const renderGrid = (columns, rows) => {
|
|
1716
|
+
grid.replaceChildren();
|
|
1717
|
+
if (columns.length === 0) {
|
|
1718
|
+
const empty = document.createElement("p");
|
|
1719
|
+
empty.className = "empty";
|
|
1720
|
+
empty.textContent = "No rows.";
|
|
1721
|
+
grid.append(empty);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
const table = document.createElement("table");
|
|
1725
|
+
const head = document.createElement("thead");
|
|
1726
|
+
const headRow = document.createElement("tr");
|
|
1727
|
+
for (const column of columns) {
|
|
1728
|
+
const th = document.createElement("th");
|
|
1729
|
+
th.textContent = column;
|
|
1730
|
+
headRow.append(th);
|
|
1731
|
+
}
|
|
1732
|
+
head.append(headRow);
|
|
1733
|
+
const body = document.createElement("tbody");
|
|
1734
|
+
for (const row of rows) {
|
|
1735
|
+
const tr = document.createElement("tr");
|
|
1736
|
+
for (const column of columns) {
|
|
1737
|
+
const td = document.createElement("td");
|
|
1738
|
+
td.dataset.label = column;
|
|
1739
|
+
td.textContent = cell(row[column]);
|
|
1740
|
+
tr.append(td);
|
|
1741
|
+
}
|
|
1742
|
+
body.append(tr);
|
|
1743
|
+
}
|
|
1744
|
+
table.append(head, body);
|
|
1745
|
+
grid.append(table);
|
|
1746
|
+
};
|
|
1747
|
+
const rowsOf = (value) => {
|
|
1748
|
+
if (!isRecord2(value) || !Array.isArray(value.rows))
|
|
1749
|
+
return [];
|
|
1750
|
+
return value.rows.flatMap((row) => isRecord2(row) ? [row] : []);
|
|
1751
|
+
};
|
|
1752
|
+
const columnsOf = (value, rows) => {
|
|
1753
|
+
if (isRecord2(value) && Array.isArray(value.columns)) {
|
|
1754
|
+
const named = value.columns.flatMap((column) => typeof column === "string" ? [column] : []);
|
|
1755
|
+
if (named.length > 0)
|
|
1756
|
+
return named;
|
|
1757
|
+
}
|
|
1758
|
+
const first = rows[0];
|
|
1759
|
+
return first === void 0 ? [] : Object.keys(first);
|
|
1760
|
+
};
|
|
1761
|
+
let selected = "";
|
|
1762
|
+
const loadPage = async (schema, name) => {
|
|
1763
|
+
selected = `${schema}.${name}`;
|
|
1764
|
+
const page = await api.get(`/sql/tables/${encodeURIComponent(schema)}/${encodeURIComponent(name)}?limit=50`);
|
|
1765
|
+
const rows = rowsOf(page);
|
|
1766
|
+
renderGrid(columnsOf(page, rows), rows);
|
|
1767
|
+
const total = isRecord2(page) && typeof page.total === "number" ? page.total : rows.length;
|
|
1768
|
+
result.textContent = `${selected} \xB7 ${total} rows`;
|
|
1769
|
+
for (const button of tables.querySelectorAll("button")) {
|
|
1770
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1771
|
+
continue;
|
|
1772
|
+
button.setAttribute("aria-current", button.dataset.schema === schema && button.dataset.table === name ? "true" : "false");
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
const loadTables = async () => {
|
|
1776
|
+
const payload = await api.get("/sql/tables");
|
|
1777
|
+
const listed = isRecord2(payload) && Array.isArray(payload.tables) ? payload.tables : [];
|
|
1778
|
+
tables.replaceChildren();
|
|
1779
|
+
if (listed.length === 0) {
|
|
1780
|
+
const empty = document.createElement("p");
|
|
1781
|
+
empty.className = "empty";
|
|
1782
|
+
empty.textContent = "No tables.";
|
|
1783
|
+
tables.append(empty);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
for (const item of listed) {
|
|
1787
|
+
if (!isRecord2(item) || typeof item.schema !== "string" || typeof item.name !== "string")
|
|
1788
|
+
continue;
|
|
1789
|
+
const button = document.createElement("button");
|
|
1790
|
+
button.type = "button";
|
|
1791
|
+
button.dataset.schema = item.schema;
|
|
1792
|
+
button.dataset.table = item.name;
|
|
1793
|
+
button.textContent = `${item.schema}.${item.name}`;
|
|
1794
|
+
tables.append(button);
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
tables.addEventListener("click", (event) => {
|
|
1798
|
+
const target = event.target;
|
|
1799
|
+
if (!(target instanceof Element))
|
|
1800
|
+
return;
|
|
1801
|
+
const button = target.closest("button");
|
|
1802
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1803
|
+
return;
|
|
1804
|
+
const schema = button.dataset.schema;
|
|
1805
|
+
const name = button.dataset.table;
|
|
1806
|
+
if (schema === void 0 || name === void 0)
|
|
1807
|
+
return;
|
|
1808
|
+
clearError();
|
|
1809
|
+
loadPage(schema, name).catch(showError);
|
|
1810
|
+
});
|
|
1811
|
+
run.addEventListener("click", () => {
|
|
1812
|
+
const sql = input.value.trim();
|
|
1813
|
+
if (sql === "") {
|
|
1814
|
+
showError(new Error("Enter a statement"));
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
clearError();
|
|
1818
|
+
api.send("POST", "/sql/query", { sql }).then((payload) => {
|
|
1819
|
+
const rows = rowsOf(payload);
|
|
1820
|
+
renderGrid(columnsOf(payload, rows), rows);
|
|
1821
|
+
const count = isRecord2(payload) && typeof payload.rowCount === "number" ? payload.rowCount : rows.length;
|
|
1822
|
+
const truncated = isRecord2(payload) && payload.truncated === true ? " (truncated)" : "";
|
|
1823
|
+
result.textContent = `${count} rows${truncated}`;
|
|
1824
|
+
}).catch(showError);
|
|
1825
|
+
});
|
|
1826
|
+
const namespace = document.getElementById("namespace");
|
|
1827
|
+
if (namespace) {
|
|
1828
|
+
namespace.addEventListener("change", () => {
|
|
1829
|
+
clearError();
|
|
1830
|
+
loadTables().catch(showError);
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
loadTables().catch(showError);
|
|
1834
|
+
}
|
|
1674
1835
|
function bootAdmin(config) {
|
|
1675
1836
|
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1676
1837
|
const sources = ["declared", "inferred", "mixed"];
|
|
@@ -2111,6 +2272,50 @@ function bootAdmin(config) {
|
|
|
2111
2272
|
if (render2)
|
|
2112
2273
|
await render2();
|
|
2113
2274
|
}
|
|
2275
|
+
const delay = (ms) => new Promise((resolve) => {
|
|
2276
|
+
window.setTimeout(resolve, ms);
|
|
2277
|
+
});
|
|
2278
|
+
const confirmAction = async (button, pending, done, work) => {
|
|
2279
|
+
if (button.dataset.pending === "1")
|
|
2280
|
+
return;
|
|
2281
|
+
const label = button.textContent ?? "";
|
|
2282
|
+
const width = button.offsetWidth;
|
|
2283
|
+
button.dataset.pending = "1";
|
|
2284
|
+
button.classList.add("pressed");
|
|
2285
|
+
button.setAttribute("aria-busy", "true");
|
|
2286
|
+
button.style.minWidth = `${width}px`;
|
|
2287
|
+
button.textContent = pending;
|
|
2288
|
+
try {
|
|
2289
|
+
await work();
|
|
2290
|
+
button.textContent = done;
|
|
2291
|
+
await delay(1e3);
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
showError(error);
|
|
2294
|
+
} finally {
|
|
2295
|
+
button.textContent = label;
|
|
2296
|
+
button.style.minWidth = "";
|
|
2297
|
+
button.classList.remove("pressed");
|
|
2298
|
+
button.removeAttribute("aria-busy");
|
|
2299
|
+
delete button.dataset.pending;
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
document.addEventListener("click", (event) => {
|
|
2303
|
+
const target = event.target;
|
|
2304
|
+
if (!(target instanceof Element))
|
|
2305
|
+
return;
|
|
2306
|
+
const button = target.closest("button");
|
|
2307
|
+
if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
|
|
2308
|
+
return;
|
|
2309
|
+
button.classList.add("pressed");
|
|
2310
|
+
const token = String(Date.now());
|
|
2311
|
+
button.dataset.press = token;
|
|
2312
|
+
window.setTimeout(() => {
|
|
2313
|
+
if (button.dataset.press !== token || button.dataset.pending === "1")
|
|
2314
|
+
return;
|
|
2315
|
+
button.classList.remove("pressed");
|
|
2316
|
+
delete button.dataset.press;
|
|
2317
|
+
}, 450);
|
|
2318
|
+
});
|
|
2114
2319
|
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2115
2320
|
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2116
2321
|
refresh().catch(showError);
|
|
@@ -2121,7 +2326,7 @@ function bootAdmin(config) {
|
|
|
2121
2326
|
refresh().catch(showError);
|
|
2122
2327
|
});
|
|
2123
2328
|
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2124
|
-
refresh().catch(showError);
|
|
2329
|
+
confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
|
|
2125
2330
|
});
|
|
2126
2331
|
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2127
2332
|
const target = event.target;
|
|
@@ -2244,10 +2449,10 @@ function bootAdmin(config) {
|
|
|
2244
2449
|
}).then(() => renderFaults()).catch(showError);
|
|
2245
2450
|
});
|
|
2246
2451
|
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2247
|
-
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2452
|
+
confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
|
|
2248
2453
|
});
|
|
2249
2454
|
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2250
|
-
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2455
|
+
confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
|
|
2251
2456
|
});
|
|
2252
2457
|
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2253
2458
|
const target = event.target;
|
|
@@ -2303,6 +2508,22 @@ function bootAdmin(config) {
|
|
|
2303
2508
|
panel.script = value.script;
|
|
2304
2509
|
return panel;
|
|
2305
2510
|
};
|
|
2511
|
+
const decodeExtension = (value) => {
|
|
2512
|
+
if (!isRecord2(value))
|
|
2513
|
+
return void 0;
|
|
2514
|
+
if (value.kind === "sql") {
|
|
2515
|
+
return {
|
|
2516
|
+
kind: "sql",
|
|
2517
|
+
id: typeof value.id === "string" && value.id !== "" ? value.id : "sql",
|
|
2518
|
+
title: typeof value.title === "string" && value.title !== "" ? value.title : "SQL",
|
|
2519
|
+
description: typeof value.description === "string" ? value.description : "Browse tables and run queries."
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
if (value.kind !== "panel")
|
|
2523
|
+
return void 0;
|
|
2524
|
+
const panel = decodePanel(value);
|
|
2525
|
+
return panel === void 0 ? void 0 : { kind: "panel", ...panel };
|
|
2526
|
+
};
|
|
2306
2527
|
const mountPanels = (panels) => {
|
|
2307
2528
|
const nav = el("nav", HTMLElement);
|
|
2308
2529
|
const views = el("views", HTMLElement);
|
|
@@ -2356,15 +2577,57 @@ function bootAdmin(config) {
|
|
|
2356
2577
|
return decoded === void 0 ? [] : [decoded];
|
|
2357
2578
|
});
|
|
2358
2579
|
mountPanels(panels);
|
|
2580
|
+
const extensions = requireList(isRecord2(manifest) ? manifest.extensions : void 0, "Extensions").flatMap((extension) => {
|
|
2581
|
+
const decoded = decodeExtension(extension);
|
|
2582
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2583
|
+
});
|
|
2584
|
+
const extensionPanels = extensions.flatMap((extension) => extension.kind === "panel" ? [extension] : []);
|
|
2585
|
+
mountPanels(extensionPanels);
|
|
2586
|
+
for (const extension of extensions) {
|
|
2587
|
+
if (extension.kind !== "sql")
|
|
2588
|
+
continue;
|
|
2589
|
+
const nav = el("nav", HTMLElement);
|
|
2590
|
+
const views = el("views", HTMLElement);
|
|
2591
|
+
const button = document.createElement("button");
|
|
2592
|
+
button.type = "button";
|
|
2593
|
+
button.dataset.view = extension.id;
|
|
2594
|
+
button.textContent = extension.title;
|
|
2595
|
+
nav.append(button);
|
|
2596
|
+
const section = document.createElement("section");
|
|
2597
|
+
section.className = "view";
|
|
2598
|
+
section.id = `view-${extension.id}`;
|
|
2599
|
+
section.hidden = true;
|
|
2600
|
+
const title = document.createElement("h2");
|
|
2601
|
+
title.textContent = extension.title;
|
|
2602
|
+
const lede = document.createElement("p");
|
|
2603
|
+
lede.className = "lede";
|
|
2604
|
+
lede.textContent = extension.description;
|
|
2605
|
+
const body = document.createElement("div");
|
|
2606
|
+
body.className = "panel-body";
|
|
2607
|
+
section.append(title, lede, body);
|
|
2608
|
+
views.append(section);
|
|
2609
|
+
mountSqlExplorer(body, panelApi);
|
|
2610
|
+
}
|
|
2359
2611
|
return refresh();
|
|
2360
2612
|
}).catch(showError);
|
|
2361
2613
|
}
|
|
2362
|
-
var clientPrelude = [
|
|
2614
|
+
var clientPrelude = [
|
|
2615
|
+
isRecord2,
|
|
2616
|
+
mountAdminBrand,
|
|
2617
|
+
startAdminBrand,
|
|
2618
|
+
faultPresetList,
|
|
2619
|
+
mountSqlExplorer,
|
|
2620
|
+
bootAdmin
|
|
2621
|
+
].map((fn) => fn.toString()).join("\n");
|
|
2363
2622
|
var adminClientSource = (config) => {
|
|
2364
2623
|
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2624
|
+
const entry = bootAdmin.name;
|
|
2625
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
|
|
2626
|
+
throw new Error("admin client cannot call its boot function");
|
|
2627
|
+
}
|
|
2365
2628
|
return `<script>
|
|
2366
2629
|
${clientPrelude}
|
|
2367
|
-
|
|
2630
|
+
${entry}(${payload});
|
|
2368
2631
|
</script>`;
|
|
2369
2632
|
};
|
|
2370
2633
|
|
|
@@ -2405,19 +2668,33 @@ var STANDARD_ADMIN_ROUTES = [
|
|
|
2405
2668
|
"GET /ui/manifest"
|
|
2406
2669
|
];
|
|
2407
2670
|
var PANEL_ID = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
2671
|
+
var RESERVED_VIEWS = /* @__PURE__ */ new Set(["overview", "state", "clock", "faults", "journal", "routes"]);
|
|
2672
|
+
var checkPanel = (panel, seen, label) => {
|
|
2673
|
+
if (!PANEL_ID.test(panel.id)) {
|
|
2674
|
+
throw new RangeError(`${label} id must match ${PANEL_ID}: ${JSON.stringify(panel.id)}`);
|
|
2675
|
+
}
|
|
2676
|
+
if (RESERVED_VIEWS.has(panel.id)) {
|
|
2677
|
+
throw new RangeError(`${label} id ${panel.id} is reserved by the admin shell`);
|
|
2678
|
+
}
|
|
2679
|
+
if (seen.has(panel.id))
|
|
2680
|
+
throw new RangeError(`duplicate admin view id ${panel.id}`);
|
|
2681
|
+
seen.add(panel.id);
|
|
2682
|
+
if (panel.title.trim() === "")
|
|
2683
|
+
throw new RangeError(`${label} ${panel.id} needs a title`);
|
|
2684
|
+
};
|
|
2408
2685
|
var assertAdminUi = (ui) => {
|
|
2409
|
-
if (!ui
|
|
2686
|
+
if (!ui)
|
|
2410
2687
|
return;
|
|
2411
2688
|
const seen = /* @__PURE__ */ new Set();
|
|
2412
|
-
for (const panel of ui.panels)
|
|
2413
|
-
|
|
2414
|
-
|
|
2689
|
+
for (const panel of ui.panels ?? [])
|
|
2690
|
+
checkPanel(panel, seen, "admin panel");
|
|
2691
|
+
for (const extension of ui.extensions ?? []) {
|
|
2692
|
+
if (extension.kind === "panel") {
|
|
2693
|
+
checkPanel(extension, seen, "admin extension");
|
|
2694
|
+
continue;
|
|
2415
2695
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
seen.add(panel.id);
|
|
2419
|
-
if (panel.title.trim() === "")
|
|
2420
|
-
throw new RangeError(`admin panel ${panel.id} needs a title`);
|
|
2696
|
+
const id = extension.id ?? "sql";
|
|
2697
|
+
checkPanel({ id, title: extension.title ?? "SQL" }, seen, "admin extension");
|
|
2421
2698
|
}
|
|
2422
2699
|
};
|
|
2423
2700
|
|
|
@@ -2578,9 +2855,24 @@ var renderAdminDocument = (service) => {
|
|
|
2578
2855
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
2579
2856
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
2580
2857
|
label.field input, label.field select, .controls input[type="password"] {
|
|
2581
|
-
background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
|
|
2582
|
-
padding: 7px 8px; min-height: 36px;
|
|
2583
|
-
}
|
|
2858
|
+
background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
|
|
2859
|
+
padding: 7px 8px; min-height: 36px; line-height: 1.2;
|
|
2860
|
+
}
|
|
2861
|
+
/* A native menu ignores min-height, so Preset rendered about half as tall as
|
|
2862
|
+
the text fields in the same form. Paint it like those fields. */
|
|
2863
|
+
label.field select {
|
|
2864
|
+
-webkit-appearance: none; appearance: none;
|
|
2865
|
+
padding-right: 28px;
|
|
2866
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
|
|
2867
|
+
background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
|
|
2868
|
+
}
|
|
2869
|
+
.stack > .field > input,
|
|
2870
|
+
.stack > .field > select,
|
|
2871
|
+
.stack > .field > textarea,
|
|
2872
|
+
dialog .field > input,
|
|
2873
|
+
dialog .field > select,
|
|
2874
|
+
dialog .field > textarea { width: 100%; }
|
|
2875
|
+
.icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
|
|
2584
2876
|
.icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
|
|
2585
2877
|
border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
|
|
2586
2878
|
background: var(--bg-elev);
|
|
@@ -2588,6 +2880,14 @@ var renderAdminDocument = (service) => {
|
|
|
2588
2880
|
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
|
|
2589
2881
|
.btn-quiet { background: transparent; }
|
|
2590
2882
|
.btn-danger { color: var(--err); }
|
|
2883
|
+
/* Held after mouseup. Clearing an empty journal changes nothing on screen,
|
|
2884
|
+
so the press itself has to stay visible. */
|
|
2885
|
+
.icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
|
|
2886
|
+
.icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
|
|
2887
|
+
background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
|
|
2888
|
+
}
|
|
2889
|
+
.btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
|
|
2890
|
+
.btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
|
|
2591
2891
|
/* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
|
|
2592
2892
|
record or the full nav row would widen the page past the viewport. */
|
|
2593
2893
|
.layout {
|
|
@@ -2628,6 +2928,12 @@ var renderAdminDocument = (service) => {
|
|
|
2628
2928
|
border-radius: 999px; padding: 4px 8px;
|
|
2629
2929
|
}
|
|
2630
2930
|
.chip i { color: var(--faint); font-style: normal; }
|
|
2931
|
+
.sql-layout {
|
|
2932
|
+
display: grid; grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr); gap: 16px;
|
|
2933
|
+
}
|
|
2934
|
+
.sql-tables { display: flex; flex-direction: column; gap: 4px; max-height: 28rem; overflow: auto; }
|
|
2935
|
+
.sql-tables button { text-align: left; }
|
|
2936
|
+
.sql-input { font-family: var(--mono); min-height: 7rem; width: 100%; }
|
|
2631
2937
|
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
2632
2938
|
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
2633
2939
|
th { color: var(--faint); font-weight: 600; font-size: 12px; }
|
|
@@ -2676,14 +2982,22 @@ var renderAdminDocument = (service) => {
|
|
|
2676
2982
|
input, select, textarea { font-size: 16px; }
|
|
2677
2983
|
.brand, .vendor, .controls { width: 100%; }
|
|
2678
2984
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
2679
|
-
|
|
2985
|
+
/* The layout fills the screen. A single-column grid stretches every auto
|
|
2986
|
+
row, so the tab bar grew with the leftover height and the buttons floated
|
|
2987
|
+
in the middle of it. The bar stays one row; the page below takes the rest. */
|
|
2988
|
+
.layout {
|
|
2989
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2990
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
2991
|
+
align-content: start;
|
|
2992
|
+
}
|
|
2680
2993
|
nav.side {
|
|
2994
|
+
align-self: start; height: auto;
|
|
2681
2995
|
flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
|
|
2682
2996
|
border-right: 0; border-bottom: 1px solid var(--line);
|
|
2683
2997
|
padding: 8px;
|
|
2684
2998
|
}
|
|
2685
2999
|
nav.side button { white-space: nowrap; flex: none; }
|
|
2686
|
-
.split { grid-template-columns: minmax(0, 1fr); }
|
|
3000
|
+
.split, .sql-layout { grid-template-columns: minmax(0, 1fr); }
|
|
2687
3001
|
main { padding: 14px; }
|
|
2688
3002
|
table, thead, tbody, tr, th, td { display: block; }
|
|
2689
3003
|
thead { display: none; }
|
|
@@ -2854,6 +3168,24 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2854
3168
|
status: 200,
|
|
2855
3169
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
2856
3170
|
});
|
|
3171
|
+
var describeExtension = (extension) => {
|
|
3172
|
+
if (extension.kind === "sql") {
|
|
3173
|
+
return {
|
|
3174
|
+
kind: "sql",
|
|
3175
|
+
id: extension.id ?? "sql",
|
|
3176
|
+
title: extension.title ?? "SQL",
|
|
3177
|
+
...extension.description !== void 0 ? { description: extension.description } : {}
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
return {
|
|
3181
|
+
kind: "panel",
|
|
3182
|
+
id: extension.id,
|
|
3183
|
+
title: extension.title,
|
|
3184
|
+
...extension.description !== void 0 ? { description: extension.description } : {},
|
|
3185
|
+
html: extension.html,
|
|
3186
|
+
...extension.script !== void 0 ? { script: extension.script } : {}
|
|
3187
|
+
};
|
|
3188
|
+
};
|
|
2857
3189
|
var adminUiRoutes = (service, ui) => {
|
|
2858
3190
|
const shell = () => renderAdminDocument(service);
|
|
2859
3191
|
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
@@ -2868,7 +3200,8 @@ var adminUiRoutes = (service, ui) => {
|
|
|
2868
3200
|
...panel.description !== void 0 ? { description: panel.description } : {},
|
|
2869
3201
|
html: panel.html,
|
|
2870
3202
|
...panel.script !== void 0 ? { script: panel.script } : {}
|
|
2871
|
-
}))
|
|
3203
|
+
})),
|
|
3204
|
+
extensions: (ui?.extensions ?? []).map(describeExtension)
|
|
2872
3205
|
})
|
|
2873
3206
|
};
|
|
2874
3207
|
};
|
|
@@ -3471,7 +3804,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3471
3804
|
};
|
|
3472
3805
|
|
|
3473
3806
|
// ../core/dist/version.js
|
|
3474
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3807
|
+
var PACKAGE_VERSION = true ? "1.8.0" : UNRELEASED_VERSION;
|
|
3475
3808
|
|
|
3476
3809
|
// ../core/dist/webhooks.js
|
|
3477
3810
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
@@ -11990,4 +12323,4 @@ export {
|
|
|
11990
12323
|
JunctionAPI,
|
|
11991
12324
|
LabAccountConflict
|
|
11992
12325
|
};
|
|
11993
|
-
//# sourceMappingURL=chunk-
|
|
12326
|
+
//# sourceMappingURL=chunk-WDRX5POF.js.map
|