RAG/static/config/script.js

1690 lines
65 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 配置管理系统 JavaScript
// 全局变量
let currentConfig = null;
let configList = [];
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 添加删除确认模态框的HTML
const deleteConfirmModalHTML = `
<div class="modal" id="deleteConfirmModal">
<div class="modal-content">
<div class="modal-header">
<h3>删除配置</h3>
<button class="close-btn" id="deleteConfirmCloseBtn">&times;</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);
// 绑定事件监听器
bindEventListeners();
// 加载配置列表
loadConfigs();
});
// 绑定事件监听器
function bindEventListeners() {
// 配置类型选择
const configTypeSelect = document.getElementById('configTypeSelect');
// 选中下拉框选项后直接显示配置详情界面
configTypeSelect.addEventListener('change', function() {
if (this.value) {
handleAddConfigDirectly();
}
});
// 关闭添加配置模态框
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;
configItem.dataset.type = config.type;
if (currentConfig && config.id === currentConfig.id) {
configItem.classList.add('active');
}
// 生成配置描述信息
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;
}
} else if (config.type === 'folder') {
const folderConfig = config.config;
if (folderConfig.host && folderConfig.folder_path) {
configInfo = `${folderConfig.host}:${folderConfig.folder_path}`;
} else if (folderConfig.folder_path) {
configInfo = folderConfig.folder_path;
} else if (folderConfig.host) {
configInfo = folderConfig.host;
}
}
// 使用配置ID作为标题
const configName = config.id;
// 生成HTML结构
configItem.innerHTML = `
<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>
`;
configItem.addEventListener('click', () => loadConfigDetails(config.id));
configListElement.appendChild(configItem);
});
}
// 加载配置详情
async function loadConfigDetails(configId) {
try {
// 查找配置
let config = configList.find(c => c.id === configId);
// 如果配置不存在,可能是刚创建的,尝试从服务器重新获取配置列表
if (!config) {
await loadConfigs();
config = configList.find(c => c.id === configId);
}
// 如果仍然找不到配置,说明真的有问题,直接报错
if (!config) {
throw new Error('配置不存在');
}
currentConfig = config;
// 更新配置列表选中状态
renderConfigList();
// 显示配置详情界面
showConfigScreen();
// 更新配置标题使用配置ID即文件名
if (config.id === null) {
document.getElementById('configTitle').textContent = '新增数据源';
} else {
document.getElementById('configTitle').textContent = config.id;
}
// 生成配置表单
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>
<!-- 配置名称自动生成,隐藏输入框 -->
<input type="text" id="formName" value="${config.name || ''}" style="display: none;">
<div class="form-group">
<label for="formType">配置类型</label>
<select id="formType" disabled>
<option value="database" ${config.type === 'database' ? 'selected' : ''}>数据库 (database)</option>
<option value="folder" ${config.type === 'folder' ? 'selected' : ''}>文件夹 (folder)</option>
<option value="git" ${config.type === 'git' ? 'selected' : ''}>Git代码库 (git)</option>
</select>
</div>
`;
formElement.appendChild(basicSection);
// 根据配置类型生成相应的表单字段
if (config.type === 'folder') {
const folderSection = document.createElement('div');
folderSection.innerHTML = `
<h3 class="section-title">文件夹配置</h3>
<div class="form-group">
<label for="formFolderPath">文件夹路径 <span class="required">*</span></label>
<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">
<label for="formIgnorePatterns">忽略的文件模式 (用逗号分隔,示例:*.log, *.tmp, node_modules/)</label>
<input type="text" id="formIgnorePatterns" value="${config.ignore_patterns ? config.ignore_patterns.join(', ') : ''}">
<div style="margin-top: 5px; font-size: 0.85em; color: #666;">
示例:*.log, *.tmp, node_modules/
</div>
</div>
`;
formElement.appendChild(folderSection);
}
// 为folder添加SSH连接配置
if (config.type === 'folder') {
const sshSection = document.createElement('div');
sshSection.innerHTML = `
<h3 class="section-title">SSH连接配置</h3>
<div class="form-group">
<label for="formHost">主机地址 <span class="required">*</span></label>
<input type="text" id="formHost" value="${config.host || ''}" required>
</div>
<div class="form-group">
<label for="formPort">端口 <span class="required">*</span></label>
<input type="number" id="formPort" value="${config.port || 22}" min="1" max="65535" required>
</div>
<div class="form-group">
<label for="formUsername">用户名 <span class="required">*</span></label>
<input type="text" id="formUsername" value="${config.username || ''}" required>
</div>
<div class="form-group">
<label for="formPassword">密码 <span class="required">*</span></label>
<input type="password" id="formPassword" value="${config.password || ''}" required>
</div>
<div class="form-actions">
<button type="button" id="testSshConnectionBtn" class="btn secondary" style="margin-top: 10px;">🔌 测试SSH连接</button>
</div>
`;
formElement.appendChild(sshSection);
// 绑定事件
bindFolderEvents();
}
// 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();
}
if (config.type === 'database') {
// 数据库连接配置(放在前面,方便先测试连接)
const connectionSection = document.createElement('div');
connectionSection.innerHTML = `
<h3 class="section-title">数据库连接配置</h3>
<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>
<div class="form-group">
<label for="formMysqlHost">主机地址 <span class="required">*</span></label>
<input type="text" id="formMysqlHost" value="${config.mysql_host || ''}" required>
</div>
<div class="form-group">
<label for="formMysqlPort">端口 <span class="required">*</span></label>
<input type="number" id="formMysqlPort" value="${config.mysql_port || 3306}" min="1" max="65535" required>
</div>
<div class="form-group">
<label for="formMysqlUser">用户名 <span class="required">*</span></label>
<input type="text" id="formMysqlUser" value="${config.mysql_user || ''}" required>
</div>
<div class="form-group">
<label for="formMysqlPassword">密码 <span class="required">*</span></label>
<input type="password" id="formMysqlPassword" value="${config.mysql_password || ''}" required>
</div>
<div class="form-group">
<button type="button" id="testConnectionBtn" class="btn primary" style="margin-right: 10px;">🔌 测试连接</button>
<button type="button" id="getDatabasesBtn" class="btn primary">📋 获取数据库列表</button>
</div>
`;
formElement.appendChild(connectionSection);
const databaseSection = document.createElement('div');
databaseSection.innerHTML = `
<h3 class="section-title">数据库配置</h3>
<div class="form-group">
<label for="formDatabase">数据库名称 <span class="required">*</span></label>
<select id="formDatabase" required>
<option value="">-- 选择数据库 --</option>
</select>
</div>
<div class="form-group">
<button type="button" id="getTablesBtn" class="btn primary">📋 获取表列表</button>
</div>
<div class="form-group">
<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>
</div>
<div class="form-group">
<label for="formUpdatedAtColumn">更新时间列(可选)</label>
<select id="formUpdatedAtColumn">
<option value="">-- 选择更新时间列(可选)--</option>
</select>
</div>
`;
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;
const dbType = document.getElementById('formDbType').value;
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,
db_type: dbType,
...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;
const dbType = document.getElementById('formDbType').value;
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,
db_type: dbType,
...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;
const dbType = document.getElementById('formDbType').value;
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,
db_type: dbType,
...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;
const dbType = document.getElementById('formDbType').value;
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,
db_type: dbType,
...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,
table_name: config.table_name,
db_type: config.db_type || 'mysql'
})
});
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;
}
}
// 显示欢迎界面
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');
// 添加配置类型选择的事件监听器
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'));
}
// 隐藏添加配置模态框
function hideAddConfigModal() {
document.getElementById('addConfigModal').classList.remove('show');
document.getElementById('addConfigForm').reset();
}
// 直接创建配置(根据下拉框选择的类型)
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: '',
content_column: '',
db_type: 'mysql'
};
} else if (configType === 'folder') {
tempConfig = {
type: configType,
folder_path: '',
recursive: true,
ignore_patterns: [],
host: '',
port: 22,
username: '',
password: ''
};
} else if (configType === 'git') {
tempConfig = {
type: configType,
git_url: '',
branch: 'main',
protocol: 'https',
https_token: '',
ssh_key: '',
poll_interval: 300
};
} else {
alert('不支持的配置类型');
return;
}
// 直接设置当前配置,不立即发送到后端
currentConfig = {
id: null, // 临时ID保存时由后端生成
config: tempConfig
};
// 渲染配置表单
generateConfigForm(tempConfig);
// 切换到配置详情界面
showConfigScreen();
// 更新配置标题为"新增数据源"
document.getElementById('configTitle').textContent = '新增数据源';
// 重置类型选择
configTypeSelect.value = '';
// 提示用户需要填写配置信息
alert('请填写配置信息,然后点击保存按钮创建配置');
}
// 处理添加配置(旧的模态框方式,保留以便兼容)
async function handleAddConfig(event) {
event.preventDefault();
// 获取表单数据
const formData = new FormData(event.target);
const configType = formData.get('type');
const configName = formData.get('name');
const dbType = formData.get('dbType') || 'mysql';
// 根据配置类型创建不同的配置对象
let configData;
if (configType === 'database') {
configData = {
name: configName,
type: configType,
mysql_host: '',
mysql_port: 3306,
mysql_password: '',
database: '',
table_name: '',
id_column: '',
content_column: '',
db_type: dbType
};
} else if (configType === 'folder') {
configData = {
name: configName,
type: configType,
folder_path: '',
recursive: true,
ignore_patterns: [],
host: '',
port: 22,
username: '',
password: ''
};
} else {
alert('不支持的配置类型');
return;
}
try {
// 创建新配置
const response = await fetch('/folder-configs', {
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();
// 刷新配置列表并等待完成
await loadConfigs();
// 加载新配置详情
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();
console.log(formData);
// 检查必要的信息是否都填写了
let missingFields = [];
// 检查基本信息
if (!formData.type) missingFields.push('配置类型');
// 自动生成配置名称(如果为空)
if (!formData.name) {
let generatedName = '';
if (formData.type === 'database') {
// 数据库配置database_数据库名_表名
generatedName = `database_${formData.database || 'unknown'}_${formData.table_name || 'unknown'}`;
} else if (formData.type === 'folder') {
// 文件夹folder_主机_文件夹路径替换特殊字符
const folderName = formData.folder_path ? formData.folder_path.replace(/[\\/:*?"<>|]/g, '_') : 'unknown';
generatedName = `folder_${formData.host || 'unknown'}_${folderName}`;
} else if (formData.type === 'git') {
// Git配置git_仓库地址替换特殊字符
const repoName = formData.git_url ? formData.git_url.replace(/[\\/:*?"<>|]/g, '_') : 'unknown';
generatedName = `git_${repoName}_${formData.branch || 'main'}`;
} else {
// 不支持的配置类型
alert('不支持的配置类型');
return;
}
formData.name = generatedName;
}
// 根据不同类型检查特定字段
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('内容列');
}
} else if (formData.type === 'folder') {
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('密码');
} else if (formData.type === 'git') {
if (!formData.git_url) missingFields.push('Git仓库地址');
if (!formData.branch) missingFields.push('分支名称');
if (!formData.protocol) missingFields.push('协议类型');
}
// 如果有缺失的字段,提示用户
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)
});
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || 'Failed to save config');
}
// 获取保存/更新后的配置
const updatedConfig = await response.json();
// 检查是否是新配置POST
const isNewConfig = currentConfig.id === null;
// 更新当前配置的ID
currentConfig.id = updatedConfig.id;
// 刷新配置列表
await loadConfigs();
// 使用新的ID重新加载配置详情
loadConfigDetails(updatedConfig.id);
// 根据配置类型执行不同的同步操作
try {
// 数据源名称是配置的ID而不是config中的name字段
const sourceName = updatedConfig.id;
if (isNewConfig) {
// 新配置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();
} 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;
// 文件夹配置
if (formData.type === 'folder') {
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()) : [];
// 文件夹的SSH配置
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;
// 收集多选的内容列
const contentGroup = document.getElementById('formContentColumn');
const checkedBoxes = Array.from(contentGroup.querySelectorAll('input[name="contentColumn"]:checked'));
formData.content_column = checkedBoxes.map(checkbox => checkbox.value).join(','); // 转换为逗号分隔的字符串
formData.title_column = document.getElementById('formTitleColumn').value;
formData.updated_at_column = document.getElementById('formUpdatedAtColumn').value;
// 数据库连接配置
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;
formData.db_type = document.getElementById('formDbType').value;
}
// 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);
}
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);
}
}
// 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;
}
});
}
// 为文件夹配置添加事件绑定
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;
}
};
}
}
// 添加CSS样式确保模态框显示
const style = document.createElement('style');
style.textContent = `
.modal {
display: none;
}
.modal.show {
display: block;
}
`;
document.head.appendChild(style);
// 绑定事件
bindDatabaseEvents();