From cce64e213f2a9fd529b8efc6496d2e22415cebcc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Wed, 25 Mar 2026 19:49:14 +0800
Subject: [PATCH] Add files via upload
---
web/static/css/style.css | 99 ++++++++++++++++++++++++++++----------
web/static/i18n/zh-CN.json | 1 +
web/static/js/webshell.js | 92 ++++++++++++++++++++++++++++-------
3 files changed, 149 insertions(+), 43 deletions(-)
diff --git a/web/static/css/style.css b/web/static/css/style.css
index 889da714..0e22af09 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -10359,54 +10359,93 @@ header {
flex-direction: column;
gap: 10px;
}
+.webshell-db-profile-modal-content {
+ max-width: 840px;
+ border-radius: 12px;
+ border: 1px solid rgba(15, 23, 42, 0.1);
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.18);
+}
+#webshell-db-profile-modal .modal-header {
+ padding: 14px 18px;
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
+ background: #fff;
+ box-shadow: none;
+}
+#webshell-db-profile-modal .modal-header h2 {
+ font-size: 1.08rem;
+ font-weight: 600;
+ background: none;
+ -webkit-text-fill-color: currentColor;
+ color: #0f172a;
+}
+#webshell-db-profile-modal .modal-body {
+ padding: 14px 18px 10px;
+}
+#webshell-db-profile-modal .modal-footer {
+ padding: 10px 18px 14px;
+ border-top: 1px solid rgba(15, 23, 42, 0.08);
+ background: #fff;
+}
+#webshell-db-profile-modal .modal-footer .btn-secondary,
+#webshell-db-profile-modal .modal-footer .btn-primary {
+ min-width: 78px;
+ height: 36px;
+ border-radius: 8px;
+}
.webshell-db-toolbar {
display: grid;
- grid-template-columns: repeat(4, minmax(160px, 1fr));
- gap: 12px;
- padding: 14px;
+ grid-template-columns: repeat(4, minmax(130px, 1fr));
+ gap: 10px;
+ padding: 12px;
border: 1px solid rgba(15, 23, 42, 0.08);
- border-radius: 12px;
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 250, 252, 0.96) 100%);
- box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border-radius: 10px;
+ background: #f8fafc;
+ box-shadow: none;
}
.webshell-db-toolbar label {
display: flex;
flex-direction: column;
- gap: 6px;
+ gap: 5px;
min-width: 0;
- padding: 8px 10px;
- border-radius: 10px;
- background: rgba(255, 255, 255, 0.82);
+ padding: 7px 9px;
+ border-radius: 8px;
+ background: #fff;
border: 1px solid rgba(15, 23, 42, 0.08);
- transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.webshell-db-toolbar label:focus-within {
- border-color: rgba(0, 102, 255, 0.38);
- box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.12);
- transform: translateY(-1px);
+ border-color: rgba(0, 102, 255, 0.32);
+ box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
}
.webshell-db-toolbar label span {
- font-size: 0.75rem;
- color: var(--text-secondary);
+ font-size: 0.72rem;
+ color: #64748b;
font-weight: 600;
- letter-spacing: 0.03em;
- text-transform: uppercase;
+ letter-spacing: 0.01em;
+ text-transform: none;
}
.webshell-db-toolbar .form-control {
- height: 36px;
- border-radius: 8px;
- border: 1px solid rgba(15, 23, 42, 0.16);
+ height: 34px;
+ border-radius: 7px;
+ border: 1px solid rgba(15, 23, 42, 0.14);
background: #fff;
- font-size: 0.9rem;
+ font-size: 0.88rem;
padding-left: 10px;
padding-right: 10px;
- transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.webshell-db-toolbar .form-control:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
background: #fff;
}
+.webshell-db-toolbar select.form-control {
+ appearance: auto;
+ -webkit-appearance: menulist;
+ -moz-appearance: menulist;
+ padding-right: 8px;
+ background-image: none;
+}
#webshell-db-sqlite-row {
grid-column: 1 / -1;
}
@@ -10522,7 +10561,7 @@ header {
grid-template-columns: 240px minmax(0, 1fr);
}
.webshell-db-toolbar {
- grid-template-columns: repeat(3, minmax(140px, 1fr));
+ grid-template-columns: repeat(3, minmax(120px, 1fr));
}
}
@media (max-width: 980px) {
@@ -10533,13 +10572,23 @@ header {
min-height: 200px;
}
.webshell-db-toolbar {
- grid-template-columns: repeat(2, minmax(140px, 1fr));
+ grid-template-columns: repeat(2, minmax(120px, 1fr));
}
}
@media (max-width: 700px) {
.webshell-db-toolbar {
grid-template-columns: 1fr;
}
+ #webshell-db-profile-modal .modal-content {
+ width: calc(100% - 24px);
+ margin: 32px auto;
+ }
+ #webshell-db-profile-modal .modal-header,
+ #webshell-db-profile-modal .modal-body,
+ #webshell-db-profile-modal .modal-footer {
+ padding-left: 14px;
+ padding-right: 14px;
+ }
}
/* 仪表盘页面样式(最佳实践布局 + 视觉增强) */
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index 6cc7d816..7703ec4d 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -407,6 +407,7 @@
"dbDeleteProfile": "删除连接",
"dbDeleteProfileConfirm": "确定删除该数据库连接配置吗?",
"dbProfileNamePrompt": "请输入连接名称",
+ "dbProfileName": "连接名称",
"dbProfiles": "数据库连接",
"aiSystemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
"aiNewConversation": "新对话",
diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js
index 27834502..91ec1c31 100644
--- a/web/static/js/webshell.js
+++ b/web/static/js/webshell.js
@@ -132,6 +132,7 @@ function wsT(key) {
'webshell.dbDeleteProfile': '删除连接',
'webshell.dbDeleteProfileConfirm': '确定删除该数据库连接配置吗?',
'webshell.dbProfileNamePrompt': '请输入连接名称',
+ 'webshell.dbProfileName': '连接名称',
'webshell.dbProfiles': '数据库连接',
'webshell.aiSystemReadyMessage': '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。',
'webshell.aiPlaceholder': '例如:列出当前目录下的文件',
@@ -864,14 +865,17 @@ function webshellDbGetFieldValue(id) {
}
function webshellDbCollectConfig(conn) {
+ var curr = getWebshellDbConfig(conn) || {};
+ var nameVal = webshellDbGetFieldValue('webshell-db-profile-name');
var cfg = {
+ name: nameVal || curr.name || 'DB-1',
type: webshellDbGetFieldValue('webshell-db-type') || 'mysql',
host: webshellDbGetFieldValue('webshell-db-host') || '127.0.0.1',
port: webshellDbGetFieldValue('webshell-db-port') || '',
username: webshellDbGetFieldValue('webshell-db-user') || '',
password: (document.getElementById('webshell-db-pass') || {}).value || '',
database: webshellDbGetFieldValue('webshell-db-name') || '',
- selectedDatabase: getWebshellDbConfig(conn).selectedDatabase || '',
+ selectedDatabase: curr.selectedDatabase || '',
sqlitePath: webshellDbGetFieldValue('webshell-db-sqlite-path') || '/tmp/test.db',
sql: (document.getElementById('webshell-db-sql') || {}).value || ''
};
@@ -1574,7 +1578,19 @@ function selectWebshell(id, stateReady) {
'
' +
'' +
'' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' + (wsT('webshell.dbOutput') || '执行输出') + '
' + (wsT('webshell.dbCliHint') || '如果提示命令不存在,请先在目标主机安装对应客户端(mysql/psql/sqlite3/sqlcmd)') + '
' +
+ '' +
'';
@@ -1770,6 +1783,12 @@ function selectWebshell(id, stateReady) {
var dbSchemaTreeEl = document.getElementById('webshell-db-schema-tree');
var dbProfilesEl = document.getElementById('webshell-db-profiles');
var dbAddProfileBtn = document.getElementById('webshell-db-add-profile-btn');
+ var dbProfileModalEl = document.getElementById('webshell-db-profile-modal');
+ var dbProfileModalTitleEl = document.getElementById('webshell-db-profile-modal-title');
+ var dbProfileModalCloseBtn = document.getElementById('webshell-db-profile-modal-close');
+ var dbProfileModalCancelBtn = document.getElementById('webshell-db-profile-cancel-btn');
+ var dbProfileModalSaveBtn = document.getElementById('webshell-db-profile-save-btn');
+ var dbProfileNameEl = document.getElementById('webshell-db-profile-name');
var dbHostEl = document.getElementById('webshell-db-host');
var dbPortEl = document.getElementById('webshell-db-port');
var dbUserEl = document.getElementById('webshell-db-user');
@@ -1793,9 +1812,19 @@ function selectWebshell(id, stateReady) {
if (dbAddProfileBtn) dbAddProfileBtn.disabled = disabled;
}
+ function setDbProfileModalVisible(visible, mode) {
+ if (!dbProfileModalEl) return;
+ dbProfileModalEl.style.display = visible ? 'block' : 'none';
+ if (dbProfileModalTitleEl) {
+ if (mode === 'add') dbProfileModalTitleEl.textContent = wsT('webshell.dbAddProfile') || '新增连接';
+ else dbProfileModalTitleEl.textContent = wsT('webshell.editConnectionTitle') || '编辑连接';
+ }
+ }
+
function applyActiveDbProfileToForm() {
var dbCfg = getWebshellDbConfig(conn);
if (!dbCfg) return;
+ if (dbProfileNameEl) dbProfileNameEl.value = dbCfg.name || 'DB-1';
if (dbTypeEl) dbTypeEl.value = dbCfg.type || 'mysql';
if (dbHostEl) dbHostEl.value = dbCfg.host || '127.0.0.1';
if (dbPortEl) dbPortEl.value = dbCfg.port || '';
@@ -1817,7 +1846,7 @@ function selectWebshell(id, stateReady) {
var active = p.id === state.activeProfileId;
html += '' +
'' +
- '' +
+ '' +
'' +
'
';
});
@@ -1839,15 +1868,12 @@ function selectWebshell(id, stateReady) {
renderDbSchemaTree();
return;
}
- if (action === 'rename') {
- var curr = state.profiles[idx].name || '';
- var next = prompt(wsT('webshell.dbProfileNamePrompt') || '请输入连接名称', curr);
- if (next == null) return;
- next = String(next || '').trim();
- if (!next) return;
- state.profiles[idx].name = next.slice(0, 30);
+ if (action === 'edit') {
+ state.activeProfileId = id;
saveWebshellDbState(conn, state);
+ applyActiveDbProfileToForm();
renderDbProfileTabs();
+ setDbProfileModalVisible(true, 'edit');
return;
}
if (action === 'delete') {
@@ -2179,11 +2205,12 @@ function selectWebshell(id, stateReady) {
resetDbColumnLoadCache();
renderDbSchemaTree();
});
- ['webshell-db-host', 'webshell-db-port', 'webshell-db-user', 'webshell-db-pass', 'webshell-db-name', 'webshell-db-sqlite-path'].forEach(function (id) {
+ ['webshell-db-profile-name', 'webshell-db-host', 'webshell-db-port', 'webshell-db-user', 'webshell-db-pass', 'webshell-db-name', 'webshell-db-sqlite-path'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.addEventListener('change', function () {
webshellDbCollectConfig(conn);
resetDbColumnLoadCache();
+ renderDbProfileTabs();
});
});
if (dbSqlEl) dbSqlEl.addEventListener('change', function () { webshellDbCollectConfig(conn); });
@@ -2203,6 +2230,25 @@ function selectWebshell(id, stateReady) {
if (dbSqlEl) dbSqlEl.value = '';
webshellDbCollectConfig(conn);
});
+ if (dbProfileModalCloseBtn) dbProfileModalCloseBtn.addEventListener('click', function () {
+ setDbProfileModalVisible(false);
+ });
+ if (dbProfileModalCancelBtn) dbProfileModalCancelBtn.addEventListener('click', function () {
+ applyActiveDbProfileToForm();
+ setDbProfileModalVisible(false);
+ });
+ if (dbProfileModalSaveBtn) dbProfileModalSaveBtn.addEventListener('click', function () {
+ webshellDbCollectConfig(conn);
+ renderDbProfileTabs();
+ resetDbColumnLoadCache();
+ setDbProfileModalVisible(false);
+ });
+ if (dbProfileModalEl) dbProfileModalEl.addEventListener('click', function (evt) {
+ if (evt.target === dbProfileModalEl) {
+ applyActiveDbProfileToForm();
+ setDbProfileModalVisible(false);
+ }
+ });
if (dbAddProfileBtn) dbAddProfileBtn.addEventListener('click', function () {
var state = getWebshellDbState(conn);
var name = 'DB-' + (state.profiles.length + 1);
@@ -2213,10 +2259,12 @@ function selectWebshell(id, stateReady) {
applyActiveDbProfileToForm();
renderDbProfileTabs();
renderDbSchemaTree();
+ setDbProfileModalVisible(true, 'add');
});
renderDbProfileTabs();
applyActiveDbProfileToForm();
renderDbSchemaTree();
+ setDbProfileModalVisible(false);
initWebshellTerminal(conn);
}
@@ -3699,6 +3747,8 @@ function refreshWebshellUIOnLanguageChange() {
}
var dbTypeLabel = document.querySelector('#webshell-db-type') ? document.querySelector('#webshell-db-type').closest('label') : null;
if (dbTypeLabel && dbTypeLabel.querySelector('span')) dbTypeLabel.querySelector('span').textContent = wsT('webshell.dbType') || '数据库类型';
+ var dbProfileNameLabel = document.querySelector('#webshell-db-profile-name') ? document.querySelector('#webshell-db-profile-name').closest('label') : null;
+ if (dbProfileNameLabel && dbProfileNameLabel.querySelector('span')) dbProfileNameLabel.querySelector('span').textContent = wsT('webshell.dbProfileName') || '连接名称';
var dbHostLabel = document.querySelector('#webshell-db-host') ? document.querySelector('#webshell-db-host').closest('label') : null;
if (dbHostLabel && dbHostLabel.querySelector('span')) dbHostLabel.querySelector('span').textContent = wsT('webshell.dbHost') || '主机';
var dbPortLabel = document.querySelector('#webshell-db-port') ? document.querySelector('#webshell-db-port').closest('label') : null;
@@ -3733,8 +3783,14 @@ function refreshWebshellUIOnLanguageChange() {
if (dbTreeHint) dbTreeHint.textContent = wsT('webshell.dbSelectTableHint') || '点击表名可生成查询 SQL';
var dbAddProfileBtn = document.getElementById('webshell-db-add-profile-btn');
if (dbAddProfileBtn) dbAddProfileBtn.textContent = '+ ' + (wsT('webshell.dbAddProfile') || '新增连接');
- document.querySelectorAll('.webshell-db-profile-menu[data-action="rename"]').forEach(function (el) {
- el.title = wsT('webshell.dbRenameProfile') || '重命名';
+ var dbProfileModalTitle = document.getElementById('webshell-db-profile-modal-title');
+ if (dbProfileModalTitle) dbProfileModalTitle.textContent = wsT('webshell.editConnectionTitle') || '编辑连接';
+ var dbProfileCancelBtn = document.getElementById('webshell-db-profile-cancel-btn');
+ if (dbProfileCancelBtn) dbProfileCancelBtn.textContent = '取消';
+ var dbProfileSaveBtn = document.getElementById('webshell-db-profile-save-btn');
+ if (dbProfileSaveBtn) dbProfileSaveBtn.textContent = '保存';
+ document.querySelectorAll('.webshell-db-profile-menu[data-action="edit"]').forEach(function (el) {
+ el.title = wsT('webshell.editConnection') || '编辑';
});
document.querySelectorAll('.webshell-db-profile-menu[data-action="delete"]').forEach(function (el) {
el.title = wsT('webshell.dbDeleteProfile') || '删除连接';