gaard-api 0.2.11__tar.gz → 0.2.13__tar.gz
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.
- {gaard_api-0.2.11/src/gaard_api.egg-info → gaard_api-0.2.13}/PKG-INFO +1 -1
- {gaard_api-0.2.11 → gaard_api-0.2.13}/pyproject.toml +1 -1
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/services.py +1 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/main.js +446 -22
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/styles.css +22 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/admin.py +703 -1
- {gaard_api-0.2.11 → gaard_api-0.2.13/src/gaard_api.egg-info}/PKG-INFO +1 -1
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_admin_api.py +160 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/MANIFEST.in +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/README.md +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/setup.cfg +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/database.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/database_initial.sql +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/database_legacy_updates.sql +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/database_updates.sql +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/defaults.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/migration_runner.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/models.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/prompt_runtime.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin/security.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/getgaard.svg +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/identity.js +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack-all.js +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack.min.css +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/index.html +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/admin-web/package.json +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/analysis.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/dashboards.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/prompts.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/query.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api/v1/schema.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/api_registry.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/auth_dependencies.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/auth_hooks.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/cli.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/cli_commands.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/conversations.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/core/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/core/error_handlers.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/core/schema_cache.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/core/settings.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/example_data/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/example_database.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/extension_services.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/extensions.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/license.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/main.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/package_updates.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/py.typed +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/query_hooks.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/server_cli.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/siem.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api/tls_http.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api.egg-info/SOURCES.txt +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api.egg-info/dependency_links.txt +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api.egg-info/entry_points.txt +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api.egg-info/requires.txt +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/src/gaard_api.egg-info/top_level.txt +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_analysis_api.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_api_cli.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_api_registry.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_conversation_api.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_database_migrations.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_error_handlers.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_license_api.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_license_service.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_prompt_runtime.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_query_error_messages.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_server_cli.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_settings.py +0 -0
- {gaard_api-0.2.11 → gaard_api-0.2.13}/tests/test_tls_http.py +0 -0
|
@@ -2229,6 +2229,7 @@ def build_table_settings(
|
|
|
2229
2229
|
tables[table.name] = {
|
|
2230
2230
|
"selected": bool(existing.get("selected", True)),
|
|
2231
2231
|
"description": str(existing.get("description", "")),
|
|
2232
|
+
"view_description": str(existing.get("view_description", "")),
|
|
2232
2233
|
"primary_key_prompt": str(existing.get("primary_key_prompt", "")),
|
|
2233
2234
|
"foreign_key_prompt": str(existing.get("foreign_key_prompt", "")),
|
|
2234
2235
|
"join_logic": str(existing.get("join_logic", "")),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createIdentityModule } from "./identity.js";
|
|
2
2
|
|
|
3
3
|
var app = document.querySelector("#app");
|
|
4
|
+
var renderedSection = "";
|
|
5
|
+
var scrollRestoreGeneration = 0;
|
|
4
6
|
var licensePackageUpdatePollTimer = null;
|
|
5
7
|
var overviewGridStack = null;
|
|
6
8
|
var overviewGridSaveTimer = null;
|
|
@@ -91,6 +93,20 @@ var state = {
|
|
|
91
93
|
datasourceSchemaSelectedObjectName: "",
|
|
92
94
|
datasourceSchemaShowEnabledOnly: false,
|
|
93
95
|
datasourceSchemaDraftTables: null,
|
|
96
|
+
datasourceViewDialog: {
|
|
97
|
+
open: false,
|
|
98
|
+
mode: "create",
|
|
99
|
+
originalName: "",
|
|
100
|
+
loadedName: "",
|
|
101
|
+
loadedSql: "",
|
|
102
|
+
name: "",
|
|
103
|
+
view_description: "",
|
|
104
|
+
sql: "",
|
|
105
|
+
error: "",
|
|
106
|
+
success: "",
|
|
107
|
+
busy: "",
|
|
108
|
+
executed: false
|
|
109
|
+
},
|
|
94
110
|
datasourceDetailTab: "config",
|
|
95
111
|
datasourceSchemaContentTab: "details",
|
|
96
112
|
datasourceSchemaEditorTab: "tables",
|
|
@@ -420,24 +436,100 @@ function logout() {
|
|
|
420
436
|
}
|
|
421
437
|
function render() {
|
|
422
438
|
if (!app) return;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
439
|
+
const scrollSnapshot = captureScrollSnapshot();
|
|
440
|
+
const restoreGeneration = ++scrollRestoreGeneration;
|
|
441
|
+
if (!state.token) {
|
|
442
|
+
renderLogin();
|
|
443
|
+
} else if (state.mustChangePassword) {
|
|
444
|
+
renderPasswordChange();
|
|
445
|
+
} else {
|
|
446
|
+
renderShell();
|
|
447
|
+
}
|
|
448
|
+
restoreScrollSnapshot(scrollSnapshot, restoreGeneration);
|
|
426
449
|
}
|
|
427
450
|
function renderDatasourceSchemaPreservingScroll() {
|
|
428
|
-
const content = document.querySelector(".content");
|
|
429
|
-
const schemaObjectListBody = document.querySelector(".schema-object-list-body");
|
|
430
|
-
const contentScrollTop = content?.scrollTop || 0;
|
|
431
|
-
const schemaObjectListScrollTop = schemaObjectListBody?.scrollTop || 0;
|
|
432
|
-
|
|
433
451
|
render();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
if (
|
|
452
|
+
}
|
|
453
|
+
function collectScrollableElements() {
|
|
454
|
+
const targets = [];
|
|
455
|
+
const rootScroller = document.scrollingElement || document.documentElement;
|
|
456
|
+
if (rootScroller) targets.push(rootScroller);
|
|
457
|
+
if (!app) return targets;
|
|
458
|
+
app.querySelectorAll("*").forEach((element) => {
|
|
459
|
+
if (!(element instanceof HTMLElement)) return;
|
|
460
|
+
const styles = window.getComputedStyle(element);
|
|
461
|
+
const canScrollY = ["auto", "scroll", "overlay"].includes(styles.overflowY) && element.scrollHeight > element.clientHeight + 1;
|
|
462
|
+
const canScrollX = ["auto", "scroll", "overlay"].includes(styles.overflowX) && element.scrollWidth > element.clientWidth + 1;
|
|
463
|
+
if (canScrollY || canScrollX) targets.push(element);
|
|
464
|
+
});
|
|
465
|
+
return targets;
|
|
466
|
+
}
|
|
467
|
+
function getScrollElementKey(element) {
|
|
468
|
+
const rootScroller = document.scrollingElement || document.documentElement;
|
|
469
|
+
if (element === rootScroller) return "__document__";
|
|
470
|
+
const parts = [];
|
|
471
|
+
let current = element;
|
|
472
|
+
while (current && current !== app && current instanceof HTMLElement) {
|
|
473
|
+
let part = current.tagName.toLowerCase();
|
|
474
|
+
if (current.id) {
|
|
475
|
+
part += `#${current.id}`;
|
|
476
|
+
} else {
|
|
477
|
+
const dataEntry = Object.entries(current.dataset || {}).find(([, value]) => value);
|
|
478
|
+
if (dataEntry) part += `[data-${dataEntry[0]}="${dataEntry[1]}"]`;
|
|
479
|
+
const classNames = Array.from(current.classList || []).sort();
|
|
480
|
+
if (classNames.length) part += `.${classNames.join(".")}`;
|
|
481
|
+
const parent = current.parentElement;
|
|
482
|
+
if (parent) {
|
|
483
|
+
const siblings = Array.from(parent.children).filter((sibling) => sibling.tagName === current.tagName);
|
|
484
|
+
if (siblings.length > 1) part += `:nth(${siblings.indexOf(current)})`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
parts.unshift(part);
|
|
488
|
+
current = current.parentElement;
|
|
489
|
+
}
|
|
490
|
+
return parts.join(">");
|
|
491
|
+
}
|
|
492
|
+
function getScrollPosition(element) {
|
|
493
|
+
const maxTop = Math.max(0, element.scrollHeight - element.clientHeight);
|
|
494
|
+
const maxLeft = Math.max(0, element.scrollWidth - element.clientWidth);
|
|
495
|
+
return {
|
|
496
|
+
top: element.scrollTop,
|
|
497
|
+
left: element.scrollLeft,
|
|
498
|
+
atBottom: maxTop > 0 && maxTop - element.scrollTop <= 2,
|
|
499
|
+
atRight: maxLeft > 0 && maxLeft - element.scrollLeft <= 2
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function captureScrollSnapshot() {
|
|
503
|
+
if (!app || !renderedSection) return null;
|
|
504
|
+
const positions = new Map();
|
|
505
|
+
collectScrollableElements().forEach((element) => {
|
|
506
|
+
positions.set(getScrollElementKey(element), getScrollPosition(element));
|
|
507
|
+
});
|
|
508
|
+
return { section: renderedSection, positions };
|
|
509
|
+
}
|
|
510
|
+
function restoreScrollSnapshot(snapshot, restoreGeneration) {
|
|
511
|
+
if (!snapshot || snapshot.section !== renderedSection || !snapshot.positions.size) return;
|
|
512
|
+
window.requestAnimationFrame(() => {
|
|
513
|
+
if (restoreGeneration !== scrollRestoreGeneration) return;
|
|
514
|
+
window.requestAnimationFrame(() => {
|
|
515
|
+
if (restoreGeneration !== scrollRestoreGeneration) return;
|
|
516
|
+
const targets = new Map();
|
|
517
|
+
collectScrollableElements().forEach((element) => {
|
|
518
|
+
targets.set(getScrollElementKey(element), element);
|
|
519
|
+
});
|
|
520
|
+
snapshot.positions.forEach((position, key) => {
|
|
521
|
+
const element = targets.get(key);
|
|
522
|
+
if (!element) return;
|
|
523
|
+
const maxTop = Math.max(0, element.scrollHeight - element.clientHeight);
|
|
524
|
+
const maxLeft = Math.max(0, element.scrollWidth - element.clientWidth);
|
|
525
|
+
element.scrollTop = position.atBottom ? maxTop : Math.min(position.top, maxTop);
|
|
526
|
+
element.scrollLeft = position.atRight ? maxLeft : Math.min(position.left, maxLeft);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
});
|
|
439
530
|
}
|
|
440
531
|
function renderLogin() {
|
|
532
|
+
renderedSection = "login";
|
|
441
533
|
app.innerHTML = `
|
|
442
534
|
<main class="login-shell">
|
|
443
535
|
<section class="login-panel">
|
|
@@ -476,6 +568,7 @@ function renderLogin() {
|
|
|
476
568
|
});
|
|
477
569
|
}
|
|
478
570
|
function renderPasswordChange() {
|
|
571
|
+
renderedSection = "password-change";
|
|
479
572
|
app.innerHTML = `
|
|
480
573
|
<main class="login-shell">
|
|
481
574
|
<section class="login-panel">
|
|
@@ -519,6 +612,7 @@ function renderPasswordChange() {
|
|
|
519
612
|
}
|
|
520
613
|
function renderShell() {
|
|
521
614
|
const activeLabel = getSectionLabel(state.section);
|
|
615
|
+
renderedSection = state.section;
|
|
522
616
|
app.innerHTML = `
|
|
523
617
|
<div class="app-shell">
|
|
524
618
|
${renderSidebar()}
|
|
@@ -1441,7 +1535,8 @@ function renderDatasources() {
|
|
|
1441
1535
|
<div class="panel-body datasource-detail-grid">${renderDatasourceForm(selected)}</div>
|
|
1442
1536
|
</section>
|
|
1443
1537
|
</div>
|
|
1444
|
-
${selected ? renderDatasourceSchema() : ""}
|
|
1538
|
+
${selected ? renderDatasourceSchema() : ""}
|
|
1539
|
+
${renderDatasourceViewDialog()}`;
|
|
1445
1540
|
}
|
|
1446
1541
|
function getSelectedDatasource() {
|
|
1447
1542
|
if (state.selectedDatasourceId === "new") return null;
|
|
@@ -1782,7 +1877,13 @@ function renderDatasourceSchema() {
|
|
|
1782
1877
|
const activeContentTab = getDatasourceExtensionTab("schema", extensionPanels);
|
|
1783
1878
|
return `
|
|
1784
1879
|
<section class="panel">
|
|
1785
|
-
<div class="panel-header"
|
|
1880
|
+
<div class="panel-header">
|
|
1881
|
+
<h2>Schema introspection</h2>
|
|
1882
|
+
<div class="panel-header-actions">
|
|
1883
|
+
<button type="button" id="create-datasource-view">Create view</button>
|
|
1884
|
+
<span class="badge">${escapeHtml(schema?.introspected_at || "not cached")}</span>
|
|
1885
|
+
</div>
|
|
1886
|
+
</div>
|
|
1786
1887
|
<div class="panel-body datasource-schema-layout">
|
|
1787
1888
|
${state.datasourceSchemaLoading ? `<p class="muted">loading schema</p>` : state.datasourceSchemaError ? `<p class="error">${escapeHtml(state.datasourceSchemaError)}</p>` : schema ? `
|
|
1788
1889
|
<form id="datasource-schema-form" class="schema-editor mobile-active">
|
|
@@ -1808,6 +1909,41 @@ function renderDatasourceSchema() {
|
|
|
1808
1909
|
</div>
|
|
1809
1910
|
</section>`;
|
|
1810
1911
|
}
|
|
1912
|
+
function renderDatasourceViewDialog() {
|
|
1913
|
+
const dialog = state.datasourceViewDialog || {};
|
|
1914
|
+
if (!dialog.open) return "";
|
|
1915
|
+
const busy = dialog.busy || "";
|
|
1916
|
+
const isBusy = Boolean(busy);
|
|
1917
|
+
const isEdit = dialog.mode === "edit";
|
|
1918
|
+
const title = isEdit ? "Edit view" : "Create view";
|
|
1919
|
+
return `
|
|
1920
|
+
<div class="modal-backdrop" data-datasource-view-backdrop>
|
|
1921
|
+
<section class="modal-panel" role="dialog" aria-modal="true" aria-labelledby="datasource-view-dialog-title">
|
|
1922
|
+
<div class="modal-header">
|
|
1923
|
+
<div>
|
|
1924
|
+
<h2 id="datasource-view-dialog-title">${escapeHtml(title)}</h2>
|
|
1925
|
+
</div>
|
|
1926
|
+
</div>
|
|
1927
|
+
<form id="datasource-view-form" class="form-grid">
|
|
1928
|
+
<label>View name<input name="name" value="${escapeHtml(dialog.name || "")}" autocomplete="off" /></label>
|
|
1929
|
+
<label>View description<textarea name="view_description">${escapeHtml(dialog.view_description || "")}</textarea></label>
|
|
1930
|
+
<label>SQL<textarea name="sql" class="textarea-small" spellcheck="false">${escapeHtml(dialog.sql || "")}</textarea></label>
|
|
1931
|
+
${busy === "load" ? `<p class="muted"><span class="spinner" aria-hidden="true"></span> Loading view definition.</p>` : ""}
|
|
1932
|
+
${dialog.error ? `<p class="error">${escapeHtml(dialog.error)}</p>` : ""}
|
|
1933
|
+
${dialog.success ? `<p class="success">${escapeHtml(dialog.success)}</p>` : ""}
|
|
1934
|
+
<div class="form-actions modal-actions">
|
|
1935
|
+
<button type="button" data-datasource-view-cancel ${isBusy ? "disabled" : ""}>Cancel</button>
|
|
1936
|
+
<button type="button" data-datasource-view-generate ${isBusy ? "disabled" : ""}>${renderBusyButtonLabel(busy, "generate", "Generate SQL")}</button>
|
|
1937
|
+
<button type="button" data-datasource-view-execute ${isBusy ? "disabled" : ""}>${renderBusyButtonLabel(busy, "execute", "Execute SQL")}</button>
|
|
1938
|
+
<button type="submit" class="primary" ${isBusy ? "disabled" : ""}>${renderBusyButtonLabel(busy, "save", "Save View")}</button>
|
|
1939
|
+
</div>
|
|
1940
|
+
</form>
|
|
1941
|
+
</section>
|
|
1942
|
+
</div>`;
|
|
1943
|
+
}
|
|
1944
|
+
function renderBusyButtonLabel(activeBusy, key, label) {
|
|
1945
|
+
return activeBusy === key ? `<span class="spinner" aria-hidden="true"></span><span>${escapeHtml(label)}</span>` : escapeHtml(label);
|
|
1946
|
+
}
|
|
1811
1947
|
function getDatasourceExtensionPanels(slot, arguments_) {
|
|
1812
1948
|
const renderer = slot === "detail" ? "renderDetail" : "renderSchema";
|
|
1813
1949
|
const label = slot === "detail" ? "detailLabel" : "schemaLabel";
|
|
@@ -1840,6 +1976,7 @@ function getDatasourceSchemaDraftTables(rawTables, tableSettings) {
|
|
|
1840
1976
|
state.datasourceSchemaDraftTables[table.name] = {
|
|
1841
1977
|
selected: settings.selected !== false,
|
|
1842
1978
|
description: settings.description || "",
|
|
1979
|
+
view_description: settings.view_description || "",
|
|
1843
1980
|
primary_key_prompt: settings.primary_key_prompt || "",
|
|
1844
1981
|
foreign_key_prompt: settings.foreign_key_prompt || "",
|
|
1845
1982
|
join_logic: settings.join_logic || ""
|
|
@@ -2035,11 +2172,10 @@ function renderDatasourceObjectDetails(table, settings) {
|
|
|
2035
2172
|
<h3>${escapeHtml(table.name)}</h3>
|
|
2036
2173
|
<span class="badge">${escapeHtml(objectType)}</span>
|
|
2037
2174
|
</div>
|
|
2175
|
+
${objectType === "view" ? `<div class="schema-object-actions"><button type="button" data-edit-datasource-view="${escapeHtml(table.name)}">Edit view</button><button type="button" class="danger" data-delete-datasource-view="${escapeHtml(table.name)}">Delete view</button></div>` : ""}
|
|
2038
2176
|
</div>
|
|
2039
2177
|
<div class="schema-object-columns">${escapeHtml((table.columns || []).map((column) => `${column.name}:${column.type}${column.primary_key ? " pk" : ""}`).join(", ") || "No columns available.")}</div>
|
|
2040
|
-
<label>
|
|
2041
|
-
<label>Primary key guidance<input data-schema-detail="primary_key_prompt" name="${escapeHtml(table.name)}__primary_key_prompt" value="${escapeHtml(settings.primary_key_prompt || "")}" /></label>
|
|
2042
|
-
<label>Foreign key guidance<input data-schema-detail="foreign_key_prompt" name="${escapeHtml(table.name)}__foreign_key_prompt" value="${escapeHtml(settings.foreign_key_prompt || "")}" /></label>
|
|
2178
|
+
<label>Datasource logic<textarea data-schema-detail="description" class="textarea-small" name="${escapeHtml(table.name)}__description">${escapeHtml(settings.description || "")}</textarea></label>
|
|
2043
2179
|
<label>Join logic<textarea data-schema-detail="join_logic" class="textarea-small" name="${escapeHtml(table.name)}__join_logic">${escapeHtml(settings.join_logic || "")}</textarea></label>`;
|
|
2044
2180
|
}
|
|
2045
2181
|
function renderSchemaCache() {
|
|
@@ -2263,6 +2399,16 @@ function attachSectionHandlers() {
|
|
|
2263
2399
|
document.querySelector("#datasource-type")?.addEventListener("change", syncDatasourceTypeFields);
|
|
2264
2400
|
document.querySelector("[data-connection-field='connection_mode']")?.addEventListener("change", syncOdbcConnectionModeFields);
|
|
2265
2401
|
document.querySelector("#datasource-schema-form")?.addEventListener("submit", saveDatasourceSchema);
|
|
2402
|
+
document.querySelector("#create-datasource-view")?.addEventListener("click", openDatasourceViewDialog);
|
|
2403
|
+
document.querySelector("#datasource-view-form")?.addEventListener("submit", saveDatasourceView);
|
|
2404
|
+
document.querySelector("[data-datasource-view-generate]")?.addEventListener("click", generateDatasourceViewSql);
|
|
2405
|
+
document.querySelector("[data-datasource-view-execute]")?.addEventListener("click", executeDatasourceViewSql);
|
|
2406
|
+
document.querySelector("[data-datasource-view-cancel]")?.addEventListener("click", closeDatasourceViewDialog);
|
|
2407
|
+
document.querySelector("[data-datasource-view-backdrop]")?.addEventListener("click", (event) => {
|
|
2408
|
+
if (event.target === event.currentTarget && !state.datasourceViewDialog?.busy) {
|
|
2409
|
+
closeDatasourceViewDialog();
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2266
2412
|
document.querySelectorAll("[data-datasource-detail-tab]").forEach((button) => button.addEventListener("click", () => {
|
|
2267
2413
|
state.datasourceDetailTab = button.dataset.datasourceDetailTab || "config";
|
|
2268
2414
|
render();
|
|
@@ -2294,6 +2440,12 @@ function attachSectionHandlers() {
|
|
|
2294
2440
|
render();
|
|
2295
2441
|
});
|
|
2296
2442
|
});
|
|
2443
|
+
document.querySelectorAll("[data-delete-datasource-view]").forEach((button) => {
|
|
2444
|
+
button.addEventListener("click", deleteDatasourceView);
|
|
2445
|
+
});
|
|
2446
|
+
document.querySelectorAll("[data-edit-datasource-view]").forEach((button) => {
|
|
2447
|
+
button.addEventListener("click", openEditDatasourceViewDialog);
|
|
2448
|
+
});
|
|
2297
2449
|
document.querySelector("#invalidate-schema-cache")?.addEventListener("click", invalidateSchemaCache);
|
|
2298
2450
|
document.querySelector("#test-datasource")?.addEventListener("click", testDatasource);
|
|
2299
2451
|
document.querySelector("#introspect-datasource")?.addEventListener("click", introspectDatasource);
|
|
@@ -2344,6 +2496,7 @@ function attachSectionHandlers() {
|
|
|
2344
2496
|
state.datasourceSchemaError = "";
|
|
2345
2497
|
state.datasourceSchemaSelectedObjectName = "";
|
|
2346
2498
|
state.datasourceSchemaDraftTables = null;
|
|
2499
|
+
resetDatasourceViewDialog();
|
|
2347
2500
|
render();
|
|
2348
2501
|
});
|
|
2349
2502
|
document.querySelectorAll("[data-prompt]").forEach((button) => {
|
|
@@ -2355,6 +2508,7 @@ function attachSectionHandlers() {
|
|
|
2355
2508
|
document.querySelectorAll("[data-datasource]").forEach((button) => {
|
|
2356
2509
|
button.addEventListener("click", async () => {
|
|
2357
2510
|
state.selectedDatasourceId = Number(button.dataset.datasource);
|
|
2511
|
+
resetDatasourceViewDialog();
|
|
2358
2512
|
await loadDatasourceExtensions();
|
|
2359
2513
|
await loadDatasourceSchema();
|
|
2360
2514
|
});
|
|
@@ -3179,6 +3333,269 @@ async function testDatasource() {
|
|
|
3179
3333
|
setMessage("error", extractErrorMessage(error));
|
|
3180
3334
|
}
|
|
3181
3335
|
}
|
|
3336
|
+
function defaultDatasourceViewDialog() {
|
|
3337
|
+
return {
|
|
3338
|
+
open: false,
|
|
3339
|
+
mode: "create",
|
|
3340
|
+
originalName: "",
|
|
3341
|
+
loadedName: "",
|
|
3342
|
+
loadedSql: "",
|
|
3343
|
+
name: "",
|
|
3344
|
+
view_description: "",
|
|
3345
|
+
sql: "",
|
|
3346
|
+
error: "",
|
|
3347
|
+
success: "",
|
|
3348
|
+
busy: "",
|
|
3349
|
+
executed: false
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
function resetDatasourceViewDialog() {
|
|
3353
|
+
state.datasourceViewDialog = defaultDatasourceViewDialog();
|
|
3354
|
+
}
|
|
3355
|
+
function openDatasourceViewDialog() {
|
|
3356
|
+
state.datasourceViewDialog = {
|
|
3357
|
+
...defaultDatasourceViewDialog(),
|
|
3358
|
+
mode: "create",
|
|
3359
|
+
open: true
|
|
3360
|
+
};
|
|
3361
|
+
render();
|
|
3362
|
+
}
|
|
3363
|
+
async function openEditDatasourceViewDialog(event) {
|
|
3364
|
+
const selected = getSelectedDatasource();
|
|
3365
|
+
const viewName = event.currentTarget?.dataset?.editDatasourceView || "";
|
|
3366
|
+
if (!selected || !viewName) return;
|
|
3367
|
+
syncDatasourceSchemaDraftFromForm();
|
|
3368
|
+
const rawTables = state.datasourceSchema?.item?.raw_schema?.tables || [];
|
|
3369
|
+
const tableSettings = state.datasourceSchema?.item?.table_settings?.tables || {};
|
|
3370
|
+
const draftSettings = getDatasourceSchemaDraftTables(rawTables, tableSettings)[viewName] || {};
|
|
3371
|
+
const draftViewDescription = draftSettings.view_description || "";
|
|
3372
|
+
state.datasourceViewDialog = {
|
|
3373
|
+
...defaultDatasourceViewDialog(),
|
|
3374
|
+
open: true,
|
|
3375
|
+
mode: "edit",
|
|
3376
|
+
originalName: viewName,
|
|
3377
|
+
loadedName: viewName,
|
|
3378
|
+
name: viewName,
|
|
3379
|
+
view_description: draftViewDescription,
|
|
3380
|
+
busy: "load"
|
|
3381
|
+
};
|
|
3382
|
+
render();
|
|
3383
|
+
try {
|
|
3384
|
+
const result = await api(`/api/v1/admin/datasources/${selected.id}/schema/views/${encodeURIComponent(viewName)}`);
|
|
3385
|
+
const item = result.item || {};
|
|
3386
|
+
const viewDescription = draftViewDescription || item.view_description || item.description || "";
|
|
3387
|
+
state.datasourceViewDialog = {
|
|
3388
|
+
...state.datasourceViewDialog,
|
|
3389
|
+
mode: "edit",
|
|
3390
|
+
originalName: item.name || viewName,
|
|
3391
|
+
loadedName: item.name || viewName,
|
|
3392
|
+
loadedSql: item.sql || "",
|
|
3393
|
+
name: item.name || viewName,
|
|
3394
|
+
view_description: viewDescription,
|
|
3395
|
+
sql: item.sql || "",
|
|
3396
|
+
busy: "",
|
|
3397
|
+
error: "",
|
|
3398
|
+
success: "",
|
|
3399
|
+
executed: true
|
|
3400
|
+
};
|
|
3401
|
+
render();
|
|
3402
|
+
} catch (error) {
|
|
3403
|
+
state.datasourceViewDialog = {
|
|
3404
|
+
...state.datasourceViewDialog,
|
|
3405
|
+
busy: "",
|
|
3406
|
+
error: error.message,
|
|
3407
|
+
success: ""
|
|
3408
|
+
};
|
|
3409
|
+
render();
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
function closeDatasourceViewDialog() {
|
|
3413
|
+
if (state.datasourceViewDialog?.busy) return;
|
|
3414
|
+
resetDatasourceViewDialog();
|
|
3415
|
+
render();
|
|
3416
|
+
}
|
|
3417
|
+
function readDatasourceViewDialogPayload() {
|
|
3418
|
+
const form = document.querySelector("#datasource-view-form");
|
|
3419
|
+
if (!form) {
|
|
3420
|
+
const dialog = state.datasourceViewDialog || {};
|
|
3421
|
+
return {
|
|
3422
|
+
name: String(dialog.name || "").trim(),
|
|
3423
|
+
view_description: String(dialog.view_description || ""),
|
|
3424
|
+
sql: String(dialog.sql || "")
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
const data = new FormData(form);
|
|
3428
|
+
return {
|
|
3429
|
+
name: String(data.get("name") || "").trim(),
|
|
3430
|
+
view_description: String(data.get("view_description") || ""),
|
|
3431
|
+
sql: String(data.get("sql") || "")
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
function setDatasourceViewDialogBusy(payload, busy) {
|
|
3435
|
+
state.datasourceViewDialog = {
|
|
3436
|
+
...state.datasourceViewDialog,
|
|
3437
|
+
...payload,
|
|
3438
|
+
busy,
|
|
3439
|
+
error: "",
|
|
3440
|
+
success: ""
|
|
3441
|
+
};
|
|
3442
|
+
render();
|
|
3443
|
+
}
|
|
3444
|
+
function validateDatasourceViewPayload(payload, requireSql = false) {
|
|
3445
|
+
if (!payload.name) return "View name is required.";
|
|
3446
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(payload.name)) {
|
|
3447
|
+
return "View name must start with a letter or underscore and contain only letters, numbers and underscores.";
|
|
3448
|
+
}
|
|
3449
|
+
if (requireSql && !payload.sql.trim()) return "SQL is required.";
|
|
3450
|
+
return "";
|
|
3451
|
+
}
|
|
3452
|
+
async function generateDatasourceViewSql() {
|
|
3453
|
+
const selected = getSelectedDatasource();
|
|
3454
|
+
if (!selected) return;
|
|
3455
|
+
const dialog = state.datasourceViewDialog || {};
|
|
3456
|
+
const payload = readDatasourceViewDialogPayload();
|
|
3457
|
+
const validationError = validateDatasourceViewPayload(payload);
|
|
3458
|
+
if (validationError) {
|
|
3459
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, error: validationError, success: "" };
|
|
3460
|
+
render();
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
setDatasourceViewDialogBusy(payload, "generate");
|
|
3464
|
+
try {
|
|
3465
|
+
const result = await api(`/api/v1/admin/datasources/${selected.id}/schema/views/generate-sql`, {
|
|
3466
|
+
method: "POST",
|
|
3467
|
+
body: JSON.stringify({ name: payload.name, view_description: payload.view_description })
|
|
3468
|
+
});
|
|
3469
|
+
state.datasourceViewDialog = {
|
|
3470
|
+
...state.datasourceViewDialog,
|
|
3471
|
+
...payload,
|
|
3472
|
+
mode: dialog.mode || "create",
|
|
3473
|
+
sql: result.sql || "",
|
|
3474
|
+
busy: "",
|
|
3475
|
+
error: "",
|
|
3476
|
+
success: "SQL generated.",
|
|
3477
|
+
executed: false
|
|
3478
|
+
};
|
|
3479
|
+
render();
|
|
3480
|
+
} catch (error) {
|
|
3481
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, busy: "", error: error.message, success: "" };
|
|
3482
|
+
render();
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
async function executeDatasourceViewSql() {
|
|
3486
|
+
const selected = getSelectedDatasource();
|
|
3487
|
+
if (!selected) return;
|
|
3488
|
+
const dialog = state.datasourceViewDialog || {};
|
|
3489
|
+
const payload = readDatasourceViewDialogPayload();
|
|
3490
|
+
const validationError = validateDatasourceViewPayload(payload, true);
|
|
3491
|
+
if (validationError) {
|
|
3492
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, error: validationError, success: "" };
|
|
3493
|
+
render();
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
setDatasourceViewDialogBusy(payload, "execute");
|
|
3497
|
+
try {
|
|
3498
|
+
const result = dialog.mode === "edit"
|
|
3499
|
+
? await api(`/api/v1/admin/datasources/${selected.id}/schema/views/${encodeURIComponent(dialog.originalName || payload.name)}/execute`, {
|
|
3500
|
+
method: "PUT",
|
|
3501
|
+
body: JSON.stringify(payload)
|
|
3502
|
+
})
|
|
3503
|
+
: await api(`/api/v1/admin/datasources/${selected.id}/schema/views/execute`, {
|
|
3504
|
+
method: "POST",
|
|
3505
|
+
body: JSON.stringify(payload)
|
|
3506
|
+
});
|
|
3507
|
+
state.datasourceSchema = result;
|
|
3508
|
+
state.datasourceSchemaSelectedObjectName = payload.name;
|
|
3509
|
+
state.datasourceSchemaDraftTables = null;
|
|
3510
|
+
state.datasourceViewDialog = {
|
|
3511
|
+
...state.datasourceViewDialog,
|
|
3512
|
+
...payload,
|
|
3513
|
+
originalName: payload.name,
|
|
3514
|
+
loadedName: payload.name,
|
|
3515
|
+
loadedSql: payload.sql,
|
|
3516
|
+
busy: "",
|
|
3517
|
+
error: "",
|
|
3518
|
+
success: "View executed and schema introspection refreshed.",
|
|
3519
|
+
executed: true
|
|
3520
|
+
};
|
|
3521
|
+
render();
|
|
3522
|
+
} catch (error) {
|
|
3523
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, busy: "", error: error.message, success: "" };
|
|
3524
|
+
render();
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
async function saveDatasourceView(event) {
|
|
3528
|
+
event.preventDefault();
|
|
3529
|
+
const selected = getSelectedDatasource();
|
|
3530
|
+
if (!selected) return;
|
|
3531
|
+
const dialog = state.datasourceViewDialog || {};
|
|
3532
|
+
const payload = readDatasourceViewDialogPayload();
|
|
3533
|
+
const canSaveSettingsOnly = (
|
|
3534
|
+
dialog.executed
|
|
3535
|
+
&& (dialog.loadedName || dialog.name) === payload.name
|
|
3536
|
+
&& (dialog.loadedSql || dialog.sql) === payload.sql
|
|
3537
|
+
);
|
|
3538
|
+
const validationError = validateDatasourceViewPayload(payload, !canSaveSettingsOnly);
|
|
3539
|
+
if (validationError) {
|
|
3540
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, error: validationError, success: "" };
|
|
3541
|
+
render();
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
setDatasourceViewDialogBusy(payload, "save");
|
|
3545
|
+
try {
|
|
3546
|
+
const result = canSaveSettingsOnly
|
|
3547
|
+
? await api(`/api/v1/admin/datasources/${selected.id}/schema/views/${encodeURIComponent(dialog.originalName || payload.name)}`, {
|
|
3548
|
+
method: "PUT",
|
|
3549
|
+
body: JSON.stringify({ view_description: payload.view_description })
|
|
3550
|
+
})
|
|
3551
|
+
: dialog.mode === "edit"
|
|
3552
|
+
? await api(`/api/v1/admin/datasources/${selected.id}/schema/views/${encodeURIComponent(dialog.originalName || payload.name)}/execute`, {
|
|
3553
|
+
method: "PUT",
|
|
3554
|
+
body: JSON.stringify(payload)
|
|
3555
|
+
})
|
|
3556
|
+
: await api(`/api/v1/admin/datasources/${selected.id}/schema/views/execute`, {
|
|
3557
|
+
method: "POST",
|
|
3558
|
+
body: JSON.stringify(payload)
|
|
3559
|
+
});
|
|
3560
|
+
state.datasourceSchema = result;
|
|
3561
|
+
state.datasourceSchemaSelectedObjectName = payload.name;
|
|
3562
|
+
state.datasourceSchemaDraftTables = null;
|
|
3563
|
+
resetDatasourceViewDialog();
|
|
3564
|
+
setMessage("success", "View saved.");
|
|
3565
|
+
render();
|
|
3566
|
+
} catch (error) {
|
|
3567
|
+
state.datasourceViewDialog = { ...state.datasourceViewDialog, ...payload, busy: "", error: error.message, success: "" };
|
|
3568
|
+
render();
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
async function deleteDatasourceView(event) {
|
|
3572
|
+
const selected = getSelectedDatasource();
|
|
3573
|
+
const viewName = event.currentTarget?.dataset?.deleteDatasourceView || "";
|
|
3574
|
+
if (!selected || !viewName) return;
|
|
3575
|
+
if (!await requestConfirmation({
|
|
3576
|
+
title: "Delete view",
|
|
3577
|
+
message: `Delete view "${viewName}"?`,
|
|
3578
|
+
confirmLabel: "Delete"
|
|
3579
|
+
})) return;
|
|
3580
|
+
try {
|
|
3581
|
+
state.datasourceSchemaLoading = true;
|
|
3582
|
+
state.datasourceSchemaError = "";
|
|
3583
|
+
render();
|
|
3584
|
+
state.datasourceSchema = await api(`/api/v1/admin/datasources/${selected.id}/schema/views/${encodeURIComponent(viewName)}`, {
|
|
3585
|
+
method: "DELETE"
|
|
3586
|
+
});
|
|
3587
|
+
state.datasourceSchemaSelectedObjectName = "";
|
|
3588
|
+
state.datasourceSchemaDraftTables = null;
|
|
3589
|
+
state.datasourceSchemaLoading = false;
|
|
3590
|
+
setMessage("success", "View deleted.");
|
|
3591
|
+
render();
|
|
3592
|
+
} catch (error) {
|
|
3593
|
+
state.datasourceSchemaLoading = false;
|
|
3594
|
+
state.datasourceSchemaError = error.message;
|
|
3595
|
+
setMessage("error", error.message);
|
|
3596
|
+
render();
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3182
3599
|
async function introspectDatasource() {
|
|
3183
3600
|
const selected = getSelectedDatasource();
|
|
3184
3601
|
if (!selected) return;
|
|
@@ -3227,6 +3644,7 @@ async function deleteDatasource() {
|
|
|
3227
3644
|
state.datasourceSchemaError = "";
|
|
3228
3645
|
state.datasourceSchemaSelectedObjectName = "";
|
|
3229
3646
|
state.datasourceSchemaDraftTables = null;
|
|
3647
|
+
resetDatasourceViewDialog();
|
|
3230
3648
|
setMessage("success", "Datasource deleted.");
|
|
3231
3649
|
await loadDatasources();
|
|
3232
3650
|
} catch (error) {
|
|
@@ -3248,10 +3666,14 @@ function syncDatasourceSchemaDraftFromForm() {
|
|
|
3248
3666
|
const selectedName = state.datasourceSchemaSelectedObjectName;
|
|
3249
3667
|
const selectedSettings = selectedName ? draftTables[selectedName] : null;
|
|
3250
3668
|
if (!selectedSettings) return;
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3669
|
+
const descriptionInput = form.querySelector("[data-schema-detail='description']");
|
|
3670
|
+
const primaryKeyPromptInput = form.querySelector("[data-schema-detail='primary_key_prompt']");
|
|
3671
|
+
const foreignKeyPromptInput = form.querySelector("[data-schema-detail='foreign_key_prompt']");
|
|
3672
|
+
const joinLogicInput = form.querySelector("[data-schema-detail='join_logic']");
|
|
3673
|
+
if (descriptionInput) selectedSettings.description = descriptionInput.value || "";
|
|
3674
|
+
if (primaryKeyPromptInput) selectedSettings.primary_key_prompt = primaryKeyPromptInput.value || "";
|
|
3675
|
+
if (foreignKeyPromptInput) selectedSettings.foreign_key_prompt = foreignKeyPromptInput.value || "";
|
|
3676
|
+
if (joinLogicInput) selectedSettings.join_logic = joinLogicInput.value || "";
|
|
3255
3677
|
}
|
|
3256
3678
|
async function saveDatasourceSchema(event) {
|
|
3257
3679
|
event.preventDefault();
|
|
@@ -3267,6 +3689,7 @@ async function saveDatasourceSchema(event) {
|
|
|
3267
3689
|
tables[table.name] = {
|
|
3268
3690
|
selected: draft.selected !== false,
|
|
3269
3691
|
description: draft.description || "",
|
|
3692
|
+
view_description: draft.view_description || "",
|
|
3270
3693
|
primary_key_prompt: draft.primary_key_prompt || "",
|
|
3271
3694
|
foreign_key_prompt: draft.foreign_key_prompt || "",
|
|
3272
3695
|
join_logic: draft.join_logic || ""
|
|
@@ -3488,6 +3911,7 @@ async function loadDatasourceSchema() {
|
|
|
3488
3911
|
state.datasourceSchemaDraftTables = null;
|
|
3489
3912
|
state.datasourceSchemaLoading = false;
|
|
3490
3913
|
state.datasourceSchemaError = "";
|
|
3914
|
+
resetDatasourceViewDialog();
|
|
3491
3915
|
render();
|
|
3492
3916
|
return;
|
|
3493
3917
|
}
|
|
@@ -447,6 +447,14 @@ label {
|
|
|
447
447
|
font-size: 16px;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
.schema-object-actions {
|
|
451
|
+
display: flex;
|
|
452
|
+
align-items: center;
|
|
453
|
+
justify-content: flex-end;
|
|
454
|
+
gap: 8px;
|
|
455
|
+
flex-wrap: wrap;
|
|
456
|
+
}
|
|
457
|
+
|
|
450
458
|
.schema-object-columns {
|
|
451
459
|
padding: 10px;
|
|
452
460
|
border: 1px solid var(--line);
|
|
@@ -724,6 +732,12 @@ label {
|
|
|
724
732
|
animation: spin 0.8s linear infinite;
|
|
725
733
|
}
|
|
726
734
|
|
|
735
|
+
button .spinner {
|
|
736
|
+
display: inline-block;
|
|
737
|
+
margin-right: 6px;
|
|
738
|
+
vertical-align: -2px;
|
|
739
|
+
}
|
|
740
|
+
|
|
727
741
|
@keyframes spin {
|
|
728
742
|
to {
|
|
729
743
|
transform: rotate(360deg);
|
|
@@ -763,6 +777,14 @@ label {
|
|
|
763
777
|
font-size: 16px;
|
|
764
778
|
}
|
|
765
779
|
|
|
780
|
+
.panel-header-actions {
|
|
781
|
+
display: flex;
|
|
782
|
+
align-items: center;
|
|
783
|
+
justify-content: flex-end;
|
|
784
|
+
gap: 10px;
|
|
785
|
+
flex-wrap: wrap;
|
|
786
|
+
}
|
|
787
|
+
|
|
766
788
|
.audit-controls {
|
|
767
789
|
display: flex;
|
|
768
790
|
justify-content: flex-end;
|