var settingsSession = null; var settingsCapabilities = null; function tKey(key) { return typeof t === 'function' ? t(key) : key; } function tfKey(key, vars) { return typeof tf === 'function' ? tf(key, vars) : key; } function settingsWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function initials(displayName, email) { var source = safeIdentityText(displayName, 80, '') || safeIdentityText(email, 254, '') || 'Crank'; return source .split(/\s+/) .filter(Boolean) .slice(0, 2) .map(function(part) { return part.charAt(0).toUpperCase(); }) .join('') || 'CR'; } function safeIdentityText(value, maxLength, fallback) { var text = String(value || '') .replace(/[\r\n\t]+/g, ' ') .replace(/\s{2,}/g, ' ') .trim(); if (!text || /<\s*(?:!doctype|html|head|body|nav|script)\b/i.test(text)) { return fallback || ''; } if (text.length > maxLength) { return fallback || ''; } return text; } function setStatus(id, text, isError) { var node = document.getElementById(id); if (!node) return; node.textContent = text || ''; node.style.color = text ? (isError ? 'var(--red)' : 'var(--green)') : 'var(--text-muted)'; } function titleCaseRole(role) { var normalized = String(role || 'viewer').toLowerCase(); if (normalized === 'operator') return tKey('workspace_setup.role.operator'); if (normalized === 'owner') return tKey('workspace_setup.role.owner'); if (normalized === 'admin') return tKey('workspace_setup.role.admin'); if (normalized === 'viewer') return tKey('workspace_setup.role.viewer'); return normalized.replace(/^\w/, function(character) { return character.toUpperCase(); }); } function populateCurrentSession(session) { var summary = document.getElementById('settings-session-summary'); if (!summary) { return; } if (!(session && session.user)) { summary.textContent = tKey('settings.session.unavailable'); return; } var membership = null; if (Array.isArray(session.memberships)) { membership = session.memberships.find(function(item) { return item.workspace && item.workspace.id === session.current_workspace_id; }) || session.memberships[0] || null; } var workspaceLabel = membership && membership.workspace ? (membership.workspace.display_name || membership.workspace.slug || membership.workspace.id) : tKey('settings.session.current_workspace'); var roleLabel = membership ? titleCaseRole(membership.role) : tKey('workspace_setup.role.viewer'); summary.textContent = tfKey('settings.session.summary', { email: session.user.email, role: roleLabel, workspace: workspaceLabel, }); } function capabilityLabel(key) { return tKey('settings.capability.' + key); } function populateNotificationsCapabilityHint(capabilities) { var title = document.getElementById('settings-notifications-capability-title'); var body = document.getElementById('settings-notifications-capability-body'); if (!title || !body || !capabilities) { return; } title.textContent = tKey('settings.notifications.capability_title_' + capabilities.edition); body.textContent = tfKey('settings.notifications.capability_body_' + capabilities.edition, { edition: capabilities.edition, }); } function populateCapabilities(capabilities) { var title = document.getElementById('settings-security-capability-title'); var body = document.getElementById('settings-security-capability-body'); if (!title || !body || !capabilities) { return; } var protocols = (capabilities.supported_protocols || []).map(capabilityLabel).join(', '); var accessModes = (capabilities.machine_access_modes || []).map(capabilityLabel).join(', '); var securityLevels = (capabilities.supported_security_levels || []).map(capabilityLabel).join(', '); var limits = capabilities.limits || {}; title.textContent = tKey('settings.security.capability_title_' + capabilities.edition); body.textContent = tfKey('settings.security.capability_summary', { protocols: protocols || capabilityLabel('none'), access_modes: accessModes || capabilityLabel('none'), security_levels: securityLevels || capabilityLabel('none'), max_workspaces: limits.max_workspaces == null ? capabilityLabel('unlimited') : String(limits.max_workspaces), max_users: limits.max_users_per_workspace == null ? capabilityLabel('unlimited') : String(limits.max_users_per_workspace), max_agents: limits.max_agents_per_workspace == null ? capabilityLabel('unlimited') : String(limits.max_agents_per_workspace), }); populateNotificationsCapabilityHint(capabilities); } function populateProfile(session) { if (!session || !session.user) { return; } var user = session.user; var displayName = safeIdentityText(user.display_name, 80, 'Crank'); var userEmail = safeIdentityText(user.email, 254, ''); document.getElementById('profile-avatar').textContent = initials(displayName, userEmail); document.getElementById('profile-display-name').textContent = displayName || 'Crank'; document.getElementById('profile-email').textContent = userEmail; var firstName = document.getElementById('field-firstname'); var email = document.getElementById('field-email'); if (firstName) { firstName.value = displayName === 'Crank' ? '' : displayName; } if (email) { email.value = userEmail; } populateCurrentSession(session); } async function loadProfile() { if (!window.CrankAuth || !window.CrankApi) { return; } settingsSession = await window.CrankAuth.fetchSession(true); populateProfile(settingsSession); } async function loadCapabilities() { if (!window.CrankApi || typeof window.CrankApi.getCapabilities !== 'function') { return; } try { settingsCapabilities = await window.CrankApi.getCapabilities(); populateCapabilities(settingsCapabilities); } catch (_error) { } } async function renderOverlaySlots() { if (!window.CrankOverlay || typeof window.CrankOverlay.render !== 'function') { return; } await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve()); await window.CrankOverlay.render(document, { workspace: window.getCurrentWorkspace ? window.getCurrentWorkspace() : null, capabilities: settingsCapabilities, locale: localStorage.getItem('crank_lang') || 'en', }); } function bindProfileSave() { var button = document.getElementById('settings-profile-save-btn'); if (!button || button.dataset.bound === 'true') { return; } button.dataset.bound = 'true'; button.addEventListener('click', async function() { var original = button.textContent; button.disabled = true; button.textContent = tKey('settings.profile.saving'); setStatus('settings-profile-status', '', false); try { var session = await window.CrankApi.updateProfile({ display_name: document.getElementById('field-firstname').value.trim(), email: document.getElementById('field-email').value.trim(), }); settingsSession = session; if (window.CrankAuth && typeof window.CrankAuth.replaceSession === 'function') { window.CrankAuth.replaceSession(session); } populateProfile(session); setStatus('settings-profile-status', tKey('settings.profile.saved_status'), false); button.textContent = tKey('settings.profile.saved'); } catch (error) { setStatus('settings-profile-status', error.message || tKey('settings.profile.save_error'), true); button.textContent = original; } finally { setTimeout(function() { button.disabled = false; button.textContent = original; }, 1200); } }); } function bindPasswordSave() { var button = document.getElementById('settings-password-save-btn'); if (!button || button.dataset.bound === 'true') { return; } button.dataset.bound = 'true'; button.addEventListener('click', async function() { var currentPassword = document.getElementById('security-current-password').value; var newPassword = document.getElementById('security-new-password').value; var confirmPassword = document.getElementById('security-confirm-password').value; if (newPassword !== confirmPassword) { setStatus('settings-password-status', tKey('settings.security.mismatch'), true); return; } var original = button.textContent; button.disabled = true; button.textContent = tKey('settings.profile.saving'); setStatus('settings-password-status', '', false); try { await window.CrankApi.changePassword({ current_password: currentPassword, new_password: newPassword, }); document.getElementById('security-current-password').value = ''; document.getElementById('security-new-password').value = ''; document.getElementById('security-confirm-password').value = ''; setStatus('settings-password-status', tKey('settings.security.saved'), false); button.textContent = tKey('settings.profile.saved'); } catch (error) { setStatus('settings-password-status', error.message || tKey('settings.security.save_error'), true); button.textContent = original; } finally { setTimeout(function() { button.disabled = false; button.textContent = original; }, 1200); } }); } function bindSectionNavigation() { var navItems = document.querySelectorAll('.settings-nav-item[data-section]'); navItems.forEach(function (button) { button.addEventListener('click', function () { navItems.forEach(function (item) { item.classList.remove('active'); }); this.classList.add('active'); var target = document.getElementById('section-' + this.dataset.section); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); var initialSection = window.location.hash ? window.location.hash.slice(1) : 'profile'; var initialButton = document.querySelector('.settings-nav-item[data-section="' + initialSection + '"]') || document.querySelector('.settings-nav-item[data-section="profile"]'); if (initialButton) { setTimeout(function () { initialButton.click(); }, 50); } var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) { return; } var section = entry.target.id.replace('section-', ''); document.querySelectorAll('.settings-nav-item[data-section]').forEach(function (button) { button.classList.toggle('active', button.dataset.section === section); }); }); }, { threshold: 0.3 }); document.querySelectorAll('[id^="section-"]').forEach(function (section) { if (!section.hidden) { observer.observe(section); } }); } async function loadWorkspaceSettings() { if (!window.CrankApi || !window.whenWorkspacesReady) { return; } await window.whenWorkspacesReady(); var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; if (!workspace) { return; } try { var record = await window.CrankApi.getWorkspace(workspace.id); var item = record.workspace; var settings = item.settings || {}; document.getElementById('settings-ws-slug').value = item.slug || ''; document.getElementById('settings-ws-display-name').value = item.display_name || ''; document.getElementById('settings-ws-description').value = settings.description || ''; var saveButton = document.getElementById('settings-ws-save-btn'); if (saveButton.dataset.bound === 'true') { return; } saveButton.dataset.bound = 'true'; saveButton.addEventListener('click', async function () { var saveButton = this; var original = saveButton.textContent; saveButton.disabled = true; saveButton.textContent = tKey('settings.workspace.saving'); try { await window.CrankApi.updateWorkspace(item.id, { slug: document.getElementById('settings-ws-slug').value.trim(), display_name: document.getElementById('settings-ws-display-name').value.trim(), settings: { description: document.getElementById('settings-ws-description').value.trim(), color: (workspace.settings && workspace.settings.color) || '#0d9488', }, }); if (window.refreshWorkspaces) { var workspaces = await window.refreshWorkspaces(); var updated = workspaces.find(function (entry) { return entry.id === item.id; }); if (updated && window.setCurrentWorkspace) { await window.setCurrentWorkspace(updated); } } saveButton.textContent = tKey('settings.workspace.saved'); } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || tKey('settings.workspace.save_error'), tKey('settings.workspace.save_error_title')); } saveButton.textContent = original; } finally { setTimeout(function () { saveButton.disabled = false; saveButton.textContent = original; }, 1200); } }); } catch (_error) { } } async function initSettingsPage() { bindSectionNavigation(); await loadProfile(); await loadCapabilities(); bindProfileSave(); bindPasswordSave(); await loadWorkspaceSettings(); await renderOverlaySlots(); } document.addEventListener('DOMContentLoaded', function () { void initSettingsPage(); });