@youtyan/code-viewer 0.8.1 → 0.8.2
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 +7 -4
- package/dist/code-viewer.js +120 -12
- package/package.json +1 -1
- package/web/app.js +311 -118
- package/web/style.css +46 -24
package/README.md
CHANGED
|
@@ -178,7 +178,9 @@ files or directory names completely.
|
|
|
178
178
|
|
|
179
179
|
Scope settings control directory exclusions shared by the sidebar, Ctrl+K file
|
|
180
180
|
palette, Ctrl+G grep palette, the Datastores browser, and the file change
|
|
181
|
-
watcher — the same list applies to all five.
|
|
181
|
+
watcher — the same list applies to all five. Both the skip list and the hide
|
|
182
|
+
list accept gitignore-style wildcards (`*`, `?`, `[abc]`, `[!abc]`) in
|
|
183
|
+
addition to exact names. Everything you change in Viewer
|
|
182
184
|
Settings is saved on the server under `.code-viewer/settings.json` (no
|
|
183
185
|
separate project-level config file). `.DS_Store` and a broad set of
|
|
184
186
|
build/cache directories (`node_modules`, `dist`, `build`, `.next`, `.turbo`,
|
|
@@ -249,9 +251,10 @@ concurrency via `_seq_no` / `_primary_term`), create new documents, and delete
|
|
|
249
251
|
existing ones. Snapshots and diffs over `_search` iteration are supported.
|
|
250
252
|
|
|
251
253
|
**DynamoDB** support: detect LocalStack services with DynamoDB enabled, list
|
|
252
|
-
tables across paginated responses,
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
tables across paginated responses, and browse a Structure tab (key schema,
|
|
255
|
+
global/local secondary indexes, and non-key attribute types inferred from
|
|
256
|
+
loaded items) alongside scan or query items, `LastEvaluatedKey` pagination,
|
|
257
|
+
and an item detail view with a copyable key. The explorer is read-only.
|
|
255
258
|
|
|
256
259
|
**S3-compatible object storage** (MinIO, LocalStack): browse
|
|
257
260
|
buckets as a folder tree, search by prefix or filename, sort scanned objects by
|
package/dist/code-viewer.js
CHANGED
|
@@ -835,6 +835,110 @@ var init_command_resolver = __esm(() => {
|
|
|
835
835
|
activeOverrides = new Map;
|
|
836
836
|
});
|
|
837
837
|
|
|
838
|
+
// web-src/server/name-pattern.ts
|
|
839
|
+
function parseGlobSegment(pattern) {
|
|
840
|
+
const matchers = [];
|
|
841
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
842
|
+
const ch = pattern[i];
|
|
843
|
+
if (ch === "*") {
|
|
844
|
+
matchers.push({ kind: "star" });
|
|
845
|
+
} else if (ch === "?") {
|
|
846
|
+
matchers.push({ kind: "any" });
|
|
847
|
+
} else if (ch === "[") {
|
|
848
|
+
const close = pattern.indexOf("]", i + 1);
|
|
849
|
+
if (close === -1) {
|
|
850
|
+
matchers.push({ kind: "literal", ch: "[" });
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const rawBody = pattern.slice(i + 1, close);
|
|
854
|
+
const negate = rawBody.startsWith("!");
|
|
855
|
+
matchers.push({
|
|
856
|
+
kind: "class",
|
|
857
|
+
body: negate ? rawBody.slice(1) : rawBody,
|
|
858
|
+
negate
|
|
859
|
+
});
|
|
860
|
+
i = close;
|
|
861
|
+
} else {
|
|
862
|
+
matchers.push({ kind: "literal", ch });
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return matchers;
|
|
866
|
+
}
|
|
867
|
+
function charInClassBody(ch, body) {
|
|
868
|
+
for (let i = 0;i < body.length; ) {
|
|
869
|
+
if (body[i + 1] === "-" && i + 2 < body.length) {
|
|
870
|
+
if (ch >= body[i] && ch <= body[i + 2])
|
|
871
|
+
return true;
|
|
872
|
+
i += 3;
|
|
873
|
+
} else {
|
|
874
|
+
if (ch === body[i])
|
|
875
|
+
return true;
|
|
876
|
+
i += 1;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
function matchesAt(matcher, ch) {
|
|
882
|
+
switch (matcher.kind) {
|
|
883
|
+
case "literal":
|
|
884
|
+
return matcher.ch === ch;
|
|
885
|
+
case "any":
|
|
886
|
+
return true;
|
|
887
|
+
case "class":
|
|
888
|
+
return charInClassBody(ch, matcher.body) !== matcher.negate;
|
|
889
|
+
case "star":
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function matchGlobSegment(matchers, name) {
|
|
894
|
+
let mi = 0;
|
|
895
|
+
let ni = 0;
|
|
896
|
+
let starMi = -1;
|
|
897
|
+
let starNi = -1;
|
|
898
|
+
while (ni < name.length) {
|
|
899
|
+
const matcher = matchers[mi];
|
|
900
|
+
if (matcher && matcher.kind === "star") {
|
|
901
|
+
starMi = mi;
|
|
902
|
+
starNi = ni;
|
|
903
|
+
mi++;
|
|
904
|
+
} else if (matcher && matchesAt(matcher, name[ni])) {
|
|
905
|
+
mi++;
|
|
906
|
+
ni++;
|
|
907
|
+
} else if (starMi !== -1) {
|
|
908
|
+
mi = starMi + 1;
|
|
909
|
+
starNi++;
|
|
910
|
+
ni = starNi;
|
|
911
|
+
} else {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
while (matchers[mi]?.kind === "star")
|
|
916
|
+
mi++;
|
|
917
|
+
return mi === matchers.length;
|
|
918
|
+
}
|
|
919
|
+
function compileNamePatterns(patterns) {
|
|
920
|
+
const literals = new Set;
|
|
921
|
+
const globs = [];
|
|
922
|
+
for (const pattern of patterns) {
|
|
923
|
+
const lower = pattern.toLowerCase();
|
|
924
|
+
if (GLOB_CHARS.test(lower)) {
|
|
925
|
+
globs.push(parseGlobSegment(lower));
|
|
926
|
+
} else {
|
|
927
|
+
literals.add(lower);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
matches(name) {
|
|
932
|
+
const lower = name.toLowerCase();
|
|
933
|
+
return literals.has(lower) || globs.some((matchers) => matchGlobSegment(matchers, lower));
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
var GLOB_CHARS;
|
|
938
|
+
var init_name_pattern = __esm(() => {
|
|
939
|
+
GLOB_CHARS = /[*?[\]]/;
|
|
940
|
+
});
|
|
941
|
+
|
|
838
942
|
// web-src/server/runtime.ts
|
|
839
943
|
import { spawn, spawnSync } from "node:child_process";
|
|
840
944
|
import { createReadStream, promises as fs } from "node:fs";
|
|
@@ -2110,7 +2214,7 @@ function sortTreeEntries(entries) {
|
|
|
2110
2214
|
function omittedWorktreeDirectoryReason(name, omitDirNames) {
|
|
2111
2215
|
if (name === ".git")
|
|
2112
2216
|
return "internal";
|
|
2113
|
-
return omitDirNames.
|
|
2217
|
+
return omitDirNames.matches(name) ? "heavy" : undefined;
|
|
2114
2218
|
}
|
|
2115
2219
|
function worktreeSubmodulePaths(cwd) {
|
|
2116
2220
|
if (!existsSync(join3(cwd, ".gitmodules")))
|
|
@@ -2137,7 +2241,7 @@ async function worktreeSubmodulePathsAsync(cwd) {
|
|
|
2137
2241
|
}).filter(Boolean));
|
|
2138
2242
|
}
|
|
2139
2243
|
function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
|
|
2140
|
-
if (excludeNames.
|
|
2244
|
+
if (excludeNames.matches(name))
|
|
2141
2245
|
return {
|
|
2142
2246
|
name,
|
|
2143
2247
|
path: "",
|
|
@@ -2157,8 +2261,8 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
|
|
|
2157
2261
|
function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
2158
2262
|
const base = normalizeTreePath(path);
|
|
2159
2263
|
const root = join3(cwd, base);
|
|
2160
|
-
const omitDirNameSet =
|
|
2161
|
-
const excludeNameSet =
|
|
2264
|
+
const omitDirNameSet = compileNamePatterns(omitDirNames);
|
|
2265
|
+
const excludeNameSet = compileNamePatterns(excludeNames);
|
|
2162
2266
|
const submodulePaths = worktreeSubmodulePaths(cwd);
|
|
2163
2267
|
let directEntries;
|
|
2164
2268
|
try {
|
|
@@ -2200,7 +2304,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
2200
2304
|
return;
|
|
2201
2305
|
}
|
|
2202
2306
|
for (const entry of entries) {
|
|
2203
|
-
if (excludeNameSet.
|
|
2307
|
+
if (excludeNameSet.matches(entry.name))
|
|
2204
2308
|
continue;
|
|
2205
2309
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2206
2310
|
const full = join3(dir, entry.name);
|
|
@@ -2236,8 +2340,8 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
2236
2340
|
async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
2237
2341
|
const base = normalizeTreePath(path);
|
|
2238
2342
|
const root = join3(cwd, base);
|
|
2239
|
-
const omitDirNameSet =
|
|
2240
|
-
const excludeNameSet =
|
|
2343
|
+
const omitDirNameSet = compileNamePatterns(omitDirNames);
|
|
2344
|
+
const excludeNameSet = compileNamePatterns(excludeNames);
|
|
2241
2345
|
const submodulePaths = await worktreeSubmodulePathsAsync(cwd);
|
|
2242
2346
|
let directEntries;
|
|
2243
2347
|
try {
|
|
@@ -2287,7 +2391,7 @@ async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames
|
|
|
2287
2391
|
return;
|
|
2288
2392
|
}
|
|
2289
2393
|
for (const entry of entries) {
|
|
2290
|
-
if (excludeNameSet.
|
|
2394
|
+
if (excludeNameSet.matches(entry.name))
|
|
2291
2395
|
continue;
|
|
2292
2396
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2293
2397
|
const full = join3(dir, entry.name);
|
|
@@ -2726,6 +2830,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
2726
2830
|
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
2727
2831
|
var init_git = __esm(() => {
|
|
2728
2832
|
init_command_resolver();
|
|
2833
|
+
init_name_pattern();
|
|
2729
2834
|
init_runtime();
|
|
2730
2835
|
ALWAYS_WORKTREE_OMIT_DIR_NAMES = [".devbox", ".direnv"];
|
|
2731
2836
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
@@ -8766,11 +8871,11 @@ function normalizeGrepMax(value) {
|
|
|
8766
8871
|
return Math.min(parsed, GREP_ABSOLUTE_MAX);
|
|
8767
8872
|
}
|
|
8768
8873
|
function isSkippableSearchPath(path, omitDirNames = [], excludeNames = []) {
|
|
8769
|
-
const omitDirs =
|
|
8770
|
-
const excluded =
|
|
8874
|
+
const omitDirs = compileNamePatterns(omitDirNames);
|
|
8875
|
+
const excluded = compileNamePatterns(excludeNames);
|
|
8771
8876
|
return path.split(/[\\/]+/).some((part) => {
|
|
8772
8877
|
const lower = part.toLowerCase();
|
|
8773
|
-
return lower === ".git" || lower === ".code-viewer" || omitDirs.
|
|
8878
|
+
return lower === ".git" || lower === ".code-viewer" || omitDirs.matches(part) || excluded.matches(part);
|
|
8774
8879
|
});
|
|
8775
8880
|
}
|
|
8776
8881
|
function fixedStringLineMatches(path, text, query, max) {
|
|
@@ -8877,6 +8982,7 @@ function parseGitGrepOutput(stdout, ref, max, omitDirNames = [], excludeNames =
|
|
|
8877
8982
|
}
|
|
8878
8983
|
var GREP_DEFAULT_MAX = 200, GREP_ABSOLUTE_MAX = 500, GREP_MAX_FILE_BYTES, FILE_SEARCH_ABSOLUTE_MAX = 50000, DEFAULT_EXCLUDE_NAMES;
|
|
8879
8984
|
var init_search = __esm(() => {
|
|
8985
|
+
init_name_pattern();
|
|
8880
8986
|
GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
8881
8987
|
DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
|
|
8882
8988
|
});
|
|
@@ -23141,7 +23247,8 @@ async function rgAvailableAsync(cwd) {
|
|
|
23141
23247
|
return rgAvailableCache;
|
|
23142
23248
|
}
|
|
23143
23249
|
function isExcludedScopePath(path, excludeNames) {
|
|
23144
|
-
|
|
23250
|
+
const excluded = compileNamePatterns(excludeNames);
|
|
23251
|
+
return path.split(/[\\/]+/).some((part) => excluded.matches(part));
|
|
23145
23252
|
}
|
|
23146
23253
|
function isSafePath(path) {
|
|
23147
23254
|
if (!path || path.startsWith("/") || path.startsWith("\\") || path.includes("\x00"))
|
|
@@ -23340,6 +23447,7 @@ var init_search_service = __esm(() => {
|
|
|
23340
23447
|
init_command_resolver();
|
|
23341
23448
|
init_spawn_runner();
|
|
23342
23449
|
init_git();
|
|
23450
|
+
init_name_pattern();
|
|
23343
23451
|
init_search();
|
|
23344
23452
|
});
|
|
23345
23453
|
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -10480,6 +10480,80 @@ ${frontmatter.yaml}
|
|
|
10480
10480
|
return true;
|
|
10481
10481
|
}
|
|
10482
10482
|
|
|
10483
|
+
// web-src/views/database/detail-table.ts
|
|
10484
|
+
function createDetailTable(headers, rows, emptyText) {
|
|
10485
|
+
const table2 = document.createElement("table");
|
|
10486
|
+
table2.className = "db-detail-table";
|
|
10487
|
+
const thead = document.createElement("thead");
|
|
10488
|
+
const headRow = document.createElement("tr");
|
|
10489
|
+
for (const label of headers) {
|
|
10490
|
+
const th = document.createElement("th");
|
|
10491
|
+
th.textContent = label;
|
|
10492
|
+
headRow.appendChild(th);
|
|
10493
|
+
}
|
|
10494
|
+
thead.appendChild(headRow);
|
|
10495
|
+
table2.appendChild(thead);
|
|
10496
|
+
const tbody = document.createElement("tbody");
|
|
10497
|
+
if (rows.length === 0) {
|
|
10498
|
+
const row = document.createElement("tr");
|
|
10499
|
+
const td = document.createElement("td");
|
|
10500
|
+
td.colSpan = headers.length;
|
|
10501
|
+
td.className = "db-value-empty";
|
|
10502
|
+
td.textContent = emptyText;
|
|
10503
|
+
row.appendChild(td);
|
|
10504
|
+
tbody.appendChild(row);
|
|
10505
|
+
}
|
|
10506
|
+
for (const cells of rows) {
|
|
10507
|
+
const row = document.createElement("tr");
|
|
10508
|
+
cells.forEach((cellText, index) => {
|
|
10509
|
+
const td = document.createElement("td");
|
|
10510
|
+
td.textContent = cellText;
|
|
10511
|
+
td.className = index === 0 ? "db-detail-table-primary" : "db-detail-table-muted";
|
|
10512
|
+
row.appendChild(td);
|
|
10513
|
+
});
|
|
10514
|
+
tbody.appendChild(row);
|
|
10515
|
+
}
|
|
10516
|
+
table2.appendChild(tbody);
|
|
10517
|
+
return table2;
|
|
10518
|
+
}
|
|
10519
|
+
|
|
10520
|
+
// web-src/views/database/detail-tabs.ts
|
|
10521
|
+
function createDetailTabs(specs, initial, onSelect) {
|
|
10522
|
+
const tabsEl = document.createElement("div");
|
|
10523
|
+
tabsEl.className = "db-detail-tabs";
|
|
10524
|
+
const buttons = {};
|
|
10525
|
+
const bodies = {};
|
|
10526
|
+
let active = initial;
|
|
10527
|
+
function setActive(tab) {
|
|
10528
|
+
active = tab;
|
|
10529
|
+
for (const spec of specs) {
|
|
10530
|
+
buttons[spec.id].classList.toggle("active", spec.id === tab);
|
|
10531
|
+
bodies[spec.id].hidden = spec.id !== tab;
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
for (const spec of specs) {
|
|
10535
|
+
const btn = document.createElement("button");
|
|
10536
|
+
btn.type = "button";
|
|
10537
|
+
btn.className = "db-detail-tab";
|
|
10538
|
+
btn.textContent = spec.label;
|
|
10539
|
+
btn.addEventListener("click", () => {
|
|
10540
|
+
setActive(spec.id);
|
|
10541
|
+
onSelect(spec.id);
|
|
10542
|
+
});
|
|
10543
|
+
buttons[spec.id] = btn;
|
|
10544
|
+
tabsEl.appendChild(btn);
|
|
10545
|
+
const body = document.createElement("div");
|
|
10546
|
+
body.className = "db-detail-tab-body";
|
|
10547
|
+
bodies[spec.id] = body;
|
|
10548
|
+
}
|
|
10549
|
+
setActive(initial);
|
|
10550
|
+
function setLabels(labels) {
|
|
10551
|
+
for (const spec of specs)
|
|
10552
|
+
buttons[spec.id].textContent = labels[spec.id];
|
|
10553
|
+
}
|
|
10554
|
+
return { tabsEl, bodies, getActive: () => active, setActive, setLabels };
|
|
10555
|
+
}
|
|
10556
|
+
|
|
10483
10557
|
// web-src/views/database/i18n.ts
|
|
10484
10558
|
var EN = {
|
|
10485
10559
|
nav: {
|
|
@@ -10820,7 +10894,21 @@ ${frontmatter.yaml}
|
|
|
10820
10894
|
copied: "Copied",
|
|
10821
10895
|
copyFailed: "Copy failed",
|
|
10822
10896
|
invalidAttributeValues: "Invalid attribute values JSON",
|
|
10823
|
-
runQuery: "Run"
|
|
10897
|
+
runQuery: "Run",
|
|
10898
|
+
structureTab: "Structure",
|
|
10899
|
+
itemTab: "Item",
|
|
10900
|
+
selectTable: "Select a table to view its structure.",
|
|
10901
|
+
attributeHeader: "Attribute",
|
|
10902
|
+
typeHeader: "Type",
|
|
10903
|
+
keyRoleHeader: "Key",
|
|
10904
|
+
noAttributes: "(no attributes)",
|
|
10905
|
+
globalSecondaryIndexes: "Global secondary indexes",
|
|
10906
|
+
localSecondaryIndexes: "Local secondary indexes",
|
|
10907
|
+
projectionAll: "ALL",
|
|
10908
|
+
projectionKeysOnly: "KEYS_ONLY",
|
|
10909
|
+
projectionInclude: (attrs) => `INCLUDE (${attrs})`,
|
|
10910
|
+
keySchemaOnlyHint: "DynamoDB only enforces types for key attributes. Additional attributes will appear here once items are loaded.",
|
|
10911
|
+
inferredAttributesNote: (count) => `Attributes beyond the key schema are inferred from ${count.toLocaleString()} loaded item${count === 1 ? "" : "s"} and may not reflect every item.`
|
|
10824
10912
|
}
|
|
10825
10913
|
}
|
|
10826
10914
|
};
|
|
@@ -11163,7 +11251,21 @@ ${frontmatter.yaml}
|
|
|
11163
11251
|
copied: "コピーしました",
|
|
11164
11252
|
copyFailed: "コピーに失敗しました",
|
|
11165
11253
|
invalidAttributeValues: "属性値の JSON が不正です",
|
|
11166
|
-
runQuery: "実行"
|
|
11254
|
+
runQuery: "実行",
|
|
11255
|
+
structureTab: "構造",
|
|
11256
|
+
itemTab: "アイテム",
|
|
11257
|
+
selectTable: "テーブルを選択すると構造が表示されます。",
|
|
11258
|
+
attributeHeader: "属性",
|
|
11259
|
+
typeHeader: "型",
|
|
11260
|
+
keyRoleHeader: "キー",
|
|
11261
|
+
noAttributes: "(属性がありません)",
|
|
11262
|
+
globalSecondaryIndexes: "グローバルセカンダリインデックス",
|
|
11263
|
+
localSecondaryIndexes: "ローカルセカンダリインデックス",
|
|
11264
|
+
projectionAll: "ALL",
|
|
11265
|
+
projectionKeysOnly: "KEYS_ONLY",
|
|
11266
|
+
projectionInclude: (attrs) => `INCLUDE (${attrs})`,
|
|
11267
|
+
keySchemaOnlyHint: "DynamoDBがスキーマとして強制するのはキー属性のみです。アイテムを読み込むと追加の属性がここに表示されます。",
|
|
11268
|
+
inferredAttributesNote: (count) => `キー以外の属性は、読み込み済みの${count.toLocaleString()}件のアイテムから検出したものです (全アイテムを網羅するとは限りません)。`
|
|
11167
11269
|
}
|
|
11168
11270
|
}
|
|
11169
11271
|
};
|
|
@@ -11237,6 +11339,25 @@ ${frontmatter.yaml}
|
|
|
11237
11339
|
out[k] = unwrapAttributeValue(v);
|
|
11238
11340
|
return out;
|
|
11239
11341
|
}
|
|
11342
|
+
var ATTRIBUTE_VALUE_TAG_ORDER = [
|
|
11343
|
+
"S",
|
|
11344
|
+
"N",
|
|
11345
|
+
"B",
|
|
11346
|
+
"BOOL",
|
|
11347
|
+
"NULL",
|
|
11348
|
+
"M",
|
|
11349
|
+
"L",
|
|
11350
|
+
"SS",
|
|
11351
|
+
"NS",
|
|
11352
|
+
"BS"
|
|
11353
|
+
];
|
|
11354
|
+
function attributeValueTag(av) {
|
|
11355
|
+
for (const tag of ATTRIBUTE_VALUE_TAG_ORDER) {
|
|
11356
|
+
if (tag in av)
|
|
11357
|
+
return tag;
|
|
11358
|
+
}
|
|
11359
|
+
return "?";
|
|
11360
|
+
}
|
|
11240
11361
|
function previewItem(item) {
|
|
11241
11362
|
const entries = Object.entries(item).slice(0, 6);
|
|
11242
11363
|
const parts = entries.map(([key, rawValue]) => {
|
|
@@ -11347,8 +11468,17 @@ ${frontmatter.yaml}
|
|
|
11347
11468
|
moreBtn.hidden = true;
|
|
11348
11469
|
itemListPane.appendChild(moreBtn);
|
|
11349
11470
|
const detailPane = document.createElement("div");
|
|
11350
|
-
detailPane.className = "
|
|
11351
|
-
|
|
11471
|
+
detailPane.className = "db-detail-pane";
|
|
11472
|
+
const detailTabs = createDetailTabs([
|
|
11473
|
+
{ id: "structure", label: text3().dynamodb.structureTab },
|
|
11474
|
+
{ id: "item", label: text3().dynamodb.itemTab }
|
|
11475
|
+
], "structure", () => notifySelectionChange());
|
|
11476
|
+
detailPane.appendChild(detailTabs.tabsEl);
|
|
11477
|
+
const structureBody = detailTabs.bodies.structure;
|
|
11478
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11479
|
+
const itemBody = detailTabs.bodies.item;
|
|
11480
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
11481
|
+
detailPane.append(structureBody, itemBody);
|
|
11352
11482
|
container.append(itemListPane, detailPane);
|
|
11353
11483
|
let currentDbId = null;
|
|
11354
11484
|
let currentTable = null;
|
|
@@ -11360,6 +11490,7 @@ ${frontmatter.yaml}
|
|
|
11360
11490
|
let currentItemKeyToken = null;
|
|
11361
11491
|
let cumulativeShownCount = 0;
|
|
11362
11492
|
let cumulativeScannedCount = 0;
|
|
11493
|
+
let lastRenderedItem = null;
|
|
11363
11494
|
let disposed = false;
|
|
11364
11495
|
let loadRunId = 0;
|
|
11365
11496
|
let itemRunId = 0;
|
|
@@ -11384,6 +11515,9 @@ ${frontmatter.yaml}
|
|
|
11384
11515
|
queryModeBtn.classList.toggle("active", mode === "query");
|
|
11385
11516
|
keyConditionInput.hidden = mode !== "query";
|
|
11386
11517
|
}
|
|
11518
|
+
function setDetailTab(tab) {
|
|
11519
|
+
detailTabs.setActive(tab);
|
|
11520
|
+
}
|
|
11387
11521
|
function renderTables(tableNames, append = false) {
|
|
11388
11522
|
if (!append) {
|
|
11389
11523
|
tableList.innerHTML = "";
|
|
@@ -11436,10 +11570,51 @@ ${frontmatter.yaml}
|
|
|
11436
11570
|
}
|
|
11437
11571
|
itemList.appendChild(fragment);
|
|
11438
11572
|
}
|
|
11439
|
-
function
|
|
11440
|
-
|
|
11441
|
-
if (!
|
|
11573
|
+
function projectionText(projection) {
|
|
11574
|
+
const t2 = text3().dynamodb;
|
|
11575
|
+
if (!projection?.ProjectionType)
|
|
11576
|
+
return "";
|
|
11577
|
+
if (projection.ProjectionType === "ALL")
|
|
11578
|
+
return t2.projectionAll;
|
|
11579
|
+
if (projection.ProjectionType === "KEYS_ONLY")
|
|
11580
|
+
return t2.projectionKeysOnly;
|
|
11581
|
+
return t2.projectionInclude((projection.NonKeyAttributes ?? []).join(", "));
|
|
11582
|
+
}
|
|
11583
|
+
function renderSecondaryIndexes(label, indexes) {
|
|
11584
|
+
if (!indexes || indexes.length === 0)
|
|
11442
11585
|
return;
|
|
11586
|
+
const section = document.createElement("div");
|
|
11587
|
+
section.className = "dynamodb-index-section";
|
|
11588
|
+
const heading2 = document.createElement("div");
|
|
11589
|
+
heading2.className = "dynamodb-index-heading";
|
|
11590
|
+
heading2.textContent = label;
|
|
11591
|
+
section.appendChild(heading2);
|
|
11592
|
+
for (const ix of indexes) {
|
|
11593
|
+
const row = document.createElement("div");
|
|
11594
|
+
row.className = "dynamodb-index-row";
|
|
11595
|
+
const name = document.createElement("div");
|
|
11596
|
+
name.className = "dynamodb-index-name";
|
|
11597
|
+
name.textContent = ix.IndexName ?? "";
|
|
11598
|
+
const detail = document.createElement("div");
|
|
11599
|
+
detail.className = "dynamodb-index-detail";
|
|
11600
|
+
const keyPart = (ix.KeySchema ?? []).map((k) => `${k.AttributeName} (${k.KeyType})`).join(", ");
|
|
11601
|
+
detail.textContent = [
|
|
11602
|
+
keyPart,
|
|
11603
|
+
projectionText(ix.Projection),
|
|
11604
|
+
ix.ItemCount !== undefined ? `${ix.ItemCount.toLocaleString()} items` : undefined
|
|
11605
|
+
].filter(Boolean).join(" / ");
|
|
11606
|
+
row.append(name, detail);
|
|
11607
|
+
section.appendChild(row);
|
|
11608
|
+
}
|
|
11609
|
+
structureBody.appendChild(section);
|
|
11610
|
+
}
|
|
11611
|
+
function renderTableStructure() {
|
|
11612
|
+
structureBody.innerHTML = "";
|
|
11613
|
+
if (!currentTableInfo) {
|
|
11614
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11615
|
+
return;
|
|
11616
|
+
}
|
|
11617
|
+
const t2 = text3().dynamodb;
|
|
11443
11618
|
const header = document.createElement("div");
|
|
11444
11619
|
header.className = "dynamodb-table-info-header";
|
|
11445
11620
|
header.textContent = currentTableInfo.TableName ?? currentTable ?? "";
|
|
@@ -11447,26 +11622,54 @@ ${frontmatter.yaml}
|
|
|
11447
11622
|
meta.className = "dynamodb-table-info-meta";
|
|
11448
11623
|
meta.textContent = [
|
|
11449
11624
|
currentTableInfo.TableStatus,
|
|
11450
|
-
currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined
|
|
11625
|
+
currentTableInfo.ItemCount !== undefined ? `${currentTableInfo.ItemCount.toLocaleString()} items` : undefined,
|
|
11626
|
+
currentTableInfo.TableSizeBytes !== undefined ? formatBytes(currentTableInfo.TableSizeBytes) : undefined,
|
|
11627
|
+
currentTableInfo.BillingModeSummary?.BillingMode
|
|
11451
11628
|
].filter(Boolean).join(" / ");
|
|
11452
|
-
|
|
11453
|
-
const
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
const
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11629
|
+
structureBody.append(header, meta);
|
|
11630
|
+
const attributeTypeByName = new Map((currentTableInfo.AttributeDefinitions ?? []).map((a2) => [
|
|
11631
|
+
a2.AttributeName,
|
|
11632
|
+
a2.AttributeType
|
|
11633
|
+
]));
|
|
11634
|
+
const keyRoleByName = new Map((currentTableInfo.KeySchema ?? []).map((k) => [
|
|
11635
|
+
k.AttributeName,
|
|
11636
|
+
k.KeyType
|
|
11637
|
+
]));
|
|
11638
|
+
const attrNames = [...attributeTypeByName.keys()];
|
|
11639
|
+
const inferredTypesByName = new Map;
|
|
11640
|
+
for (const item of itemsByKeyToken.values()) {
|
|
11641
|
+
for (const [attrName, av] of Object.entries(item)) {
|
|
11642
|
+
if (attributeTypeByName.has(attrName))
|
|
11643
|
+
continue;
|
|
11644
|
+
const set2 = inferredTypesByName.get(attrName) ?? new Set;
|
|
11645
|
+
set2.add(attributeValueTag(av));
|
|
11646
|
+
inferredTypesByName.set(attrName, set2);
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11649
|
+
const inferredNames = [...inferredTypesByName.keys()].sort();
|
|
11650
|
+
const rows = [
|
|
11651
|
+
...attrNames.map((name) => [
|
|
11652
|
+
name,
|
|
11653
|
+
attributeTypeByName.get(name) ?? "",
|
|
11654
|
+
keyRoleByName.get(name) ?? ""
|
|
11655
|
+
]),
|
|
11656
|
+
...inferredNames.map((name) => [
|
|
11657
|
+
name,
|
|
11658
|
+
[...inferredTypesByName.get(name) ?? []].join(", "),
|
|
11659
|
+
""
|
|
11660
|
+
])
|
|
11661
|
+
];
|
|
11662
|
+
structureBody.appendChild(createDetailTable([t2.attributeHeader, t2.typeHeader, t2.keyRoleHeader], rows, t2.noAttributes));
|
|
11663
|
+
const note = document.createElement("div");
|
|
11664
|
+
note.className = "dynamodb-attr-note";
|
|
11665
|
+
note.textContent = inferredNames.length === 0 ? t2.keySchemaOnlyHint : t2.inferredAttributesNote(itemsByKeyToken.size);
|
|
11666
|
+
structureBody.appendChild(note);
|
|
11667
|
+
renderSecondaryIndexes(t2.globalSecondaryIndexes, currentTableInfo.GlobalSecondaryIndexes);
|
|
11668
|
+
renderSecondaryIndexes(t2.localSecondaryIndexes, currentTableInfo.LocalSecondaryIndexes);
|
|
11467
11669
|
}
|
|
11468
11670
|
function renderItemDetail(item) {
|
|
11469
|
-
|
|
11671
|
+
lastRenderedItem = item;
|
|
11672
|
+
itemBody.innerHTML = "";
|
|
11470
11673
|
const header = document.createElement("div");
|
|
11471
11674
|
header.className = "dynamodb-item-detail-header";
|
|
11472
11675
|
const title = document.createElement("span");
|
|
@@ -11489,7 +11692,7 @@ ${frontmatter.yaml}
|
|
|
11489
11692
|
}
|
|
11490
11693
|
});
|
|
11491
11694
|
header.append(title, copyBtn, copyStatus);
|
|
11492
|
-
|
|
11695
|
+
itemBody.appendChild(header);
|
|
11493
11696
|
const pre = document.createElement("pre");
|
|
11494
11697
|
pre.className = "dynamodb-item-source";
|
|
11495
11698
|
try {
|
|
@@ -11497,12 +11700,13 @@ ${frontmatter.yaml}
|
|
|
11497
11700
|
} catch {
|
|
11498
11701
|
pre.textContent = String(item);
|
|
11499
11702
|
}
|
|
11500
|
-
|
|
11703
|
+
itemBody.appendChild(pre);
|
|
11501
11704
|
}
|
|
11502
11705
|
function selectItem(item, token) {
|
|
11503
11706
|
currentItemKeyToken = token ?? itemKeyToken(extractItemKey(item, currentTableInfo?.KeySchema));
|
|
11504
11707
|
highlightActiveItem(currentItemKeyToken);
|
|
11505
11708
|
renderItemDetail(item);
|
|
11709
|
+
setDetailTab("item");
|
|
11506
11710
|
notifySelectionChange();
|
|
11507
11711
|
}
|
|
11508
11712
|
async function loadItems(append) {
|
|
@@ -11538,8 +11742,6 @@ ${frontmatter.yaml}
|
|
|
11538
11742
|
cumulativeShownCount = 0;
|
|
11539
11743
|
cumulativeScannedCount = 0;
|
|
11540
11744
|
setPaneStatus(itemList, "Loading items...");
|
|
11541
|
-
if (!currentTableInfo)
|
|
11542
|
-
setPaneEmpty(detailPane, text3().dynamodb.selectItem);
|
|
11543
11745
|
}
|
|
11544
11746
|
try {
|
|
11545
11747
|
const params = new URLSearchParams({
|
|
@@ -11581,6 +11783,8 @@ ${frontmatter.yaml}
|
|
|
11581
11783
|
} else {
|
|
11582
11784
|
appendItems(data.items);
|
|
11583
11785
|
}
|
|
11786
|
+
if (currentTableInfo)
|
|
11787
|
+
renderTableStructure();
|
|
11584
11788
|
currentNextToken = data.lastEvaluatedKey;
|
|
11585
11789
|
moreBtn.hidden = !data.lastEvaluatedKey;
|
|
11586
11790
|
cumulativeShownCount += data.items.length;
|
|
@@ -11602,21 +11806,29 @@ ${frontmatter.yaml}
|
|
|
11602
11806
|
return;
|
|
11603
11807
|
const slot = tableInfoGuard.start();
|
|
11604
11808
|
const requestDbId = currentDbId;
|
|
11809
|
+
const isStaleRequest = () => disposed || slot.isStale() || requestDbId !== currentDbId || currentTable !== table2;
|
|
11605
11810
|
try {
|
|
11606
11811
|
const params = new URLSearchParams({ db: requestDbId, table: table2 });
|
|
11607
11812
|
const res = await trackLoad(fetch(`/_db/dynamodb/table?${params}`, { signal: slot.signal }));
|
|
11608
|
-
if (
|
|
11813
|
+
if (isStaleRequest())
|
|
11609
11814
|
return;
|
|
11610
|
-
if (!res.ok)
|
|
11815
|
+
if (!res.ok) {
|
|
11816
|
+
const errText = await res.text();
|
|
11817
|
+
setPaneStatus(structureBody, `Error: ${errText || res.statusText}`, {
|
|
11818
|
+
error: true
|
|
11819
|
+
});
|
|
11611
11820
|
return;
|
|
11821
|
+
}
|
|
11612
11822
|
const data = await res.json();
|
|
11613
|
-
if (
|
|
11823
|
+
if (isStaleRequest())
|
|
11614
11824
|
return;
|
|
11615
|
-
}
|
|
11616
11825
|
currentTableInfo = data.table;
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11826
|
+
renderTableStructure();
|
|
11827
|
+
} catch (err) {
|
|
11828
|
+
if (isStaleRequest())
|
|
11829
|
+
return;
|
|
11830
|
+
setPaneStatus(structureBody, `Error: ${err instanceof Error ? err.message : String(err)}`, { error: true });
|
|
11831
|
+
} finally {
|
|
11620
11832
|
slot.finish();
|
|
11621
11833
|
}
|
|
11622
11834
|
}
|
|
@@ -11656,7 +11868,14 @@ ${frontmatter.yaml}
|
|
|
11656
11868
|
currentNextToken = undefined;
|
|
11657
11869
|
highlightActiveTable(name);
|
|
11658
11870
|
notifySelectionChange();
|
|
11659
|
-
|
|
11871
|
+
lastRenderedItem = null;
|
|
11872
|
+
itemsByKeyToken.clear();
|
|
11873
|
+
itemRowsByKeyToken.clear();
|
|
11874
|
+
structureBody.innerHTML = "";
|
|
11875
|
+
setPaneStatus(structureBody, "Loading table...");
|
|
11876
|
+
itemBody.innerHTML = "";
|
|
11877
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
11878
|
+
setDetailTab("structure");
|
|
11660
11879
|
await fetchTableInfo(name);
|
|
11661
11880
|
await loadItems(false);
|
|
11662
11881
|
}
|
|
@@ -11773,7 +11992,12 @@ ${frontmatter.yaml}
|
|
|
11773
11992
|
activeItemRow = null;
|
|
11774
11993
|
moreBtn.hidden = true;
|
|
11775
11994
|
tableMoreBtn.hidden = true;
|
|
11776
|
-
|
|
11995
|
+
lastRenderedItem = null;
|
|
11996
|
+
structureBody.innerHTML = "";
|
|
11997
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
11998
|
+
itemBody.innerHTML = "";
|
|
11999
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
12000
|
+
setDetailTab("structure");
|
|
11777
12001
|
setPaneStatus(tableList, "Loading tables...");
|
|
11778
12002
|
try {
|
|
11779
12003
|
const res = await trackLoad(fetch(`/_db/dynamodb/tables?db=${encodeURIComponent(dbId)}`, {
|
|
@@ -11809,6 +12033,8 @@ ${frontmatter.yaml}
|
|
|
11809
12033
|
await selectItemByKey(key);
|
|
11810
12034
|
} catch {}
|
|
11811
12035
|
}
|
|
12036
|
+
if (initial?.detailTab)
|
|
12037
|
+
setDetailTab(initial.detailTab);
|
|
11812
12038
|
} finally {
|
|
11813
12039
|
suppressNotify = false;
|
|
11814
12040
|
}
|
|
@@ -11854,7 +12080,12 @@ ${frontmatter.yaml}
|
|
|
11854
12080
|
itemRowsByKeyToken.clear();
|
|
11855
12081
|
activeItemRow = null;
|
|
11856
12082
|
moreBtn.hidden = true;
|
|
11857
|
-
|
|
12083
|
+
lastRenderedItem = null;
|
|
12084
|
+
structureBody.innerHTML = "";
|
|
12085
|
+
setPaneEmpty(structureBody, text3().dynamodb.selectTable);
|
|
12086
|
+
itemBody.innerHTML = "";
|
|
12087
|
+
setPaneEmpty(itemBody, text3().dynamodb.selectItem);
|
|
12088
|
+
setDetailTab("structure");
|
|
11858
12089
|
}
|
|
11859
12090
|
setMode("scan");
|
|
11860
12091
|
function getSelection() {
|
|
@@ -11865,7 +12096,8 @@ ${frontmatter.yaml}
|
|
|
11865
12096
|
filterExpression: filterInput.value.trim() || undefined,
|
|
11866
12097
|
expressionAttributeValues: attributeValuesInput.value.trim() || undefined,
|
|
11867
12098
|
scanIndexForward: currentMode === "query" ? currentScanIndexForward : undefined,
|
|
11868
|
-
itemKey: currentItemKeyToken ?? undefined
|
|
12099
|
+
itemKey: currentItemKeyToken ?? undefined,
|
|
12100
|
+
detailTab: detailTabs.getActive()
|
|
11869
12101
|
};
|
|
11870
12102
|
}
|
|
11871
12103
|
function dispose() {
|
|
@@ -11883,12 +12115,18 @@ ${frontmatter.yaml}
|
|
|
11883
12115
|
runBtn.textContent = t2.dynamodb.runQuery;
|
|
11884
12116
|
tableMoreBtn.textContent = t2.common.loadMore;
|
|
11885
12117
|
moreBtn.textContent = t2.common.loadMore;
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
12118
|
+
detailTabs.setLabels({
|
|
12119
|
+
structure: t2.dynamodb.structureTab,
|
|
12120
|
+
item: t2.dynamodb.itemTab
|
|
12121
|
+
});
|
|
12122
|
+
if (currentTableInfo)
|
|
12123
|
+
renderTableStructure();
|
|
12124
|
+
else
|
|
12125
|
+
setPaneEmpty(structureBody, t2.dynamodb.selectTable);
|
|
12126
|
+
if (lastRenderedItem)
|
|
12127
|
+
renderItemDetail(lastRenderedItem);
|
|
12128
|
+
else
|
|
12129
|
+
setPaneEmpty(itemBody, t2.dynamodb.selectItem);
|
|
11892
12130
|
}
|
|
11893
12131
|
return {
|
|
11894
12132
|
el: container,
|
|
@@ -11951,25 +12189,15 @@ ${frontmatter.yaml}
|
|
|
11951
12189
|
docMoreBtn.hidden = true;
|
|
11952
12190
|
docListPane.appendChild(docMoreBtn);
|
|
11953
12191
|
const detailPane = document.createElement("div");
|
|
11954
|
-
detailPane.className = "
|
|
11955
|
-
const detailTabs =
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
const tabDoc = document.createElement("button");
|
|
11962
|
-
tabDoc.type = "button";
|
|
11963
|
-
tabDoc.className = "es-detail-tab";
|
|
11964
|
-
tabDoc.textContent = text3().es.doc;
|
|
11965
|
-
detailTabs.append(tabMapping, tabDoc);
|
|
11966
|
-
detailPane.appendChild(detailTabs);
|
|
11967
|
-
const mappingBody = document.createElement("div");
|
|
11968
|
-
mappingBody.className = "es-mapping-body";
|
|
12192
|
+
detailPane.className = "db-detail-pane";
|
|
12193
|
+
const detailTabs = createDetailTabs([
|
|
12194
|
+
{ id: "mapping", label: text3().es.mapping },
|
|
12195
|
+
{ id: "doc", label: text3().es.doc }
|
|
12196
|
+
], "mapping", () => {});
|
|
12197
|
+
detailPane.appendChild(detailTabs.tabsEl);
|
|
12198
|
+
const mappingBody = detailTabs.bodies.mapping;
|
|
11969
12199
|
setPaneEmpty(mappingBody, text3().es.selectIndex);
|
|
11970
|
-
const docBody =
|
|
11971
|
-
docBody.className = "es-doc-body";
|
|
11972
|
-
docBody.hidden = true;
|
|
12200
|
+
const docBody = detailTabs.bodies.doc;
|
|
11973
12201
|
setPaneEmpty(docBody, text3().es.selectDoc);
|
|
11974
12202
|
detailPane.append(mappingBody, docBody);
|
|
11975
12203
|
container.append(docListPane, detailPane);
|
|
@@ -12094,13 +12322,8 @@ ${frontmatter.yaml}
|
|
|
12094
12322
|
}
|
|
12095
12323
|
function setDetailTab(tab) {
|
|
12096
12324
|
detailTab = tab;
|
|
12097
|
-
|
|
12098
|
-
tabDoc.classList.toggle("active", tab === "doc");
|
|
12099
|
-
mappingBody.hidden = tab !== "mapping";
|
|
12100
|
-
docBody.hidden = tab !== "doc";
|
|
12325
|
+
detailTabs.setActive(tab);
|
|
12101
12326
|
}
|
|
12102
|
-
tabMapping.addEventListener("click", () => setDetailTab("mapping"));
|
|
12103
|
-
tabDoc.addEventListener("click", () => setDetailTab("doc"));
|
|
12104
12327
|
function renderMapping(resp) {
|
|
12105
12328
|
lastMapping = resp;
|
|
12106
12329
|
mappingBody.innerHTML = "";
|
|
@@ -12108,43 +12331,13 @@ ${frontmatter.yaml}
|
|
|
12108
12331
|
header.className = "es-mapping-header";
|
|
12109
12332
|
header.textContent = resp.mapping.index;
|
|
12110
12333
|
mappingBody.appendChild(header);
|
|
12111
|
-
const table2 = document.createElement("table");
|
|
12112
|
-
table2.className = "es-mapping-table";
|
|
12113
|
-
const thead = document.createElement("thead");
|
|
12114
|
-
const headRow = document.createElement("tr");
|
|
12115
|
-
for (const label of [text3().es.fieldHeader, text3().es.typeHeader]) {
|
|
12116
|
-
const th = document.createElement("th");
|
|
12117
|
-
th.textContent = label;
|
|
12118
|
-
headRow.appendChild(th);
|
|
12119
|
-
}
|
|
12120
|
-
thead.appendChild(headRow);
|
|
12121
|
-
table2.appendChild(thead);
|
|
12122
|
-
const tbody = document.createElement("tbody");
|
|
12123
12334
|
const props = resp.mapping.properties;
|
|
12124
12335
|
const keys = Object.keys(props).sort();
|
|
12125
|
-
|
|
12126
|
-
const row = document.createElement("tr");
|
|
12127
|
-
const td = document.createElement("td");
|
|
12128
|
-
td.colSpan = 2;
|
|
12129
|
-
td.className = "es-value-empty";
|
|
12130
|
-
td.textContent = text3().es.noMappedFields;
|
|
12131
|
-
row.appendChild(td);
|
|
12132
|
-
tbody.appendChild(row);
|
|
12133
|
-
}
|
|
12134
|
-
for (const key of keys) {
|
|
12135
|
-
const row = document.createElement("tr");
|
|
12136
|
-
const fieldTd = document.createElement("td");
|
|
12137
|
-
fieldTd.className = "es-mapping-field";
|
|
12138
|
-
fieldTd.textContent = key;
|
|
12139
|
-
const typeTd = document.createElement("td");
|
|
12140
|
-
typeTd.className = "es-mapping-type";
|
|
12336
|
+
const rows = keys.map((key) => {
|
|
12141
12337
|
const p2 = props[key];
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
}
|
|
12146
|
-
table2.appendChild(tbody);
|
|
12147
|
-
mappingBody.appendChild(table2);
|
|
12338
|
+
return [key, p2.type ?? (p2.properties ? "object" : "(unknown)")];
|
|
12339
|
+
});
|
|
12340
|
+
mappingBody.appendChild(createDetailTable([text3().es.fieldHeader, text3().es.typeHeader], rows, text3().es.noMappedFields));
|
|
12148
12341
|
}
|
|
12149
12342
|
function mkBtn(label, cls) {
|
|
12150
12343
|
const b2 = document.createElement("button");
|
|
@@ -12649,11 +12842,10 @@ ${frontmatter.yaml}
|
|
|
12649
12842
|
searchInput.placeholder = t2.es.queryPlaceholder;
|
|
12650
12843
|
searchBtn.textContent = t2.common.search;
|
|
12651
12844
|
docMoreBtn.textContent = t2.common.loadMore;
|
|
12652
|
-
|
|
12653
|
-
tabDoc.textContent = t2.es.doc;
|
|
12845
|
+
detailTabs.setLabels({ mapping: t2.es.mapping, doc: t2.es.doc });
|
|
12654
12846
|
if (!currentIndex) {
|
|
12655
12847
|
setPaneEmpty(mappingBody, t2.es.selectIndex);
|
|
12656
|
-
} else if (lastMapping && mappingBody.querySelector(".
|
|
12848
|
+
} else if (lastMapping && mappingBody.querySelector(".db-detail-table")) {
|
|
12657
12849
|
renderMapping(lastMapping);
|
|
12658
12850
|
}
|
|
12659
12851
|
if (!activeDocRow) {
|
|
@@ -21430,11 +21622,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21430
21622
|
dbSelect.value = target;
|
|
21431
21623
|
currentDbInfo = files.find((f2) => f2.id === target) || null;
|
|
21432
21624
|
syncConnectionActions();
|
|
21625
|
+
const forceReload = options.forceReload ?? false;
|
|
21433
21626
|
const explorerInitial = {
|
|
21434
|
-
redis: pendingRedisInitial,
|
|
21435
|
-
es: pendingEsInitial,
|
|
21436
|
-
s3: pendingS3Initial,
|
|
21437
|
-
dynamodb: pendingDynamodbInitial
|
|
21627
|
+
redis: pendingRedisInitial ?? (forceReload && currentDbInfo?.kind === "redis" ? redisExplorer.getSelection() : undefined),
|
|
21628
|
+
es: pendingEsInitial ?? (forceReload && currentDbInfo?.kind === "elasticsearch" ? esExplorer.getSelection() : undefined),
|
|
21629
|
+
s3: pendingS3Initial ?? (forceReload && currentDbInfo?.kind === "s3" ? s3Explorer.getSelection() : undefined),
|
|
21630
|
+
dynamodb: pendingDynamodbInitial ?? (forceReload && currentDbInfo?.kind === "dynamodb" ? dynamodbExplorer.getSelection() : undefined)
|
|
21438
21631
|
};
|
|
21439
21632
|
pendingRedisInitial = undefined;
|
|
21440
21633
|
pendingEsInitial = undefined;
|
|
@@ -21567,7 +21760,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21567
21760
|
}
|
|
21568
21761
|
} else if (currentDbInfo?.kind === "dynamodb") {
|
|
21569
21762
|
const sel = dynamodbExplorer.getSelection();
|
|
21570
|
-
if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined) {
|
|
21763
|
+
if (sel.table !== undefined || sel.mode !== "scan" || sel.keyConditionExpression !== undefined || sel.filterExpression !== undefined || sel.expressionAttributeValues !== undefined || sel.itemKey !== undefined || sel.detailTab === "item") {
|
|
21571
21764
|
state.dynamodb = sel;
|
|
21572
21765
|
}
|
|
21573
21766
|
}
|
|
@@ -22632,7 +22825,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22632
22825
|
if (!mounted || activeTabId !== id)
|
|
22633
22826
|
return;
|
|
22634
22827
|
const state = entry.pane.getState();
|
|
22635
|
-
await entry.pane.enter(state.dbId ?? undefined, state.schema ?? undefined, state.table ?? undefined, state.view, { autoSelectFirst: state.dbId !== null });
|
|
22828
|
+
await entry.pane.enter(state.dbId ?? undefined, state.schema ?? undefined, state.table ?? undefined, state.view, { autoSelectFirst: state.dbId !== null, forceReload: true });
|
|
22636
22829
|
if (!mounted || activeTabId !== id)
|
|
22637
22830
|
return;
|
|
22638
22831
|
refreshChipLabel(id);
|
|
@@ -26128,7 +26321,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26128
26321
|
],
|
|
26129
26322
|
[
|
|
26130
26323
|
"DynamoDB / LocalStack",
|
|
26131
|
-
"Detected when DynamoDB is enabled on a LocalStack compose service. List tables,
|
|
26324
|
+
"Detected when DynamoDB is enabled on a LocalStack compose service. List tables, browse a Structure tab (key schema, GSI/LSI, and non-key attribute types inferred from loaded items), scan or query items, follow pagination tokens, and open item details with a copyable key. Browsing is read-only."
|
|
26132
26325
|
],
|
|
26133
26326
|
[
|
|
26134
26327
|
"S3 / MinIO / LocalStack",
|
|
@@ -26795,7 +26988,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
26795
26988
|
],
|
|
26796
26989
|
[
|
|
26797
26990
|
"DynamoDB / LocalStack",
|
|
26798
|
-
"LocalStack の compose サービスで DynamoDB
|
|
26991
|
+
"LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、構造タブ(キースキーマ・GSI/LSI・読み込み済みアイテムから推測した非キー属性の型)、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
|
|
26799
26992
|
],
|
|
26800
26993
|
[
|
|
26801
26994
|
"S3 / MinIO / LocalStack",
|
|
@@ -36172,9 +36365,9 @@ code-viewer query agent-help`
|
|
|
36172
36365
|
displaySource: "Applies to all projects in this browser.",
|
|
36173
36366
|
excludedDirectories: "Excluded directories",
|
|
36174
36367
|
omitDirs: "Skip these directory names while browsing and searching",
|
|
36175
|
-
omitDirsHelp: "Reads no contents inside these directories. Applies to the sidebar (Files), Ctrl+K (file search), Ctrl+G (grep), Datastores, and the file change watcher.",
|
|
36368
|
+
omitDirsHelp: "Reads no contents inside these directories. Applies to the sidebar (Files), Ctrl+K (file search), Ctrl+G (grep), Datastores, and the file change watcher. Supports gitignore-style wildcards (*, ?, [abc], [!abc]).",
|
|
36176
36369
|
excludeNames: "Hide these file or directory names completely",
|
|
36177
|
-
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI.",
|
|
36370
|
+
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI. Supports gitignore-style wildcards (*, ?, [abc], [!abc]).",
|
|
36178
36371
|
reset: "Restore defaults",
|
|
36179
36372
|
autosaveNote: "Changes save automatically.",
|
|
36180
36373
|
scopeSource: (project, source) => `Saved for project "${project}" in this browser. Source: ${source}. Used by the sidebar, Ctrl+K, Ctrl+G, Datastores, and the file change watcher. Restore defaults removes the browser override.`,
|
|
@@ -36462,9 +36655,9 @@ code-viewer query agent-help`
|
|
|
36462
36655
|
displaySource: "このブラウザのすべてのプロジェクトに適用されます。",
|
|
36463
36656
|
excludedDirectories: "除外ディレクトリ",
|
|
36464
36657
|
omitDirs: "閲覧と検索でスキップするディレクトリ名",
|
|
36465
|
-
omitDirsHelp: "これらのディレクトリの中身は読み込みません。サイドバー(Files)・Ctrl+K(ファイル検索)・Ctrl+G(grep)・Datastores・File change watcher の5機能すべてに適用されます。",
|
|
36658
|
+
omitDirsHelp: "これらのディレクトリの中身は読み込みません。サイドバー(Files)・Ctrl+K(ファイル検索)・Ctrl+G(grep)・Datastores・File change watcher の5機能すべてに適用されます。gitignore方式のワイルドカード(*, ?, [abc], [!abc])に対応しています。",
|
|
36466
36659
|
excludeNames: "完全に非表示にするファイル名またはディレクトリ名",
|
|
36467
|
-
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。",
|
|
36660
|
+
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。gitignore方式のワイルドカード(*, ?, [abc], [!abc])に対応しています。",
|
|
36468
36661
|
reset: "デフォルトに戻す",
|
|
36469
36662
|
autosaveNote: "変更は自動で保存されます。",
|
|
36470
36663
|
scopeSource: (project, source) => `このブラウザのプロジェクト "${project}" に保存されます。ソース: ${source}。サイドバー、Ctrl+K、Ctrl+G、Datastores、File change watcher で使われます。「デフォルトに戻す」でブラウザ側の上書きを削除します。`,
|
package/web/style.css
CHANGED
|
@@ -11654,20 +11654,23 @@ body.db-resizing {
|
|
|
11654
11654
|
cursor: default;
|
|
11655
11655
|
color: var(--fg-muted);
|
|
11656
11656
|
}
|
|
11657
|
-
.
|
|
11657
|
+
/* .db-detail-* は es-explorer (mapping/doc) と dynamodb-explorer
|
|
11658
|
+
(structure/item) が共有する詳細ペイン/タブ切替/テーブルの共通スタイル。
|
|
11659
|
+
detail-tabs.ts / detail-table.ts のクラス名と対応する。 */
|
|
11660
|
+
.db-detail-pane {
|
|
11658
11661
|
flex: 1;
|
|
11659
11662
|
display: flex;
|
|
11660
11663
|
flex-direction: column;
|
|
11661
11664
|
overflow: hidden;
|
|
11662
11665
|
background: var(--bg);
|
|
11663
11666
|
}
|
|
11664
|
-
.
|
|
11667
|
+
.db-detail-tabs {
|
|
11665
11668
|
display: flex;
|
|
11666
11669
|
border-bottom: 1px solid var(--border);
|
|
11667
11670
|
background: var(--bg-soft);
|
|
11668
11671
|
flex-shrink: 0;
|
|
11669
11672
|
}
|
|
11670
|
-
.
|
|
11673
|
+
.db-detail-tab {
|
|
11671
11674
|
padding: 6px 14px;
|
|
11672
11675
|
border: none;
|
|
11673
11676
|
background: transparent;
|
|
@@ -11676,12 +11679,11 @@ body.db-resizing {
|
|
|
11676
11679
|
color: var(--fg-muted);
|
|
11677
11680
|
border-bottom: 2px solid transparent;
|
|
11678
11681
|
}
|
|
11679
|
-
.
|
|
11682
|
+
.db-detail-tab.active {
|
|
11680
11683
|
color: var(--fg);
|
|
11681
11684
|
border-bottom-color: var(--accent);
|
|
11682
11685
|
}
|
|
11683
|
-
.
|
|
11684
|
-
.es-doc-body {
|
|
11686
|
+
.db-detail-tab-body {
|
|
11685
11687
|
flex: 1;
|
|
11686
11688
|
overflow: auto;
|
|
11687
11689
|
padding: 12px;
|
|
@@ -11753,26 +11755,27 @@ body.db-resizing {
|
|
|
11753
11755
|
color: var(--fg-muted);
|
|
11754
11756
|
margin-bottom: 8px;
|
|
11755
11757
|
}
|
|
11756
|
-
.
|
|
11758
|
+
.db-detail-table {
|
|
11757
11759
|
width: 100%;
|
|
11758
11760
|
border-collapse: collapse;
|
|
11759
11761
|
font-family: var(--mono, monospace);
|
|
11760
11762
|
font-size: var(--db-font-mono);
|
|
11763
|
+
margin-bottom: 16px;
|
|
11761
11764
|
}
|
|
11762
|
-
.
|
|
11763
|
-
.
|
|
11765
|
+
.db-detail-table th,
|
|
11766
|
+
.db-detail-table td {
|
|
11764
11767
|
text-align: left;
|
|
11765
11768
|
padding: 4px 8px;
|
|
11766
11769
|
border-bottom: 1px solid var(--border);
|
|
11767
11770
|
}
|
|
11768
|
-
.
|
|
11771
|
+
.db-detail-table th {
|
|
11769
11772
|
font-weight: 600;
|
|
11770
11773
|
background: var(--bg-soft);
|
|
11771
11774
|
}
|
|
11772
|
-
.
|
|
11775
|
+
.db-detail-table-primary {
|
|
11773
11776
|
font-weight: 500;
|
|
11774
11777
|
}
|
|
11775
|
-
.
|
|
11778
|
+
.db-detail-table-muted {
|
|
11776
11779
|
color: var(--fg-muted);
|
|
11777
11780
|
}
|
|
11778
11781
|
.es-doc-source {
|
|
@@ -11782,7 +11785,7 @@ body.db-resizing {
|
|
|
11782
11785
|
word-break: break-all;
|
|
11783
11786
|
margin: 0;
|
|
11784
11787
|
}
|
|
11785
|
-
.
|
|
11788
|
+
.db-value-empty {
|
|
11786
11789
|
color: var(--fg-muted);
|
|
11787
11790
|
font-size: var(--db-font-base);
|
|
11788
11791
|
font-style: italic;
|
|
@@ -12472,13 +12475,6 @@ body.db-resizing {
|
|
|
12472
12475
|
cursor: default;
|
|
12473
12476
|
color: var(--fg-muted);
|
|
12474
12477
|
}
|
|
12475
|
-
.dynamodb-detail-pane {
|
|
12476
|
-
flex: 1;
|
|
12477
|
-
overflow: auto;
|
|
12478
|
-
padding: 12px;
|
|
12479
|
-
color: var(--fg);
|
|
12480
|
-
background: var(--bg);
|
|
12481
|
-
}
|
|
12482
12478
|
.dynamodb-table-info-header,
|
|
12483
12479
|
.dynamodb-item-detail-header {
|
|
12484
12480
|
display: flex;
|
|
@@ -12491,8 +12487,7 @@ body.db-resizing {
|
|
|
12491
12487
|
border-bottom: 1px solid var(--border);
|
|
12492
12488
|
margin-bottom: 8px;
|
|
12493
12489
|
}
|
|
12494
|
-
.dynamodb-table-info-meta
|
|
12495
|
-
.dynamodb-table-info-keys {
|
|
12490
|
+
.dynamodb-table-info-meta {
|
|
12496
12491
|
font-family: var(--mono, monospace);
|
|
12497
12492
|
font-size: var(--db-font-sm);
|
|
12498
12493
|
color: var(--fg-muted);
|
|
@@ -12504,8 +12499,35 @@ body.db-resizing {
|
|
|
12504
12499
|
font-size: var(--db-font-sm);
|
|
12505
12500
|
font-weight: 400;
|
|
12506
12501
|
}
|
|
12507
|
-
.dynamodb-
|
|
12508
|
-
margin-
|
|
12502
|
+
.dynamodb-index-section {
|
|
12503
|
+
margin-bottom: 16px;
|
|
12504
|
+
}
|
|
12505
|
+
.dynamodb-index-heading {
|
|
12506
|
+
font-weight: 600;
|
|
12507
|
+
font-size: var(--db-font-sm);
|
|
12508
|
+
color: var(--fg-muted);
|
|
12509
|
+
margin-bottom: 4px;
|
|
12510
|
+
}
|
|
12511
|
+
.dynamodb-index-row {
|
|
12512
|
+
padding: 4px 0;
|
|
12513
|
+
border-bottom: 1px solid var(--border);
|
|
12514
|
+
}
|
|
12515
|
+
.dynamodb-index-name {
|
|
12516
|
+
font-family: var(--mono, monospace);
|
|
12517
|
+
font-size: var(--db-font-mono);
|
|
12518
|
+
font-weight: 500;
|
|
12519
|
+
}
|
|
12520
|
+
.dynamodb-index-detail {
|
|
12521
|
+
font-family: var(--mono, monospace);
|
|
12522
|
+
font-size: var(--db-font-sm);
|
|
12523
|
+
color: var(--fg-muted);
|
|
12524
|
+
}
|
|
12525
|
+
.dynamodb-attr-note {
|
|
12526
|
+
margin-top: -4px;
|
|
12527
|
+
margin-bottom: 16px;
|
|
12528
|
+
font-size: var(--db-font-sm);
|
|
12529
|
+
color: var(--fg-muted);
|
|
12530
|
+
font-style: italic;
|
|
12509
12531
|
}
|
|
12510
12532
|
.dynamodb-item-source {
|
|
12511
12533
|
font-family: var(--mono, monospace);
|