@youtyan/code-viewer 0.6.10 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -13
- package/dist/code-viewer.js +1797 -342
- package/package.json +1 -1
- package/web/app.js +810 -18
- package/web/style.css +179 -0
package/web/app.js
CHANGED
|
@@ -10352,6 +10352,24 @@ ${frontmatter.yaml}
|
|
|
10352
10352
|
confirmDeleteObject: (key) => `Delete object "${key}"?`,
|
|
10353
10353
|
editTextHint: "Only text objects can be edited in the browser.",
|
|
10354
10354
|
create: "Create"
|
|
10355
|
+
},
|
|
10356
|
+
dynamodb: {
|
|
10357
|
+
table: "Table",
|
|
10358
|
+
scanMode: "Scan",
|
|
10359
|
+
queryMode: "Query",
|
|
10360
|
+
keyConditionPlaceholder: "Key condition expression, e.g. pk = :pk",
|
|
10361
|
+
filterPlaceholder: "Filter expression (optional)",
|
|
10362
|
+
attributeValuesPlaceholder: '{":pk": {"S": "value"}}',
|
|
10363
|
+
sortAsc: "Ascending",
|
|
10364
|
+
sortDesc: "Descending",
|
|
10365
|
+
selectItem: "Select an item to preview.",
|
|
10366
|
+
noItems: "(no items)",
|
|
10367
|
+
noTables: "(no tables)",
|
|
10368
|
+
copyKey: "Copy key",
|
|
10369
|
+
copied: "Copied",
|
|
10370
|
+
copyFailed: "Copy failed",
|
|
10371
|
+
invalidAttributeValues: "Invalid attribute values JSON",
|
|
10372
|
+
runQuery: "Run"
|
|
10355
10373
|
}
|
|
10356
10374
|
}
|
|
10357
10375
|
};
|
|
@@ -10674,6 +10692,24 @@ ${frontmatter.yaml}
|
|
|
10674
10692
|
confirmDeleteObject: (key) => `オブジェクト "${key}" を削除しますか?`,
|
|
10675
10693
|
editTextHint: "ブラウザで編集できるのはテキストオブジェクトのみです。",
|
|
10676
10694
|
create: "作成"
|
|
10695
|
+
},
|
|
10696
|
+
dynamodb: {
|
|
10697
|
+
table: "テーブル",
|
|
10698
|
+
scanMode: "Scan",
|
|
10699
|
+
queryMode: "Query",
|
|
10700
|
+
keyConditionPlaceholder: "キー条件式 例: pk = :pk",
|
|
10701
|
+
filterPlaceholder: "フィルタ式 (任意)",
|
|
10702
|
+
attributeValuesPlaceholder: '{":pk": {"S": "value"}}',
|
|
10703
|
+
sortAsc: "昇順",
|
|
10704
|
+
sortDesc: "降順",
|
|
10705
|
+
selectItem: "アイテムを選択するとプレビューが表示されます。",
|
|
10706
|
+
noItems: "(アイテムがありません)",
|
|
10707
|
+
noTables: "(テーブルがありません)",
|
|
10708
|
+
copyKey: "キーをコピー",
|
|
10709
|
+
copied: "コピーしました",
|
|
10710
|
+
copyFailed: "コピーに失敗しました",
|
|
10711
|
+
invalidAttributeValues: "属性値の JSON が不正です",
|
|
10712
|
+
runQuery: "実行"
|
|
10677
10713
|
}
|
|
10678
10714
|
}
|
|
10679
10715
|
};
|
|
@@ -10711,6 +10747,706 @@ ${frontmatter.yaml}
|
|
|
10711
10747
|
el.appendChild(empty);
|
|
10712
10748
|
}
|
|
10713
10749
|
|
|
10750
|
+
// web-src/views/database/dynamodb-explorer.ts
|
|
10751
|
+
function unwrapAttributeValue(av) {
|
|
10752
|
+
if (!av)
|
|
10753
|
+
return;
|
|
10754
|
+
if ("S" in av)
|
|
10755
|
+
return av.S;
|
|
10756
|
+
if ("N" in av)
|
|
10757
|
+
return av.N;
|
|
10758
|
+
if ("BOOL" in av)
|
|
10759
|
+
return av.BOOL;
|
|
10760
|
+
if ("NULL" in av)
|
|
10761
|
+
return null;
|
|
10762
|
+
if ("B" in av)
|
|
10763
|
+
return "(binary)";
|
|
10764
|
+
if ("SS" in av)
|
|
10765
|
+
return av.SS;
|
|
10766
|
+
if ("NS" in av)
|
|
10767
|
+
return av.NS;
|
|
10768
|
+
if ("BS" in av)
|
|
10769
|
+
return "(binary set)";
|
|
10770
|
+
if ("L" in av)
|
|
10771
|
+
return av.L.map(unwrapAttributeValue);
|
|
10772
|
+
if ("M" in av) {
|
|
10773
|
+
const out = {};
|
|
10774
|
+
for (const [k, v] of Object.entries(av.M))
|
|
10775
|
+
out[k] = unwrapAttributeValue(v);
|
|
10776
|
+
return out;
|
|
10777
|
+
}
|
|
10778
|
+
return;
|
|
10779
|
+
}
|
|
10780
|
+
function unwrapItem(item) {
|
|
10781
|
+
const out = {};
|
|
10782
|
+
for (const [k, v] of Object.entries(item))
|
|
10783
|
+
out[k] = unwrapAttributeValue(v);
|
|
10784
|
+
return out;
|
|
10785
|
+
}
|
|
10786
|
+
function previewItem(item) {
|
|
10787
|
+
const entries = Object.entries(item).slice(0, 6);
|
|
10788
|
+
const parts = entries.map(([key, rawValue]) => {
|
|
10789
|
+
const value = unwrapAttributeValue(rawValue);
|
|
10790
|
+
const text2 = value !== null && typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
10791
|
+
return `${key}=${text2.length > 40 ? `${text2.slice(0, 40)}...` : text2}`;
|
|
10792
|
+
});
|
|
10793
|
+
return parts.join(" / ");
|
|
10794
|
+
}
|
|
10795
|
+
function extractItemKey(item, keySchema) {
|
|
10796
|
+
if (!keySchema || keySchema.length === 0)
|
|
10797
|
+
return item;
|
|
10798
|
+
const key = {};
|
|
10799
|
+
for (const k of keySchema) {
|
|
10800
|
+
if (item[k.AttributeName] !== undefined)
|
|
10801
|
+
key[k.AttributeName] = item[k.AttributeName];
|
|
10802
|
+
}
|
|
10803
|
+
return Object.keys(key).length > 0 ? key : item;
|
|
10804
|
+
}
|
|
10805
|
+
function itemKeyToken(key) {
|
|
10806
|
+
const sorted = {};
|
|
10807
|
+
for (const k of Object.keys(key).sort())
|
|
10808
|
+
sorted[k] = key[k];
|
|
10809
|
+
return JSON.stringify(sorted);
|
|
10810
|
+
}
|
|
10811
|
+
function parseAttributeValuesJson(raw) {
|
|
10812
|
+
if (!raw.trim())
|
|
10813
|
+
return {};
|
|
10814
|
+
try {
|
|
10815
|
+
const parsed = JSON.parse(raw);
|
|
10816
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10817
|
+
return null;
|
|
10818
|
+
}
|
|
10819
|
+
return parsed;
|
|
10820
|
+
} catch {
|
|
10821
|
+
return null;
|
|
10822
|
+
}
|
|
10823
|
+
}
|
|
10824
|
+
function createDynamoDbExplorer(callbacks = {}) {
|
|
10825
|
+
const text2 = () => (callbacks.getText?.() ?? dbText("en")).explorer;
|
|
10826
|
+
const trackLoad = (promise) => callbacks.trackLoad ? callbacks.trackLoad(promise) : promise;
|
|
10827
|
+
const container = document.createElement("div");
|
|
10828
|
+
container.className = "dynamodb-explorer";
|
|
10829
|
+
const sidebarSlot = document.createElement("div");
|
|
10830
|
+
sidebarSlot.className = "db-explorer-sidebar-slot dynamodb-table-list-pane";
|
|
10831
|
+
const tableListHeader = document.createElement("div");
|
|
10832
|
+
tableListHeader.className = "db-explorer-pane-header";
|
|
10833
|
+
tableListHeader.textContent = text2().dynamodb.table;
|
|
10834
|
+
sidebarSlot.appendChild(tableListHeader);
|
|
10835
|
+
const tableList = document.createElement("div");
|
|
10836
|
+
tableList.className = "dynamodb-table-list";
|
|
10837
|
+
sidebarSlot.appendChild(tableList);
|
|
10838
|
+
const tableMoreBtn = document.createElement("button");
|
|
10839
|
+
tableMoreBtn.type = "button";
|
|
10840
|
+
tableMoreBtn.className = "dynamodb-item-more-btn dynamodb-table-more-btn";
|
|
10841
|
+
tableMoreBtn.textContent = text2().common.loadMore;
|
|
10842
|
+
tableMoreBtn.hidden = true;
|
|
10843
|
+
sidebarSlot.appendChild(tableMoreBtn);
|
|
10844
|
+
const itemListPane = document.createElement("div");
|
|
10845
|
+
itemListPane.className = "dynamodb-item-list-pane";
|
|
10846
|
+
const modeSeg = document.createElement("div");
|
|
10847
|
+
modeSeg.className = "seg dynamodb-mode-seg";
|
|
10848
|
+
const scanModeBtn = document.createElement("button");
|
|
10849
|
+
scanModeBtn.type = "button";
|
|
10850
|
+
scanModeBtn.textContent = text2().dynamodb.scanMode;
|
|
10851
|
+
const queryModeBtn = document.createElement("button");
|
|
10852
|
+
queryModeBtn.type = "button";
|
|
10853
|
+
queryModeBtn.textContent = text2().dynamodb.queryMode;
|
|
10854
|
+
modeSeg.append(scanModeBtn, queryModeBtn);
|
|
10855
|
+
itemListPane.appendChild(modeSeg);
|
|
10856
|
+
const queryForm = document.createElement("form");
|
|
10857
|
+
queryForm.className = "dynamodb-query-form";
|
|
10858
|
+
const keyConditionInput = document.createElement("input");
|
|
10859
|
+
keyConditionInput.type = "text";
|
|
10860
|
+
keyConditionInput.className = "dynamodb-key-condition-input";
|
|
10861
|
+
keyConditionInput.placeholder = text2().dynamodb.keyConditionPlaceholder;
|
|
10862
|
+
keyConditionInput.autocomplete = "off";
|
|
10863
|
+
keyConditionInput.hidden = true;
|
|
10864
|
+
const filterInput = document.createElement("input");
|
|
10865
|
+
filterInput.type = "text";
|
|
10866
|
+
filterInput.className = "dynamodb-filter-input";
|
|
10867
|
+
filterInput.placeholder = text2().dynamodb.filterPlaceholder;
|
|
10868
|
+
filterInput.autocomplete = "off";
|
|
10869
|
+
const attributeValuesInput = document.createElement("textarea");
|
|
10870
|
+
attributeValuesInput.className = "dynamodb-attribute-values-input";
|
|
10871
|
+
attributeValuesInput.placeholder = text2().dynamodb.attributeValuesPlaceholder;
|
|
10872
|
+
attributeValuesInput.rows = 2;
|
|
10873
|
+
const runBtn = document.createElement("button");
|
|
10874
|
+
runBtn.type = "submit";
|
|
10875
|
+
runBtn.className = "db-btn db-btn-primary dynamodb-run-btn";
|
|
10876
|
+
runBtn.textContent = text2().dynamodb.runQuery;
|
|
10877
|
+
queryForm.append(keyConditionInput, filterInput, attributeValuesInput, runBtn);
|
|
10878
|
+
itemListPane.appendChild(queryForm);
|
|
10879
|
+
const queryError = document.createElement("div");
|
|
10880
|
+
queryError.className = "dynamodb-query-error";
|
|
10881
|
+
queryError.hidden = true;
|
|
10882
|
+
itemListPane.appendChild(queryError);
|
|
10883
|
+
const itemStatus = document.createElement("div");
|
|
10884
|
+
itemStatus.className = "dynamodb-item-status";
|
|
10885
|
+
itemListPane.appendChild(itemStatus);
|
|
10886
|
+
const itemList = document.createElement("div");
|
|
10887
|
+
itemList.className = "dynamodb-item-list";
|
|
10888
|
+
itemListPane.appendChild(itemList);
|
|
10889
|
+
const moreBtn = document.createElement("button");
|
|
10890
|
+
moreBtn.type = "button";
|
|
10891
|
+
moreBtn.className = "dynamodb-item-more-btn";
|
|
10892
|
+
moreBtn.textContent = text2().common.loadMore;
|
|
10893
|
+
moreBtn.hidden = true;
|
|
10894
|
+
itemListPane.appendChild(moreBtn);
|
|
10895
|
+
const detailPane = document.createElement("div");
|
|
10896
|
+
detailPane.className = "dynamodb-detail-pane";
|
|
10897
|
+
setPaneEmpty(detailPane, text2().dynamodb.selectItem);
|
|
10898
|
+
container.append(itemListPane, detailPane);
|
|
10899
|
+
let currentDbId = null;
|
|
10900
|
+
let currentTable = null;
|
|
10901
|
+
let currentMode = "scan";
|
|
10902
|
+
let currentScanIndexForward = true;
|
|
10903
|
+
let currentTableInfo = null;
|
|
10904
|
+
let currentTableNextToken;
|
|
10905
|
+
let currentNextToken;
|
|
10906
|
+
let currentItemKeyToken = null;
|
|
10907
|
+
let cumulativeShownCount = 0;
|
|
10908
|
+
let cumulativeScannedCount = 0;
|
|
10909
|
+
let disposed = false;
|
|
10910
|
+
let loadRunId = 0;
|
|
10911
|
+
let itemRunId = 0;
|
|
10912
|
+
let suppressNotify = false;
|
|
10913
|
+
let activeTableRow = null;
|
|
10914
|
+
let activeItemRow = null;
|
|
10915
|
+
const itemsByKeyToken = new Map;
|
|
10916
|
+
const itemRowsByKeyToken = new Map;
|
|
10917
|
+
const tableGuard = createAbortGuard();
|
|
10918
|
+
const tablePageGuard = createAbortGuard();
|
|
10919
|
+
const tableInfoGuard = createAbortGuard();
|
|
10920
|
+
const itemsGuard = createAbortGuard();
|
|
10921
|
+
const itemGuard = createAbortGuard();
|
|
10922
|
+
function notifySelectionChange() {
|
|
10923
|
+
if (suppressNotify)
|
|
10924
|
+
return;
|
|
10925
|
+
callbacks.onSelectionChange?.(getSelection());
|
|
10926
|
+
}
|
|
10927
|
+
function setMode(mode) {
|
|
10928
|
+
currentMode = mode;
|
|
10929
|
+
scanModeBtn.classList.toggle("active", mode === "scan");
|
|
10930
|
+
queryModeBtn.classList.toggle("active", mode === "query");
|
|
10931
|
+
keyConditionInput.hidden = mode !== "query";
|
|
10932
|
+
}
|
|
10933
|
+
function renderTables(tableNames, append = false) {
|
|
10934
|
+
if (!append) {
|
|
10935
|
+
tableList.innerHTML = "";
|
|
10936
|
+
activeTableRow = null;
|
|
10937
|
+
}
|
|
10938
|
+
if (tableNames.length === 0 && !append) {
|
|
10939
|
+
setPaneStatus(tableList, text2().dynamodb.noTables);
|
|
10940
|
+
return;
|
|
10941
|
+
}
|
|
10942
|
+
const fragment = document.createDocumentFragment();
|
|
10943
|
+
for (const name of tableNames) {
|
|
10944
|
+
const row = document.createElement("div");
|
|
10945
|
+
row.className = "dynamodb-table-item";
|
|
10946
|
+
row.dataset.tableName = name;
|
|
10947
|
+
row.textContent = name;
|
|
10948
|
+
row.title = name;
|
|
10949
|
+
fragment.appendChild(row);
|
|
10950
|
+
}
|
|
10951
|
+
tableList.appendChild(fragment);
|
|
10952
|
+
}
|
|
10953
|
+
function highlightActiveTable(name) {
|
|
10954
|
+
if (activeTableRow?.dataset.tableName === name)
|
|
10955
|
+
return;
|
|
10956
|
+
activeTableRow?.classList.remove("active");
|
|
10957
|
+
activeTableRow = tableList.querySelector(`[data-table-name="${CSS.escape(name)}"]`) ?? null;
|
|
10958
|
+
activeTableRow?.classList.add("active");
|
|
10959
|
+
}
|
|
10960
|
+
function highlightActiveItem(token) {
|
|
10961
|
+
if (activeItemRow && activeItemRow.dataset.keyToken === token)
|
|
10962
|
+
return;
|
|
10963
|
+
activeItemRow?.classList.remove("active");
|
|
10964
|
+
activeItemRow = token ? itemRowsByKeyToken.get(token) ?? null : null;
|
|
10965
|
+
activeItemRow?.classList.add("active");
|
|
10966
|
+
}
|
|
10967
|
+
function appendItems(items) {
|
|
10968
|
+
const fragment = document.createDocumentFragment();
|
|
10969
|
+
for (const item of items) {
|
|
10970
|
+
const key = extractItemKey(item, currentTableInfo?.KeySchema);
|
|
10971
|
+
const token = itemKeyToken(key);
|
|
10972
|
+
itemsByKeyToken.set(token, item);
|
|
10973
|
+
const row = document.createElement("div");
|
|
10974
|
+
row.className = "dynamodb-item-row";
|
|
10975
|
+
row.dataset.keyToken = token;
|
|
10976
|
+
itemRowsByKeyToken.set(token, row);
|
|
10977
|
+
const preview = document.createElement("span");
|
|
10978
|
+
preview.className = "dynamodb-item-preview";
|
|
10979
|
+
preview.textContent = previewItem(item);
|
|
10980
|
+
row.appendChild(preview);
|
|
10981
|
+
fragment.appendChild(row);
|
|
10982
|
+
}
|
|
10983
|
+
itemList.appendChild(fragment);
|
|
10984
|
+
}
|
|
10985
|
+
function renderTableInfo() {
|
|
10986
|
+
detailPane.innerHTML = "";
|
|
10987
|
+
if (!currentTableInfo)
|
|
10988
|
+
return;
|
|
10989
|
+
const header = document.createElement("div");
|
|
10990
|
+
header.className = "dynamodb-table-info-header";
|
|
10991
|
+
header.textContent = currentTableInfo.TableName ?? currentTable ?? "";
|
|
10992
|
+
const meta = document.createElement("div");
|
|
10993
|
+
meta.className = "dynamodb-table-info-meta";
|
|
10994
|
+
meta.textContent = [
|
|
10995
|
+
currentTableInfo.TableStatus,
|
|
10996
|
+
currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined
|
|
10997
|
+
].filter(Boolean).join(" / ");
|
|
10998
|
+
detailPane.append(header, meta);
|
|
10999
|
+
const keySchema = currentTableInfo.KeySchema ?? [];
|
|
11000
|
+
if (keySchema.length > 0) {
|
|
11001
|
+
const keyList = document.createElement("div");
|
|
11002
|
+
keyList.className = "dynamodb-table-info-keys";
|
|
11003
|
+
keyList.textContent = keySchema.map((k) => `${k.AttributeName} (${k.KeyType})`).join(", ");
|
|
11004
|
+
detailPane.appendChild(keyList);
|
|
11005
|
+
}
|
|
11006
|
+
const empty = document.createElement("div");
|
|
11007
|
+
empty.className = "db-pane-empty dynamodb-table-info-empty";
|
|
11008
|
+
const emptyTitle = document.createElement("div");
|
|
11009
|
+
emptyTitle.className = "db-pane-empty-title";
|
|
11010
|
+
emptyTitle.textContent = text2().dynamodb.selectItem;
|
|
11011
|
+
empty.appendChild(emptyTitle);
|
|
11012
|
+
detailPane.appendChild(empty);
|
|
11013
|
+
}
|
|
11014
|
+
function renderItemDetail(item) {
|
|
11015
|
+
detailPane.innerHTML = "";
|
|
11016
|
+
const header = document.createElement("div");
|
|
11017
|
+
header.className = "dynamodb-item-detail-header";
|
|
11018
|
+
const title = document.createElement("span");
|
|
11019
|
+
title.textContent = currentTable ?? "";
|
|
11020
|
+
const copyBtn = document.createElement("button");
|
|
11021
|
+
copyBtn.type = "button";
|
|
11022
|
+
copyBtn.className = "db-btn db-btn-sm dynamodb-copy-key-btn";
|
|
11023
|
+
copyBtn.textContent = text2().dynamodb.copyKey;
|
|
11024
|
+
const copyStatus = document.createElement("span");
|
|
11025
|
+
copyStatus.className = "dynamodb-copy-status";
|
|
11026
|
+
copyStatus.setAttribute("aria-live", "polite");
|
|
11027
|
+
copyBtn.addEventListener("click", async () => {
|
|
11028
|
+
copyStatus.textContent = "";
|
|
11029
|
+
try {
|
|
11030
|
+
const key = extractItemKey(item, currentTableInfo?.KeySchema);
|
|
11031
|
+
await navigator.clipboard.writeText(JSON.stringify(key));
|
|
11032
|
+
copyStatus.textContent = text2().dynamodb.copied;
|
|
11033
|
+
} catch {
|
|
11034
|
+
copyStatus.textContent = text2().dynamodb.copyFailed;
|
|
11035
|
+
}
|
|
11036
|
+
});
|
|
11037
|
+
header.append(title, copyBtn, copyStatus);
|
|
11038
|
+
detailPane.appendChild(header);
|
|
11039
|
+
const pre = document.createElement("pre");
|
|
11040
|
+
pre.className = "dynamodb-item-source";
|
|
11041
|
+
try {
|
|
11042
|
+
pre.textContent = JSON.stringify(unwrapItem(item), null, 2);
|
|
11043
|
+
} catch {
|
|
11044
|
+
pre.textContent = String(item);
|
|
11045
|
+
}
|
|
11046
|
+
detailPane.appendChild(pre);
|
|
11047
|
+
}
|
|
11048
|
+
function selectItem(item, token) {
|
|
11049
|
+
currentItemKeyToken = token ?? itemKeyToken(extractItemKey(item, currentTableInfo?.KeySchema));
|
|
11050
|
+
highlightActiveItem(currentItemKeyToken);
|
|
11051
|
+
renderItemDetail(item);
|
|
11052
|
+
notifySelectionChange();
|
|
11053
|
+
}
|
|
11054
|
+
async function loadItems(append) {
|
|
11055
|
+
if (!currentDbId || !currentTable || disposed)
|
|
11056
|
+
return;
|
|
11057
|
+
const keyConditionExpression = keyConditionInput.value.trim();
|
|
11058
|
+
const filterExpression = filterInput.value.trim();
|
|
11059
|
+
const attributeValues = parseAttributeValuesJson(attributeValuesInput.value);
|
|
11060
|
+
queryError.hidden = true;
|
|
11061
|
+
if (currentMode === "query" && !keyConditionExpression) {
|
|
11062
|
+
queryError.hidden = false;
|
|
11063
|
+
queryError.textContent = text2().dynamodb.keyConditionPlaceholder;
|
|
11064
|
+
return;
|
|
11065
|
+
}
|
|
11066
|
+
if (attributeValues === null) {
|
|
11067
|
+
queryError.hidden = false;
|
|
11068
|
+
queryError.textContent = text2().dynamodb.invalidAttributeValues;
|
|
11069
|
+
return;
|
|
11070
|
+
}
|
|
11071
|
+
const slot = itemsGuard.start();
|
|
11072
|
+
const requestRunId = loadRunId;
|
|
11073
|
+
const requestDbId = currentDbId;
|
|
11074
|
+
const requestTable = currentTable;
|
|
11075
|
+
const requestMode = currentMode;
|
|
11076
|
+
moreBtn.disabled = true;
|
|
11077
|
+
if (!append) {
|
|
11078
|
+
itemList.innerHTML = "";
|
|
11079
|
+
itemsByKeyToken.clear();
|
|
11080
|
+
itemRowsByKeyToken.clear();
|
|
11081
|
+
activeItemRow = null;
|
|
11082
|
+
currentItemKeyToken = null;
|
|
11083
|
+
currentNextToken = undefined;
|
|
11084
|
+
cumulativeShownCount = 0;
|
|
11085
|
+
cumulativeScannedCount = 0;
|
|
11086
|
+
setPaneStatus(itemList, "Loading items...");
|
|
11087
|
+
if (!currentTableInfo)
|
|
11088
|
+
setPaneEmpty(detailPane, text2().dynamodb.selectItem);
|
|
11089
|
+
}
|
|
11090
|
+
try {
|
|
11091
|
+
const params = new URLSearchParams({
|
|
11092
|
+
db: requestDbId,
|
|
11093
|
+
table: requestTable,
|
|
11094
|
+
mode: requestMode,
|
|
11095
|
+
limit: "200"
|
|
11096
|
+
});
|
|
11097
|
+
if (filterExpression)
|
|
11098
|
+
params.set("filterExpression", filterExpression);
|
|
11099
|
+
if (Object.keys(attributeValues).length > 0) {
|
|
11100
|
+
params.set("expressionAttributeValues", JSON.stringify(attributeValues));
|
|
11101
|
+
}
|
|
11102
|
+
if (requestMode === "query") {
|
|
11103
|
+
params.set("keyConditionExpression", keyConditionExpression);
|
|
11104
|
+
params.set("scanIndexForward", String(currentScanIndexForward));
|
|
11105
|
+
}
|
|
11106
|
+
if (append && currentNextToken) {
|
|
11107
|
+
params.set("exclusiveStartKey", JSON.stringify(currentNextToken));
|
|
11108
|
+
}
|
|
11109
|
+
const res = await trackLoad(fetch(`/_db/dynamodb/items?${params}`, { signal: slot.signal }));
|
|
11110
|
+
if (disposed || slot.isStale())
|
|
11111
|
+
return;
|
|
11112
|
+
if (!res.ok) {
|
|
11113
|
+
const errText = await res.text();
|
|
11114
|
+
setPaneStatus(itemList, `Error: ${errText || res.statusText}`, {
|
|
11115
|
+
error: true
|
|
11116
|
+
});
|
|
11117
|
+
return;
|
|
11118
|
+
}
|
|
11119
|
+
const data = await res.json();
|
|
11120
|
+
if (disposed || slot.isStale() || requestRunId !== loadRunId || requestDbId !== currentDbId || requestTable !== currentTable || requestMode !== currentMode) {
|
|
11121
|
+
return;
|
|
11122
|
+
}
|
|
11123
|
+
if (!append)
|
|
11124
|
+
itemList.innerHTML = "";
|
|
11125
|
+
if (data.items.length === 0 && !append) {
|
|
11126
|
+
setPaneStatus(itemList, text2().dynamodb.noItems);
|
|
11127
|
+
} else {
|
|
11128
|
+
appendItems(data.items);
|
|
11129
|
+
}
|
|
11130
|
+
currentNextToken = data.lastEvaluatedKey;
|
|
11131
|
+
moreBtn.hidden = !data.lastEvaluatedKey;
|
|
11132
|
+
cumulativeShownCount += data.items.length;
|
|
11133
|
+
cumulativeScannedCount += data.scannedCount;
|
|
11134
|
+
itemStatus.textContent = `${cumulativeShownCount.toLocaleString()} shown / ${cumulativeScannedCount.toLocaleString()} scanned`;
|
|
11135
|
+
highlightActiveItem(currentItemKeyToken);
|
|
11136
|
+
} catch (err) {
|
|
11137
|
+
if (slot.isStale())
|
|
11138
|
+
return;
|
|
11139
|
+
setPaneStatus(itemList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11140
|
+
} finally {
|
|
11141
|
+
slot.finish();
|
|
11142
|
+
if (!slot.isStale())
|
|
11143
|
+
moreBtn.disabled = false;
|
|
11144
|
+
}
|
|
11145
|
+
}
|
|
11146
|
+
async function fetchTableInfo(table2) {
|
|
11147
|
+
if (disposed || !currentDbId)
|
|
11148
|
+
return;
|
|
11149
|
+
const slot = tableInfoGuard.start();
|
|
11150
|
+
const requestDbId = currentDbId;
|
|
11151
|
+
try {
|
|
11152
|
+
const params = new URLSearchParams({ db: requestDbId, table: table2 });
|
|
11153
|
+
const res = await trackLoad(fetch(`/_db/dynamodb/table?${params}`, { signal: slot.signal }));
|
|
11154
|
+
if (disposed || slot.isStale())
|
|
11155
|
+
return;
|
|
11156
|
+
if (!res.ok)
|
|
11157
|
+
return;
|
|
11158
|
+
const data = await res.json();
|
|
11159
|
+
if (disposed || slot.isStale() || requestDbId !== currentDbId || currentTable !== table2) {
|
|
11160
|
+
return;
|
|
11161
|
+
}
|
|
11162
|
+
currentTableInfo = data.table;
|
|
11163
|
+
if (!currentItemKeyToken)
|
|
11164
|
+
renderTableInfo();
|
|
11165
|
+
} catch {} finally {
|
|
11166
|
+
slot.finish();
|
|
11167
|
+
}
|
|
11168
|
+
}
|
|
11169
|
+
async function selectItemByKey(key) {
|
|
11170
|
+
if (disposed || !currentDbId || !currentTable)
|
|
11171
|
+
return;
|
|
11172
|
+
const slot = itemGuard.start();
|
|
11173
|
+
const requestRunId = ++itemRunId;
|
|
11174
|
+
const requestDbId = currentDbId;
|
|
11175
|
+
const requestTable = currentTable;
|
|
11176
|
+
try {
|
|
11177
|
+
const params = new URLSearchParams({
|
|
11178
|
+
db: requestDbId,
|
|
11179
|
+
table: requestTable,
|
|
11180
|
+
key: JSON.stringify(key)
|
|
11181
|
+
});
|
|
11182
|
+
const res = await trackLoad(fetch(`/_db/dynamodb/item?${params}`, { signal: slot.signal }));
|
|
11183
|
+
if (disposed || slot.isStale())
|
|
11184
|
+
return;
|
|
11185
|
+
if (!res.ok)
|
|
11186
|
+
return;
|
|
11187
|
+
const data = await res.json();
|
|
11188
|
+
if (disposed || slot.isStale() || requestRunId !== itemRunId || requestDbId !== currentDbId || requestTable !== currentTable || !data.item) {
|
|
11189
|
+
return;
|
|
11190
|
+
}
|
|
11191
|
+
selectItem(data.item);
|
|
11192
|
+
} catch {} finally {
|
|
11193
|
+
slot.finish();
|
|
11194
|
+
}
|
|
11195
|
+
}
|
|
11196
|
+
async function selectTable(name) {
|
|
11197
|
+
if (disposed || !currentDbId)
|
|
11198
|
+
return;
|
|
11199
|
+
currentTable = name;
|
|
11200
|
+
currentTableInfo = null;
|
|
11201
|
+
currentItemKeyToken = null;
|
|
11202
|
+
currentNextToken = undefined;
|
|
11203
|
+
highlightActiveTable(name);
|
|
11204
|
+
notifySelectionChange();
|
|
11205
|
+
setPaneEmpty(detailPane, text2().dynamodb.selectItem);
|
|
11206
|
+
await fetchTableInfo(name);
|
|
11207
|
+
await loadItems(false);
|
|
11208
|
+
}
|
|
11209
|
+
scanModeBtn.addEventListener("click", () => {
|
|
11210
|
+
if (currentMode === "scan")
|
|
11211
|
+
return;
|
|
11212
|
+
setMode("scan");
|
|
11213
|
+
notifySelectionChange();
|
|
11214
|
+
loadItems(false);
|
|
11215
|
+
});
|
|
11216
|
+
queryModeBtn.addEventListener("click", () => {
|
|
11217
|
+
if (currentMode === "query")
|
|
11218
|
+
return;
|
|
11219
|
+
setMode("query");
|
|
11220
|
+
notifySelectionChange();
|
|
11221
|
+
});
|
|
11222
|
+
queryForm.addEventListener("submit", (e2) => {
|
|
11223
|
+
e2.preventDefault();
|
|
11224
|
+
notifySelectionChange();
|
|
11225
|
+
loadItems(false);
|
|
11226
|
+
});
|
|
11227
|
+
for (const input of [keyConditionInput, filterInput]) {
|
|
11228
|
+
input.addEventListener("keydown", (e2) => {
|
|
11229
|
+
if (isImeComposing(e2))
|
|
11230
|
+
return;
|
|
11231
|
+
});
|
|
11232
|
+
}
|
|
11233
|
+
moreBtn.addEventListener("click", () => loadItems(true));
|
|
11234
|
+
tableMoreBtn.addEventListener("click", async () => {
|
|
11235
|
+
if (!currentDbId || !currentTableNextToken || disposed)
|
|
11236
|
+
return;
|
|
11237
|
+
const slot = tablePageGuard.start();
|
|
11238
|
+
const requestDbId = currentDbId;
|
|
11239
|
+
const requestToken = currentTableNextToken;
|
|
11240
|
+
tableMoreBtn.disabled = true;
|
|
11241
|
+
tableMoreBtn.title = "";
|
|
11242
|
+
try {
|
|
11243
|
+
const params = new URLSearchParams({
|
|
11244
|
+
db: requestDbId,
|
|
11245
|
+
exclusiveStartTableName: requestToken
|
|
11246
|
+
});
|
|
11247
|
+
const res = await trackLoad(fetch(`/_db/dynamodb/tables?${params}`, { signal: slot.signal }));
|
|
11248
|
+
if (disposed || slot.isStale() || requestDbId !== currentDbId)
|
|
11249
|
+
return;
|
|
11250
|
+
if (!res.ok) {
|
|
11251
|
+
tableMoreBtn.title = await res.text() || res.statusText;
|
|
11252
|
+
return;
|
|
11253
|
+
}
|
|
11254
|
+
const data = await res.json();
|
|
11255
|
+
if (disposed || slot.isStale() || requestDbId !== currentDbId)
|
|
11256
|
+
return;
|
|
11257
|
+
renderTables(data.tableNames, true);
|
|
11258
|
+
currentTableNextToken = data.lastEvaluatedTableName;
|
|
11259
|
+
tableMoreBtn.hidden = !currentTableNextToken;
|
|
11260
|
+
if (currentTable)
|
|
11261
|
+
highlightActiveTable(currentTable);
|
|
11262
|
+
} catch (err) {
|
|
11263
|
+
if (slot.isStale())
|
|
11264
|
+
return;
|
|
11265
|
+
tableMoreBtn.title = err instanceof Error ? err.message : String(err);
|
|
11266
|
+
} finally {
|
|
11267
|
+
slot.finish();
|
|
11268
|
+
if (!slot.isStale())
|
|
11269
|
+
tableMoreBtn.disabled = false;
|
|
11270
|
+
}
|
|
11271
|
+
});
|
|
11272
|
+
tableList.addEventListener("click", (e2) => {
|
|
11273
|
+
const row = e2.target?.closest(".dynamodb-table-item");
|
|
11274
|
+
if (!row || !tableList.contains(row))
|
|
11275
|
+
return;
|
|
11276
|
+
const name = row.dataset.tableName;
|
|
11277
|
+
if (name)
|
|
11278
|
+
selectTable(name);
|
|
11279
|
+
});
|
|
11280
|
+
itemList.addEventListener("click", (e2) => {
|
|
11281
|
+
const row = e2.target?.closest(".dynamodb-item-row");
|
|
11282
|
+
if (!row || !itemList.contains(row))
|
|
11283
|
+
return;
|
|
11284
|
+
const token = row.dataset.keyToken;
|
|
11285
|
+
if (!token)
|
|
11286
|
+
return;
|
|
11287
|
+
const item = itemsByKeyToken.get(token);
|
|
11288
|
+
if (item)
|
|
11289
|
+
selectItem(item, token);
|
|
11290
|
+
});
|
|
11291
|
+
async function load(dbId, initial) {
|
|
11292
|
+
if (disposed)
|
|
11293
|
+
return;
|
|
11294
|
+
if (currentDbId === dbId && !initial)
|
|
11295
|
+
return;
|
|
11296
|
+
tableGuard.dispose();
|
|
11297
|
+
tablePageGuard.dispose();
|
|
11298
|
+
tableInfoGuard.dispose();
|
|
11299
|
+
itemsGuard.dispose();
|
|
11300
|
+
itemGuard.dispose();
|
|
11301
|
+
const slot = tableGuard.start();
|
|
11302
|
+
const requestRunId = ++loadRunId;
|
|
11303
|
+
currentDbId = dbId;
|
|
11304
|
+
currentTable = null;
|
|
11305
|
+
currentTableInfo = null;
|
|
11306
|
+
currentTableNextToken = undefined;
|
|
11307
|
+
currentItemKeyToken = null;
|
|
11308
|
+
currentNextToken = undefined;
|
|
11309
|
+
setMode(initial?.mode === "query" ? "query" : "scan");
|
|
11310
|
+
currentScanIndexForward = initial?.scanIndexForward ?? true;
|
|
11311
|
+
keyConditionInput.value = initial?.keyConditionExpression ?? "";
|
|
11312
|
+
filterInput.value = initial?.filterExpression ?? "";
|
|
11313
|
+
attributeValuesInput.value = initial?.expressionAttributeValues ?? "";
|
|
11314
|
+
queryError.hidden = true;
|
|
11315
|
+
itemStatus.textContent = "";
|
|
11316
|
+
itemList.innerHTML = "";
|
|
11317
|
+
itemsByKeyToken.clear();
|
|
11318
|
+
itemRowsByKeyToken.clear();
|
|
11319
|
+
activeItemRow = null;
|
|
11320
|
+
moreBtn.hidden = true;
|
|
11321
|
+
tableMoreBtn.hidden = true;
|
|
11322
|
+
setPaneEmpty(detailPane, text2().dynamodb.selectItem);
|
|
11323
|
+
setPaneStatus(tableList, "Loading tables...");
|
|
11324
|
+
try {
|
|
11325
|
+
const res = await trackLoad(fetch(`/_db/dynamodb/tables?db=${encodeURIComponent(dbId)}`, {
|
|
11326
|
+
signal: slot.signal
|
|
11327
|
+
}));
|
|
11328
|
+
if (disposed || slot.isStale())
|
|
11329
|
+
return;
|
|
11330
|
+
if (!res.ok) {
|
|
11331
|
+
const errText = await res.text();
|
|
11332
|
+
setPaneStatus(tableList, `Error: ${errText || res.statusText}`, {
|
|
11333
|
+
error: true
|
|
11334
|
+
});
|
|
11335
|
+
return;
|
|
11336
|
+
}
|
|
11337
|
+
const data = await res.json();
|
|
11338
|
+
if (disposed || slot.isStale() || requestRunId !== loadRunId || currentDbId !== dbId) {
|
|
11339
|
+
return;
|
|
11340
|
+
}
|
|
11341
|
+
renderTables(data.tableNames);
|
|
11342
|
+
currentTableNextToken = data.lastEvaluatedTableName;
|
|
11343
|
+
tableMoreBtn.hidden = !currentTableNextToken;
|
|
11344
|
+
const selected = initial?.table && data.tableNames.includes(initial.table) && initial.table || data.tableNames[0] || null;
|
|
11345
|
+
if (!selected)
|
|
11346
|
+
return;
|
|
11347
|
+
suppressNotify = true;
|
|
11348
|
+
try {
|
|
11349
|
+
await selectTable(selected);
|
|
11350
|
+
if (currentDbId !== dbId)
|
|
11351
|
+
return;
|
|
11352
|
+
if (initial?.itemKey) {
|
|
11353
|
+
try {
|
|
11354
|
+
const key = JSON.parse(initial.itemKey);
|
|
11355
|
+
await selectItemByKey(key);
|
|
11356
|
+
} catch {}
|
|
11357
|
+
}
|
|
11358
|
+
} finally {
|
|
11359
|
+
suppressNotify = false;
|
|
11360
|
+
}
|
|
11361
|
+
notifySelectionChange();
|
|
11362
|
+
} catch (err) {
|
|
11363
|
+
if (slot.isStale())
|
|
11364
|
+
return;
|
|
11365
|
+
setPaneStatus(tableList, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11366
|
+
} finally {
|
|
11367
|
+
slot.finish();
|
|
11368
|
+
}
|
|
11369
|
+
}
|
|
11370
|
+
function clear() {
|
|
11371
|
+
tableGuard.dispose();
|
|
11372
|
+
tablePageGuard.dispose();
|
|
11373
|
+
tableInfoGuard.dispose();
|
|
11374
|
+
itemsGuard.dispose();
|
|
11375
|
+
itemGuard.dispose();
|
|
11376
|
+
loadRunId++;
|
|
11377
|
+
itemRunId++;
|
|
11378
|
+
suppressNotify = false;
|
|
11379
|
+
currentDbId = null;
|
|
11380
|
+
currentTable = null;
|
|
11381
|
+
currentTableInfo = null;
|
|
11382
|
+
currentTableNextToken = undefined;
|
|
11383
|
+
currentItemKeyToken = null;
|
|
11384
|
+
currentNextToken = undefined;
|
|
11385
|
+
cumulativeShownCount = 0;
|
|
11386
|
+
cumulativeScannedCount = 0;
|
|
11387
|
+
setMode("scan");
|
|
11388
|
+
keyConditionInput.value = "";
|
|
11389
|
+
filterInput.value = "";
|
|
11390
|
+
attributeValuesInput.value = "";
|
|
11391
|
+
queryError.hidden = true;
|
|
11392
|
+
tableList.innerHTML = "";
|
|
11393
|
+
tableMoreBtn.hidden = true;
|
|
11394
|
+
tableMoreBtn.disabled = false;
|
|
11395
|
+
tableMoreBtn.title = "";
|
|
11396
|
+
activeTableRow = null;
|
|
11397
|
+
itemStatus.textContent = "";
|
|
11398
|
+
itemList.innerHTML = "";
|
|
11399
|
+
itemsByKeyToken.clear();
|
|
11400
|
+
itemRowsByKeyToken.clear();
|
|
11401
|
+
activeItemRow = null;
|
|
11402
|
+
moreBtn.hidden = true;
|
|
11403
|
+
setPaneEmpty(detailPane, text2().dynamodb.selectItem);
|
|
11404
|
+
}
|
|
11405
|
+
setMode("scan");
|
|
11406
|
+
function getSelection() {
|
|
11407
|
+
return {
|
|
11408
|
+
table: currentTable ?? undefined,
|
|
11409
|
+
mode: currentMode,
|
|
11410
|
+
keyConditionExpression: keyConditionInput.value.trim() || undefined,
|
|
11411
|
+
filterExpression: filterInput.value.trim() || undefined,
|
|
11412
|
+
expressionAttributeValues: attributeValuesInput.value.trim() || undefined,
|
|
11413
|
+
scanIndexForward: currentMode === "query" ? currentScanIndexForward : undefined,
|
|
11414
|
+
itemKey: currentItemKeyToken ?? undefined
|
|
11415
|
+
};
|
|
11416
|
+
}
|
|
11417
|
+
function dispose() {
|
|
11418
|
+
disposed = true;
|
|
11419
|
+
clear();
|
|
11420
|
+
}
|
|
11421
|
+
function localize() {
|
|
11422
|
+
const t2 = text2();
|
|
11423
|
+
tableListHeader.textContent = t2.dynamodb.table;
|
|
11424
|
+
scanModeBtn.textContent = t2.dynamodb.scanMode;
|
|
11425
|
+
queryModeBtn.textContent = t2.dynamodb.queryMode;
|
|
11426
|
+
keyConditionInput.placeholder = t2.dynamodb.keyConditionPlaceholder;
|
|
11427
|
+
filterInput.placeholder = t2.dynamodb.filterPlaceholder;
|
|
11428
|
+
attributeValuesInput.placeholder = t2.dynamodb.attributeValuesPlaceholder;
|
|
11429
|
+
runBtn.textContent = t2.dynamodb.runQuery;
|
|
11430
|
+
tableMoreBtn.textContent = t2.common.loadMore;
|
|
11431
|
+
moreBtn.textContent = t2.common.loadMore;
|
|
11432
|
+
if (!activeItemRow) {
|
|
11433
|
+
if (currentTableInfo)
|
|
11434
|
+
renderTableInfo();
|
|
11435
|
+
else
|
|
11436
|
+
setPaneEmpty(detailPane, t2.dynamodb.selectItem);
|
|
11437
|
+
}
|
|
11438
|
+
}
|
|
11439
|
+
return {
|
|
11440
|
+
el: container,
|
|
11441
|
+
sidebarSlot,
|
|
11442
|
+
load,
|
|
11443
|
+
clear,
|
|
11444
|
+
dispose,
|
|
11445
|
+
getSelection,
|
|
11446
|
+
localize
|
|
11447
|
+
};
|
|
11448
|
+
}
|
|
11449
|
+
|
|
10714
11450
|
// web-src/views/database/elasticsearch-explorer.ts
|
|
10715
11451
|
function createElasticsearchExplorer(callbacks = {}) {
|
|
10716
11452
|
const text2 = () => (callbacks.getText?.() ?? dbText("en")).explorer;
|
|
@@ -18769,7 +19505,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18769
19505
|
snapshotHidden: !sqlMode || tab !== "snapshot",
|
|
18770
19506
|
redisHidden: kind !== "redis",
|
|
18771
19507
|
esHidden: kind !== "elasticsearch",
|
|
18772
|
-
s3Hidden: kind !== "s3"
|
|
19508
|
+
s3Hidden: kind !== "s3",
|
|
19509
|
+
dynamodbHidden: kind !== "dynamodb"
|
|
18773
19510
|
};
|
|
18774
19511
|
}
|
|
18775
19512
|
function normalizeViewForDb(view, db) {
|
|
@@ -19151,7 +19888,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19151
19888
|
});
|
|
19152
19889
|
s3Explorer.el.hidden = true;
|
|
19153
19890
|
s3Explorer.sidebarSlot.hidden = true;
|
|
19154
|
-
|
|
19891
|
+
const dynamodbExplorer = createDynamoDbExplorer({
|
|
19892
|
+
onSelectionChange: () => cb.onStateChange(),
|
|
19893
|
+
getText: () => paneText(),
|
|
19894
|
+
trackLoad: (p2) => deps.trackLoad(p2)
|
|
19895
|
+
});
|
|
19896
|
+
dynamodbExplorer.el.hidden = true;
|
|
19897
|
+
dynamodbExplorer.sidebarSlot.hidden = true;
|
|
19898
|
+
explorerSidebarHost.append(redisExplorer.sidebarSlot, esExplorer.sidebarSlot, s3Explorer.sidebarSlot, dynamodbExplorer.sidebarSlot);
|
|
19155
19899
|
const noDatastoresPane = document.createElement("div");
|
|
19156
19900
|
noDatastoresPane.className = "db-no-datastores";
|
|
19157
19901
|
noDatastoresPane.hidden = true;
|
|
@@ -19175,7 +19919,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19175
19919
|
}
|
|
19176
19920
|
const mainContent = document.createElement("div");
|
|
19177
19921
|
mainContent.className = "db-main-content";
|
|
19178
|
-
mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el, s3Explorer.el, noDatastoresPane);
|
|
19922
|
+
mainContent.append(tabBar, grid.el, queryEditor.el, schemaView.el, erDiagram.el, globalSearchView.el, snapshotView.el, redisExplorer.el, esExplorer.el, s3Explorer.el, dynamodbExplorer.el, noDatastoresPane);
|
|
19179
19923
|
queryEditor.el.hidden = true;
|
|
19180
19924
|
globalSearchView.el.hidden = true;
|
|
19181
19925
|
snapshotView.el.hidden = true;
|
|
@@ -19303,10 +20047,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19303
20047
|
redisExplorer.el.hidden = visibility.redisHidden;
|
|
19304
20048
|
esExplorer.el.hidden = visibility.esHidden;
|
|
19305
20049
|
s3Explorer.el.hidden = visibility.s3Hidden;
|
|
20050
|
+
dynamodbExplorer.el.hidden = visibility.dynamodbHidden;
|
|
19306
20051
|
redisExplorer.sidebarSlot.hidden = visibility.redisHidden;
|
|
19307
20052
|
esExplorer.sidebarSlot.hidden = visibility.esHidden;
|
|
19308
20053
|
s3Explorer.sidebarSlot.hidden = visibility.s3Hidden;
|
|
19309
|
-
|
|
20054
|
+
dynamodbExplorer.sidebarSlot.hidden = visibility.dynamodbHidden;
|
|
20055
|
+
explorerSidebarHost.hidden = visibility.redisHidden && visibility.esHidden && visibility.s3Hidden && visibility.dynamodbHidden;
|
|
19310
20056
|
if (noDatastoresAvailable) {
|
|
19311
20057
|
toolsSection.hidden = true;
|
|
19312
20058
|
prefsBar.hidden = true;
|
|
@@ -19324,9 +20070,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19324
20070
|
redisExplorer.el.hidden = true;
|
|
19325
20071
|
esExplorer.el.hidden = true;
|
|
19326
20072
|
s3Explorer.el.hidden = true;
|
|
20073
|
+
dynamodbExplorer.el.hidden = true;
|
|
19327
20074
|
redisExplorer.sidebarSlot.hidden = true;
|
|
19328
20075
|
esExplorer.sidebarSlot.hidden = true;
|
|
19329
20076
|
s3Explorer.sidebarSlot.hidden = true;
|
|
20077
|
+
dynamodbExplorer.sidebarSlot.hidden = true;
|
|
19330
20078
|
explorerSidebarHost.hidden = true;
|
|
19331
20079
|
}
|
|
19332
20080
|
if (!sqlMode) {
|
|
@@ -19679,7 +20427,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19679
20427
|
return;
|
|
19680
20428
|
tableSelectGuard.dispose();
|
|
19681
20429
|
currentTable = null;
|
|
19682
|
-
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
|
|
20430
|
+
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
|
|
19683
20431
|
currentSchema = null;
|
|
19684
20432
|
renderSchemaOptions([], null);
|
|
19685
20433
|
currentTab = "data";
|
|
@@ -19692,15 +20440,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19692
20440
|
if (currentDbInfo.kind === "redis") {
|
|
19693
20441
|
esExplorer.clear();
|
|
19694
20442
|
s3Explorer.clear();
|
|
20443
|
+
dynamodbExplorer.clear();
|
|
19695
20444
|
await redisExplorer.load(dbId, explorerInitial?.redis);
|
|
19696
20445
|
} else if (currentDbInfo.kind === "elasticsearch") {
|
|
19697
20446
|
redisExplorer.clear();
|
|
19698
20447
|
s3Explorer.clear();
|
|
20448
|
+
dynamodbExplorer.clear();
|
|
19699
20449
|
await esExplorer.load(dbId, explorerInitial?.es);
|
|
19700
|
-
} else {
|
|
20450
|
+
} else if (currentDbInfo.kind === "s3") {
|
|
19701
20451
|
redisExplorer.clear();
|
|
19702
20452
|
esExplorer.clear();
|
|
20453
|
+
dynamodbExplorer.clear();
|
|
19703
20454
|
await s3Explorer.load(dbId, explorerInitial?.s3);
|
|
20455
|
+
} else {
|
|
20456
|
+
redisExplorer.clear();
|
|
20457
|
+
esExplorer.clear();
|
|
20458
|
+
s3Explorer.clear();
|
|
20459
|
+
await dynamodbExplorer.load(dbId, explorerInitial?.dynamodb);
|
|
19704
20460
|
}
|
|
19705
20461
|
if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
|
|
19706
20462
|
return;
|
|
@@ -19710,6 +20466,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19710
20466
|
redisExplorer.clear();
|
|
19711
20467
|
esExplorer.clear();
|
|
19712
20468
|
s3Explorer.clear();
|
|
20469
|
+
dynamodbExplorer.clear();
|
|
19713
20470
|
applyVisibility();
|
|
19714
20471
|
tableList.render([]);
|
|
19715
20472
|
setTableListStatus(paneText().nav.loadingSchema);
|
|
@@ -20039,6 +20796,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20039
20796
|
let pendingRedisInitial = initial.redis;
|
|
20040
20797
|
let pendingEsInitial = initial.es;
|
|
20041
20798
|
let pendingS3Initial = initial.s3;
|
|
20799
|
+
let pendingDynamodbInitial = initial.dynamodb;
|
|
20042
20800
|
async function enter(db, schema, table2, view, options = {}) {
|
|
20043
20801
|
const generation = ++loadGeneration;
|
|
20044
20802
|
const filesResponse = await fetchDbFiles();
|
|
@@ -20071,6 +20829,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20071
20829
|
redisExplorer.clear();
|
|
20072
20830
|
esExplorer.clear();
|
|
20073
20831
|
s3Explorer.clear();
|
|
20832
|
+
dynamodbExplorer.clear();
|
|
20074
20833
|
renderNoDatastoresEmpty();
|
|
20075
20834
|
applyVisibility();
|
|
20076
20835
|
cb.onStateChange();
|
|
@@ -20106,6 +20865,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20106
20865
|
redisExplorer.clear();
|
|
20107
20866
|
esExplorer.clear();
|
|
20108
20867
|
s3Explorer.clear();
|
|
20868
|
+
dynamodbExplorer.clear();
|
|
20109
20869
|
setActiveTab("data", false);
|
|
20110
20870
|
cb.onStateChange();
|
|
20111
20871
|
return;
|
|
@@ -20116,15 +20876,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20116
20876
|
const explorerInitial = {
|
|
20117
20877
|
redis: pendingRedisInitial,
|
|
20118
20878
|
es: pendingEsInitial,
|
|
20119
|
-
s3: pendingS3Initial
|
|
20879
|
+
s3: pendingS3Initial,
|
|
20880
|
+
dynamodb: pendingDynamodbInitial
|
|
20120
20881
|
};
|
|
20121
20882
|
pendingRedisInitial = undefined;
|
|
20122
20883
|
pendingEsInitial = undefined;
|
|
20123
20884
|
pendingS3Initial = undefined;
|
|
20885
|
+
pendingDynamodbInitial = undefined;
|
|
20124
20886
|
await selectDb(target, explorerInitial, generation, schema !== undefined ? schema : currentSchema, table2, view);
|
|
20125
20887
|
if (generation !== loadGeneration || currentDbInfo?.id !== target)
|
|
20126
20888
|
return;
|
|
20127
|
-
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3") {
|
|
20889
|
+
if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch" || currentDbInfo?.kind === "s3" || currentDbInfo?.kind === "dynamodb") {
|
|
20128
20890
|
cb.onStateChange();
|
|
20129
20891
|
return;
|
|
20130
20892
|
}
|
|
@@ -20229,6 +20991,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20229
20991
|
state.es = initial.es;
|
|
20230
20992
|
if (initial.s3)
|
|
20231
20993
|
state.s3 = initial.s3;
|
|
20994
|
+
if (initial.dynamodb)
|
|
20995
|
+
state.dynamodb = initial.dynamodb;
|
|
20232
20996
|
} else if (currentDbInfo?.kind === "redis") {
|
|
20233
20997
|
const sel = redisExplorer.getSelection();
|
|
20234
20998
|
if (sel.dbIndex !== undefined || sel.key !== undefined || sel.keyFilter !== undefined) {
|
|
@@ -20244,6 +21008,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20244
21008
|
if (sel.bucket !== undefined || sel.prefix !== undefined || sel.query !== undefined || sel.key !== undefined || sel.mode !== "prefix" || sel.sort !== "updated-desc" || sel.view === "explorer") {
|
|
20245
21009
|
state.s3 = sel;
|
|
20246
21010
|
}
|
|
21011
|
+
} else if (currentDbInfo?.kind === "dynamodb") {
|
|
21012
|
+
const sel = dynamodbExplorer.getSelection();
|
|
21013
|
+
if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined) {
|
|
21014
|
+
state.dynamodb = sel;
|
|
21015
|
+
}
|
|
20247
21016
|
}
|
|
20248
21017
|
return state;
|
|
20249
21018
|
}
|
|
@@ -20300,6 +21069,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20300
21069
|
redisExplorer.dispose();
|
|
20301
21070
|
esExplorer.dispose();
|
|
20302
21071
|
s3Explorer.dispose();
|
|
21072
|
+
dynamodbExplorer.dispose();
|
|
20303
21073
|
currentDbInfo = null;
|
|
20304
21074
|
currentSchema = null;
|
|
20305
21075
|
currentTable = null;
|
|
@@ -20320,6 +21090,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20320
21090
|
redisExplorer.localize();
|
|
20321
21091
|
esExplorer.localize();
|
|
20322
21092
|
s3Explorer.localize();
|
|
21093
|
+
dynamodbExplorer.localize();
|
|
20323
21094
|
}
|
|
20324
21095
|
return {
|
|
20325
21096
|
el: container,
|
|
@@ -20732,7 +21503,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20732
21503
|
view: tab.view,
|
|
20733
21504
|
redis: tab.redis ?? null,
|
|
20734
21505
|
es: tab.es ?? null,
|
|
20735
|
-
s3: tab.s3 ?? null
|
|
21506
|
+
s3: tab.s3 ?? null,
|
|
21507
|
+
dynamodb: tab.dynamodb ?? null
|
|
20736
21508
|
});
|
|
20737
21509
|
if (seen.has(key))
|
|
20738
21510
|
continue;
|
|
@@ -20990,7 +21762,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
20990
21762
|
detailPanelHeight: initial?.detailPanelHeight,
|
|
20991
21763
|
redis: initial?.redis,
|
|
20992
21764
|
es: initial?.es,
|
|
20993
|
-
s3: initial?.s3
|
|
21765
|
+
s3: initial?.s3,
|
|
21766
|
+
dynamodb: initial?.dynamodb
|
|
20994
21767
|
});
|
|
20995
21768
|
pane.el.hidden = true;
|
|
20996
21769
|
tabsList.appendChild(chip);
|
|
@@ -24761,7 +25534,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24761
25534
|
database: {
|
|
24762
25535
|
nav: "Datastores",
|
|
24763
25536
|
title: "Datastore Viewer",
|
|
24764
|
-
intro: "Browse
|
|
25537
|
+
intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores from one local viewer.",
|
|
24765
25538
|
groups: [
|
|
24766
25539
|
{
|
|
24767
25540
|
title: "Supported datastores",
|
|
@@ -24789,6 +25562,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24789
25562
|
"Elasticsearch",
|
|
24790
25563
|
"Detected from compose files. List indices, view mappings, paginate with search_after, run lucene q= or DSL queries on an allowlist. Edit / create / delete documents with _seq_no / _primary_term optimistic concurrency. Take snapshots/diffs."
|
|
24791
25564
|
],
|
|
25565
|
+
[
|
|
25566
|
+
"DynamoDB / LocalStack",
|
|
25567
|
+
"Detected when DynamoDB is enabled on a LocalStack compose service. List tables, inspect key schemas, scan or query items, follow pagination tokens, and open item details with a copyable key. Browsing is read-only."
|
|
25568
|
+
],
|
|
24792
25569
|
[
|
|
24793
25570
|
"S3 / MinIO / LocalStack",
|
|
24794
25571
|
"Detected from compose files. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. Edit text/markdown/JSON object bodies inline, upload new objects, and delete existing ones. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
|
|
@@ -24845,7 +25622,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
24845
25622
|
],
|
|
24846
25623
|
[
|
|
24847
25624
|
"Datastore explorers",
|
|
24848
|
-
"Redis / Elasticsearch / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / folder-tree explorer.
|
|
25625
|
+
"Redis / Elasticsearch / DynamoDB / S3 keep the same Multi-DB tab UI but swap the table tree for a key-space / index-tree / table-list / folder-tree explorer. Redis, Elasticsearch, and S3 provide editing or creation flows; DynamoDB browsing is read-only."
|
|
24849
25626
|
]
|
|
24850
25627
|
]
|
|
24851
25628
|
}
|
|
@@ -25420,7 +26197,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
25420
26197
|
database: {
|
|
25421
26198
|
nav: "データストア",
|
|
25422
26199
|
title: "データストアビューア",
|
|
25423
|
-
intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、S3
|
|
26200
|
+
intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
|
|
25424
26201
|
groups: [
|
|
25425
26202
|
{
|
|
25426
26203
|
title: "対応データストア",
|
|
@@ -25448,6 +26225,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
25448
26225
|
"Elasticsearch",
|
|
25449
26226
|
"compose ファイルから検出。インデックス一覧、マッピング、search_after ページング、lucene q= と許可リスト経由の DSL、スナップショット/差分に対応。_seq_no / _primary_term 楽観ロックでドキュメントの編集 / 新規作成 / 削除も可能。"
|
|
25450
26227
|
],
|
|
26228
|
+
[
|
|
26229
|
+
"DynamoDB / LocalStack",
|
|
26230
|
+
"LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、キースキーマ、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
|
|
26231
|
+
],
|
|
25451
26232
|
[
|
|
25452
26233
|
"S3 / MinIO / LocalStack",
|
|
25453
26234
|
"compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
|
|
@@ -25504,7 +26285,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
25504
26285
|
],
|
|
25505
26286
|
[
|
|
25506
26287
|
"データストア専用エクスプローラ",
|
|
25507
|
-
"Redis / Elasticsearch / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー /
|
|
26288
|
+
"Redis / Elasticsearch / DynamoDB / S3 はマルチ DB タブ UI を共有しつつ、テーブルツリーをキー空間ツリー / インデックスツリー / テーブル一覧 / フォルダツリーに差し替えます。Redis、Elasticsearch、S3 は編集・作成フローを提供し、DynamoDB は閲覧専用です。"
|
|
25508
26289
|
]
|
|
25509
26290
|
]
|
|
25510
26291
|
}
|
|
@@ -31658,6 +32439,7 @@ code-viewer query agent-help`
|
|
|
31658
32439
|
getServerGeneration
|
|
31659
32440
|
} = deps;
|
|
31660
32441
|
let PALETTE = null;
|
|
32442
|
+
let repoFileRequestGeneration = 0;
|
|
31661
32443
|
const REPO_FILE_CACHE = new Map;
|
|
31662
32444
|
function paletteSource() {
|
|
31663
32445
|
if (STATE.route.screen === "diff")
|
|
@@ -31868,7 +32650,6 @@ code-viewer query agent-help`
|
|
|
31868
32650
|
throw new Error("failed to load files");
|
|
31869
32651
|
return r2.json();
|
|
31870
32652
|
}));
|
|
31871
|
-
REPO_FILE_CACHE.set(cacheKey, res);
|
|
31872
32653
|
return res;
|
|
31873
32654
|
}
|
|
31874
32655
|
function diffFilePaletteItems(state, query) {
|
|
@@ -31920,9 +32701,18 @@ code-viewer query agent-help`
|
|
|
31920
32701
|
} else {
|
|
31921
32702
|
state.status.textContent = "Loading files...";
|
|
31922
32703
|
const ref = paletteRef(source);
|
|
31923
|
-
const
|
|
31924
|
-
|
|
32704
|
+
const requestGeneration = ++repoFileRequestGeneration;
|
|
32705
|
+
let response;
|
|
32706
|
+
try {
|
|
32707
|
+
response = await repoPaletteFiles(ref);
|
|
32708
|
+
} catch (err) {
|
|
32709
|
+
if (requestGeneration !== repoFileRequestGeneration)
|
|
32710
|
+
return;
|
|
32711
|
+
throw err;
|
|
32712
|
+
}
|
|
32713
|
+
if (PALETTE !== state || state.input.value !== query || requestGeneration !== repoFileRequestGeneration)
|
|
31925
32714
|
return;
|
|
32715
|
+
REPO_FILE_CACHE.set(repoFileCacheKey(ref), response);
|
|
31926
32716
|
state.items = limitPaletteResults(rankPathMatches(query, response.files, PALETTE_RESULT_LIMIT)).map((match2) => ({
|
|
31927
32717
|
kind: "file",
|
|
31928
32718
|
path: match2.item.path,
|
|
@@ -36829,6 +37619,7 @@ code-viewer query agent-help`
|
|
|
36829
37619
|
}, 200);
|
|
36830
37620
|
});
|
|
36831
37621
|
}
|
|
37622
|
+
let diffLoadGeneration = 0;
|
|
36832
37623
|
function load(options = {}) {
|
|
36833
37624
|
if (STATE.route.screen === "help") {
|
|
36834
37625
|
setStatus("live");
|
|
@@ -36863,10 +37654,11 @@ code-viewer query agent-help`
|
|
|
36863
37654
|
}
|
|
36864
37655
|
}
|
|
36865
37656
|
const routeAtRequest = STATE.route;
|
|
37657
|
+
const requestGeneration = ++diffLoadGeneration;
|
|
36866
37658
|
const fromAtRequest = STATE.from;
|
|
36867
37659
|
const toAtRequest = STATE.to;
|
|
36868
37660
|
const ignoreWsAtRequest = STATE.ignoreWs;
|
|
36869
|
-
const isCurrentDiffRequest = () => STATE.route === routeAtRequest && STATE.from === fromAtRequest && STATE.to === toAtRequest && STATE.ignoreWs === ignoreWsAtRequest;
|
|
37661
|
+
const isCurrentDiffRequest = () => requestGeneration === diffLoadGeneration && STATE.route === routeAtRequest && STATE.from === fromAtRequest && STATE.to === toAtRequest && STATE.ignoreWs === ignoreWsAtRequest;
|
|
36870
37662
|
setStatus("refreshing");
|
|
36871
37663
|
const params = new URLSearchParams;
|
|
36872
37664
|
if (STATE.ignoreWs)
|