function initSecretsPage() { var state = { workspaceId: null, secrets: [], profiles: [], derived: { activeSecretCount: 0, secretsById: {}, usageBySecret: {}, }, search: '', loading: false, error: '', modalMode: 'create', modalSecretId: null, }; var modal = document.getElementById('secret-modal'); var tbody = document.getElementById('secrets-tbody'); var cardList = document.getElementById('secrets-card-list'); var summary = document.getElementById('secrets-summary'); var searchInput = document.getElementById('secret-search'); var modalTitle = document.getElementById('secret-modal-title'); var modalName = document.getElementById('secret-name'); var modalKind = document.getElementById('secret-kind'); var modalNote = document.getElementById('secret-modal-note'); var modalSubmit = document.getElementById('secret-modal-submit-btn'); var kindHint = document.getElementById('secret-kind-hint'); var stringLabel = document.getElementById('secret-string-label'); var stringField = document.getElementById('secret-string-value'); var usernameField = document.getElementById('secret-username'); var passwordField = document.getElementById('secret-password'); var jsonField = document.getElementById('secret-json-value'); function tKey(key) { return typeof t === 'function' ? t(key) : key; } function tfKey(key, vars) { return typeof tf === 'function' ? tf(key, vars) : tKey(key); } function currentWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function currentWorkspace() { return window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; } function secretKindLabel(kind) { return tKey('secrets.kind.' + String(kind || '').toLowerCase()); } function formatDate(value) { if (!value) return '—'; return new Date(value).toLocaleDateString(); } function secretIdsForProfile(profile) { var config = profile && profile.config ? profile.config : {}; if (config.bearer) return [config.bearer.secret_id]; if (config.basic) return [config.basic.username_secret_id, config.basic.password_secret_id]; if (config.api_key_header) return [config.api_key_header.secret_id]; if (config.api_key_query) return [config.api_key_query.secret_id]; return []; } function buildUsageBySecret() { var usage = {}; state.profiles.forEach(function (profile) { secretIdsForProfile(profile).forEach(function (secretId) { if (!secretId) return; if (!usage[secretId]) usage[secretId] = []; usage[secretId].push(profile); }); }); return usage; } function recomputeDerivedState() { var secretsById = {}; var activeSecretCount = 0; state.secrets.forEach(function (secret) { secretsById[secret.id] = secret; if (secret.status === 'active') { activeSecretCount += 1; } }); state.derived = { activeSecretCount: activeSecretCount, secretsById: secretsById, usageBySecret: buildUsageBySecret(), }; } function resetModalFields() { modalName.value = ''; modalKind.value = 'token'; stringField.value = ''; usernameField.value = ''; passwordField.value = ''; jsonField.value = '{\n "value": ""\n}'; } function openModal(mode, secret) { state.modalMode = mode; state.modalSecretId = secret ? secret.id : null; resetModalFields(); if (mode === 'rotate' && secret) { modalTitle.textContent = tKey('secrets.modal.rotate_title'); modalName.value = secret.name; modalName.disabled = true; modalKind.value = secret.kind; modalKind.disabled = true; modalSubmit.textContent = tKey('secrets.modal.rotate_action'); modalNote.textContent = tKey('secrets.modal.rotate_note'); } else { modalTitle.textContent = tKey('secrets.modal.create_title'); modalName.disabled = false; modalKind.disabled = false; modalSubmit.textContent = tKey('secrets.modal.create_action'); modalNote.textContent = tKey('secrets.modal.create_note'); } updateKindFields(); modal.classList.add('open'); if (window.CrankDiagnostics && typeof window.CrankDiagnostics.ensureTestId === 'function') { window.CrankDiagnostics.ensureTestId(modal, mode === 'rotate' ? 'secret-rotate-modal' : 'secret-create-modal'); } setTimeout(function () { if (mode === 'rotate') { stringField.focus(); } else { modalName.focus(); } }, 30); } function closeModal() { modal.classList.remove('open'); state.modalMode = 'create'; state.modalSecretId = null; } function updateKindFields() { var kind = modalKind.value; document.querySelectorAll('[data-kind-field="string"]').forEach(function (element) { element.hidden = !(kind === 'token' || kind === 'header'); }); document.querySelectorAll('[data-kind-field="basic"]').forEach(function (element) { element.hidden = kind !== 'username_password'; }); document.querySelectorAll('[data-kind-field="json"]').forEach(function (element) { element.hidden = kind !== 'generic'; }); if (kind === 'token') { stringLabel.textContent = tKey('secrets.modal.value'); } else if (kind === 'header') { stringLabel.textContent = tKey('secrets.modal.header_value'); } else { stringLabel.textContent = tKey('secrets.modal.value'); } kindHint.textContent = tKey('secrets.hint.' + kind); } function buildSecretValue() { var kind = modalKind.value; if (kind === 'token' || kind === 'header') { var value = stringField.value.trim(); if (!value) { throw new Error(tKey('secrets.validation.value_required')); } return value; } if (kind === 'username_password') { var username = usernameField.value.trim(); var password = passwordField.value; if (!username || !password) { throw new Error(tKey('secrets.validation.username_password_required')); } return { username: username, password: password, }; } try { return JSON.parse(jsonField.value); } catch (_error) { throw new Error(tKey('secrets.validation.json_invalid')); } } function renderSecrets() { var usage = state.derived.usageBySecret; var query = state.search.toLowerCase(); var rows = state.secrets.filter(function (secret) { return !query || secret.name.toLowerCase().includes(query) || String(secret.kind || '').toLowerCase().includes(query); }); summary.textContent = tfKey('secrets.active.subtitle', { active: state.derived.activeSecretCount, total: state.secrets.length, }); window.CrankDom.clear(tbody); if (cardList) { window.CrankDom.clear(cardList); } if (state.loading && state.secrets.length === 0) { var loadingRow = document.createElement('tr'); var loadingCell = document.createElement('td'); loadingCell.colSpan = 7; loadingCell.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);'; loadingCell.textContent = tKey('secrets.loading'); loadingRow.appendChild(loadingCell); tbody.appendChild(loadingRow); renderSecretCards([], tKey('secrets.loading')); return; } if (state.error) { var errorRow = document.createElement('tr'); var errorCell = document.createElement('td'); errorCell.colSpan = 7; errorCell.style.cssText = 'text-align:center;padding:36px;color:var(--danger,#f85149);'; errorCell.textContent = state.error; errorRow.appendChild(errorCell); tbody.appendChild(errorRow); renderSecretCards([], state.error); return; } if (!rows.length) { var emptyRow = document.createElement('tr'); var emptyCell = document.createElement('td'); emptyCell.colSpan = 7; emptyCell.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);'; emptyCell.textContent = state.secrets.length ? tKey('secrets.empty.search') : tKey('secrets.empty.none'); emptyRow.appendChild(emptyCell); tbody.appendChild(emptyRow); renderSecretCards(rows); return; } rows.forEach(function (secret) { var tr = document.createElement('tr'); var references = usage[secret.id] || []; var nameCell = document.createElement('td'); nameCell.className = 'col-name'; nameCell.textContent = secret.name; tr.appendChild(nameCell); var kindCell = document.createElement('td'); kindCell.textContent = secretKindLabel(secret.kind); tr.appendChild(kindCell); var versionCell = document.createElement('td'); versionCell.className = 'col-mono'; versionCell.textContent = 'v' + secret.current_version; tr.appendChild(versionCell); var lastUsedCell = document.createElement('td'); lastUsedCell.textContent = secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never'); tr.appendChild(lastUsedCell); var usedByCell = document.createElement('td'); usedByCell.textContent = window.tPlural('secrets.used_by_count', references.length, { count: references.length }); tr.appendChild(usedByCell); var statusCell = document.createElement('td'); var badge = document.createElement('span'); badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active'); badge.textContent = tKey('secrets.status.' + secret.status); statusCell.appendChild(badge); tr.appendChild(statusCell); var actions = document.createElement('td'); actions.className = 'col-actions'; var rotateButton = document.createElement('button'); rotateButton.type = 'button'; rotateButton.className = 'btn-secondary table-action-btn'; rotateButton.textContent = tKey('secrets.action.rotate'); rotateButton.setAttribute('data-testid', 'secret-rotate-action'); rotateButton.setAttribute('data-secret-id', secret.id); rotateButton.addEventListener('click', function () { openModal('rotate', secret); }); actions.appendChild(rotateButton); var deleteButton = document.createElement('button'); deleteButton.type = 'button'; deleteButton.className = 'btn-danger table-action-btn'; deleteButton.textContent = tKey('secrets.action.delete'); deleteButton.setAttribute('data-testid', 'secret-delete-action'); deleteButton.setAttribute('data-secret-id', secret.id); deleteButton.addEventListener('click', async function () { await deleteSecret(secret); }); actions.appendChild(deleteButton); tr.appendChild(actions); tbody.appendChild(tr); }); renderSecretCards(rows); } function renderSecretCards(rows, statusText) { if (!cardList) { return; } window.CrankDom.clear(cardList); if (statusText && !rows.length) { var messageCard = document.createElement('div'); messageCard.className = 'resource-card'; var message = document.createElement('div'); message.className = 'resource-meta-value'; message.textContent = statusText; message.style.color = state.error ? 'var(--danger,#f85149)' : 'var(--text-muted)'; messageCard.appendChild(message); cardList.appendChild(messageCard); return; } rows.forEach(function(secret) { var references = state.derived.usageBySecret[secret.id] || []; var card = document.createElement('div'); card.className = 'resource-card'; var header = document.createElement('div'); header.className = 'resource-card-header'; var headerBody = document.createElement('div'); var title = document.createElement('div'); title.className = 'resource-card-title'; title.textContent = secret.name; var subtitle = document.createElement('div'); subtitle.className = 'resource-card-subtitle'; subtitle.textContent = secretKindLabel(secret.kind); headerBody.appendChild(title); headerBody.appendChild(subtitle); var actions = document.createElement('div'); actions.className = 'resource-card-actions'; var badge = document.createElement('span'); badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active'); badge.textContent = tKey('secrets.status.' + secret.status); actions.appendChild(badge); header.appendChild(headerBody); header.appendChild(actions); card.appendChild(header); var metaGrid = document.createElement('div'); metaGrid.className = 'resource-meta-grid'; metaGrid.appendChild(buildSecretMetaItem('secrets.th.version', 'v' + secret.current_version)); metaGrid.appendChild(buildSecretMetaItem( 'secrets.th.last_used', secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never') )); metaGrid.appendChild(buildSecretMetaItem( 'secrets.th.used_by', window.tPlural('secrets.used_by_count', references.length, { count: references.length }) )); card.appendChild(metaGrid); var actionRow = document.createElement('div'); actionRow.className = 'resource-card-actions'; var rotateButton = document.createElement('button'); rotateButton.className = 'btn-secondary'; rotateButton.type = 'button'; rotateButton.textContent = tKey('secrets.action.rotate'); rotateButton.addEventListener('click', function() { openModal('rotate', secret); }); actionRow.appendChild(rotateButton); var deleteButton = document.createElement('button'); deleteButton.className = 'btn-secondary'; deleteButton.type = 'button'; deleteButton.textContent = tKey('secrets.action.delete'); deleteButton.addEventListener('click', async function() { await deleteSecret(secret); }); actionRow.appendChild(deleteButton); card.appendChild(actionRow); cardList.appendChild(card); }); } function buildSecretMetaItem(labelKey, valueText) { var item = document.createElement('div'); item.className = 'resource-meta-item'; var label = document.createElement('div'); label.className = 'resource-meta-label'; label.textContent = tKey(labelKey); var value = document.createElement('div'); value.className = 'resource-meta-value'; value.textContent = valueText; item.appendChild(label); item.appendChild(value); return item; } async function load() { state.workspaceId = currentWorkspaceId(); state.loading = true; state.error = ''; renderSecrets(); if (!state.workspaceId) { state.secrets = []; state.profiles = []; recomputeDerivedState(); state.loading = false; state.error = tKey('secrets.error.workspace'); renderSecrets(); return; } try { var results = await Promise.all([ window.CrankApi.listSecrets(state.workspaceId), window.CrankApi.listAuthProfiles(state.workspaceId), ]); state.secrets = (results[0] && results[0].items) || []; state.profiles = (results[1] && results[1].items) || []; recomputeDerivedState(); } catch (error) { state.error = error.message || tKey('secrets.error.load'); state.secrets = []; state.profiles = []; recomputeDerivedState(); } finally { state.loading = false; renderSecrets(); } } async function deleteSecret(secret) { if (!confirm(tfKey('secrets.confirm.delete', { name: secret.name }))) { return; } try { await window.CrankApi.deleteSecret(state.workspaceId, secret.id); await load(); if (window.CrankUi) { window.CrankUi.success( tfKey('secrets.toast.delete_message', { name: secret.name }), tKey('secrets.toast.delete_title') ); } } catch (error) { if (window.CrankUi) { window.CrankUi.error( error.message || tKey('secrets.toast.delete_error_message'), tKey('secrets.toast.delete_error_title') ); } } } async function submitModal() { var kind = modalKind.value; var value = buildSecretValue(); modalSubmit.disabled = true; modalSubmit.textContent = state.modalMode === 'rotate' ? tKey('secrets.modal.rotating') : tKey('secrets.modal.creating'); try { if (state.modalMode === 'rotate') { await window.CrankApi.rotateSecret(state.workspaceId, state.modalSecretId, { value: value }); if (window.CrankUi) { window.CrankUi.success( tKey('secrets.toast.rotate_message'), tKey('secrets.toast.rotate_title') ); } } else { var name = modalName.value.trim(); if (!name) { throw new Error(tKey('secrets.validation.name_required')); } await window.CrankApi.createSecret(state.workspaceId, { name: name, kind: kind, value: value, }); if (window.CrankUi) { window.CrankUi.success( tKey('secrets.toast.create_message'), tKey('secrets.toast.create_title') ); } } closeModal(); await load(); } catch (error) { if (window.CrankUi) { window.CrankUi.error( error.message || tKey(state.modalMode === 'rotate' ? 'secrets.toast.rotate_error_message' : 'secrets.toast.create_error_message'), tKey(state.modalMode === 'rotate' ? 'secrets.toast.rotate_error_title' : 'secrets.toast.create_error_title') ); } } finally { modalSubmit.disabled = false; modalSubmit.textContent = state.modalMode === 'rotate' ? tKey('secrets.modal.rotate_action') : tKey('secrets.modal.create_action'); } } document.getElementById('btn-create-secret').addEventListener('click', function () { openModal('create'); }); document.getElementById('secret-modal-close-btn').addEventListener('click', closeModal); document.getElementById('secret-modal-cancel-btn').addEventListener('click', closeModal); modal.addEventListener('click', function (event) { if (event.target === modal) { closeModal(); } }); modalKind.addEventListener('change', updateKindFields); modalSubmit.addEventListener('click', function () { void submitModal(); }); searchInput.addEventListener('input', function (event) { state.search = event.target.value || ''; renderSecrets(); }); window.addEventListener('crank:workspacechange', function () { void load(); }); updateKindFields(); void (async function bootSecretsPage() { await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve()); await load(); }()); } if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') { window.CrankDiagnostics.bootstrap('secrets', initSecretsPage); } else { document.addEventListener('DOMContentLoaded', initSecretsPage); }