dsh-db-tool 1.5.6 → 1.5.8
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 +76 -75
- 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,24 @@ 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;
|
|
394
|
-
".dbt-browse-tablewrap{flex:
|
|
395
|
-
".dbt-browse-footer{flex:none;display:flex;align-items:center;gap:8px;padding:
|
|
391
|
+
".dbt-browse-content{flex:1;min-height:0;position:relative;display:flex;flex-direction:column;}",
|
|
392
|
+
".dbt-browse-tablewrap{flex:1 1 auto;min-height:120px;overflow:auto;border:none;border-radius:var(--dbt-radius-card);background:var(--dbt-surface);}",
|
|
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
|
-
"
|
|
406
|
-
".dbt-
|
|
402
|
+
".dbt-celldetail{flex:none;margin-top:10px;padding:12px;background:#2c2c2e;border-radius:var(--dbt-radius-card);display:flex;flex-direction:column;gap:8px;}",
|
|
403
|
+
"@media (prefers-color-scheme: light){.dbt-celldetail{background:#f5f5f7;}}",
|
|
404
|
+
".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;}",
|
|
405
|
+
".dbt-celldetail-input:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
407
406
|
".dbt-celledit{display:flex;flex-direction:column;gap:8px;}",
|
|
408
407
|
".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
408
|
".dbt-celledit textarea:focus{outline:2px solid var(--dbt-accent);outline-offset:-1px;}",
|
|
@@ -416,8 +415,8 @@ window.__ModuleLoader__.load({
|
|
|
416
415
|
".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
416
|
".dbt-seg button.active{background:var(--dbt-seg-active);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,.12);}",
|
|
418
417
|
"/* ===== 降级:透明度减弱 / 动效减弱 ===== */",
|
|
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
|
|
418
|
+
"@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;}}",
|
|
419
|
+
"@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
420
|
"@media (prefers-reduced-motion: reduce){.dbt-panel *,.dbt-browse-root *{transition:none!important;animation:none!important;}.dbt-btn:active{transform:none;}}",
|
|
422
421
|
].join("\n");
|
|
423
422
|
document.head.appendChild(styleEl);
|
|
@@ -1127,9 +1126,8 @@ window.__ModuleLoader__.load({
|
|
|
1127
1126
|
};
|
|
1128
1127
|
function PreviewGrid(props) {
|
|
1129
1128
|
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 值(进入浮层时就位)
|
|
1129
|
+
const [sel, setSel] = React.useState(null); // {rowIdx, colName, cell} 底部详情栏当前单元格(null=收起底栏)
|
|
1130
|
+
const [text, setText] = React.useState(""); // 详情栏 textarea 值(选中即就位)
|
|
1133
1131
|
const [editNull, setEditNull] = React.useState(false); // 「设为 NULL」勾选(null 单元格默认勾选)
|
|
1134
1132
|
const [cellErr, setCellErr] = React.useState("");
|
|
1135
1133
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -1140,23 +1138,23 @@ window.__ModuleLoader__.load({
|
|
|
1140
1138
|
? schema.filter((c) => c && c.key === "PRI").map((c) => c.name)
|
|
1141
1139
|
: [];
|
|
1142
1140
|
|
|
1143
|
-
//
|
|
1144
|
-
function
|
|
1145
|
-
setPop({ rowIdx, colName, cell });
|
|
1146
|
-
setEditing(false);
|
|
1141
|
+
// 点单元格开合底栏:同格再点收起;新格选中即把 textarea 初值就位——textarea 空 ↔ 勾选设为 NULL
|
|
1142
|
+
function toggleCell(rowIdx, colName, cell) {
|
|
1147
1143
|
setCellErr("");
|
|
1144
|
+
if (sel && sel.rowIdx === rowIdx && sel.colName === colName) { setSel(null); return; }
|
|
1145
|
+
setSel({ rowIdx, colName, cell });
|
|
1148
1146
|
setEditNull(cell === null);
|
|
1149
1147
|
setText(cell === null ? "" : String(cell));
|
|
1150
1148
|
}
|
|
1151
1149
|
function copyCell() {
|
|
1152
|
-
if (!
|
|
1153
|
-
navigator.clipboard.writeText(String(
|
|
1150
|
+
if (!sel) return;
|
|
1151
|
+
navigator.clipboard.writeText(String(sel.cell)).then(() => {
|
|
1154
1152
|
setCopied(true);
|
|
1155
1153
|
window.setTimeout(() => setCopied(false), 1500);
|
|
1156
1154
|
}, () => { /* 剪贴板不可用(无权限等):静默,按钮文案不变 */ });
|
|
1157
1155
|
}
|
|
1158
1156
|
|
|
1159
|
-
// 单元格编辑资格:返回 null=可编辑,否则为原因 i18n key
|
|
1157
|
+
// 单元格编辑资格:返回 null=可编辑,否则为原因 i18n key(底栏 .dbt-readhint 展示)
|
|
1160
1158
|
function cellReason(colName, shown) {
|
|
1161
1159
|
if (!editable || !dialectOf(kind)) return "readOnlyRo";
|
|
1162
1160
|
if (isMongo) return colName === "_id" ? "readOnlyRo" : null;
|
|
@@ -1171,11 +1169,11 @@ window.__ModuleLoader__.load({
|
|
|
1171
1169
|
return null;
|
|
1172
1170
|
}
|
|
1173
1171
|
|
|
1174
|
-
// 保存:构造写回命令(按 kind 分派)→ 顺序执行(redis 两步)→
|
|
1172
|
+
// 保存:构造写回命令(按 kind 分派)→ 顺序执行(redis 两步)→ 全部成功收起底栏并回调 onSaved
|
|
1175
1173
|
async function saveCell() {
|
|
1176
|
-
if (!
|
|
1174
|
+
if (!sel) return;
|
|
1177
1175
|
setCellErr("");
|
|
1178
|
-
const { rowIdx, colName } =
|
|
1176
|
+
const { rowIdx, colName } = sel;
|
|
1179
1177
|
const row = (preview.rows || [])[rowIdx] || [];
|
|
1180
1178
|
let r;
|
|
1181
1179
|
if (kind === "redis") {
|
|
@@ -1200,26 +1198,32 @@ window.__ModuleLoader__.load({
|
|
|
1200
1198
|
askConfirm,
|
|
1201
1199
|
);
|
|
1202
1200
|
}
|
|
1203
|
-
|
|
1201
|
+
setSel(null);
|
|
1204
1202
|
if (onSaved) onSaved();
|
|
1205
1203
|
} catch (e) {
|
|
1206
|
-
// 取消确认(e.cancelled
|
|
1204
|
+
// 取消确认(e.cancelled)不算失败:底栏保持打开供重试
|
|
1207
1205
|
if (!e.cancelled) setCellErr(t("error") + ": " + String(e && e.message ? e.message : e));
|
|
1208
1206
|
} finally {
|
|
1209
1207
|
setBusy(false);
|
|
1210
1208
|
}
|
|
1211
1209
|
}
|
|
1212
1210
|
|
|
1213
|
-
|
|
1211
|
+
// 切表/翻页联动:预览数据引用变化即收起底栏,避免行号错位指向旧行
|
|
1212
|
+
React.useEffect(() => { setSel(null); }, [preview]);
|
|
1213
|
+
|
|
1214
|
+
const shown = sel ? (sel.cell === null ? "NULL" : String(sel.cell)) : null;
|
|
1215
|
+
const selReason = sel ? cellReason(sel.colName, shown) : null;
|
|
1216
|
+
// 截断/BLOB 独立提示:命中即补显,若已作为只读原因展示过则不再重复
|
|
1217
|
+
const truncHint = !sel ? null : isTruncatedCell(shown) ? "truncatedNoEdit" : isBlobCell(shown) ? "blobNoEdit" : null;
|
|
1214
1218
|
return React.createElement(
|
|
1215
1219
|
"div",
|
|
1216
1220
|
{ className: "dbt-browse-root", style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 } },
|
|
1217
|
-
// 顶层只读提示:授权为 ro
|
|
1221
|
+
// 顶层只读提示:授权为 ro 时整格只读,单元格仍可点开底栏查看完整值
|
|
1218
1222
|
!editable ? React.createElement("div", { className: "dbt-readhint" }, t("readOnlyRo")) : null,
|
|
1219
|
-
//
|
|
1223
|
+
// 预览表格区(flex:1 撑满剩余空间;底部详情栏在表格之后、面板流内)
|
|
1220
1224
|
React.createElement(
|
|
1221
1225
|
"div",
|
|
1222
|
-
{ style: {
|
|
1226
|
+
{ style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" } },
|
|
1223
1227
|
React.createElement(
|
|
1224
1228
|
"div",
|
|
1225
1229
|
{ className: "dbt-browse-tablewrap" },
|
|
@@ -1240,51 +1244,48 @@ window.__ModuleLoader__.load({
|
|
|
1240
1244
|
React.createElement("td", { key: j, title: row[j] === null ? "NULL" : String(row[j]) },
|
|
1241
1245
|
React.createElement("button", {
|
|
1242
1246
|
className: "dbt-cellbtn", type: "button",
|
|
1243
|
-
onClick: () =>
|
|
1244
|
-
}, row[j] === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : String(row[j]))))),
|
|
1247
|
+
onClick: () => toggleCell(i, colName, row[j]),
|
|
1248
|
+
}, row[j] === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : String(row[j])))))),
|
|
1245
1249
|
),
|
|
1246
1250
|
),
|
|
1247
1251
|
),
|
|
1248
|
-
|
|
1252
|
+
),
|
|
1253
|
+
// 底部详情栏(Navicat/检查器式):预览表格下方一栏,textarea 直接可编辑,点保存写回
|
|
1254
|
+
sel ? React.createElement(
|
|
1255
|
+
"div",
|
|
1256
|
+
{ className: "dbt-celldetail" },
|
|
1257
|
+
// 标题行:完整值 · 列名;null 单元格带 NULL 芯片;只读原因随之展示
|
|
1258
|
+
React.createElement(
|
|
1249
1259
|
"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,
|
|
1260
|
+
null,
|
|
1261
|
+
React.createElement("strong", null, t("cellValue") + " · " + sel.colName),
|
|
1262
|
+
sel.cell === null ? React.createElement("span", { className: "dbt-nullchip" }, "NULL") : null,
|
|
1263
|
+
selReason ? React.createElement("span", { className: "dbt-readhint" }, t(selReason)) : null,
|
|
1286
1264
|
),
|
|
1287
|
-
|
|
1265
|
+
// 完整值编辑区:textarea 恒为编辑形态,不可编辑时只读
|
|
1266
|
+
React.createElement("textarea", {
|
|
1267
|
+
className: "dbt-celldetail-input",
|
|
1268
|
+
value: text,
|
|
1269
|
+
readOnly: selReason !== null,
|
|
1270
|
+
onChange: (e) => setText(e.target.value),
|
|
1271
|
+
}),
|
|
1272
|
+
sel.cell === null ? React.createElement(
|
|
1273
|
+
"label",
|
|
1274
|
+
// 不用 .dbt-row:全局 input flex:1 会把 checkbox 拉伸占满、标签被推到远端(视觉审查命中)
|
|
1275
|
+
{ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "nowrap", width: "fit-content" } },
|
|
1276
|
+
React.createElement("input", { type: "checkbox", style: { flex: "none", minWidth: 0, width: "auto" }, checked: editNull, onChange: (e) => setEditNull(e.target.checked) }),
|
|
1277
|
+
t("setNull"),
|
|
1278
|
+
) : null,
|
|
1279
|
+
truncHint && truncHint !== selReason ? React.createElement("span", { className: "dbt-readhint" }, t(truncHint)) : null,
|
|
1280
|
+
cellErr ? React.createElement("span", { className: "dbt-err" }, cellErr) : null,
|
|
1281
|
+
React.createElement(
|
|
1282
|
+
"div",
|
|
1283
|
+
{ className: "dbt-row" },
|
|
1284
|
+
React.createElement("button", { className: "dbt-btn", onClick: copyCell }, copied ? t("copied") : t("copyBtn")),
|
|
1285
|
+
selReason === null ? React.createElement("button", { className: "dbt-btn primary", disabled: busy, onClick: saveCell }, busy ? t("cellSaving") : t("save")) : null,
|
|
1286
|
+
React.createElement("button", { className: "dbt-btn", onClick: () => setSel(null) }, t("close")),
|
|
1287
|
+
),
|
|
1288
|
+
) : null,
|
|
1288
1289
|
);
|
|
1289
1290
|
}
|
|
1290
1291
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.8",
|
|
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",
|