sqlite-hub 1.4.0 → 2.0.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/README.md +14 -1
- package/bin/sqlite-hub-mcp.js +8 -0
- package/bin/sqlite-hub.js +555 -122
- package/docs/API.md +30 -0
- package/docs/CLI.md +22 -0
- package/docs/CLI_API_PARITY.md +61 -0
- package/docs/MCP.md +85 -0
- package/docs/changelog.md +13 -1
- package/docs/guidelines/AGENTS.md +32 -0
- package/docs/{DESIGN_GUIDELINES.md → guidelines/DESIGN.md} +10 -0
- package/docs/todo.md +1 -14
- package/frontend/js/api.js +49 -0
- package/frontend/js/app.js +202 -2
- package/frontend/js/components/connectionCard.js +3 -1
- package/frontend/js/components/modal.js +356 -15
- package/frontend/js/components/sidebar.js +41 -7
- package/frontend/js/components/topNav.js +2 -14
- package/frontend/js/router.js +10 -0
- package/frontend/js/store.js +483 -7
- package/frontend/js/utils/inputClear.js +36 -0
- package/frontend/js/utils/syntheticData.js +240 -0
- package/frontend/js/views/backups.js +52 -0
- package/frontend/js/views/data.js +16 -0
- package/frontend/js/views/logs.js +339 -0
- package/frontend/js/views/settings.js +266 -30
- package/frontend/js/views/structure.js +6 -0
- package/frontend/js/views/tableAdvisor.js +385 -0
- package/frontend/js/views/tableDesigner.js +6 -0
- package/frontend/styles/components.css +149 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +31 -0
- package/package.json +4 -2
- package/server/mcp/stdioServer.js +272 -0
- package/server/routes/data.js +45 -0
- package/server/routes/externalApi.js +285 -2
- package/server/routes/logs.js +127 -0
- package/server/routes/settings.js +47 -0
- package/server/server.js +3 -0
- package/server/services/databaseCommandService.js +284 -2
- package/server/services/mcpStatusService.js +140 -0
- package/server/services/mcpToolService.js +274 -0
- package/server/services/sqlite/dataBrowserService.js +36 -0
- package/server/services/sqlite/introspection.js +226 -22
- package/server/services/sqlite/syntheticDataGenerator.js +728 -0
- package/server/services/sqlite/tableAdvisor.js +790 -0
- package/server/services/storage/appStateStore.js +821 -169
package/frontend/js/app.js
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
initializeApp,
|
|
49
49
|
insertMarkdownIntoLastOpenDocument,
|
|
50
50
|
loadMoreQueryHistory,
|
|
51
|
+
loadMoreLogs,
|
|
51
52
|
openModal,
|
|
52
53
|
openOverviewInFinder,
|
|
53
54
|
openQueryHistoryInEditor,
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
openRestoreBackupModal,
|
|
65
66
|
openDeleteQueryChartModal,
|
|
66
67
|
openDataExportModal,
|
|
68
|
+
openGenerateDataModal,
|
|
67
69
|
openDeleteDocumentModal,
|
|
68
70
|
openQueryExportModal,
|
|
69
71
|
openDataRowByIdentity,
|
|
@@ -77,6 +79,7 @@ import {
|
|
|
77
79
|
openDataRowUpdatePreview,
|
|
78
80
|
openEditorRowUpdatePreview,
|
|
79
81
|
refreshCurrentRoute,
|
|
82
|
+
refreshLogs,
|
|
80
83
|
refreshBackups,
|
|
81
84
|
refreshMediaTaggingPreview,
|
|
82
85
|
removeConnection,
|
|
@@ -94,6 +97,9 @@ import {
|
|
|
94
97
|
selectQueryHistoryItem,
|
|
95
98
|
selectStructureEntry,
|
|
96
99
|
setDocumentsSearchQuery,
|
|
100
|
+
setLogFilter,
|
|
101
|
+
setLogSearchInput,
|
|
102
|
+
applyLogSearch,
|
|
97
103
|
setTableDesignerSearchQuery,
|
|
98
104
|
setTableDesignerSqlPreviewVisibility,
|
|
99
105
|
toggleTableDesignerTablesPanel,
|
|
@@ -126,7 +132,9 @@ import {
|
|
|
126
132
|
submitBackupSafetyChoice,
|
|
127
133
|
submitCreateBackupConfirmation,
|
|
128
134
|
submitEditBackupConfirmation,
|
|
135
|
+
submitGenerateDataRows,
|
|
129
136
|
submitRowUpdatePreviewConfirmation,
|
|
137
|
+
previewGenerateDataRows,
|
|
130
138
|
setQueryHistoryPanelVisibility,
|
|
131
139
|
sortDataTableByColumn,
|
|
132
140
|
sortEditorResultsByColumn,
|
|
@@ -170,6 +178,8 @@ import {
|
|
|
170
178
|
updateCurrentTableDesignerColumnField,
|
|
171
179
|
updateCurrentTableDesignerConstraintField,
|
|
172
180
|
updateCurrentTableDesignerField,
|
|
181
|
+
updateGenerateDataMapping,
|
|
182
|
+
updateGenerateDataModal,
|
|
173
183
|
updateGenerateTypesModal,
|
|
174
184
|
addCurrentTableDesignerColumn,
|
|
175
185
|
applyCurrentMediaTaggingSelection,
|
|
@@ -182,10 +192,12 @@ import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
|
182
192
|
import { renderDocumentsView } from './views/documents.js';
|
|
183
193
|
import { renderEditorView } from './views/editor.js';
|
|
184
194
|
import { renderLandingView } from './views/landing.js';
|
|
195
|
+
import { renderLogsView, renderLogTable } from './views/logs.js';
|
|
185
196
|
import { renderMediaTaggingView } from './views/mediaTagging.js';
|
|
186
197
|
import { renderOverviewView } from './views/overview.js';
|
|
187
198
|
import { renderSettingsView } from './views/settings.js';
|
|
188
199
|
import { renderStructureView } from './views/structure.js';
|
|
200
|
+
import { renderTableAdvisorView } from './views/tableAdvisor.js';
|
|
189
201
|
import { renderTableDesignerView } from './views/tableDesigner.js';
|
|
190
202
|
import { replaceChildrenFromRenderedMarkup, replaceElementFromRenderedMarkup } from './utils/dom.js';
|
|
191
203
|
import {
|
|
@@ -200,6 +212,7 @@ import {
|
|
|
200
212
|
detectFilePathValue,
|
|
201
213
|
getPathTypeLabel,
|
|
202
214
|
} from './utils/filePathPreview.js';
|
|
215
|
+
import { clearInputForEscape } from './utils/inputClear.js';
|
|
203
216
|
import { formatSqlQuery } from './utils/sqlFormatter.js';
|
|
204
217
|
import {
|
|
205
218
|
buildDataRowEditorJsonObject,
|
|
@@ -263,6 +276,7 @@ const ROUTE_TITLE_SEGMENTS = {
|
|
|
263
276
|
backups: 'Backups',
|
|
264
277
|
overview: 'Overview',
|
|
265
278
|
data: 'Data',
|
|
279
|
+
tableAdvisor: 'Table Advisor',
|
|
266
280
|
structure: 'Structure',
|
|
267
281
|
editor: 'SQL Editor',
|
|
268
282
|
editorResults: 'SQL Editor',
|
|
@@ -272,6 +286,7 @@ const ROUTE_TITLE_SEGMENTS = {
|
|
|
272
286
|
mediaTaggingSetup: 'Media Tagging',
|
|
273
287
|
mediaTaggingQueue: 'Tagging Queue',
|
|
274
288
|
settings: 'Settings',
|
|
289
|
+
logs: 'Logs',
|
|
275
290
|
notFound: 'Not Found',
|
|
276
291
|
};
|
|
277
292
|
|
|
@@ -973,6 +988,92 @@ function patchChartsDetailUi(state, { preserveCharts = false } = {}) {
|
|
|
973
988
|
return true;
|
|
974
989
|
}
|
|
975
990
|
|
|
991
|
+
function getLogsActiveDatabaseId(state) {
|
|
992
|
+
return String(state.connections?.active?.id ?? state.logs?.metadata?.activeDatabase?.id ?? '');
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function syncLogsFilterControls(state) {
|
|
996
|
+
const filters = state.logs?.filters ?? {};
|
|
997
|
+
|
|
998
|
+
for (const button of shellRefs.view.querySelectorAll('[data-action="set-log-filter"][data-field][data-value]')) {
|
|
999
|
+
if (!(button instanceof HTMLElement)) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const field = button.dataset.field;
|
|
1004
|
+
const value = button.dataset.value;
|
|
1005
|
+
const active = String(filters[field] ?? '') === String(value ?? '');
|
|
1006
|
+
|
|
1007
|
+
button.classList.toggle('is-active', active);
|
|
1008
|
+
button.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function syncLogsMetaStrip(state) {
|
|
1013
|
+
const logs = state.logs ?? {};
|
|
1014
|
+
const values = {
|
|
1015
|
+
visible: formatNumber((logs.items ?? []).length),
|
|
1016
|
+
matched: formatNumber(logs.total ?? 0),
|
|
1017
|
+
scope: logs.metadata?.activeDatabase?.label ?? 'Active Database',
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
for (const [key, value] of Object.entries(values)) {
|
|
1021
|
+
const node = shellRefs.view.querySelector(`[data-logs-meta="${key}"]`);
|
|
1022
|
+
|
|
1023
|
+
if (node instanceof HTMLElement) {
|
|
1024
|
+
node.textContent = value;
|
|
1025
|
+
node.title = value;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function patchLogsTableUi(state) {
|
|
1031
|
+
const logsView = shellRefs.view.querySelector('[data-logs-view]');
|
|
1032
|
+
|
|
1033
|
+
if (!(logsView instanceof HTMLElement)) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const activeDatabaseId = getLogsActiveDatabaseId(state);
|
|
1038
|
+
|
|
1039
|
+
if (String(logsView.dataset.logsActiveDatabaseId ?? '') !== activeDatabaseId) {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const currentTable = logsView.querySelector('[data-logs-table]');
|
|
1044
|
+
|
|
1045
|
+
if (!(currentTable instanceof HTMLElement)) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const currentScrollNode = currentTable.querySelector('[data-logs-table-scroll]');
|
|
1050
|
+
const scrollState =
|
|
1051
|
+
currentScrollNode instanceof HTMLElement
|
|
1052
|
+
? {
|
|
1053
|
+
left: currentScrollNode.scrollLeft,
|
|
1054
|
+
top: currentScrollNode.scrollTop,
|
|
1055
|
+
}
|
|
1056
|
+
: null;
|
|
1057
|
+
const patched = replaceElementFromRenderedMarkup(currentTable, renderLogTable(state.logs ?? {}));
|
|
1058
|
+
|
|
1059
|
+
if (!patched) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (scrollState) {
|
|
1064
|
+
const nextScrollNode = logsView.querySelector('[data-logs-table-scroll]');
|
|
1065
|
+
|
|
1066
|
+
if (nextScrollNode instanceof HTMLElement) {
|
|
1067
|
+
nextScrollNode.scrollLeft = scrollState.left;
|
|
1068
|
+
nextScrollNode.scrollTop = scrollState.top;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
syncLogsFilterControls(state);
|
|
1073
|
+
syncLogsMetaStrip(state);
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
976
1077
|
function readFileAsBase64(file) {
|
|
977
1078
|
return new Promise((resolve, reject) => {
|
|
978
1079
|
const reader = new FileReader();
|
|
@@ -1112,12 +1213,16 @@ function resolveView(state) {
|
|
|
1112
1213
|
return renderStructureView(state);
|
|
1113
1214
|
case 'tableDesigner':
|
|
1114
1215
|
return renderTableDesignerView(state);
|
|
1216
|
+
case 'tableAdvisor':
|
|
1217
|
+
return renderTableAdvisorView(state);
|
|
1115
1218
|
case 'mediaTaggingSetup':
|
|
1116
1219
|
return renderMediaTaggingView(state, { subView: 'setup' });
|
|
1117
1220
|
case 'mediaTaggingQueue':
|
|
1118
1221
|
return renderMediaTaggingView(state, { subView: 'queue' });
|
|
1119
1222
|
case 'settings':
|
|
1120
1223
|
return renderSettingsView(state);
|
|
1224
|
+
case 'logs':
|
|
1225
|
+
return renderLogsView(state);
|
|
1121
1226
|
default:
|
|
1122
1227
|
return renderNotFoundView();
|
|
1123
1228
|
}
|
|
@@ -1403,9 +1508,11 @@ function renderApp(state) {
|
|
|
1403
1508
|
'editorResults',
|
|
1404
1509
|
'data',
|
|
1405
1510
|
'charts',
|
|
1511
|
+
'logs',
|
|
1406
1512
|
'documents',
|
|
1407
1513
|
'structure',
|
|
1408
1514
|
'tableDesigner',
|
|
1515
|
+
'tableAdvisor',
|
|
1409
1516
|
'mediaTaggingSetup',
|
|
1410
1517
|
'mediaTaggingQueue',
|
|
1411
1518
|
].includes(state.route.name);
|
|
@@ -1457,8 +1564,8 @@ function renderApp(state) {
|
|
|
1457
1564
|
pendingNewTableDesignerAutofocus = false;
|
|
1458
1565
|
}
|
|
1459
1566
|
|
|
1460
|
-
const canPatchChartsMain =
|
|
1461
|
-
|
|
1567
|
+
const canPatchChartsMain = mainChanged && previousRouteName === 'charts' && state.route.name === 'charts';
|
|
1568
|
+
const canPatchLogsMain = mainChanged && previousRouteName === 'logs' && state.route.name === 'logs';
|
|
1462
1569
|
let mainPatched = false;
|
|
1463
1570
|
let preservedChartsDom = false;
|
|
1464
1571
|
|
|
@@ -1484,6 +1591,10 @@ function renderApp(state) {
|
|
|
1484
1591
|
}
|
|
1485
1592
|
}
|
|
1486
1593
|
|
|
1594
|
+
if (!mainPatched && canPatchLogsMain) {
|
|
1595
|
+
mainPatched = patchLogsTableUi(state);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1487
1598
|
if (mainChanged) {
|
|
1488
1599
|
if (!mainPatched) {
|
|
1489
1600
|
teardownStructureGraph();
|
|
@@ -2428,6 +2539,15 @@ async function handleAction(actionNode) {
|
|
|
2428
2539
|
case 'refresh-view':
|
|
2429
2540
|
await refreshCurrentRoute();
|
|
2430
2541
|
return;
|
|
2542
|
+
case 'refresh-logs':
|
|
2543
|
+
await refreshLogs();
|
|
2544
|
+
return;
|
|
2545
|
+
case 'set-log-filter':
|
|
2546
|
+
await setLogFilter(actionNode.dataset.field, actionNode.dataset.value);
|
|
2547
|
+
return;
|
|
2548
|
+
case 'load-more-logs':
|
|
2549
|
+
await loadMoreLogs();
|
|
2550
|
+
return;
|
|
2431
2551
|
case 'set-settings-section':
|
|
2432
2552
|
setSettingsSection(actionNode.dataset.section);
|
|
2433
2553
|
return;
|
|
@@ -2457,6 +2577,16 @@ async function handleAction(actionNode) {
|
|
|
2457
2577
|
}
|
|
2458
2578
|
return;
|
|
2459
2579
|
}
|
|
2580
|
+
case 'copy-mcp-config': {
|
|
2581
|
+
const configNode = document.querySelector('[data-mcp-config]');
|
|
2582
|
+
const config = configNode instanceof HTMLTextAreaElement ? configNode.value : '';
|
|
2583
|
+
|
|
2584
|
+
if (config && navigator.clipboard?.writeText) {
|
|
2585
|
+
await navigator.clipboard.writeText(config);
|
|
2586
|
+
showToast('MCP config copied.', 'success');
|
|
2587
|
+
}
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2460
2590
|
case 'open-row-editor-url':
|
|
2461
2591
|
openRowEditorUrl(actionNode);
|
|
2462
2592
|
return;
|
|
@@ -2878,6 +3008,12 @@ async function handleAction(actionNode) {
|
|
|
2878
3008
|
case 'open-data-export-modal':
|
|
2879
3009
|
openDataExportModal();
|
|
2880
3010
|
return;
|
|
3011
|
+
case 'open-generate-data-modal':
|
|
3012
|
+
openGenerateDataModal();
|
|
3013
|
+
return;
|
|
3014
|
+
case 'preview-generate-data':
|
|
3015
|
+
await previewGenerateDataRows();
|
|
3016
|
+
return;
|
|
2881
3017
|
case 'export-data-format': {
|
|
2882
3018
|
const format = actionNode.dataset.exportFormat;
|
|
2883
3019
|
const filename = getExportFilenameFromAction(actionNode);
|
|
@@ -2948,6 +3084,30 @@ async function handleAction(actionNode) {
|
|
|
2948
3084
|
}
|
|
2949
3085
|
return;
|
|
2950
3086
|
}
|
|
3087
|
+
case 'copy-table-advisor-sql': {
|
|
3088
|
+
const issueId = actionNode.dataset.issueId;
|
|
3089
|
+
const advisor = getState().tableAdvisor;
|
|
3090
|
+
const issue = (advisor.result?.issues ?? []).find(item => item.id === issueId);
|
|
3091
|
+
const sql = String(issue?.sql ?? '');
|
|
3092
|
+
|
|
3093
|
+
if (!sql.trim()) {
|
|
3094
|
+
showToast('No advisor SQL to copy.', 'alert');
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
if (!navigator.clipboard?.writeText) {
|
|
3099
|
+
showToast('Clipboard API is not available.', 'alert');
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
try {
|
|
3104
|
+
await navigator.clipboard.writeText(sql);
|
|
3105
|
+
showToast('Advisor SQL copied.', 'success');
|
|
3106
|
+
} catch (error) {
|
|
3107
|
+
showToast('Advisor SQL could not be copied.', 'alert');
|
|
3108
|
+
}
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
2951
3111
|
case 'toggle-table-designer-sql-preview':
|
|
2952
3112
|
setTableDesignerSqlPreviewVisibility(
|
|
2953
3113
|
actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
|
|
@@ -3236,6 +3396,11 @@ document.addEventListener('keydown', event => {
|
|
|
3236
3396
|
return;
|
|
3237
3397
|
}
|
|
3238
3398
|
|
|
3399
|
+
if (target instanceof HTMLInputElement && clearInputForEscape(target)) {
|
|
3400
|
+
event.preventDefault();
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3239
3404
|
if (state.modal) {
|
|
3240
3405
|
event.preventDefault();
|
|
3241
3406
|
closeModal();
|
|
@@ -3327,6 +3492,20 @@ document.addEventListener('input', event => {
|
|
|
3327
3492
|
return;
|
|
3328
3493
|
}
|
|
3329
3494
|
|
|
3495
|
+
if (bindNode.dataset.bind === 'generate-data-field') {
|
|
3496
|
+
updateGenerateDataModal(bindNode.dataset.field, bindNode.value, { notify: false });
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
if (bindNode.dataset.bind === 'generate-data-mapping') {
|
|
3501
|
+
if (bindNode instanceof HTMLSelectElement) {
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
updateGenerateDataMapping(bindNode.dataset.columnName, bindNode.dataset.field, bindNode.value, { notify: false });
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3330
3509
|
if (bindNode.dataset.bind === 'copy-column-format-field') {
|
|
3331
3510
|
updateCopyColumnModalFormatField(
|
|
3332
3511
|
bindNode.dataset.field,
|
|
@@ -3354,6 +3533,11 @@ document.addEventListener('input', event => {
|
|
|
3354
3533
|
return;
|
|
3355
3534
|
}
|
|
3356
3535
|
|
|
3536
|
+
if (bindNode.dataset.bind === 'logs-search') {
|
|
3537
|
+
setLogSearchInput(bindNode.value);
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3357
3541
|
if (bindNode.dataset.bind === 'data-search-query') {
|
|
3358
3542
|
void setDataSearchQuery(bindNode.value);
|
|
3359
3543
|
return;
|
|
@@ -3505,6 +3689,16 @@ document.addEventListener('change', event => {
|
|
|
3505
3689
|
return;
|
|
3506
3690
|
}
|
|
3507
3691
|
|
|
3692
|
+
if (bindNode.dataset.bind === 'generate-data-field') {
|
|
3693
|
+
updateGenerateDataModal(bindNode.dataset.field, bindNode.value);
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
if (bindNode.dataset.bind === 'generate-data-mapping') {
|
|
3698
|
+
updateGenerateDataMapping(bindNode.dataset.columnName, bindNode.dataset.field, bindNode.value);
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3508
3702
|
if (bindNode.dataset.bind === 'data-search-column') {
|
|
3509
3703
|
void setDataSearchColumn(bindNode.value);
|
|
3510
3704
|
return;
|
|
@@ -3649,6 +3843,9 @@ document.addEventListener('submit', async event => {
|
|
|
3649
3843
|
const formData = submitter ? new FormData(form, submitter) : new FormData(form);
|
|
3650
3844
|
|
|
3651
3845
|
switch (form.dataset.form) {
|
|
3846
|
+
case 'logs-search':
|
|
3847
|
+
await applyLogSearch(String(formData.get('search') ?? ''));
|
|
3848
|
+
return;
|
|
3652
3849
|
case 'create-api-token':
|
|
3653
3850
|
await createSettingsApiToken(String(formData.get('name') ?? ''));
|
|
3654
3851
|
return;
|
|
@@ -3780,6 +3977,9 @@ document.addEventListener('submit', async event => {
|
|
|
3780
3977
|
case 'apply-row-update-preview':
|
|
3781
3978
|
await submitRowUpdatePreviewConfirmation();
|
|
3782
3979
|
return;
|
|
3980
|
+
case 'generate-data':
|
|
3981
|
+
await submitGenerateDataRows();
|
|
3982
|
+
return;
|
|
3783
3983
|
case 'create-media-tagging-tag-table':
|
|
3784
3984
|
await submitCreateMediaTaggingTagTable();
|
|
3785
3985
|
return;
|
|
@@ -56,7 +56,9 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
56
56
|
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Last Opened</div><div class="text-xs font-bold text-on-surface">',
|
|
57
57
|
escapeHtml(formatDateTime(connection.lastOpenedAt)),
|
|
58
58
|
"</div></div>",
|
|
59
|
-
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Mode</div><div class="text-xs font-bold
|
|
59
|
+
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Mode</div><div class="text-xs font-bold ',
|
|
60
|
+
connection.readOnly ? "text-primary-container" : "text-on-surface",
|
|
61
|
+
'">',
|
|
60
62
|
connection.readOnly ? "Read only" : "Read / Write",
|
|
61
63
|
"</div></div></div></div>",
|
|
62
64
|
'<div class="border-t border-outline-variant/10 bg-surface-container-low px-4 py-3">',
|