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.dbSelectTableHint') || '点击表名可生成查询 SQL') + '
' + '' + '
' + + '
' + + '' + + '
' + + '' + + '' + + '
' + + '
' + (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') || '删除连接';