2026-01-18 15:46:45 +08:00
|
|
|
|
// 配置管理系统 JavaScript
|
|
|
|
|
|
|
|
|
|
|
|
// 全局变量
|
|
|
|
|
|
let currentConfig = null;
|
|
|
|
|
|
let configList = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后执行
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 添加删除确认模态框的HTML
|
|
|
|
|
|
const deleteConfirmModalHTML = `
|
|
|
|
|
|
<div class="modal" id="deleteConfirmModal">
|
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
|
<h3>删除配置</h3>
|
|
|
|
|
|
<button class="close-btn" id="deleteConfirmCloseBtn">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
|
<p>确定要删除这个配置吗?此操作不可恢复。</p>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
|
<button type="button" class="btn secondary" id="cancelDeleteBtn">取消</button>
|
|
|
|
|
|
<button type="button" class="btn danger" id="confirmDeleteBtn">删除</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
document.body.insertAdjacentHTML('beforeend', deleteConfirmModalHTML);
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
// 绑定事件监听器
|
|
|
|
|
|
bindEventListeners();
|
|
|
|
|
|
|
|
|
|
|
|
// 加载配置列表
|
|
|
|
|
|
loadConfigs();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定事件监听器
|
|
|
|
|
|
function bindEventListeners() {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 配置类型选择
|
|
|
|
|
|
const configTypeSelect = document.getElementById('configTypeSelect');
|
|
|
|
|
|
|
|
|
|
|
|
// 选中下拉框选项后直接显示配置详情界面
|
|
|
|
|
|
configTypeSelect.addEventListener('change', function() {
|
|
|
|
|
|
if (this.value) {
|
|
|
|
|
|
handleAddConfigDirectly();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 关闭添加配置模态框
|
|
|
|
|
|
document.getElementById('addConfigCloseBtn').addEventListener('click', hideAddConfigModal);
|
|
|
|
|
|
|
|
|
|
|
|
// 取消添加配置
|
|
|
|
|
|
document.getElementById('cancelAddBtn').addEventListener('click', hideAddConfigModal);
|
|
|
|
|
|
|
|
|
|
|
|
// 提交添加配置表单
|
|
|
|
|
|
document.getElementById('addConfigForm').addEventListener('submit', handleAddConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 保存配置按钮
|
|
|
|
|
|
document.getElementById('saveBtn').addEventListener('click', saveConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 删除配置按钮
|
|
|
|
|
|
document.getElementById('deleteBtn').addEventListener('click', showDeleteConfirm);
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭删除确认模态框
|
|
|
|
|
|
document.getElementById('deleteConfirmCloseBtn').addEventListener('click', hideDeleteConfirmModal);
|
|
|
|
|
|
|
|
|
|
|
|
// 取消删除
|
|
|
|
|
|
document.getElementById('cancelDeleteBtn').addEventListener('click', hideDeleteConfirmModal);
|
|
|
|
|
|
|
|
|
|
|
|
// 确认删除
|
|
|
|
|
|
document.getElementById('confirmDeleteBtn').addEventListener('click', confirmDeleteConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 点击模态框外部关闭模态框
|
|
|
|
|
|
window.addEventListener('click', function(event) {
|
|
|
|
|
|
if (event.target.classList.contains('modal')) {
|
|
|
|
|
|
event.target.classList.remove('show');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载配置列表
|
|
|
|
|
|
async function loadConfigs() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/folder-configs');
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error('Failed to load configurations');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
configList = data.configurations;
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染配置列表
|
|
|
|
|
|
renderConfigList();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有选择任何配置,显示欢迎界面
|
|
|
|
|
|
if (!currentConfig) {
|
|
|
|
|
|
showWelcomeScreen();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 重新加载当前配置
|
|
|
|
|
|
loadConfigDetails(currentConfig.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error loading configurations:', error);
|
|
|
|
|
|
alert('加载配置失败: ' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染配置列表
|
|
|
|
|
|
function renderConfigList() {
|
|
|
|
|
|
const configListElement = document.getElementById('configList');
|
|
|
|
|
|
configListElement.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (configList.length === 0) {
|
|
|
|
|
|
configListElement.innerHTML = '<div style="text-align: center; padding: 20px; color: #666;">暂无配置</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
configList.forEach(config => {
|
|
|
|
|
|
const configItem = document.createElement('div');
|
|
|
|
|
|
configItem.className = 'config-item';
|
|
|
|
|
|
configItem.dataset.id = config.id;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
configItem.dataset.type = config.type;
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (currentConfig && config.id === currentConfig.id) {
|
|
|
|
|
|
configItem.classList.add('active');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 生成配置描述信息
|
|
|
|
|
|
let configInfo = '';
|
|
|
|
|
|
if (config.type === 'database') {
|
|
|
|
|
|
const dbConfig = config.config;
|
|
|
|
|
|
if (dbConfig.database && dbConfig.table_name) {
|
|
|
|
|
|
configInfo = `${dbConfig.database}.${dbConfig.table_name}`;
|
|
|
|
|
|
} else if (dbConfig.database) {
|
|
|
|
|
|
configInfo = dbConfig.database;
|
|
|
|
|
|
}
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (config.type === 'folder') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const folderConfig = config.config;
|
2026-01-25 21:59:25 +08:00
|
|
|
|
if (folderConfig.host && folderConfig.folder_path) {
|
|
|
|
|
|
configInfo = `${folderConfig.host}:${folderConfig.folder_path}`;
|
|
|
|
|
|
} else if (folderConfig.folder_path) {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
configInfo = folderConfig.folder_path;
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (folderConfig.host) {
|
|
|
|
|
|
configInfo = folderConfig.host;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用配置ID作为标题
|
|
|
|
|
|
const configName = config.id;
|
|
|
|
|
|
|
|
|
|
|
|
// 生成HTML结构
|
2026-01-18 15:46:45 +08:00
|
|
|
|
configItem.innerHTML = `
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<div class="config-item-content">
|
|
|
|
|
|
<div class="config-item-details">
|
|
|
|
|
|
<div class="config-item-title">${configName}</div>
|
|
|
|
|
|
<div class="config-item-info">${configInfo}</div>
|
|
|
|
|
|
<div class="config-item-meta">
|
|
|
|
|
|
<div class="config-item-time">${new Date(config.update_at).toLocaleString()}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
configItem.addEventListener('click', () => loadConfigDetails(config.id));
|
|
|
|
|
|
|
|
|
|
|
|
configListElement.appendChild(configItem);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载配置详情
|
|
|
|
|
|
async function loadConfigDetails(configId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 查找配置
|
2026-01-22 13:04:29 +08:00
|
|
|
|
let config = configList.find(c => c.id === configId);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果配置不存在,可能是刚创建的,尝试从服务器重新获取配置列表
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
|
await loadConfigs();
|
|
|
|
|
|
config = configList.find(c => c.id === configId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果仍然找不到配置,说明真的有问题,直接报错
|
2026-01-18 15:46:45 +08:00
|
|
|
|
if (!config) {
|
|
|
|
|
|
throw new Error('配置不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentConfig = config;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新配置列表选中状态
|
|
|
|
|
|
renderConfigList();
|
|
|
|
|
|
|
|
|
|
|
|
// 显示配置详情界面
|
|
|
|
|
|
showConfigScreen();
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 更新配置标题,使用配置ID(即文件名)
|
2026-01-27 11:20:01 +08:00
|
|
|
|
if (config.id === null) {
|
|
|
|
|
|
document.getElementById('configTitle').textContent = '新增数据源';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
document.getElementById('configTitle').textContent = config.id;
|
|
|
|
|
|
}
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 生成配置表单
|
|
|
|
|
|
generateConfigForm(config.config);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error loading config details:', error);
|
|
|
|
|
|
alert('加载配置详情失败: ' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成配置表单
|
|
|
|
|
|
function generateConfigForm(config) {
|
|
|
|
|
|
const formElement = document.getElementById('configForm');
|
|
|
|
|
|
formElement.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 基本信息部分
|
|
|
|
|
|
const basicSection = document.createElement('div');
|
|
|
|
|
|
basicSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">基本信息</h3>
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<!-- 配置名称自动生成,隐藏输入框 -->
|
|
|
|
|
|
<input type="text" id="formName" value="${config.name || ''}" style="display: none;">
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formType">配置类型</label>
|
|
|
|
|
|
<select id="formType" disabled>
|
|
|
|
|
|
<option value="database" ${config.type === 'database' ? 'selected' : ''}>数据库 (database)</option>
|
2026-01-25 21:59:25 +08:00
|
|
|
|
<option value="folder" ${config.type === 'folder' ? 'selected' : ''}>文件夹 (folder)</option>
|
2026-02-24 00:48:55 +08:00
|
|
|
|
<option value="git" ${config.type === 'git' ? 'selected' : ''}>Git代码库 (git)</option>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
formElement.appendChild(basicSection);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据配置类型生成相应的表单字段
|
2026-01-25 21:59:25 +08:00
|
|
|
|
if (config.type === 'folder') {
|
2026-01-18 15:46:45 +08:00
|
|
|
|
const folderSection = document.createElement('div');
|
|
|
|
|
|
folderSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">文件夹配置</h3>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formFolderPath">文件夹路径 <span class="required">*</span></label>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<input type="text" id="formFolderPath" value="${config.folder_path || ''}" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group checkbox">
|
|
|
|
|
|
<input type="checkbox" id="formRecursive" ${config.recursive ? 'checked' : ''}>
|
|
|
|
|
|
<label for="formRecursive">递归扫描子文件夹</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formIgnorePatterns">忽略的文件模式 (用逗号分隔,示例:*.log, *.tmp, node_modules/)</label>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<input type="text" id="formIgnorePatterns" value="${config.ignore_patterns ? config.ignore_patterns.join(', ') : ''}">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<div style="margin-top: 5px; font-size: 0.85em; color: #666;">
|
|
|
|
|
|
示例:*.log, *.tmp, node_modules/
|
|
|
|
|
|
</div>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
formElement.appendChild(folderSection);
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 21:59:25 +08:00
|
|
|
|
// 为folder添加SSH连接配置
|
|
|
|
|
|
if (config.type === 'folder') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const sshSection = document.createElement('div');
|
|
|
|
|
|
sshSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">SSH连接配置</h3>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formHost">主机地址 <span class="required">*</span></label>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<input type="text" id="formHost" value="${config.host || ''}" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formPort">端口 <span class="required">*</span></label>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<input type="number" id="formPort" value="${config.port || 22}" min="1" max="65535" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formUsername">用户名 <span class="required">*</span></label>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<input type="text" id="formUsername" value="${config.username || ''}" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formPassword">密码 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="password" id="formPassword" value="${config.password || ''}" required>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
2026-01-26 15:09:12 +08:00
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<button type="button" id="testSshConnectionBtn" class="btn secondary" style="margin-top: 10px;">🔌 测试SSH连接</button>
|
|
|
|
|
|
</div>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
`;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
formElement.appendChild(sshSection);
|
|
|
|
|
|
|
2026-01-26 15:09:12 +08:00
|
|
|
|
// 绑定事件
|
|
|
|
|
|
bindFolderEvents();
|
2026-01-18 15:46:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:48:55 +08:00
|
|
|
|
// Git代码库配置
|
|
|
|
|
|
if (config.type === 'git') {
|
|
|
|
|
|
const gitSection = document.createElement('div');
|
|
|
|
|
|
gitSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">Git代码库配置</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formGitUrl">Git仓库地址 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="text" id="formGitUrl" value="${config.git_url || ''}" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formBranch">分支名称</label>
|
|
|
|
|
|
<input type="text" id="formBranch" value="${config.branch || 'main'}">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formProtocol">协议类型</label>
|
|
|
|
|
|
<select id="formProtocol">
|
|
|
|
|
|
<option value="https" ${config.protocol === 'https' ? 'selected' : ''}>HTTPS</option>
|
|
|
|
|
|
<option value="ssh" ${config.protocol === 'ssh' ? 'selected' : ''}>SSH</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group" id="httpsTokenGroup">
|
|
|
|
|
|
<label for="formHttpsToken">HTTPS令牌</label>
|
|
|
|
|
|
<input type="password" id="formHttpsToken" value="${config.https_token || ''}">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group" id="sshKeyGroup" style="display: none;">
|
|
|
|
|
|
<label for="formSshKey">SSH私钥</label>
|
|
|
|
|
|
<textarea id="formSshKey" rows="10" value="${config.ssh_key || ''}">${config.ssh_key || ''}</textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formPollInterval">轮询间隔(秒)</label>
|
|
|
|
|
|
<input type="number" id="formPollInterval" value="${config.poll_interval || 300}" min="60" max="3600">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<button type="button" id="testGitConnectionBtn" class="btn secondary" style="margin-top: 10px;">🔌 测试Git连接</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
formElement.appendChild(gitSection);
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定事件
|
|
|
|
|
|
bindGitEvents();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
if (config.type === 'database') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 数据库连接配置(放在前面,方便先测试连接)
|
|
|
|
|
|
const connectionSection = document.createElement('div');
|
|
|
|
|
|
connectionSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">数据库连接配置</h3>
|
2026-02-10 16:08:46 +08:00
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formDbType">数据库类型 <span class="required">*</span></label>
|
|
|
|
|
|
<select id="formDbType" required>
|
|
|
|
|
|
<option value="mysql" ${config.db_type === 'mysql' ? 'selected' : config.db_type === undefined ? 'selected' : ''}>MySQL</option>
|
|
|
|
|
|
<option value="dameng" ${config.db_type === 'dameng' ? 'selected' : ''}>达梦数据库</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formMysqlHost">主机地址 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="text" id="formMysqlHost" value="${config.mysql_host || ''}" required>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formMysqlPort">端口 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="number" id="formMysqlPort" value="${config.mysql_port || 3306}" min="1" max="65535" required>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formMysqlUser">用户名 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="text" id="formMysqlUser" value="${config.mysql_user || ''}" required>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formMysqlPassword">密码 <span class="required">*</span></label>
|
|
|
|
|
|
<input type="password" id="formMysqlPassword" value="${config.mysql_password || ''}" required>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<button type="button" id="testConnectionBtn" class="btn primary" style="margin-right: 10px;">🔌 测试连接</button>
|
|
|
|
|
|
<button type="button" id="getDatabasesBtn" class="btn primary">📋 获取数据库列表</button>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
formElement.appendChild(connectionSection);
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const databaseSection = document.createElement('div');
|
|
|
|
|
|
databaseSection.innerHTML = `
|
|
|
|
|
|
<h3 class="section-title">数据库配置</h3>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formDatabase">数据库名称 <span class="required">*</span></label>
|
|
|
|
|
|
<select id="formDatabase" required>
|
|
|
|
|
|
<option value="">-- 选择数据库 --</option>
|
|
|
|
|
|
</select>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<button type="button" id="getTablesBtn" class="btn primary">📋 获取表列表</button>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formTableName">表名 <span class="required">*</span></label>
|
|
|
|
|
|
<select id="formTableName" required>
|
|
|
|
|
|
<option value="">-- 选择表 --</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<button type="button" id="getTableStructureBtn" class="btn primary">📋 获取表结构</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formIdColumn">ID列 <span class="required">*</span></label>
|
|
|
|
|
|
<select id="formIdColumn" required>
|
|
|
|
|
|
<option value="">-- 选择ID列 --</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>内容列(可多选) <span class="required">*</span></label>
|
|
|
|
|
|
<div id="formContentColumn" class="checkbox-group"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label for="formTitleColumn">标题列(可选)</label>
|
|
|
|
|
|
<select id="formTitleColumn">
|
|
|
|
|
|
<option value="">-- 选择标题列(可选)--</option>
|
|
|
|
|
|
</select>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
2026-01-22 13:04:29 +08:00
|
|
|
|
<label for="formUpdatedAtColumn">更新时间列(可选)</label>
|
|
|
|
|
|
<select id="formUpdatedAtColumn">
|
|
|
|
|
|
<option value="">-- 选择更新时间列(可选)--</option>
|
|
|
|
|
|
</select>
|
2026-01-18 15:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
formElement.appendChild(databaseSection);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置初始值
|
|
|
|
|
|
const formDatabase = document.getElementById('formDatabase');
|
|
|
|
|
|
const formTableName = document.getElementById('formTableName');
|
|
|
|
|
|
|
|
|
|
|
|
if (config.database) {
|
|
|
|
|
|
// 添加已保存的数据库名称作为选项
|
|
|
|
|
|
const dbOption = document.createElement('option');
|
|
|
|
|
|
dbOption.value = config.database;
|
|
|
|
|
|
dbOption.textContent = config.database;
|
|
|
|
|
|
dbOption.selected = true;
|
|
|
|
|
|
formDatabase.appendChild(dbOption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.table_name) {
|
|
|
|
|
|
// 添加已保存的表名作为选项
|
|
|
|
|
|
const tableOption = document.createElement('option');
|
|
|
|
|
|
tableOption.value = config.table_name;
|
|
|
|
|
|
tableOption.textContent = config.table_name;
|
|
|
|
|
|
tableOption.selected = true;
|
|
|
|
|
|
formTableName.appendChild(tableOption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有完整的数据库配置,尝试获取实际的数据表结构
|
|
|
|
|
|
if (config.database && config.table_name) {
|
|
|
|
|
|
// 尝试从后端获取实际的数据表结构
|
|
|
|
|
|
fetchTableStructure(config).then(columns => {
|
|
|
|
|
|
if (columns && columns.length > 0) {
|
|
|
|
|
|
// 更新列选择器,显示所有列
|
|
|
|
|
|
updateColumnSelectors(columns);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保所有选择器都有正确的值
|
|
|
|
|
|
if (config.id_column) {
|
|
|
|
|
|
document.getElementById('formIdColumn').value = config.id_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (config.title_column) {
|
|
|
|
|
|
document.getElementById('formTitleColumn').value = config.title_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理内容列的选中状态(复选框组)
|
|
|
|
|
|
const contentColumns = config.content_column ? config.content_column.split(',') : [];
|
|
|
|
|
|
if (contentColumns.length > 0) {
|
|
|
|
|
|
const contentGroup = document.getElementById('formContentColumn');
|
|
|
|
|
|
const checkboxes = contentGroup.querySelectorAll('input[name="contentColumn"]');
|
|
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
|
|
|
|
if (contentColumns.includes(checkbox.value)) {
|
|
|
|
|
|
checkbox.checked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
console.error('获取表结构失败:', error);
|
|
|
|
|
|
// 如果获取失败,继续使用原有的模拟列列表
|
|
|
|
|
|
const mockColumns = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 添加ID列
|
|
|
|
|
|
mockColumns.push({ name: config.id_column || '', type: '', key: 'PRI' });
|
|
|
|
|
|
|
|
|
|
|
|
// 添加内容列
|
|
|
|
|
|
const contentColumns = config.content_column ? config.content_column.split(',') : [];
|
|
|
|
|
|
contentColumns.forEach(colName => {
|
|
|
|
|
|
if (colName !== config.id_column) {
|
|
|
|
|
|
mockColumns.push({ name: colName, type: '', key: '' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加标题列
|
|
|
|
|
|
if (config.title_column && config.title_column !== config.id_column && !contentColumns.includes(config.title_column)) {
|
|
|
|
|
|
mockColumns.push({ name: config.title_column, type: '', key: '' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新列选择器
|
|
|
|
|
|
updateColumnSelectors(mockColumns);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保所有选择器都有正确的值
|
|
|
|
|
|
if (config.id_column) {
|
|
|
|
|
|
document.getElementById('formIdColumn').value = config.id_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (config.title_column) {
|
|
|
|
|
|
document.getElementById('formTitleColumn').value = config.title_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理内容列的选中状态(复选框组)
|
|
|
|
|
|
const contentColumns = config.content_column ? config.content_column.split(',') : [];
|
|
|
|
|
|
if (contentColumns.length > 0) {
|
|
|
|
|
|
const contentGroup = document.getElementById('formContentColumn');
|
|
|
|
|
|
const checkboxes = contentGroup.querySelectorAll('input[name="contentColumn"]');
|
|
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
|
|
|
|
if (contentColumns.includes(checkbox.value)) {
|
|
|
|
|
|
checkbox.checked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定事件
|
|
|
|
|
|
bindDatabaseEvents();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定认证方式选择的事件
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库事件绑定函数
|
|
|
|
|
|
function bindDatabaseEvents() {
|
|
|
|
|
|
// 测试数据库连接
|
|
|
|
|
|
document.getElementById('testConnectionBtn')?.addEventListener('click', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const host = document.getElementById('formMysqlHost').value;
|
|
|
|
|
|
const port = parseInt(document.getElementById('formMysqlPort').value);
|
|
|
|
|
|
const username = document.getElementById('formMysqlUser').value;
|
|
|
|
|
|
const password = document.getElementById('formMysqlPassword').value;
|
2026-02-10 16:08:46 +08:00
|
|
|
|
const dbType = document.getElementById('formDbType').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (!host || !username) {
|
|
|
|
|
|
alert('请填写主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SSH隧道配置
|
|
|
|
|
|
const useSshTunnel = document.getElementById('useSshTunnel')?.checked || false;
|
|
|
|
|
|
let sshConfig = {};
|
|
|
|
|
|
if (useSshTunnel) {
|
|
|
|
|
|
const sshHost = document.getElementById('formSshHost').value;
|
|
|
|
|
|
const sshPort = parseInt(document.getElementById('formSshPort').value || 22);
|
|
|
|
|
|
const sshUsername = document.getElementById('formSshUsername').value;
|
|
|
|
|
|
const sshPassword = document.getElementById('formSshPassword').value;
|
|
|
|
|
|
const sshKeyPath = document.getElementById('formSshKeyPathDisplay').value;
|
|
|
|
|
|
const sshKeyContent = document.getElementById('sshPrivateKeyContent')?.value || '';
|
|
|
|
|
|
|
|
|
|
|
|
if (!sshHost || !sshUsername) {
|
|
|
|
|
|
alert('请填写SSH主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sshConfig = {
|
|
|
|
|
|
use_ssh_tunnel: true,
|
|
|
|
|
|
ssh_host: sshHost,
|
|
|
|
|
|
ssh_port: sshPort,
|
|
|
|
|
|
ssh_username: sshUsername,
|
|
|
|
|
|
ssh_password: sshPassword,
|
|
|
|
|
|
ssh_key_path: sshKeyPath,
|
|
|
|
|
|
ssh_key_content: sshKeyContent
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('testConnectionBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '🔌 测试中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/database/databases', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host,
|
|
|
|
|
|
port,
|
|
|
|
|
|
username,
|
|
|
|
|
|
password,
|
2026-02-10 16:08:46 +08:00
|
|
|
|
db_type: dbType,
|
2026-01-22 13:04:29 +08:00
|
|
|
|
...sshConfig
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
|
alert('数据库连接成功!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '连接失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('数据库连接失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('testConnectionBtn');
|
|
|
|
|
|
btn.textContent = '🔌 测试连接';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取数据库列表
|
|
|
|
|
|
document.getElementById('getDatabasesBtn')?.addEventListener('click', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const host = document.getElementById('formMysqlHost').value;
|
|
|
|
|
|
const port = parseInt(document.getElementById('formMysqlPort').value);
|
|
|
|
|
|
const username = document.getElementById('formMysqlUser').value;
|
|
|
|
|
|
const password = document.getElementById('formMysqlPassword').value;
|
2026-02-10 16:08:46 +08:00
|
|
|
|
const dbType = document.getElementById('formDbType').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (!host || !username) {
|
|
|
|
|
|
alert('请填写主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSH隧道配置
|
|
|
|
|
|
const useSshTunnel = document.getElementById('useSshTunnel')?.checked || false;
|
|
|
|
|
|
let sshConfig = {};
|
|
|
|
|
|
if (useSshTunnel) {
|
|
|
|
|
|
const sshHost = document.getElementById('formSshHost').value;
|
|
|
|
|
|
const sshPort = parseInt(document.getElementById('formSshPort').value || 22);
|
|
|
|
|
|
const sshUsername = document.getElementById('formSshUsername').value;
|
|
|
|
|
|
const sshPassword = document.getElementById('formSshPassword').value;
|
|
|
|
|
|
const sshKeyPath = document.getElementById('formSshKeyPathDisplay').value;
|
|
|
|
|
|
const sshKeyContent = document.getElementById('sshPrivateKeyContent')?.value || '';
|
|
|
|
|
|
|
|
|
|
|
|
if (!sshHost || !sshUsername) {
|
|
|
|
|
|
alert('请填写SSH主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sshConfig = {
|
|
|
|
|
|
use_ssh_tunnel: true,
|
|
|
|
|
|
ssh_host: sshHost,
|
|
|
|
|
|
ssh_port: sshPort,
|
|
|
|
|
|
ssh_username: sshUsername,
|
|
|
|
|
|
ssh_password: sshPassword,
|
|
|
|
|
|
ssh_key_path: sshKeyPath,
|
|
|
|
|
|
ssh_key_content: sshKeyContent
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('getDatabasesBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '📋 获取中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/database/databases', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host,
|
|
|
|
|
|
port,
|
|
|
|
|
|
username,
|
|
|
|
|
|
password,
|
2026-02-10 16:08:46 +08:00
|
|
|
|
db_type: dbType,
|
2026-01-22 13:04:29 +08:00
|
|
|
|
...sshConfig
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '获取失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
const databases = data.databases;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新下拉列表
|
|
|
|
|
|
const select = document.getElementById('formDatabase');
|
|
|
|
|
|
select.innerHTML = '<option value="">-- 选择数据库 --</option>';
|
|
|
|
|
|
|
|
|
|
|
|
databases.forEach(db => {
|
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
|
option.value = db;
|
|
|
|
|
|
option.textContent = db;
|
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
alert(`成功获取 ${databases.length} 个数据库`);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('获取数据库列表失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('getDatabasesBtn');
|
|
|
|
|
|
btn.textContent = '📋 获取数据库列表';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取表列表
|
|
|
|
|
|
document.getElementById('getTablesBtn')?.addEventListener('click', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const host = document.getElementById('formMysqlHost').value;
|
|
|
|
|
|
const port = parseInt(document.getElementById('formMysqlPort').value);
|
|
|
|
|
|
const username = document.getElementById('formMysqlUser').value;
|
|
|
|
|
|
const password = document.getElementById('formMysqlPassword').value;
|
|
|
|
|
|
const database = document.getElementById('formDatabase').value;
|
2026-02-10 16:08:46 +08:00
|
|
|
|
const dbType = document.getElementById('formDbType').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (!host || !username || !database) {
|
|
|
|
|
|
alert('请填写完整的数据库连接信息并选择数据库');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSH隧道配置
|
|
|
|
|
|
const useSshTunnel = document.getElementById('useSshTunnel')?.checked || false;
|
|
|
|
|
|
let sshConfig = {};
|
|
|
|
|
|
if (useSshTunnel) {
|
|
|
|
|
|
const sshHost = document.getElementById('formSshHost').value;
|
|
|
|
|
|
const sshPort = parseInt(document.getElementById('formSshPort').value || 22);
|
|
|
|
|
|
const sshUsername = document.getElementById('formSshUsername').value;
|
|
|
|
|
|
const sshPassword = document.getElementById('formSshPassword').value;
|
|
|
|
|
|
const sshKeyPath = document.getElementById('formSshKeyPathDisplay').value;
|
|
|
|
|
|
const sshKeyContent = document.getElementById('sshPrivateKeyContent')?.value || '';
|
|
|
|
|
|
|
|
|
|
|
|
if (!sshHost || !sshUsername) {
|
|
|
|
|
|
alert('请填写SSH主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sshConfig = {
|
|
|
|
|
|
use_ssh_tunnel: true,
|
|
|
|
|
|
ssh_host: sshHost,
|
|
|
|
|
|
ssh_port: sshPort,
|
|
|
|
|
|
ssh_username: sshUsername,
|
|
|
|
|
|
ssh_password: sshPassword,
|
|
|
|
|
|
ssh_key_path: sshKeyPath,
|
|
|
|
|
|
ssh_key_content: sshKeyContent
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('getTablesBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '📋 获取中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/database/tables', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host,
|
|
|
|
|
|
port,
|
|
|
|
|
|
username,
|
|
|
|
|
|
password,
|
|
|
|
|
|
database,
|
2026-02-10 16:08:46 +08:00
|
|
|
|
db_type: dbType,
|
2026-01-22 13:04:29 +08:00
|
|
|
|
...sshConfig
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '获取失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
const tables = data.tables;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新下拉列表
|
|
|
|
|
|
const select = document.getElementById('formTableName');
|
|
|
|
|
|
select.innerHTML = '<option value="">-- 选择表 --</option>';
|
|
|
|
|
|
|
|
|
|
|
|
tables.forEach(table => {
|
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
|
option.value = table;
|
|
|
|
|
|
option.textContent = table;
|
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
alert(`成功获取 ${tables.length} 个表`);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('获取表列表失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('getTablesBtn');
|
|
|
|
|
|
btn.textContent = '📋 获取表列表';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取表结构
|
|
|
|
|
|
document.getElementById('getTableStructureBtn')?.addEventListener('click', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const host = document.getElementById('formMysqlHost').value;
|
|
|
|
|
|
const port = parseInt(document.getElementById('formMysqlPort').value);
|
|
|
|
|
|
const username = document.getElementById('formMysqlUser').value;
|
|
|
|
|
|
const password = document.getElementById('formMysqlPassword').value;
|
|
|
|
|
|
const database = document.getElementById('formDatabase').value;
|
|
|
|
|
|
const table_name = document.getElementById('formTableName').value;
|
2026-02-10 16:08:46 +08:00
|
|
|
|
const dbType = document.getElementById('formDbType').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (!host || !username || !database || !table_name) {
|
|
|
|
|
|
alert('请填写完整的数据库连接信息并选择数据库和表');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SSH隧道配置
|
|
|
|
|
|
const useSshTunnel = document.getElementById('useSshTunnel')?.checked || false;
|
|
|
|
|
|
let sshConfig = {};
|
|
|
|
|
|
if (useSshTunnel) {
|
|
|
|
|
|
const sshHost = document.getElementById('formSshHost').value;
|
|
|
|
|
|
const sshPort = parseInt(document.getElementById('formSshPort').value || 22);
|
|
|
|
|
|
const sshUsername = document.getElementById('formSshUsername').value;
|
|
|
|
|
|
const sshPassword = document.getElementById('formSshPassword').value;
|
|
|
|
|
|
const sshKeyPath = document.getElementById('formSshKeyPathDisplay').value;
|
|
|
|
|
|
const sshKeyContent = document.getElementById('sshPrivateKeyContent')?.value || '';
|
|
|
|
|
|
|
|
|
|
|
|
if (!sshHost || !sshUsername) {
|
|
|
|
|
|
alert('请填写SSH主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sshConfig = {
|
|
|
|
|
|
use_ssh_tunnel: true,
|
|
|
|
|
|
ssh_host: sshHost,
|
|
|
|
|
|
ssh_port: sshPort,
|
|
|
|
|
|
ssh_username: sshUsername,
|
|
|
|
|
|
ssh_password: sshPassword,
|
|
|
|
|
|
ssh_key_path: sshKeyPath,
|
|
|
|
|
|
ssh_key_content: sshKeyContent
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('getTableStructureBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '📋 获取中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/database/table-structure', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host,
|
|
|
|
|
|
port,
|
|
|
|
|
|
username,
|
|
|
|
|
|
password,
|
|
|
|
|
|
database,
|
|
|
|
|
|
table_name,
|
2026-02-10 16:08:46 +08:00
|
|
|
|
db_type: dbType,
|
2026-01-22 13:04:29 +08:00
|
|
|
|
...sshConfig
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '获取失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
const columns = data.columns;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新所有列选择器
|
|
|
|
|
|
updateColumnSelectors(columns);
|
|
|
|
|
|
|
|
|
|
|
|
alert(`成功获取表结构,共 ${columns.length} 列`);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('获取表结构失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('getTableStructureBtn');
|
|
|
|
|
|
btn.textContent = '📋 获取表结构';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 自动填充已有的列信息
|
|
|
|
|
|
const fillExistingColumns = () => {
|
|
|
|
|
|
const database = document.getElementById('formDatabase').value;
|
|
|
|
|
|
const table_name = document.getElementById('formTableName').value;
|
|
|
|
|
|
|
|
|
|
|
|
if (database && table_name && currentConfig) {
|
|
|
|
|
|
// 如果有当前配置,尝试获取表结构
|
|
|
|
|
|
document.getElementById('getTableStructureBtn')?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 监听数据库和表选择变化
|
|
|
|
|
|
document.getElementById('formDatabase')?.addEventListener('change', async () => {
|
|
|
|
|
|
// 清除表选择
|
|
|
|
|
|
document.getElementById('formTableName').innerHTML = '<option value="">-- 选择表 --</option>';
|
|
|
|
|
|
// 清除列选择
|
|
|
|
|
|
clearColumnSelectors();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('formTableName')?.addEventListener('change', fillExistingColumns);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新列选择器
|
|
|
|
|
|
function updateColumnSelectors(columns) {
|
|
|
|
|
|
// ID列选择器
|
|
|
|
|
|
const idSelect = document.getElementById('formIdColumn');
|
|
|
|
|
|
idSelect.innerHTML = '<option value="">-- 选择ID列 --</option>';
|
|
|
|
|
|
|
|
|
|
|
|
// 内容列选择器(改为复选框组)
|
|
|
|
|
|
const contentGroup = document.getElementById('formContentColumn');
|
|
|
|
|
|
contentGroup.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 标题列选择器
|
|
|
|
|
|
const titleSelect = document.getElementById('formTitleColumn');
|
|
|
|
|
|
titleSelect.innerHTML = '<option value="">-- 选择标题列(可选)--</option>';
|
|
|
|
|
|
|
|
|
|
|
|
// 更新时间列选择器
|
|
|
|
|
|
const updatedAtSelect = document.getElementById('formUpdatedAtColumn');
|
|
|
|
|
|
updatedAtSelect.innerHTML = '<option value="">-- 选择更新时间列(可选)--</option>';
|
|
|
|
|
|
|
|
|
|
|
|
// 文件列选择器 - 只在元素存在时操作(database类型配置中已删除)
|
|
|
|
|
|
const fileSelect = document.getElementById('formFileColumn');
|
|
|
|
|
|
|
|
|
|
|
|
// 填充选项
|
|
|
|
|
|
columns.forEach(col => {
|
|
|
|
|
|
const colName = col.name;
|
|
|
|
|
|
const colType = col.type;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到ID列选择器(优先选择主键)
|
|
|
|
|
|
const idOption = document.createElement('option');
|
|
|
|
|
|
idOption.value = colName;
|
|
|
|
|
|
idOption.textContent = colName + (col.key === 'PRI' ? ' (主键)' : '');
|
|
|
|
|
|
idSelect.appendChild(idOption);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是主键,默认选中
|
|
|
|
|
|
if (col.key === 'PRI') {
|
|
|
|
|
|
idSelect.value = colName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到内容列选择器(复选框组)
|
|
|
|
|
|
const checkboxItem = document.createElement('label');
|
|
|
|
|
|
// 类型为空时不显示类型信息
|
|
|
|
|
|
const displayText = colType ? `${colName} (${colType})` : colName;
|
|
|
|
|
|
checkboxItem.innerHTML = `
|
|
|
|
|
|
<input type="checkbox" name="contentColumn" value="${colName}">
|
|
|
|
|
|
<span>${displayText}</span>
|
|
|
|
|
|
`;
|
|
|
|
|
|
contentGroup.appendChild(checkboxItem);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到文件列选择器 - 只在元素存在时操作(database类型配置中已删除)
|
|
|
|
|
|
if (fileSelect) {
|
|
|
|
|
|
const fileOption = document.createElement('option');
|
|
|
|
|
|
fileOption.value = colName;
|
|
|
|
|
|
fileOption.textContent = colType ? `${colName} (${colType})` : colName;
|
|
|
|
|
|
fileSelect.appendChild(fileOption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到标题列选择器
|
|
|
|
|
|
const titleOption = document.createElement('option');
|
|
|
|
|
|
titleOption.value = colName;
|
|
|
|
|
|
titleOption.textContent = colType ? `${colName} (${colType})` : colName;
|
|
|
|
|
|
titleSelect.appendChild(titleOption);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到更新时间列选择器
|
|
|
|
|
|
const updatedAtOption = document.createElement('option');
|
|
|
|
|
|
updatedAtOption.value = colName;
|
|
|
|
|
|
updatedAtOption.textContent = colType ? `${colName} (${colType})` : colName;
|
|
|
|
|
|
updatedAtSelect.appendChild(updatedAtOption);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有当前配置,保持选中状态
|
|
|
|
|
|
if (currentConfig) {
|
|
|
|
|
|
const config = currentConfig.config;
|
|
|
|
|
|
if (config.id_column) {
|
|
|
|
|
|
idSelect.value = config.id_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理内容列的选中状态(复选框组)
|
|
|
|
|
|
const contentColumns = config.content_column ? config.content_column.split(',') : [];
|
|
|
|
|
|
if (contentColumns.length > 0) {
|
|
|
|
|
|
const checkboxes = contentGroup.querySelectorAll('input[name="contentColumn"]');
|
|
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
|
|
|
|
if (contentColumns.includes(checkbox.value)) {
|
|
|
|
|
|
checkbox.checked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.title_column) {
|
|
|
|
|
|
titleSelect.value = config.title_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (config.updated_at_column) {
|
|
|
|
|
|
updatedAtSelect.value = config.updated_at_column;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除列选择器
|
|
|
|
|
|
function clearColumnSelectors() {
|
|
|
|
|
|
document.getElementById('formIdColumn').innerHTML = '<option value="">-- 选择ID列 --</option>';
|
|
|
|
|
|
document.getElementById('formContentColumn').innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 文件列选择器 - 只在元素存在时操作(database类型配置中已删除)
|
|
|
|
|
|
const fileSelect = document.getElementById('formFileColumn');
|
|
|
|
|
|
if (fileSelect) {
|
|
|
|
|
|
fileSelect.innerHTML = '<option value="">-- 选择文件列(可选)--</option>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('formTitleColumn').innerHTML = '<option value="">-- 选择标题列(可选)--</option>';
|
|
|
|
|
|
document.getElementById('formUpdatedAtColumn').innerHTML = '<option value="">-- 选择更新时间列(可选)--</option>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取数据表结构
|
|
|
|
|
|
async function fetchTableStructure(config) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 确保有必要的连接信息
|
|
|
|
|
|
if (!config.mysql_host || !config.mysql_user) {
|
|
|
|
|
|
throw new Error('缺少数据库连接信息');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求获取表结构
|
|
|
|
|
|
const response = await fetch('/database/table-structure', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host: config.mysql_host,
|
|
|
|
|
|
port: config.mysql_port || 3306,
|
|
|
|
|
|
username: config.mysql_user,
|
|
|
|
|
|
password: config.mysql_password || '',
|
|
|
|
|
|
database: config.database,
|
2026-02-10 16:08:46 +08:00
|
|
|
|
table_name: config.table_name,
|
|
|
|
|
|
db_type: config.db_type || 'mysql'
|
2026-01-22 13:04:29 +08:00
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '获取表结构失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
return data.columns;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取表结构失败:', error);
|
|
|
|
|
|
throw error;
|
2026-01-18 15:46:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示欢迎界面
|
|
|
|
|
|
function showWelcomeScreen() {
|
|
|
|
|
|
document.getElementById('welcomeScreen').style.display = 'flex';
|
|
|
|
|
|
document.getElementById('configScreen').style.display = 'none';
|
|
|
|
|
|
currentConfig = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示配置详情界面
|
|
|
|
|
|
function showConfigScreen() {
|
|
|
|
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
|
|
|
|
document.getElementById('configScreen').style.display = 'flex';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示添加配置模态框
|
|
|
|
|
|
function showAddConfigModal() {
|
|
|
|
|
|
document.getElementById('addConfigModal').classList.add('show');
|
2026-02-10 16:08:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加配置类型选择的事件监听器
|
|
|
|
|
|
const configTypeSelect = document.getElementById('configType');
|
|
|
|
|
|
const dbTypeGroup = document.getElementById('dbTypeGroup');
|
|
|
|
|
|
|
|
|
|
|
|
configTypeSelect.addEventListener('change', function() {
|
|
|
|
|
|
if (this.value === 'database') {
|
|
|
|
|
|
dbTypeGroup.style.display = 'block';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
dbTypeGroup.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 触发一次change事件,确保初始状态正确
|
|
|
|
|
|
configTypeSelect.dispatchEvent(new Event('change'));
|
2026-01-18 15:46:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏添加配置模态框
|
|
|
|
|
|
function hideAddConfigModal() {
|
|
|
|
|
|
document.getElementById('addConfigModal').classList.remove('show');
|
|
|
|
|
|
document.getElementById('addConfigForm').reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 直接创建配置(根据下拉框选择的类型)
|
|
|
|
|
|
async function handleAddConfigDirectly() {
|
|
|
|
|
|
// 获取选择的配置类型
|
|
|
|
|
|
const configTypeSelect = document.getElementById('configTypeSelect');
|
|
|
|
|
|
const configType = configTypeSelect.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否选择了类型
|
|
|
|
|
|
if (!configType) {
|
|
|
|
|
|
alert('请选择配置类型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据配置类型创建一个临时的配置对象
|
|
|
|
|
|
let tempConfig;
|
|
|
|
|
|
|
|
|
|
|
|
if (configType === 'database') {
|
|
|
|
|
|
tempConfig = {
|
|
|
|
|
|
type: configType,
|
|
|
|
|
|
mysql_host: '',
|
|
|
|
|
|
mysql_port: 3306,
|
|
|
|
|
|
mysql_password: '',
|
|
|
|
|
|
database: '',
|
|
|
|
|
|
table_name: '',
|
|
|
|
|
|
id_column: '',
|
2026-02-10 16:08:46 +08:00
|
|
|
|
content_column: '',
|
|
|
|
|
|
db_type: 'mysql'
|
2026-01-22 13:04:29 +08:00
|
|
|
|
};
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (configType === 'folder') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
tempConfig = {
|
|
|
|
|
|
type: configType,
|
|
|
|
|
|
folder_path: '',
|
|
|
|
|
|
recursive: true,
|
|
|
|
|
|
ignore_patterns: [],
|
|
|
|
|
|
host: '',
|
|
|
|
|
|
port: 22,
|
|
|
|
|
|
username: '',
|
|
|
|
|
|
password: ''
|
|
|
|
|
|
};
|
2026-02-24 00:48:55 +08:00
|
|
|
|
} else if (configType === 'git') {
|
|
|
|
|
|
tempConfig = {
|
|
|
|
|
|
type: configType,
|
|
|
|
|
|
git_url: '',
|
|
|
|
|
|
branch: 'main',
|
|
|
|
|
|
protocol: 'https',
|
|
|
|
|
|
https_token: '',
|
|
|
|
|
|
ssh_key: '',
|
|
|
|
|
|
poll_interval: 300
|
|
|
|
|
|
};
|
2026-01-22 13:04:29 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
alert('不支持的配置类型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接设置当前配置,不立即发送到后端
|
|
|
|
|
|
currentConfig = {
|
|
|
|
|
|
id: null, // 临时ID,保存时由后端生成
|
|
|
|
|
|
config: tempConfig
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染配置表单
|
|
|
|
|
|
generateConfigForm(tempConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 切换到配置详情界面
|
|
|
|
|
|
showConfigScreen();
|
|
|
|
|
|
|
2026-01-27 11:20:01 +08:00
|
|
|
|
// 更新配置标题为"新增数据源"
|
|
|
|
|
|
document.getElementById('configTitle').textContent = '新增数据源';
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 重置类型选择
|
|
|
|
|
|
configTypeSelect.value = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 提示用户需要填写配置信息
|
|
|
|
|
|
alert('请填写配置信息,然后点击保存按钮创建配置');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理添加配置(旧的模态框方式,保留以便兼容)
|
2026-01-18 15:46:45 +08:00
|
|
|
|
async function handleAddConfig(event) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取表单数据
|
|
|
|
|
|
const formData = new FormData(event.target);
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const configType = formData.get('type');
|
|
|
|
|
|
const configName = formData.get('name');
|
2026-02-10 16:08:46 +08:00
|
|
|
|
const dbType = formData.get('dbType') || 'mysql';
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据配置类型创建不同的配置对象
|
|
|
|
|
|
let configData;
|
|
|
|
|
|
|
|
|
|
|
|
if (configType === 'database') {
|
|
|
|
|
|
configData = {
|
|
|
|
|
|
name: configName,
|
|
|
|
|
|
type: configType,
|
|
|
|
|
|
mysql_host: '',
|
|
|
|
|
|
mysql_port: 3306,
|
|
|
|
|
|
mysql_password: '',
|
|
|
|
|
|
database: '',
|
|
|
|
|
|
table_name: '',
|
|
|
|
|
|
id_column: '',
|
2026-02-10 16:08:46 +08:00
|
|
|
|
content_column: '',
|
|
|
|
|
|
db_type: dbType
|
2026-01-22 13:04:29 +08:00
|
|
|
|
};
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (configType === 'folder') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
configData = {
|
|
|
|
|
|
name: configName,
|
|
|
|
|
|
type: configType,
|
|
|
|
|
|
folder_path: '',
|
|
|
|
|
|
recursive: true,
|
|
|
|
|
|
ignore_patterns: [],
|
|
|
|
|
|
host: '',
|
|
|
|
|
|
port: 22,
|
|
|
|
|
|
username: '',
|
|
|
|
|
|
password: ''
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert('不支持的配置类型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 创建新配置
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const response = await fetch('/folder-configs', {
|
2026-01-18 15:46:45 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(configData)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || 'Failed to create config');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const newConfig = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏模态框
|
|
|
|
|
|
hideAddConfigModal();
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 刷新配置列表并等待完成
|
|
|
|
|
|
await loadConfigs();
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载新配置详情
|
|
|
|
|
|
loadConfigDetails(newConfig.id);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error creating config:', error);
|
|
|
|
|
|
alert('创建配置失败: ' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存配置
|
|
|
|
|
|
async function saveConfig() {
|
|
|
|
|
|
if (!currentConfig) {
|
|
|
|
|
|
alert('没有选中的配置');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 收集表单数据
|
|
|
|
|
|
const formData = collectFormData();
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
console.log(formData);
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 检查必要的信息是否都填写了
|
|
|
|
|
|
let missingFields = [];
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 检查基本信息
|
|
|
|
|
|
if (!formData.type) missingFields.push('配置类型');
|
|
|
|
|
|
|
|
|
|
|
|
// 自动生成配置名称(如果为空)
|
|
|
|
|
|
if (!formData.name) {
|
|
|
|
|
|
let generatedName = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (formData.type === 'database') {
|
|
|
|
|
|
// 数据库配置:database_数据库名_表名
|
|
|
|
|
|
generatedName = `database_${formData.database || 'unknown'}_${formData.table_name || 'unknown'}`;
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (formData.type === 'folder') {
|
|
|
|
|
|
// 文件夹:folder_主机_文件夹路径(替换特殊字符)
|
2026-01-22 13:04:29 +08:00
|
|
|
|
const folderName = formData.folder_path ? formData.folder_path.replace(/[\\/:*?"<>|]/g, '_') : 'unknown';
|
2026-01-25 21:59:25 +08:00
|
|
|
|
generatedName = `folder_${formData.host || 'unknown'}_${folderName}`;
|
2026-02-24 00:48:55 +08:00
|
|
|
|
} else if (formData.type === 'git') {
|
|
|
|
|
|
// Git配置:git_仓库地址(替换特殊字符)
|
|
|
|
|
|
const repoName = formData.git_url ? formData.git_url.replace(/[\\/:*?"<>|]/g, '_') : 'unknown';
|
|
|
|
|
|
generatedName = `git_${repoName}_${formData.branch || 'main'}`;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
} else {
|
2026-01-25 21:59:25 +08:00
|
|
|
|
// 不支持的配置类型
|
|
|
|
|
|
alert('不支持的配置类型');
|
|
|
|
|
|
return;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:48:55 +08:00
|
|
|
|
formData.name = generatedName;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据不同类型检查特定字段
|
|
|
|
|
|
if (formData.type === 'database') {
|
|
|
|
|
|
if (!formData.mysql_host) missingFields.push('数据库主机');
|
|
|
|
|
|
if (!formData.mysql_port) missingFields.push('数据库端口');
|
|
|
|
|
|
if (!formData.mysql_user) missingFields.push('数据库用户名');
|
|
|
|
|
|
if (!formData.mysql_password) missingFields.push('数据库密码');
|
|
|
|
|
|
if (!formData.database) missingFields.push('数据库名称');
|
|
|
|
|
|
if (!formData.table_name) missingFields.push('表名');
|
|
|
|
|
|
if (!formData.id_column) missingFields.push('ID列');
|
|
|
|
|
|
if (!formData.content_column || formData.content_column.trim() === '') {
|
|
|
|
|
|
missingFields.push('内容列');
|
|
|
|
|
|
}
|
2026-01-25 21:59:25 +08:00
|
|
|
|
} else if (formData.type === 'folder') {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
if (!formData.folder_path) missingFields.push('文件夹路径');
|
|
|
|
|
|
if (!formData.host) missingFields.push('主机地址');
|
|
|
|
|
|
if (!formData.port) missingFields.push('端口');
|
|
|
|
|
|
if (!formData.username) missingFields.push('用户名');
|
|
|
|
|
|
if (!formData.password) missingFields.push('密码');
|
2026-02-24 00:48:55 +08:00
|
|
|
|
} else if (formData.type === 'git') {
|
|
|
|
|
|
if (!formData.git_url) missingFields.push('Git仓库地址');
|
|
|
|
|
|
if (!formData.branch) missingFields.push('分支名称');
|
|
|
|
|
|
if (!formData.protocol) missingFields.push('协议类型');
|
2026-01-22 13:04:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有缺失的字段,提示用户
|
|
|
|
|
|
if (missingFields.length > 0) {
|
|
|
|
|
|
alert('请填写以下必要信息:\n' + missingFields.join('\n'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前配置 - 直接使用新的表单数据作为完整配置,这样可以删除不再需要的旧字段
|
|
|
|
|
|
currentConfig.config = formData;
|
|
|
|
|
|
|
|
|
|
|
|
// 根据当前配置ID决定使用POST还是PUT方法
|
|
|
|
|
|
let response;
|
|
|
|
|
|
if (currentConfig.id === null) {
|
|
|
|
|
|
// 新配置,使用POST方法创建
|
|
|
|
|
|
response = await fetch('/folder-configs', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(currentConfig.config)
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 现有配置,使用PUT方法更新
|
|
|
|
|
|
response = await fetch(`/folder-configs/${currentConfig.id}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(currentConfig.config)
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || 'Failed to save config');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 获取保存/更新后的配置
|
|
|
|
|
|
const updatedConfig = await response.json();
|
|
|
|
|
|
|
2026-01-25 21:59:25 +08:00
|
|
|
|
// 检查是否是新配置(POST)
|
|
|
|
|
|
const isNewConfig = currentConfig.id === null;
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 更新当前配置的ID
|
|
|
|
|
|
currentConfig.id = updatedConfig.id;
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
// 刷新配置列表
|
2026-01-22 13:04:29 +08:00
|
|
|
|
await loadConfigs();
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新的ID重新加载配置详情
|
|
|
|
|
|
loadConfigDetails(updatedConfig.id);
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 根据配置类型执行不同的同步操作
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 数据源名称是配置的ID,而不是config中的name字段
|
|
|
|
|
|
const sourceName = updatedConfig.id;
|
|
|
|
|
|
|
2026-01-25 21:59:25 +08:00
|
|
|
|
if (isNewConfig) {
|
2026-01-22 13:04:29 +08:00
|
|
|
|
// 新配置(POST):保存成功后,立刻启动数据源的同步操作
|
|
|
|
|
|
alert('配置保存成功,正在启动同步...');
|
|
|
|
|
|
|
|
|
|
|
|
// 启动同步
|
|
|
|
|
|
const syncResponse = await fetch('/sync', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
full_sync: true,
|
|
|
|
|
|
source_name: sourceName
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (syncResponse.ok) {
|
|
|
|
|
|
const syncResult = await syncResponse.json();
|
|
|
|
|
|
alert('同步已启动:' + syncResult.message);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('同步启动失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 现有配置(PUT):保存成功后,先删除chromadb中已有的知识,然后立刻启动数据源的同步操作
|
|
|
|
|
|
alert('配置更新成功,正在清理旧数据并启动同步...');
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 删除旧数据
|
|
|
|
|
|
const deleteResponse = await fetch(`/documents/source/${encodeURIComponent(sourceName)}`, {
|
|
|
|
|
|
method: 'DELETE'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!deleteResponse.ok) {
|
|
|
|
|
|
throw new Error('删除旧数据失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 启动同步
|
|
|
|
|
|
const syncResponse = await fetch('/sync', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
full_sync: true,
|
|
|
|
|
|
source_name: sourceName
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (syncResponse.ok) {
|
|
|
|
|
|
const syncResult = await syncResponse.json();
|
|
|
|
|
|
alert('数据清理完成,同步已启动:' + syncResult.message);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('同步启动失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('同步操作失败:', error);
|
|
|
|
|
|
alert('配置保存成功,但同步操作失败:' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染配置列表
|
|
|
|
|
|
renderConfigList();
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error saving config:', error);
|
|
|
|
|
|
alert('保存配置失败: ' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集表单数据
|
|
|
|
|
|
function collectFormData() {
|
|
|
|
|
|
const formData = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 基本信息
|
|
|
|
|
|
formData.name = document.getElementById('formName').value;
|
|
|
|
|
|
formData.type = document.getElementById('formType').value;
|
|
|
|
|
|
|
|
|
|
|
|
// 文件夹配置
|
2026-01-25 21:59:25 +08:00
|
|
|
|
if (formData.type === 'folder') {
|
2026-01-18 15:46:45 +08:00
|
|
|
|
formData.folder_path = document.getElementById('formFolderPath').value;
|
|
|
|
|
|
formData.recursive = document.getElementById('formRecursive').checked;
|
|
|
|
|
|
|
|
|
|
|
|
const ignorePatterns = document.getElementById('formIgnorePatterns').value;
|
|
|
|
|
|
formData.ignore_patterns = ignorePatterns ? ignorePatterns.split(',').map(p => p.trim()) : [];
|
2026-01-25 21:59:25 +08:00
|
|
|
|
|
|
|
|
|
|
// 文件夹的SSH配置
|
2026-01-18 15:46:45 +08:00
|
|
|
|
formData.host = document.getElementById('formHost').value;
|
|
|
|
|
|
formData.port = parseInt(document.getElementById('formPort').value);
|
|
|
|
|
|
formData.username = document.getElementById('formUsername').value;
|
|
|
|
|
|
formData.password = document.getElementById('formPassword').value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库配置
|
|
|
|
|
|
if (formData.type === 'database') {
|
|
|
|
|
|
formData.database = document.getElementById('formDatabase').value;
|
|
|
|
|
|
formData.table_name = document.getElementById('formTableName').value;
|
|
|
|
|
|
formData.id_column = document.getElementById('formIdColumn').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 收集多选的内容列
|
|
|
|
|
|
const contentGroup = document.getElementById('formContentColumn');
|
|
|
|
|
|
const checkedBoxes = Array.from(contentGroup.querySelectorAll('input[name="contentColumn"]:checked'));
|
|
|
|
|
|
formData.content_column = checkedBoxes.map(checkbox => checkbox.value).join(','); // 转换为逗号分隔的字符串
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
formData.title_column = document.getElementById('formTitleColumn').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
formData.updated_at_column = document.getElementById('formUpdatedAtColumn').value;
|
2026-01-18 15:46:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 数据库连接配置
|
|
|
|
|
|
formData.mysql_host = document.getElementById('formMysqlHost').value;
|
|
|
|
|
|
formData.mysql_port = parseInt(document.getElementById('formMysqlPort').value);
|
|
|
|
|
|
formData.mysql_user = document.getElementById('formMysqlUser').value;
|
|
|
|
|
|
formData.mysql_password = document.getElementById('formMysqlPassword').value;
|
2026-02-10 16:08:46 +08:00
|
|
|
|
formData.db_type = document.getElementById('formDbType').value;
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
2026-01-25 21:59:25 +08:00
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:48:55 +08:00
|
|
|
|
// Git配置
|
|
|
|
|
|
if (formData.type === 'git') {
|
|
|
|
|
|
formData.git_url = document.getElementById('formGitUrl').value;
|
|
|
|
|
|
formData.branch = document.getElementById('formBranch').value;
|
|
|
|
|
|
formData.protocol = document.getElementById('formProtocol').value;
|
|
|
|
|
|
formData.https_token = document.getElementById('formHttpsToken').value;
|
|
|
|
|
|
formData.ssh_key = document.getElementById('formSshKey').value;
|
|
|
|
|
|
formData.poll_interval = parseInt(document.getElementById('formPollInterval').value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
return formData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示删除确认
|
|
|
|
|
|
function showDeleteConfirm() {
|
|
|
|
|
|
if (!currentConfig) {
|
|
|
|
|
|
alert('没有选中的配置');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('deleteConfirmModal').classList.add('show');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏删除确认模态框
|
|
|
|
|
|
function hideDeleteConfirmModal() {
|
|
|
|
|
|
document.getElementById('deleteConfirmModal').classList.remove('show');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确认删除配置
|
|
|
|
|
|
async function confirmDeleteConfig() {
|
|
|
|
|
|
if (!currentConfig) {
|
|
|
|
|
|
alert('没有选中的配置');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`/folder-configs/${currentConfig.id}`, {
|
|
|
|
|
|
method: 'DELETE'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error('Failed to delete config');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏模态框
|
|
|
|
|
|
hideDeleteConfirmModal();
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新配置列表
|
|
|
|
|
|
loadConfigs();
|
|
|
|
|
|
|
|
|
|
|
|
// 显示欢迎界面
|
|
|
|
|
|
showWelcomeScreen();
|
|
|
|
|
|
|
|
|
|
|
|
alert('配置删除成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error deleting config:', error);
|
|
|
|
|
|
alert('删除配置失败: ' + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:48:55 +08:00
|
|
|
|
// Git事件绑定函数
|
|
|
|
|
|
function bindGitEvents() {
|
|
|
|
|
|
// 协议切换逻辑
|
|
|
|
|
|
const protocolSelect = document.getElementById('formProtocol');
|
|
|
|
|
|
const httpsTokenGroup = document.getElementById('httpsTokenGroup');
|
|
|
|
|
|
const sshKeyGroup = document.getElementById('sshKeyGroup');
|
|
|
|
|
|
|
|
|
|
|
|
protocolSelect.addEventListener('change', function() {
|
|
|
|
|
|
const protocol = this.value;
|
|
|
|
|
|
if (protocol === 'https') {
|
|
|
|
|
|
httpsTokenGroup.style.display = 'block';
|
|
|
|
|
|
sshKeyGroup.style.display = 'none';
|
|
|
|
|
|
} else if (protocol === 'ssh') {
|
|
|
|
|
|
httpsTokenGroup.style.display = 'none';
|
|
|
|
|
|
sshKeyGroup.style.display = 'block';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 触发一次change事件,确保初始状态正确
|
|
|
|
|
|
protocolSelect.dispatchEvent(new Event('change'));
|
|
|
|
|
|
|
|
|
|
|
|
// 测试Git连接
|
|
|
|
|
|
document.getElementById('testGitConnectionBtn')?.addEventListener('click', async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const gitUrl = document.getElementById('formGitUrl').value;
|
|
|
|
|
|
const branch = document.getElementById('formBranch').value;
|
|
|
|
|
|
const protocol = document.getElementById('formProtocol').value;
|
|
|
|
|
|
const httpsToken = document.getElementById('formHttpsToken').value;
|
|
|
|
|
|
const sshKey = document.getElementById('formSshKey').value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!gitUrl) {
|
|
|
|
|
|
alert('请填写Git仓库地址');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('testGitConnectionBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '🔌 测试中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/git/test-connection', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
git_url: gitUrl,
|
|
|
|
|
|
branch: branch,
|
|
|
|
|
|
protocol: protocol,
|
|
|
|
|
|
https_token: httpsToken,
|
|
|
|
|
|
ssh_key: sshKey
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
alert('Git连接测试成功!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '连接失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('Git连接测试失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('testGitConnectionBtn');
|
|
|
|
|
|
btn.textContent = '🔌 测试Git连接';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 15:09:12 +08:00
|
|
|
|
// 为文件夹配置添加事件绑定
|
|
|
|
|
|
function bindFolderEvents() {
|
|
|
|
|
|
// 测试SSH连接
|
|
|
|
|
|
const testBtn = document.getElementById('testSshConnectionBtn');
|
|
|
|
|
|
if (testBtn) {
|
|
|
|
|
|
testBtn.onclick = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const host = document.getElementById('formHost').value;
|
|
|
|
|
|
const port = parseInt(document.getElementById('formPort').value);
|
|
|
|
|
|
const username = document.getElementById('formUsername').value;
|
|
|
|
|
|
const password = document.getElementById('formPassword').value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!host || !username) {
|
|
|
|
|
|
alert('请填写主机地址和用户名');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用按钮
|
|
|
|
|
|
const btn = document.getElementById('testSshConnectionBtn');
|
|
|
|
|
|
const originalText = btn.textContent;
|
|
|
|
|
|
btn.textContent = '🔌 测试中...';
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
const response = await fetch('/folder/test-connection', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
host,
|
|
|
|
|
|
port,
|
|
|
|
|
|
username,
|
|
|
|
|
|
password
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
|
alert('SSH连接成功!');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
|
throw new Error(errorData.detail || '连接失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
alert('SSH连接失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复按钮
|
|
|
|
|
|
const btn = document.getElementById('testSshConnectionBtn');
|
|
|
|
|
|
btn.textContent = '🔌 测试SSH连接';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:46:45 +08:00
|
|
|
|
// 添加CSS样式(确保模态框显示)
|
|
|
|
|
|
const style = document.createElement('style');
|
|
|
|
|
|
style.textContent = `
|
|
|
|
|
|
.modal {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.modal.show {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
document.head.appendChild(style);
|
|
|
|
|
|
|
2026-01-26 15:09:12 +08:00
|
|
|
|
// 绑定事件
|
|
|
|
|
|
bindDatabaseEvents();
|
|
|
|
|
|
|
2026-01-22 13:04:29 +08:00
|
|
|
|
|