dsh-db-tool 1.5.6 → 1.5.7
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/client/client.js +74 -74
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -93,8 +93,7 @@ window.__ModuleLoader__.load({
|
|
|
93
93
|
emptyData: "暂无数据",
|
|
94
94
|
retry: "重试",
|
|
95
95
|
selectTableHint: "在左侧选择表后查看数据",
|
|
96
|
-
//
|
|
97
|
-
editCell: "编辑",
|
|
96
|
+
// 单元格底部详情栏(Navicat 式就地写回)
|
|
98
97
|
cellValue: "完整值",
|
|
99
98
|
copyBtn: "复制",
|
|
100
99
|
copied: "已复制",
|
|
@@ -200,8 +199,7 @@ window.__ModuleLoader__.load({
|
|
|
200
199
|
emptyData: "No rows",
|
|
201
200
|
retry: "Retry",
|
|
202
201
|
selectTableHint: "Select a table on the left to view data",
|
|
203
|
-
// Cell
|
|
204
|
-
editCell: "Edit",
|
|
202
|
+
// Cell bottom detail bar (Navicat-style in-place write-back)
|
|
205
203
|
cellValue: "Full value",
|
|
206
204
|
copyBtn: "Copy",
|
|
207
205
|
copied: "Copied",
|
|
@@ -349,7 +347,7 @@ window.__ModuleLoader__.load({
|
|
|
349
347
|
".dbt-btn.danger:hover{background:rgba(255,69,58,.12);}",
|
|
350
348
|
".dbt-btn:disabled{opacity:.4;cursor:default;transform:none;}",
|
|
351
349
|
"/* ===== 文本反馈 ===== */",
|
|
352
|
-
".dbt-panel strong{font-size:13px;font-weight:600;}",
|
|
350
|
+
".dbt-panel strong,.dbt-browse-root strong{font-size:13px;font-weight:600;}",
|
|
353
351
|
".dbt-muted,.dbt-readhint{color:var(--dbt-text-secondary);font-size:11px;}",
|
|
354
352
|
".dbt-err{color:var(--dbt-danger);font-size:12px;white-space:pre-wrap;}",
|
|
355
353
|
".dbt-msg{color:var(--dbt-success);font-size:12px;white-space:pre-wrap;}",
|
|
@@ -387,23 +385,23 @@ window.__ModuleLoader__.load({
|
|
|
387
385
|
".dbt-treename{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
388
386
|
/* ===== Navicat 式浏览(内嵌面板,纵向流式;浮窗壳类已随 DOM 回收) ===== */
|
|
389
387
|
".dbt-browse-body{display:flex;flex-direction:column;gap:10px;min-height:0;}",
|
|
390
|
-
".dbt-browse-tree{flex:none;max-height:280px;overflow-y:auto;border-
|
|
391
|
-
".dbt-browse-main{flex:1;min-height:0;display:flex;flex-direction:column;gap:
|
|
388
|
+
".dbt-browse-tree{flex:none;max-height:280px;overflow-y:auto;background:var(--dbt-surface);border-radius:var(--dbt-radius-card);}",
|
|
389
|
+
".dbt-browse-main{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;}",
|
|
392
390
|
".dbt-browse-tabs{flex:none;}",
|
|
393
|
-
".dbt-browse-content{flex:1;min-height:0;position:relative;display:flex;flex-direction:column;
|
|
391
|
+
".dbt-browse-content{flex:1;min-height:0;position:relative;display:flex;flex-direction:column;}",
|
|
394
392
|
".dbt-browse-tablewrap{flex:none;max-height:320px;overflow:auto;border:none;border-radius:var(--dbt-radius-card);background:var(--dbt-surface);}",
|
|
395
|
-
".dbt-browse-footer{flex:none;display:flex;align-items:center;gap:8px;padding:
|
|
393
|
+
".dbt-browse-footer{flex:none;display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--dbt-surface);border-radius:var(--dbt-radius-ctrl);}",
|
|
396
394
|
".dbt-browse-empty{display:flex;align-items:center;justify-content:center;color:var(--dbt-text-secondary);font-size:13px;padding:24px 12px;}",
|
|
397
395
|
".dbt-browse-errorbox{display:flex;flex-direction:column;gap:8px;align-items:flex-start;}",
|
|
398
396
|
".dbt-browse-skel{display:flex;flex-direction:column;gap:8px;}",
|
|
399
397
|
".dbt-browse-skelrow{display:flex;gap:8px;}",
|
|
400
398
|
".dbt-browse-skelcell{flex:1;height:24px;background:var(--dbt-surface-strong);border-radius:var(--dbt-radius-pill);}",
|
|
401
|
-
"/* =====
|
|
399
|
+
"/* ===== 浏览面板:单元格按钮与详情底栏(Apple 分区卡) ===== */",
|
|
402
400
|
".dbt-cellbtn{border:none;background:transparent;color:inherit;font:inherit;padding:0;margin:0;cursor:pointer;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;text-align:left;}",
|
|
403
401
|
".dbt-cellbtn:focus-visible{outline:2px solid var(--dbt-accent);outline-offset:-1px;border-radius:2px;}",
|
|
404
|
-
".dbt-
|
|
405
|
-
".dbt-
|
|
406
|
-
".dbt-
|
|
402
|
+
".dbt-celldetail{margin-top:10px;padding:12px;background:var(--dbt-surface);border-radius:var(--dbt-radius-card);display:flex;flex-direction:column;gap:8px;}",
|
|
403
|
+
".dbt-celldetail-input{font-family:var(--dbt-mono);font-size:12px;min-height:72px;resize:vertical;background:var(--dbt-surface-strong);border:none;color:inherit;border-radius:var(--dbt-radius-ctrl);padding:8px;width:100%;box-sizing:border-box;}",
|
|
404
|
+
".dbt-celldetail-input:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
407
405
|
".dbt-celledit{display:flex;flex-direction:column;gap:8px;}",
|
|
408
406
|
".dbt-celledit textarea{font-family:var(--dbt-mono);font-size:13px;min-height:96px;resize:vertical;background:var(--dbt-surface-strong);border:none;color:inherit;border-radius:var(--dbt-radius-ctrl);padding:5px 12px;}",
|
|
409
407
|
".dbt-celledit textarea:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
@@ -416,8 +414,8 @@ window.__ModuleLoader__.load({
|
|
|
416
414
|
".dbt-seg button{flex:none;border:none;background:transparent;color:inherit;font-size:12px;padding:5px 12px;border-radius:var(--dbt-radius-pill);cursor:pointer;transition:all .18s var(--dbt-ease);}",
|
|
417
415
|
".dbt-seg button.active{background:var(--dbt-seg-active);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.12);}",
|
|
418
416
|
"/* ===== 降级:透明度减弱 / 动效减弱 ===== */",
|
|
419
|
-
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog
|
|
420
|
-
"@media (prefers-color-scheme: light) and (prefers-reduced-transparency: reduce){.dbt-dialog
|
|
417
|
+
"@media (prefers-reduced-transparency: reduce){.dbt-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(0,0,0,.55);}.dbt-dialog{backdrop-filter:none;-webkit-backdrop-filter:none;background:#2c2c2e;}.dbt-celldetail{background:#2c2c2e;}.dbt-table th{backdrop-filter:none;-webkit-backdrop-filter:none;background:#1e1e20;}.dbt-browse-root .dbt-table th{background:#1e1e20;}}",
|
|
418
|
+
"@media (prefers-color-scheme: light) and (prefers-reduced-transparency: reduce){.dbt-dialog{background:#f5f5f7;}.dbt-celldetail{background:#f2f2f7;}.dbt-table th,.dbt-browse-root .dbt-table th{background:#f2f2f7;}}",
|
|
421
419
|
"@media (prefers-reduced-motion: reduce){.dbt-panel *,.dbt-browse-root *{transition:none!important;animation:none!important;}.dbt-btn:active{transform:none;}}",
|
|
422
420
|
].join("\n");
|
|
423
421
|
document.head.appendChild(styleEl);
|
|
@@ -1127,9 +1125,8 @@ window.__ModuleLoader__.load({
|
|
|
1127
1125
|
};
|
|
1128
1126
|
function PreviewGrid(props) {
|
|
1129
1127
|
const { preview, schema, kind, editable, tableType, tableName, dbRef, schemaName, connId, projectPath, onSaved, askConfirm } = props;
|
|
1130
|
-
const [
|
|
1131
|
-
const [
|
|
1132
|
-
const [text, setText] = React.useState(""); // 编辑态 textarea 值(进入浮层时就位)
|
|
1128
|
+
const [sel, setSel] = React.useState(null); // {rowIdx, colName, cell} 底部详情栏当前单元格(null=收起底栏)
|
|
1129
|
+
const [text, setText] = React.useState(""); // 详情栏 textarea 值(选中即就位)
|
|
1133
1130
|
const [editNull, setEditNull] = React.useState(false); // 「设为 NULL」勾选(null 单元格默认勾选)
|
|
1134
1131
|
const [cellErr, setCellErr] = React.useState("");
|
|
1135
1132
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -1140,23 +1137,23 @@ window.__ModuleLoader__.load({
|
|
|
1140
1137
|
? schema.filter((c) => c && c.key === "PRI").map((c) => c.name)
|
|
1141
1138
|
: [];
|
|
1142
1139
|
|
|
1143
|
-
//
|
|
1144
|
-
function
|
|
1145
|
-
setPop({ rowIdx, colName, cell });
|
|
1146
|
-
setEditing(false);
|
|
1140
|
+
// 点单元格开合底栏:同格再点收起;新格选中即把 textarea 初值就位——textarea 空 ↔ 勾选设为 NULL
|
|
1141
|
+
function toggleCell(rowIdx, colName, cell) {
|
|
1147
1142
|
setCellErr("");
|
|
1143
|
+
if (sel && sel.rowIdx === rowIdx && sel.colName === colName) { setSel(null); return; }
|
|
1144
|
+
setSel({ rowIdx, colName, cell });
|
|
1148
1145
|
setEditNull(cell === null);
|
|
1149
1146
|
setText(cell === null ? "" : String(cell));
|
|
1150
1147
|
}
|
|
1151
1148
|
function copyCell() {
|
|
1152
|
-
if (!
|
|
1153
|
-
navigator.clipboard.writeText(String(
|
|
1149
|
+
if (!sel) return;
|
|
1150
|
+
navigator.clipboard.writeText(String(sel.cell)).then(() => {
|
|
1154
1151
|
setCopied(true);
|
|
1155
1152
|
window.setTimeout(() => setCopied(false), 1500);
|
|
1156
1153
|
}, () => { /* 剪贴板不可用(无权限等):静默,按钮文案不变 */ });
|
|
1157
1154
|
}
|
|
1158
1155
|
|
|
1159
|
-
// 单元格编辑资格:返回 null=可编辑,否则为原因 i18n key
|
|
1156
|
+
// 单元格编辑资格:返回 null=可编辑,否则为原因 i18n key(底栏 .dbt-readhint 展示)
|
|
1160
1157
|
function cellReason(colName, shown) {
|
|
1161
1158
|
if (!editable || !dialectOf(kind)) return "readOnlyRo";
|
|
1162
1159
|
if (isMongo) return colName === "_id" ? "readOnlyRo" : null;
|
|
@@ -1171,11 +1168,11 @@ window.__ModuleLoader__.load({
|
|
|
1171
1168
|
return null;
|
|
1172
1169
|
}
|
|
1173
1170
|
|
|
1174
|
-
// 保存:构造写回命令(按 kind 分派)→ 顺序执行(redis 两步)→
|
|
1171
|
+
// 保存:构造写回命令(按 kind 分派)→ 顺序执行(redis 两步)→ 全部成功收起底栏并回调 onSaved
|
|
1175
1172
|
async function saveCell() {
|
|
1176
|
-
if (!
|
|
1173
|
+
if (!sel) return;
|
|
1177
1174
|
setCellErr("");
|
|
1178
|
-
const { rowIdx, colName } =
|
|
1175
|
+
const { rowIdx, colName } = sel;
|
|
1179
1176
|
const row = (preview.rows || [])[rowIdx] || [];
|
|
1180
1177
|
let r;
|
|
1181
1178
|
if (kind === "redis") {
|
|
@@ -1200,26 +1197,32 @@ window.__ModuleLoader__.load({
|
|
|
1200
1197
|
askConfirm,
|
|
1201
1198
|
);
|
|
1202
1199
|
}
|
|
1203
|
-
|
|
1200
|
+
setSel(null);
|
|
1204
1201
|
if (onSaved) onSaved();
|
|
1205
1202
|
} catch (e) {
|
|
1206
|
-
// 取消确认(e.cancelled
|
|
1203
|
+
// 取消确认(e.cancelled)不算失败:底栏保持打开供重试
|
|
1207
1204
|
if (!e.cancelled) setCellErr(t("error") + ": " + String(e && e.message ? e.message : e));
|
|
1208
1205
|
} finally {
|
|
1209
1206
|
setBusy(false);
|
|
1210
1207
|
}
|
|
1211
1208
|
}
|
|
1212
1209
|
|
|
1213
|
-
|
|
1210
|
+
// 切表/翻页联动:预览数据引用变化即收起底栏,避免行号错位指向旧行
|
|
1211
|
+
React.useEffect(() => { setSel(null); }, [preview]);
|
|
1212
|
+
|
|
1213
|
+
const shown = sel ? (sel.cell === null ? "NULL" : String(sel.cell)) : null;
|
|
1214
|
+
const selReason = sel ? cellReason(sel.colName, shown) : null;
|
|
1215
|
+
// 截断/BLOB 独立提示:命中即补显,若已作为只读原因展示过则不再重复
|
|
1216
|
+
const truncHint = !sel ? null : isTruncatedCell(shown) ? "truncatedNoEdit" : isBlobCell(shown) ? "blobNoEdit" : null;
|
|
1214
1217
|
return React.createElement(
|
|
1215
1218
|
"div",
|
|
1216
1219
|
{ className: "dbt-browse-root", style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 } },
|
|
1217
|
-
// 顶层只读提示:授权为 ro
|
|
1220
|
+
// 顶层只读提示:授权为 ro 时整格只读,单元格仍可点开底栏查看完整值
|
|
1218
1221
|
!editable ? React.createElement("div", { className: "dbt-readhint" }, t("readOnlyRo")) : null,
|
|
1219
|
-
//
|
|
1222
|
+
// 预览表格区(flex:1 撑满剩余空间;底部详情栏在表格之后、面板流内)
|
|
1220
1223
|
React.createElement(
|
|
1221
1224
|
"div",
|
|
1222
|
-
{ style: {
|
|
1225
|
+
{ style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" } },
|
|
1223
1226
|
React.createElement(
|
|
1224
1227
|
"div",
|
|
1225
1228
|
{ className: "dbt-browse-tablewrap" },
|
|
@@ -1240,51 +1243,48 @@ window.__ModuleLoader__.load({
|
|
|
1240
1243
|
React.createElement("td", { key: j, title: row[j] === null ? "NULL" : String(row[j]) },
|
|
1241
1244
|
React.createElement("button", {
|
|
1242
1245
|
className: "dbt-cellbtn", type: "button",
|
|
1243
|
-
onClick: () =>
|
|
1244
|
-
}, row[j] === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : String(row[j]))))),
|
|
1246
|
+
onClick: () => toggleCell(i, colName, row[j]),
|
|
1247
|
+
}, row[j] === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : String(row[j])))))),
|
|
1245
1248
|
),
|
|
1246
1249
|
),
|
|
1247
1250
|
),
|
|
1248
|
-
|
|
1251
|
+
),
|
|
1252
|
+
// 底部详情栏(Navicat/检查器式):预览表格下方一栏,textarea 直接可编辑,点保存写回
|
|
1253
|
+
sel ? React.createElement(
|
|
1254
|
+
"div",
|
|
1255
|
+
{ className: "dbt-celldetail" },
|
|
1256
|
+
// 标题行:完整值 · 列名;null 单元格带 NULL 芯片;只读原因随之展示
|
|
1257
|
+
React.createElement(
|
|
1249
1258
|
"div",
|
|
1250
|
-
|
|
1251
|
-
React.createElement("strong", null, t("cellValue") + " · " +
|
|
1252
|
-
React.createElement(
|
|
1253
|
-
|
|
1254
|
-
{ className: "dbt-cellpop-value" },
|
|
1255
|
-
pop.cell === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : String(pop.cell),
|
|
1256
|
-
),
|
|
1257
|
-
popReason ? React.createElement("div", { className: "dbt-readhint" }, t(popReason)) : null,
|
|
1258
|
-
cellErr ? React.createElement("div", { className: "dbt-err" }, cellErr) : null,
|
|
1259
|
-
editing
|
|
1260
|
-
? React.createElement(
|
|
1261
|
-
"div",
|
|
1262
|
-
{ className: "dbt-celledit" },
|
|
1263
|
-
React.createElement("textarea", { value: text, onChange: (e) => setText(e.target.value) }),
|
|
1264
|
-
React.createElement(
|
|
1265
|
-
"label",
|
|
1266
|
-
// 不用 .dbt-row:全局 input flex:1 会把 checkbox 拉伸占满、标签被推到远端(视觉审查命中)
|
|
1267
|
-
{ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "nowrap", width: "fit-content" } },
|
|
1268
|
-
React.createElement("input", { type: "checkbox", style: { flex: "none", minWidth: 0, width: "auto" }, checked: editNull, onChange: (e) => setEditNull(e.target.checked) }),
|
|
1269
|
-
t("setNull"),
|
|
1270
|
-
),
|
|
1271
|
-
React.createElement(
|
|
1272
|
-
"div",
|
|
1273
|
-
{ className: "dbt-row" },
|
|
1274
|
-
React.createElement("button", { className: "dbt-btn primary", disabled: busy, onClick: saveCell }, busy ? t("cellSaving") : t("save")),
|
|
1275
|
-
React.createElement("button", { className: "dbt-btn", disabled: busy, onClick: () => setEditing(false) }, t("cancel")),
|
|
1276
|
-
),
|
|
1277
|
-
)
|
|
1278
|
-
: React.createElement(
|
|
1279
|
-
"div",
|
|
1280
|
-
{ className: "dbt-cellpop-actions" },
|
|
1281
|
-
React.createElement("button", { className: "dbt-btn", onClick: copyCell }, copied ? t("copied") : t("copyBtn")),
|
|
1282
|
-
popReason === null ? React.createElement("button", { className: "dbt-btn primary", onClick: () => setEditing(true) }, t("editCell")) : null,
|
|
1283
|
-
React.createElement("button", { className: "dbt-btn", onClick: () => setPop(null) }, t("close")),
|
|
1284
|
-
),
|
|
1285
|
-
) : null,
|
|
1259
|
+
null,
|
|
1260
|
+
React.createElement("strong", null, t("cellValue") + " · " + sel.colName),
|
|
1261
|
+
sel.cell === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : null,
|
|
1262
|
+
selReason ? React.createElement("span", { className: "dbt-readhint" }, t(selReason)) : null,
|
|
1286
1263
|
),
|
|
1287
|
-
|
|
1264
|
+
// 完整值编辑区:textarea 恒为编辑形态,不可编辑时只读
|
|
1265
|
+
React.createElement("textarea", {
|
|
1266
|
+
className: "dbt-celldetail-input",
|
|
1267
|
+
value: text,
|
|
1268
|
+
readOnly: selReason !== null,
|
|
1269
|
+
onChange: (e) => setText(e.target.value),
|
|
1270
|
+
}),
|
|
1271
|
+
sel.cell === null ? React.createElement(
|
|
1272
|
+
"label",
|
|
1273
|
+
// 不用 .dbt-row:全局 input flex:1 会把 checkbox 拉伸占满、标签被推到远端(视觉审查命中)
|
|
1274
|
+
{ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "nowrap", width: "fit-content" } },
|
|
1275
|
+
React.createElement("input", { type: "checkbox", style: { flex: "none", minWidth: 0, width: "auto" }, checked: editNull, onChange: (e) => setEditNull(e.target.checked) }),
|
|
1276
|
+
t("setNull"),
|
|
1277
|
+
) : null,
|
|
1278
|
+
truncHint && truncHint !== selReason ? React.createElement("span", { className: "dbt-readhint" }, t(truncHint)) : null,
|
|
1279
|
+
cellErr ? React.createElement("span", { className: "dbt-err" }, cellErr) : null,
|
|
1280
|
+
React.createElement(
|
|
1281
|
+
"div",
|
|
1282
|
+
{ className: "dbt-row" },
|
|
1283
|
+
React.createElement("button", { className: "dbt-btn", onClick: copyCell }, copied ? t("copied") : t("copyBtn")),
|
|
1284
|
+
selReason === null ? React.createElement("button", { className: "dbt-btn primary", disabled: busy, onClick: saveCell }, busy ? t("cellSaving") : t("save")) : null,
|
|
1285
|
+
React.createElement("button", { className: "dbt-btn", onClick: () => setSel(null) }, t("close")),
|
|
1286
|
+
),
|
|
1287
|
+
) : null,
|
|
1288
1288
|
);
|
|
1289
1289
|
}
|
|
1290
1290
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.7",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|