feat(config): cles des sources connectees et recherche a cle editables depuis la page Configurations (#103)
CI / lint (push) Successful in 1m46s
CI / security (push) Successful in 1m23s
CI / test (push) Successful in 3m42s
CI / build (push) Successful in 58s
CI / e2e (push) Successful in 10m50s

This commit is contained in:
2026-09-17 13:59:26 -04:00
parent 22e9240e4f
commit ba0ec3d1fa
19 changed files with 629 additions and 21 deletions
+100
View File
@@ -715,6 +715,7 @@ function initConfigModal() {
await loadHiddenFilesSettings();
loadWebhooksUI();
loadSharesUI();
loadToolKeys();
safeCreateIcons();
});
@@ -765,6 +766,9 @@ function initConfigModal() {
if (saveAIKeysBtn) saveAIKeysBtn.addEventListener("click", saveAIKeys);
const testAIKeysBtn = document.getElementById("cfg-test-ai-keys");
if (testAIKeysBtn) testAIKeysBtn.addEventListener("click", testAIKeys);
// Tool & connected-source keys (#103)
const saveToolKeysBtn = document.getElementById("cfg-save-tool-keys");
if (saveToolKeysBtn) saveToolKeysBtn.addEventListener("click", saveToolKeys);
// Default provider/model selection
const aiDefaultProviderSel = document.getElementById("cfg-ai-default-provider");
if (aiDefaultProviderSel) {
@@ -1715,6 +1719,102 @@ async function testAIKeys() {
}
// ── Tool & connected-source keys (#103) ──
const TOOL_KEY_MAP = {
"cfg-tavily-key": "OBSIGATE_TAVILY_API_KEY",
"cfg-brave-key": "OBSIGATE_BRAVE_API_KEY",
"cfg-serpapi-key": "OBSIGATE_SERPAPI_API_KEY",
"cfg-exa-key": "OBSIGATE_EXA_API_KEY",
"cfg-gitea-url": "OBSIGATE_GITEA_URL",
"cfg-gitea-token": "OBSIGATE_GITEA_TOKEN",
"cfg-github-token": "OBSIGATE_GITHUB_TOKEN",
};
function _ensureToolKeyUI() {
for (const [inputId] of Object.entries(TOOL_KEY_MAP)) {
const input = document.getElementById(inputId);
if (!input) continue;
const row = input.closest(".config-row");
if (!row || row.dataset.toolKeyEnhanced) continue;
row.dataset.toolKeyEnhanced = "1";
row.style.cssText += "display:flex;align-items:center;gap:8px;flex-wrap:wrap;";
const badge = document.createElement("span");
badge.id = inputId + "-badge";
badge.style.cssText = "font-size:11px;padding:2px 8px;border-radius:10px;white-space:nowrap;";
row.appendChild(badge);
const delBtn = document.createElement("button");
delBtn.type = "button";
delBtn.id = inputId + "-delete";
delBtn.className = "config-btn-secondary";
delBtn.style.cssText = "font-size:11px;padding:4px 10px;color:var(--danger,#e74c3c);border-color:var(--danger,#e74c3c);cursor:pointer;display:none;";
delBtn.textContent = "\u00d7 " + t("config.delete_key");
delBtn.addEventListener("click", () => deleteToolKey(inputId));
row.appendChild(delBtn);
}
}
function _setToolKeyBadge(inputId, hasKey) {
const badge = document.getElementById(inputId + "-badge");
const delBtn = document.getElementById(inputId + "-delete");
if (badge) {
if (hasKey) {
badge.textContent = "\u2713 " + t("config.key_set");
badge.style.background = "var(--success-bg, #27ae6022)";
badge.style.color = "var(--success, #27ae60)";
badge.style.border = "1px solid var(--success, #27ae60)";
} else {
badge.textContent = t("config.key_unset");
badge.style.background = "var(--muted-bg, #ffffff10)";
badge.style.color = "var(--text-muted, #888)";
badge.style.border = "1px solid var(--border, #444)";
}
}
if (delBtn) delBtn.style.display = hasKey ? "inline-block" : "none";
}
async function loadToolKeys() {
_ensureToolKeyUI();
try {
const data = await api("/api/config/tool-keys");
for (const [inputId, name] of Object.entries(TOOL_KEY_MAP)) {
const input = document.getElementById(inputId);
const val = data[name] || "";
if (input && !input.value.trim()) input.placeholder = val || input.placeholder;
_setToolKeyBadge(inputId, !!val);
}
} catch(e) { /* non-admin: the section stays inert */ }
}
async function saveToolKeys() {
const keys = {};
for (const [id, name] of Object.entries(TOOL_KEY_MAP)) {
const input = document.getElementById(id);
if (input && input.value.trim()) keys[name] = input.value.trim();
}
if (!Object.keys(keys).length) {
showToast(t("config.no_keys"), "warning");
return;
}
try {
await api("/api/config/tool-keys", { method: "POST", body: JSON.stringify(keys) });
showToast(t("config.api_keys_saved"), "success");
Object.keys(TOOL_KEY_MAP).forEach(id => { const el = document.getElementById(id); if (el) el.value = ""; });
loadToolKeys();
} catch(e) { showToast("Erreur: " + e.message, "error"); }
}
async function deleteToolKey(inputId) {
const name = TOOL_KEY_MAP[inputId];
if (!name) return;
if (!confirm(t("config.delete_key_confirm") + " " + name + " ?")) return;
try {
await api("/api/config/tool-keys/" + name, { method: "DELETE" });
showToast(t("config.key_deleted") + " " + name, "success");
loadToolKeys();
} catch(e) { showToast("Erreur: " + e.message, "error"); }
}
export {
initSidebarTabs,
initConfigModal,