mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-30 15:49:11 +02:00
Add files via upload
This commit is contained in:
@@ -10129,7 +10129,8 @@ header {
|
|||||||
.webshell-db-schema-tree {
|
.webshell-db-schema-tree {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
.webshell-db-sidebar-hint {
|
.webshell-db-sidebar-hint {
|
||||||
@@ -10154,6 +10155,9 @@ header {
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.webshell-db-group-title::-webkit-details-marker {
|
.webshell-db-group-title::-webkit-details-marker {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -10162,13 +10166,22 @@ header {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-size: 0.74rem;
|
font-size: 0.74rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.webshell-db-group-items {
|
.webshell-db-group-items {
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 260px;
|
max-height: 260px;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-table-node {
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.webshell-db-table-node:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
.webshell-db-table-item {
|
.webshell-db-table-item {
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -10181,13 +10194,57 @@ header {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
list-style: none;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-table-item::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.webshell-db-table-item:hover {
|
.webshell-db-table-item:hover {
|
||||||
background: rgba(0, 102, 255, 0.06);
|
background: rgba(0, 102, 255, 0.06);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
.webshell-db-column-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
.webshell-db-column-item {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 10px 6px 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-column-item:hover {
|
||||||
|
background: rgba(0, 102, 255, 0.06);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.webshell-db-column-empty {
|
||||||
|
padding: 4px 10px 8px 24px;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
.webshell-db-icon {
|
.webshell-db-icon {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.webshell-db-label {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.webshell-db-main {
|
.webshell-db-main {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -393,7 +393,8 @@
|
|||||||
"dbSchema": "Database Schema",
|
"dbSchema": "Database Schema",
|
||||||
"dbLoadSchema": "Load Schema",
|
"dbLoadSchema": "Load Schema",
|
||||||
"dbNoSchema": "No schema yet, click Load Schema",
|
"dbNoSchema": "No schema yet, click Load Schema",
|
||||||
"dbSelectTableHint": "Click a table to generate SELECT SQL",
|
"dbSelectTableHint": "Click a table to expand columns and generate SQL",
|
||||||
|
"dbNoColumns": "No column details",
|
||||||
"dbResultTable": "Result Table",
|
"dbResultTable": "Result Table",
|
||||||
"dbClearSql": "Clear SQL",
|
"dbClearSql": "Clear SQL",
|
||||||
"dbTemplateSql": "SQL Template",
|
"dbTemplateSql": "SQL Template",
|
||||||
|
|||||||
@@ -393,7 +393,8 @@
|
|||||||
"dbSchema": "数据库结构",
|
"dbSchema": "数据库结构",
|
||||||
"dbLoadSchema": "加载结构",
|
"dbLoadSchema": "加载结构",
|
||||||
"dbNoSchema": "暂无数据库结构,请先加载",
|
"dbNoSchema": "暂无数据库结构,请先加载",
|
||||||
"dbSelectTableHint": "点击表名可自动生成查询 SQL",
|
"dbSelectTableHint": "点击表名可展开列信息并生成查询 SQL",
|
||||||
|
"dbNoColumns": "暂无列信息",
|
||||||
"dbResultTable": "结果表格",
|
"dbResultTable": "结果表格",
|
||||||
"dbClearSql": "清空 SQL",
|
"dbClearSql": "清空 SQL",
|
||||||
"dbTemplateSql": "示例 SQL",
|
"dbTemplateSql": "示例 SQL",
|
||||||
|
|||||||
+317
-16
@@ -118,7 +118,8 @@ function wsT(key) {
|
|||||||
'webshell.dbSchema': '数据库结构',
|
'webshell.dbSchema': '数据库结构',
|
||||||
'webshell.dbLoadSchema': '加载结构',
|
'webshell.dbLoadSchema': '加载结构',
|
||||||
'webshell.dbNoSchema': '暂无数据库结构,请先加载',
|
'webshell.dbNoSchema': '暂无数据库结构,请先加载',
|
||||||
'webshell.dbSelectTableHint': '点击表名可生成查询 SQL',
|
'webshell.dbSelectTableHint': '点击表名可展开列信息并生成查询 SQL',
|
||||||
|
'webshell.dbNoColumns': '暂无列信息',
|
||||||
'webshell.dbResultTable': '结果表格',
|
'webshell.dbResultTable': '结果表格',
|
||||||
'webshell.dbClearSql': '清空 SQL',
|
'webshell.dbClearSql': '清空 SQL',
|
||||||
'webshell.dbTemplateSql': '示例 SQL',
|
'webshell.dbTemplateSql': '示例 SQL',
|
||||||
@@ -948,6 +949,10 @@ function webshellDbQuoteIdentifier(type, name) {
|
|||||||
return '"' + v.replace(/"/g, '""') + '"';
|
return '"' + v.replace(/"/g, '""') + '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function webshellDbQuoteLiteral(value) {
|
||||||
|
return "'" + String(value == null ? '' : value).replace(/'/g, "''") + "'";
|
||||||
|
}
|
||||||
|
|
||||||
function buildWebshellDbCommand(cfg, isTestOnly, options) {
|
function buildWebshellDbCommand(cfg, isTestOnly, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var type = cfg.type || 'mysql';
|
var type = cfg.type || 'mysql';
|
||||||
@@ -998,19 +1003,53 @@ function buildWebshellDbSchemaCommand(cfg) {
|
|||||||
var type = cfg.type || 'mysql';
|
var type = cfg.type || 'mysql';
|
||||||
var schemaSQL = '';
|
var schemaSQL = '';
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
schemaSQL = "SELECT SCHEMA_NAME AS db_name, '' AS table_name FROM INFORMATION_SCHEMA.SCHEMATA UNION ALL SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' ORDER BY db_name, table_name;";
|
schemaSQL = "SELECT SCHEMA_NAME AS db_name, '' AS table_name, '' AS column_name FROM INFORMATION_SCHEMA.SCHEMATA UNION ALL SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name, '' AS column_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' UNION ALL SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name, COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS ORDER BY db_name, table_name, column_name;";
|
||||||
} else if (type === 'pgsql') {
|
} else if (type === 'pgsql') {
|
||||||
schemaSQL = "SELECT table_schema AS db_name, table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema NOT IN ('pg_catalog','information_schema') ORDER BY table_schema, table_name;";
|
schemaSQL = "SELECT table_schema AS db_name, table_name, '' AS column_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema NOT IN ('pg_catalog','information_schema') UNION ALL SELECT table_schema AS db_name, table_name, column_name FROM information_schema.columns WHERE table_schema NOT IN ('pg_catalog','information_schema') ORDER BY db_name, table_name, column_name;";
|
||||||
} else if (type === 'sqlite') {
|
} else if (type === 'sqlite') {
|
||||||
schemaSQL = "SELECT 'main' AS db_name, name AS table_name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;";
|
schemaSQL = "SELECT 'main' AS db_name, name AS table_name, '' AS column_name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' UNION ALL SELECT 'main' AS db_name, m.name AS table_name, p.name AS column_name FROM sqlite_master m JOIN pragma_table_info(m.name) p ON 1=1 WHERE m.type='table' AND m.name NOT LIKE 'sqlite_%' ORDER BY db_name, table_name, column_name;";
|
||||||
} else if (type === 'mssql') {
|
} else if (type === 'mssql') {
|
||||||
schemaSQL = "SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' ORDER BY TABLE_SCHEMA, TABLE_NAME;";
|
schemaSQL = "SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name, '' AS column_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' UNION ALL SELECT TABLE_SCHEMA AS db_name, TABLE_NAME AS table_name, COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS ORDER BY db_name, table_name, column_name;";
|
||||||
} else {
|
} else {
|
||||||
return { error: (wsT('webshell.dbExecFailed') || '数据库执行失败') + ': unsupported type ' + type };
|
return { error: (wsT('webshell.dbExecFailed') || '数据库执行失败') + ': unsupported type ' + type };
|
||||||
}
|
}
|
||||||
return buildWebshellDbCommand(cfg, false, { sql: schemaSQL });
|
return buildWebshellDbCommand(cfg, false, { sql: schemaSQL });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildWebshellDbColumnsCommand(cfg, dbName, tableName) {
|
||||||
|
var type = cfg.type || 'mysql';
|
||||||
|
var sql = '';
|
||||||
|
if (type === 'mysql') {
|
||||||
|
sql = "SELECT COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=" + webshellDbQuoteLiteral(dbName) + " AND TABLE_NAME=" + webshellDbQuoteLiteral(tableName) + " ORDER BY ORDINAL_POSITION;";
|
||||||
|
} else if (type === 'pgsql') {
|
||||||
|
sql = "SELECT column_name FROM information_schema.columns WHERE table_schema=" + webshellDbQuoteLiteral(dbName) + " AND table_name=" + webshellDbQuoteLiteral(tableName) + " ORDER BY ordinal_position;";
|
||||||
|
} else if (type === 'sqlite') {
|
||||||
|
sql = "SELECT name AS column_name FROM pragma_table_info(" + webshellDbQuoteLiteral(tableName) + ") ORDER BY cid;";
|
||||||
|
} else if (type === 'mssql') {
|
||||||
|
sql = "SELECT COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=" + webshellDbQuoteLiteral(dbName) + " AND TABLE_NAME=" + webshellDbQuoteLiteral(tableName) + " ORDER BY ORDINAL_POSITION;";
|
||||||
|
} else {
|
||||||
|
return { error: (wsT('webshell.dbExecFailed') || '数据库执行失败') + ': unsupported type ' + type };
|
||||||
|
}
|
||||||
|
return buildWebshellDbCommand(cfg, false, { sql: sql });
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWebshellDbColumnsByDatabaseCommand(cfg, dbName) {
|
||||||
|
var type = cfg.type || 'mysql';
|
||||||
|
var sql = '';
|
||||||
|
if (type === 'mysql') {
|
||||||
|
sql = "SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=" + webshellDbQuoteLiteral(dbName) + " ORDER BY TABLE_NAME, ORDINAL_POSITION;";
|
||||||
|
} else if (type === 'pgsql') {
|
||||||
|
sql = "SELECT table_name, column_name FROM information_schema.columns WHERE table_schema=" + webshellDbQuoteLiteral(dbName) + " ORDER BY table_name, ordinal_position;";
|
||||||
|
} else if (type === 'sqlite') {
|
||||||
|
sql = "SELECT m.name AS table_name, p.name AS column_name FROM sqlite_master m JOIN pragma_table_info(m.name) p ON 1=1 WHERE m.type='table' AND m.name NOT LIKE 'sqlite_%' ORDER BY m.name, p.cid;";
|
||||||
|
} else if (type === 'mssql') {
|
||||||
|
sql = "SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=" + webshellDbQuoteLiteral(dbName) + " ORDER BY TABLE_NAME, ORDINAL_POSITION;";
|
||||||
|
} else {
|
||||||
|
return { error: (wsT('webshell.dbExecFailed') || '数据库执行失败') + ': unsupported type ' + type };
|
||||||
|
}
|
||||||
|
return buildWebshellDbCommand(cfg, false, { sql: sql });
|
||||||
|
}
|
||||||
|
|
||||||
function parseWebshellDbExecOutput(rawOutput) {
|
function parseWebshellDbExecOutput(rawOutput) {
|
||||||
var raw = String(rawOutput || '');
|
var raw = String(rawOutput || '');
|
||||||
var rc = null;
|
var rc = null;
|
||||||
@@ -1033,6 +1072,7 @@ function parseWebshellDbSchema(rawOutput) {
|
|||||||
var headers = lines[0].split(delimiter).map(function (s) { return String(s || '').trim().toLowerCase(); });
|
var headers = lines[0].split(delimiter).map(function (s) { return String(s || '').trim().toLowerCase(); });
|
||||||
var dbIdx = headers.indexOf('db_name');
|
var dbIdx = headers.indexOf('db_name');
|
||||||
var tableIdx = headers.indexOf('table_name');
|
var tableIdx = headers.indexOf('table_name');
|
||||||
|
var columnIdx = headers.indexOf('column_name');
|
||||||
if (dbIdx < 0 || tableIdx < 0) return {};
|
if (dbIdx < 0 || tableIdx < 0) return {};
|
||||||
var schema = {};
|
var schema = {};
|
||||||
for (var i = 1; i < lines.length; i++) {
|
for (var i = 1; i < lines.length; i++) {
|
||||||
@@ -1040,13 +1080,115 @@ function parseWebshellDbSchema(rawOutput) {
|
|||||||
if (cols.length !== headers.length) continue;
|
if (cols.length !== headers.length) continue;
|
||||||
var db = cols[dbIdx] || 'default';
|
var db = cols[dbIdx] || 'default';
|
||||||
var table = cols[tableIdx] || '';
|
var table = cols[tableIdx] || '';
|
||||||
if (!schema[db]) schema[db] = [];
|
var column = columnIdx >= 0 ? (cols[columnIdx] || '') : '';
|
||||||
if (table) schema[db].push(table);
|
if (!schema[db]) schema[db] = { tables: {} };
|
||||||
|
if (!table) continue;
|
||||||
|
if (!schema[db].tables[table]) schema[db].tables[table] = [];
|
||||||
|
if (column && schema[db].tables[table].indexOf(column) < 0) {
|
||||||
|
schema[db].tables[table].push(column);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Object.keys(schema).forEach(function (dbName) {
|
return normalizeWebshellDbSchema(schema);
|
||||||
schema[dbName].sort(function (a, b) { return a.localeCompare(b); });
|
}
|
||||||
|
|
||||||
|
function parseWebshellDbColumns(rawOutput) {
|
||||||
|
var text = String(rawOutput || '').trim();
|
||||||
|
if (!text) return [];
|
||||||
|
var lines = text.split(/\r?\n/).filter(function (line) {
|
||||||
|
return line && line.trim() && !/^\(\d+\s+rows?\)$/i.test(line.trim()) && !/^[-+\s|]+$/.test(line.trim());
|
||||||
});
|
});
|
||||||
return schema;
|
if (lines.length < 2) return [];
|
||||||
|
var delimiter = lines[0].indexOf('\t') >= 0 ? '\t' : (lines[0].indexOf('|') >= 0 ? '|' : '');
|
||||||
|
if (!delimiter) {
|
||||||
|
if (String(lines[0] || '').trim().toLowerCase() !== 'column_name') return [];
|
||||||
|
var plainColumns = [];
|
||||||
|
for (var p = 1; p < lines.length; p++) {
|
||||||
|
var plainName = String(lines[p] || '').trim();
|
||||||
|
if (!plainName || plainColumns.indexOf(plainName) >= 0) continue;
|
||||||
|
plainColumns.push(plainName);
|
||||||
|
}
|
||||||
|
return plainColumns;
|
||||||
|
}
|
||||||
|
var headers = lines[0].split(delimiter).map(function (s) { return String(s || '').trim().toLowerCase(); });
|
||||||
|
var colIdx = headers.indexOf('column_name');
|
||||||
|
if (colIdx < 0) return [];
|
||||||
|
var columns = [];
|
||||||
|
for (var i = 1; i < lines.length; i++) {
|
||||||
|
var cols = lines[i].split(delimiter).map(function (s) { return String(s || '').trim(); });
|
||||||
|
if (cols.length !== headers.length) continue;
|
||||||
|
var name = cols[colIdx] || '';
|
||||||
|
if (!name || columns.indexOf(name) >= 0) continue;
|
||||||
|
columns.push(name);
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWebshellDbTableColumns(rawOutput) {
|
||||||
|
var text = String(rawOutput || '').trim();
|
||||||
|
if (!text) return {};
|
||||||
|
var lines = text.split(/\r?\n/).filter(function (line) {
|
||||||
|
return line && line.trim() && !/^\(\d+\s+rows?\)$/i.test(line.trim()) && !/^[-+\s|]+$/.test(line.trim());
|
||||||
|
});
|
||||||
|
if (lines.length < 2) return {};
|
||||||
|
var delimiter = lines[0].indexOf('\t') >= 0 ? '\t' : (lines[0].indexOf('|') >= 0 ? '|' : '');
|
||||||
|
if (!delimiter) return {};
|
||||||
|
var headers = lines[0].split(delimiter).map(function (s) { return String(s || '').trim().toLowerCase(); });
|
||||||
|
var tableIdx = headers.indexOf('table_name');
|
||||||
|
var colIdx = headers.indexOf('column_name');
|
||||||
|
if (tableIdx < 0 || colIdx < 0) return {};
|
||||||
|
var tableColumns = {};
|
||||||
|
for (var i = 1; i < lines.length; i++) {
|
||||||
|
var cols = lines[i].split(delimiter).map(function (s) { return String(s || '').trim(); });
|
||||||
|
if (cols.length !== headers.length) continue;
|
||||||
|
var tableName = cols[tableIdx] || '';
|
||||||
|
var colName = cols[colIdx] || '';
|
||||||
|
if (!tableName || !colName) continue;
|
||||||
|
if (!tableColumns[tableName]) tableColumns[tableName] = [];
|
||||||
|
if (tableColumns[tableName].indexOf(colName) >= 0) continue;
|
||||||
|
tableColumns[tableName].push(colName);
|
||||||
|
}
|
||||||
|
return tableColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWebshellDbSchema(rawSchema) {
|
||||||
|
if (!rawSchema || typeof rawSchema !== 'object') return {};
|
||||||
|
var normalized = {};
|
||||||
|
Object.keys(rawSchema).forEach(function (dbName) {
|
||||||
|
var dbEntry = rawSchema[dbName];
|
||||||
|
var tableMap = {};
|
||||||
|
|
||||||
|
if (Array.isArray(dbEntry)) {
|
||||||
|
dbEntry.forEach(function (tableName) {
|
||||||
|
var t = String(tableName || '').trim();
|
||||||
|
if (!t) return;
|
||||||
|
if (!tableMap[t]) tableMap[t] = [];
|
||||||
|
});
|
||||||
|
} else if (dbEntry && typeof dbEntry === 'object') {
|
||||||
|
var tablesSource = (dbEntry.tables && typeof dbEntry.tables === 'object') ? dbEntry.tables : dbEntry;
|
||||||
|
Object.keys(tablesSource).forEach(function (tableName) {
|
||||||
|
if (tableName === 'tables') return;
|
||||||
|
var t = String(tableName || '').trim();
|
||||||
|
if (!t) return;
|
||||||
|
var rawColumns = tablesSource[tableName];
|
||||||
|
var columns = Array.isArray(rawColumns) ? rawColumns : [];
|
||||||
|
var uniqColumns = [];
|
||||||
|
columns.forEach(function (colName) {
|
||||||
|
var c = String(colName || '').trim();
|
||||||
|
if (!c || uniqColumns.indexOf(c) >= 0) return;
|
||||||
|
uniqColumns.push(c);
|
||||||
|
});
|
||||||
|
uniqColumns.sort(function (a, b) { return a.localeCompare(b); });
|
||||||
|
tableMap[t] = uniqColumns;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortedTables = {};
|
||||||
|
Object.keys(tableMap).sort(function (a, b) { return a.localeCompare(b); }).forEach(function (tableName) {
|
||||||
|
sortedTables[tableName] = tableMap[tableName];
|
||||||
|
});
|
||||||
|
normalized[dbName] = { tables: sortedTables };
|
||||||
|
});
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyWebshellAiError(rawMessage) {
|
function simplifyWebshellAiError(rawMessage) {
|
||||||
@@ -1549,6 +1691,15 @@ function selectWebshell(id, stateReady) {
|
|||||||
var dbPassEl = document.getElementById('webshell-db-pass');
|
var dbPassEl = document.getElementById('webshell-db-pass');
|
||||||
var dbNameEl = document.getElementById('webshell-db-name');
|
var dbNameEl = document.getElementById('webshell-db-name');
|
||||||
var dbSqliteEl = document.getElementById('webshell-db-sqlite-path');
|
var dbSqliteEl = document.getElementById('webshell-db-sqlite-path');
|
||||||
|
var dbColumnsLoading = {};
|
||||||
|
var dbColumnsBatchLoading = {};
|
||||||
|
var dbColumnsBatchLoaded = {};
|
||||||
|
|
||||||
|
function resetDbColumnLoadCache() {
|
||||||
|
dbColumnsLoading = {};
|
||||||
|
dbColumnsBatchLoading = {};
|
||||||
|
dbColumnsBatchLoaded = {};
|
||||||
|
}
|
||||||
|
|
||||||
function setDbActionButtonsDisabled(disabled) {
|
function setDbActionButtonsDisabled(disabled) {
|
||||||
if (dbRunBtn) dbRunBtn.disabled = disabled;
|
if (dbRunBtn) dbRunBtn.disabled = disabled;
|
||||||
@@ -1599,6 +1750,7 @@ function selectWebshell(id, stateReady) {
|
|||||||
saveWebshellDbState(conn, state);
|
saveWebshellDbState(conn, state);
|
||||||
applyActiveDbProfileToForm();
|
applyActiveDbProfileToForm();
|
||||||
renderDbProfileTabs();
|
renderDbProfileTabs();
|
||||||
|
resetDbColumnLoadCache();
|
||||||
renderDbSchemaTree();
|
renderDbSchemaTree();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1623,6 +1775,7 @@ function selectWebshell(id, stateReady) {
|
|||||||
saveWebshellDbState(conn, state);
|
saveWebshellDbState(conn, state);
|
||||||
applyActiveDbProfileToForm();
|
applyActiveDbProfileToForm();
|
||||||
renderDbProfileTabs();
|
renderDbProfileTabs();
|
||||||
|
resetDbColumnLoadCache();
|
||||||
renderDbSchemaTree();
|
renderDbSchemaTree();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1632,8 +1785,15 @@ function selectWebshell(id, stateReady) {
|
|||||||
function renderDbSchemaTree() {
|
function renderDbSchemaTree() {
|
||||||
if (!dbSchemaTreeEl) return;
|
if (!dbSchemaTreeEl) return;
|
||||||
var cfg = getWebshellDbConfig(conn);
|
var cfg = getWebshellDbConfig(conn);
|
||||||
var schema = (cfg && cfg.schema && typeof cfg.schema === 'object') ? cfg.schema : {};
|
var schema = normalizeWebshellDbSchema((cfg && cfg.schema && typeof cfg.schema === 'object') ? cfg.schema : {});
|
||||||
var dbs = Object.keys(schema).sort(function (a, b) { return a.localeCompare(b); });
|
var dbs = Object.keys(schema).sort(function (a, b) { return a.localeCompare(b); });
|
||||||
|
var openTableKeys = {};
|
||||||
|
dbSchemaTreeEl.querySelectorAll('.webshell-db-table-node[open]').forEach(function (node) {
|
||||||
|
var openDb = node.getAttribute('data-db') || '';
|
||||||
|
var openTable = node.getAttribute('data-table') || '';
|
||||||
|
if (!openDb || !openTable) return;
|
||||||
|
openTableKeys[openDb + '::' + openTable] = true;
|
||||||
|
});
|
||||||
if (!dbs.length) {
|
if (!dbs.length) {
|
||||||
dbSchemaTreeEl.innerHTML = '<div class="webshell-empty">' + escapeHtml(wsT('webshell.dbNoSchema') || '暂无数据库结构,请先加载') + '</div>';
|
dbSchemaTreeEl.innerHTML = '<div class="webshell-empty">' + escapeHtml(wsT('webshell.dbNoSchema') || '暂无数据库结构,请先加载') + '</div>';
|
||||||
return;
|
return;
|
||||||
@@ -1641,13 +1801,29 @@ function selectWebshell(id, stateReady) {
|
|||||||
var selectedDb = (cfg.selectedDatabase || '').trim();
|
var selectedDb = (cfg.selectedDatabase || '').trim();
|
||||||
var html = '';
|
var html = '';
|
||||||
dbs.forEach(function (dbName) {
|
dbs.forEach(function (dbName) {
|
||||||
var tables = schema[dbName] || [];
|
var tables = (schema[dbName] && schema[dbName].tables) ? schema[dbName].tables : {};
|
||||||
|
var tableNames = Object.keys(tables).sort(function (a, b) { return a.localeCompare(b); });
|
||||||
var isActive = selectedDb && selectedDb === dbName;
|
var isActive = selectedDb && selectedDb === dbName;
|
||||||
html += '<details class="webshell-db-group"' + (isActive ? ' open' : '') + '>';
|
html += '<details class="webshell-db-group"' + (isActive ? ' open' : '') + '>';
|
||||||
html += '<summary class="webshell-db-group-title" data-db="' + escapeHtml(dbName) + '"><span class="webshell-db-icon">🗄</span><span>' + escapeHtml(dbName) + '</span><span class="webshell-db-count">' + tables.length + '</span></summary>';
|
html += '<summary class="webshell-db-group-title" data-db="' + escapeHtml(dbName) + '" title="' + escapeHtml(dbName) + '"><span class="webshell-db-icon">🗄</span><span class="webshell-db-label">' + escapeHtml(dbName) + '</span><span class="webshell-db-count">' + tableNames.length + '</span></summary>';
|
||||||
html += '<div class="webshell-db-group-items">';
|
html += '<div class="webshell-db-group-items">';
|
||||||
tables.forEach(function (tableName) {
|
tableNames.forEach(function (tableName) {
|
||||||
html += '<button type="button" class="webshell-db-table-item" data-db="' + escapeHtml(dbName) + '" data-table="' + escapeHtml(tableName) + '"><span class="webshell-db-icon">📄</span><span>' + escapeHtml(tableName) + '</span></button>';
|
var columns = Array.isArray(tables[tableName]) ? tables[tableName] : [];
|
||||||
|
var columnCountText = columns.length > 0 ? String(columns.length) : '-';
|
||||||
|
var tableKey = dbName + '::' + tableName;
|
||||||
|
var tableOpen = !!openTableKeys[tableKey];
|
||||||
|
html += '<details class="webshell-db-table-node" data-db="' + escapeHtml(dbName) + '" data-table="' + escapeHtml(tableName) + '" data-columns-loaded="' + (columns.length ? '1' : '0') + '"' + (tableOpen ? ' open' : '') + '>';
|
||||||
|
html += '<summary class="webshell-db-table-item" data-db="' + escapeHtml(dbName) + '" data-table="' + escapeHtml(tableName) + '" title="' + escapeHtml(tableName) + '"><span class="webshell-db-icon">📄</span><span class="webshell-db-label">' + escapeHtml(tableName) + '</span><span class="webshell-db-count">' + escapeHtml(columnCountText) + '</span></summary>';
|
||||||
|
if (columns.length) {
|
||||||
|
html += '<div class="webshell-db-column-list">';
|
||||||
|
columns.forEach(function (columnName) {
|
||||||
|
html += '<button type="button" class="webshell-db-column-item" data-db="' + escapeHtml(dbName) + '" data-table="' + escapeHtml(tableName) + '" data-column="' + escapeHtml(columnName) + '" title="' + escapeHtml(columnName) + '"><span class="webshell-db-icon">🧱</span><span class="webshell-db-label">' + escapeHtml(columnName) + '</span></button>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="webshell-db-column-empty">' + escapeHtml(wsT('webshell.dbNoColumns') || '暂无列信息') + '</div>';
|
||||||
|
}
|
||||||
|
html += '</details>';
|
||||||
});
|
});
|
||||||
html += '</div></details>';
|
html += '</div></details>';
|
||||||
});
|
});
|
||||||
@@ -1659,6 +1835,7 @@ function selectWebshell(id, stateReady) {
|
|||||||
cfg.selectedDatabase = el.getAttribute('data-db') || '';
|
cfg.selectedDatabase = el.getAttribute('data-db') || '';
|
||||||
saveWebshellDbConfig(conn, cfg);
|
saveWebshellDbConfig(conn, cfg);
|
||||||
if (dbNameEl && cfg.type !== 'sqlite') dbNameEl.value = cfg.selectedDatabase;
|
if (dbNameEl && cfg.type !== 'sqlite') dbNameEl.value = cfg.selectedDatabase;
|
||||||
|
ensureDbDatabaseColumns(cfg.selectedDatabase);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
dbSchemaTreeEl.querySelectorAll('.webshell-db-table-item').forEach(function (el) {
|
dbSchemaTreeEl.querySelectorAll('.webshell-db-table-item').forEach(function (el) {
|
||||||
@@ -1678,8 +1855,127 @@ function selectWebshell(id, stateReady) {
|
|||||||
dbSqlEl.value = 'SELECT * FROM ' + tableRef + ' ORDER BY 1 DESC LIMIT 20;';
|
dbSqlEl.value = 'SELECT * FROM ' + tableRef + ' ORDER BY 1 DESC LIMIT 20;';
|
||||||
webshellDbCollectConfig(conn);
|
webshellDbCollectConfig(conn);
|
||||||
}
|
}
|
||||||
|
ensureDbTableColumns(dbName, table);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
dbSchemaTreeEl.querySelectorAll('.webshell-db-table-node').forEach(function (node) {
|
||||||
|
node.addEventListener('toggle', function () {
|
||||||
|
if (!node.open) return;
|
||||||
|
var dbName = node.getAttribute('data-db') || '';
|
||||||
|
var table = node.getAttribute('data-table') || '';
|
||||||
|
if (!dbName || !table) return;
|
||||||
|
ensureDbTableColumns(dbName, table);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dbSchemaTreeEl.querySelectorAll('.webshell-db-column-item').forEach(function (el) {
|
||||||
|
el.addEventListener('click', function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
var table = el.getAttribute('data-table') || '';
|
||||||
|
var column = el.getAttribute('data-column') || '';
|
||||||
|
var dbName = el.getAttribute('data-db') || '';
|
||||||
|
if (!table || !column) return;
|
||||||
|
var cfg = webshellDbCollectConfig(conn);
|
||||||
|
cfg.selectedDatabase = dbName;
|
||||||
|
if (cfg.type !== 'sqlite') cfg.database = dbName;
|
||||||
|
saveWebshellDbConfig(conn, cfg);
|
||||||
|
if (dbNameEl && cfg.type !== 'sqlite') dbNameEl.value = dbName;
|
||||||
|
var tableRef = cfg.type === 'sqlite'
|
||||||
|
? webshellDbQuoteIdentifier(cfg.type, table)
|
||||||
|
: webshellDbQuoteIdentifier(cfg.type, dbName) + '.' + webshellDbQuoteIdentifier(cfg.type, table);
|
||||||
|
if (dbSqlEl) {
|
||||||
|
dbSqlEl.value = 'SELECT ' + webshellDbQuoteIdentifier(cfg.type, column) + ' FROM ' + tableRef + ' LIMIT 20;';
|
||||||
|
webshellDbCollectConfig(conn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var autoDb = selectedDb || dbs[0] || '';
|
||||||
|
if (autoDb) ensureDbDatabaseColumns(autoDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDbTableColumns(dbName, tableName) {
|
||||||
|
if (!dbName || !tableName || webshellRunning) return;
|
||||||
|
var loadKey = (conn && conn.id ? conn.id : 'local') + '::' + dbName + '::' + tableName;
|
||||||
|
if (dbColumnsLoading[loadKey]) return;
|
||||||
|
webshellDbCollectConfig(conn);
|
||||||
|
var cfg = getWebshellDbConfig(conn);
|
||||||
|
var schema = normalizeWebshellDbSchema((cfg && cfg.schema && typeof cfg.schema === 'object') ? cfg.schema : {});
|
||||||
|
if (!schema[dbName]) return;
|
||||||
|
if (!schema[dbName].tables[tableName]) return;
|
||||||
|
if (Array.isArray(schema[dbName].tables[tableName]) && schema[dbName].tables[tableName].length > 0) return;
|
||||||
|
|
||||||
|
var built = buildWebshellDbColumnsCommand(cfg, dbName, tableName);
|
||||||
|
if (!built.command) return;
|
||||||
|
dbColumnsLoading[loadKey] = true;
|
||||||
|
webshellRunning = true;
|
||||||
|
setDbActionButtonsDisabled(true);
|
||||||
|
execWebshellCommand(conn, built.command).then(function (out) {
|
||||||
|
var parsed = parseWebshellDbExecOutput(out);
|
||||||
|
var success = parsed.rc === 0 || (parsed.rc == null && parsed.output && !/error|failed|denied|unknown|not found|access/i.test(parsed.output));
|
||||||
|
if (!success) return;
|
||||||
|
var columns = parseWebshellDbColumns(parsed.output);
|
||||||
|
if (!columns.length) return;
|
||||||
|
var nextCfg = getWebshellDbConfig(conn);
|
||||||
|
var nextSchema = normalizeWebshellDbSchema((nextCfg && nextCfg.schema && typeof nextCfg.schema === 'object') ? nextCfg.schema : {});
|
||||||
|
if (!nextSchema[dbName]) nextSchema[dbName] = { tables: {} };
|
||||||
|
if (!nextSchema[dbName].tables[tableName]) nextSchema[dbName].tables[tableName] = [];
|
||||||
|
nextSchema[dbName].tables[tableName] = columns;
|
||||||
|
nextCfg.schema = nextSchema;
|
||||||
|
saveWebshellDbConfig(conn, nextCfg);
|
||||||
|
renderDbSchemaTree();
|
||||||
|
}).catch(function () {
|
||||||
|
// ignore single-table column load errors to avoid interrupting main flow
|
||||||
|
}).finally(function () {
|
||||||
|
delete dbColumnsLoading[loadKey];
|
||||||
|
webshellRunning = false;
|
||||||
|
setDbActionButtonsDisabled(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDbDatabaseColumns(dbName) {
|
||||||
|
if (!dbName || webshellRunning) return;
|
||||||
|
webshellDbCollectConfig(conn);
|
||||||
|
var cfg = getWebshellDbConfig(conn);
|
||||||
|
var schema = normalizeWebshellDbSchema((cfg && cfg.schema && typeof cfg.schema === 'object') ? cfg.schema : {});
|
||||||
|
if (!schema[dbName] || !schema[dbName].tables) return;
|
||||||
|
var hasUnknown = Object.keys(schema[dbName].tables).some(function (tableName) {
|
||||||
|
var cols = schema[dbName].tables[tableName];
|
||||||
|
return !Array.isArray(cols) || cols.length === 0;
|
||||||
|
});
|
||||||
|
if (!hasUnknown) return;
|
||||||
|
|
||||||
|
var batchKey = (conn && conn.id ? conn.id : 'local') + '::' + (cfg.type || 'mysql') + '::' + dbName;
|
||||||
|
if (dbColumnsBatchLoading[batchKey] || dbColumnsBatchLoaded[batchKey]) return;
|
||||||
|
var built = buildWebshellDbColumnsByDatabaseCommand(cfg, dbName);
|
||||||
|
if (!built.command) return;
|
||||||
|
|
||||||
|
dbColumnsBatchLoading[batchKey] = true;
|
||||||
|
webshellRunning = true;
|
||||||
|
setDbActionButtonsDisabled(true);
|
||||||
|
execWebshellCommand(conn, built.command).then(function (out) {
|
||||||
|
var parsed = parseWebshellDbExecOutput(out);
|
||||||
|
var success = parsed.rc === 0 || (parsed.rc == null && parsed.output && !/error|failed|denied|unknown|not found|access/i.test(parsed.output));
|
||||||
|
if (!success) return;
|
||||||
|
var tableColumns = parseWebshellDbTableColumns(parsed.output);
|
||||||
|
if (!Object.keys(tableColumns).length) return;
|
||||||
|
|
||||||
|
var nextCfg = getWebshellDbConfig(conn);
|
||||||
|
var nextSchema = normalizeWebshellDbSchema((nextCfg && nextCfg.schema && typeof nextCfg.schema === 'object') ? nextCfg.schema : {});
|
||||||
|
if (!nextSchema[dbName]) nextSchema[dbName] = { tables: {} };
|
||||||
|
Object.keys(tableColumns).forEach(function (tableName) {
|
||||||
|
nextSchema[dbName].tables[tableName] = tableColumns[tableName];
|
||||||
|
});
|
||||||
|
nextCfg.schema = nextSchema;
|
||||||
|
saveWebshellDbConfig(conn, nextCfg);
|
||||||
|
renderDbSchemaTree();
|
||||||
|
}).catch(function () {
|
||||||
|
// ignore batch column load errors to avoid interrupting main flow
|
||||||
|
}).finally(function () {
|
||||||
|
delete dbColumnsBatchLoading[batchKey];
|
||||||
|
dbColumnsBatchLoaded[batchKey] = true;
|
||||||
|
webshellRunning = false;
|
||||||
|
setDbActionButtonsDisabled(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadDbSchema() {
|
function loadDbSchema() {
|
||||||
@@ -1692,6 +1988,7 @@ function selectWebshell(id, stateReady) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cfg = webshellDbCollectConfig(conn);
|
var cfg = webshellDbCollectConfig(conn);
|
||||||
|
resetDbColumnLoadCache();
|
||||||
var built = buildWebshellDbSchemaCommand(cfg);
|
var built = buildWebshellDbSchemaCommand(cfg);
|
||||||
if (!built.command) {
|
if (!built.command) {
|
||||||
webshellDbSetOutput(built.error || (wsT('webshell.dbSchemaFailed') || '加载数据库结构失败'), true);
|
webshellDbSetOutput(built.error || (wsT('webshell.dbSchemaFailed') || '加载数据库结构失败'), true);
|
||||||
@@ -1794,11 +2091,15 @@ function selectWebshell(id, stateReady) {
|
|||||||
cfg.selectedDatabase = '';
|
cfg.selectedDatabase = '';
|
||||||
cfg.schema = {};
|
cfg.schema = {};
|
||||||
saveWebshellDbConfig(conn, cfg);
|
saveWebshellDbConfig(conn, cfg);
|
||||||
|
resetDbColumnLoadCache();
|
||||||
renderDbSchemaTree();
|
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-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);
|
var el = document.getElementById(id);
|
||||||
if (el) el.addEventListener('change', function () { webshellDbCollectConfig(conn); });
|
if (el) el.addEventListener('change', function () {
|
||||||
|
webshellDbCollectConfig(conn);
|
||||||
|
resetDbColumnLoadCache();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if (dbSqlEl) dbSqlEl.addEventListener('change', function () { webshellDbCollectConfig(conn); });
|
if (dbSqlEl) dbSqlEl.addEventListener('change', function () { webshellDbCollectConfig(conn); });
|
||||||
if (dbRunBtn) dbRunBtn.addEventListener('click', function () { runDbQuery(false); });
|
if (dbRunBtn) dbRunBtn.addEventListener('click', function () { runDbQuery(false); });
|
||||||
|
|||||||
Reference in New Issue
Block a user