// 外部系统连接管理:凭据字段由系统定义声明,只提交、不回显。
import { $ } from "./dom.js";
import { api } from "./api.js";
import { escapeHtml, fmtTime } from "./format.js";
import { dialogConfirm, message } from "./dialog.js";
let editingId = "";
let providerReady = false;
let definitions = [];
let systemsById = new Map();
function selectedDefinition() {
return definitions.find(x => x.definition_id === $("ext-definition").value);
}
function renderCredentialFields(fields = []) {
$("ext-credential-fields").innerHTML = fields.map(field => `
`).join("");
}
function renderDefinitionOptions(items) {
$("ext-definition").innerHTML = items.map(item =>
``
).join("");
}
function unconnectedDefinitions() {
const connectedIds = new Set(Array.from(systemsById.values()).map(item => item.definition_id));
return definitions.filter(item => !connectedIds.has(item.definition_id));
}
function resetForm() {
editingId = "";
$("ext-form-title").textContent = "新建外部系统连接";
$("ext-name").disabled = false;
$("ext-definition").disabled = false;
$("ext-err").textContent = "";
$("ext-save").textContent = "连接并验证";
const available = unconnectedDefinitions();
renderDefinitionOptions(available);
const selected = available[0];
$("ext-name").value = selected ? selected.name : "";
renderCredentialFields((selected || {}).credential_fields || []);
}
function closeConnectionModal() {
$("external-connection-modal").classList.remove("show");
editingId = "";
}
function openConnectionModal(system = null) {
if (!providerReady) return;
resetForm();
if (system) {
editingId = system.external_system_id;
$("ext-form-title").textContent = `更新凭据:${system.system_name || system.name}`;
$("ext-name").value = system.name;
$("ext-name").disabled = true;
renderDefinitionOptions(definitions.filter(item => item.definition_id === system.definition_id));
$("ext-definition").disabled = true;
$("ext-save").textContent = "验证并更新";
renderCredentialFields(system.credential_fields || []);
}
$("external-connection-modal").classList.add("show");
$("ext-credential-fields").querySelector("input")?.focus();
}
export function closeExternalSystemsModal() {
closeConnectionModal();
$("external-modal").classList.remove("show");
}
async function openExternalSystemsModal() {
$("external-modal").classList.add("show");
await loadExternalSystems();
}
function connectorBadgeHtml(connector) {
if (connector === "mcp") {
return `
MCP
`;
}
return `
OpenAPI
`;
}
function cardHtml(item) {
const good = item.status === "active";
const badge = good
? "已连接"
: (item.status === "disabled"
? "系统已停用"
: (item.status === "needs_reverify" ? "需重新验证" : "需更新凭据"));
const checked = item.last_verified_at ? fmtTime(item.last_verified_at) : "尚未验证";
return `
${escapeHtml(item.name)}${connectorBadgeHtml(item.connector)}${badge}
${escapeHtml(item.system_name || "外部系统")} · ${escapeHtml(item.username_masked || "***")} · 最近验证 ${escapeHtml(checked)}
`;
}
async function loadExternalSystems() {
const provider = $("ext-provider");
const list = $("ext-list");
provider.textContent = "加载中…";
list.innerHTML = '加载中…
';
try {
const [catalog, response] = await Promise.all([
api("GET", "/v1/external-system-providers"),
api("GET", "/v1/external-systems"),
]);
const providers = catalog.providers || [];
definitions = providers.flatMap(item => item.definitions || []);
providerReady = definitions.length > 0 && providers.some(item => item.configured);
const rows = response.results || [];
systemsById = new Map(rows.map(item => [item.external_system_id, item]));
provider.textContent = providerReady
? `管理员已配置 ${definitions.length} 个外部系统,请选择后填写自己的访问凭据`
: `暂没有可连接的外部系统${providers.find(item => item.reason)?.reason ? ":" + providers.find(item => item.reason).reason : ",请联系管理员配置"}`;
const hasAvailableDefinition = unconnectedDefinitions().length > 0;
$("ext-add").disabled = !providerReady || !hasAvailableDefinition;
$("ext-add").title = hasAvailableDefinition ? "" : "所有可用外部系统均已连接";
list.innerHTML = rows.length
? rows.map(cardHtml).join("")
: '还没有外部系统连接。点击“新建连接”后,助手即可按你的权限查询。
';
} catch (error) {
providerReady = false;
provider.textContent = "加载失败";
list.innerHTML = `${escapeHtml(error.message)}
`;
$("ext-add").disabled = true;
}
}
$("hd-external").onclick = openExternalSystemsModal;
$("ext-close").onclick = closeExternalSystemsModal;
$("ext-add").onclick = () => openConnectionModal();
$("ext-form-close").onclick = closeConnectionModal;
$("ext-form-cancel").onclick = closeConnectionModal;
$("external-modal").addEventListener("click", event => {
if (event.target.id === "external-modal") closeExternalSystemsModal();
});
$("external-connection-modal").addEventListener("click", event => {
if (event.target.id === "external-connection-modal") closeConnectionModal();
});
$("external-connection-modal").addEventListener("keydown", event => {
if (event.key === "Escape") closeConnectionModal();
});
$("ext-definition").onchange = () => {
const selected = selectedDefinition();
if (selected) $("ext-name").value = selected.name;
renderCredentialFields((selected || {}).credential_fields || []);
};
$("ext-form").addEventListener("submit", async event => {
event.preventDefault();
if (!providerReady) return;
const name = $("ext-name").value.trim();
const definitionId = $("ext-definition").value;
const credentials = Object.fromEntries(
Array.from($("ext-credential-fields").querySelectorAll("[data-credential-name]"))
.map(input => [input.dataset.credentialName, input.value]),
);
if (Object.values(credentials).some(value => !value.trim()) || (!editingId && (!name || !definitionId))) {
$("ext-err").textContent = "请填写连接名称和全部访问凭据";
return;
}
const button = $("ext-save");
const wasEditing = Boolean(editingId);
button.disabled = true;
button.textContent = "正在验证…";
$("ext-err").textContent = "";
try {
if (wasEditing) {
await api("PUT", `/v1/external-systems/${editingId}/credentials`, { credentials });
message("外部系统已重新连接,凭据已更新", "success");
} else {
await api("POST", "/v1/external-systems", { definition_id: definitionId, name, credentials });
message("外部系统已连接;下一轮对话即可使用", "success");
}
closeConnectionModal();
await loadExternalSystems();
} catch (error) {
$("ext-err").textContent = error.message;
} finally {
button.disabled = false;
button.textContent = wasEditing ? "验证并更新" : "连接并验证";
}
});
$("ext-list").addEventListener("click", async event => {
const card = event.target.closest(".ext-card");
if (!card) return;
const id = card.dataset.id;
if (event.target.closest("[data-ext-test]")) {
const button = event.target.closest("button");
button.disabled = true;
try {
const result = await api("POST", `/v1/external-systems/${id}/test`);
message(result.ok ? `连接正常,可用接口 ${result.operation_count || 0} 个` : `连接失败:${result.error}`, result.ok ? "success" : "error");
await loadExternalSystems();
} catch (error) { message("测试失败:" + error.message, "error"); }
finally { button.disabled = false; }
return;
}
if (event.target.closest("[data-ext-delete]")) {
const name = card.querySelector(".ext-card-name").textContent;
if (!await dialogConfirm({ title:"断开外部系统", message:`断开「${name}」?保存的密文凭据和连接配置将被清除。`, okText:"断开", danger:true })) return;
try {
await api("DELETE", `/v1/external-systems/${id}`);
message("外部系统已断开", "success");
await loadExternalSystems();
} catch (error) { message("断开失败:" + error.message, "error"); }
return;
}
openConnectionModal(systemsById.get(id));
});