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
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
MEDIA_TAGGING_DEFAULT_TAG_TABLE,
|
|
18
18
|
MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL,
|
|
19
19
|
} from '../lib/mediaTaggingDefaults.js';
|
|
20
|
+
import { SYNTHETIC_GENERATOR_TYPES, getSyntheticGeneratorLabel } from '../utils/syntheticData.js';
|
|
20
21
|
|
|
21
22
|
function renderField({ label, name, type = 'text', placeholder = '', value = '' }) {
|
|
22
23
|
return `
|
|
@@ -225,7 +226,7 @@ function renderEditConnectionForm(modal) {
|
|
|
225
226
|
label: 'Upload image',
|
|
226
227
|
name: 'logoFile',
|
|
227
228
|
accept: '.png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp',
|
|
228
|
-
helpText: 'Allowed formats: PNG, JPG, WEBP.
|
|
229
|
+
helpText: 'Allowed formats: PNG, JPG, WEBP.',
|
|
229
230
|
})}
|
|
230
231
|
${
|
|
231
232
|
connection.logoUrl
|
|
@@ -548,12 +549,13 @@ function renderTypeGenerationCheckbox({ label, field, checked }) {
|
|
|
548
549
|
|
|
549
550
|
function renderTypeGenerationCodePreview(code) {
|
|
550
551
|
const text = String(code ?? '');
|
|
551
|
-
const normalized =
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
552
|
+
const normalized =
|
|
553
|
+
text.includes('\n') || text.includes('\\n')
|
|
554
|
+
? text.replace(/\\n/g, '\n')
|
|
555
|
+
: text
|
|
556
|
+
.replace(/\{\s*/g, '{\n ')
|
|
557
|
+
.replace(/;\s*/g, ';\n ')
|
|
558
|
+
.replace(/\s*\}\s*$/g, '\n}');
|
|
557
559
|
|
|
558
560
|
return normalized
|
|
559
561
|
.replace(/\r\n/g, '\n')
|
|
@@ -670,7 +672,7 @@ export function renderGenerateTypesForm(modal) {
|
|
|
670
672
|
: ''
|
|
671
673
|
}
|
|
672
674
|
<pre class="type-generation-code-preview custom-scrollbar border border-outline-variant/10 bg-surface-container-lowest px-4 py-4 font-mono text-xs leading-6 text-on-surface"><code>${renderTypeGenerationCodePreview(
|
|
673
|
-
modal.loading ? 'Generating...' : result.code ?? '',
|
|
675
|
+
modal.loading ? 'Generating...' : (result.code ?? ''),
|
|
674
676
|
)}</code></pre>
|
|
675
677
|
${renderError(modal.error)}
|
|
676
678
|
</div>
|
|
@@ -696,6 +698,337 @@ export function renderGenerateTypesForm(modal) {
|
|
|
696
698
|
`;
|
|
697
699
|
}
|
|
698
700
|
|
|
701
|
+
function renderSyntheticGeneratorOptions(value) {
|
|
702
|
+
return SYNTHETIC_GENERATOR_TYPES.map(
|
|
703
|
+
type =>
|
|
704
|
+
`<option value="${escapeHtml(type.value)}" ${String(type.value) === String(value) ? 'selected' : ''}>${escapeHtml(
|
|
705
|
+
type.label,
|
|
706
|
+
)}</option>`,
|
|
707
|
+
).join('');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function renderSyntheticOptionInput({
|
|
711
|
+
columnName,
|
|
712
|
+
option,
|
|
713
|
+
type = 'text',
|
|
714
|
+
value = '',
|
|
715
|
+
min = null,
|
|
716
|
+
max = null,
|
|
717
|
+
step = null,
|
|
718
|
+
placeholder = '',
|
|
719
|
+
}) {
|
|
720
|
+
const attributes = [
|
|
721
|
+
`class="control-input w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"`,
|
|
722
|
+
placeholder ? `aria-label="${escapeHtml(placeholder)}"` : '',
|
|
723
|
+
`data-bind="generate-data-mapping"`,
|
|
724
|
+
`data-column-name="${escapeHtml(columnName)}"`,
|
|
725
|
+
`data-field="${escapeHtml(option)}"`,
|
|
726
|
+
placeholder ? `placeholder="${escapeHtml(placeholder)}"` : '',
|
|
727
|
+
`type="${escapeHtml(type)}"`,
|
|
728
|
+
`value="${escapeHtml(value)}"`,
|
|
729
|
+
min !== null ? `min="${escapeHtml(min)}"` : '',
|
|
730
|
+
max !== null ? `max="${escapeHtml(max)}"` : '',
|
|
731
|
+
step !== null ? `step="${escapeHtml(step)}"` : '',
|
|
732
|
+
].filter(Boolean);
|
|
733
|
+
|
|
734
|
+
return `<input ${attributes.join(' ')} />`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function renderSyntheticOptionSelect({ columnName, option, value = '', options = [] }) {
|
|
738
|
+
return `
|
|
739
|
+
<select
|
|
740
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
741
|
+
data-bind="generate-data-mapping"
|
|
742
|
+
data-column-name="${escapeHtml(columnName)}"
|
|
743
|
+
data-field="${escapeHtml(option)}"
|
|
744
|
+
>
|
|
745
|
+
${options
|
|
746
|
+
.map(
|
|
747
|
+
item =>
|
|
748
|
+
`<option value="${escapeHtml(item.value)}" ${String(item.value) === String(value) ? 'selected' : ''}>${escapeHtml(
|
|
749
|
+
item.label,
|
|
750
|
+
)}</option>`,
|
|
751
|
+
)
|
|
752
|
+
.join('')}
|
|
753
|
+
</select>
|
|
754
|
+
`;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function renderSyntheticMappingOptions(mapping) {
|
|
758
|
+
const options = mapping.options ?? {};
|
|
759
|
+
const columnName = mapping.columnName;
|
|
760
|
+
|
|
761
|
+
switch (mapping.generator) {
|
|
762
|
+
case 'static':
|
|
763
|
+
return renderSyntheticOptionInput({
|
|
764
|
+
columnName,
|
|
765
|
+
option: 'value',
|
|
766
|
+
value: options.value ?? '',
|
|
767
|
+
placeholder: 'Value',
|
|
768
|
+
});
|
|
769
|
+
case 'randomInteger':
|
|
770
|
+
return `
|
|
771
|
+
<div class="synthetic-generator-options-grid synthetic-generator-options-grid--two">
|
|
772
|
+
${renderSyntheticOptionInput({
|
|
773
|
+
columnName,
|
|
774
|
+
option: 'min',
|
|
775
|
+
type: 'number',
|
|
776
|
+
value: options.min ?? 1,
|
|
777
|
+
step: 1,
|
|
778
|
+
placeholder: 'Min',
|
|
779
|
+
})}
|
|
780
|
+
${renderSyntheticOptionInput({
|
|
781
|
+
columnName,
|
|
782
|
+
option: 'max',
|
|
783
|
+
type: 'number',
|
|
784
|
+
value: options.max ?? 1000,
|
|
785
|
+
step: 1,
|
|
786
|
+
placeholder: 'Max',
|
|
787
|
+
})}
|
|
788
|
+
</div>
|
|
789
|
+
`;
|
|
790
|
+
case 'randomDecimal':
|
|
791
|
+
return `
|
|
792
|
+
<div class="synthetic-generator-options-grid synthetic-generator-options-grid--three">
|
|
793
|
+
${renderSyntheticOptionInput({
|
|
794
|
+
columnName,
|
|
795
|
+
option: 'min',
|
|
796
|
+
type: 'number',
|
|
797
|
+
value: options.min ?? 0,
|
|
798
|
+
step: '0.01',
|
|
799
|
+
placeholder: 'Min',
|
|
800
|
+
})}
|
|
801
|
+
${renderSyntheticOptionInput({
|
|
802
|
+
columnName,
|
|
803
|
+
option: 'max',
|
|
804
|
+
type: 'number',
|
|
805
|
+
value: options.max ?? 1000,
|
|
806
|
+
step: '0.01',
|
|
807
|
+
placeholder: 'Max',
|
|
808
|
+
})}
|
|
809
|
+
${renderSyntheticOptionInput({
|
|
810
|
+
columnName,
|
|
811
|
+
option: 'decimals',
|
|
812
|
+
type: 'number',
|
|
813
|
+
value: options.decimals ?? 2,
|
|
814
|
+
min: 0,
|
|
815
|
+
max: 8,
|
|
816
|
+
step: 1,
|
|
817
|
+
placeholder: 'Decimals',
|
|
818
|
+
})}
|
|
819
|
+
</div>
|
|
820
|
+
`;
|
|
821
|
+
case 'boolean':
|
|
822
|
+
return renderSyntheticOptionInput({
|
|
823
|
+
columnName,
|
|
824
|
+
option: 'trueProbability',
|
|
825
|
+
type: 'number',
|
|
826
|
+
value: options.trueProbability ?? 50,
|
|
827
|
+
min: 0,
|
|
828
|
+
max: 100,
|
|
829
|
+
step: 1,
|
|
830
|
+
placeholder: 'True %',
|
|
831
|
+
});
|
|
832
|
+
case 'timestamp':
|
|
833
|
+
return `
|
|
834
|
+
<div class="synthetic-generator-options-grid synthetic-generator-options-grid--timestamp">
|
|
835
|
+
${renderSyntheticOptionSelect({
|
|
836
|
+
columnName,
|
|
837
|
+
option: 'range',
|
|
838
|
+
value: options.range ?? 'last30',
|
|
839
|
+
options: [
|
|
840
|
+
{ value: 'last30', label: 'Last 30 days' },
|
|
841
|
+
{ value: 'last365', label: 'Last 365 days' },
|
|
842
|
+
{ value: 'custom', label: 'Custom' },
|
|
843
|
+
],
|
|
844
|
+
})}
|
|
845
|
+
${
|
|
846
|
+
options.range === 'custom'
|
|
847
|
+
? `${renderSyntheticOptionInput({
|
|
848
|
+
columnName,
|
|
849
|
+
option: 'from',
|
|
850
|
+
type: 'datetime-local',
|
|
851
|
+
value: options.from ?? '',
|
|
852
|
+
placeholder: 'From',
|
|
853
|
+
})}
|
|
854
|
+
${renderSyntheticOptionInput({
|
|
855
|
+
columnName,
|
|
856
|
+
option: 'to',
|
|
857
|
+
type: 'datetime-local',
|
|
858
|
+
value: options.to ?? '',
|
|
859
|
+
placeholder: 'To',
|
|
860
|
+
})}`
|
|
861
|
+
: ''
|
|
862
|
+
}
|
|
863
|
+
</div>
|
|
864
|
+
`;
|
|
865
|
+
case 'oneOf':
|
|
866
|
+
return renderSyntheticOptionInput({
|
|
867
|
+
columnName,
|
|
868
|
+
option: 'values',
|
|
869
|
+
value: options.values ?? '',
|
|
870
|
+
placeholder: 'Comma values',
|
|
871
|
+
});
|
|
872
|
+
case 'skip':
|
|
873
|
+
default:
|
|
874
|
+
return `<span class="font-mono text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">No options</span>`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function formatSyntheticPreviewValue(value) {
|
|
879
|
+
if (value === null || value === undefined) {
|
|
880
|
+
return 'NULL';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (typeof value === 'object') {
|
|
884
|
+
return JSON.stringify(value);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return String(value);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function renderSyntheticPreview(modal) {
|
|
891
|
+
const rows = (modal.previewRows ?? []).slice(0, 3);
|
|
892
|
+
const columns = modal.previewColumns?.length
|
|
893
|
+
? modal.previewColumns
|
|
894
|
+
: (modal.columns ?? []).map(column => column.name);
|
|
895
|
+
|
|
896
|
+
if (!rows.length) {
|
|
897
|
+
return `
|
|
898
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-4 text-sm text-on-surface-variant/55">
|
|
899
|
+
Preview creates up to 3 rows without writing to the database.
|
|
900
|
+
</div>
|
|
901
|
+
`;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return `
|
|
905
|
+
<div class="synthetic-generator-preview">
|
|
906
|
+
<table>
|
|
907
|
+
<thead>
|
|
908
|
+
<tr>
|
|
909
|
+
${columns.map(column => `<th>${escapeHtml(column)}</th>`).join('')}
|
|
910
|
+
</tr>
|
|
911
|
+
</thead>
|
|
912
|
+
<tbody>
|
|
913
|
+
${rows
|
|
914
|
+
.map(
|
|
915
|
+
row => `
|
|
916
|
+
<tr>
|
|
917
|
+
${columns
|
|
918
|
+
.map(
|
|
919
|
+
column =>
|
|
920
|
+
`<td title="${escapeHtml(formatSyntheticPreviewValue(row[column]))}">${escapeHtml(
|
|
921
|
+
truncateMiddle(formatSyntheticPreviewValue(row[column]), 56),
|
|
922
|
+
)}</td>`,
|
|
923
|
+
)
|
|
924
|
+
.join('')}
|
|
925
|
+
</tr>
|
|
926
|
+
`,
|
|
927
|
+
)
|
|
928
|
+
.join('')}
|
|
929
|
+
</tbody>
|
|
930
|
+
</table>
|
|
931
|
+
</div>
|
|
932
|
+
`;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export function renderGenerateDataForm(modal) {
|
|
936
|
+
const columns = modal.columns ?? [];
|
|
937
|
+
const mappings = modal.mappings ?? [];
|
|
938
|
+
const disabledAttribute = modal.submitting || modal.previewLoading ? 'disabled aria-disabled="true"' : '';
|
|
939
|
+
|
|
940
|
+
return `
|
|
941
|
+
<form class="generate-data-modal-form" data-form="generate-data">
|
|
942
|
+
<div class="synthetic-generator-count-row">
|
|
943
|
+
<label class="block space-y-2">
|
|
944
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">Rows</span>
|
|
945
|
+
<input
|
|
946
|
+
class="control-input w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
947
|
+
data-bind="generate-data-field"
|
|
948
|
+
data-field="rowCount"
|
|
949
|
+
max="10000"
|
|
950
|
+
min="1"
|
|
951
|
+
name="rowCount"
|
|
952
|
+
step="1"
|
|
953
|
+
type="number"
|
|
954
|
+
value="${escapeHtml(modal.rowCount ?? 100)}"
|
|
955
|
+
/>
|
|
956
|
+
</label>
|
|
957
|
+
<div class="flex items-end text-sm leading-6 text-on-surface-variant/60">
|
|
958
|
+
Create test rows for "${escapeHtml(modal.tableName ?? '')}".
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
<div class="synthetic-generator-grid custom-scrollbar">
|
|
962
|
+
<div class="synthetic-generator-grid__header">
|
|
963
|
+
<div>Column</div>
|
|
964
|
+
<div>SQLite Type</div>
|
|
965
|
+
<div>Generator</div>
|
|
966
|
+
<div>Options</div>
|
|
967
|
+
</div>
|
|
968
|
+
${
|
|
969
|
+
columns.length
|
|
970
|
+
? mappings
|
|
971
|
+
.map(mapping => {
|
|
972
|
+
const column = columns.find(item => item.name === mapping.columnName) ?? {};
|
|
973
|
+
const sqliteType = column.declaredType || column.affinity || 'ANY';
|
|
974
|
+
|
|
975
|
+
return `
|
|
976
|
+
<div class="synthetic-generator-grid__row">
|
|
977
|
+
<div class="min-w-0">
|
|
978
|
+
<div class="synthetic-generator-column-name">${escapeHtml(mapping.columnName)}</div>
|
|
979
|
+
${
|
|
980
|
+
mapping.note
|
|
981
|
+
? `<div class="synthetic-generator-column-note">${escapeHtml(mapping.note)}</div>`
|
|
982
|
+
: ''
|
|
983
|
+
}
|
|
984
|
+
</div>
|
|
985
|
+
<div class="synthetic-generator-sqlite-type">
|
|
986
|
+
${escapeHtml(sqliteType)}
|
|
987
|
+
${column.notNull ? '<span>NOT NULL</span>' : ''}
|
|
988
|
+
</div>
|
|
989
|
+
<div>
|
|
990
|
+
<select
|
|
991
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
992
|
+
data-bind="generate-data-mapping"
|
|
993
|
+
data-column-name="${escapeHtml(mapping.columnName)}"
|
|
994
|
+
data-field="generator"
|
|
995
|
+
title="${escapeHtml(getSyntheticGeneratorLabel(mapping.generator))}"
|
|
996
|
+
>
|
|
997
|
+
${renderSyntheticGeneratorOptions(mapping.generator)}
|
|
998
|
+
</select>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="min-w-0">
|
|
1001
|
+
${renderSyntheticMappingOptions(mapping)}
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
`;
|
|
1005
|
+
})
|
|
1006
|
+
.join('')
|
|
1007
|
+
: `<div class="px-4 py-6 text-sm text-on-surface-variant/55">No writable columns available.</div>`
|
|
1008
|
+
}
|
|
1009
|
+
</div>
|
|
1010
|
+
<div class="space-y-3">
|
|
1011
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container">Preview</div>
|
|
1012
|
+
${renderSyntheticPreview(modal)}
|
|
1013
|
+
</div>
|
|
1014
|
+
${renderError(modal.error)}
|
|
1015
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1016
|
+
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
1017
|
+
<div class="flex flex-wrap items-center justify-end gap-2">
|
|
1018
|
+
<button class="standard-button" data-action="preview-generate-data" type="button" ${disabledAttribute}>
|
|
1019
|
+
<span class="material-symbols-outlined text-sm">visibility</span>
|
|
1020
|
+
${modal.previewLoading ? 'Previewing...' : 'Preview'}
|
|
1021
|
+
</button>
|
|
1022
|
+
<button class="signature-button" type="submit" ${modal.submitting ? 'disabled aria-disabled="true"' : ''}>
|
|
1023
|
+
<span class="material-symbols-outlined text-sm">auto_awesome</span>
|
|
1024
|
+
${modal.submitting ? 'Inserting...' : 'Insert Rows'}
|
|
1025
|
+
</button>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
</form>
|
|
1029
|
+
`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
699
1032
|
function renderDeleteRowConfirmForm(modal) {
|
|
700
1033
|
const rowPreview = modal.rowPreview ?? [];
|
|
701
1034
|
const rowLabelMarkup = modal.rowLabel
|
|
@@ -2005,6 +2338,11 @@ export function renderModal(state) {
|
|
|
2005
2338
|
title: 'Generate Types',
|
|
2006
2339
|
body: renderGenerateTypesForm(modal),
|
|
2007
2340
|
},
|
|
2341
|
+
'generate-data': {
|
|
2342
|
+
eyebrow: 'Data Browser // Synthetic rows',
|
|
2343
|
+
title: 'Generate Synthetic Data',
|
|
2344
|
+
body: renderGenerateDataForm(modal),
|
|
2345
|
+
},
|
|
2008
2346
|
'edit-connection': {
|
|
2009
2347
|
eyebrow: 'Registry // Update saved SQLite target',
|
|
2010
2348
|
title: 'Edit Connection',
|
|
@@ -2093,6 +2431,11 @@ export function renderModal(state) {
|
|
|
2093
2431
|
return '';
|
|
2094
2432
|
}
|
|
2095
2433
|
|
|
2434
|
+
const modalBodyClass =
|
|
2435
|
+
modal.kind === 'generate-data'
|
|
2436
|
+
? 'app-modal-body app-modal-body--no-scroll px-6 py-6'
|
|
2437
|
+
: 'app-modal-body custom-scrollbar space-y-5 px-6 py-6';
|
|
2438
|
+
|
|
2096
2439
|
return `
|
|
2097
2440
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-background/85 px-4 backdrop-blur-sm">
|
|
2098
2441
|
<div class="app-modal-shell w-full ${
|
|
@@ -2102,13 +2445,11 @@ export function renderModal(state) {
|
|
|
2102
2445
|
modal.kind === 'row-update-preview' ||
|
|
2103
2446
|
modal.kind === 'table-designer-constraints'
|
|
2104
2447
|
? 'max-w-3xl'
|
|
2105
|
-
: modal.kind === 'generate-types'
|
|
2448
|
+
: modal.kind === 'generate-types' || modal.kind === 'generate-data'
|
|
2106
2449
|
? 'max-w-6xl'
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
? 'max-w-4xl'
|
|
2111
|
-
: 'max-w-xl'
|
|
2450
|
+
: modal.kind === 'query-export' || modal.kind === 'data-export' || modal.kind === 'backup-safety'
|
|
2451
|
+
? 'max-w-4xl'
|
|
2452
|
+
: 'max-w-xl'
|
|
2112
2453
|
} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
|
|
2113
2454
|
<div class="shrink-0 flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
|
|
2114
2455
|
<div>
|
|
@@ -2127,7 +2468,7 @@ export function renderModal(state) {
|
|
|
2127
2468
|
<span class="material-symbols-outlined">close</span>
|
|
2128
2469
|
</button>
|
|
2129
2470
|
</div>
|
|
2130
|
-
<div class="
|
|
2471
|
+
<div class="${modalBodyClass}">${config.body}</div>
|
|
2131
2472
|
</div>
|
|
2132
2473
|
</div>
|
|
2133
2474
|
`;
|
|
@@ -3,13 +3,36 @@ import { renderConnectionLogo } from './connectionLogo.js';
|
|
|
3
3
|
|
|
4
4
|
const sidebarItems = [
|
|
5
5
|
{ label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
|
|
6
|
-
{ label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
|
|
7
6
|
{ label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
|
|
8
|
-
{ label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
|
|
9
7
|
{ label: 'SQL_Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
{
|
|
9
|
+
label: 'SCHEMA',
|
|
10
|
+
key: 'schema',
|
|
11
|
+
icon: 'account_tree',
|
|
12
|
+
children: [
|
|
13
|
+
{ label: 'STRUCTURE', href: '#/structure', key: 'structure' },
|
|
14
|
+
{ label: 'ADVISOR', href: '#/table-advisor', key: 'tableAdvisor' },
|
|
15
|
+
{ label: 'DESIGNER', href: '#/table-designer', key: 'tableDesigner' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Insights',
|
|
20
|
+
key: 'insights',
|
|
21
|
+
icon: 'monitoring',
|
|
22
|
+
children: [
|
|
23
|
+
{ label: 'CHARTS', href: '#/charts', key: 'charts' },
|
|
24
|
+
{ label: 'OVERVIEW', href: '#/overview', key: 'overview' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Workspace',
|
|
29
|
+
key: 'workspace',
|
|
30
|
+
icon: 'folder_open',
|
|
31
|
+
children: [
|
|
32
|
+
{ label: 'DOCUMENTS', href: '#/documents', key: 'documents' },
|
|
33
|
+
{ label: 'BACKUPS', href: '#/backups', key: 'backups' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
13
36
|
{
|
|
14
37
|
label: 'MEDIA_TAGGING',
|
|
15
38
|
key: 'mediaTagging',
|
|
@@ -19,7 +42,6 @@ const sidebarItems = [
|
|
|
19
42
|
{ label: 'TAGGING_QUEUE', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
|
|
20
43
|
],
|
|
21
44
|
},
|
|
22
|
-
{ label: 'Backups', href: '#/backups', key: 'backups', icon: 'inventory_2' },
|
|
23
45
|
{ label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
|
|
24
46
|
];
|
|
25
47
|
|
|
@@ -36,6 +58,18 @@ function getActiveSidebarKey(routeName) {
|
|
|
36
58
|
return 'mediaTagging';
|
|
37
59
|
}
|
|
38
60
|
|
|
61
|
+
if (routeName === 'structure' || routeName === 'tableAdvisor' || routeName === 'tableDesigner') {
|
|
62
|
+
return 'schema';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (routeName === 'charts' || routeName === 'overview') {
|
|
66
|
+
return 'insights';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (routeName === 'documents' || routeName === 'backups') {
|
|
70
|
+
return 'workspace';
|
|
71
|
+
}
|
|
72
|
+
|
|
39
73
|
return routeName;
|
|
40
74
|
}
|
|
41
75
|
|
|
@@ -140,7 +174,7 @@ function renderConnectionQuickPicker(state) {
|
|
|
140
174
|
|
|
141
175
|
export function renderSidebar(state) {
|
|
142
176
|
const activeKey = getActiveSidebarKey(state.route.name);
|
|
143
|
-
const expandedKey =
|
|
177
|
+
const expandedKey = sidebarItems.some(item => item.key === activeKey && item.children) ? activeKey : null;
|
|
144
178
|
|
|
145
179
|
return `
|
|
146
180
|
<nav class="sidebar-links">
|
|
@@ -3,20 +3,8 @@ export function renderTopNav() {
|
|
|
3
3
|
<div class="top-nav-shell">
|
|
4
4
|
<a class="top-nav-brand" href="#/">SQLite Hub</a>
|
|
5
5
|
<div class="top-nav-actions">
|
|
6
|
-
<button
|
|
7
|
-
class="
|
|
8
|
-
data-action="open-modal"
|
|
9
|
-
data-modal="open-connection"
|
|
10
|
-
type="button"
|
|
11
|
-
aria-label="Open Database"
|
|
12
|
-
>
|
|
13
|
-
<span class="material-symbols-outlined">folder_open</span>
|
|
14
|
-
</button>
|
|
15
|
-
<button class="top-nav-icon" data-action="navigate" data-to="/editor" type="button" aria-label="SQL Editor">
|
|
16
|
-
<span class="material-symbols-outlined">terminal</span>
|
|
17
|
-
</button>
|
|
18
|
-
<button class="top-nav-icon" data-action="navigate" data-to="/settings" type="button" aria-label="Settings">
|
|
19
|
-
<span class="material-symbols-outlined">settings</span>
|
|
6
|
+
<button class="top-nav-icon" data-action="navigate" data-to="/logs" type="button" aria-label="Logs">
|
|
7
|
+
<span class="material-symbols-outlined">receipt_long</span>
|
|
20
8
|
</button>
|
|
21
9
|
</div>
|
|
22
10
|
</div>
|
package/frontend/js/router.js
CHANGED
|
@@ -84,6 +84,14 @@ export function parseHash(hash = window.location.hash) {
|
|
|
84
84
|
tableName: segments[1] && segments[1] !== 'new' ? decodeRouteValue(segments[1]) : null,
|
|
85
85
|
},
|
|
86
86
|
};
|
|
87
|
+
case 'table-advisor':
|
|
88
|
+
return {
|
|
89
|
+
name: 'tableAdvisor',
|
|
90
|
+
path: cleanPath,
|
|
91
|
+
params: {
|
|
92
|
+
tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
87
95
|
case 'media-tagging':
|
|
88
96
|
if (segments[1] === 'queue') {
|
|
89
97
|
return { name: 'mediaTaggingQueue', path: '/media-tagging/queue', params: {} };
|
|
@@ -91,6 +99,8 @@ export function parseHash(hash = window.location.hash) {
|
|
|
91
99
|
return { name: 'mediaTaggingSetup', path: '/media-tagging', params: {} };
|
|
92
100
|
case 'settings':
|
|
93
101
|
return { name: 'settings', path: '/settings', params: {} };
|
|
102
|
+
case 'logs':
|
|
103
|
+
return { name: 'logs', path: '/logs', params: {} };
|
|
94
104
|
default:
|
|
95
105
|
return { name: 'notFound', path: cleanPath, params: {} };
|
|
96
106
|
}
|