feat: Add credentials viewer with copy buttons

- Add 'Credenciais Carregadas' section in web interface
- Show services (Coolify, Gitea, Supabase, Logto) status
- Display path and number of keys found
- Add refresh button to reload credentials
- Add copy to clipboard functionality for credentials
This commit is contained in:
Marcos
2026-03-22 16:00:58 -03:00
parent a229d1a64b
commit 9b429f5505

View File

@@ -864,6 +864,29 @@
</div>
</div>
<!-- CREDENCIAIS CARREGADAS -->
<div class="section-title">Credenciais Carregadas</div>
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span style="font-size: 0.85rem; color: var(--text-muted);">
Credenciais sincronizadas dos serviços (Coolify, Gitea, Supabase, etc)
</span>
<button type="button" class="btn" onclick="loadCredentials()" style="padding: 0.4rem 0.75rem; font-size: 0.75rem;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14">
<path d="M23 4v6h-6M1 20v-6h6"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>
Atualizar
</button>
</div>
<div id="credentials-container" style="display: none;">
<div id="credentials-list" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<div id="credentials-loading" style="text-align: center; padding: 1rem; color: var(--text-muted);">
Clique em "Atualizar" para carregar as credenciais
</div>
</div>
<div class="section-title">Terminal & Insights da IA</div>
<div class="chat-layout">
<!-- Coluna 1: Chat Técnico -->
@@ -1333,6 +1356,76 @@
showToast('Erro ao sincronizar.', true);
}
}
async function loadCredentials() {
const loadingEl = document.getElementById('credentials-loading');
const containerEl = document.getElementById('credentials-container');
const listEl = document.getElementById('credentials-list');
if (loadingEl) loadingEl.innerHTML = 'Carregando...';
try {
// Sync first
await apiFetch('/api/sync-credentials', { method: 'POST' });
// Get orchestrator status which has services info
const res = await apiFetch('/api/orchestrator-status');
const data = await res.json();
const services = data.credentials || {};
const serviceNames = {
coolify: 'Coolify (Orquestrador)',
supabase: 'Supabase (BaaS)',
gitea: 'Gitea (Git Server)',
logto: 'Logto (Autenticação)'
};
let html = '<div style="display: grid; gap: 1rem;">';
for (const [key, info] of Object.entries(services)) {
const name = serviceNames[key] || key;
const status = info.exists ? '<span style="color: var(--success);">Disponivel</span>' : '<span style="color: var(--danger);">Nao disponivel</span>';
const keysCount = info.keys_count || 0;
html += `
<div style="padding: 1rem; background: var(--bg-input); border-radius: 8px; border: 1px solid var(--border);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
<strong style="color: var(--accent);">${name}</strong>
${status}
</div>
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.5rem;">
Caminho: <code style="background: var(--bg-base); padding: 0.1rem 0.3rem; border-radius: 4px;">${info.path || 'N/A'}</code>
</div>
<div style="font-size: 0.75rem; color: var(--text-muted);">
${keysCount} chave(s) encontrada(s)
</div>
</div>
`;
}
html += '</div>';
if (loadingEl) loadingEl.style.display = 'none';
if (containerEl) containerEl.style.display = 'block';
if (listEl) listEl.innerHTML = html;
} catch (e) {
if (loadingEl) {
loadingEl.innerHTML = 'Erro ao carregar credenciais';
loadingEl.style.color = 'var(--danger)';
}
console.error('Erro ao carregar credenciais:', e);
}
}
async function copyToClipboard(text, label) {
try {
await navigator.clipboard.writeText(text);
showToast(label + ' copiado!');
} catch (e) {
showToast('Erro ao copiar.', true);
}
}
async function testLLMSpeed() {
const btn = document.getElementById('btn-test-llm');