@youtyan/code-viewer 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code-viewer.js +37 -13
- package/package.json +1 -1
- package/web/app.js +674 -130
- package/web/style.css +467 -97
package/web/app.js
CHANGED
|
@@ -680,12 +680,16 @@
|
|
|
680
680
|
const table = params.get("table") || undefined;
|
|
681
681
|
const tabRaw = params.get("tab");
|
|
682
682
|
const tab = tabRaw === "data" || tabRaw === "query" || tabRaw === "schema" || tabRaw === "er" || tabRaw === "search" || tabRaw === "snapshot" ? tabRaw : undefined;
|
|
683
|
+
const diffBefore = params.get("diffBefore") || undefined;
|
|
684
|
+
const diffAfter = params.get("diffAfter") || undefined;
|
|
683
685
|
return {
|
|
684
686
|
screen: "database",
|
|
685
687
|
...db ? { db } : {},
|
|
686
688
|
...schema ? { schema } : {},
|
|
687
689
|
...table ? { table } : {},
|
|
688
690
|
...tab ? { tab } : {},
|
|
691
|
+
...diffBefore ? { diffBefore } : {},
|
|
692
|
+
...diffAfter ? { diffAfter } : {},
|
|
689
693
|
range
|
|
690
694
|
};
|
|
691
695
|
}
|
|
@@ -752,6 +756,10 @@
|
|
|
752
756
|
params.set("table", route.table);
|
|
753
757
|
if (route.tab)
|
|
754
758
|
params.set("tab", route.tab);
|
|
759
|
+
if (route.diffBefore)
|
|
760
|
+
params.set("diffBefore", route.diffBefore);
|
|
761
|
+
if (route.diffAfter)
|
|
762
|
+
params.set("diffAfter", route.diffAfter);
|
|
755
763
|
const qs = params.toString();
|
|
756
764
|
return `/database${qs ? `?${qs}` : ""}`;
|
|
757
765
|
}
|
|
@@ -9642,7 +9650,24 @@ ${frontmatter.yaml}
|
|
|
9642
9650
|
listTitle: (n2) => `Snapshots (${n2})`,
|
|
9643
9651
|
diffSummary: (changed, unchanged) => `${changed} tables changed` + (unchanged > 0 ? `, ${unchanged} unchanged` : ""),
|
|
9644
9652
|
showingRows: (shown, total) => `Showing ${shown} of ${total}`,
|
|
9645
|
-
label: (date, tables, note) => `${date} (${tables} tables)${note}
|
|
9653
|
+
label: (date, tables, note) => `${date} (${tables} tables)${note}`,
|
|
9654
|
+
statusRunning: "running",
|
|
9655
|
+
statusError: "error",
|
|
9656
|
+
statusDone: "done",
|
|
9657
|
+
progressLabel: (table2, index, total) => table2 ? `Snapshotting ${table2} (${index + 1} / ${total})` : `Finalizing… (${total} / ${total})`,
|
|
9658
|
+
progressFinalizing: "Finalizing…",
|
|
9659
|
+
aborted: "cancelled",
|
|
9660
|
+
failed: "Failed",
|
|
9661
|
+
errorPrefix: "Error",
|
|
9662
|
+
compareBeforeBadge: "before",
|
|
9663
|
+
compareAfterBadge: "after",
|
|
9664
|
+
diffShowing: (before, after) => `Showing: ${before} → ${after}`,
|
|
9665
|
+
filterPlaceholder: "Filter tables…",
|
|
9666
|
+
tableCounter: (selected, total) => `${selected} / ${total} selected`,
|
|
9667
|
+
coverageBeforeOnly: "not snapshotted in after",
|
|
9668
|
+
coverageAfterOnly: "not snapshotted in before",
|
|
9669
|
+
coverageNoteBeforeOnly: (rows) => `Only in the before snapshot (${rows} rows). Not selected for the after snapshot, so no comparison.`,
|
|
9670
|
+
coverageNoteAfterOnly: (rows) => `Only in the after snapshot (${rows} rows). Not selected for the before snapshot, so no comparison.`
|
|
9646
9671
|
},
|
|
9647
9672
|
explorer: {
|
|
9648
9673
|
common: {
|
|
@@ -9914,7 +9939,24 @@ ${frontmatter.yaml}
|
|
|
9914
9939
|
listTitle: (n2) => `スナップショット (${n2}件)`,
|
|
9915
9940
|
diffSummary: (changed, unchanged) => `${changed}テーブルに変更あり` + (unchanged > 0 ? `、${unchanged}テーブルは変更なし` : ""),
|
|
9916
9941
|
showingRows: (shown, total) => `全${total}件中 ${shown}件を表示中`,
|
|
9917
|
-
label: (date, tables, note) => `${date} (${tables}テーブル)${note}
|
|
9942
|
+
label: (date, tables, note) => `${date} (${tables}テーブル)${note}`,
|
|
9943
|
+
statusRunning: "取得中",
|
|
9944
|
+
statusError: "エラー",
|
|
9945
|
+
statusDone: "完了",
|
|
9946
|
+
progressLabel: (table2, index, total) => table2 ? `スナップショット取得中: ${table2} (${index + 1} / ${total})` : `仕上げ中… (${total} / ${total})`,
|
|
9947
|
+
progressFinalizing: "仕上げ中…",
|
|
9948
|
+
aborted: "キャンセル",
|
|
9949
|
+
failed: "失敗",
|
|
9950
|
+
errorPrefix: "エラー",
|
|
9951
|
+
compareBeforeBadge: "比較元",
|
|
9952
|
+
compareAfterBadge: "比較先",
|
|
9953
|
+
diffShowing: (before, after) => `比較中: ${before} → ${after}`,
|
|
9954
|
+
filterPlaceholder: "テーブル名で絞り込み…",
|
|
9955
|
+
tableCounter: (selected, total) => `${selected} / ${total} 件選択中`,
|
|
9956
|
+
coverageBeforeOnly: "比較先で未取得",
|
|
9957
|
+
coverageAfterOnly: "比較元で未取得",
|
|
9958
|
+
coverageNoteBeforeOnly: (rows) => `比較元のみに存在 (${rows}行)。比較先では対象テーブルに選ばれていないため比較できません。`,
|
|
9959
|
+
coverageNoteAfterOnly: (rows) => `比較先のみに存在 (${rows}行)。比較元では対象テーブルに選ばれていないため比較できません。`
|
|
9918
9960
|
},
|
|
9919
9961
|
explorer: {
|
|
9920
9962
|
common: {
|
|
@@ -14495,18 +14537,18 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14495
14537
|
const sb = [...b2].sort();
|
|
14496
14538
|
return sa.every((v, i2) => v === sb[i2]);
|
|
14497
14539
|
}
|
|
14498
|
-
function
|
|
14499
|
-
|
|
14540
|
+
function createSnapshotView(deps) {
|
|
14541
|
+
const text2 = () => deps.getText?.() ?? dbText("en");
|
|
14542
|
+
const trackLoad = (p2) => deps.trackLoad ? deps.trackLoad(p2) : p2;
|
|
14543
|
+
const postJson = (path, body) => trackLoad(fetch(path, {
|
|
14500
14544
|
method: "POST",
|
|
14501
14545
|
headers: {
|
|
14502
14546
|
"Content-Type": "application/json",
|
|
14503
14547
|
"X-Code-Viewer-Action": "1"
|
|
14504
14548
|
},
|
|
14505
14549
|
body: JSON.stringify(body)
|
|
14506
|
-
});
|
|
14507
|
-
|
|
14508
|
-
function createSnapshotView(deps) {
|
|
14509
|
-
const text2 = () => deps.getText?.() ?? dbText("en");
|
|
14550
|
+
}));
|
|
14551
|
+
const getJson = (path) => trackLoad(fetch(path));
|
|
14510
14552
|
const changeTypeLabel = (type) => {
|
|
14511
14553
|
if (type === "inserted")
|
|
14512
14554
|
return text2().snapshot.inserted;
|
|
@@ -14540,18 +14582,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14540
14582
|
refreshBtn.type = "button";
|
|
14541
14583
|
refreshBtn.className = "db-snapshot-refresh-btn";
|
|
14542
14584
|
refreshBtn.textContent = text2().snapshot.refresh;
|
|
14543
|
-
|
|
14544
|
-
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
snapshotCancelBtn.hidden = true;
|
|
14548
|
-
toolbar.append(createBtn, refreshBtn, snapshotCancelBtn);
|
|
14585
|
+
toolbar.append(createBtn, refreshBtn);
|
|
14586
|
+
const progressArea = document.createElement("div");
|
|
14587
|
+
progressArea.className = "db-snapshot-progress-area";
|
|
14588
|
+
progressArea.hidden = true;
|
|
14549
14589
|
const tableSelector = document.createElement("div");
|
|
14550
14590
|
tableSelector.className = "db-snapshot-table-selector";
|
|
14551
14591
|
tableSelector.hidden = true;
|
|
14552
14592
|
const tableSelectorHeader = document.createElement("div");
|
|
14553
14593
|
tableSelectorHeader.className = "db-snapshot-table-selector-header";
|
|
14554
|
-
|
|
14594
|
+
const tableSelectorHeaderLabel = document.createElement("span");
|
|
14595
|
+
tableSelectorHeaderLabel.textContent = text2().snapshot.selectTablesHeader;
|
|
14596
|
+
const tableSelectorCounter = document.createElement("span");
|
|
14597
|
+
tableSelectorCounter.className = "db-snapshot-table-counter";
|
|
14598
|
+
tableSelectorHeader.append(tableSelectorHeaderLabel, tableSelectorCounter);
|
|
14599
|
+
const filterRow = document.createElement("div");
|
|
14600
|
+
filterRow.className = "db-snapshot-table-filter";
|
|
14601
|
+
const filterInput = document.createElement("input");
|
|
14602
|
+
filterInput.type = "search";
|
|
14603
|
+
filterInput.className = "db-snapshot-table-filter-input";
|
|
14604
|
+
filterInput.placeholder = text2().snapshot.filterPlaceholder;
|
|
14605
|
+
filterRow.appendChild(filterInput);
|
|
14555
14606
|
const tableCheckboxes = document.createElement("div");
|
|
14556
14607
|
tableCheckboxes.className = "db-snapshot-table-checkboxes";
|
|
14557
14608
|
const selectActions = document.createElement("div");
|
|
@@ -14562,16 +14613,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14562
14613
|
const deselectAllBtn = document.createElement("button");
|
|
14563
14614
|
deselectAllBtn.type = "button";
|
|
14564
14615
|
deselectAllBtn.textContent = text2().snapshot.deselectAll;
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
noteInput.placeholder = text2().snapshot.notePlaceholder;
|
|
14616
|
+
selectActions.append(selectAllBtn, deselectAllBtn);
|
|
14617
|
+
const noteRow = document.createElement("div");
|
|
14618
|
+
noteRow.className = "db-snapshot-note-row";
|
|
14569
14619
|
const noteField = document.createElement("label");
|
|
14570
14620
|
noteField.className = "db-snapshot-note-field";
|
|
14571
14621
|
const noteLabel = document.createElement("span");
|
|
14572
14622
|
noteLabel.className = "db-snapshot-note-label";
|
|
14573
14623
|
noteLabel.textContent = text2().snapshot.noteLabel;
|
|
14624
|
+
const noteInput = document.createElement("input");
|
|
14625
|
+
noteInput.type = "text";
|
|
14626
|
+
noteInput.className = "db-snapshot-note-input";
|
|
14627
|
+
noteInput.placeholder = text2().snapshot.notePlaceholder;
|
|
14574
14628
|
noteField.append(noteLabel, noteInput);
|
|
14629
|
+
noteRow.appendChild(noteField);
|
|
14630
|
+
const actionRow = document.createElement("div");
|
|
14631
|
+
actionRow.className = "db-snapshot-action-row";
|
|
14575
14632
|
const confirmBtn = document.createElement("button");
|
|
14576
14633
|
confirmBtn.type = "button";
|
|
14577
14634
|
confirmBtn.className = "db-snapshot-confirm-btn";
|
|
@@ -14580,41 +14637,178 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14580
14637
|
cancelBtn.type = "button";
|
|
14581
14638
|
cancelBtn.className = "db-snapshot-cancel-btn";
|
|
14582
14639
|
cancelBtn.textContent = text2().snapshot.cancel;
|
|
14583
|
-
|
|
14584
|
-
tableSelector.append(tableSelectorHeader, tableCheckboxes, selectActions);
|
|
14640
|
+
actionRow.append(confirmBtn, cancelBtn);
|
|
14641
|
+
tableSelector.append(tableSelectorHeader, filterRow, tableCheckboxes, selectActions, noteRow, actionRow);
|
|
14585
14642
|
const mainArea = document.createElement("div");
|
|
14586
14643
|
mainArea.className = "db-snapshot-main-area";
|
|
14587
|
-
|
|
14644
|
+
const splitContainer = document.createElement("div");
|
|
14645
|
+
splitContainer.className = "db-snapshot-split";
|
|
14646
|
+
const leftPane = document.createElement("div");
|
|
14647
|
+
leftPane.className = "db-snapshot-left-pane";
|
|
14648
|
+
const rightPane = document.createElement("div");
|
|
14649
|
+
rightPane.className = "db-snapshot-right-pane";
|
|
14650
|
+
splitContainer.append(leftPane, rightPane);
|
|
14651
|
+
mainArea.appendChild(splitContainer);
|
|
14652
|
+
el.append(guide, toolbar, progressArea, tableSelector, mainArea);
|
|
14588
14653
|
let snapshots = [];
|
|
14589
|
-
let activeSnapshotId = null;
|
|
14590
14654
|
let currentDiff = null;
|
|
14591
14655
|
let disposed = false;
|
|
14592
14656
|
const autoRefreshTimers = new Set;
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14657
|
+
const activeJobs = new Map;
|
|
14658
|
+
const cancellingJobs = new Set;
|
|
14659
|
+
const jobClearTimers = new Map;
|
|
14660
|
+
function upsertJob(id, patch) {
|
|
14661
|
+
const prev = activeJobs.get(id) ?? { id, total: 0, done: false };
|
|
14662
|
+
activeJobs.set(id, { ...prev, ...patch, id });
|
|
14663
|
+
renderProgress();
|
|
14664
|
+
}
|
|
14665
|
+
function scheduleJobRemove(id) {
|
|
14666
|
+
const existing = jobClearTimers.get(id);
|
|
14667
|
+
if (existing)
|
|
14668
|
+
clearTimeout(existing);
|
|
14669
|
+
const t2 = setTimeout(() => {
|
|
14670
|
+
jobClearTimers.delete(id);
|
|
14671
|
+
cancellingJobs.delete(id);
|
|
14672
|
+
activeJobs.delete(id);
|
|
14673
|
+
renderProgress();
|
|
14674
|
+
}, 5000);
|
|
14675
|
+
jobClearTimers.set(id, t2);
|
|
14676
|
+
}
|
|
14677
|
+
async function cancelJob(id) {
|
|
14678
|
+
if (cancellingJobs.has(id))
|
|
14679
|
+
return;
|
|
14680
|
+
cancellingJobs.add(id);
|
|
14681
|
+
renderProgress();
|
|
14682
|
+
try {
|
|
14683
|
+
await postJson("/_db/snapshot/cancel", { id });
|
|
14684
|
+
} catch {}
|
|
14685
|
+
}
|
|
14686
|
+
function renderProgress() {
|
|
14687
|
+
progressArea.innerHTML = "";
|
|
14688
|
+
if (activeJobs.size === 0) {
|
|
14689
|
+
progressArea.hidden = true;
|
|
14690
|
+
return;
|
|
14691
|
+
}
|
|
14692
|
+
progressArea.hidden = false;
|
|
14693
|
+
const t2 = text2().snapshot;
|
|
14694
|
+
for (const job of activeJobs.values()) {
|
|
14695
|
+
const row = document.createElement("div");
|
|
14696
|
+
row.className = "db-snapshot-progress-row";
|
|
14697
|
+
row.dataset.snapshotId = job.id;
|
|
14698
|
+
const label = document.createElement("span");
|
|
14699
|
+
label.className = "db-snapshot-progress-label";
|
|
14700
|
+
if (job.errorMessage) {
|
|
14701
|
+
label.textContent = `${t2.errorPrefix}: ${job.errorMessage}`;
|
|
14702
|
+
label.title = job.errorMessage;
|
|
14703
|
+
row.classList.add("error");
|
|
14704
|
+
} else if (job.aborted) {
|
|
14705
|
+
label.textContent = t2.aborted;
|
|
14706
|
+
row.classList.add("aborted");
|
|
14707
|
+
} else if (job.done) {
|
|
14708
|
+
label.textContent = t2.statusDone;
|
|
14709
|
+
row.classList.add("done");
|
|
14710
|
+
} else if (job.total > 0 && job.table) {
|
|
14711
|
+
label.textContent = t2.progressLabel(job.table, job.index ?? 0, job.total);
|
|
14712
|
+
row.classList.add("running");
|
|
14713
|
+
} else {
|
|
14714
|
+
label.textContent = t2.creating;
|
|
14715
|
+
row.classList.add("running");
|
|
14716
|
+
}
|
|
14717
|
+
row.appendChild(label);
|
|
14718
|
+
if (!job.done) {
|
|
14719
|
+
const cancelRowBtn = document.createElement("button");
|
|
14720
|
+
cancelRowBtn.type = "button";
|
|
14721
|
+
cancelRowBtn.className = "db-snapshot-job-cancel-btn";
|
|
14722
|
+
if (cancellingJobs.has(job.id)) {
|
|
14723
|
+
cancelRowBtn.disabled = true;
|
|
14724
|
+
cancelRowBtn.textContent = t2.cancelling;
|
|
14725
|
+
} else {
|
|
14726
|
+
cancelRowBtn.textContent = t2.cancel;
|
|
14727
|
+
cancelRowBtn.addEventListener("click", () => {
|
|
14728
|
+
cancelJob(job.id);
|
|
14729
|
+
});
|
|
14730
|
+
}
|
|
14731
|
+
row.appendChild(cancelRowBtn);
|
|
14732
|
+
}
|
|
14733
|
+
progressArea.appendChild(row);
|
|
14734
|
+
}
|
|
14735
|
+
}
|
|
14736
|
+
let tableSelectorTables = [];
|
|
14737
|
+
const selectedTableSet = new Set;
|
|
14738
|
+
let tableFilterValue = "";
|
|
14739
|
+
function setTableSelectorVisible(visible) {
|
|
14740
|
+
tableSelector.hidden = !visible;
|
|
14741
|
+
createBtn.disabled = visible;
|
|
14742
|
+
createBtn.classList.toggle("is-active-dialog", visible);
|
|
14598
14743
|
}
|
|
14599
14744
|
function showTableSelector() {
|
|
14600
|
-
|
|
14601
|
-
const
|
|
14745
|
+
tableSelectorTables = deps.getTables().filter((t2) => t2.type === "table");
|
|
14746
|
+
const currentTableNames = new Set(tableSelectorTables.map((t2) => t2.name));
|
|
14747
|
+
const stored = deps.getStoredSelectedTables?.();
|
|
14748
|
+
const rawTables = stored?.length ? stored : getLastTables();
|
|
14749
|
+
const lastTables = rawTables.filter((name) => currentTableNames.has(name));
|
|
14750
|
+
selectedTableSet.clear();
|
|
14751
|
+
if (lastTables.length === 0) {
|
|
14752
|
+
for (const t2 of tableSelectorTables)
|
|
14753
|
+
selectedTableSet.add(t2.name);
|
|
14754
|
+
} else {
|
|
14755
|
+
for (const name of lastTables)
|
|
14756
|
+
selectedTableSet.add(name);
|
|
14757
|
+
}
|
|
14758
|
+
tableFilterValue = "";
|
|
14759
|
+
filterInput.value = "";
|
|
14760
|
+
renderTableList();
|
|
14761
|
+
setTableSelectorVisible(true);
|
|
14762
|
+
noteInput.value = "";
|
|
14763
|
+
filterInput.focus();
|
|
14764
|
+
}
|
|
14765
|
+
function persistSelectedTables() {
|
|
14766
|
+
deps.setStoredSelectedTables?.(Array.from(selectedTableSet).sort());
|
|
14767
|
+
}
|
|
14768
|
+
function renderTableList() {
|
|
14769
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14602
14770
|
tableCheckboxes.innerHTML = "";
|
|
14603
|
-
|
|
14604
|
-
|
|
14771
|
+
const t2 = text2().snapshot;
|
|
14772
|
+
let visible = 0;
|
|
14773
|
+
for (const tbl of tableSelectorTables) {
|
|
14774
|
+
if (needle && !tbl.name.toLowerCase().includes(needle))
|
|
14605
14775
|
continue;
|
|
14606
|
-
|
|
14607
|
-
|
|
14776
|
+
visible++;
|
|
14777
|
+
const row = document.createElement("label");
|
|
14778
|
+
row.className = "db-snapshot-table-row";
|
|
14608
14779
|
const cb = document.createElement("input");
|
|
14609
14780
|
cb.type = "checkbox";
|
|
14610
|
-
cb.
|
|
14611
|
-
cb.
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14781
|
+
cb.value = tbl.name;
|
|
14782
|
+
cb.checked = selectedTableSet.has(tbl.name);
|
|
14783
|
+
cb.addEventListener("change", () => {
|
|
14784
|
+
if (cb.checked)
|
|
14785
|
+
selectedTableSet.add(tbl.name);
|
|
14786
|
+
else
|
|
14787
|
+
selectedTableSet.delete(tbl.name);
|
|
14788
|
+
updateSelectorState();
|
|
14789
|
+
persistSelectedTables();
|
|
14790
|
+
});
|
|
14791
|
+
const name = document.createElement("span");
|
|
14792
|
+
name.className = "db-snapshot-table-row-name";
|
|
14793
|
+
name.textContent = tbl.name;
|
|
14794
|
+
const count = document.createElement("span");
|
|
14795
|
+
count.className = "db-snapshot-table-row-count";
|
|
14796
|
+
count.textContent = tbl.rowCount != null ? t2.rowCount(tbl.rowCount).trim() : "";
|
|
14797
|
+
row.append(cb, name, count);
|
|
14798
|
+
tableCheckboxes.appendChild(row);
|
|
14615
14799
|
}
|
|
14616
|
-
|
|
14617
|
-
|
|
14800
|
+
if (visible === 0 && needle) {
|
|
14801
|
+
const empty = document.createElement("div");
|
|
14802
|
+
empty.className = "db-snapshot-table-row-empty";
|
|
14803
|
+
empty.textContent = t2.empty;
|
|
14804
|
+
tableCheckboxes.appendChild(empty);
|
|
14805
|
+
}
|
|
14806
|
+
updateSelectorState();
|
|
14807
|
+
}
|
|
14808
|
+
function updateSelectorState() {
|
|
14809
|
+
const t2 = text2().snapshot;
|
|
14810
|
+
tableSelectorCounter.textContent = t2.tableCounter(selectedTableSet.size, tableSelectorTables.length);
|
|
14811
|
+
confirmBtn.disabled = selectedTableSet.size === 0;
|
|
14618
14812
|
}
|
|
14619
14813
|
function getLastTables() {
|
|
14620
14814
|
const dbId = deps.getDbId();
|
|
@@ -14624,36 +14818,47 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14624
14818
|
return done.length > 0 ? done[0].tables : [];
|
|
14625
14819
|
}
|
|
14626
14820
|
function getSelectedTables() {
|
|
14627
|
-
return Array.from(
|
|
14821
|
+
return Array.from(selectedTableSet);
|
|
14628
14822
|
}
|
|
14823
|
+
filterInput.addEventListener("input", () => {
|
|
14824
|
+
tableFilterValue = filterInput.value;
|
|
14825
|
+
renderTableList();
|
|
14826
|
+
});
|
|
14827
|
+
filterInput.addEventListener("keydown", (e2) => {
|
|
14828
|
+
if (e2.key === "Escape" && filterInput.value) {
|
|
14829
|
+
e2.preventDefault();
|
|
14830
|
+
filterInput.value = "";
|
|
14831
|
+
tableFilterValue = "";
|
|
14832
|
+
renderTableList();
|
|
14833
|
+
}
|
|
14834
|
+
});
|
|
14629
14835
|
selectAllBtn.addEventListener("click", () => {
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14836
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14837
|
+
for (const tbl of tableSelectorTables) {
|
|
14838
|
+
if (needle && !tbl.name.toLowerCase().includes(needle))
|
|
14839
|
+
continue;
|
|
14840
|
+
selectedTableSet.add(tbl.name);
|
|
14841
|
+
}
|
|
14842
|
+
renderTableList();
|
|
14843
|
+
persistSelectedTables();
|
|
14633
14844
|
});
|
|
14634
14845
|
deselectAllBtn.addEventListener("click", () => {
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14846
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14847
|
+
if (!needle) {
|
|
14848
|
+
selectedTableSet.clear();
|
|
14849
|
+
} else {
|
|
14850
|
+
for (const tbl of tableSelectorTables) {
|
|
14851
|
+
if (tbl.name.toLowerCase().includes(needle)) {
|
|
14852
|
+
selectedTableSet.delete(tbl.name);
|
|
14853
|
+
}
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
renderTableList();
|
|
14857
|
+
persistSelectedTables();
|
|
14638
14858
|
});
|
|
14639
14859
|
createBtn.addEventListener("click", showTableSelector);
|
|
14640
14860
|
cancelBtn.addEventListener("click", () => {
|
|
14641
|
-
|
|
14642
|
-
});
|
|
14643
|
-
snapshotCancelBtn.addEventListener("click", async () => {
|
|
14644
|
-
if (!activeSnapshotId)
|
|
14645
|
-
return;
|
|
14646
|
-
const snapshotId = activeSnapshotId;
|
|
14647
|
-
snapshotCancelBtn.disabled = true;
|
|
14648
|
-
snapshotCancelBtn.textContent = text2().snapshot.cancelling;
|
|
14649
|
-
try {
|
|
14650
|
-
await postJson("/_db/snapshot/cancel", { id: snapshotId });
|
|
14651
|
-
} catch {} finally {
|
|
14652
|
-
if (!disposed && activeSnapshotId === snapshotId) {
|
|
14653
|
-
snapshotCancelBtn.disabled = false;
|
|
14654
|
-
snapshotCancelBtn.textContent = text2().snapshot.cancel;
|
|
14655
|
-
}
|
|
14656
|
-
}
|
|
14861
|
+
setTableSelectorVisible(false);
|
|
14657
14862
|
});
|
|
14658
14863
|
confirmBtn.addEventListener("click", async () => {
|
|
14659
14864
|
const dbId = deps.getDbId();
|
|
@@ -14672,7 +14877,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14672
14877
|
tables,
|
|
14673
14878
|
note: noteInput.value.trim()
|
|
14674
14879
|
});
|
|
14675
|
-
|
|
14880
|
+
setTableSelectorVisible(false);
|
|
14676
14881
|
scheduleAutoRefresh(dbId, schema);
|
|
14677
14882
|
} catch {} finally {
|
|
14678
14883
|
if (!disposed) {
|
|
@@ -14692,7 +14897,20 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14692
14897
|
autoRefreshTimers.add(timer);
|
|
14693
14898
|
}
|
|
14694
14899
|
refreshBtn.addEventListener("click", refresh);
|
|
14900
|
+
let inflightRefresh = null;
|
|
14695
14901
|
async function refresh() {
|
|
14902
|
+
if (inflightRefresh) {
|
|
14903
|
+
await inflightRefresh;
|
|
14904
|
+
return;
|
|
14905
|
+
}
|
|
14906
|
+
inflightRefresh = doRefresh();
|
|
14907
|
+
try {
|
|
14908
|
+
await inflightRefresh;
|
|
14909
|
+
} finally {
|
|
14910
|
+
inflightRefresh = null;
|
|
14911
|
+
}
|
|
14912
|
+
}
|
|
14913
|
+
async function doRefresh() {
|
|
14696
14914
|
const dbId = deps.getDbId();
|
|
14697
14915
|
const schema = deps.getSchema();
|
|
14698
14916
|
if (!dbId)
|
|
@@ -14701,7 +14919,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14701
14919
|
const params = new URLSearchParams({ db: dbId });
|
|
14702
14920
|
if (schema)
|
|
14703
14921
|
params.set("schema", schema);
|
|
14704
|
-
const snapRes = await
|
|
14922
|
+
const snapRes = await getJson(`/_db/snapshot/list?${params}`);
|
|
14705
14923
|
if (snapRes.ok) {
|
|
14706
14924
|
const data = await snapRes.json();
|
|
14707
14925
|
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
@@ -14712,6 +14930,18 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14712
14930
|
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema)
|
|
14713
14931
|
return;
|
|
14714
14932
|
renderMain();
|
|
14933
|
+
if (currentDiff) {
|
|
14934
|
+
const stillValid = snapshots.some((s2) => s2.id === currentDiff?.beforeId) && snapshots.some((s2) => s2.id === currentDiff?.afterId);
|
|
14935
|
+
if (!stillValid) {
|
|
14936
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
14937
|
+
if (existing) {
|
|
14938
|
+
const prev = existing;
|
|
14939
|
+
prev.__tocObserver?.disconnect();
|
|
14940
|
+
existing.remove();
|
|
14941
|
+
}
|
|
14942
|
+
currentDiff = null;
|
|
14943
|
+
}
|
|
14944
|
+
}
|
|
14715
14945
|
} catch {}
|
|
14716
14946
|
}
|
|
14717
14947
|
async function refreshAndAutoDiff() {
|
|
@@ -14726,21 +14956,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14726
14956
|
showDiffInline(prev.id, newest.id);
|
|
14727
14957
|
}
|
|
14728
14958
|
function renderMain() {
|
|
14729
|
-
|
|
14959
|
+
leftPane.innerHTML = "";
|
|
14730
14960
|
const done = snapshots.filter((s2) => s2.status === "done");
|
|
14731
14961
|
if (snapshots.length === 0) {
|
|
14732
14962
|
const empty = document.createElement("div");
|
|
14733
14963
|
empty.className = "db-snapshot-empty";
|
|
14734
14964
|
empty.textContent = text2().snapshot.empty;
|
|
14735
|
-
|
|
14965
|
+
leftPane.appendChild(empty);
|
|
14736
14966
|
return;
|
|
14737
14967
|
}
|
|
14968
|
+
const stickyHeader = document.createElement("div");
|
|
14969
|
+
stickyHeader.className = "db-snapshot-sticky-header";
|
|
14738
14970
|
const snapshotSection = document.createElement("div");
|
|
14739
14971
|
snapshotSection.className = "db-snapshot-list-section";
|
|
14740
14972
|
const snapTitle = document.createElement("h3");
|
|
14741
14973
|
snapTitle.className = "db-snapshot-section-title";
|
|
14742
|
-
snapTitle.textContent = text2().snapshot.listTitle(
|
|
14974
|
+
snapTitle.textContent = text2().snapshot.listTitle(snapshots.length);
|
|
14743
14975
|
snapshotSection.appendChild(snapTitle);
|
|
14976
|
+
const t2 = text2().snapshot;
|
|
14744
14977
|
for (const snap of snapshots) {
|
|
14745
14978
|
const item = document.createElement("div");
|
|
14746
14979
|
item.className = "db-snapshot-item";
|
|
@@ -14750,17 +14983,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14750
14983
|
const date = document.createElement("span");
|
|
14751
14984
|
date.className = "db-snapshot-date";
|
|
14752
14985
|
date.textContent = new Date(snap.createdAt).toLocaleString();
|
|
14986
|
+
const status = document.createElement("span");
|
|
14987
|
+
status.className = `db-snapshot-status ${snap.status}`;
|
|
14988
|
+
status.textContent = snap.status === "running" ? t2.statusRunning : snap.status === "error" ? t2.statusError : t2.statusDone;
|
|
14753
14989
|
const tables = document.createElement("span");
|
|
14754
14990
|
tables.className = "db-snapshot-tables-count";
|
|
14755
14991
|
tables.title = snap.tables.join(", ");
|
|
14756
|
-
tables.textContent =
|
|
14757
|
-
info.append(date, tables);
|
|
14992
|
+
tables.textContent = t2.tableCount(snap.tables.length);
|
|
14993
|
+
info.append(date, status, tables);
|
|
14758
14994
|
if (snap.note) {
|
|
14759
14995
|
const note = document.createElement("span");
|
|
14760
14996
|
note.className = "db-snapshot-note";
|
|
14761
14997
|
note.textContent = snap.note;
|
|
14762
14998
|
info.appendChild(note);
|
|
14763
14999
|
}
|
|
15000
|
+
if (snap.status === "error" && snap.errorMessage) {
|
|
15001
|
+
const errMsg = document.createElement("span");
|
|
15002
|
+
errMsg.className = "db-snapshot-error-message";
|
|
15003
|
+
errMsg.textContent = `${t2.errorPrefix}: ${snap.errorMessage}`;
|
|
15004
|
+
errMsg.title = snap.errorMessage;
|
|
15005
|
+
info.appendChild(errMsg);
|
|
15006
|
+
}
|
|
14764
15007
|
const actions = document.createElement("div");
|
|
14765
15008
|
actions.className = "db-snapshot-actions";
|
|
14766
15009
|
const noteBtn = document.createElement("button");
|
|
@@ -14797,7 +15040,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14797
15040
|
beforeSelect.className = "db-snapshot-diff-select";
|
|
14798
15041
|
const afterSelect = document.createElement("select");
|
|
14799
15042
|
afterSelect.className = "db-snapshot-diff-select";
|
|
14800
|
-
const sorted = [...done].sort((a2, b2) => new Date(
|
|
15043
|
+
const sorted = [...done].sort((a2, b2) => new Date(b2.createdAt).getTime() - new Date(a2.createdAt).getTime());
|
|
14801
15044
|
for (const s2 of sorted) {
|
|
14802
15045
|
const optB = document.createElement("option");
|
|
14803
15046
|
optB.value = s2.id;
|
|
@@ -14809,8 +15052,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14809
15052
|
afterSelect.appendChild(optA);
|
|
14810
15053
|
}
|
|
14811
15054
|
if (sorted.length >= 2) {
|
|
14812
|
-
beforeSelect.selectedIndex =
|
|
14813
|
-
afterSelect.selectedIndex =
|
|
15055
|
+
beforeSelect.selectedIndex = 1;
|
|
15056
|
+
afterSelect.selectedIndex = 0;
|
|
14814
15057
|
}
|
|
14815
15058
|
const diffBtn = document.createElement("button");
|
|
14816
15059
|
diffBtn.type = "button";
|
|
@@ -14820,32 +15063,89 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14820
15063
|
diffBtn.disabled = true;
|
|
14821
15064
|
diffBtn.textContent = text2().snapshot.comparing;
|
|
14822
15065
|
try {
|
|
14823
|
-
showDiffInline(beforeSelect.value, afterSelect.value);
|
|
15066
|
+
await showDiffInline(beforeSelect.value, afterSelect.value);
|
|
15067
|
+
scrollDiffIntoView();
|
|
14824
15068
|
} finally {
|
|
14825
15069
|
diffBtn.disabled = false;
|
|
14826
15070
|
diffBtn.textContent = text2().snapshot.diffManual;
|
|
14827
15071
|
}
|
|
14828
15072
|
});
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
15073
|
+
const applyDiffSelectionHighlight = () => {
|
|
15074
|
+
const items = snapshotSection.querySelectorAll(".db-snapshot-item");
|
|
15075
|
+
const before = beforeSelect.value;
|
|
15076
|
+
const after = afterSelect.value;
|
|
15077
|
+
for (const it of items) {
|
|
15078
|
+
it.classList.remove("is-before", "is-after");
|
|
15079
|
+
const old = it.querySelector(".db-snapshot-compare-badge");
|
|
15080
|
+
if (old)
|
|
15081
|
+
old.remove();
|
|
15082
|
+
}
|
|
15083
|
+
for (const it of items) {
|
|
15084
|
+
const id = it.dataset.snapshotId ?? "";
|
|
15085
|
+
let label = null;
|
|
15086
|
+
if (id === before) {
|
|
15087
|
+
it.classList.add("is-before");
|
|
15088
|
+
label = t2.compareBeforeBadge;
|
|
15089
|
+
} else if (id === after) {
|
|
15090
|
+
it.classList.add("is-after");
|
|
15091
|
+
label = t2.compareAfterBadge;
|
|
15092
|
+
}
|
|
15093
|
+
if (label) {
|
|
15094
|
+
const badge = document.createElement("span");
|
|
15095
|
+
badge.className = "db-snapshot-compare-badge";
|
|
15096
|
+
badge.textContent = label;
|
|
15097
|
+
const info = it.querySelector(".db-snapshot-info");
|
|
15098
|
+
if (info)
|
|
15099
|
+
info.appendChild(badge);
|
|
15100
|
+
}
|
|
15101
|
+
}
|
|
15102
|
+
};
|
|
15103
|
+
beforeSelect.addEventListener("change", applyDiffSelectionHighlight);
|
|
15104
|
+
afterSelect.addEventListener("change", applyDiffSelectionHighlight);
|
|
15105
|
+
applyDiffSelectionHighlight();
|
|
15106
|
+
const beforeRow = document.createElement("label");
|
|
15107
|
+
beforeRow.className = "db-snapshot-diff-row is-before";
|
|
15108
|
+
const beforeLabel = document.createElement("span");
|
|
15109
|
+
beforeLabel.className = "db-snapshot-diff-row-label";
|
|
15110
|
+
beforeLabel.textContent = t2.compareBeforeBadge;
|
|
15111
|
+
beforeRow.append(beforeLabel, beforeSelect);
|
|
15112
|
+
const afterRow = document.createElement("label");
|
|
15113
|
+
afterRow.className = "db-snapshot-diff-row is-after";
|
|
15114
|
+
const afterLabel = document.createElement("span");
|
|
15115
|
+
afterLabel.className = "db-snapshot-diff-row-label";
|
|
15116
|
+
afterLabel.textContent = t2.compareAfterBadge;
|
|
15117
|
+
afterRow.append(afterLabel, afterSelect);
|
|
15118
|
+
diffCreate.append(beforeRow, afterRow, diffBtn);
|
|
15119
|
+
stickyHeader.appendChild(diffCreate);
|
|
15120
|
+
}
|
|
15121
|
+
leftPane.append(stickyHeader, snapshotSection);
|
|
15122
|
+
}
|
|
15123
|
+
function scrollDiffIntoView() {
|
|
15124
|
+
const section = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15125
|
+
if (!section)
|
|
15126
|
+
return;
|
|
15127
|
+
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
14833
15128
|
}
|
|
14834
15129
|
async function showDiffInline(beforeId, afterId) {
|
|
14835
15130
|
currentDiff = { beforeId, afterId };
|
|
14836
|
-
|
|
14837
|
-
|
|
15131
|
+
deps.setRouteDiff?.({ before: beforeId, after: afterId });
|
|
15132
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15133
|
+
if (existing) {
|
|
15134
|
+
const prev = existing;
|
|
15135
|
+
prev.__tocObserver?.disconnect();
|
|
14838
15136
|
existing.remove();
|
|
15137
|
+
}
|
|
14839
15138
|
const loading = document.createElement("div");
|
|
14840
15139
|
loading.className = "db-snapshot-diff-inline";
|
|
14841
15140
|
const loadingText = document.createElement("div");
|
|
14842
15141
|
loadingText.className = "db-snapshot-loading";
|
|
14843
15142
|
loadingText.textContent = text2().snapshot.calculatingDiff;
|
|
14844
15143
|
loading.appendChild(loadingText);
|
|
14845
|
-
|
|
15144
|
+
rightPane.appendChild(loading);
|
|
14846
15145
|
try {
|
|
14847
|
-
const res = await
|
|
15146
|
+
const res = await getJson(`/_db/snapshot/diff/tables?before=${encodeURIComponent(beforeId)}&after=${encodeURIComponent(afterId)}`);
|
|
14848
15147
|
if (!res.ok) {
|
|
15148
|
+
currentDiff = null;
|
|
14849
15149
|
loading.innerHTML = "";
|
|
14850
15150
|
const error2 = document.createElement("div");
|
|
14851
15151
|
error2.className = "db-snapshot-error";
|
|
@@ -14857,6 +15157,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14857
15157
|
loading.remove();
|
|
14858
15158
|
renderDiffInline(beforeId, afterId, data.tables);
|
|
14859
15159
|
} catch {
|
|
15160
|
+
currentDiff = null;
|
|
14860
15161
|
loading.innerHTML = "";
|
|
14861
15162
|
const error2 = document.createElement("div");
|
|
14862
15163
|
error2.className = "db-snapshot-error";
|
|
@@ -14865,26 +15166,76 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14865
15166
|
}
|
|
14866
15167
|
}
|
|
14867
15168
|
function renderDiffInline(beforeId, afterId, tables) {
|
|
14868
|
-
const existing =
|
|
14869
|
-
if (existing)
|
|
15169
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15170
|
+
if (existing) {
|
|
15171
|
+
const prev = existing;
|
|
15172
|
+
prev.__tocObserver?.disconnect();
|
|
14870
15173
|
existing.remove();
|
|
15174
|
+
}
|
|
14871
15175
|
const section = document.createElement("div");
|
|
14872
15176
|
section.className = "db-snapshot-diff-inline";
|
|
14873
|
-
const
|
|
15177
|
+
const beforeMeta = snapshots.find((s2) => s2.id === beforeId);
|
|
15178
|
+
const afterMeta = snapshots.find((s2) => s2.id === afterId);
|
|
15179
|
+
if (beforeMeta && afterMeta) {
|
|
15180
|
+
const header = document.createElement("div");
|
|
15181
|
+
header.className = "db-snapshot-diff-header";
|
|
15182
|
+
const beforeLabel = new Date(beforeMeta.createdAt).toLocaleString();
|
|
15183
|
+
const afterLabel = new Date(afterMeta.createdAt).toLocaleString();
|
|
15184
|
+
header.textContent = text2().snapshot.diffShowing(beforeLabel, afterLabel);
|
|
15185
|
+
section.appendChild(header);
|
|
15186
|
+
}
|
|
15187
|
+
const changedTables = tables.filter((t2) => t2.insertedCount + t2.updatedCount + t2.deletedCount > 0 || t2.coverage && t2.coverage !== "both");
|
|
14874
15188
|
const unchangedCount = tables.length - changedTables.length;
|
|
14875
15189
|
const summary = document.createElement("div");
|
|
14876
15190
|
summary.className = "db-snapshot-diff-summary";
|
|
14877
15191
|
if (changedTables.length === 0) {
|
|
14878
15192
|
summary.textContent = text2().snapshot.noChanges;
|
|
14879
15193
|
section.appendChild(summary);
|
|
14880
|
-
|
|
15194
|
+
rightPane.appendChild(section);
|
|
14881
15195
|
return;
|
|
14882
15196
|
}
|
|
14883
15197
|
summary.textContent = text2().snapshot.diffSummary(changedTables.length, unchangedCount);
|
|
14884
15198
|
section.appendChild(summary);
|
|
15199
|
+
const toc = document.createElement("div");
|
|
15200
|
+
toc.className = "db-snapshot-diff-toc";
|
|
15201
|
+
const tocChips = new Map;
|
|
15202
|
+
section.appendChild(toc);
|
|
15203
|
+
function buildStatSpans(target, inserted, updated, deleted) {
|
|
15204
|
+
const make = (cls, prefix, n2) => {
|
|
15205
|
+
if (n2 <= 0)
|
|
15206
|
+
return;
|
|
15207
|
+
const s2 = document.createElement("span");
|
|
15208
|
+
s2.className = `db-snapshot-diff-stat ${cls}`;
|
|
15209
|
+
s2.textContent = `${prefix}${n2}`;
|
|
15210
|
+
target.appendChild(s2);
|
|
15211
|
+
};
|
|
15212
|
+
make("add", "+", inserted);
|
|
15213
|
+
make("upd", "~", updated);
|
|
15214
|
+
make("del", "-", deleted);
|
|
15215
|
+
}
|
|
15216
|
+
function renderCoverageOrStats(target, summary2) {
|
|
15217
|
+
const tx = text2().snapshot;
|
|
15218
|
+
if (summary2.coverage === "before-only") {
|
|
15219
|
+
const lbl = document.createElement("span");
|
|
15220
|
+
lbl.className = "db-snapshot-diff-coverage-badge before-only";
|
|
15221
|
+
lbl.textContent = tx.coverageBeforeOnly;
|
|
15222
|
+
target.appendChild(lbl);
|
|
15223
|
+
} else if (summary2.coverage === "after-only") {
|
|
15224
|
+
const lbl = document.createElement("span");
|
|
15225
|
+
lbl.className = "db-snapshot-diff-coverage-badge after-only";
|
|
15226
|
+
lbl.textContent = tx.coverageAfterOnly;
|
|
15227
|
+
target.appendChild(lbl);
|
|
15228
|
+
} else {
|
|
15229
|
+
buildStatSpans(target, summary2.insertedCount, summary2.updatedCount, summary2.deletedCount);
|
|
15230
|
+
}
|
|
15231
|
+
}
|
|
15232
|
+
const pendingLoads = [];
|
|
15233
|
+
let scrollTarget = null;
|
|
14885
15234
|
for (const t2 of changedTables) {
|
|
14886
15235
|
const tableEl = document.createElement("div");
|
|
14887
15236
|
tableEl.className = "db-snapshot-diff-table";
|
|
15237
|
+
const sectionId = `diff-table-${t2.tableName.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
15238
|
+
tableEl.id = sectionId;
|
|
14888
15239
|
const tableHeader = document.createElement("div");
|
|
14889
15240
|
tableHeader.className = "db-snapshot-diff-table-header";
|
|
14890
15241
|
const nameSpan = document.createElement("span");
|
|
@@ -14892,26 +15243,82 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14892
15243
|
nameSpan.textContent = t2.tableName;
|
|
14893
15244
|
const statsSpan = document.createElement("span");
|
|
14894
15245
|
statsSpan.className = "db-snapshot-diff-table-stats";
|
|
14895
|
-
|
|
14896
|
-
if (t2.insertedCount > 0)
|
|
14897
|
-
parts.push(`+${t2.insertedCount}`);
|
|
14898
|
-
if (t2.updatedCount > 0)
|
|
14899
|
-
parts.push(`~${t2.updatedCount}`);
|
|
14900
|
-
if (t2.deletedCount > 0)
|
|
14901
|
-
parts.push(`-${t2.deletedCount}`);
|
|
14902
|
-
statsSpan.textContent = parts.join(" ");
|
|
15246
|
+
renderCoverageOrStats(statsSpan, t2);
|
|
14903
15247
|
tableHeader.append(nameSpan, statsSpan);
|
|
14904
15248
|
tableHeader.style.cursor = "pointer";
|
|
14905
15249
|
const rowsContainer = document.createElement("div");
|
|
14906
15250
|
rowsContainer.className = "db-snapshot-diff-rows-container";
|
|
14907
|
-
|
|
15251
|
+
if (t2.coverage === "before-only" || t2.coverage === "after-only") {
|
|
15252
|
+
const tx = text2().snapshot;
|
|
15253
|
+
const note = document.createElement("div");
|
|
15254
|
+
note.className = "db-snapshot-diff-coverage-note";
|
|
15255
|
+
const rows = t2.unsnapshottedRowCount ?? 0;
|
|
15256
|
+
note.textContent = t2.coverage === "before-only" ? tx.coverageNoteBeforeOnly(rows) : tx.coverageNoteAfterOnly(rows);
|
|
15257
|
+
rowsContainer.appendChild(note);
|
|
15258
|
+
} else {
|
|
15259
|
+
pendingLoads.push(loadDiffRows(beforeId, afterId, t2.tableName, rowsContainer));
|
|
15260
|
+
}
|
|
14908
15261
|
tableHeader.addEventListener("click", () => {
|
|
14909
15262
|
rowsContainer.hidden = !rowsContainer.hidden;
|
|
14910
15263
|
});
|
|
14911
15264
|
tableEl.append(tableHeader, rowsContainer);
|
|
14912
15265
|
section.appendChild(tableEl);
|
|
15266
|
+
const chip = document.createElement("button");
|
|
15267
|
+
chip.type = "button";
|
|
15268
|
+
chip.className = "db-snapshot-diff-toc-chip";
|
|
15269
|
+
chip.dataset.targetId = sectionId;
|
|
15270
|
+
const chipName = document.createElement("span");
|
|
15271
|
+
chipName.className = "db-snapshot-diff-toc-chip-name";
|
|
15272
|
+
chipName.textContent = t2.tableName;
|
|
15273
|
+
const chipStats = document.createElement("span");
|
|
15274
|
+
chipStats.className = "db-snapshot-diff-toc-chip-stats";
|
|
15275
|
+
renderCoverageOrStats(chipStats, t2);
|
|
15276
|
+
if (t2.coverage === "before-only" || t2.coverage === "after-only") {
|
|
15277
|
+
chip.classList.add("coverage-incomplete");
|
|
15278
|
+
}
|
|
15279
|
+
chip.append(chipName, chipStats);
|
|
15280
|
+
chip.addEventListener("click", () => {
|
|
15281
|
+
scrollTarget = tableEl;
|
|
15282
|
+
tableEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
15283
|
+
});
|
|
15284
|
+
toc.appendChild(chip);
|
|
15285
|
+
tocChips.set(sectionId, chip);
|
|
15286
|
+
}
|
|
15287
|
+
Promise.allSettled(pendingLoads).then(() => {
|
|
15288
|
+
if (scrollTarget && rightPane.contains(scrollTarget)) {
|
|
15289
|
+
scrollTarget.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
15290
|
+
}
|
|
15291
|
+
});
|
|
15292
|
+
const observer = new IntersectionObserver((entries) => {
|
|
15293
|
+
const intersecting = entries.filter((e2) => e2.isIntersecting);
|
|
15294
|
+
if (intersecting.length === 0)
|
|
15295
|
+
return;
|
|
15296
|
+
intersecting.sort((a2, b2) => a2.boundingClientRect.top - b2.boundingClientRect.top);
|
|
15297
|
+
const activeId = intersecting[0].target.id;
|
|
15298
|
+
for (const [id, chip] of tocChips) {
|
|
15299
|
+
chip.classList.toggle("active", id === activeId);
|
|
15300
|
+
}
|
|
15301
|
+
const activeChip = tocChips.get(activeId);
|
|
15302
|
+
if (activeChip) {
|
|
15303
|
+
const tocBox = toc.getBoundingClientRect();
|
|
15304
|
+
const chipBox = activeChip.getBoundingClientRect();
|
|
15305
|
+
const margin = 16;
|
|
15306
|
+
if (chipBox.left < tocBox.left + margin) {
|
|
15307
|
+
toc.scrollLeft += chipBox.left - tocBox.left - margin;
|
|
15308
|
+
} else if (chipBox.right > tocBox.right - margin) {
|
|
15309
|
+
toc.scrollLeft += chipBox.right - tocBox.right + margin;
|
|
15310
|
+
}
|
|
15311
|
+
}
|
|
15312
|
+
}, {
|
|
15313
|
+
root: rightPane,
|
|
15314
|
+
rootMargin: "-48px 0px -60% 0px",
|
|
15315
|
+
threshold: 0
|
|
15316
|
+
});
|
|
15317
|
+
for (const el2 of section.querySelectorAll(".db-snapshot-diff-table")) {
|
|
15318
|
+
observer.observe(el2);
|
|
14913
15319
|
}
|
|
14914
|
-
|
|
15320
|
+
section.__tocObserver = observer;
|
|
15321
|
+
rightPane.appendChild(section);
|
|
14915
15322
|
}
|
|
14916
15323
|
async function loadDiffRows(beforeId, afterId, table2, container) {
|
|
14917
15324
|
container.innerHTML = "";
|
|
@@ -14920,7 +15327,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14920
15327
|
loading.textContent = text2().snapshot.loading;
|
|
14921
15328
|
container.appendChild(loading);
|
|
14922
15329
|
try {
|
|
14923
|
-
const res = await
|
|
15330
|
+
const res = await getJson(`/_db/snapshot/diff/rows?before=${encodeURIComponent(beforeId)}&after=${encodeURIComponent(afterId)}&table=${encodeURIComponent(table2)}&limit=200`);
|
|
14924
15331
|
if (!res.ok) {
|
|
14925
15332
|
container.innerHTML = "";
|
|
14926
15333
|
const error2 = document.createElement("div");
|
|
@@ -15069,7 +15476,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15069
15476
|
if (item) {
|
|
15070
15477
|
item.after(dialog);
|
|
15071
15478
|
} else {
|
|
15072
|
-
|
|
15479
|
+
leftPane.prepend(dialog);
|
|
15073
15480
|
}
|
|
15074
15481
|
input.focus();
|
|
15075
15482
|
}
|
|
@@ -15082,6 +15489,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15082
15489
|
refresh();
|
|
15083
15490
|
}
|
|
15084
15491
|
function handleSse(data) {
|
|
15492
|
+
if (disposed)
|
|
15493
|
+
return;
|
|
15085
15494
|
try {
|
|
15086
15495
|
const parsed = JSON.parse(data);
|
|
15087
15496
|
const dbId = deps.getDbId();
|
|
@@ -15090,21 +15499,57 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15090
15499
|
return;
|
|
15091
15500
|
if (parsed.schema && parsed.schema !== schema)
|
|
15092
15501
|
return;
|
|
15093
|
-
|
|
15094
|
-
|
|
15502
|
+
const id = parsed.id ?? null;
|
|
15503
|
+
if (parsed.action === "started" && id) {
|
|
15504
|
+
upsertJob(id, {
|
|
15505
|
+
done: false,
|
|
15506
|
+
total: parsed.total ?? 0,
|
|
15507
|
+
index: 0
|
|
15508
|
+
});
|
|
15509
|
+
}
|
|
15510
|
+
if (parsed.action === "progress" && id) {
|
|
15511
|
+
upsertJob(id, {
|
|
15512
|
+
table: parsed.table,
|
|
15513
|
+
index: parsed.index,
|
|
15514
|
+
total: parsed.total ?? 0,
|
|
15515
|
+
done: parsed.done === true
|
|
15516
|
+
});
|
|
15095
15517
|
}
|
|
15096
|
-
if (parsed.action === "created"
|
|
15097
|
-
|
|
15518
|
+
if (parsed.action === "created" && id) {
|
|
15519
|
+
upsertJob(id, { done: true });
|
|
15520
|
+
scheduleJobRemove(id);
|
|
15098
15521
|
refreshAndAutoDiff();
|
|
15099
15522
|
}
|
|
15523
|
+
if (parsed.action === "aborted" && id) {
|
|
15524
|
+
upsertJob(id, { done: true, aborted: true });
|
|
15525
|
+
scheduleJobRemove(id);
|
|
15526
|
+
refresh();
|
|
15527
|
+
}
|
|
15528
|
+
if (parsed.action === "error" && id) {
|
|
15529
|
+
upsertJob(id, {
|
|
15530
|
+
done: true,
|
|
15531
|
+
errorMessage: parsed.error || text2().snapshot.failed
|
|
15532
|
+
});
|
|
15533
|
+
scheduleJobRemove(id);
|
|
15534
|
+
refresh();
|
|
15535
|
+
}
|
|
15100
15536
|
} catch {}
|
|
15101
15537
|
}
|
|
15102
15538
|
function dispose() {
|
|
15103
15539
|
disposed = true;
|
|
15104
|
-
setActiveSnapshotId(null);
|
|
15105
15540
|
for (const timer of autoRefreshTimers)
|
|
15106
15541
|
clearTimeout(timer);
|
|
15107
15542
|
autoRefreshTimers.clear();
|
|
15543
|
+
for (const timer of jobClearTimers.values())
|
|
15544
|
+
clearTimeout(timer);
|
|
15545
|
+
jobClearTimers.clear();
|
|
15546
|
+
activeJobs.clear();
|
|
15547
|
+
cancellingJobs.clear();
|
|
15548
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15549
|
+
if (existing) {
|
|
15550
|
+
const prev = existing;
|
|
15551
|
+
prev.__tocObserver?.disconnect();
|
|
15552
|
+
}
|
|
15108
15553
|
}
|
|
15109
15554
|
function localize() {
|
|
15110
15555
|
const t2 = text2().snapshot;
|
|
@@ -15112,8 +15557,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15112
15557
|
guideBody.textContent = t2.guideBody;
|
|
15113
15558
|
createBtn.textContent = t2.create;
|
|
15114
15559
|
refreshBtn.textContent = t2.refresh;
|
|
15115
|
-
|
|
15116
|
-
snapshotCancelBtn.textContent = t2.cancel;
|
|
15560
|
+
renderProgress();
|
|
15117
15561
|
tableSelectorHeader.textContent = t2.selectTablesHeader;
|
|
15118
15562
|
selectAllBtn.textContent = t2.selectAll;
|
|
15119
15563
|
deselectAllBtn.textContent = t2.deselectAll;
|
|
@@ -15122,21 +15566,54 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15122
15566
|
if (!confirmBtn.disabled)
|
|
15123
15567
|
confirmBtn.textContent = t2.confirm;
|
|
15124
15568
|
cancelBtn.textContent = t2.cancel;
|
|
15125
|
-
const prevSelects =
|
|
15569
|
+
const prevSelects = leftPane.querySelectorAll(".db-snapshot-diff-select");
|
|
15126
15570
|
const prevBeforeVal = prevSelects[0]?.value;
|
|
15127
15571
|
const prevAfterVal = prevSelects[1]?.value;
|
|
15128
|
-
const hadDiff =
|
|
15572
|
+
const hadDiff = rightPane.querySelector(".db-snapshot-diff-inline") != null;
|
|
15129
15573
|
renderMain();
|
|
15130
|
-
const newSelects =
|
|
15131
|
-
if (newSelects[0] && prevBeforeVal)
|
|
15574
|
+
const newSelects = leftPane.querySelectorAll(".db-snapshot-diff-select");
|
|
15575
|
+
if (newSelects[0] && prevBeforeVal) {
|
|
15132
15576
|
newSelects[0].value = prevBeforeVal;
|
|
15133
|
-
|
|
15577
|
+
newSelects[0].dispatchEvent(new Event("change"));
|
|
15578
|
+
}
|
|
15579
|
+
if (newSelects[1] && prevAfterVal) {
|
|
15134
15580
|
newSelects[1].value = prevAfterVal;
|
|
15581
|
+
newSelects[1].dispatchEvent(new Event("change"));
|
|
15582
|
+
}
|
|
15135
15583
|
if (hadDiff && currentDiff) {
|
|
15136
15584
|
showDiffInline(currentDiff.beforeId, currentDiff.afterId);
|
|
15137
15585
|
}
|
|
15138
15586
|
}
|
|
15139
|
-
|
|
15587
|
+
async function restoreDiffFromRoute() {
|
|
15588
|
+
const target = deps.getRouteDiff?.();
|
|
15589
|
+
if (!target || !target.before || !target.after)
|
|
15590
|
+
return;
|
|
15591
|
+
if (currentDiff && currentDiff.beforeId === target.before && currentDiff.afterId === target.after) {
|
|
15592
|
+
return;
|
|
15593
|
+
}
|
|
15594
|
+
if (snapshots.length === 0) {
|
|
15595
|
+
await refresh();
|
|
15596
|
+
}
|
|
15597
|
+
if (disposed)
|
|
15598
|
+
return;
|
|
15599
|
+
const hasBefore = snapshots.some((s2) => s2.id === target.before);
|
|
15600
|
+
const hasAfter = snapshots.some((s2) => s2.id === target.after);
|
|
15601
|
+
if (!hasBefore || !hasAfter) {
|
|
15602
|
+
deps.setRouteDiff?.(null);
|
|
15603
|
+
return;
|
|
15604
|
+
}
|
|
15605
|
+
await showDiffInline(target.before, target.after);
|
|
15606
|
+
}
|
|
15607
|
+
return {
|
|
15608
|
+
el,
|
|
15609
|
+
refresh,
|
|
15610
|
+
handleSse,
|
|
15611
|
+
dispose,
|
|
15612
|
+
localize,
|
|
15613
|
+
restoreDiffFromRoute: () => {
|
|
15614
|
+
restoreDiffFromRoute();
|
|
15615
|
+
}
|
|
15616
|
+
};
|
|
15140
15617
|
}
|
|
15141
15618
|
|
|
15142
15619
|
// web-src/views/database/table-grid.ts
|
|
@@ -17377,6 +17854,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
17377
17854
|
onClick: () => {
|
|
17378
17855
|
setActiveTab("snapshot");
|
|
17379
17856
|
snapshotView.refresh();
|
|
17857
|
+
snapshotView.restoreDiffFromRoute();
|
|
17380
17858
|
}
|
|
17381
17859
|
});
|
|
17382
17860
|
const disposeInferFkPrefSub = outerDeps.onDbUiPrefChange(() => {
|
|
@@ -17481,7 +17959,45 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
17481
17959
|
getDbId: () => currentDbInfo?.id || null,
|
|
17482
17960
|
getSchema: () => currentSchema,
|
|
17483
17961
|
getTables: () => schemaCache?.tables || [],
|
|
17484
|
-
getText: () => paneText()
|
|
17962
|
+
getText: () => paneText(),
|
|
17963
|
+
trackLoad: deps.trackLoad,
|
|
17964
|
+
getStoredSelectedTables: () => {
|
|
17965
|
+
if (!currentDbInfo)
|
|
17966
|
+
return;
|
|
17967
|
+
const scope = dbUiTableExpansionScope(currentDbInfo.id, currentSchema);
|
|
17968
|
+
return outerDeps.getSnapshotSelectedTables(scope);
|
|
17969
|
+
},
|
|
17970
|
+
setStoredSelectedTables: (tables) => {
|
|
17971
|
+
if (!currentDbInfo)
|
|
17972
|
+
return;
|
|
17973
|
+
const scope = dbUiTableExpansionScope(currentDbInfo.id, currentSchema);
|
|
17974
|
+
outerDeps.setSnapshotSelectedTables(scope, tables);
|
|
17975
|
+
},
|
|
17976
|
+
getRouteDiff: () => {
|
|
17977
|
+
const parsed = parseRoute(window.location.pathname, window.location.search, deps.currentRange());
|
|
17978
|
+
if (parsed.screen !== "database")
|
|
17979
|
+
return;
|
|
17980
|
+
const before = parsed.diffBefore;
|
|
17981
|
+
const after = parsed.diffAfter;
|
|
17982
|
+
if (!before && !after)
|
|
17983
|
+
return;
|
|
17984
|
+
return { before, after };
|
|
17985
|
+
},
|
|
17986
|
+
setRouteDiff: (diff) => {
|
|
17987
|
+
const dbId = currentDbInfo?.id;
|
|
17988
|
+
if (!dbId)
|
|
17989
|
+
return;
|
|
17990
|
+
deps.setRoute({
|
|
17991
|
+
screen: "database",
|
|
17992
|
+
db: dbId,
|
|
17993
|
+
schema: currentSchema ?? undefined,
|
|
17994
|
+
table: currentTable ?? undefined,
|
|
17995
|
+
tab: "snapshot",
|
|
17996
|
+
...diff?.before ? { diffBefore: diff.before } : {},
|
|
17997
|
+
...diff?.after ? { diffAfter: diff.after } : {},
|
|
17998
|
+
range: deps.currentRange()
|
|
17999
|
+
}, true);
|
|
18000
|
+
}
|
|
17485
18001
|
});
|
|
17486
18002
|
const historyView = createQueryHistoryView({
|
|
17487
18003
|
getDbId: () => currentDbInfo?.id || null,
|
|
@@ -18337,6 +18853,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18337
18853
|
renderErDiagram();
|
|
18338
18854
|
} else if (normalizedView === "snapshot") {
|
|
18339
18855
|
snapshotView.refresh();
|
|
18856
|
+
snapshotView.restoreDiffFromRoute();
|
|
18340
18857
|
}
|
|
18341
18858
|
}
|
|
18342
18859
|
applyAnnotationTarget(options.annotationTarget);
|
|
@@ -18610,33 +19127,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18610
19127
|
return;
|
|
18611
19128
|
});
|
|
18612
19129
|
}
|
|
18613
|
-
function
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
const expandedTables = { ...dbUiState.expandedTables || {} };
|
|
18618
|
-
const tables = new Set(expandedTables[scopeKey] || []);
|
|
18619
|
-
if (expanded)
|
|
18620
|
-
tables.add(table2);
|
|
18621
|
-
else
|
|
18622
|
-
tables.delete(table2);
|
|
19130
|
+
function updateScopedTableList(stateKey, scopeKey, mutate) {
|
|
19131
|
+
const current = { ...dbUiState[stateKey] || {} };
|
|
19132
|
+
const tables = new Set(current[scopeKey] || []);
|
|
19133
|
+
mutate(tables);
|
|
18623
19134
|
const nextTables = [...tables].sort((a2, b2) => a2.localeCompare(b2));
|
|
18624
19135
|
if (nextTables.length > 0)
|
|
18625
|
-
|
|
19136
|
+
current[scopeKey] = nextTables;
|
|
18626
19137
|
else
|
|
18627
|
-
delete
|
|
18628
|
-
const
|
|
19138
|
+
delete current[scopeKey];
|
|
19139
|
+
const nextMap = Object.keys(current).length > 0 ? current : undefined;
|
|
18629
19140
|
const nextState = { ...dbUiState, version: 1 };
|
|
18630
|
-
if (
|
|
18631
|
-
nextState
|
|
19141
|
+
if (nextMap)
|
|
19142
|
+
nextState[stateKey] = nextMap;
|
|
18632
19143
|
else
|
|
18633
|
-
delete nextState
|
|
19144
|
+
delete nextState[stateKey];
|
|
18634
19145
|
applyDbUiState(nextState);
|
|
18635
19146
|
fetch("/_db/ui", {
|
|
18636
19147
|
method: "PATCH",
|
|
18637
19148
|
headers: actionHeaders(),
|
|
18638
19149
|
body: JSON.stringify({
|
|
18639
|
-
|
|
19150
|
+
[stateKey]: {
|
|
18640
19151
|
[scopeKey]: nextTables.length > 0 ? nextTables : null
|
|
18641
19152
|
}
|
|
18642
19153
|
})
|
|
@@ -18644,6 +19155,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18644
19155
|
return;
|
|
18645
19156
|
});
|
|
18646
19157
|
}
|
|
19158
|
+
function getExpandedTables(scopeKey) {
|
|
19159
|
+
return [...dbUiState.expandedTables?.[scopeKey] || []];
|
|
19160
|
+
}
|
|
19161
|
+
function setExpandedTable(scopeKey, table2, expanded) {
|
|
19162
|
+
updateScopedTableList("expandedTables", scopeKey, (tables) => {
|
|
19163
|
+
if (expanded)
|
|
19164
|
+
tables.add(table2);
|
|
19165
|
+
else
|
|
19166
|
+
tables.delete(table2);
|
|
19167
|
+
});
|
|
19168
|
+
}
|
|
19169
|
+
function getSnapshotSelectedTables(scopeKey) {
|
|
19170
|
+
return [...dbUiState.snapshotSelectedTables?.[scopeKey] || []];
|
|
19171
|
+
}
|
|
19172
|
+
function setSnapshotSelectedTables(scopeKey, tables) {
|
|
19173
|
+
updateScopedTableList("snapshotSelectedTables", scopeKey, (set2) => {
|
|
19174
|
+
set2.clear();
|
|
19175
|
+
for (const t2 of tables)
|
|
19176
|
+
set2.add(t2);
|
|
19177
|
+
});
|
|
19178
|
+
}
|
|
18647
19179
|
const dbUiPrefListeners = new Set;
|
|
18648
19180
|
function applyDbUiState(next) {
|
|
18649
19181
|
dbUiState = next;
|
|
@@ -18865,12 +19397,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18865
19397
|
if (!entry)
|
|
18866
19398
|
return;
|
|
18867
19399
|
const s2 = entry.pane.getState();
|
|
19400
|
+
const isSnapshot = s2.view === "snapshot";
|
|
19401
|
+
let diffBefore;
|
|
19402
|
+
let diffAfter;
|
|
19403
|
+
if (isSnapshot) {
|
|
19404
|
+
const url = new URL(window.location.href);
|
|
19405
|
+
diffBefore = url.searchParams.get("diffBefore") ?? undefined;
|
|
19406
|
+
diffAfter = url.searchParams.get("diffAfter") ?? undefined;
|
|
19407
|
+
}
|
|
18868
19408
|
deps.setRoute({
|
|
18869
19409
|
screen: "database",
|
|
18870
19410
|
db: s2.dbId ?? undefined,
|
|
18871
19411
|
schema: s2.schema ?? undefined,
|
|
18872
19412
|
table: s2.table ?? undefined,
|
|
18873
19413
|
tab: s2.view === "data" ? undefined : s2.view,
|
|
19414
|
+
...diffBefore ? { diffBefore } : {},
|
|
19415
|
+
...diffAfter ? { diffAfter } : {},
|
|
18874
19416
|
range: deps.currentRange()
|
|
18875
19417
|
}, true);
|
|
18876
19418
|
}
|
|
@@ -19120,6 +19662,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19120
19662
|
setColumnWidths,
|
|
19121
19663
|
getExpandedTables,
|
|
19122
19664
|
setExpandedTable,
|
|
19665
|
+
getSnapshotSelectedTables,
|
|
19666
|
+
setSnapshotSelectedTables,
|
|
19123
19667
|
getDbUiPref,
|
|
19124
19668
|
setDbUiPref,
|
|
19125
19669
|
onDbUiPrefChange,
|