(function () { const root = document.getElementById("qortal-admin-root"); if (!root) { return; } const statusUrl = root.dataset.statusUrl; const setupUrl = root.dataset.setupUrl; const setupPlanUrl = root.dataset.setupPlanUrl; const setupOccUrl = root.dataset.setupOccUrl; const notifyUrl = root.dataset.notifyUrl; const searchUsersUrl = root.dataset.searchUsersUrl; const searchGroupsUrl = root.dataset.searchGroupsUrl; const settingsUrl = root.dataset.settingsUrl; const walletsUrl = root.dataset.walletsUrl; const createWalletUrl = root.dataset.createWalletUrl; const registerExternalAuthUrl = root.dataset.registerExternalAuthUrl; const mappingsUrl = root.dataset.mappingsUrl; const linkMappingUrl = root.dataset.linkMappingUrl; const unlinkMappingUrl = root.dataset.unlinkMappingUrl; const allowlistUrl = root.dataset.allowlistUrl; const addAllowlistUrl = root.dataset.addAllowlistUrl; const removeAllowlistUrl = root.dataset.removeAllowlistUrl; const invitesUrl = root.dataset.invitesUrl; const createInviteUrl = root.dataset.createInviteUrl; const revokeInviteUrl = root.dataset.revokeInviteUrl; const qappsJson = root.dataset.qappsJson || "[]"; const qappsEnabledValue = root.dataset.qappsEnabled === "1"; const qappsBrowserEnabledValue = root.dataset.qappsBrowserEnabled === "1"; const qappsBrowserAddressValue = root.dataset.qappsBrowserAddress || ""; const qappsDebugEnabledValue = root.dataset.qappsDebugEnabled === "1"; const nextcloudPublicUrlValue = root.dataset.nextcloudPublicUrl || ""; const feedbackEl = document.getElementById("qortal-admin-feedback"); const statusEl = document.getElementById("qortal-admin-status"); const saveButton = document.getElementById("qortal-save-settings"); const refreshSetupButton = document.getElementById("qortal-refresh-setup"); const testButton = document.getElementById("qortal-test-connection"); const setupPlanButton = document.getElementById("qortal-setup-plan"); const setupOccButton = document.getElementById("qortal-setup-occ"); const sendNotificationsButton = document.getElementById( "qortal-send-notifications" ); const searchUsersButton = document.getElementById( "qortal-user-search-button" ); const searchGroupsButton = document.getElementById( "qortal-group-search-button" ); const setupOverviewList = document.getElementById( "qortal-setup-overview-list" ); const setupOverviewNote = document.getElementById( "qortal-setup-overview-note" ); const setupComponentsCard = document.getElementById( "qortal-setup-components" ); const allowlistCard = document.getElementById("qortal-allowlist-card"); const invitesCard = document.getElementById("qortal-invites-card"); const inviteQortalCard = document.getElementById("qortal-invite-qortal-card"); const oidcPolicyMode = document.getElementById("qortal-oidc-policy-mode"); const oidcGuard = document.getElementById("qortal-oidc-guard"); const oidcInviteTtl = document.getElementById("qortal-oidc-invite-ttl"); const oidcRequireEmail = document.getElementById("qortal-oidc-require-email"); const oidcRedirects = document.getElementById("qortal-oidc-redirects"); const oidcPolicyNote = document.getElementById("qortal-oidc-policy-note"); const oidcPolicySelect = document.getElementById("qortal-oidc-policy-select"); const oidcGuardToggle = document.getElementById("qortal-oidc-guard-toggle"); const oidcInviteInput = document.getElementById( "qortal-oidc-invite-ttl-input" ); const oidcRequireEmailToggle = document.getElementById( "qortal-oidc-require-email-toggle" ); const oidcRedirectInput = document.getElementById( "qortal-oidc-redirect-allowlist-input" ); const oidcEnvButton = document.getElementById("qortal-oidc-env-generate"); const oidcEnvOutput = document.getElementById("qortal-oidc-env-output"); const externalAuthBaseUrlInput = document.getElementById( "qortal-external-auth-base-url" ); const brokerInternalApiTokenInput = document.getElementById( "qortal-broker-internal-api-token" ); const externalAuthAppIdInput = document.getElementById( "qortal-external-auth-app-id" ); const externalAuthAppSecretInput = document.getElementById( "qortal-external-auth-app-secret" ); const externalAuthNodeUrlInput = document.getElementById( "qortal-external-auth-node-url" ); const externalAuthNodeApiKeyInput = document.getElementById( "qortal-external-auth-node-api-key" ); const externalAuthNodeApiKeyModeInput = document.getElementById( "qortal-external-auth-node-api-key-mode" ); const externalAuthNodeApiKeyPathsInput = document.getElementById( "qortal-external-auth-node-api-key-paths" ); const externalAuthEnvButton = document.getElementById( "qortal-external-auth-env-generate" ); const externalAuthEnvOutput = document.getElementById( "qortal-external-auth-env-output" ); const externalAuthAppNameInput = document.getElementById( "qortal-external-auth-app-name" ); const externalAuthRegisterButton = document.getElementById( "qortal-external-auth-register" ); const qortalNodeUrlInput = document.getElementById("qortal-node-url"); const qortalNodeApiKeyInput = document.getElementById("qortal-node-api-key"); const qortalGatewayUrlInput = document.getElementById("qortal-gateway-url"); const qortalGatewayInsecureInput = document.getElementById( "qortal-gateway-insecure" ); const qortalNodeEnvButton = document.getElementById( "qortal-node-env-generate" ); const qortalNodeEnvOutput = document.getElementById("qortal-node-env-output"); const createWalletButton = document.getElementById("qortal-create-wallet"); const createWalletLinkButton = document.getElementById( "qortal-create-wallet-link" ); const refreshWalletsButton = document.getElementById( "qortal-refresh-wallets" ); const walletUserIdInput = document.getElementById("qortal-wallet-user-id"); const linkMappingButton = document.getElementById("qortal-link-mapping"); const refreshMappingsButton = document.getElementById( "qortal-refresh-mappings" ); const addAllowlistButton = document.getElementById("qortal-add-allowlist"); const refreshAllowlistButton = document.getElementById( "qortal-refresh-allowlist" ); const createInviteButton = document.getElementById("qortal-create-invite"); const refreshInvitesButton = document.getElementById( "qortal-refresh-invites" ); const walletCreateResult = document.getElementById( "qortal-wallet-create-result" ); const walletsBody = document.getElementById("qortal-wallets-body"); const mappingsBody = document.getElementById("qortal-mappings-body"); const allowlistBody = document.getElementById("qortal-allowlist-body"); const inviteCreateResult = document.getElementById( "qortal-invite-create-result" ); const invitesBody = document.getElementById("qortal-invites-body"); const qappsEnabledInput = document.getElementById("qortal-qapps-enabled"); const qappsBrowserEnabledInput = document.getElementById( "qortal-qapps-browser-enabled" ); const qappsBrowserAddressInput = document.getElementById( "qortal-qapps-browser-address" ); const qappsDebugEnabledInput = document.getElementById( "qortal-qapps-debug-enabled" ); const qappsNameInput = document.getElementById("qortal-qapps-name"); const qappsAddressInput = document.getElementById("qortal-qapps-address"); const qappsIconModeInput = document.getElementById("qortal-qapps-icon-mode"); const qappsIconUrlInput = document.getElementById("qortal-qapps-icon-url"); const qappsDescriptionInput = document.getElementById( "qortal-qapps-description" ); const addQappButton = document.getElementById("qortal-add-qapp"); const clearQappsButton = document.getElementById("qortal-clear-qapps"); const qappsBody = document.getElementById("qortal-qapps-body"); const oidcIssuerInput = document.getElementById("qortal-oidc-issuer-url"); const oidcClientIdInput = document.getElementById("qortal-oidc-client-id"); const oidcClientSecretInput = document.getElementById( "qortal-oidc-client-secret" ); const nextcloudPublicUrlInput = document.getElementById( "qortal-nextcloud-public-url" ); const setupResult = document.getElementById("qortal-setup-result"); const inviteMessageButton = document.getElementById( "qortal-generate-invite-message" ); const copyInviteMessageButton = document.getElementById( "qortal-copy-invite-message" ); const inviteMessageBox = document.getElementById("qortal-invite-message"); const notifyUserIds = document.getElementById("qortal-notify-user-ids"); const notifyGroupIds = document.getElementById("qortal-notify-group-ids"); const notifyEmail = document.getElementById("qortal-notify-email"); const notifyInApp = document.getElementById("qortal-notify-inapp"); const notifyQueue = document.getElementById("qortal-notify-queue"); const notifyIncludeInvite = document.getElementById( "qortal-notify-include-invite" ); const notifyResult = document.getElementById("qortal-notify-result"); const notifyEmailSubject = document.getElementById( "qortal-notify-email-subject" ); const notifyEmailBody = document.getElementById("qortal-notify-email-body"); const userSearchQuery = document.getElementById("qortal-user-search-query"); const userSearchResults = document.getElementById( "qortal-user-search-results" ); const groupSearchQuery = document.getElementById("qortal-group-search-query"); const groupSearchResults = document.getElementById( "qortal-group-search-results" ); const previewEmailButton = document.getElementById("qortal-preview-email"); const previewSubject = document.getElementById( "qortal-email-preview-subject" ); const previewBody = document.getElementById("qortal-email-preview-body"); if ( !statusUrl || !setupUrl || !setupPlanUrl || !setupOccUrl || !notifyUrl || !searchUsersUrl || !searchGroupsUrl || !settingsUrl || !walletsUrl || !createWalletUrl || !registerExternalAuthUrl || !mappingsUrl || !linkMappingUrl || !unlinkMappingUrl || !allowlistUrl || !addAllowlistUrl || !removeAllowlistUrl || !invitesUrl || !createInviteUrl || !revokeInviteUrl ) { return; } if ( !feedbackEl || !statusEl || !saveButton || !refreshSetupButton || !testButton || !setupPlanButton || !setupOccButton || !sendNotificationsButton || !searchUsersButton || !searchGroupsButton || !setupOverviewList || !setupOverviewNote || !setupComponentsCard || !allowlistCard || !invitesCard || !inviteQortalCard || !oidcPolicyMode || !oidcGuard || !oidcInviteTtl || !oidcRequireEmail || !oidcRedirects || !oidcPolicyNote || !oidcPolicySelect || !oidcGuardToggle || !oidcInviteInput || !oidcRequireEmailToggle || !oidcRedirectInput || !oidcEnvButton || !oidcEnvOutput || !externalAuthBaseUrlInput || !brokerInternalApiTokenInput || !externalAuthAppIdInput || !externalAuthAppSecretInput || !externalAuthNodeUrlInput || !externalAuthNodeApiKeyInput || !externalAuthNodeApiKeyModeInput || !externalAuthNodeApiKeyPathsInput || !externalAuthEnvButton || !externalAuthEnvOutput || !externalAuthAppNameInput || !externalAuthRegisterButton || !qortalNodeUrlInput || !qortalNodeApiKeyInput || !qortalGatewayUrlInput || !qortalGatewayInsecureInput || !qortalNodeEnvButton || !qortalNodeEnvOutput || !createWalletButton || !createWalletLinkButton || !refreshWalletsButton || !walletUserIdInput || !linkMappingButton || !refreshMappingsButton || !walletCreateResult || !walletsBody || !mappingsBody || !addAllowlistButton || !refreshAllowlistButton || !createInviteButton || !refreshInvitesButton || !allowlistBody || !inviteCreateResult || !invitesBody || !qappsEnabledInput || !qappsBrowserEnabledInput || !qappsBrowserAddressInput || !qappsDebugEnabledInput || !qappsNameInput || !qappsAddressInput || !qappsIconModeInput || !qappsIconUrlInput || !qappsDescriptionInput || !addQappButton || !clearQappsButton || !qappsBody || !oidcIssuerInput || !oidcClientIdInput || !oidcClientSecretInput || !nextcloudPublicUrlInput || !setupResult || !inviteMessageButton || !copyInviteMessageButton || !inviteMessageBox || !notifyUserIds || !notifyGroupIds || !notifyEmail || !notifyInApp || !notifyQueue || !notifyIncludeInvite || !notifyResult || !notifyEmailSubject || !notifyEmailBody || !userSearchQuery || !userSearchResults || !groupSearchQuery || !groupSearchResults || !previewEmailButton || !previewSubject || !previewBody ) { return; } const headers = { requesttoken: OC.requestToken, }; let lastUserResults = []; let lastSetupPlan = null; let autoProvisionEnabled = false; let autoProvisionGuardEnabled = false; let hasUnsavedChanges = false; let baselineSnapshot = null; let qapps = []; try { const parsed = JSON.parse(qappsJson); if (Array.isArray(parsed)) { qapps = parsed .filter(function (entry) { return ( entry && typeof entry.address === "string" && entry.address.length > 0 ); }) .map(function (entry) { return { name: typeof entry.name === "string" ? entry.name : "", address: entry.address, description: typeof entry.description === "string" ? entry.description : "", iconMode: typeof entry.iconMode === "string" ? entry.iconMode : "auto", iconUrl: typeof entry.iconUrl === "string" ? entry.iconUrl : "", }; }); } } catch (error) { qapps = []; } qappsEnabledInput.checked = qappsEnabledValue; qappsBrowserEnabledInput.checked = qappsBrowserEnabledValue; qappsBrowserAddressInput.value = qappsBrowserAddressValue; qappsDebugEnabledInput.checked = qappsDebugEnabledValue; qappsIconModeInput.value = "auto"; qappsIconUrlInput.value = ""; updateIconUrlState(); oidcEnvOutput.textContent = buildEnvSnippet(); externalAuthEnvOutput.textContent = buildExternalAuthEnvSnippet(); qortalNodeEnvOutput.textContent = buildQortalNodeEnvSnippet(); baselineSnapshot = JSON.stringify(collectSettings()); function markDirty() { hasUnsavedChanges = true; } function updateIconUrlState() { const isCustom = qappsIconModeInput.value === "custom"; qappsIconUrlInput.disabled = !isCustom; if (!isCustom) { qappsIconUrlInput.value = ""; } } function resetDirty() { hasUnsavedChanges = false; baselineSnapshot = JSON.stringify(collectSettings()); } function setFeedback(message, isError) { feedbackEl.textContent = message; feedbackEl.classList.toggle("error", Boolean(isError)); feedbackEl.classList.toggle("success", !isError && message.length > 0); } function collectSettings() { return { brokerBaseUrl: document.getElementById("qortal-broker-base-url").value, brokerInternalApiToken: brokerInternalApiTokenInput.value, externalAuthDocsUrl: document.getElementById( "qortal-external-auth-docs-url" ).value, externalAuthBaseUrl: externalAuthBaseUrlInput.value, externalAuthAppId: externalAuthAppIdInput.value, externalAuthAppSecret: externalAuthAppSecretInput.value, externalAuthNodeUrl: externalAuthNodeUrlInput.value, externalAuthNodeApiKey: externalAuthNodeApiKeyInput.value, externalAuthNodeApiKeyMode: externalAuthNodeApiKeyModeInput.value, externalAuthNodeApiKeyPaths: externalAuthNodeApiKeyPathsInput.value, featureQdnBackups: document.getElementById("qortal-feature-qdn-backups") .checked ? "1" : "", featureQmail: document.getElementById("qortal-feature-qmail").checked ? "1" : "", oidcIssuerUrl: oidcIssuerInput.value, oidcClientId: oidcClientIdInput.value, oidcClientSecret: oidcClientSecretInput.value, oidcPolicyMode: oidcPolicySelect.value, oidcAutoProvisionGuard: oidcGuardToggle.value, oidcInviteTtlSeconds: oidcInviteInput.value.trim(), oidcRequireEmailForNewAccount: oidcRequireEmailToggle.value, oidcRedirectAllowlist: oidcRedirectInput.value, nextcloudPublicUrl: nextcloudPublicUrlInput.value, qortalNodeUrl: qortalNodeUrlInput.value, qortalNodeApiKey: qortalNodeApiKeyInput.value, qortalGatewayUrl: qortalGatewayUrlInput.value, qortalGatewayAllowInsecure: qortalGatewayInsecureInput.checked ? "1" : "", notifyEmailSubject: notifyEmailSubject.value, notifyEmailBody: notifyEmailBody.value, qappsEnabled: qappsEnabledInput.checked ? "1" : "", qappsFullBrowserEnabled: qappsBrowserEnabledInput.checked ? "1" : "", qappsFullBrowserAddress: qappsBrowserAddressInput.value, qappsDebugEnabled: qappsDebugEnabledInput.checked ? "1" : "", qappsList: JSON.stringify(qapps), }; } async function requestJson(url, options) { const response = await fetch(url, options); const payload = await response.json(); if (!response.ok) { throw new Error(payload.error || "Request failed"); } return payload; } function assertBrokerOk(payload, fallbackError) { if (payload && payload.ok === false) { throw new Error(payload.error || fallbackError); } } function formatTimestamp(value) { if (!value) { return ""; } try { return new Date(value).toLocaleString(); } catch (error) { return String(value); } } function renderStatusItem(label, status, detail) { const li = document.createElement("li"); const dot = document.createElement("span"); dot.className = "qortal-status-dot " + status; const text = document.createElement("span"); text.textContent = detail ? `${label}: ${detail}` : label; li.appendChild(dot); li.appendChild(text); return li; } function normalizeGuardMode(value) { if (value === true) { return "invite_or_allowlist"; } if (value === false) { return "off"; } if (typeof value !== "string") { return ""; } const raw = value.trim().toLowerCase(); if (["invite_or_allowlist", "enabled", "on", "true", "1"].includes(raw)) { return "invite_or_allowlist"; } if (["off", "disabled", "none", "false", "0"].includes(raw)) { return "off"; } return ""; } function isGuardEnabled(value) { return normalizeGuardMode(value) === "invite_or_allowlist"; } function extractBrokerError(config) { if (!config || typeof config !== "object") { return ""; } if ( config.ok === false && typeof config.error === "string" && config.error.trim() !== "" ) { return config.error; } if ( config.data && typeof config.data === "object" && typeof config.data.error === "string" && config.data.error.trim() !== "" ) { return config.data.error; } return ""; } function renderSetupOverview(payload) { setupOverviewList.innerHTML = ""; setupOverviewNote.textContent = ""; const brokerHealth = payload && payload.status ? payload.status.brokerHealth : null; const qortalHealth = payload && payload.status ? payload.status.qortalHealth : null; const rawConfig = payload ? payload.oidcConfig : null; const oidcConfig = rawConfig && rawConfig.ok !== false ? rawConfig.data || rawConfig : null; if (brokerHealth && brokerHealth.ok !== false) { setupOverviewList.appendChild(renderStatusItem("Broker reachable", "ok")); } else { setupOverviewList.appendChild( renderStatusItem( "Broker reachable", "error", brokerHealth && brokerHealth.error ? brokerHealth.error : "unreachable" ) ); } if (qortalHealth && qortalHealth.ok !== false) { setupOverviewList.appendChild( renderStatusItem("External Auth reachable", "ok") ); } else { setupOverviewList.appendChild( renderStatusItem( "External Auth reachable", "error", qortalHealth && qortalHealth.error ? qortalHealth.error : "unreachable" ) ); } const externalAuthBaseUrl = externalAuthBaseUrlInput.value.trim(); const externalAuthAppId = externalAuthAppIdInput.value.trim(); const externalAuthAppSecret = externalAuthAppSecretInput.value.trim(); const externalAuthNodeUrl = externalAuthNodeUrlInput.value.trim(); const externalAuthNodeApiKey = externalAuthNodeApiKeyInput.value.trim(); const externalAuthNodeApiKeyMode = externalAuthNodeApiKeyModeInput.value.trim(); const externalAuthNodeApiKeyPaths = externalAuthNodeApiKeyPathsInput.value.trim(); const qortalNodeApiKey = qortalNodeApiKeyInput.value.trim(); if (externalAuthBaseUrl) { setupOverviewList.appendChild( renderStatusItem("External Auth Base URL", "ok", externalAuthBaseUrl) ); } else { setupOverviewList.appendChild( renderStatusItem("External Auth Base URL", "warn", "missing") ); } if (externalAuthAppId && externalAuthAppSecret) { setupOverviewList.appendChild( renderStatusItem("External Auth app credentials", "ok") ); } else { setupOverviewList.appendChild( renderStatusItem("External Auth app credentials", "warn", "missing") ); } if (externalAuthNodeUrl) { setupOverviewList.appendChild( renderStatusItem("External Auth Qortal node", "ok", externalAuthNodeUrl) ); } else { setupOverviewList.appendChild( renderStatusItem("External Auth Qortal node", "warn", "missing") ); } if (externalAuthNodeApiKey) { setupOverviewList.appendChild( renderStatusItem("External Auth node API key", "ok", "set") ); } else if (qortalNodeApiKey) { setupOverviewList.appendChild( renderStatusItem( "External Auth node API key", "warn", "using Qortal node key fallback" ) ); } else if (externalAuthNodeUrl) { setupOverviewList.appendChild( renderStatusItem("External Auth node API key", "warn", "missing") ); } if (externalAuthNodeApiKeyMode) { setupOverviewList.appendChild( renderStatusItem( "External Auth key mode", "ok", externalAuthNodeApiKeyMode ) ); } if (externalAuthNodeApiKeyPaths) { setupOverviewList.appendChild( renderStatusItem( "External Auth key paths", "ok", externalAuthNodeApiKeyPaths ) ); } const qortalNodeUrl = qortalNodeUrlInput.value.trim(); const qortalGatewayUrl = qortalGatewayUrlInput.value.trim(); const qortalGatewayAllowInsecure = qortalGatewayInsecureInput.checked; if (qortalNodeUrl) { setupOverviewList.appendChild( renderStatusItem("Qortal node URL", "ok", qortalNodeUrl) ); } else { setupOverviewList.appendChild( renderStatusItem("Qortal node URL", "warn", "missing") ); } if (qortalNodeApiKey) { setupOverviewList.appendChild( renderStatusItem("Qortal node API key", "ok", "set") ); } else { setupOverviewList.appendChild( renderStatusItem("Qortal node API key", "warn", "missing") ); } if (qortalGatewayUrl) { setupOverviewList.appendChild( renderStatusItem("Gateway node URL", "ok", qortalGatewayUrl) ); } else { setupOverviewList.appendChild( renderStatusItem("Gateway node URL", "warn", "missing") ); } if (qortalGatewayAllowInsecure) { setupOverviewList.appendChild( renderStatusItem("Gateway TLS verification", "warn", "disabled") ); } const publicUrl = nextcloudPublicUrlInput.value.trim(); if (publicUrl) { setupOverviewList.appendChild( renderStatusItem("Nextcloud public URL set", "ok", publicUrl) ); } else { setupOverviewList.appendChild( renderStatusItem("Nextcloud public URL set", "warn", "missing") ); } const issuer = oidcIssuerInput.value.trim() || brokerBaseFromSettings(); if (issuer) { setupOverviewList.appendChild( renderStatusItem("OIDC issuer URL", "ok", issuer) ); } else { setupOverviewList.appendChild( renderStatusItem("OIDC issuer URL", "warn", "missing") ); } if (oidcConfig && oidcConfig.policyMode) { setupOverviewList.appendChild( renderStatusItem("OIDC policy mode", "ok", oidcConfig.policyMode) ); } else { const policyError = extractBrokerError(rawConfig); setupOverviewList.appendChild( renderStatusItem( "OIDC policy mode", "warn", policyError ? `unknown (${policyError})` : "unknown (broker config not available)" ) ); } if (oidcConfig) { const guardMode = normalizeGuardMode(oidcConfig.autoProvisionGuard); if (guardMode) { setupOverviewList.appendChild( renderStatusItem( "Auto-provision guard", guardMode === "invite_or_allowlist" ? "ok" : "warn", guardMode ) ); } else { setupOverviewList.appendChild( renderStatusItem("Auto-provision guard", "warn", "unknown") ); } } const hasClientSecret = Boolean(oidcClientSecretInput.value.trim()); setupOverviewList.appendChild( renderStatusItem( "OIDC client secret", hasClientSecret ? "ok" : "error", hasClientSecret ? "set" : "missing" ) ); } function brokerBaseFromSettings() { const broker = document.getElementById("qortal-broker-base-url"); if (!broker) { return ""; } return broker.value.trim(); } function renderOidcPolicy(config) { const data = config && config.ok !== false ? config.data || config : null; if (!data) { const brokerError = extractBrokerError(config); oidcPolicyMode.textContent = "unavailable"; oidcGuard.textContent = "unavailable"; oidcInviteTtl.textContent = "-"; oidcRequireEmail.textContent = "unavailable"; oidcRedirects.textContent = "-"; oidcPolicyNote.textContent = brokerError ? `Broker policy config is unavailable: ${brokerError}` : "Broker policy config is unavailable. Check broker connectivity."; autoProvisionEnabled = oidcPolicySelect.value === "auto_provision"; autoProvisionGuardEnabled = isGuardEnabled(oidcGuardToggle.value); updateAutoProvisionVisibility({ policyMode: autoProvisionEnabled ? "auto_provision" : "link_only", autoProvisionGuard: autoProvisionGuardEnabled, }); oidcEnvOutput.textContent = buildEnvSnippet(); return; } const source = data.sources || {}; const policySource = source.policyMode === "admin" ? "admin override" : "env"; const guardSource = source.autoProvisionGuard === "admin" ? "admin override" : "env"; const ttlSource = source.inviteTtlSeconds === "admin" ? "admin override" : "env"; const requireEmailSource = source.requireEmailForNewAccount === "admin" ? "admin override" : "env"; const redirectsSource = source.redirectUriAllowlist === "admin" ? "admin override" : "env"; const guardMode = normalizeGuardMode(data.autoProvisionGuard); oidcPolicyMode.textContent = `${ data.policyMode || "unknown" } (${policySource})`; oidcGuard.textContent = `${guardMode || "unknown"} (${guardSource})`; oidcInviteTtl.textContent = `${ typeof data.inviteTtlSeconds === "number" ? String(data.inviteTtlSeconds) : "-" } (${ttlSource})`; oidcRequireEmail.textContent = `${ data.requireEmailForNewAccount ? "required" : "off" } (${requireEmailSource})`; const redirectsText = Array.isArray(data.redirectUriAllowlist) && data.redirectUriAllowlist.length > 0 ? data.redirectUriAllowlist.join(", ") : "(none)"; oidcRedirects.textContent = `${redirectsText} (${redirectsSource})`; const overrides = data.overrides || {}; oidcPolicySelect.value = typeof overrides.policyMode === "string" ? overrides.policyMode : ""; oidcGuardToggle.value = normalizeGuardMode(overrides.autoProvisionGuard); oidcInviteInput.value = typeof overrides.inviteTtlSeconds === "number" ? String(overrides.inviteTtlSeconds) : ""; if (typeof overrides.requireEmailForNewAccount === "boolean") { oidcRequireEmailToggle.value = overrides.requireEmailForNewAccount ? "required" : "off"; } else { oidcRequireEmailToggle.value = ""; } oidcRedirectInput.value = Array.isArray(overrides.redirectUriAllowlist) ? overrides.redirectUriAllowlist.join(",") : ""; oidcEnvOutput.textContent = buildEnvSnippet(); autoProvisionEnabled = data.policyMode === "auto_provision"; autoProvisionGuardEnabled = guardMode === "invite_or_allowlist"; if (autoProvisionEnabled) { oidcPolicyNote.textContent = `Auto-provision is enabled. Guard mode is ${ guardMode || "unknown" }. Source labels show env vs admin overrides.`; } else { oidcPolicyNote.textContent = "Auto-provision is disabled. Invite tokens are hidden; allowlist entries are not enforced. Source labels show env vs admin overrides."; } updateAutoProvisionVisibility(data); } function updateAutoProvisionVisibility(config) { const policy = config && config.policyMode ? config.policyMode : "link_only"; const guardEnabled = isGuardEnabled(config && config.autoProvisionGuard); const autoProvisionEnabled = policy === "auto_provision"; allowlistCard.classList.toggle("qortal-hidden", false); invitesCard.classList.toggle( "qortal-hidden", !autoProvisionEnabled || !guardEnabled ); notifyIncludeInvite.disabled = !autoProvisionEnabled; if (!autoProvisionEnabled) { notifyIncludeInvite.checked = false; } } function updateSetupComponentsVisibility(plan) { if (!plan || !Array.isArray(plan.errors)) { setupComponentsCard.classList.remove("qortal-hidden"); setupOverviewNote.textContent = "Run setup if this is a fresh instance."; return; } if (plan.errors.length > 0) { setupComponentsCard.classList.remove("qortal-hidden"); setupOverviewNote.textContent = "Setup commands are required to complete OIDC provider configuration."; } else { setupComponentsCard.classList.add("qortal-hidden"); setupOverviewNote.textContent = "Setup commands are hidden because configuration looks complete."; } } function buildEnvSnippet() { const policyMode = oidcPolicySelect.value.trim(); const guard = oidcGuardToggle.value.trim(); const inviteTtl = oidcInviteInput.value.trim(); const requireEmail = oidcRequireEmailToggle.value.trim(); const redirectAllowlist = oidcRedirectInput.value.trim(); const lines = []; if (policyMode) { lines.push(`OIDC_POLICY_MODE=${policyMode}`); } if (guard) { lines.push(`OIDC_AUTO_PROVISION_GUARD=${guard}`); } if (inviteTtl) { lines.push(`OIDC_INVITE_TTL_SECONDS=${inviteTtl}`); } if (requireEmail) { lines.push( `OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT=${ requireEmail === "required" ? "true" : "false" }` ); } if (redirectAllowlist) { lines.push(`OIDC_REDIRECT_URI_ALLOWLIST=${redirectAllowlist}`); } if (lines.length === 0) { lines.push("# Using env defaults for OIDC policy settings"); } return lines.join("\n"); } function buildExternalAuthEnvSnippet() { const baseUrl = externalAuthBaseUrlInput.value.trim() || "http://external_auth:3191"; const brokerInternalApiToken = brokerInternalApiTokenInput.value.trim(); const appId = externalAuthAppIdInput.value.trim(); const appSecret = externalAuthAppSecretInput.value.trim(); const nodeUrl = externalAuthNodeUrlInput.value.trim(); const nodeApiKey = externalAuthNodeApiKeyInput.value.trim(); const nodeApiKeyMode = externalAuthNodeApiKeyModeInput.value.trim() || "paths"; const defaultNodeApiKeyPaths = "/"; const nodeApiKeyPathsRaw = externalAuthNodeApiKeyPathsInput.value.trim(); const nodeApiKeyPaths = nodeApiKeyMode === "paths" ? nodeApiKeyPathsRaw || defaultNodeApiKeyPaths : nodeApiKeyPathsRaw; const lines = [`QORTAL_EXTERNAL_AUTH_BASE_URL=${baseUrl}`]; if (brokerInternalApiToken) { lines.push(`BROKER_INTERNAL_API_TOKEN=${brokerInternalApiToken}`); } if (appId) { lines.push(`QORTAL_EXTERNAL_AUTH_APP_ID=${appId}`); } if (appSecret) { lines.push(`QORTAL_EXTERNAL_AUTH_APP_SECRET=${appSecret}`); } if (nodeUrl) { lines.push(`QORTAL_AUTH_NODE_URL=${nodeUrl}`); } if (nodeApiKey) { lines.push(`QORTAL_AUTH_NODE_API_KEY=${nodeApiKey}`); } if (nodeApiKeyMode) { lines.push(`QORTAL_AUTH_NODE_API_KEY_MODE=${nodeApiKeyMode}`); } if (nodeApiKeyPaths) { lines.push(`QORTAL_AUTH_NODE_API_KEY_PATHS=${nodeApiKeyPaths}`); } return lines.join("\n"); } async function registerExternalAuthApp() { const name = externalAuthAppNameInput.value.trim() || "qortal-nextcloud-integration"; const warning = "Registering a new External Auth app will replace existing credentials.\\n\\n" + "If External Auth is already configured via .env, this will generate a new App ID/Secret and you may lose access to existing wallets.\\n\\n" + "Backup your .env or .env.devprod before continuing.\\n\\n" + "Continue?"; if (!window.confirm(warning)) { return; } setFeedback("Registering External Auth app...", false); const body = new URLSearchParams({ name }); const payload = await requestJson(registerExternalAuthUrl, { method: "POST", headers, body, }); externalAuthAppIdInput.value = payload.appId || ""; externalAuthAppSecretInput.value = payload.appSecret || ""; externalAuthEnvOutput.textContent = buildExternalAuthEnvSnippet(); await saveSettings(); setFeedback("External Auth app registered and saved.", false); } function buildQortalNodeEnvSnippet() { const nodeUrl = qortalNodeUrlInput.value.trim(); const nodeApiKey = qortalNodeApiKeyInput.value.trim(); const gatewayUrl = qortalGatewayUrlInput.value.trim(); const lines = []; if (nodeUrl) { lines.push(`QORTAL_NODE_URL=${nodeUrl}`); } if (nodeApiKey) { lines.push(`QORTAL_NODE_API_KEY=${nodeApiKey}`); } if (gatewayUrl) { lines.push(`QORTAL_GATEWAY_URL=${gatewayUrl}`); } if (lines.length === 0) { lines.push( "# Set Qortal node/gateway values above to generate a snippet." ); } return lines.join("\n"); } function renderWallets(payload) { const wallets = payload && payload.ok && payload.data && Array.isArray(payload.data.wallets) ? payload.data.wallets : []; walletsBody.innerHTML = ""; if (wallets.length === 0) { walletsBody.innerHTML = '