@youtyan/code-viewer 0.2.3 → 0.2.5
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 -5
- package/dist/code-viewer.js +2951 -1436
- package/package.json +11 -1
- package/web/app.js +1390 -248
- package/web/index.html +2 -2
- package/web/style.css +232 -4
package/web/app.js
CHANGED
|
@@ -633,12 +633,14 @@
|
|
|
633
633
|
}
|
|
634
634
|
case "/database": {
|
|
635
635
|
const db = params.get("db") || undefined;
|
|
636
|
+
const schema = params.get("schema") || undefined;
|
|
636
637
|
const table = params.get("table") || undefined;
|
|
637
638
|
const tabRaw = params.get("tab");
|
|
638
639
|
const tab = tabRaw === "data" || tabRaw === "query" || tabRaw === "schema" || tabRaw === "er" || tabRaw === "search" || tabRaw === "snapshot" ? tabRaw : undefined;
|
|
639
640
|
return {
|
|
640
641
|
screen: "database",
|
|
641
642
|
...db ? { db } : {},
|
|
643
|
+
...schema ? { schema } : {},
|
|
642
644
|
...table ? { table } : {},
|
|
643
645
|
...tab ? { tab } : {},
|
|
644
646
|
range
|
|
@@ -694,6 +696,8 @@
|
|
|
694
696
|
const params = new URLSearchParams;
|
|
695
697
|
if (route.db)
|
|
696
698
|
params.set("db", route.db);
|
|
699
|
+
if (route.schema)
|
|
700
|
+
params.set("schema", route.schema);
|
|
697
701
|
if (route.table)
|
|
698
702
|
params.set("table", route.table);
|
|
699
703
|
if (route.tab)
|
|
@@ -6696,7 +6700,7 @@
|
|
|
6696
6700
|
}
|
|
6697
6701
|
return resolved.join("/");
|
|
6698
6702
|
}
|
|
6699
|
-
function createMarkdownIt(target, highlighter, signal) {
|
|
6703
|
+
function createMarkdownIt(target, highlighter, signal, resolveAssetUrl) {
|
|
6700
6704
|
const md = new lib_default({
|
|
6701
6705
|
html: false,
|
|
6702
6706
|
linkify: true,
|
|
@@ -6761,8 +6765,11 @@
|
|
|
6761
6765
|
const token = tokens[idx];
|
|
6762
6766
|
const src = token.attrGet("src") || "";
|
|
6763
6767
|
const resolved = resolveMarkdownAssetPath(target.path, src);
|
|
6764
|
-
if (resolved)
|
|
6765
|
-
|
|
6768
|
+
if (resolved) {
|
|
6769
|
+
const assetUrl = resolveAssetUrl ? resolveAssetUrl(resolved, src) : buildRawFileUrl({ path: resolved, ref: target.ref || "worktree" });
|
|
6770
|
+
if (assetUrl)
|
|
6771
|
+
token.attrSet("src", assetUrl);
|
|
6772
|
+
}
|
|
6766
6773
|
token.attrSet("loading", "lazy");
|
|
6767
6774
|
return image2(tokens, idx, options, env, self);
|
|
6768
6775
|
};
|
|
@@ -6792,7 +6799,7 @@
|
|
|
6792
6799
|
markdown.className = "gdp-markdown-preview markdown-body";
|
|
6793
6800
|
if (options.signal?.aborted)
|
|
6794
6801
|
return markdown;
|
|
6795
|
-
markdown.innerHTML = renderMarkdownHtml(textValue, target, highlighter, options.signal);
|
|
6802
|
+
markdown.innerHTML = renderMarkdownHtml(textValue, target, highlighter, options.signal, options.resolveAssetUrl);
|
|
6796
6803
|
if (options.signal?.aborted)
|
|
6797
6804
|
return markdown;
|
|
6798
6805
|
enhanceTaskLists(markdown);
|
|
@@ -6808,8 +6815,8 @@
|
|
|
6808
6815
|
wireMarkdownInteractions(markdown, target, options);
|
|
6809
6816
|
return markdown;
|
|
6810
6817
|
}
|
|
6811
|
-
function renderMarkdownHtml(textValue, target, highlighter, signal) {
|
|
6812
|
-
const md = createMarkdownIt(target, highlighter, signal);
|
|
6818
|
+
function renderMarkdownHtml(textValue, target, highlighter, signal, resolveAssetUrl) {
|
|
6819
|
+
const md = createMarkdownIt(target, highlighter, signal, resolveAssetUrl);
|
|
6813
6820
|
const frontmatter = splitYamlFrontmatter(textValue);
|
|
6814
6821
|
if (!frontmatter)
|
|
6815
6822
|
return md.render(textValue);
|
|
@@ -7300,9 +7307,11 @@ ${frontmatter.yaml}
|
|
|
7300
7307
|
}
|
|
7301
7308
|
function annotationLocationLabel(entry) {
|
|
7302
7309
|
if (entry.target?.kind === "database") {
|
|
7303
|
-
const parts = ["
|
|
7310
|
+
const parts = ["Datastores"];
|
|
7304
7311
|
if (entry.target.db)
|
|
7305
7312
|
parts.push(entry.target.db);
|
|
7313
|
+
if (entry.target.schema)
|
|
7314
|
+
parts.push(entry.target.schema);
|
|
7306
7315
|
if (entry.target.table)
|
|
7307
7316
|
parts.push(entry.target.table);
|
|
7308
7317
|
if (entry.target.tab)
|
|
@@ -7326,6 +7335,8 @@ ${frontmatter.yaml}
|
|
|
7326
7335
|
const target = entry.target;
|
|
7327
7336
|
if (target.db && target.db !== route.db)
|
|
7328
7337
|
return false;
|
|
7338
|
+
if (target.schema && target.schema !== route.schema)
|
|
7339
|
+
return false;
|
|
7329
7340
|
if (target.table && target.table !== route.table)
|
|
7330
7341
|
return false;
|
|
7331
7342
|
if (target.tab && target.tab !== (route.tab || "data"))
|
|
@@ -7404,7 +7415,7 @@ ${frontmatter.yaml}
|
|
|
7404
7415
|
return;
|
|
7405
7416
|
const strip = document.createElement("section");
|
|
7406
7417
|
strip.className = "gdp-db-annotation-strip";
|
|
7407
|
-
strip.setAttribute("aria-label", "
|
|
7418
|
+
strip.setAttribute("aria-label", "Datastore annotations");
|
|
7408
7419
|
for (const entry of matches) {
|
|
7409
7420
|
strip.appendChild(buildDatabaseAnnotationBlock(entry));
|
|
7410
7421
|
}
|
|
@@ -7647,7 +7658,9 @@ ${frontmatter.yaml}
|
|
|
7647
7658
|
return firstLine.length > 90 ? `${firstLine.slice(0, 90)}…` : firstLine;
|
|
7648
7659
|
}
|
|
7649
7660
|
function databaseAnnotationTitle(target) {
|
|
7650
|
-
const parts = [target.table || target.db || "
|
|
7661
|
+
const parts = [target.table || target.schema || target.db || "Datastores"];
|
|
7662
|
+
if (target.schema && target.table)
|
|
7663
|
+
parts.unshift(target.schema);
|
|
7651
7664
|
if (target.tab === "data" && target.data?.search)
|
|
7652
7665
|
parts.push(`search: ${target.data.search}`);
|
|
7653
7666
|
else if (target.tab === "query" && target.query?.sql)
|
|
@@ -7659,7 +7672,7 @@ ${frontmatter.yaml}
|
|
|
7659
7672
|
return parts.join(" / ");
|
|
7660
7673
|
}
|
|
7661
7674
|
function openDatabaseCaptureForm(target) {
|
|
7662
|
-
$("#annotation-detail-session").textContent = activeSessionId || "
|
|
7675
|
+
$("#annotation-detail-session").textContent = activeSessionId || "Datastore annotations";
|
|
7663
7676
|
$("#annotation-detail-step").textContent = "new";
|
|
7664
7677
|
const location2 = $("#annotation-detail-location");
|
|
7665
7678
|
location2.textContent = databaseAnnotationTitle(target);
|
|
@@ -7700,7 +7713,7 @@ ${frontmatter.yaml}
|
|
|
7700
7713
|
body: JSON.stringify({
|
|
7701
7714
|
action: "add",
|
|
7702
7715
|
session_id: activeSessionId || undefined,
|
|
7703
|
-
session_title: activeSessionId ? undefined : "
|
|
7716
|
+
session_title: activeSessionId ? undefined : "Datastore annotations",
|
|
7704
7717
|
target,
|
|
7705
7718
|
title: titleInput.value,
|
|
7706
7719
|
body: bodyInput.value
|
|
@@ -7954,6 +7967,7 @@ ${frontmatter.yaml}
|
|
|
7954
7967
|
deps.setRoute({
|
|
7955
7968
|
screen: "database",
|
|
7956
7969
|
db: target.db,
|
|
7970
|
+
schema: target.schema,
|
|
7957
7971
|
table: target.table,
|
|
7958
7972
|
tab: target.tab,
|
|
7959
7973
|
range: deps.currentRange()
|
|
@@ -8137,6 +8151,35 @@ ${frontmatter.yaml}
|
|
|
8137
8151
|
return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
8138
8152
|
}
|
|
8139
8153
|
|
|
8154
|
+
// web-src/views/database/abort-guard.ts
|
|
8155
|
+
function createAbortGuard() {
|
|
8156
|
+
let active = null;
|
|
8157
|
+
let runId = 0;
|
|
8158
|
+
return {
|
|
8159
|
+
start() {
|
|
8160
|
+
active?.abort();
|
|
8161
|
+
const abort = new AbortController;
|
|
8162
|
+
active = abort;
|
|
8163
|
+
const requestRunId = ++runId;
|
|
8164
|
+
return {
|
|
8165
|
+
signal: abort.signal,
|
|
8166
|
+
isStale() {
|
|
8167
|
+
return abort.signal.aborted || requestRunId !== runId;
|
|
8168
|
+
},
|
|
8169
|
+
finish() {
|
|
8170
|
+
if (active === abort)
|
|
8171
|
+
active = null;
|
|
8172
|
+
}
|
|
8173
|
+
};
|
|
8174
|
+
},
|
|
8175
|
+
dispose() {
|
|
8176
|
+
runId++;
|
|
8177
|
+
active?.abort();
|
|
8178
|
+
active = null;
|
|
8179
|
+
}
|
|
8180
|
+
};
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8140
8183
|
// web-src/views/media-embed.ts
|
|
8141
8184
|
var MEDIA_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico|mp4|webm|mov|mp3|wav|ogg|flac|m4a|aac|opus)(\?.*)?$/i;
|
|
8142
8185
|
var IMAGE_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i;
|
|
@@ -8580,35 +8623,6 @@ ${frontmatter.yaml}
|
|
|
8580
8623
|
return fallback.charAt(0).toUpperCase() + fallback.slice(1);
|
|
8581
8624
|
}
|
|
8582
8625
|
|
|
8583
|
-
// web-src/views/database/abort-guard.ts
|
|
8584
|
-
function createAbortGuard() {
|
|
8585
|
-
let active = null;
|
|
8586
|
-
let runId = 0;
|
|
8587
|
-
return {
|
|
8588
|
-
start() {
|
|
8589
|
-
active?.abort("abort-guard:start");
|
|
8590
|
-
const abort = new AbortController;
|
|
8591
|
-
active = abort;
|
|
8592
|
-
const requestRunId = ++runId;
|
|
8593
|
-
return {
|
|
8594
|
-
signal: abort.signal,
|
|
8595
|
-
isStale() {
|
|
8596
|
-
return abort.signal.aborted || requestRunId !== runId;
|
|
8597
|
-
},
|
|
8598
|
-
finish() {
|
|
8599
|
-
if (active === abort)
|
|
8600
|
-
active = null;
|
|
8601
|
-
}
|
|
8602
|
-
};
|
|
8603
|
-
},
|
|
8604
|
-
dispose() {
|
|
8605
|
-
runId++;
|
|
8606
|
-
active?.abort("abort-guard:dispose");
|
|
8607
|
-
active = null;
|
|
8608
|
-
}
|
|
8609
|
-
};
|
|
8610
|
-
}
|
|
8611
|
-
|
|
8612
8626
|
// web-src/views/database/pane-status.ts
|
|
8613
8627
|
function setPaneStatus(el, message, options = {}) {
|
|
8614
8628
|
el.innerHTML = "";
|
|
@@ -8692,6 +8706,10 @@ ${frontmatter.yaml}
|
|
|
8692
8706
|
let suppressNotify = false;
|
|
8693
8707
|
let queryNotifyTimer = null;
|
|
8694
8708
|
let disposed = false;
|
|
8709
|
+
let activeIndexRow = null;
|
|
8710
|
+
let activeDocRow = null;
|
|
8711
|
+
const indexRowsByName = new Map;
|
|
8712
|
+
const docRowsById = new Map;
|
|
8695
8713
|
const indexGuard = createAbortGuard();
|
|
8696
8714
|
const mappingGuard = createAbortGuard();
|
|
8697
8715
|
const docsGuard = createAbortGuard();
|
|
@@ -8717,14 +8735,17 @@ ${frontmatter.yaml}
|
|
|
8717
8735
|
}
|
|
8718
8736
|
function renderIndices(indices) {
|
|
8719
8737
|
indexList.innerHTML = "";
|
|
8738
|
+
indexRowsByName.clear();
|
|
8720
8739
|
if (indices.length === 0) {
|
|
8721
8740
|
setIndexStatus("(no indices)");
|
|
8722
8741
|
return;
|
|
8723
8742
|
}
|
|
8743
|
+
const fragment = document.createDocumentFragment();
|
|
8724
8744
|
for (const ix of indices) {
|
|
8725
8745
|
const item = document.createElement("div");
|
|
8726
8746
|
item.className = "es-index-item";
|
|
8727
8747
|
item.dataset.indexName = ix.name;
|
|
8748
|
+
indexRowsByName.set(ix.name, item);
|
|
8728
8749
|
const name = document.createElement("span");
|
|
8729
8750
|
name.className = "es-index-name";
|
|
8730
8751
|
name.textContent = ix.name;
|
|
@@ -8733,20 +8754,24 @@ ${frontmatter.yaml}
|
|
|
8733
8754
|
meta.className = "es-index-meta";
|
|
8734
8755
|
meta.textContent = `${ix.docCount.toLocaleString()} docs / ${formatBytes(ix.sizeBytes)}`;
|
|
8735
8756
|
item.append(name, meta);
|
|
8736
|
-
|
|
8737
|
-
indexList.appendChild(item);
|
|
8757
|
+
fragment.appendChild(item);
|
|
8738
8758
|
}
|
|
8759
|
+
indexList.appendChild(fragment);
|
|
8739
8760
|
}
|
|
8740
8761
|
function highlightActiveIndex(name) {
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8762
|
+
if (activeIndexRow?.dataset.indexName === name)
|
|
8763
|
+
return;
|
|
8764
|
+
activeIndexRow?.classList.remove("active");
|
|
8765
|
+
activeIndexRow = indexRowsByName.get(name) ?? null;
|
|
8766
|
+
activeIndexRow?.classList.add("active");
|
|
8744
8767
|
}
|
|
8745
8768
|
function appendDocs(hits) {
|
|
8769
|
+
const fragment = document.createDocumentFragment();
|
|
8746
8770
|
for (const hit of hits) {
|
|
8747
8771
|
const row = document.createElement("div");
|
|
8748
8772
|
row.className = "es-doc-item";
|
|
8749
8773
|
row.dataset.docId = hit._id;
|
|
8774
|
+
docRowsById.set(hit._id, row);
|
|
8750
8775
|
const id = document.createElement("span");
|
|
8751
8776
|
id.className = "es-doc-id";
|
|
8752
8777
|
id.textContent = hit._id;
|
|
@@ -8755,24 +8780,38 @@ ${frontmatter.yaml}
|
|
|
8755
8780
|
preview.className = "es-doc-preview";
|
|
8756
8781
|
preview.textContent = previewSource(hit._source);
|
|
8757
8782
|
row.append(id, preview);
|
|
8758
|
-
|
|
8759
|
-
docList.appendChild(row);
|
|
8783
|
+
fragment.appendChild(row);
|
|
8760
8784
|
}
|
|
8785
|
+
docList.appendChild(fragment);
|
|
8761
8786
|
}
|
|
8762
8787
|
function previewSource(src) {
|
|
8763
8788
|
if (src === null || src === undefined)
|
|
8764
8789
|
return "";
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8790
|
+
if (Array.isArray(src))
|
|
8791
|
+
return `array(${src.length})`;
|
|
8792
|
+
if (typeof src === "object") {
|
|
8793
|
+
const entries = Object.entries(src).slice(0, 6);
|
|
8794
|
+
const parts = entries.map(([key, value]) => {
|
|
8795
|
+
if (value === null)
|
|
8796
|
+
return `${key}=null`;
|
|
8797
|
+
if (Array.isArray(value))
|
|
8798
|
+
return `${key}=array(${value.length})`;
|
|
8799
|
+
if (typeof value === "object")
|
|
8800
|
+
return `${key}=object`;
|
|
8801
|
+
const text3 = String(value);
|
|
8802
|
+
return `${key}=${text3.length > 40 ? `${text3.slice(0, 40)}...` : text3}`;
|
|
8803
|
+
});
|
|
8804
|
+
return parts.join(" / ");
|
|
8770
8805
|
}
|
|
8806
|
+
const text2 = String(src);
|
|
8807
|
+
return text2.length > 200 ? `${text2.slice(0, 200)}...` : text2;
|
|
8771
8808
|
}
|
|
8772
8809
|
function highlightActiveDoc(id) {
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8810
|
+
if (activeDocRow?.dataset.docId === id)
|
|
8811
|
+
return;
|
|
8812
|
+
activeDocRow?.classList.remove("active");
|
|
8813
|
+
activeDocRow = docRowsById.get(id) ?? null;
|
|
8814
|
+
activeDocRow?.classList.add("active");
|
|
8776
8815
|
}
|
|
8777
8816
|
function setDetailTab(tab) {
|
|
8778
8817
|
detailTab = tab;
|
|
@@ -8902,6 +8941,8 @@ ${frontmatter.yaml}
|
|
|
8902
8941
|
if (!append) {
|
|
8903
8942
|
lastSort = undefined;
|
|
8904
8943
|
setDocStatus("Loading docs...");
|
|
8944
|
+
docRowsById.clear();
|
|
8945
|
+
activeDocRow = null;
|
|
8905
8946
|
}
|
|
8906
8947
|
try {
|
|
8907
8948
|
const params = new URLSearchParams({
|
|
@@ -8927,8 +8968,10 @@ ${frontmatter.yaml}
|
|
|
8927
8968
|
if (disposed || slot.isStale() || requestRunId !== loadRunId || requestDbId !== currentDbId || requestIndex !== currentIndex || data.index !== requestIndex || requestQuery !== currentQuery) {
|
|
8928
8969
|
return;
|
|
8929
8970
|
}
|
|
8930
|
-
if (!append)
|
|
8971
|
+
if (!append) {
|
|
8931
8972
|
docList.innerHTML = "";
|
|
8973
|
+
docRowsById.clear();
|
|
8974
|
+
}
|
|
8932
8975
|
if (data.hits.length === 0 && !append) {
|
|
8933
8976
|
setDocStatus("(no docs)");
|
|
8934
8977
|
} else {
|
|
@@ -9028,6 +9071,22 @@ ${frontmatter.yaml}
|
|
|
9028
9071
|
}, 300);
|
|
9029
9072
|
});
|
|
9030
9073
|
docMoreBtn.addEventListener("click", () => loadDocs(true));
|
|
9074
|
+
indexList.addEventListener("click", (e2) => {
|
|
9075
|
+
const row = e2.target?.closest(".es-index-item");
|
|
9076
|
+
if (!row || !indexList.contains(row))
|
|
9077
|
+
return;
|
|
9078
|
+
const name = row.dataset.indexName;
|
|
9079
|
+
if (name)
|
|
9080
|
+
selectIndex(name);
|
|
9081
|
+
});
|
|
9082
|
+
docList.addEventListener("click", (e2) => {
|
|
9083
|
+
const row = e2.target?.closest(".es-doc-item");
|
|
9084
|
+
if (!row || !docList.contains(row))
|
|
9085
|
+
return;
|
|
9086
|
+
const id = row.dataset.docId;
|
|
9087
|
+
if (id)
|
|
9088
|
+
selectDoc(id);
|
|
9089
|
+
});
|
|
9031
9090
|
async function load(dbId, initial) {
|
|
9032
9091
|
if (disposed)
|
|
9033
9092
|
return;
|
|
@@ -9050,6 +9109,9 @@ ${frontmatter.yaml}
|
|
|
9050
9109
|
searchInput.value = initial?.query ?? "";
|
|
9051
9110
|
currentQuery = searchInput.value.trim();
|
|
9052
9111
|
docList.innerHTML = "";
|
|
9112
|
+
docRowsById.clear();
|
|
9113
|
+
activeIndexRow = null;
|
|
9114
|
+
activeDocRow = null;
|
|
9053
9115
|
docMoreBtn.hidden = true;
|
|
9054
9116
|
mappingBody.innerHTML = "";
|
|
9055
9117
|
mappingBody.textContent = "Select an index to view its mapping.";
|
|
@@ -9106,6 +9168,10 @@ ${frontmatter.yaml}
|
|
|
9106
9168
|
searchInput.value = "";
|
|
9107
9169
|
indexList.innerHTML = "";
|
|
9108
9170
|
docList.innerHTML = "";
|
|
9171
|
+
indexRowsByName.clear();
|
|
9172
|
+
docRowsById.clear();
|
|
9173
|
+
activeIndexRow = null;
|
|
9174
|
+
activeDocRow = null;
|
|
9109
9175
|
docMoreBtn.hidden = true;
|
|
9110
9176
|
mappingBody.innerHTML = "";
|
|
9111
9177
|
mappingBody.textContent = "Select an index to view its mapping.";
|
|
@@ -9396,6 +9462,7 @@ ${frontmatter.yaml}
|
|
|
9396
9462
|
if (disposed)
|
|
9397
9463
|
return;
|
|
9398
9464
|
const dbId = deps.getDbId();
|
|
9465
|
+
const schema = deps.getSchema();
|
|
9399
9466
|
if (!dbId)
|
|
9400
9467
|
return;
|
|
9401
9468
|
const term = input.value.trim();
|
|
@@ -9416,6 +9483,7 @@ ${frontmatter.yaml}
|
|
|
9416
9483
|
},
|
|
9417
9484
|
body: JSON.stringify({
|
|
9418
9485
|
db: dbId,
|
|
9486
|
+
...schema ? { schema } : {},
|
|
9419
9487
|
term,
|
|
9420
9488
|
includeNonText: nonTextCheck.checked
|
|
9421
9489
|
})
|
|
@@ -9494,16 +9562,17 @@ ${frontmatter.yaml}
|
|
|
9494
9562
|
return;
|
|
9495
9563
|
const grouped = new Map;
|
|
9496
9564
|
for (const h of hits) {
|
|
9497
|
-
const
|
|
9565
|
+
const key = h.schema ? `${h.schema}.${h.table}` : h.table;
|
|
9566
|
+
const existing = grouped.get(key) || [];
|
|
9498
9567
|
existing.push(h);
|
|
9499
|
-
grouped.set(
|
|
9568
|
+
grouped.set(key, existing);
|
|
9500
9569
|
}
|
|
9501
|
-
for (const [
|
|
9570
|
+
for (const [tableLabel, tableHits] of grouped) {
|
|
9502
9571
|
const section = document.createElement("div");
|
|
9503
9572
|
section.className = "db-search-table-section";
|
|
9504
9573
|
const tableHeader = document.createElement("div");
|
|
9505
9574
|
tableHeader.className = "db-search-table-header";
|
|
9506
|
-
tableHeader.textContent = `${
|
|
9575
|
+
tableHeader.textContent = `${tableLabel} (${tableHits.length}件)`;
|
|
9507
9576
|
section.appendChild(tableHeader);
|
|
9508
9577
|
const hitsList = document.createElement("div");
|
|
9509
9578
|
hitsList.className = "db-search-hits-list";
|
|
@@ -9950,6 +10019,22 @@ ${frontmatter.yaml}
|
|
|
9950
10019
|
let entries = [];
|
|
9951
10020
|
const expandedIds = new Set;
|
|
9952
10021
|
let clearConfirmTimer = null;
|
|
10022
|
+
let lastRefreshKey = null;
|
|
10023
|
+
let lastRefreshAt = 0;
|
|
10024
|
+
let inFlightRefresh = null;
|
|
10025
|
+
const entryRowsById = new Map;
|
|
10026
|
+
let selectedEntryRow = null;
|
|
10027
|
+
function currentRefreshParams() {
|
|
10028
|
+
const dbId = callbacks.getDbId();
|
|
10029
|
+
const schema = callbacks.getSchema();
|
|
10030
|
+
const searchParams = new URLSearchParams;
|
|
10031
|
+
if (dbId)
|
|
10032
|
+
searchParams.set("db", dbId);
|
|
10033
|
+
if (schema)
|
|
10034
|
+
searchParams.set("schema", schema);
|
|
10035
|
+
const params = searchParams.toString() ? `?${searchParams.toString()}` : "";
|
|
10036
|
+
return { key: params, params };
|
|
10037
|
+
}
|
|
9953
10038
|
function armButtonConfirm(button, confirmText, defaultText) {
|
|
9954
10039
|
if (button.dataset.confirm === "1") {
|
|
9955
10040
|
button.dataset.confirm = "";
|
|
@@ -9966,23 +10051,42 @@ ${frontmatter.yaml}
|
|
|
9966
10051
|
}, 3000);
|
|
9967
10052
|
return false;
|
|
9968
10053
|
}
|
|
9969
|
-
async function refresh() {
|
|
9970
|
-
const
|
|
9971
|
-
const
|
|
9972
|
-
|
|
10054
|
+
async function refresh(options = {}) {
|
|
10055
|
+
const { key: refreshKey, params } = currentRefreshParams();
|
|
10056
|
+
const now = Date.now();
|
|
10057
|
+
if (inFlightRefresh?.key === refreshKey) {
|
|
10058
|
+
return inFlightRefresh.promise;
|
|
10059
|
+
}
|
|
10060
|
+
if (!options.force && refreshKey === lastRefreshKey && now - lastRefreshAt < 1000) {
|
|
10061
|
+
return;
|
|
10062
|
+
}
|
|
10063
|
+
const promise = (async () => {
|
|
9973
10064
|
const res = await fetch(`/_db/history${params}`);
|
|
9974
10065
|
if (!res.ok)
|
|
9975
10066
|
return;
|
|
9976
10067
|
const state = await res.json();
|
|
10068
|
+
if (currentRefreshParams().key !== refreshKey)
|
|
10069
|
+
return;
|
|
9977
10070
|
entries = state.entries;
|
|
10071
|
+
lastRefreshKey = refreshKey;
|
|
10072
|
+
lastRefreshAt = Date.now();
|
|
9978
10073
|
if (selectedEntryId && !entries.some((entry) => entry.id === selectedEntryId)) {
|
|
9979
10074
|
clearDetail();
|
|
9980
10075
|
}
|
|
9981
10076
|
render();
|
|
9982
|
-
}
|
|
10077
|
+
})().catch(() => {});
|
|
10078
|
+
inFlightRefresh = { key: refreshKey, promise };
|
|
10079
|
+
try {
|
|
10080
|
+
await promise;
|
|
10081
|
+
} finally {
|
|
10082
|
+
if (inFlightRefresh?.promise === promise)
|
|
10083
|
+
inFlightRefresh = null;
|
|
10084
|
+
}
|
|
9983
10085
|
}
|
|
9984
10086
|
function render() {
|
|
9985
10087
|
listEl.innerHTML = "";
|
|
10088
|
+
entryRowsById.clear();
|
|
10089
|
+
selectedEntryRow = null;
|
|
9986
10090
|
if (entries.length === 0) {
|
|
9987
10091
|
const empty = document.createElement("div");
|
|
9988
10092
|
empty.className = "db-query-history-empty";
|
|
@@ -9990,21 +10094,25 @@ ${frontmatter.yaml}
|
|
|
9990
10094
|
listEl.appendChild(empty);
|
|
9991
10095
|
return;
|
|
9992
10096
|
}
|
|
10097
|
+
const fragment = document.createDocumentFragment();
|
|
9993
10098
|
for (const entry of entries) {
|
|
9994
|
-
|
|
10099
|
+
fragment.appendChild(renderEntry(entry));
|
|
9995
10100
|
}
|
|
10101
|
+
listEl.appendChild(fragment);
|
|
9996
10102
|
}
|
|
9997
10103
|
let selectedEntryId = null;
|
|
9998
10104
|
function clearDetail() {
|
|
9999
10105
|
selectedEntryId = null;
|
|
10106
|
+
selectedEntryRow?.classList.remove("selected");
|
|
10107
|
+
selectedEntryRow = null;
|
|
10000
10108
|
detailCol.innerHTML = "";
|
|
10001
10109
|
detailCol.appendChild(detailPlaceholder);
|
|
10002
10110
|
}
|
|
10003
10111
|
function selectEntry(entry) {
|
|
10004
10112
|
selectedEntryId = entry.id;
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10113
|
+
selectedEntryRow?.classList.remove("selected");
|
|
10114
|
+
selectedEntryRow = entryRowsById.get(entry.id) ?? null;
|
|
10115
|
+
selectedEntryRow?.classList.add("selected");
|
|
10008
10116
|
renderDetail(entry);
|
|
10009
10117
|
}
|
|
10010
10118
|
function renderDetail(entry) {
|
|
@@ -10061,9 +10169,12 @@ ${frontmatter.yaml}
|
|
|
10061
10169
|
function renderEntry(entry) {
|
|
10062
10170
|
const item = document.createElement("div");
|
|
10063
10171
|
item.className = "db-query-history-entry";
|
|
10064
|
-
if (entry.id === selectedEntryId)
|
|
10172
|
+
if (entry.id === selectedEntryId) {
|
|
10065
10173
|
item.classList.add("selected");
|
|
10174
|
+
selectedEntryRow = item;
|
|
10175
|
+
}
|
|
10066
10176
|
item.dataset.id = entry.id;
|
|
10177
|
+
entryRowsById.set(entry.id, item);
|
|
10067
10178
|
const meta = document.createElement("div");
|
|
10068
10179
|
meta.className = "db-query-history-entry-meta";
|
|
10069
10180
|
const byIcon = document.createElement("span");
|
|
@@ -10150,8 +10261,9 @@ ${frontmatter.yaml}
|
|
|
10150
10261
|
expandedIds.delete(id);
|
|
10151
10262
|
if (selectedEntryId === id)
|
|
10152
10263
|
resetDetailCol();
|
|
10153
|
-
const itemEl =
|
|
10264
|
+
const itemEl = entryRowsById.get(id);
|
|
10154
10265
|
if (itemEl) {
|
|
10266
|
+
entryRowsById.delete(id);
|
|
10155
10267
|
itemEl.remove();
|
|
10156
10268
|
if (entries.length === 0)
|
|
10157
10269
|
render();
|
|
@@ -10181,24 +10293,27 @@ ${frontmatter.yaml}
|
|
|
10181
10293
|
clearConfirmTimer = null;
|
|
10182
10294
|
}
|
|
10183
10295
|
try {
|
|
10296
|
+
const schema = callbacks.getSchema();
|
|
10184
10297
|
await fetch("/_db/history/clear", {
|
|
10185
10298
|
method: "POST",
|
|
10186
10299
|
headers: {
|
|
10187
10300
|
"Content-Type": "application/json",
|
|
10188
10301
|
"X-Code-Viewer-Action": "1"
|
|
10189
10302
|
},
|
|
10190
|
-
body: JSON.stringify(dbId ? { db: dbId } : {})
|
|
10303
|
+
body: JSON.stringify(dbId ? { db: dbId, ...schema ? { schema } : {} } : {})
|
|
10191
10304
|
});
|
|
10192
10305
|
entries = [];
|
|
10193
10306
|
render();
|
|
10194
10307
|
} catch {}
|
|
10195
10308
|
});
|
|
10196
10309
|
refreshBtn.addEventListener("click", () => {
|
|
10197
|
-
refresh();
|
|
10310
|
+
refresh({ force: true });
|
|
10198
10311
|
});
|
|
10199
10312
|
function clear() {
|
|
10200
10313
|
entries = [];
|
|
10201
10314
|
expandedIds.clear();
|
|
10315
|
+
lastRefreshKey = null;
|
|
10316
|
+
lastRefreshAt = 0;
|
|
10202
10317
|
if (clearConfirmTimer) {
|
|
10203
10318
|
clearTimeout(clearConfirmTimer);
|
|
10204
10319
|
clearConfirmTimer = null;
|
|
@@ -10300,6 +10415,10 @@ ${frontmatter.yaml}
|
|
|
10300
10415
|
let keyRunId = 0;
|
|
10301
10416
|
let suppressNotify = false;
|
|
10302
10417
|
let disposed = false;
|
|
10418
|
+
let activeDbRow = null;
|
|
10419
|
+
let activeKeyRow = null;
|
|
10420
|
+
const dbRowsByIndex = new Map;
|
|
10421
|
+
const keyRowsByName = new Map;
|
|
10303
10422
|
const dbGuard = createAbortGuard();
|
|
10304
10423
|
const keysGuard = createAbortGuard();
|
|
10305
10424
|
const valueGuard = createAbortGuard();
|
|
@@ -10325,10 +10444,13 @@ ${frontmatter.yaml}
|
|
|
10325
10444
|
}
|
|
10326
10445
|
function renderDatabases(databases) {
|
|
10327
10446
|
dbList.innerHTML = "";
|
|
10447
|
+
dbRowsByIndex.clear();
|
|
10448
|
+
const fragment = document.createDocumentFragment();
|
|
10328
10449
|
for (const db of databases) {
|
|
10329
10450
|
const item = document.createElement("div");
|
|
10330
10451
|
item.className = "redis-db-item";
|
|
10331
10452
|
item.dataset.dbIndex = String(db.index);
|
|
10453
|
+
dbRowsByIndex.set(db.index, item);
|
|
10332
10454
|
const name = document.createElement("span");
|
|
10333
10455
|
name.className = "redis-db-name";
|
|
10334
10456
|
name.textContent = `db${db.index}`;
|
|
@@ -10336,20 +10458,24 @@ ${frontmatter.yaml}
|
|
|
10336
10458
|
count.className = "redis-db-count";
|
|
10337
10459
|
count.textContent = `${db.keyCount.toLocaleString()} keys`;
|
|
10338
10460
|
item.append(name, count);
|
|
10339
|
-
|
|
10340
|
-
dbList.appendChild(item);
|
|
10461
|
+
fragment.appendChild(item);
|
|
10341
10462
|
}
|
|
10463
|
+
dbList.appendChild(fragment);
|
|
10342
10464
|
}
|
|
10343
10465
|
function highlightActiveDb(index) {
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10466
|
+
if (activeDbRow?.dataset.dbIndex === String(index))
|
|
10467
|
+
return;
|
|
10468
|
+
activeDbRow?.classList.remove("active");
|
|
10469
|
+
activeDbRow = dbRowsByIndex.get(index) ?? null;
|
|
10470
|
+
activeDbRow?.classList.add("active");
|
|
10347
10471
|
}
|
|
10348
10472
|
function appendKeys(keys) {
|
|
10473
|
+
const fragment = document.createDocumentFragment();
|
|
10349
10474
|
for (const k of keys) {
|
|
10350
10475
|
const row = document.createElement("div");
|
|
10351
10476
|
row.className = "redis-key-item";
|
|
10352
10477
|
row.dataset.keyName = k.name;
|
|
10478
|
+
keyRowsByName.set(k.name, row);
|
|
10353
10479
|
const typeBadge = document.createElement("span");
|
|
10354
10480
|
typeBadge.className = `redis-type-badge redis-type-${k.type}`;
|
|
10355
10481
|
typeBadge.textContent = k.type;
|
|
@@ -10358,18 +10484,20 @@ ${frontmatter.yaml}
|
|
|
10358
10484
|
nameEl.textContent = k.name;
|
|
10359
10485
|
nameEl.title = k.name;
|
|
10360
10486
|
row.append(typeBadge, nameEl);
|
|
10361
|
-
|
|
10362
|
-
keyList.appendChild(row);
|
|
10487
|
+
fragment.appendChild(row);
|
|
10363
10488
|
}
|
|
10489
|
+
keyList.appendChild(fragment);
|
|
10364
10490
|
}
|
|
10365
10491
|
function highlightActiveKey(name) {
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10492
|
+
if (activeKeyRow?.dataset.keyName === name)
|
|
10493
|
+
return;
|
|
10494
|
+
activeKeyRow?.classList.remove("active");
|
|
10495
|
+
activeKeyRow = keyRowsByName.get(name) ?? null;
|
|
10496
|
+
activeKeyRow?.classList.add("active");
|
|
10369
10497
|
}
|
|
10370
10498
|
function makeNotice(message, kind = "info") {
|
|
10371
10499
|
const div = document.createElement("div");
|
|
10372
|
-
div.className = kind === "warn" ? "
|
|
10500
|
+
div.className = kind === "warn" ? "datastore-value-truncation" : "datastore-value-info";
|
|
10373
10501
|
div.textContent = message;
|
|
10374
10502
|
return div;
|
|
10375
10503
|
}
|
|
@@ -10527,6 +10655,8 @@ ${frontmatter.yaml}
|
|
|
10527
10655
|
notifySelectionChange();
|
|
10528
10656
|
highlightActiveDb(dbIndex);
|
|
10529
10657
|
keyList.innerHTML = "";
|
|
10658
|
+
keyRowsByName.clear();
|
|
10659
|
+
activeKeyRow = null;
|
|
10530
10660
|
mainPane.textContent = "Select a key to view its value.";
|
|
10531
10661
|
await loadKeys(false);
|
|
10532
10662
|
}
|
|
@@ -10562,8 +10692,11 @@ ${frontmatter.yaml}
|
|
|
10562
10692
|
if (disposed || slot.isStale() || requestRunId !== loadRunId || requestDbId !== currentDbId || requestDbIndex !== currentDbIndex || data.dbIndex !== requestDbIndex) {
|
|
10563
10693
|
return;
|
|
10564
10694
|
}
|
|
10565
|
-
if (!append)
|
|
10695
|
+
if (!append) {
|
|
10566
10696
|
keyList.innerHTML = "";
|
|
10697
|
+
keyRowsByName.clear();
|
|
10698
|
+
activeKeyRow = null;
|
|
10699
|
+
}
|
|
10567
10700
|
if (data.keys.length === 0 && !append) {
|
|
10568
10701
|
setKeyStatus("(no keys)");
|
|
10569
10702
|
} else {
|
|
@@ -10584,6 +10717,23 @@ ${frontmatter.yaml}
|
|
|
10584
10717
|
}
|
|
10585
10718
|
}
|
|
10586
10719
|
keyMoreBtn.addEventListener("click", () => loadKeys(true));
|
|
10720
|
+
dbList.addEventListener("click", (e2) => {
|
|
10721
|
+
const row = e2.target?.closest(".redis-db-item");
|
|
10722
|
+
if (!row || !dbList.contains(row))
|
|
10723
|
+
return;
|
|
10724
|
+
const raw = row.dataset.dbIndex;
|
|
10725
|
+
const index = raw === undefined ? NaN : Number(raw);
|
|
10726
|
+
if (Number.isInteger(index))
|
|
10727
|
+
selectDatabase(index);
|
|
10728
|
+
});
|
|
10729
|
+
keyList.addEventListener("click", (e2) => {
|
|
10730
|
+
const row = e2.target?.closest(".redis-key-item");
|
|
10731
|
+
if (!row || !keyList.contains(row))
|
|
10732
|
+
return;
|
|
10733
|
+
const key = row.dataset.keyName;
|
|
10734
|
+
if (key)
|
|
10735
|
+
selectKey(key);
|
|
10736
|
+
});
|
|
10587
10737
|
keyFilterForm.addEventListener("submit", (e2) => {
|
|
10588
10738
|
e2.preventDefault();
|
|
10589
10739
|
const nextFilter = keyFilterInput.value.trim() || "*";
|
|
@@ -10613,6 +10763,9 @@ ${frontmatter.yaml}
|
|
|
10613
10763
|
keyFilterInput.value = currentKeyFilter === "*" ? "" : currentKeyFilter;
|
|
10614
10764
|
currentCursor = "0";
|
|
10615
10765
|
keyList.innerHTML = "";
|
|
10766
|
+
keyRowsByName.clear();
|
|
10767
|
+
activeDbRow = null;
|
|
10768
|
+
activeKeyRow = null;
|
|
10616
10769
|
keyMoreBtn.hidden = true;
|
|
10617
10770
|
mainPane.textContent = "Select a key to view its value.";
|
|
10618
10771
|
setDbStatus("Loading databases...");
|
|
@@ -10632,11 +10785,21 @@ ${frontmatter.yaml}
|
|
|
10632
10785
|
if (initial?.dbIndex !== undefined && data.databases.some((d2) => d2.index === initial.dbIndex)) {
|
|
10633
10786
|
suppressNotify = true;
|
|
10634
10787
|
try {
|
|
10635
|
-
|
|
10788
|
+
currentDbIndex = initial.dbIndex;
|
|
10789
|
+
currentKey = null;
|
|
10790
|
+
currentCursor = "0";
|
|
10791
|
+
highlightActiveDb(initial.dbIndex);
|
|
10792
|
+
keyList.innerHTML = "";
|
|
10793
|
+
keyRowsByName.clear();
|
|
10794
|
+
mainPane.textContent = "Select a key to view its value.";
|
|
10795
|
+
const valuePromise = initial.key ? selectKey(initial.key).catch(() => {}) : null;
|
|
10796
|
+
await loadKeys(false);
|
|
10636
10797
|
if (currentDbId !== dbId)
|
|
10637
10798
|
return;
|
|
10638
|
-
if (
|
|
10639
|
-
await
|
|
10799
|
+
if (valuePromise) {
|
|
10800
|
+
await valuePromise;
|
|
10801
|
+
if (initial.key)
|
|
10802
|
+
highlightActiveKey(initial.key);
|
|
10640
10803
|
}
|
|
10641
10804
|
} finally {
|
|
10642
10805
|
suppressNotify = false;
|
|
@@ -10666,6 +10829,10 @@ ${frontmatter.yaml}
|
|
|
10666
10829
|
currentCursor = "0";
|
|
10667
10830
|
dbList.innerHTML = "";
|
|
10668
10831
|
keyList.innerHTML = "";
|
|
10832
|
+
dbRowsByIndex.clear();
|
|
10833
|
+
keyRowsByName.clear();
|
|
10834
|
+
activeDbRow = null;
|
|
10835
|
+
activeKeyRow = null;
|
|
10669
10836
|
keyMoreBtn.hidden = true;
|
|
10670
10837
|
mainPane.textContent = "Select a database to view keys.";
|
|
10671
10838
|
}
|
|
@@ -10683,6 +10850,654 @@ ${frontmatter.yaml}
|
|
|
10683
10850
|
return { el: container, load, clear, dispose, getSelection };
|
|
10684
10851
|
}
|
|
10685
10852
|
|
|
10853
|
+
// web-src/core/database/s3-keys.ts
|
|
10854
|
+
function s3ObjectName(key) {
|
|
10855
|
+
const trimmed = key.replace(/\/+$/, "");
|
|
10856
|
+
const idx = trimmed.lastIndexOf("/");
|
|
10857
|
+
return idx >= 0 ? trimmed.slice(idx + 1) : trimmed;
|
|
10858
|
+
}
|
|
10859
|
+
|
|
10860
|
+
// web-src/views/source-preview-elements.ts
|
|
10861
|
+
function renderHtmlPreviewFrame(title, html, extraClass = "") {
|
|
10862
|
+
const preview = document.createElement("div");
|
|
10863
|
+
preview.className = ["gdp-html-preview", extraClass].filter(Boolean).join(" ");
|
|
10864
|
+
const frame = document.createElement("iframe");
|
|
10865
|
+
frame.title = title;
|
|
10866
|
+
frame.sandbox.value = "";
|
|
10867
|
+
frame.referrerPolicy = "no-referrer";
|
|
10868
|
+
frame.srcdoc = html;
|
|
10869
|
+
preview.appendChild(frame);
|
|
10870
|
+
return preview;
|
|
10871
|
+
}
|
|
10872
|
+
function appendMediaEmbed(view, opts) {
|
|
10873
|
+
if (opts.kind === "video") {
|
|
10874
|
+
const video = document.createElement("video");
|
|
10875
|
+
video.src = opts.url;
|
|
10876
|
+
video.controls = true;
|
|
10877
|
+
video.preload = "metadata";
|
|
10878
|
+
view.appendChild(video);
|
|
10879
|
+
} else if (opts.kind === "audio") {
|
|
10880
|
+
const audio = document.createElement("audio");
|
|
10881
|
+
audio.src = opts.url;
|
|
10882
|
+
audio.controls = true;
|
|
10883
|
+
audio.preload = "metadata";
|
|
10884
|
+
view.appendChild(audio);
|
|
10885
|
+
} else if (opts.kind === "pdf") {
|
|
10886
|
+
const frame = document.createElement("iframe");
|
|
10887
|
+
frame.src = opts.url;
|
|
10888
|
+
frame.title = opts.title;
|
|
10889
|
+
frame.loading = "lazy";
|
|
10890
|
+
view.appendChild(frame);
|
|
10891
|
+
} else {
|
|
10892
|
+
const img = document.createElement("img");
|
|
10893
|
+
img.src = opts.url;
|
|
10894
|
+
img.alt = "";
|
|
10895
|
+
if (opts.onImageLoad) {
|
|
10896
|
+
img.addEventListener("load", () => opts.onImageLoad?.(img), {
|
|
10897
|
+
once: true
|
|
10898
|
+
});
|
|
10899
|
+
}
|
|
10900
|
+
view.appendChild(img);
|
|
10901
|
+
}
|
|
10902
|
+
}
|
|
10903
|
+
function renderUnsupportedPreview(opts) {
|
|
10904
|
+
const view = document.createElement("div");
|
|
10905
|
+
view.className = ["gdp-source-viewer unsupported", opts.className || ""].filter(Boolean).join(" ");
|
|
10906
|
+
const content = document.createElement("div");
|
|
10907
|
+
content.className = "gdp-source-unsupported-content";
|
|
10908
|
+
const title = document.createElement("strong");
|
|
10909
|
+
title.className = "gdp-source-unsupported-title";
|
|
10910
|
+
title.textContent = "Preview unavailable";
|
|
10911
|
+
const message = document.createElement("div");
|
|
10912
|
+
message.className = "gdp-source-unsupported-message";
|
|
10913
|
+
message.textContent = opts.message;
|
|
10914
|
+
content.append(title, message, ...opts.extraChildren || []);
|
|
10915
|
+
view.appendChild(content);
|
|
10916
|
+
return view;
|
|
10917
|
+
}
|
|
10918
|
+
|
|
10919
|
+
// web-src/views/database/s3-explorer.ts
|
|
10920
|
+
function buildS3RawUrl(dbId, bucket, key) {
|
|
10921
|
+
const params = new URLSearchParams({ db: dbId, bucket, key });
|
|
10922
|
+
return `/_db/s3/raw?${params}`;
|
|
10923
|
+
}
|
|
10924
|
+
function s3Uri(bucket, key) {
|
|
10925
|
+
return `s3://${bucket}/${key}`;
|
|
10926
|
+
}
|
|
10927
|
+
function objectTypeLabel(key, contentType) {
|
|
10928
|
+
const kind = sourceDisplayKind(key);
|
|
10929
|
+
return humanFileKind(key, contentType, kind === "unsupported" ? "unsupported file" : kind);
|
|
10930
|
+
}
|
|
10931
|
+
function createS3Explorer(callbacks = {}) {
|
|
10932
|
+
const container = document.createElement("div");
|
|
10933
|
+
container.className = "s3-explorer";
|
|
10934
|
+
const objectPane = document.createElement("div");
|
|
10935
|
+
objectPane.className = "s3-object-list-pane";
|
|
10936
|
+
const toolbar = document.createElement("div");
|
|
10937
|
+
toolbar.className = "s3-toolbar";
|
|
10938
|
+
const bucketRow = document.createElement("div");
|
|
10939
|
+
bucketRow.className = "s3-bucket-row";
|
|
10940
|
+
const bucketLabel = document.createElement("label");
|
|
10941
|
+
bucketLabel.className = "s3-field-label";
|
|
10942
|
+
bucketLabel.textContent = "Bucket";
|
|
10943
|
+
const bucketSelect = document.createElement("select");
|
|
10944
|
+
bucketSelect.className = "s3-bucket-select";
|
|
10945
|
+
bucketLabel.appendChild(bucketSelect);
|
|
10946
|
+
bucketRow.appendChild(bucketLabel);
|
|
10947
|
+
const searchRow = document.createElement("form");
|
|
10948
|
+
searchRow.className = "s3-search-row";
|
|
10949
|
+
const searchInput = document.createElement("input");
|
|
10950
|
+
searchInput.type = "search";
|
|
10951
|
+
searchInput.className = "s3-search-input";
|
|
10952
|
+
searchInput.placeholder = "Search objects";
|
|
10953
|
+
searchInput.autocomplete = "off";
|
|
10954
|
+
const searchBtn = document.createElement("button");
|
|
10955
|
+
searchBtn.type = "submit";
|
|
10956
|
+
searchBtn.className = "db-btn db-btn-primary s3-search-btn";
|
|
10957
|
+
searchBtn.textContent = "Search";
|
|
10958
|
+
searchRow.append(searchInput, searchBtn);
|
|
10959
|
+
const optionRow = document.createElement("div");
|
|
10960
|
+
optionRow.className = "s3-options-row";
|
|
10961
|
+
const modeSeg = document.createElement("div");
|
|
10962
|
+
modeSeg.className = "seg s3-mode-seg";
|
|
10963
|
+
const prefixModeBtn = document.createElement("button");
|
|
10964
|
+
prefixModeBtn.type = "button";
|
|
10965
|
+
prefixModeBtn.textContent = "Prefix";
|
|
10966
|
+
const containsModeBtn = document.createElement("button");
|
|
10967
|
+
containsModeBtn.type = "button";
|
|
10968
|
+
containsModeBtn.textContent = "Contains";
|
|
10969
|
+
modeSeg.append(prefixModeBtn, containsModeBtn);
|
|
10970
|
+
const sortSelect = document.createElement("select");
|
|
10971
|
+
sortSelect.className = "s3-sort-select";
|
|
10972
|
+
const sortUpdated = document.createElement("option");
|
|
10973
|
+
sortUpdated.value = "updated-desc";
|
|
10974
|
+
sortUpdated.textContent = "Updated newest";
|
|
10975
|
+
const sortKey = document.createElement("option");
|
|
10976
|
+
sortKey.value = "key-asc";
|
|
10977
|
+
sortKey.textContent = "Key A-Z";
|
|
10978
|
+
sortSelect.append(sortUpdated, sortKey);
|
|
10979
|
+
optionRow.append(modeSeg, sortSelect);
|
|
10980
|
+
toolbar.append(bucketRow, searchRow, optionRow);
|
|
10981
|
+
objectPane.appendChild(toolbar);
|
|
10982
|
+
const objectStatus = document.createElement("div");
|
|
10983
|
+
objectStatus.className = "s3-object-status";
|
|
10984
|
+
objectPane.appendChild(objectStatus);
|
|
10985
|
+
const objectList = document.createElement("div");
|
|
10986
|
+
objectList.className = "s3-object-list";
|
|
10987
|
+
objectPane.appendChild(objectList);
|
|
10988
|
+
const moreBtn = document.createElement("button");
|
|
10989
|
+
moreBtn.type = "button";
|
|
10990
|
+
moreBtn.className = "s3-object-more-btn";
|
|
10991
|
+
moreBtn.textContent = "Load more";
|
|
10992
|
+
moreBtn.hidden = true;
|
|
10993
|
+
objectPane.appendChild(moreBtn);
|
|
10994
|
+
const previewPane = document.createElement("div");
|
|
10995
|
+
previewPane.className = "s3-preview-pane";
|
|
10996
|
+
previewPane.textContent = "Select an object to preview.";
|
|
10997
|
+
container.append(objectPane, previewPane);
|
|
10998
|
+
let currentDbId = null;
|
|
10999
|
+
let currentBucket = null;
|
|
11000
|
+
let currentKey = null;
|
|
11001
|
+
let currentMode = "prefix";
|
|
11002
|
+
let currentSort = "updated-desc";
|
|
11003
|
+
let currentSearch = "";
|
|
11004
|
+
let currentNextToken;
|
|
11005
|
+
const objectsByKey = new Map;
|
|
11006
|
+
const objectRowsByKey = new Map;
|
|
11007
|
+
let disposed = false;
|
|
11008
|
+
let loadRunId = 0;
|
|
11009
|
+
let objectRunId = 0;
|
|
11010
|
+
let suppressNotify = false;
|
|
11011
|
+
let activeObjectRow = null;
|
|
11012
|
+
const bucketGuard = createAbortGuard();
|
|
11013
|
+
const objectGuard = createAbortGuard();
|
|
11014
|
+
const previewGuard = createAbortGuard();
|
|
11015
|
+
function notifySelectionChange() {
|
|
11016
|
+
if (suppressNotify)
|
|
11017
|
+
return;
|
|
11018
|
+
callbacks.onSelectionChange?.(getSelection());
|
|
11019
|
+
}
|
|
11020
|
+
function setMode(mode) {
|
|
11021
|
+
currentMode = mode;
|
|
11022
|
+
prefixModeBtn.classList.toggle("active", mode === "prefix");
|
|
11023
|
+
containsModeBtn.classList.toggle("active", mode === "contains");
|
|
11024
|
+
searchInput.placeholder = mode === "prefix" ? "Prefix, e.g. photos/2026/" : "Filename contains";
|
|
11025
|
+
}
|
|
11026
|
+
function renderBuckets(buckets) {
|
|
11027
|
+
bucketSelect.innerHTML = "";
|
|
11028
|
+
for (const bucket of buckets) {
|
|
11029
|
+
const opt = document.createElement("option");
|
|
11030
|
+
opt.value = bucket.name;
|
|
11031
|
+
opt.textContent = bucket.name;
|
|
11032
|
+
bucketSelect.appendChild(opt);
|
|
11033
|
+
}
|
|
11034
|
+
bucketSelect.disabled = buckets.length === 0;
|
|
11035
|
+
}
|
|
11036
|
+
function setObjectStatusFromResponse(resp) {
|
|
11037
|
+
objectStatus.textContent = "";
|
|
11038
|
+
const parts = [
|
|
11039
|
+
`${resp.objects.length.toLocaleString()} shown`,
|
|
11040
|
+
`${resp.scannedObjects.toLocaleString()} scanned`,
|
|
11041
|
+
resp.sort === "updated-desc" ? "newest first in scanned objects" : "sorted by key"
|
|
11042
|
+
];
|
|
11043
|
+
if (resp.scanLimitReached) {
|
|
11044
|
+
parts.push("scan cap reached; narrow the prefix to search more precisely");
|
|
11045
|
+
}
|
|
11046
|
+
objectStatus.textContent = parts.join(" / ");
|
|
11047
|
+
objectStatus.classList.toggle("warn", !!resp.scanLimitReached);
|
|
11048
|
+
}
|
|
11049
|
+
function highlightActiveObject(key) {
|
|
11050
|
+
if (activeObjectRow?.dataset.key === key)
|
|
11051
|
+
return;
|
|
11052
|
+
activeObjectRow?.classList.remove("active");
|
|
11053
|
+
activeObjectRow = key ? objectRowsByKey.get(key) ?? null : null;
|
|
11054
|
+
activeObjectRow?.classList.add("active");
|
|
11055
|
+
}
|
|
11056
|
+
async function fetchObjectHeadForSelection(key, signal) {
|
|
11057
|
+
if (!currentDbId || !currentBucket)
|
|
11058
|
+
return { key, sizeBytes: 0 };
|
|
11059
|
+
const params = new URLSearchParams({
|
|
11060
|
+
db: currentDbId,
|
|
11061
|
+
bucket: currentBucket,
|
|
11062
|
+
key
|
|
11063
|
+
});
|
|
11064
|
+
const res = await fetch(`/_db/s3/head?${params}`, { signal });
|
|
11065
|
+
if (!res.ok)
|
|
11066
|
+
return { key, sizeBytes: 0 };
|
|
11067
|
+
const data = await res.json();
|
|
11068
|
+
return {
|
|
11069
|
+
key,
|
|
11070
|
+
sizeBytes: data.sizeBytes ?? 0,
|
|
11071
|
+
...data.updatedAt ? { updatedAt: data.updatedAt } : {},
|
|
11072
|
+
...data.contentType ? { contentType: data.contentType } : {},
|
|
11073
|
+
...data.etag ? { etag: data.etag } : {}
|
|
11074
|
+
};
|
|
11075
|
+
}
|
|
11076
|
+
function appendObjects(objects) {
|
|
11077
|
+
const fragment = document.createDocumentFragment();
|
|
11078
|
+
for (const object of objects) {
|
|
11079
|
+
objectsByKey.set(object.key, object);
|
|
11080
|
+
const row = document.createElement("div");
|
|
11081
|
+
row.className = "s3-object-item";
|
|
11082
|
+
row.dataset.key = object.key;
|
|
11083
|
+
objectRowsByKey.set(object.key, row);
|
|
11084
|
+
const name = document.createElement("span");
|
|
11085
|
+
name.className = "s3-object-name";
|
|
11086
|
+
name.textContent = object.key;
|
|
11087
|
+
name.title = object.key;
|
|
11088
|
+
const meta = document.createElement("span");
|
|
11089
|
+
meta.className = "s3-object-meta";
|
|
11090
|
+
const kind = objectTypeLabel(object.key, object.contentType);
|
|
11091
|
+
const size = formatBytes(object.sizeBytes);
|
|
11092
|
+
const updated = formatFileDate(object.updatedAt);
|
|
11093
|
+
meta.textContent = [kind, size, updated].filter(Boolean).join(" / ");
|
|
11094
|
+
row.append(name, meta);
|
|
11095
|
+
fragment.appendChild(row);
|
|
11096
|
+
}
|
|
11097
|
+
objectList.appendChild(fragment);
|
|
11098
|
+
}
|
|
11099
|
+
async function loadObjects(append) {
|
|
11100
|
+
if (!currentDbId || !currentBucket || disposed)
|
|
11101
|
+
return;
|
|
11102
|
+
const slot = objectGuard.start();
|
|
11103
|
+
const requestRunId = loadRunId;
|
|
11104
|
+
const requestDbId = currentDbId;
|
|
11105
|
+
const requestBucket = currentBucket;
|
|
11106
|
+
const requestSearch = currentSearch;
|
|
11107
|
+
const requestMode = currentMode;
|
|
11108
|
+
const requestSort = currentSort;
|
|
11109
|
+
moreBtn.disabled = true;
|
|
11110
|
+
if (!append) {
|
|
11111
|
+
objectList.innerHTML = "";
|
|
11112
|
+
objectsByKey.clear();
|
|
11113
|
+
objectRowsByKey.clear();
|
|
11114
|
+
activeObjectRow = null;
|
|
11115
|
+
currentKey = null;
|
|
11116
|
+
currentNextToken = undefined;
|
|
11117
|
+
setPaneStatus(objectList, "Loading objects...");
|
|
11118
|
+
previewPane.textContent = "Select an object to preview.";
|
|
11119
|
+
}
|
|
11120
|
+
try {
|
|
11121
|
+
const params = new URLSearchParams({
|
|
11122
|
+
db: requestDbId,
|
|
11123
|
+
bucket: requestBucket,
|
|
11124
|
+
mode: requestMode,
|
|
11125
|
+
sort: requestSort,
|
|
11126
|
+
limit: requestSort === "updated-desc" ? "1000" : "200"
|
|
11127
|
+
});
|
|
11128
|
+
if (requestMode === "prefix") {
|
|
11129
|
+
if (requestSearch)
|
|
11130
|
+
params.set("q", requestSearch);
|
|
11131
|
+
} else if (requestSearch) {
|
|
11132
|
+
params.set("q", requestSearch);
|
|
11133
|
+
}
|
|
11134
|
+
if (append && currentNextToken)
|
|
11135
|
+
params.set("token", currentNextToken);
|
|
11136
|
+
const res = await fetch(`/_db/s3/objects?${params}`, {
|
|
11137
|
+
signal: slot.signal
|
|
11138
|
+
});
|
|
11139
|
+
if (disposed || slot.isStale())
|
|
11140
|
+
return;
|
|
11141
|
+
if (!res.ok) {
|
|
11142
|
+
const text2 = await res.text();
|
|
11143
|
+
setPaneStatus(objectList, `Error: ${text2 || res.statusText}`, {
|
|
11144
|
+
error: true
|
|
11145
|
+
});
|
|
11146
|
+
objectStatus.textContent = "";
|
|
11147
|
+
return;
|
|
11148
|
+
}
|
|
11149
|
+
const data = await res.json();
|
|
11150
|
+
if (disposed || slot.isStale() || requestRunId !== loadRunId || requestDbId !== currentDbId || requestBucket !== currentBucket || requestSearch !== currentSearch || requestMode !== currentMode || requestSort !== currentSort) {
|
|
11151
|
+
return;
|
|
11152
|
+
}
|
|
11153
|
+
if (!append)
|
|
11154
|
+
objectList.innerHTML = "";
|
|
11155
|
+
if (data.objects.length === 0 && !append) {
|
|
11156
|
+
setPaneStatus(objectList, data.scanLimitReached ? `(no matches in the first ${data.scannedObjects.toLocaleString()} scanned objects; narrow the prefix and search again)` : "(no objects)");
|
|
11157
|
+
} else {
|
|
11158
|
+
appendObjects(data.objects);
|
|
11159
|
+
}
|
|
11160
|
+
currentNextToken = data.nextToken;
|
|
11161
|
+
moreBtn.hidden = !data.nextToken;
|
|
11162
|
+
setObjectStatusFromResponse(data);
|
|
11163
|
+
highlightActiveObject(currentKey);
|
|
11164
|
+
} catch (err) {
|
|
11165
|
+
if (slot.isStale())
|
|
11166
|
+
return;
|
|
11167
|
+
setPaneStatus(objectList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11168
|
+
objectStatus.textContent = "";
|
|
11169
|
+
} finally {
|
|
11170
|
+
slot.finish();
|
|
11171
|
+
if (!slot.isStale())
|
|
11172
|
+
moreBtn.disabled = false;
|
|
11173
|
+
}
|
|
11174
|
+
}
|
|
11175
|
+
function renderPreviewHeader(object) {
|
|
11176
|
+
const header = document.createElement("div");
|
|
11177
|
+
header.className = "s3-preview-header";
|
|
11178
|
+
const title = document.createElement("div");
|
|
11179
|
+
title.className = "s3-preview-title";
|
|
11180
|
+
title.textContent = object.key;
|
|
11181
|
+
title.title = object.key;
|
|
11182
|
+
const meta = document.createElement("div");
|
|
11183
|
+
meta.className = "s3-preview-meta";
|
|
11184
|
+
meta.textContent = [
|
|
11185
|
+
objectTypeLabel(object.key, object.contentType),
|
|
11186
|
+
formatBytes(object.sizeBytes),
|
|
11187
|
+
formatFileDate(object.updatedAt)
|
|
11188
|
+
].filter(Boolean).join(" / ");
|
|
11189
|
+
const actions = document.createElement("div");
|
|
11190
|
+
actions.className = "s3-preview-actions";
|
|
11191
|
+
if (currentDbId && currentBucket) {
|
|
11192
|
+
const rawUrl = buildS3RawUrl(currentDbId, currentBucket, object.key);
|
|
11193
|
+
const open = document.createElement("a");
|
|
11194
|
+
open.className = "db-btn db-btn-sm";
|
|
11195
|
+
open.href = rawUrl;
|
|
11196
|
+
open.target = "_blank";
|
|
11197
|
+
open.rel = "noreferrer";
|
|
11198
|
+
open.textContent = "Open raw";
|
|
11199
|
+
const download = document.createElement("a");
|
|
11200
|
+
download.className = "db-btn db-btn-sm";
|
|
11201
|
+
download.href = rawUrl;
|
|
11202
|
+
download.download = s3ObjectName(object.key);
|
|
11203
|
+
download.textContent = "Download";
|
|
11204
|
+
const copy = document.createElement("button");
|
|
11205
|
+
copy.type = "button";
|
|
11206
|
+
copy.className = "db-btn db-btn-sm";
|
|
11207
|
+
copy.textContent = "Copy S3 URI";
|
|
11208
|
+
copy.addEventListener("click", async () => {
|
|
11209
|
+
try {
|
|
11210
|
+
await navigator.clipboard.writeText(s3Uri(currentBucket || "", object.key));
|
|
11211
|
+
copy.textContent = "Copied";
|
|
11212
|
+
window.setTimeout(() => {
|
|
11213
|
+
copy.textContent = "Copy S3 URI";
|
|
11214
|
+
}, 1200);
|
|
11215
|
+
} catch {
|
|
11216
|
+
copy.textContent = "Copy failed";
|
|
11217
|
+
}
|
|
11218
|
+
});
|
|
11219
|
+
actions.append(open, download, copy);
|
|
11220
|
+
}
|
|
11221
|
+
header.append(title, meta, actions);
|
|
11222
|
+
return header;
|
|
11223
|
+
}
|
|
11224
|
+
function renderMediaPreview(object, kind) {
|
|
11225
|
+
const view = document.createElement("div");
|
|
11226
|
+
view.className = `gdp-source-viewer media ${kind} s3-source-preview`;
|
|
11227
|
+
if (!currentDbId || !currentBucket)
|
|
11228
|
+
return view;
|
|
11229
|
+
const url = buildS3RawUrl(currentDbId, currentBucket, object.key);
|
|
11230
|
+
appendMediaEmbed(view, { url, kind, title: object.key });
|
|
11231
|
+
return view;
|
|
11232
|
+
}
|
|
11233
|
+
function renderUnsupported() {
|
|
11234
|
+
return renderUnsupportedPreview({
|
|
11235
|
+
className: "s3-source-preview",
|
|
11236
|
+
message: "This object type cannot be previewed safely in the browser."
|
|
11237
|
+
});
|
|
11238
|
+
}
|
|
11239
|
+
async function renderTextPreview(object, slot) {
|
|
11240
|
+
if (!currentDbId || !currentBucket)
|
|
11241
|
+
return null;
|
|
11242
|
+
const params = new URLSearchParams({
|
|
11243
|
+
db: currentDbId,
|
|
11244
|
+
bucket: currentBucket,
|
|
11245
|
+
key: object.key
|
|
11246
|
+
});
|
|
11247
|
+
const res = await fetch(`/_db/s3/text?${params}`, { signal: slot.signal });
|
|
11248
|
+
if (disposed || slot.isStale())
|
|
11249
|
+
return null;
|
|
11250
|
+
if (!res.ok) {
|
|
11251
|
+
const text2 = await res.text();
|
|
11252
|
+
const error2 = document.createElement("div");
|
|
11253
|
+
error2.className = "db-pane-error";
|
|
11254
|
+
error2.textContent = text2 || res.statusText;
|
|
11255
|
+
return error2;
|
|
11256
|
+
}
|
|
11257
|
+
const data = await res.json();
|
|
11258
|
+
if (disposed || slot.isStale())
|
|
11259
|
+
return null;
|
|
11260
|
+
const previewKind = sourcePreviewKind(object.key);
|
|
11261
|
+
let body;
|
|
11262
|
+
if (previewKind === "html") {
|
|
11263
|
+
body = renderHtmlPreviewFrame(`${object.key} preview`, data.text, "s3-html-preview");
|
|
11264
|
+
} else if (previewKind === "markdown") {
|
|
11265
|
+
body = await renderMarkdownPreview(data.text, { path: object.key, ref: "s3" }, {
|
|
11266
|
+
syntaxHighlight: true,
|
|
11267
|
+
signal: slot.signal,
|
|
11268
|
+
resolveAssetUrl: (path) => currentDbId && currentBucket ? buildS3RawUrl(currentDbId, currentBucket, path) : null
|
|
11269
|
+
});
|
|
11270
|
+
} else {
|
|
11271
|
+
const pre = document.createElement("pre");
|
|
11272
|
+
pre.className = "s3-text-preview";
|
|
11273
|
+
pre.textContent = data.text;
|
|
11274
|
+
body = pre;
|
|
11275
|
+
}
|
|
11276
|
+
if (data.truncated) {
|
|
11277
|
+
const wrap = document.createElement("div");
|
|
11278
|
+
wrap.className = "s3-text-preview-wrap";
|
|
11279
|
+
const note = document.createElement("div");
|
|
11280
|
+
note.className = "datastore-value-truncation";
|
|
11281
|
+
note.textContent = `Showing first ${formatBytes(512 * 1024)}.`;
|
|
11282
|
+
wrap.append(note, body);
|
|
11283
|
+
return wrap;
|
|
11284
|
+
}
|
|
11285
|
+
return body;
|
|
11286
|
+
}
|
|
11287
|
+
async function selectObject(object) {
|
|
11288
|
+
if (disposed || !currentDbId || !currentBucket)
|
|
11289
|
+
return;
|
|
11290
|
+
const slot = previewGuard.start();
|
|
11291
|
+
const requestRunId = ++objectRunId;
|
|
11292
|
+
const requestDbId = currentDbId;
|
|
11293
|
+
const requestBucket = currentBucket;
|
|
11294
|
+
currentKey = object.key;
|
|
11295
|
+
notifySelectionChange();
|
|
11296
|
+
highlightActiveObject(object.key);
|
|
11297
|
+
previewPane.innerHTML = "";
|
|
11298
|
+
previewPane.appendChild(renderPreviewHeader(object));
|
|
11299
|
+
const body = document.createElement("div");
|
|
11300
|
+
body.className = "s3-preview-body";
|
|
11301
|
+
setPaneStatus(body, "Loading preview...");
|
|
11302
|
+
previewPane.appendChild(body);
|
|
11303
|
+
try {
|
|
11304
|
+
const displayKind = sourceDisplayKind(object.key);
|
|
11305
|
+
let preview;
|
|
11306
|
+
if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
|
|
11307
|
+
preview = renderMediaPreview(object, displayKind);
|
|
11308
|
+
} else if (displayKind === "text") {
|
|
11309
|
+
preview = await renderTextPreview(object, slot);
|
|
11310
|
+
} else {
|
|
11311
|
+
preview = renderUnsupported();
|
|
11312
|
+
}
|
|
11313
|
+
if (disposed || slot.isStale() || requestRunId !== objectRunId || requestDbId !== currentDbId || requestBucket !== currentBucket || object.key !== currentKey) {
|
|
11314
|
+
return;
|
|
11315
|
+
}
|
|
11316
|
+
body.innerHTML = "";
|
|
11317
|
+
if (preview)
|
|
11318
|
+
body.appendChild(preview);
|
|
11319
|
+
} catch (err) {
|
|
11320
|
+
if (slot.isStale())
|
|
11321
|
+
return;
|
|
11322
|
+
setPaneStatus(body, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11323
|
+
} finally {
|
|
11324
|
+
slot.finish();
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
async function selectBucket(bucket) {
|
|
11328
|
+
if (disposed || !currentDbId)
|
|
11329
|
+
return;
|
|
11330
|
+
currentBucket = bucket;
|
|
11331
|
+
currentKey = null;
|
|
11332
|
+
currentNextToken = undefined;
|
|
11333
|
+
notifySelectionChange();
|
|
11334
|
+
await loadObjects(false);
|
|
11335
|
+
}
|
|
11336
|
+
bucketSelect.addEventListener("change", () => {
|
|
11337
|
+
if (bucketSelect.value)
|
|
11338
|
+
selectBucket(bucketSelect.value);
|
|
11339
|
+
});
|
|
11340
|
+
prefixModeBtn.addEventListener("click", () => {
|
|
11341
|
+
setMode("prefix");
|
|
11342
|
+
notifySelectionChange();
|
|
11343
|
+
loadObjects(false);
|
|
11344
|
+
});
|
|
11345
|
+
containsModeBtn.addEventListener("click", () => {
|
|
11346
|
+
setMode("contains");
|
|
11347
|
+
notifySelectionChange();
|
|
11348
|
+
loadObjects(false);
|
|
11349
|
+
});
|
|
11350
|
+
sortSelect.addEventListener("change", () => {
|
|
11351
|
+
currentSort = sortSelect.value === "key-asc" ? "key-asc" : "updated-desc";
|
|
11352
|
+
notifySelectionChange();
|
|
11353
|
+
loadObjects(false);
|
|
11354
|
+
});
|
|
11355
|
+
searchRow.addEventListener("submit", (e2) => {
|
|
11356
|
+
e2.preventDefault();
|
|
11357
|
+
currentSearch = searchInput.value.trim();
|
|
11358
|
+
notifySelectionChange();
|
|
11359
|
+
loadObjects(false);
|
|
11360
|
+
});
|
|
11361
|
+
searchInput.addEventListener("keydown", (e2) => {
|
|
11362
|
+
if (isImeComposing(e2))
|
|
11363
|
+
return;
|
|
11364
|
+
if (e2.key === "Escape") {
|
|
11365
|
+
searchInput.value = "";
|
|
11366
|
+
currentSearch = "";
|
|
11367
|
+
notifySelectionChange();
|
|
11368
|
+
loadObjects(false);
|
|
11369
|
+
}
|
|
11370
|
+
});
|
|
11371
|
+
moreBtn.addEventListener("click", () => loadObjects(true));
|
|
11372
|
+
objectList.addEventListener("click", (e2) => {
|
|
11373
|
+
const row = e2.target?.closest(".s3-object-item");
|
|
11374
|
+
if (!row || !objectList.contains(row))
|
|
11375
|
+
return;
|
|
11376
|
+
const key = row.dataset.key;
|
|
11377
|
+
if (!key)
|
|
11378
|
+
return;
|
|
11379
|
+
const object = objectsByKey.get(key);
|
|
11380
|
+
if (object)
|
|
11381
|
+
selectObject(object);
|
|
11382
|
+
});
|
|
11383
|
+
async function load(dbId, initial) {
|
|
11384
|
+
if (disposed)
|
|
11385
|
+
return;
|
|
11386
|
+
if (currentDbId === dbId && !initial)
|
|
11387
|
+
return;
|
|
11388
|
+
bucketGuard.dispose();
|
|
11389
|
+
objectGuard.dispose();
|
|
11390
|
+
previewGuard.dispose();
|
|
11391
|
+
const slot = bucketGuard.start();
|
|
11392
|
+
const requestRunId = ++loadRunId;
|
|
11393
|
+
const initialMode = initial?.mode === "contains" ? "contains" : "prefix";
|
|
11394
|
+
currentDbId = dbId;
|
|
11395
|
+
currentBucket = null;
|
|
11396
|
+
currentKey = null;
|
|
11397
|
+
currentSearch = initialMode === "contains" ? initial?.query ?? "" : initial?.prefix ?? "";
|
|
11398
|
+
currentSort = initial?.sort === "key-asc" ? "key-asc" : "updated-desc";
|
|
11399
|
+
setMode(initialMode);
|
|
11400
|
+
sortSelect.value = currentSort;
|
|
11401
|
+
searchInput.value = currentSearch;
|
|
11402
|
+
objectStatus.textContent = "";
|
|
11403
|
+
objectList.innerHTML = "";
|
|
11404
|
+
objectsByKey.clear();
|
|
11405
|
+
objectRowsByKey.clear();
|
|
11406
|
+
activeObjectRow = null;
|
|
11407
|
+
moreBtn.hidden = true;
|
|
11408
|
+
previewPane.textContent = "Select an object to preview.";
|
|
11409
|
+
setPaneStatus(objectList, "Loading buckets...");
|
|
11410
|
+
try {
|
|
11411
|
+
const res = await fetch(`/_db/s3/buckets?db=${encodeURIComponent(dbId)}`, { signal: slot.signal });
|
|
11412
|
+
if (disposed || slot.isStale())
|
|
11413
|
+
return;
|
|
11414
|
+
if (!res.ok) {
|
|
11415
|
+
const text2 = await res.text();
|
|
11416
|
+
setPaneStatus(objectList, `Error: ${text2 || res.statusText}`, {
|
|
11417
|
+
error: true
|
|
11418
|
+
});
|
|
11419
|
+
return;
|
|
11420
|
+
}
|
|
11421
|
+
const data = await res.json();
|
|
11422
|
+
if (disposed || slot.isStale() || requestRunId !== loadRunId || currentDbId !== dbId) {
|
|
11423
|
+
return;
|
|
11424
|
+
}
|
|
11425
|
+
renderBuckets(data.buckets);
|
|
11426
|
+
const selected = initial?.bucket && data.buckets.some((bucket) => bucket.name === initial.bucket) && initial.bucket || data.buckets[0]?.name || null;
|
|
11427
|
+
if (!selected) {
|
|
11428
|
+
setPaneStatus(objectList, "(no buckets)");
|
|
11429
|
+
return;
|
|
11430
|
+
}
|
|
11431
|
+
bucketSelect.value = selected;
|
|
11432
|
+
suppressNotify = true;
|
|
11433
|
+
try {
|
|
11434
|
+
currentBucket = selected;
|
|
11435
|
+
currentKey = null;
|
|
11436
|
+
currentNextToken = undefined;
|
|
11437
|
+
notifySelectionChange();
|
|
11438
|
+
highlightActiveObject(null);
|
|
11439
|
+
const headPromise = initial?.key ? fetchObjectHeadForSelection(initial.key, slot.signal).catch(() => null) : null;
|
|
11440
|
+
await loadObjects(false);
|
|
11441
|
+
if (currentDbId !== dbId)
|
|
11442
|
+
return;
|
|
11443
|
+
if (initial?.key) {
|
|
11444
|
+
const object = objectsByKey.get(initial.key) || (headPromise ? await headPromise : null) || { key: initial.key, sizeBytes: 0 };
|
|
11445
|
+
await selectObject(object);
|
|
11446
|
+
}
|
|
11447
|
+
} finally {
|
|
11448
|
+
suppressNotify = false;
|
|
11449
|
+
}
|
|
11450
|
+
notifySelectionChange();
|
|
11451
|
+
} catch (err) {
|
|
11452
|
+
if (slot.isStale())
|
|
11453
|
+
return;
|
|
11454
|
+
setPaneStatus(objectList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11455
|
+
} finally {
|
|
11456
|
+
slot.finish();
|
|
11457
|
+
}
|
|
11458
|
+
}
|
|
11459
|
+
function clear() {
|
|
11460
|
+
bucketGuard.dispose();
|
|
11461
|
+
objectGuard.dispose();
|
|
11462
|
+
previewGuard.dispose();
|
|
11463
|
+
loadRunId++;
|
|
11464
|
+
objectRunId++;
|
|
11465
|
+
suppressNotify = false;
|
|
11466
|
+
currentDbId = null;
|
|
11467
|
+
currentBucket = null;
|
|
11468
|
+
currentKey = null;
|
|
11469
|
+
currentSearch = "";
|
|
11470
|
+
currentNextToken = undefined;
|
|
11471
|
+
currentSort = "updated-desc";
|
|
11472
|
+
setMode("prefix");
|
|
11473
|
+
sortSelect.value = "updated-desc";
|
|
11474
|
+
searchInput.value = "";
|
|
11475
|
+
bucketSelect.innerHTML = "";
|
|
11476
|
+
objectStatus.textContent = "";
|
|
11477
|
+
objectList.innerHTML = "";
|
|
11478
|
+
objectsByKey.clear();
|
|
11479
|
+
objectRowsByKey.clear();
|
|
11480
|
+
activeObjectRow = null;
|
|
11481
|
+
moreBtn.hidden = true;
|
|
11482
|
+
previewPane.textContent = "Select an object to preview.";
|
|
11483
|
+
}
|
|
11484
|
+
function getSelection() {
|
|
11485
|
+
return {
|
|
11486
|
+
bucket: currentBucket ?? undefined,
|
|
11487
|
+
...currentMode === "prefix" ? { prefix: currentSearch || undefined } : { query: currentSearch || undefined },
|
|
11488
|
+
mode: currentMode,
|
|
11489
|
+
sort: currentSort,
|
|
11490
|
+
key: currentKey ?? undefined
|
|
11491
|
+
};
|
|
11492
|
+
}
|
|
11493
|
+
function dispose() {
|
|
11494
|
+
disposed = true;
|
|
11495
|
+
clear();
|
|
11496
|
+
}
|
|
11497
|
+
setMode("prefix");
|
|
11498
|
+
return { el: container, load, clear, dispose, getSelection };
|
|
11499
|
+
}
|
|
11500
|
+
|
|
10686
11501
|
// web-src/views/database/schema-view.ts
|
|
10687
11502
|
function createSchemaView() {
|
|
10688
11503
|
const el = document.createElement("div");
|
|
@@ -11017,6 +11832,7 @@ ${frontmatter.yaml}
|
|
|
11017
11832
|
});
|
|
11018
11833
|
confirmBtn.addEventListener("click", async () => {
|
|
11019
11834
|
const dbId = deps.getDbId();
|
|
11835
|
+
const schema = deps.getSchema();
|
|
11020
11836
|
if (!dbId)
|
|
11021
11837
|
return;
|
|
11022
11838
|
const tables = getSelectedTables();
|
|
@@ -11027,11 +11843,12 @@ ${frontmatter.yaml}
|
|
|
11027
11843
|
try {
|
|
11028
11844
|
await postJson("/_db/snapshot/create", {
|
|
11029
11845
|
db: dbId,
|
|
11846
|
+
...schema ? { schema } : {},
|
|
11030
11847
|
tables,
|
|
11031
11848
|
note: noteInput.value.trim()
|
|
11032
11849
|
});
|
|
11033
11850
|
tableSelector.hidden = true;
|
|
11034
|
-
scheduleAutoRefresh(dbId);
|
|
11851
|
+
scheduleAutoRefresh(dbId, schema);
|
|
11035
11852
|
} catch {} finally {
|
|
11036
11853
|
if (!disposed) {
|
|
11037
11854
|
confirmBtn.disabled = false;
|
|
@@ -11039,11 +11856,12 @@ ${frontmatter.yaml}
|
|
|
11039
11856
|
}
|
|
11040
11857
|
}
|
|
11041
11858
|
});
|
|
11042
|
-
function scheduleAutoRefresh(dbId) {
|
|
11859
|
+
function scheduleAutoRefresh(dbId, schema) {
|
|
11043
11860
|
const timer = setTimeout(() => {
|
|
11044
11861
|
autoRefreshTimers.delete(timer);
|
|
11045
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11862
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
11046
11863
|
return;
|
|
11864
|
+
}
|
|
11047
11865
|
refreshAndAutoDiff();
|
|
11048
11866
|
}, 3000);
|
|
11049
11867
|
autoRefreshTimers.add(timer);
|
|
@@ -11051,17 +11869,22 @@ ${frontmatter.yaml}
|
|
|
11051
11869
|
refreshBtn.addEventListener("click", refresh);
|
|
11052
11870
|
async function refresh() {
|
|
11053
11871
|
const dbId = deps.getDbId();
|
|
11872
|
+
const schema = deps.getSchema();
|
|
11054
11873
|
if (!dbId)
|
|
11055
11874
|
return;
|
|
11056
11875
|
try {
|
|
11057
|
-
const
|
|
11876
|
+
const params = new URLSearchParams({ db: dbId });
|
|
11877
|
+
if (schema)
|
|
11878
|
+
params.set("schema", schema);
|
|
11879
|
+
const snapRes = await fetch(`/_db/snapshot/list?${params}`);
|
|
11058
11880
|
if (snapRes.ok) {
|
|
11059
11881
|
const data = await snapRes.json();
|
|
11060
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11882
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
11061
11883
|
return;
|
|
11884
|
+
}
|
|
11062
11885
|
snapshots = data.snapshots;
|
|
11063
11886
|
}
|
|
11064
|
-
if (disposed || deps.getDbId() !== dbId)
|
|
11887
|
+
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema)
|
|
11065
11888
|
return;
|
|
11066
11889
|
renderMain();
|
|
11067
11890
|
} catch {}
|
|
@@ -11436,8 +12259,11 @@ ${frontmatter.yaml}
|
|
|
11436
12259
|
try {
|
|
11437
12260
|
const parsed = JSON.parse(data);
|
|
11438
12261
|
const dbId = deps.getDbId();
|
|
12262
|
+
const schema = deps.getSchema();
|
|
11439
12263
|
if (parsed.dbId && dbId && parsed.dbId !== dbId)
|
|
11440
12264
|
return;
|
|
12265
|
+
if (parsed.schema && parsed.schema !== schema)
|
|
12266
|
+
return;
|
|
11441
12267
|
if (parsed.action === "started" && parsed.id) {
|
|
11442
12268
|
setActiveSnapshotId(parsed.id);
|
|
11443
12269
|
}
|
|
@@ -11514,6 +12340,7 @@ ${frontmatter.yaml}
|
|
|
11514
12340
|
let pageCache = new Map;
|
|
11515
12341
|
let pendingPages = new Map;
|
|
11516
12342
|
let loadGeneration = 0;
|
|
12343
|
+
let loadController = new AbortController;
|
|
11517
12344
|
let rafId = 0;
|
|
11518
12345
|
let statusEl = null;
|
|
11519
12346
|
let filterTimer = null;
|
|
@@ -11614,10 +12441,18 @@ ${frontmatter.yaml}
|
|
|
11614
12441
|
detailPanel.hidden = true;
|
|
11615
12442
|
detailPanel.innerHTML = "";
|
|
11616
12443
|
}
|
|
12444
|
+
function startNewLoadGeneration() {
|
|
12445
|
+
loadController.abort("db-grid:generation");
|
|
12446
|
+
loadController = new AbortController;
|
|
12447
|
+
loadGeneration++;
|
|
12448
|
+
}
|
|
12449
|
+
function isAbortError(err) {
|
|
12450
|
+
return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && err.name === "AbortError";
|
|
12451
|
+
}
|
|
11617
12452
|
function invalidateData() {
|
|
11618
12453
|
pageCache = new Map;
|
|
11619
12454
|
pendingPages = new Map;
|
|
11620
|
-
|
|
12455
|
+
startNewLoadGeneration();
|
|
11621
12456
|
viewport.scrollTop = 0;
|
|
11622
12457
|
resetSelectionAndDetail();
|
|
11623
12458
|
return ensurePage(0);
|
|
@@ -11636,7 +12471,7 @@ ${frontmatter.yaml}
|
|
|
11636
12471
|
filterRow.innerHTML = "";
|
|
11637
12472
|
pageCache = new Map;
|
|
11638
12473
|
pendingPages = new Map;
|
|
11639
|
-
|
|
12474
|
+
startNewLoadGeneration();
|
|
11640
12475
|
cancelAnimationFrame(rafId);
|
|
11641
12476
|
if (filterTimer)
|
|
11642
12477
|
clearTimeout(filterTimer);
|
|
@@ -11849,6 +12684,7 @@ ${frontmatter.yaml}
|
|
|
11849
12684
|
}
|
|
11850
12685
|
pageCache = new Map;
|
|
11851
12686
|
pendingPages = new Map;
|
|
12687
|
+
startNewLoadGeneration();
|
|
11852
12688
|
resetSelectionAndDetail();
|
|
11853
12689
|
renderHeader();
|
|
11854
12690
|
renderViewport();
|
|
@@ -11860,8 +12696,9 @@ ${frontmatter.yaml}
|
|
|
11860
12696
|
if (pending)
|
|
11861
12697
|
return pending;
|
|
11862
12698
|
const gen = loadGeneration;
|
|
12699
|
+
const signal = loadController.signal;
|
|
11863
12700
|
const filters = collectFilters();
|
|
11864
|
-
const promise = callbacks.fetchPage(currentTable, pageStart, PAGE_SIZE, sort, filters).then((data) => {
|
|
12701
|
+
const promise = callbacks.fetchPage(currentTable, pageStart, PAGE_SIZE, sort, filters, signal).then((data) => {
|
|
11865
12702
|
if (gen !== loadGeneration)
|
|
11866
12703
|
return;
|
|
11867
12704
|
pageCache.set(pageStart, data.rows);
|
|
@@ -11870,6 +12707,8 @@ ${frontmatter.yaml}
|
|
|
11870
12707
|
updateStatus();
|
|
11871
12708
|
renderViewport();
|
|
11872
12709
|
}).catch((err) => {
|
|
12710
|
+
if (gen !== loadGeneration || isAbortError(err))
|
|
12711
|
+
return;
|
|
11873
12712
|
if (gen === loadGeneration) {
|
|
11874
12713
|
showError(err instanceof Error ? err.message : String(err));
|
|
11875
12714
|
}
|
|
@@ -12397,12 +13236,20 @@ ${frontmatter.yaml}
|
|
|
12397
13236
|
function errorMessage(err) {
|
|
12398
13237
|
return err instanceof Error ? err.message : String(err);
|
|
12399
13238
|
}
|
|
13239
|
+
function isAbortError(err) {
|
|
13240
|
+
return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && err.name === "AbortError";
|
|
13241
|
+
}
|
|
12400
13242
|
function isSqlKind(kind) {
|
|
12401
13243
|
return kind === "sqlite" || kind === "postgresql" || kind === "mysql";
|
|
12402
13244
|
}
|
|
13245
|
+
function isPostgresKind(kind) {
|
|
13246
|
+
return kind === "postgresql";
|
|
13247
|
+
}
|
|
12403
13248
|
function isSqlView(view) {
|
|
12404
13249
|
return view === "data" || view === "query" || view === "schema" || view === "er" || view === "search" || view === "snapshot";
|
|
12405
13250
|
}
|
|
13251
|
+
var HISTORY_SSE_REFRESH_DELAY_MS = 250;
|
|
13252
|
+
var TABLE_SELECT_FETCH_DELAY_MS = 50;
|
|
12406
13253
|
function computeVisibility(kind, tab, userPrefersHistoryOpen) {
|
|
12407
13254
|
const sqlMode = isSqlKind(kind);
|
|
12408
13255
|
const tableScopedTab = tab === "data" || tab === "schema";
|
|
@@ -12421,7 +13268,8 @@ ${frontmatter.yaml}
|
|
|
12421
13268
|
searchHidden: !sqlMode || tab !== "search",
|
|
12422
13269
|
snapshotHidden: !sqlMode || tab !== "snapshot",
|
|
12423
13270
|
redisHidden: kind !== "redis",
|
|
12424
|
-
esHidden: kind !== "elasticsearch"
|
|
13271
|
+
esHidden: kind !== "elasticsearch",
|
|
13272
|
+
s3Hidden: kind !== "s3"
|
|
12425
13273
|
};
|
|
12426
13274
|
}
|
|
12427
13275
|
function normalizeViewForDb(view, db) {
|
|
@@ -12453,20 +13301,27 @@ ${frontmatter.yaml}
|
|
|
12453
13301
|
let currentDbInfo = null;
|
|
12454
13302
|
let schemaCache = null;
|
|
12455
13303
|
let lastFiles = [];
|
|
13304
|
+
let currentSchema = initial.schema ?? null;
|
|
12456
13305
|
let currentTable = initial.table ?? null;
|
|
12457
13306
|
let loadGeneration = 0;
|
|
13307
|
+
const tableSelectGuard = createAbortGuard();
|
|
13308
|
+
let historyRefreshPending = null;
|
|
12458
13309
|
const dbSelect = document.createElement("select");
|
|
12459
13310
|
dbSelect.className = "db-file-select";
|
|
12460
|
-
dbSelect.title = "Select
|
|
13311
|
+
dbSelect.title = "Select datastore";
|
|
13312
|
+
const schemaSelect = document.createElement("select");
|
|
13313
|
+
schemaSelect.className = "db-file-select db-schema-select";
|
|
13314
|
+
schemaSelect.title = "Select PostgreSQL schema";
|
|
13315
|
+
schemaSelect.hidden = true;
|
|
12461
13316
|
const dbToolbar = document.createElement("div");
|
|
12462
13317
|
dbToolbar.className = "db-toolbar";
|
|
12463
|
-
dbToolbar.
|
|
13318
|
+
dbToolbar.append(dbSelect, schemaSelect);
|
|
12464
13319
|
const tabBar = document.createElement("div");
|
|
12465
13320
|
tabBar.className = "db-tab-bar";
|
|
12466
13321
|
const tabData = createInnerTab("Data", true);
|
|
12467
13322
|
const tabSchema = createInnerTab("Schema", false);
|
|
12468
13323
|
tabBar.append(tabData, tabSchema);
|
|
12469
|
-
let currentTab = "data";
|
|
13324
|
+
let currentTab = initial.view ?? "data";
|
|
12470
13325
|
const tableList = createTableList({
|
|
12471
13326
|
onSelectTable: (table2) => selectTable(table2),
|
|
12472
13327
|
onViewCreateTable: (table2) => showDdl(table2),
|
|
@@ -12480,7 +13335,7 @@ ${frontmatter.yaml}
|
|
|
12480
13335
|
const toolsSection = document.createElement("div");
|
|
12481
13336
|
toolsSection.className = "db-icon-toolbar";
|
|
12482
13337
|
toolsSection.setAttribute("role", "toolbar");
|
|
12483
|
-
toolsSection.setAttribute("aria-label", "
|
|
13338
|
+
toolsSection.setAttribute("aria-label", "Datastore tools");
|
|
12484
13339
|
const queryBtn = makeIconButton({
|
|
12485
13340
|
label: "Query",
|
|
12486
13341
|
title: "Query Editor",
|
|
@@ -12539,7 +13394,7 @@ ${frontmatter.yaml}
|
|
|
12539
13394
|
window.addEventListener("mouseup", onUp);
|
|
12540
13395
|
});
|
|
12541
13396
|
const grid = createTableGrid({
|
|
12542
|
-
fetchPage: (table2, offset, limit, sort, filters) => fetchTablePage(table2, offset, limit, sort, filters),
|
|
13397
|
+
fetchPage: (table2, offset, limit, sort, filters, signal) => fetchTablePage(table2, offset, limit, sort, filters, signal),
|
|
12543
13398
|
getDbId: () => currentDbInfo?.id || null
|
|
12544
13399
|
});
|
|
12545
13400
|
const queryEditor = createQueryEditor({
|
|
@@ -12551,14 +13406,17 @@ ${frontmatter.yaml}
|
|
|
12551
13406
|
const schemaView = createSchemaView();
|
|
12552
13407
|
const erDiagram = createErDiagram();
|
|
12553
13408
|
const globalSearchView = createGlobalSearchView({
|
|
12554
|
-
getDbId: () => currentDbInfo?.id || null
|
|
13409
|
+
getDbId: () => currentDbInfo?.id || null,
|
|
13410
|
+
getSchema: () => currentSchema
|
|
12555
13411
|
});
|
|
12556
13412
|
const snapshotView = createSnapshotView({
|
|
12557
13413
|
getDbId: () => currentDbInfo?.id || null,
|
|
13414
|
+
getSchema: () => currentSchema,
|
|
12558
13415
|
getTables: () => schemaCache?.tables || []
|
|
12559
13416
|
});
|
|
12560
13417
|
const historyView = createQueryHistoryView({
|
|
12561
13418
|
getDbId: () => currentDbInfo?.id || null,
|
|
13419
|
+
getSchema: () => currentSchema,
|
|
12562
13420
|
copySqlToQuery: (sql) => {
|
|
12563
13421
|
queryEditor.setSql(sql);
|
|
12564
13422
|
setActiveTab("query");
|
|
@@ -12572,9 +13430,13 @@ ${frontmatter.yaml}
|
|
|
12572
13430
|
onSelectionChange: () => cb.onStateChange()
|
|
12573
13431
|
});
|
|
12574
13432
|
esExplorer.el.hidden = true;
|
|
13433
|
+
const s3Explorer = createS3Explorer({
|
|
13434
|
+
onSelectionChange: () => cb.onStateChange()
|
|
13435
|
+
});
|
|
13436
|
+
s3Explorer.el.hidden = true;
|
|
12575
13437
|
const mainContent = document.createElement("div");
|
|
12576
13438
|
mainContent.className = "db-main-content";
|
|
12577
|
-
mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el);
|
|
13439
|
+
mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el, s3Explorer.el);
|
|
12578
13440
|
queryEditor.el.hidden = true;
|
|
12579
13441
|
globalSearchView.el.hidden = true;
|
|
12580
13442
|
snapshotView.el.hidden = true;
|
|
@@ -12636,6 +13498,7 @@ ${frontmatter.yaml}
|
|
|
12636
13498
|
snapshotView.el.hidden = visibility.snapshotHidden;
|
|
12637
13499
|
redisExplorer.el.hidden = visibility.redisHidden;
|
|
12638
13500
|
esExplorer.el.hidden = visibility.esHidden;
|
|
13501
|
+
s3Explorer.el.hidden = visibility.s3Hidden;
|
|
12639
13502
|
if (!sqlMode) {
|
|
12640
13503
|
queryBtn.classList.remove("active");
|
|
12641
13504
|
erBtn.classList.remove("active");
|
|
@@ -12643,7 +13506,7 @@ ${frontmatter.yaml}
|
|
|
12643
13506
|
snapshotBtn.classList.remove("active");
|
|
12644
13507
|
}
|
|
12645
13508
|
historyToggle.classList.toggle("active", userPrefersHistoryOpen);
|
|
12646
|
-
if (!visibility.historyPaneHidden)
|
|
13509
|
+
if (!visibility.historyPaneHidden && cb.isActive())
|
|
12647
13510
|
historyView.refresh();
|
|
12648
13511
|
}
|
|
12649
13512
|
applyVisibility();
|
|
@@ -12680,6 +13543,33 @@ ${frontmatter.yaml}
|
|
|
12680
13543
|
if (list2)
|
|
12681
13544
|
setPaneStatus(list2, message, options);
|
|
12682
13545
|
}
|
|
13546
|
+
function waitForTableSelectFetch(signal) {
|
|
13547
|
+
if (signal.aborted) {
|
|
13548
|
+
return Promise.reject(new DOMException("aborted", "AbortError"));
|
|
13549
|
+
}
|
|
13550
|
+
return new Promise((resolve, reject) => {
|
|
13551
|
+
let done = false;
|
|
13552
|
+
const cleanup = () => {
|
|
13553
|
+
signal.removeEventListener("abort", onAbort);
|
|
13554
|
+
};
|
|
13555
|
+
const timer = setTimeout(() => {
|
|
13556
|
+
if (done)
|
|
13557
|
+
return;
|
|
13558
|
+
done = true;
|
|
13559
|
+
cleanup();
|
|
13560
|
+
resolve();
|
|
13561
|
+
}, TABLE_SELECT_FETCH_DELAY_MS);
|
|
13562
|
+
const onAbort = () => {
|
|
13563
|
+
if (done)
|
|
13564
|
+
return;
|
|
13565
|
+
done = true;
|
|
13566
|
+
clearTimeout(timer);
|
|
13567
|
+
cleanup();
|
|
13568
|
+
reject(new DOMException("aborted", "AbortError"));
|
|
13569
|
+
};
|
|
13570
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13571
|
+
});
|
|
13572
|
+
}
|
|
12683
13573
|
function setActiveTab(tab, updateUrl = true) {
|
|
12684
13574
|
currentTab = normalizeViewForDb(tab, currentDbInfo);
|
|
12685
13575
|
const tableScopedTab = currentTab === "data" || currentTab === "schema";
|
|
@@ -12700,6 +13590,7 @@ ${frontmatter.yaml}
|
|
|
12700
13590
|
deps.setRoute({
|
|
12701
13591
|
screen: "database",
|
|
12702
13592
|
db: currentDbInfo?.id,
|
|
13593
|
+
schema: currentSchema ?? undefined,
|
|
12703
13594
|
table: currentTable ?? undefined,
|
|
12704
13595
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12705
13596
|
range: deps.currentRange()
|
|
@@ -12714,20 +13605,54 @@ ${frontmatter.yaml}
|
|
|
12714
13605
|
showSchema(active.dataset.table);
|
|
12715
13606
|
});
|
|
12716
13607
|
async function fetchDbFiles() {
|
|
13608
|
+
if (deps.fetchDbFiles)
|
|
13609
|
+
return deps.fetchDbFiles();
|
|
12717
13610
|
const res = await deps.trackLoad(fetch("/_db/files"));
|
|
12718
13611
|
if (!res.ok)
|
|
12719
13612
|
return { files: [] };
|
|
12720
13613
|
const data = await res.json();
|
|
12721
13614
|
return data;
|
|
12722
13615
|
}
|
|
13616
|
+
function withCurrentSchema(params) {
|
|
13617
|
+
if (currentSchema)
|
|
13618
|
+
params.set("schema", currentSchema);
|
|
13619
|
+
return params;
|
|
13620
|
+
}
|
|
13621
|
+
async function fetchSchemas(dbId, preferredSchema) {
|
|
13622
|
+
const params = new URLSearchParams({ db: dbId });
|
|
13623
|
+
if (preferredSchema)
|
|
13624
|
+
params.set("schema", preferredSchema);
|
|
13625
|
+
const res = await deps.trackLoad(fetch(`/_db/schemas?${params}`));
|
|
13626
|
+
if (!res.ok) {
|
|
13627
|
+
throw new Error(await responseErrorMessage(res, "failed to fetch schemas"));
|
|
13628
|
+
}
|
|
13629
|
+
return await res.json();
|
|
13630
|
+
}
|
|
13631
|
+
function renderSchemaOptions(schemas, selected) {
|
|
13632
|
+
schemaSelect.innerHTML = "";
|
|
13633
|
+
for (const schema of schemas) {
|
|
13634
|
+
const opt = document.createElement("option");
|
|
13635
|
+
opt.value = schema;
|
|
13636
|
+
opt.textContent = schema;
|
|
13637
|
+
schemaSelect.appendChild(opt);
|
|
13638
|
+
}
|
|
13639
|
+
schemaSelect.hidden = schemas.length === 0;
|
|
13640
|
+
schemaSelect.disabled = schemas.length === 0;
|
|
13641
|
+
if (selected && schemas.includes(selected)) {
|
|
13642
|
+
schemaSelect.value = selected;
|
|
13643
|
+
} else if (schemas.length > 0) {
|
|
13644
|
+
schemaSelect.value = schemas[0];
|
|
13645
|
+
}
|
|
13646
|
+
}
|
|
12723
13647
|
async function fetchSchema(dbId) {
|
|
12724
|
-
const
|
|
13648
|
+
const params = withCurrentSchema(new URLSearchParams({ db: dbId, includeColumns: "1" }));
|
|
13649
|
+
const res = await deps.trackLoad(fetch(`/_db/schema?${params}`));
|
|
12725
13650
|
if (!res.ok) {
|
|
12726
13651
|
throw new Error(await responseErrorMessage(res, "failed to fetch schema"));
|
|
12727
13652
|
}
|
|
12728
13653
|
return await res.json();
|
|
12729
13654
|
}
|
|
12730
|
-
async function fetchTablePage(table2, offset, limit, sort, filters) {
|
|
13655
|
+
async function fetchTablePage(table2, offset, limit, sort, filters, signal) {
|
|
12731
13656
|
if (!currentDbInfo)
|
|
12732
13657
|
throw new Error("no database selected");
|
|
12733
13658
|
const params = new URLSearchParams({
|
|
@@ -12736,6 +13661,7 @@ ${frontmatter.yaml}
|
|
|
12736
13661
|
offset: String(offset),
|
|
12737
13662
|
limit: String(limit)
|
|
12738
13663
|
});
|
|
13664
|
+
withCurrentSchema(params);
|
|
12739
13665
|
if (sort) {
|
|
12740
13666
|
params.set("sort", sort.column);
|
|
12741
13667
|
params.set("dir", sort.direction);
|
|
@@ -12743,7 +13669,7 @@ ${frontmatter.yaml}
|
|
|
12743
13669
|
if (filters.length > 0) {
|
|
12744
13670
|
params.set("filters", JSON.stringify(filters));
|
|
12745
13671
|
}
|
|
12746
|
-
const res = await fetch(`/_db/table?${params}
|
|
13672
|
+
const res = await fetch(`/_db/table?${params}`, signal ? { signal } : undefined);
|
|
12747
13673
|
if (!res.ok) {
|
|
12748
13674
|
throw new Error(await responseErrorMessage(res, "failed to fetch table"));
|
|
12749
13675
|
}
|
|
@@ -12760,6 +13686,7 @@ ${frontmatter.yaml}
|
|
|
12760
13686
|
},
|
|
12761
13687
|
body: JSON.stringify({
|
|
12762
13688
|
db: currentDbInfo.id,
|
|
13689
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
12763
13690
|
sql,
|
|
12764
13691
|
saveHistory: true,
|
|
12765
13692
|
source: "browser",
|
|
@@ -12770,15 +13697,16 @@ ${frontmatter.yaml}
|
|
|
12770
13697
|
throw new Error(await responseErrorMessage(res, "failed to execute query"));
|
|
12771
13698
|
}
|
|
12772
13699
|
const result = await res.json();
|
|
12773
|
-
if (userPrefersHistoryOpen)
|
|
12774
|
-
historyView.refresh();
|
|
12775
13700
|
return result;
|
|
12776
13701
|
}
|
|
12777
|
-
async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredTable) {
|
|
13702
|
+
async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredSchema, preferredTable, targetView) {
|
|
12778
13703
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12779
13704
|
return;
|
|
13705
|
+
tableSelectGuard.dispose();
|
|
12780
13706
|
currentTable = null;
|
|
12781
|
-
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch") {
|
|
13707
|
+
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
|
|
13708
|
+
currentSchema = null;
|
|
13709
|
+
renderSchemaOptions([], null);
|
|
12782
13710
|
currentTab = "data";
|
|
12783
13711
|
tableList.render([]);
|
|
12784
13712
|
grid.clear();
|
|
@@ -12788,10 +13716,16 @@ ${frontmatter.yaml}
|
|
|
12788
13716
|
applyVisibility();
|
|
12789
13717
|
if (currentDbInfo.kind === "redis") {
|
|
12790
13718
|
esExplorer.clear();
|
|
13719
|
+
s3Explorer.clear();
|
|
12791
13720
|
await redisExplorer.load(dbId, explorerInitial?.redis);
|
|
12792
|
-
} else {
|
|
13721
|
+
} else if (currentDbInfo.kind === "elasticsearch") {
|
|
12793
13722
|
redisExplorer.clear();
|
|
13723
|
+
s3Explorer.clear();
|
|
12794
13724
|
await esExplorer.load(dbId, explorerInitial?.es);
|
|
13725
|
+
} else {
|
|
13726
|
+
redisExplorer.clear();
|
|
13727
|
+
esExplorer.clear();
|
|
13728
|
+
await s3Explorer.load(dbId, explorerInitial?.s3);
|
|
12795
13729
|
}
|
|
12796
13730
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12797
13731
|
return;
|
|
@@ -12800,10 +13734,39 @@ ${frontmatter.yaml}
|
|
|
12800
13734
|
}
|
|
12801
13735
|
redisExplorer.clear();
|
|
12802
13736
|
esExplorer.clear();
|
|
13737
|
+
s3Explorer.clear();
|
|
12803
13738
|
applyVisibility();
|
|
12804
13739
|
tableList.render([]);
|
|
12805
13740
|
setTableListStatus("Loading schema...");
|
|
12806
13741
|
grid.clear();
|
|
13742
|
+
if (isPostgresKind(currentDbInfo?.kind)) {
|
|
13743
|
+
const desiredSchema = preferredSchema !== undefined ? preferredSchema : currentSchema;
|
|
13744
|
+
const schemas = await fetchSchemas(dbId, desiredSchema).catch((err) => {
|
|
13745
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
13746
|
+
return;
|
|
13747
|
+
const message = errorMessage(err);
|
|
13748
|
+
currentSchema = null;
|
|
13749
|
+
renderSchemaOptions([], null);
|
|
13750
|
+
schemaCache = null;
|
|
13751
|
+
tableList.render([]);
|
|
13752
|
+
setTableListStatus(message, { error: true });
|
|
13753
|
+
grid.showError(message);
|
|
13754
|
+
schemaView.clear();
|
|
13755
|
+
erDiagram.clear();
|
|
13756
|
+
cb.onStateChange();
|
|
13757
|
+
return null;
|
|
13758
|
+
});
|
|
13759
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
13760
|
+
return;
|
|
13761
|
+
if (!schemas)
|
|
13762
|
+
return;
|
|
13763
|
+
const schemaNames = schemas.schemas.map((s2) => s2.name);
|
|
13764
|
+
currentSchema = schemas.selectedSchema || desiredSchema || (schemaNames.includes("public") ? "public" : schemaNames[0]) || null;
|
|
13765
|
+
renderSchemaOptions(schemaNames, currentSchema);
|
|
13766
|
+
} else {
|
|
13767
|
+
currentSchema = null;
|
|
13768
|
+
renderSchemaOptions([], null);
|
|
13769
|
+
}
|
|
12807
13770
|
const schema = await fetchSchema(dbId).catch((err) => {
|
|
12808
13771
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12809
13772
|
return;
|
|
@@ -12822,15 +13785,19 @@ ${frontmatter.yaml}
|
|
|
12822
13785
|
return;
|
|
12823
13786
|
if (!schema)
|
|
12824
13787
|
return;
|
|
13788
|
+
currentSchema = schema.schema || currentSchema;
|
|
12825
13789
|
schemaCache = schema;
|
|
12826
13790
|
tableList.render(schema.tables);
|
|
12827
13791
|
grid.clear();
|
|
12828
13792
|
schemaView.clear();
|
|
12829
13793
|
erDiagram.clear();
|
|
12830
|
-
|
|
13794
|
+
const normalizedTargetView = normalizeViewForDb(targetView, currentDbInfo);
|
|
13795
|
+
setActiveTab(normalizedTargetView === "schema" ? "schema" : "data", false);
|
|
12831
13796
|
const initialTable = preferredTable || schema.tables[0]?.name;
|
|
12832
|
-
if (initialTable) {
|
|
13797
|
+
if (initialTable && normalizedTargetView === "data") {
|
|
12833
13798
|
await selectTable(initialTable, generation);
|
|
13799
|
+
} else if (initialTable && normalizedTargetView === "schema") {
|
|
13800
|
+
await selectTableSchemaOnly(initialTable, generation);
|
|
12834
13801
|
}
|
|
12835
13802
|
applyVisibility();
|
|
12836
13803
|
cb.onStateChange();
|
|
@@ -12838,6 +13805,7 @@ ${frontmatter.yaml}
|
|
|
12838
13805
|
async function selectTable(table2, generation = loadGeneration) {
|
|
12839
13806
|
if (generation !== loadGeneration)
|
|
12840
13807
|
return;
|
|
13808
|
+
const slot = tableSelectGuard.start();
|
|
12841
13809
|
currentTable = table2;
|
|
12842
13810
|
tableList.setActive(table2);
|
|
12843
13811
|
if (!currentDbInfo)
|
|
@@ -12849,21 +13817,28 @@ ${frontmatter.yaml}
|
|
|
12849
13817
|
deps.setRoute({
|
|
12850
13818
|
screen: "database",
|
|
12851
13819
|
db: currentDbInfo.id,
|
|
13820
|
+
schema: currentSchema ?? undefined,
|
|
12852
13821
|
table: table2,
|
|
12853
13822
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12854
13823
|
range: deps.currentRange()
|
|
12855
13824
|
}, true);
|
|
12856
13825
|
try {
|
|
12857
|
-
|
|
12858
|
-
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
13826
|
+
await waitForTableSelectFetch(slot.signal);
|
|
13827
|
+
if (slot.isStale() || generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
13828
|
+
return;
|
|
13829
|
+
}
|
|
13830
|
+
const data = await deps.trackLoad(fetchTablePage(table2, 0, 200, null, [], slot.signal));
|
|
13831
|
+
if (slot.isStale() || generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
12859
13832
|
return;
|
|
12860
13833
|
}
|
|
12861
13834
|
grid.load(table2, data);
|
|
12862
13835
|
} catch (err) {
|
|
12863
|
-
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
13836
|
+
if (slot.isStale() || isAbortError(err) || generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
12864
13837
|
return;
|
|
12865
13838
|
}
|
|
12866
13839
|
grid.showError(errorMessage(err));
|
|
13840
|
+
} finally {
|
|
13841
|
+
slot.finish();
|
|
12867
13842
|
}
|
|
12868
13843
|
if (currentTab === "schema") {
|
|
12869
13844
|
const columns = await fetchColumns(table2);
|
|
@@ -12873,13 +13848,28 @@ ${frontmatter.yaml}
|
|
|
12873
13848
|
schemaView.render(table2, columns, schemaCache?.indexes || []);
|
|
12874
13849
|
}
|
|
12875
13850
|
}
|
|
13851
|
+
async function selectTableSchemaOnly(table2, generation = loadGeneration) {
|
|
13852
|
+
if (generation !== loadGeneration)
|
|
13853
|
+
return;
|
|
13854
|
+
currentTable = table2;
|
|
13855
|
+
tableList.setActive(table2);
|
|
13856
|
+
if (!currentDbInfo)
|
|
13857
|
+
return;
|
|
13858
|
+
const requestDbId = currentDbInfo.id;
|
|
13859
|
+
setActiveTab("schema", false);
|
|
13860
|
+
const columns = await fetchColumns(table2);
|
|
13861
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== requestDbId || currentTable !== table2) {
|
|
13862
|
+
return;
|
|
13863
|
+
}
|
|
13864
|
+
schemaView.render(table2, columns, schemaCache?.indexes || []);
|
|
13865
|
+
}
|
|
12876
13866
|
async function fetchColumns(table2) {
|
|
12877
13867
|
if (schemaCache?.columnsMap?.[table2]) {
|
|
12878
13868
|
return schemaCache.columnsMap[table2];
|
|
12879
13869
|
}
|
|
12880
13870
|
if (!currentDbInfo)
|
|
12881
13871
|
return [];
|
|
12882
|
-
const res = await fetch(`/_db/columns
|
|
13872
|
+
const res = await fetch(`/_db/columns?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`);
|
|
12883
13873
|
if (!res.ok)
|
|
12884
13874
|
return [];
|
|
12885
13875
|
const data = await res.json();
|
|
@@ -12897,7 +13887,7 @@ ${frontmatter.yaml}
|
|
|
12897
13887
|
return;
|
|
12898
13888
|
setActiveTab("schema");
|
|
12899
13889
|
try {
|
|
12900
|
-
const res = await fetch(`/_db/ddl
|
|
13890
|
+
const res = await fetch(`/_db/ddl?${withCurrentSchema(new URLSearchParams({ db: currentDbInfo.id, table: table2 }))}`);
|
|
12901
13891
|
if (!res.ok)
|
|
12902
13892
|
return;
|
|
12903
13893
|
const data = await res.json();
|
|
@@ -12930,6 +13920,33 @@ ${frontmatter.yaml}
|
|
|
12930
13920
|
dbSelect.addEventListener("change", () => {
|
|
12931
13921
|
handleDbSelectChange();
|
|
12932
13922
|
});
|
|
13923
|
+
schemaSelect.addEventListener("change", () => {
|
|
13924
|
+
handleSchemaSelectChange();
|
|
13925
|
+
});
|
|
13926
|
+
async function handleSchemaSelectChange() {
|
|
13927
|
+
if (!currentDbInfo || !isPostgresKind(currentDbInfo.kind))
|
|
13928
|
+
return;
|
|
13929
|
+
const schema = schemaSelect.value || null;
|
|
13930
|
+
if (!schema || schema === currentSchema)
|
|
13931
|
+
return;
|
|
13932
|
+
const dbId = currentDbInfo.id;
|
|
13933
|
+
currentSchema = schema;
|
|
13934
|
+
const generation = ++loadGeneration;
|
|
13935
|
+
clearDockerNotice();
|
|
13936
|
+
await selectDb(dbId, undefined, generation, schema, null);
|
|
13937
|
+
if (generation !== loadGeneration || currentDbInfo?.id !== dbId || currentSchema !== schema) {
|
|
13938
|
+
return;
|
|
13939
|
+
}
|
|
13940
|
+
deps.setRoute({
|
|
13941
|
+
screen: "database",
|
|
13942
|
+
db: dbId,
|
|
13943
|
+
schema,
|
|
13944
|
+
table: currentTable ?? undefined,
|
|
13945
|
+
tab: currentTab === "data" ? undefined : currentTab,
|
|
13946
|
+
range: deps.currentRange()
|
|
13947
|
+
}, true);
|
|
13948
|
+
cb.onStateChange();
|
|
13949
|
+
}
|
|
12933
13950
|
async function handleDbSelectChange() {
|
|
12934
13951
|
const dbId = dbSelect.value;
|
|
12935
13952
|
if (!dbId)
|
|
@@ -12944,13 +13961,15 @@ ${frontmatter.yaml}
|
|
|
12944
13961
|
sizeBytes: file?.sizeBytes || 0,
|
|
12945
13962
|
kind: file?.kind || "sqlite"
|
|
12946
13963
|
};
|
|
13964
|
+
currentSchema = null;
|
|
12947
13965
|
clearDockerNotice();
|
|
12948
|
-
await selectDb(dbId, undefined, generation);
|
|
13966
|
+
await selectDb(dbId, undefined, generation, null);
|
|
12949
13967
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
12950
13968
|
return;
|
|
12951
13969
|
deps.setRoute({
|
|
12952
13970
|
screen: "database",
|
|
12953
13971
|
db: dbId,
|
|
13972
|
+
schema: currentSchema ?? undefined,
|
|
12954
13973
|
table: currentTable ?? undefined,
|
|
12955
13974
|
tab: currentTab === "data" ? undefined : currentTab,
|
|
12956
13975
|
range: deps.currentRange()
|
|
@@ -12959,7 +13978,8 @@ ${frontmatter.yaml}
|
|
|
12959
13978
|
}
|
|
12960
13979
|
let pendingRedisInitial = initial.redis;
|
|
12961
13980
|
let pendingEsInitial = initial.es;
|
|
12962
|
-
|
|
13981
|
+
let pendingS3Initial = initial.s3;
|
|
13982
|
+
async function enter(db, schema, table2, view, options = {}) {
|
|
12963
13983
|
const generation = ++loadGeneration;
|
|
12964
13984
|
const filesResponse = await fetchDbFiles();
|
|
12965
13985
|
if (generation !== loadGeneration)
|
|
@@ -12975,21 +13995,23 @@ ${frontmatter.yaml}
|
|
|
12975
13995
|
if (files.length === 0) {
|
|
12976
13996
|
const opt = document.createElement("option");
|
|
12977
13997
|
opt.value = "";
|
|
12978
|
-
opt.textContent = "No
|
|
13998
|
+
opt.textContent = "No datastores found";
|
|
12979
13999
|
dbSelect.appendChild(opt);
|
|
12980
14000
|
dbSelect.disabled = true;
|
|
12981
14001
|
cb.onStateChange();
|
|
12982
14002
|
return;
|
|
12983
14003
|
}
|
|
12984
14004
|
dbSelect.disabled = false;
|
|
14005
|
+
const optionsFragment = document.createDocumentFragment();
|
|
12985
14006
|
for (const f2 of files) {
|
|
12986
14007
|
const opt = document.createElement("option");
|
|
12987
14008
|
opt.value = f2.id;
|
|
12988
14009
|
const isDocker = f2.id.startsWith("docker:");
|
|
12989
14010
|
const label = isDocker ? `${f2.name} (Docker)` : `${f2.path} (${formatSize(f2.sizeBytes)})`;
|
|
12990
14011
|
opt.textContent = label;
|
|
12991
|
-
|
|
14012
|
+
optionsFragment.appendChild(opt);
|
|
12992
14013
|
}
|
|
14014
|
+
dbSelect.appendChild(optionsFragment);
|
|
12993
14015
|
const autoSelectFirst = options.autoSelectFirst ?? true;
|
|
12994
14016
|
if (db && !files.find((f2) => f2.id === db)) {
|
|
12995
14017
|
db = autoSelectFirst ? files[0].id : null;
|
|
@@ -12997,14 +14019,17 @@ ${frontmatter.yaml}
|
|
|
12997
14019
|
if (!db && !autoSelectFirst) {
|
|
12998
14020
|
dbSelect.value = "";
|
|
12999
14021
|
currentDbInfo = null;
|
|
14022
|
+
currentSchema = null;
|
|
13000
14023
|
currentTable = null;
|
|
13001
14024
|
schemaCache = null;
|
|
14025
|
+
renderSchemaOptions([], null);
|
|
13002
14026
|
tableList.render([]);
|
|
13003
14027
|
grid.clear();
|
|
13004
14028
|
schemaView.clear();
|
|
13005
14029
|
erDiagram.clear();
|
|
13006
14030
|
redisExplorer.clear();
|
|
13007
14031
|
esExplorer.clear();
|
|
14032
|
+
s3Explorer.clear();
|
|
13008
14033
|
setActiveTab("data", false);
|
|
13009
14034
|
cb.onStateChange();
|
|
13010
14035
|
return;
|
|
@@ -13014,14 +14039,16 @@ ${frontmatter.yaml}
|
|
|
13014
14039
|
currentDbInfo = files.find((f2) => f2.id === target) || null;
|
|
13015
14040
|
const explorerInitial = {
|
|
13016
14041
|
redis: pendingRedisInitial,
|
|
13017
|
-
es: pendingEsInitial
|
|
14042
|
+
es: pendingEsInitial,
|
|
14043
|
+
s3: pendingS3Initial
|
|
13018
14044
|
};
|
|
13019
14045
|
pendingRedisInitial = undefined;
|
|
13020
14046
|
pendingEsInitial = undefined;
|
|
13021
|
-
|
|
14047
|
+
pendingS3Initial = undefined;
|
|
14048
|
+
await selectDb(target, explorerInitial, generation, schema !== undefined ? schema : currentSchema, table2, view);
|
|
13022
14049
|
if (generation !== loadGeneration || currentDbInfo?.id !== target)
|
|
13023
14050
|
return;
|
|
13024
|
-
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch") {
|
|
14051
|
+
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
|
|
13025
14052
|
cb.onStateChange();
|
|
13026
14053
|
return;
|
|
13027
14054
|
}
|
|
@@ -13051,6 +14078,8 @@ ${frontmatter.yaml}
|
|
|
13051
14078
|
function applyAnnotationTarget(target) {
|
|
13052
14079
|
if (!target || target.db !== currentDbInfo?.id)
|
|
13053
14080
|
return;
|
|
14081
|
+
if (target.schema && target.schema !== currentSchema)
|
|
14082
|
+
return;
|
|
13054
14083
|
if (target.data) {
|
|
13055
14084
|
if (target.table && target.table !== currentTable)
|
|
13056
14085
|
return;
|
|
@@ -13076,19 +14105,28 @@ ${frontmatter.yaml}
|
|
|
13076
14105
|
}
|
|
13077
14106
|
}
|
|
13078
14107
|
function handleSse(event, data) {
|
|
13079
|
-
if (userPrefersHistoryOpen)
|
|
13080
|
-
|
|
14108
|
+
if (userPrefersHistoryOpen && cb.isActive()) {
|
|
14109
|
+
if (historyRefreshPending !== null)
|
|
14110
|
+
clearTimeout(historyRefreshPending);
|
|
14111
|
+
historyRefreshPending = setTimeout(() => {
|
|
14112
|
+
historyRefreshPending = null;
|
|
14113
|
+
if (userPrefersHistoryOpen && cb.isActive()) {
|
|
14114
|
+
historyView.refresh({ force: true });
|
|
14115
|
+
}
|
|
14116
|
+
}, HISTORY_SSE_REFRESH_DELAY_MS);
|
|
14117
|
+
}
|
|
13081
14118
|
if (event === "db-snapshot" && data) {
|
|
13082
14119
|
snapshotView.handleSse(data);
|
|
13083
14120
|
}
|
|
13084
14121
|
}
|
|
13085
14122
|
function getState() {
|
|
13086
|
-
const
|
|
14123
|
+
const loaded = !!currentDbInfo;
|
|
13087
14124
|
const state = {
|
|
13088
14125
|
id: cb.tabId,
|
|
13089
|
-
dbId: currentDbInfo?.id ?? null,
|
|
13090
|
-
|
|
13091
|
-
|
|
14126
|
+
dbId: currentDbInfo?.id ?? initial.dbId ?? null,
|
|
14127
|
+
schema: loaded ? currentSchema : initial.schema ?? currentSchema,
|
|
14128
|
+
table: loaded ? currentTable : initial.table ?? currentTable ?? null,
|
|
14129
|
+
view: loaded ? currentTab : initial.view ?? currentTab
|
|
13092
14130
|
};
|
|
13093
14131
|
const sqlDraft = queryEditor.getSql();
|
|
13094
14132
|
if (sqlDraft)
|
|
@@ -13099,7 +14137,14 @@ ${frontmatter.yaml}
|
|
|
13099
14137
|
state.historyHeight = historyPane.style.height;
|
|
13100
14138
|
if (sidebar.style.width)
|
|
13101
14139
|
state.sidebarWidth = sidebar.style.width;
|
|
13102
|
-
if (
|
|
14140
|
+
if (!loaded) {
|
|
14141
|
+
if (initial.redis)
|
|
14142
|
+
state.redis = initial.redis;
|
|
14143
|
+
if (initial.es)
|
|
14144
|
+
state.es = initial.es;
|
|
14145
|
+
if (initial.s3)
|
|
14146
|
+
state.s3 = initial.s3;
|
|
14147
|
+
} else if (currentDbInfo?.kind === "redis") {
|
|
13103
14148
|
const sel = redisExplorer.getSelection();
|
|
13104
14149
|
if (sel.dbIndex !== undefined || sel.key !== undefined || sel.keyFilter !== undefined) {
|
|
13105
14150
|
state.redis = sel;
|
|
@@ -13109,6 +14154,11 @@ ${frontmatter.yaml}
|
|
|
13109
14154
|
if (sel.index !== undefined || sel.query !== undefined) {
|
|
13110
14155
|
state.es = sel;
|
|
13111
14156
|
}
|
|
14157
|
+
} else if (currentDbInfo?.kind === "s3") {
|
|
14158
|
+
const sel = s3Explorer.getSelection();
|
|
14159
|
+
if (sel.bucket !== undefined || sel.prefix !== undefined || sel.query !== undefined || sel.key !== undefined || sel.mode !== "prefix" || sel.sort !== "updated-desc") {
|
|
14160
|
+
state.s3 = sel;
|
|
14161
|
+
}
|
|
13112
14162
|
}
|
|
13113
14163
|
return state;
|
|
13114
14164
|
}
|
|
@@ -13118,6 +14168,7 @@ ${frontmatter.yaml}
|
|
|
13118
14168
|
const target = {
|
|
13119
14169
|
kind: "database",
|
|
13120
14170
|
db: currentDbInfo.id,
|
|
14171
|
+
...currentSchema ? { schema: currentSchema } : {},
|
|
13121
14172
|
...currentTable ? { table: currentTable } : {},
|
|
13122
14173
|
tab: currentTab
|
|
13123
14174
|
};
|
|
@@ -13134,15 +14185,22 @@ ${frontmatter.yaml}
|
|
|
13134
14185
|
function getLabel() {
|
|
13135
14186
|
if (!currentDbInfo)
|
|
13136
14187
|
return labelFromDbId(initial.dbId);
|
|
14188
|
+
const suffix = currentSchema ? ` / ${currentSchema}` : "";
|
|
13137
14189
|
if (currentDbInfo.id.startsWith("docker:")) {
|
|
13138
14190
|
const m = currentDbInfo.name.match(/^(\S+)/);
|
|
13139
|
-
return m ? m[1] : currentDbInfo.name
|
|
14191
|
+
return `${m ? m[1] : currentDbInfo.name}${suffix}`;
|
|
13140
14192
|
}
|
|
13141
14193
|
const lastSlash = currentDbInfo.path.lastIndexOf("/");
|
|
13142
|
-
|
|
14194
|
+
const label = lastSlash >= 0 ? currentDbInfo.path.slice(lastSlash + 1) : currentDbInfo.path;
|
|
14195
|
+
return `${label}${suffix}`;
|
|
13143
14196
|
}
|
|
13144
14197
|
function dispose() {
|
|
13145
14198
|
loadGeneration++;
|
|
14199
|
+
tableSelectGuard.dispose();
|
|
14200
|
+
if (historyRefreshPending !== null) {
|
|
14201
|
+
clearTimeout(historyRefreshPending);
|
|
14202
|
+
historyRefreshPending = null;
|
|
14203
|
+
}
|
|
13146
14204
|
grid.destroy();
|
|
13147
14205
|
schemaView.clear();
|
|
13148
14206
|
erDiagram.dispose();
|
|
@@ -13153,7 +14211,9 @@ ${frontmatter.yaml}
|
|
|
13153
14211
|
historyView.clear();
|
|
13154
14212
|
redisExplorer.dispose();
|
|
13155
14213
|
esExplorer.dispose();
|
|
14214
|
+
s3Explorer.dispose();
|
|
13156
14215
|
currentDbInfo = null;
|
|
14216
|
+
currentSchema = null;
|
|
13157
14217
|
currentTable = null;
|
|
13158
14218
|
schemaCache = null;
|
|
13159
14219
|
}
|
|
@@ -13195,6 +14255,7 @@ ${frontmatter.yaml}
|
|
|
13195
14255
|
let mounted = false;
|
|
13196
14256
|
const tabsById = new Map;
|
|
13197
14257
|
const paneReadyById = new Map;
|
|
14258
|
+
const lazyInitialById = new Map;
|
|
13198
14259
|
let activeTabId = null;
|
|
13199
14260
|
let draggingTabId = null;
|
|
13200
14261
|
let dropTargetId = null;
|
|
@@ -13202,9 +14263,40 @@ ${frontmatter.yaml}
|
|
|
13202
14263
|
let restoring = false;
|
|
13203
14264
|
let savePending = null;
|
|
13204
14265
|
let saveChain = Promise.resolve();
|
|
14266
|
+
let lastSavedTabsRaw = null;
|
|
14267
|
+
let pendingSavedTabsRaw = null;
|
|
14268
|
+
let saveController = null;
|
|
13205
14269
|
let lifecycleSeq = 0;
|
|
13206
14270
|
let enterQueue = Promise.resolve();
|
|
13207
14271
|
let unloadListenerInstalled = false;
|
|
14272
|
+
let dbFilesCache = null;
|
|
14273
|
+
function fetchDbFilesCached() {
|
|
14274
|
+
const now = Date.now();
|
|
14275
|
+
if (dbFilesCache?.value && dbFilesCache.expiresAt > now) {
|
|
14276
|
+
return Promise.resolve(dbFilesCache.value);
|
|
14277
|
+
}
|
|
14278
|
+
if (dbFilesCache?.promise)
|
|
14279
|
+
return dbFilesCache.promise;
|
|
14280
|
+
const promise = deps.trackLoad(fetch("/_db/files")).then(async (res) => {
|
|
14281
|
+
if (!res.ok) {
|
|
14282
|
+
const value2 = { files: [] };
|
|
14283
|
+
dbFilesCache = { value: value2, expiresAt: Date.now() + 1e4 };
|
|
14284
|
+
return value2;
|
|
14285
|
+
}
|
|
14286
|
+
const value = await res.json();
|
|
14287
|
+
dbFilesCache = { value, expiresAt: Date.now() + 1e4 };
|
|
14288
|
+
return value;
|
|
14289
|
+
}).catch(() => {
|
|
14290
|
+
const value = { files: [] };
|
|
14291
|
+
dbFilesCache = { value, expiresAt: Date.now() + 1e4 };
|
|
14292
|
+
return value;
|
|
14293
|
+
}).finally(() => {
|
|
14294
|
+
if (dbFilesCache?.promise === promise)
|
|
14295
|
+
dbFilesCache = null;
|
|
14296
|
+
});
|
|
14297
|
+
dbFilesCache = { promise };
|
|
14298
|
+
return promise;
|
|
14299
|
+
}
|
|
13208
14300
|
function scheduleSave() {
|
|
13209
14301
|
if (!mounted || restoring)
|
|
13210
14302
|
return;
|
|
@@ -13212,6 +14304,10 @@ ${frontmatter.yaml}
|
|
|
13212
14304
|
clearTimeout(savePending);
|
|
13213
14305
|
savePending = setTimeout(saveNow, 500);
|
|
13214
14306
|
}
|
|
14307
|
+
function abortActiveSave() {
|
|
14308
|
+
saveController?.abort();
|
|
14309
|
+
saveController = null;
|
|
14310
|
+
}
|
|
13215
14311
|
async function saveNow(options = {}) {
|
|
13216
14312
|
savePending = null;
|
|
13217
14313
|
if (!mounted || restoring)
|
|
@@ -13224,7 +14320,33 @@ ${frontmatter.yaml}
|
|
|
13224
14320
|
return;
|
|
13225
14321
|
const body = { version: 1, tabs, activeTabId };
|
|
13226
14322
|
const raw = JSON.stringify(body);
|
|
14323
|
+
if (raw === lastSavedTabsRaw)
|
|
14324
|
+
return;
|
|
14325
|
+
if (!options.keepalive && raw === pendingSavedTabsRaw)
|
|
14326
|
+
return;
|
|
14327
|
+
pendingSavedTabsRaw = raw;
|
|
14328
|
+
abortActiveSave();
|
|
14329
|
+
if (options.keepalive) {
|
|
14330
|
+
try {
|
|
14331
|
+
await fetch("/_db/tabs", {
|
|
14332
|
+
method: "PUT",
|
|
14333
|
+
headers: {
|
|
14334
|
+
"Content-Type": "application/json",
|
|
14335
|
+
"X-Code-Viewer-Action": "1"
|
|
14336
|
+
},
|
|
14337
|
+
body: raw,
|
|
14338
|
+
keepalive: true
|
|
14339
|
+
});
|
|
14340
|
+
lastSavedTabsRaw = raw;
|
|
14341
|
+
} catch {} finally {
|
|
14342
|
+
if (pendingSavedTabsRaw === raw)
|
|
14343
|
+
pendingSavedTabsRaw = null;
|
|
14344
|
+
}
|
|
14345
|
+
return;
|
|
14346
|
+
}
|
|
13227
14347
|
saveChain = saveChain.catch(() => {}).then(async () => {
|
|
14348
|
+
const controller = new AbortController;
|
|
14349
|
+
saveController = controller;
|
|
13228
14350
|
try {
|
|
13229
14351
|
await fetch("/_db/tabs", {
|
|
13230
14352
|
method: "PUT",
|
|
@@ -13233,9 +14355,17 @@ ${frontmatter.yaml}
|
|
|
13233
14355
|
"X-Code-Viewer-Action": "1"
|
|
13234
14356
|
},
|
|
13235
14357
|
body: raw,
|
|
13236
|
-
|
|
14358
|
+
signal: controller.signal
|
|
13237
14359
|
});
|
|
13238
|
-
|
|
14360
|
+
lastSavedTabsRaw = raw;
|
|
14361
|
+
} catch (err) {
|
|
14362
|
+
if (!isAbortError(err)) {}
|
|
14363
|
+
} finally {
|
|
14364
|
+
if (saveController === controller)
|
|
14365
|
+
saveController = null;
|
|
14366
|
+
if (pendingSavedTabsRaw === raw)
|
|
14367
|
+
pendingSavedTabsRaw = null;
|
|
14368
|
+
}
|
|
13239
14369
|
});
|
|
13240
14370
|
await saveChain;
|
|
13241
14371
|
}
|
|
@@ -13288,6 +14418,9 @@ ${frontmatter.yaml}
|
|
|
13288
14418
|
if (!restoring)
|
|
13289
14419
|
syncActiveRoute();
|
|
13290
14420
|
scheduleSave();
|
|
14421
|
+
if (mounted && !restoring) {
|
|
14422
|
+
ensureInitialEnter(id)?.catch(() => {});
|
|
14423
|
+
}
|
|
13291
14424
|
}
|
|
13292
14425
|
function syncActiveRoute() {
|
|
13293
14426
|
if (!activeTabId)
|
|
@@ -13299,6 +14432,7 @@ ${frontmatter.yaml}
|
|
|
13299
14432
|
deps.setRoute({
|
|
13300
14433
|
screen: "database",
|
|
13301
14434
|
db: s2.dbId ?? undefined,
|
|
14435
|
+
schema: s2.schema ?? undefined,
|
|
13302
14436
|
table: s2.table ?? undefined,
|
|
13303
14437
|
tab: s2.view === "data" ? undefined : s2.view,
|
|
13304
14438
|
range: deps.currentRange()
|
|
@@ -13319,10 +14453,12 @@ ${frontmatter.yaml}
|
|
|
13319
14453
|
for (const tab of tabs) {
|
|
13320
14454
|
const key = JSON.stringify({
|
|
13321
14455
|
dbId: tab.dbId,
|
|
14456
|
+
schema: tab.schema ?? null,
|
|
13322
14457
|
table: tab.table,
|
|
13323
14458
|
view: tab.view,
|
|
13324
14459
|
redis: tab.redis ?? null,
|
|
13325
|
-
es: tab.es ?? null
|
|
14460
|
+
es: tab.es ?? null,
|
|
14461
|
+
s3: tab.s3 ?? null
|
|
13326
14462
|
});
|
|
13327
14463
|
if (seen.has(key))
|
|
13328
14464
|
continue;
|
|
@@ -13537,7 +14673,7 @@ ${frontmatter.yaml}
|
|
|
13537
14673
|
closeTab(id);
|
|
13538
14674
|
}
|
|
13539
14675
|
});
|
|
13540
|
-
const pane = createTabPane(deps, {
|
|
14676
|
+
const pane = createTabPane({ ...deps, fetchDbFiles: fetchDbFilesCached }, {
|
|
13541
14677
|
tabId: id,
|
|
13542
14678
|
isActive: () => activeTabId === id,
|
|
13543
14679
|
canSyncRoute: () => !restoring,
|
|
@@ -13549,6 +14685,7 @@ ${frontmatter.yaml}
|
|
|
13549
14685
|
}
|
|
13550
14686
|
}, {
|
|
13551
14687
|
dbId: initial?.dbId ?? null,
|
|
14688
|
+
schema: initial?.schema ?? null,
|
|
13552
14689
|
table: initial?.table ?? null,
|
|
13553
14690
|
view: initial?.view ?? undefined,
|
|
13554
14691
|
sqlDraft: initial?.sqlDraft,
|
|
@@ -13556,13 +14693,15 @@ ${frontmatter.yaml}
|
|
|
13556
14693
|
historyHeight: initial?.historyHeight,
|
|
13557
14694
|
sidebarWidth: initial?.sidebarWidth,
|
|
13558
14695
|
redis: initial?.redis,
|
|
13559
|
-
es: initial?.es
|
|
14696
|
+
es: initial?.es,
|
|
14697
|
+
s3: initial?.s3
|
|
13560
14698
|
});
|
|
13561
14699
|
pane.el.hidden = true;
|
|
13562
14700
|
tabsList.appendChild(chip);
|
|
13563
14701
|
tabHost.appendChild(pane.el);
|
|
13564
14702
|
tabsById.set(id, { pane, chip, label: labelEl, closeBtn });
|
|
13565
|
-
|
|
14703
|
+
if (options.activate !== false)
|
|
14704
|
+
setActive(id);
|
|
13566
14705
|
if (!options.deferInitialEnter) {
|
|
13567
14706
|
startInitialEnter(id, initial, options);
|
|
13568
14707
|
}
|
|
@@ -13577,7 +14716,7 @@ ${frontmatter.yaml}
|
|
|
13577
14716
|
if (options.annotationTarget)
|
|
13578
14717
|
restoring = true;
|
|
13579
14718
|
try {
|
|
13580
|
-
await pane.enter(initial?.dbId || undefined, initial?.table || undefined, initial?.view || undefined, options);
|
|
14719
|
+
await pane.enter(initial?.dbId || undefined, initial?.schema || undefined, initial?.table || undefined, initial?.view || undefined, options);
|
|
13581
14720
|
refreshChipLabel(id);
|
|
13582
14721
|
} finally {
|
|
13583
14722
|
if (options.annotationTarget)
|
|
@@ -13588,6 +14727,16 @@ ${frontmatter.yaml}
|
|
|
13588
14727
|
paneReadyById.set(id, ready);
|
|
13589
14728
|
return ready;
|
|
13590
14729
|
}
|
|
14730
|
+
function ensureInitialEnter(id) {
|
|
14731
|
+
const ready = paneReadyById.get(id);
|
|
14732
|
+
if (ready)
|
|
14733
|
+
return ready;
|
|
14734
|
+
if (!lazyInitialById.has(id))
|
|
14735
|
+
return null;
|
|
14736
|
+
const initial = lazyInitialById.get(id);
|
|
14737
|
+
lazyInitialById.delete(id);
|
|
14738
|
+
return startInitialEnter(id, initial, { autoSelectFirst: false });
|
|
14739
|
+
}
|
|
13591
14740
|
function closeTab(id) {
|
|
13592
14741
|
const entry = tabsById.get(id);
|
|
13593
14742
|
if (!entry)
|
|
@@ -13598,6 +14747,7 @@ ${frontmatter.yaml}
|
|
|
13598
14747
|
entry.chip.remove();
|
|
13599
14748
|
tabsById.delete(id);
|
|
13600
14749
|
paneReadyById.delete(id);
|
|
14750
|
+
lazyInitialById.delete(id);
|
|
13601
14751
|
closeDbIfUnused(closedDbId);
|
|
13602
14752
|
if (activeTabId !== id) {
|
|
13603
14753
|
scheduleSave();
|
|
@@ -13614,35 +14764,46 @@ ${frontmatter.yaml}
|
|
|
13614
14764
|
newTabBtn.addEventListener("click", () => {
|
|
13615
14765
|
openTab({ dbId: null, table: null, view: "data" }, { autoSelectFirst: false });
|
|
13616
14766
|
});
|
|
13617
|
-
function routeMatchesState(state, db, table2, view) {
|
|
13618
|
-
if (!db && (table2 || view) && !state.dbId)
|
|
14767
|
+
function routeMatchesState(state, db, schema, table2, view) {
|
|
14768
|
+
if (!db && (schema || table2 || view) && !state.dbId)
|
|
13619
14769
|
return false;
|
|
13620
14770
|
if (db !== undefined && state.dbId !== db)
|
|
13621
14771
|
return false;
|
|
14772
|
+
if (schema !== undefined) {
|
|
14773
|
+
const routeSchema = schema || "public";
|
|
14774
|
+
const stateSchema = state.schema || "public";
|
|
14775
|
+
if (stateSchema !== routeSchema)
|
|
14776
|
+
return false;
|
|
14777
|
+
}
|
|
13622
14778
|
if (table2 !== undefined && state.table !== table2)
|
|
13623
14779
|
return false;
|
|
13624
14780
|
if (view !== undefined && state.view !== view)
|
|
13625
14781
|
return false;
|
|
13626
14782
|
return true;
|
|
13627
14783
|
}
|
|
13628
|
-
async function applyRouteToTab(db, table2, view, options = {}) {
|
|
13629
|
-
async function enterPane(pane, targetDb, targetTable, targetView, enterOptions) {
|
|
14784
|
+
async function applyRouteToTab(db, schema, table2, view, options = {}) {
|
|
14785
|
+
async function enterPane(pane, targetDb, targetSchema, targetTable, targetView, enterOptions) {
|
|
13630
14786
|
if (!enterOptions?.annotationTarget) {
|
|
13631
|
-
await pane.enter(targetDb, targetTable, targetView, enterOptions);
|
|
14787
|
+
await pane.enter(targetDb, targetSchema, targetTable, targetView, enterOptions);
|
|
13632
14788
|
return;
|
|
13633
14789
|
}
|
|
13634
14790
|
restoring = true;
|
|
13635
14791
|
try {
|
|
13636
|
-
await pane.enter(targetDb, targetTable, targetView, enterOptions);
|
|
14792
|
+
await pane.enter(targetDb, targetSchema, targetTable, targetView, enterOptions);
|
|
13637
14793
|
} finally {
|
|
13638
14794
|
restoring = false;
|
|
13639
14795
|
}
|
|
13640
14796
|
}
|
|
13641
14797
|
for (const [id2, entry] of tabsById) {
|
|
13642
|
-
if (routeMatchesState(entry.pane.getState(), db, table2, view)) {
|
|
14798
|
+
if (routeMatchesState(entry.pane.getState(), db, schema, table2, view)) {
|
|
13643
14799
|
setActive(id2);
|
|
14800
|
+
const ready2 = ensureInitialEnter(id2);
|
|
14801
|
+
if (ready2)
|
|
14802
|
+
await ready2;
|
|
14803
|
+
if (!mounted)
|
|
14804
|
+
return;
|
|
13644
14805
|
if (options.annotationTarget) {
|
|
13645
|
-
await enterPane(entry.pane, db, table2, view, options);
|
|
14806
|
+
await enterPane(entry.pane, db, schema, table2, view, options);
|
|
13646
14807
|
if (!mounted)
|
|
13647
14808
|
return;
|
|
13648
14809
|
}
|
|
@@ -13654,7 +14815,7 @@ ${frontmatter.yaml}
|
|
|
13654
14815
|
const targetId = activeTabId;
|
|
13655
14816
|
const active = tabsById.get(activeTabId);
|
|
13656
14817
|
if (active) {
|
|
13657
|
-
await enterPane(active.pane, db, table2, view, options);
|
|
14818
|
+
await enterPane(active.pane, db, schema, table2, view, options);
|
|
13658
14819
|
if (!mounted)
|
|
13659
14820
|
return;
|
|
13660
14821
|
refreshChipLabel(targetId);
|
|
@@ -13663,6 +14824,7 @@ ${frontmatter.yaml}
|
|
|
13663
14824
|
}
|
|
13664
14825
|
const id = openTab({
|
|
13665
14826
|
dbId: db ?? null,
|
|
14827
|
+
schema: schema ?? null,
|
|
13666
14828
|
table: table2 ?? null,
|
|
13667
14829
|
view: view ?? "data"
|
|
13668
14830
|
}, { autoSelectFirst: db === undefined, ...options });
|
|
@@ -13681,7 +14843,17 @@ ${frontmatter.yaml}
|
|
|
13681
14843
|
return null;
|
|
13682
14844
|
}
|
|
13683
14845
|
}
|
|
13684
|
-
|
|
14846
|
+
function parseSseSchema(data) {
|
|
14847
|
+
if (!data)
|
|
14848
|
+
return null;
|
|
14849
|
+
try {
|
|
14850
|
+
const parsed = JSON.parse(data);
|
|
14851
|
+
return typeof parsed.schema === "string" ? parsed.schema : null;
|
|
14852
|
+
} catch {
|
|
14853
|
+
return null;
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
async function doEnter(seq, db, schema, table2, view, options = {}) {
|
|
13685
14857
|
if (seq !== lifecycleSeq)
|
|
13686
14858
|
return;
|
|
13687
14859
|
const firstMount = !mounted;
|
|
@@ -13710,8 +14882,6 @@ ${frontmatter.yaml}
|
|
|
13710
14882
|
return;
|
|
13711
14883
|
if (restored && restored.tabs.length > 0) {
|
|
13712
14884
|
restoring = true;
|
|
13713
|
-
const restoredIds = [];
|
|
13714
|
-
const restoredById = new Map;
|
|
13715
14885
|
try {
|
|
13716
14886
|
const restoredTabs = dedupeTabs(restored.tabs);
|
|
13717
14887
|
for (const t2 of restoredTabs) {
|
|
@@ -13719,33 +14889,26 @@ ${frontmatter.yaml}
|
|
|
13719
14889
|
return;
|
|
13720
14890
|
const id = openTab(t2, {
|
|
13721
14891
|
autoSelectFirst: false,
|
|
13722
|
-
deferInitialEnter: true
|
|
14892
|
+
deferInitialEnter: true,
|
|
14893
|
+
activate: false
|
|
13723
14894
|
});
|
|
13724
|
-
|
|
13725
|
-
restoredById.set(id, t2);
|
|
14895
|
+
lazyInitialById.set(id, t2);
|
|
13726
14896
|
}
|
|
13727
14897
|
const targetId = restored.activeTabId && tabsById.has(restored.activeTabId) ? restored.activeTabId : tabsById.keys().next().value;
|
|
13728
14898
|
if (targetId)
|
|
13729
14899
|
setActive(targetId);
|
|
13730
14900
|
if (targetId) {
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
14901
|
+
const ready = ensureInitialEnter(targetId);
|
|
14902
|
+
if (ready)
|
|
14903
|
+
await ready;
|
|
13734
14904
|
}
|
|
13735
14905
|
if (!mounted || seq !== lifecycleSeq)
|
|
13736
14906
|
return;
|
|
13737
|
-
for (const id of restoredIds) {
|
|
13738
|
-
if (id === targetId)
|
|
13739
|
-
continue;
|
|
13740
|
-
startInitialEnter(id, restoredById.get(id), {
|
|
13741
|
-
autoSelectFirst: false
|
|
13742
|
-
}).catch(() => {});
|
|
13743
|
-
}
|
|
13744
14907
|
} finally {
|
|
13745
14908
|
restoring = false;
|
|
13746
14909
|
}
|
|
13747
|
-
if (db || table2 || view) {
|
|
13748
|
-
await applyRouteToTab(db, table2, view, {
|
|
14910
|
+
if (db || schema || table2 || view) {
|
|
14911
|
+
await applyRouteToTab(db, schema ?? null, table2, view, {
|
|
13749
14912
|
...options,
|
|
13750
14913
|
reuseActiveTab: options.reuseActiveTab ?? true
|
|
13751
14914
|
});
|
|
@@ -13770,6 +14933,7 @@ ${frontmatter.yaml}
|
|
|
13770
14933
|
if (tabsById.size === 0) {
|
|
13771
14934
|
const id = openTab({
|
|
13772
14935
|
dbId: db || null,
|
|
14936
|
+
schema: schema || null,
|
|
13773
14937
|
table: table2 || null,
|
|
13774
14938
|
view: view || "data"
|
|
13775
14939
|
}, { autoSelectFirst: !db, ...options });
|
|
@@ -13785,8 +14949,8 @@ ${frontmatter.yaml}
|
|
|
13785
14949
|
const active = tabsById.get(activeTabId);
|
|
13786
14950
|
if (!active)
|
|
13787
14951
|
return;
|
|
13788
|
-
if (db || table2 || view) {
|
|
13789
|
-
await applyRouteToTab(db, table2, view, {
|
|
14952
|
+
if (db || schema || table2 || view) {
|
|
14953
|
+
await applyRouteToTab(db, schema ?? null, table2, view, {
|
|
13790
14954
|
...options,
|
|
13791
14955
|
reuseActiveTab: options.reuseActiveTab ?? true
|
|
13792
14956
|
});
|
|
@@ -13794,9 +14958,9 @@ ${frontmatter.yaml}
|
|
|
13794
14958
|
syncActiveRoute();
|
|
13795
14959
|
}
|
|
13796
14960
|
}
|
|
13797
|
-
async function enter(db, table2, view, options = {}) {
|
|
14961
|
+
async function enter(db, schema, table2, view, options = {}) {
|
|
13798
14962
|
const seq = lifecycleSeq;
|
|
13799
|
-
enterQueue = enterQueue.catch(() => {}).then(() => doEnter(seq, db, table2, view, options));
|
|
14963
|
+
enterQueue = enterQueue.catch(() => {}).then(() => doEnter(seq, db, schema, table2, view, options));
|
|
13800
14964
|
await enterQueue;
|
|
13801
14965
|
}
|
|
13802
14966
|
function leave() {
|
|
@@ -13808,6 +14972,7 @@ ${frontmatter.yaml}
|
|
|
13808
14972
|
entry.pane.dispose();
|
|
13809
14973
|
}
|
|
13810
14974
|
tabsById.clear();
|
|
14975
|
+
dbFilesCache = null;
|
|
13811
14976
|
tabsList.innerHTML = "";
|
|
13812
14977
|
tabHost.innerHTML = "";
|
|
13813
14978
|
activeTabId = null;
|
|
@@ -13846,8 +15011,12 @@ ${frontmatter.yaml}
|
|
|
13846
15011
|
if (!mounted)
|
|
13847
15012
|
return;
|
|
13848
15013
|
const dbId = parseSseDbId(data);
|
|
15014
|
+
const schema = parseSseSchema(data);
|
|
13849
15015
|
for (const [, entry] of tabsById) {
|
|
13850
|
-
|
|
15016
|
+
const state = entry.pane.getState();
|
|
15017
|
+
if (dbId && state.dbId !== dbId)
|
|
15018
|
+
continue;
|
|
15019
|
+
if (schema && state.schema !== schema)
|
|
13851
15020
|
continue;
|
|
13852
15021
|
entry.pane.handleSse(event, data);
|
|
13853
15022
|
}
|
|
@@ -15417,12 +16586,12 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
15417
16586
|
]
|
|
15418
16587
|
},
|
|
15419
16588
|
database: {
|
|
15420
|
-
nav: "
|
|
15421
|
-
title: "
|
|
15422
|
-
intro: "Browse SQLite files
|
|
16589
|
+
nav: "Datastores",
|
|
16590
|
+
title: "Datastore Viewer",
|
|
16591
|
+
intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, and S3-compatible object stores from one local viewer.",
|
|
15423
16592
|
groups: [
|
|
15424
16593
|
{
|
|
15425
|
-
title: "Supported
|
|
16594
|
+
title: "Supported datastores",
|
|
15426
16595
|
blocks: [
|
|
15427
16596
|
{
|
|
15428
16597
|
kind: "table",
|
|
@@ -15438,6 +16607,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
15438
16607
|
[
|
|
15439
16608
|
"PostgreSQL",
|
|
15440
16609
|
"Detected from docker-compose.yml services. Multiple databases per server are listed."
|
|
16610
|
+
],
|
|
16611
|
+
[
|
|
16612
|
+
"S3 / MinIO / LocalStack",
|
|
16613
|
+
"Detected from docker-compose.yml services. Browse buckets, search objects, sort by update time, and preview common file types."
|
|
15441
16614
|
]
|
|
15442
16615
|
]
|
|
15443
16616
|
}
|
|
@@ -15780,12 +16953,12 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
15780
16953
|
]
|
|
15781
16954
|
},
|
|
15782
16955
|
database: {
|
|
15783
|
-
nav: "
|
|
15784
|
-
title: "
|
|
15785
|
-
intro: "SQLite
|
|
16956
|
+
nav: "データストア",
|
|
16957
|
+
title: "データストアビューア",
|
|
16958
|
+
intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
|
|
15786
16959
|
groups: [
|
|
15787
16960
|
{
|
|
15788
|
-
title: "
|
|
16961
|
+
title: "対応データストア",
|
|
15789
16962
|
blocks: [
|
|
15790
16963
|
{
|
|
15791
16964
|
kind: "table",
|
|
@@ -15801,6 +16974,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
15801
16974
|
[
|
|
15802
16975
|
"PostgreSQL",
|
|
15803
16976
|
"docker-compose.yml のサービスから検出。同一サーバー上の複数データベースを一覧表示します。"
|
|
16977
|
+
],
|
|
16978
|
+
[
|
|
16979
|
+
"S3 / MinIO / LocalStack",
|
|
16980
|
+
"docker-compose.yml のサービスから検出。バケット閲覧、オブジェクト検索、更新日時順表示、主要ファイル形式のプレビューに対応します。"
|
|
15804
16981
|
]
|
|
15805
16982
|
]
|
|
15806
16983
|
}
|
|
@@ -15854,7 +17031,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
15854
17031
|
},
|
|
15855
17032
|
{
|
|
15856
17033
|
kind: "command",
|
|
15857
|
-
title: "
|
|
17034
|
+
title: "データストア一覧",
|
|
15858
17035
|
command: "code-viewer query list"
|
|
15859
17036
|
},
|
|
15860
17037
|
{
|
|
@@ -18665,7 +19842,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
18665
19842
|
setRoute,
|
|
18666
19843
|
currentRange,
|
|
18667
19844
|
appendScopeParams,
|
|
18668
|
-
isAbortError,
|
|
19845
|
+
isAbortError: isAbortError2,
|
|
18669
19846
|
scrollToFile,
|
|
18670
19847
|
applySourceRouteToShell,
|
|
18671
19848
|
fileSourceTarget,
|
|
@@ -19012,7 +20189,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
19012
20189
|
state.status.textContent = response.engine + (state.grepRegex ? " regex" : " plain") + (response.truncated ? " truncated" : "") + " - " + state.items.length + " results";
|
|
19013
20190
|
renderPalette(state);
|
|
19014
20191
|
}).catch((err) => {
|
|
19015
|
-
if (
|
|
20192
|
+
if (isAbortError2(err))
|
|
19016
20193
|
return;
|
|
19017
20194
|
state.status.textContent = "Search failed";
|
|
19018
20195
|
});
|
|
@@ -20473,7 +21650,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20473
21650
|
setPageMode,
|
|
20474
21651
|
currentRange,
|
|
20475
21652
|
trackLoad,
|
|
20476
|
-
isAbortError,
|
|
21653
|
+
isAbortError: isAbortError2,
|
|
20477
21654
|
loadRepo,
|
|
20478
21655
|
repoRoute,
|
|
20479
21656
|
repoFileTargetFromRoute,
|
|
@@ -20793,16 +21970,6 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20793
21970
|
}
|
|
20794
21971
|
function renderSourceUnsupported(card, target) {
|
|
20795
21972
|
const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
|
|
20796
|
-
const view = document.createElement("div");
|
|
20797
|
-
view.className = "gdp-source-viewer unsupported";
|
|
20798
|
-
const content = document.createElement("div");
|
|
20799
|
-
content.className = "gdp-source-unsupported-content";
|
|
20800
|
-
const title = document.createElement("strong");
|
|
20801
|
-
title.className = "gdp-source-unsupported-title";
|
|
20802
|
-
title.textContent = "Preview unavailable";
|
|
20803
|
-
const message = document.createElement("div");
|
|
20804
|
-
message.className = "gdp-source-unsupported-message";
|
|
20805
|
-
message.textContent = "This file type cannot be previewed safely in the browser.";
|
|
20806
21973
|
const info = createSourceFileInfo(target, "unsupported file");
|
|
20807
21974
|
const link2 = document.createElement("a");
|
|
20808
21975
|
link2.className = "gdp-btn gdp-btn-sm gdp-source-download";
|
|
@@ -20810,23 +21977,17 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
20810
21977
|
link2.textContent = "Download raw";
|
|
20811
21978
|
link2.target = "_blank";
|
|
20812
21979
|
link2.rel = "noreferrer";
|
|
20813
|
-
|
|
20814
|
-
|
|
21980
|
+
const view = renderUnsupportedPreview({
|
|
21981
|
+
message: "This file type cannot be previewed safely in the browser.",
|
|
21982
|
+
extraChildren: [info, link2]
|
|
21983
|
+
});
|
|
20815
21984
|
if (body)
|
|
20816
21985
|
body.replaceWith(view);
|
|
20817
21986
|
else
|
|
20818
21987
|
card.appendChild(view);
|
|
20819
21988
|
}
|
|
20820
21989
|
function renderHtmlPreview(target, html) {
|
|
20821
|
-
|
|
20822
|
-
preview.className = "gdp-html-preview";
|
|
20823
|
-
const frame = document.createElement("iframe");
|
|
20824
|
-
frame.title = `${target.path} preview`;
|
|
20825
|
-
frame.sandbox.value = "";
|
|
20826
|
-
frame.referrerPolicy = "no-referrer";
|
|
20827
|
-
frame.srcdoc = html;
|
|
20828
|
-
preview.appendChild(frame);
|
|
20829
|
-
return preview;
|
|
21990
|
+
return renderHtmlPreviewFrame(`${target.path} preview`, html);
|
|
20830
21991
|
}
|
|
20831
21992
|
function createSourceFileInfo(target, kind) {
|
|
20832
21993
|
const info = document.createElement("div");
|
|
@@ -21523,7 +22684,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
21523
22684
|
renderedEnd = -1;
|
|
21524
22685
|
render();
|
|
21525
22686
|
}).catch((err) => {
|
|
21526
|
-
if (!
|
|
22687
|
+
if (!isAbortError2(err)) {
|
|
21527
22688
|
failedPages.add(page);
|
|
21528
22689
|
renderedStart = -1;
|
|
21529
22690
|
renderedEnd = -1;
|
|
@@ -21715,35 +22876,16 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
21715
22876
|
const url = buildRawFileUrl(target);
|
|
21716
22877
|
const info = createSourceFileInfo(target, mediaKind);
|
|
21717
22878
|
view.appendChild(info);
|
|
21718
|
-
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
view.appendChild(video);
|
|
21724
|
-
} else if (mediaKind === "audio") {
|
|
21725
|
-
const audio = document.createElement("audio");
|
|
21726
|
-
audio.src = url;
|
|
21727
|
-
audio.controls = true;
|
|
21728
|
-
audio.preload = "metadata";
|
|
21729
|
-
view.appendChild(audio);
|
|
21730
|
-
} else if (mediaKind === "pdf") {
|
|
21731
|
-
const frame = document.createElement("iframe");
|
|
21732
|
-
frame.src = url;
|
|
21733
|
-
frame.title = target.path;
|
|
21734
|
-
frame.loading = "lazy";
|
|
21735
|
-
view.appendChild(frame);
|
|
21736
|
-
} else {
|
|
21737
|
-
const img = document.createElement("img");
|
|
21738
|
-
img.src = url;
|
|
21739
|
-
img.alt = "";
|
|
21740
|
-
img.addEventListener("load", () => {
|
|
22879
|
+
appendMediaEmbed(view, {
|
|
22880
|
+
url,
|
|
22881
|
+
kind: mediaKind,
|
|
22882
|
+
title: target.path,
|
|
22883
|
+
onImageLoad: (img) => {
|
|
21741
22884
|
const resolution = document.createElement("span");
|
|
21742
22885
|
resolution.textContent = `${img.naturalWidth} x ${img.naturalHeight}`;
|
|
21743
22886
|
info.appendChild(resolution);
|
|
21744
|
-
}
|
|
21745
|
-
|
|
21746
|
-
}
|
|
22887
|
+
}
|
|
22888
|
+
});
|
|
21747
22889
|
if (body)
|
|
21748
22890
|
body.replaceWith(view);
|
|
21749
22891
|
else
|
|
@@ -21916,7 +23058,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
21916
23058
|
if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
|
|
21917
23059
|
return;
|
|
21918
23060
|
finishSourceLoad(req);
|
|
21919
|
-
if (
|
|
23061
|
+
if (isAbortError2(err)) {
|
|
21920
23062
|
renderSourceCancelled(card, target);
|
|
21921
23063
|
return;
|
|
21922
23064
|
}
|
|
@@ -22439,7 +23581,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22439
23581
|
setPageMode,
|
|
22440
23582
|
currentRange,
|
|
22441
23583
|
trackLoad,
|
|
22442
|
-
isAbortError,
|
|
23584
|
+
isAbortError: isAbortError2,
|
|
22443
23585
|
loadRepo: () => REPO_VIEW.loadRepo(),
|
|
22444
23586
|
repoRoute: (ref, path) => REPO_VIEW.repoRoute(ref, path),
|
|
22445
23587
|
repoFileTargetFromRoute,
|
|
@@ -22523,7 +23665,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22523
23665
|
setRoute,
|
|
22524
23666
|
currentRange,
|
|
22525
23667
|
appendScopeParams,
|
|
22526
|
-
isAbortError,
|
|
23668
|
+
isAbortError: isAbortError2,
|
|
22527
23669
|
scrollToFile: (path, line) => DIFF_VIEW.scrollToFile(path, line),
|
|
22528
23670
|
applySourceRouteToShell,
|
|
22529
23671
|
fileSourceTarget,
|
|
@@ -22539,7 +23681,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22539
23681
|
repo: "Repository",
|
|
22540
23682
|
diff: "Diff Viewer",
|
|
22541
23683
|
history: "History",
|
|
22542
|
-
database: "
|
|
23684
|
+
database: "Datastores",
|
|
22543
23685
|
help: "Help"
|
|
22544
23686
|
},
|
|
22545
23687
|
global: {
|
|
@@ -22599,7 +23741,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22599
23741
|
display: "Display",
|
|
22600
23742
|
language: "Language",
|
|
22601
23743
|
fileListFontSize: "UI font size",
|
|
22602
|
-
fileListFontSizeHelp: "Applies to the file sidebar and
|
|
23744
|
+
fileListFontSizeHelp: "Applies to the file sidebar and datastore UI.",
|
|
22603
23745
|
codeFontSize: "Code font size",
|
|
22604
23746
|
sizeSmall: "Small",
|
|
22605
23747
|
sizeRegular: "Regular",
|
|
@@ -22629,7 +23771,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22629
23771
|
repo: "リポジトリ",
|
|
22630
23772
|
diff: "Diff ビューア",
|
|
22631
23773
|
history: "履歴",
|
|
22632
|
-
database: "
|
|
23774
|
+
database: "データストア",
|
|
22633
23775
|
help: "ヘルプ"
|
|
22634
23776
|
},
|
|
22635
23777
|
global: {
|
|
@@ -22689,7 +23831,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
22689
23831
|
display: "表示",
|
|
22690
23832
|
language: "言語",
|
|
22691
23833
|
fileListFontSize: "UIの文字サイズ",
|
|
22692
|
-
fileListFontSizeHelp: "
|
|
23834
|
+
fileListFontSizeHelp: "ファイル一覧とデータストア画面に適用されます。",
|
|
22693
23835
|
codeFontSize: "コード表示の文字サイズ",
|
|
22694
23836
|
sizeSmall: "小",
|
|
22695
23837
|
sizeRegular: "標準",
|
|
@@ -23106,7 +24248,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
23106
24248
|
"'": "'"
|
|
23107
24249
|
})[c2]);
|
|
23108
24250
|
}
|
|
23109
|
-
function
|
|
24251
|
+
function isAbortError2(err) {
|
|
23110
24252
|
return err instanceof DOMException ? err.name === "AbortError" : !!err && typeof err === "object" && ("name" in err) && err.name === "AbortError";
|
|
23111
24253
|
}
|
|
23112
24254
|
function currentRange() {
|
|
@@ -23789,7 +24931,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
23789
24931
|
return Promise.resolve(null);
|
|
23790
24932
|
}
|
|
23791
24933
|
if (STATE.route.screen === "database") {
|
|
23792
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
24934
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
23793
24935
|
setStatus("live");
|
|
23794
24936
|
return Promise.resolve(null);
|
|
23795
24937
|
}
|
|
@@ -23860,7 +25002,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
23860
25002
|
HISTORY_VIEW.enterHistory();
|
|
23861
25003
|
} else if (STATE.route.screen === "database") {
|
|
23862
25004
|
setStatus("live");
|
|
23863
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
25005
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
23864
25006
|
} else
|
|
23865
25007
|
load();
|
|
23866
25008
|
syncLineRefPill();
|
|
@@ -24010,7 +25152,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24010
25152
|
cancelActiveSourceLoad("navigation");
|
|
24011
25153
|
setPageMode();
|
|
24012
25154
|
removeStandaloneSource();
|
|
24013
|
-
DATABASE_VIEW.enter(STATE.route.db, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
25155
|
+
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
24014
25156
|
setStatus("live");
|
|
24015
25157
|
return;
|
|
24016
25158
|
}
|
|
@@ -24128,7 +25270,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24128
25270
|
},
|
|
24129
25271
|
openDatabaseAnnotation: (target) => {
|
|
24130
25272
|
setStatus("live");
|
|
24131
|
-
return DATABASE_VIEW.enter(target.db, target.table, target.tab, {
|
|
25273
|
+
return DATABASE_VIEW.enter(target.db, target.schema, target.table, target.tab, {
|
|
24132
25274
|
annotationTarget: target,
|
|
24133
25275
|
reuseActiveTab: true
|
|
24134
25276
|
});
|