1364 lines
72 KiB
PHP
1364 lines
72 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
style('qortal_integration', 'admin');
|
|
script('qortal_integration', 'admin');
|
|
|
|
$settings = $_['settings'];
|
|
$notices = [];
|
|
$brokerBaseUrl = trim((string)($settings['brokerBaseUrl'] ?? ''));
|
|
$brokerInternalApiToken = trim((string)($settings['brokerInternalApiToken'] ?? ''));
|
|
$brokerInternalApiTokenEnv = trim((string)(getenv('QORTAL_BROKER_INTERNAL_API_TOKEN') ?: ''));
|
|
$externalAuthBaseUrl = trim((string)($settings['externalAuthBaseUrl'] ?? ''));
|
|
$externalAuthBaseUrlEnv = trim((string)(getenv('QORTAL_EXTERNAL_AUTH_BASE_URL') ?: ''));
|
|
$externalAuthAppId = trim((string)($settings['externalAuthAppId'] ?? ''));
|
|
$externalAuthAppSecret = trim((string)($settings['externalAuthAppSecret'] ?? ''));
|
|
$externalAuthAppIdEnv = trim((string)(getenv('QORTAL_EXTERNAL_AUTH_APP_ID') ?: ''));
|
|
$externalAuthAppSecretEnv = trim((string)(getenv('QORTAL_EXTERNAL_AUTH_APP_SECRET') ?: ''));
|
|
$externalAuthDocsUrl = trim((string)($settings['externalAuthDocsUrl'] ?? ''));
|
|
$externalAuthNodeUrl = trim((string)($settings['externalAuthNodeUrl'] ?? ''));
|
|
$externalAuthNodeUrlEnv = trim((string)(getenv('QORTAL_AUTH_NODE_URL') ?: ''));
|
|
$externalAuthNodeApiKey = trim((string)($settings['externalAuthNodeApiKey'] ?? ''));
|
|
$externalAuthNodeApiKeyMode = trim((string)($settings['externalAuthNodeApiKeyMode'] ?? ''));
|
|
if ($externalAuthNodeApiKeyMode !== 'paths') {
|
|
$externalAuthNodeApiKeyMode = 'paths';
|
|
}
|
|
$externalAuthNodeApiKeyPaths = trim((string)($settings['externalAuthNodeApiKeyPaths'] ?? ''));
|
|
$oidcIssuerUrl = trim((string)($settings['oidcIssuerUrl'] ?? ''));
|
|
$oidcClientId = trim((string)($settings['oidcClientId'] ?? ''));
|
|
$oidcClientSecret = trim((string)($settings['oidcClientSecret'] ?? ''));
|
|
$oidcPolicyModeOverride = trim((string)($settings['oidcPolicyMode'] ?? ''));
|
|
if ($oidcPolicyModeOverride !== 'link_only' && $oidcPolicyModeOverride !== 'auto_provision') {
|
|
$oidcPolicyModeOverride = '';
|
|
}
|
|
$oidcGuardOverride = trim((string)($settings['oidcAutoProvisionGuard'] ?? ''));
|
|
if ($oidcGuardOverride !== 'invite_or_allowlist' && $oidcGuardOverride !== 'off') {
|
|
$oidcGuardOverride = '';
|
|
}
|
|
$oidcRequireEmailOverride = trim((string)($settings['oidcRequireEmailForNewAccount'] ?? ''));
|
|
if ($oidcRequireEmailOverride !== 'required' && $oidcRequireEmailOverride !== 'off') {
|
|
$oidcRequireEmailOverride = '';
|
|
}
|
|
$nextcloudPublicUrl = trim((string)($settings['nextcloudPublicUrl'] ?? ''));
|
|
$qortalNodeUrl = trim((string)($settings['qortalNodeUrl'] ?? ''));
|
|
$qortalNodeApiKey = trim((string)($settings['qortalNodeApiKey'] ?? ''));
|
|
$qortalGatewayUrl = trim((string)($settings['qortalGatewayUrl'] ?? ''));
|
|
$qortalGatewayAllowInsecure = !empty($settings['qortalGatewayAllowInsecure']);
|
|
$filesPublishMaxSizeOverride = trim((string)($settings['filesPublishMaxSizeOverride'] ?? ''));
|
|
$filesPublishMaxCountOverride = trim((string)($settings['filesPublishMaxCountOverride'] ?? ''));
|
|
$filesPublishLimits = is_array($settings['filesPublishLimits'] ?? null) ? $settings['filesPublishLimits'] : [];
|
|
$filesPublishDesiredSource = trim((string)($filesPublishLimits['desiredSource'] ?? ''));
|
|
$filesPublishEnvDefaultRaw = trim((string)($filesPublishLimits['envDefaultRaw'] ?? ''));
|
|
$filesPublishEnvCountRaw = trim((string)($filesPublishLimits['envCountRaw'] ?? ''));
|
|
$filesPublishBrokerLimitRaw = trim((string)($filesPublishLimits['brokerRequestBodyLimitRaw'] ?? ''));
|
|
$filesPublishPhpUploadRaw = trim((string)($filesPublishLimits['phpUploadMaxFilesizeRaw'] ?? ''));
|
|
$filesPublishPhpPostRaw = trim((string)($filesPublishLimits['phpPostMaxSizeRaw'] ?? ''));
|
|
$filesPublishPhpMemoryRaw = trim((string)($filesPublishLimits['phpMemoryLimitRaw'] ?? ''));
|
|
$filesPublishEffectiveLabel = trim((string)($filesPublishLimits['effectiveMaxFileLabel'] ?? ''));
|
|
$filesPublishBatchLabel = trim((string)($filesPublishLimits['effectiveBatchPayloadLabel'] ?? ''));
|
|
$filesPublishEffectiveCountLabel = trim((string)($filesPublishLimits['effectiveMaxImmediateFileCountLabel'] ?? ''));
|
|
$filesPublishEffectiveCountSource = trim((string)($filesPublishLimits['effectiveMaxImmediateFileCountSource'] ?? 'default'));
|
|
$filesPublishNotes = is_array($filesPublishLimits['notes'] ?? null) ? $filesPublishLimits['notes'] : [];
|
|
$qappsNonAdminRestrictionsEnabled = !array_key_exists('qappsNonAdminRestrictionsEnabled', $settings)
|
|
|| !empty($settings['qappsNonAdminRestrictionsEnabled']);
|
|
$qappsNonAdminRestrictionRules = is_array($settings['qappsNonAdminRestrictionRules'] ?? null)
|
|
? $settings['qappsNonAdminRestrictionRules']
|
|
: [];
|
|
$qappsNonAdminRestrictionRulesJson = json_encode($qappsNonAdminRestrictionRules, JSON_UNESCAPED_SLASHES);
|
|
if (!is_string($qappsNonAdminRestrictionRulesJson) || $qappsNonAdminRestrictionRulesJson === '') {
|
|
$qappsNonAdminRestrictionRulesJson = '[]';
|
|
}
|
|
$scBillingManagedByChd = !array_key_exists('scBillingManagedByChd', $settings) || !empty($settings['scBillingManagedByChd']);
|
|
$scAdvancedAdminEnabled = !empty($settings['scAdvancedAdminEnabled']);
|
|
$scBillingProvider = trim((string)($settings['scBillingProvider'] ?? 'paymenter'));
|
|
if ($scBillingProvider !== 'custom' && $scBillingProvider !== 'paymenter') {
|
|
$scBillingProvider = 'paymenter';
|
|
}
|
|
$scBillingSyncMode = trim((string)($settings['scBillingSyncMode'] ?? 'read_only'));
|
|
if ($scBillingSyncMode !== 'read_only' && $scBillingSyncMode !== 'managed') {
|
|
$scBillingSyncMode = 'read_only';
|
|
}
|
|
$scChdBaseUrl = trim((string)($settings['scChdBaseUrl'] ?? ''));
|
|
$scChdSyncPath = trim((string)($settings['scChdSyncPath'] ?? '/api/sovereign/v1/entitlements/sync'));
|
|
$scChdRegisterPath = trim((string)($settings['scChdRegisterPath'] ?? '/api/sovereign/v1/install/register'));
|
|
$scChdCheckoutPath = trim((string)($settings['scChdCheckoutPath'] ?? '/api/sovereign/v1/billing/checkout-link'));
|
|
$scChdCatalogPath = trim((string)($settings['scChdCatalogPath'] ?? '/api/sovereign/v1/billing/catalog'));
|
|
$scChdCatalogCategoryId = trim((string)($settings['scChdCatalogCategoryId'] ?? ''));
|
|
$scChdCatalogAudience = trim((string)($settings['scChdCatalogAudience'] ?? ''));
|
|
$scChdRegistrationToken = trim((string)($settings['scChdRegistrationToken'] ?? ''));
|
|
$scChdInstanceId = trim((string)($settings['scChdInstanceId'] ?? ''));
|
|
$scChdInstanceToken = trim((string)($settings['scChdInstanceToken'] ?? ''));
|
|
$scChdInstanceSecret = trim((string)($settings['scChdInstanceSecret'] ?? ''));
|
|
$scChdLastSyncAt = trim((string)($settings['scChdLastSyncAt'] ?? ''));
|
|
$scChdLastSyncStatus = trim((string)($settings['scChdLastSyncStatus'] ?? ''));
|
|
$scChdLastSyncMessage = trim((string)($settings['scChdLastSyncMessage'] ?? ''));
|
|
$mspModeEnabled = !empty($settings['mspModeEnabled']);
|
|
$mspAdminGroup = trim((string)($settings['mspAdminGroup'] ?? 'MSP_Admin'));
|
|
if ($mspAdminGroup === '') {
|
|
$mspAdminGroup = 'MSP_Admin';
|
|
}
|
|
$chdAdminDashboardEnabled = !empty($settings['chdAdminDashboardEnabled']);
|
|
$sovereignMode = trim((string)($settings['sovereignMode'] ?? 'powered'));
|
|
if ($sovereignMode !== 'powered' && $sovereignMode !== 'full_qortal') {
|
|
$sovereignMode = 'powered';
|
|
}
|
|
$isPoweredMode = $sovereignMode === 'powered';
|
|
$scBillingDashboardEnabled = array_key_exists('scBillingDashboardEnabled', $settings)
|
|
? !empty($settings['scBillingDashboardEnabled'])
|
|
: true;
|
|
|
|
if (!$isPoweredMode) {
|
|
if ($brokerBaseUrl === '') {
|
|
$notices[] = ['type' => 'error', 'message' => 'Broker Base URL is required. Set it to http://broker:3000 (Docker) or your broker public URL.'];
|
|
}
|
|
if ($brokerInternalApiToken === '' && $brokerInternalApiTokenEnv === '') {
|
|
$notices[] = ['type' => 'error', 'message' => 'Broker Internal API Token is not configured. Set BROKER_INTERNAL_API_TOKEN in your env file and/or set a matching token below.'];
|
|
}
|
|
|
|
if ($externalAuthDocsUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth Docs URL is empty. Set it to your External Auth docs page for quick access.'];
|
|
}
|
|
|
|
if ($externalAuthBaseUrl === '' && $externalAuthBaseUrlEnv !== '') {
|
|
$notices[] = ['type' => 'info', 'message' => 'External Auth Base URL is configured via .env. Admin panel value is empty by design.'];
|
|
} elseif ($externalAuthBaseUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth Base URL is not saved in this panel and no .env value was detected. Broker account operations may fail until it is configured.'];
|
|
}
|
|
|
|
if (($externalAuthAppId === '' || $externalAuthAppSecret === '') && $externalAuthAppIdEnv !== '' && $externalAuthAppSecretEnv !== '') {
|
|
$notices[] = ['type' => 'info', 'message' => 'External Auth App ID/Secret are configured via .env. Admin panel values are empty by design.'];
|
|
} elseif ($externalAuthAppId === '' || $externalAuthAppSecret === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth App ID/Secret are not saved in this panel and no complete .env pair was detected. Set them here (or run finish-initial-setup) for account operations.'];
|
|
}
|
|
|
|
if ($externalAuthNodeUrl === '' && $externalAuthNodeUrlEnv !== '') {
|
|
$notices[] = ['type' => 'info', 'message' => 'External Auth Qortal Node URL is configured via .env. Admin panel value is empty by design.'];
|
|
} elseif ($externalAuthNodeUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth Qortal Node URL is not saved in this panel and no .env value was detected.'];
|
|
}
|
|
|
|
if ($externalAuthNodeUrl !== '' && $externalAuthNodeApiKey === '' && $qortalNodeApiKey === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth node API key is empty. Restricted Qortal endpoints may fail if the node requires an API key. In containerized setups, set QORTAL_AUTH_NODE_API_KEY in .env.devprod and recreate external_auth.'];
|
|
} elseif ($externalAuthNodeUrl !== '' && $externalAuthNodeApiKey === '' && $qortalNodeApiKey !== '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth node API key is empty. Runtime sync will fall back to the Qortal Node API key value.'];
|
|
}
|
|
|
|
if ($oidcIssuerUrl === '' && $brokerBaseUrl === '') {
|
|
$notices[] = ['type' => 'error', 'message' => 'OIDC Issuer URL cannot be resolved. Set Broker Base URL or provide an explicit issuer URL.'];
|
|
}
|
|
|
|
if ($oidcClientId === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'OIDC Client ID is empty. Default will fall back to nextcloud-local.'];
|
|
}
|
|
|
|
if ($oidcClientSecret === '') {
|
|
$notices[] = ['type' => 'error', 'message' => 'OIDC Client Secret is required. Set it in the OIDC Provider Settings section.'];
|
|
} elseif ($oidcClientSecret === 'dev-secret') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'OIDC Client Secret in this panel is set to dev-secret (default). If you use this panel to configure OIDC, replace it before production.'];
|
|
}
|
|
}
|
|
|
|
if ($nextcloudPublicUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'Nextcloud Public URL is not saved in this panel. Setup actions in this panel may not update trusted domains/overwrite settings unless this is set here.'];
|
|
}
|
|
if ($isPoweredMode && $scBillingManagedByChd) {
|
|
if ($scChdBaseUrl === '' || $scChdInstanceId === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'CHD billing is enabled but not fully connected yet. Use "Connect Installation + Sync" in NuQloud V1 Billing + Entitlements to complete one-click bootstrap.'];
|
|
}
|
|
}
|
|
?>
|
|
|
|
<div
|
|
id="qortal-admin-root"
|
|
class="section"
|
|
data-status-url="<?php p($_['statusUrl']); ?>"
|
|
data-setup-url="<?php p($_['setupUrl']); ?>"
|
|
data-setup-plan-url="<?php p($_['setupPlanUrl']); ?>"
|
|
data-setup-occ-url="<?php p($_['setupOccUrl']); ?>"
|
|
data-notify-url="<?php p($_['notifyUrl']); ?>"
|
|
data-search-users-url="<?php p($_['searchUsersUrl']); ?>"
|
|
data-search-groups-url="<?php p($_['searchGroupsUrl']); ?>"
|
|
data-user-mappings-url="<?php p($_['userMappingsUrl']); ?>"
|
|
data-account-dashboard-url="<?php p($_['accountDashboardUrl']); ?>"
|
|
data-admin-display-name="<?php p((string)($_['currentAdminDisplayName'] ?? '')); ?>"
|
|
data-admin-email="<?php p((string)($_['currentAdminEmail'] ?? '')); ?>"
|
|
data-settings-url="<?php p($_['settingsUrl']); ?>"
|
|
data-billing-test-url="<?php p($_['billingTestUrl']); ?>"
|
|
data-billing-sync-url="<?php p($_['billingSyncUrl']); ?>"
|
|
data-billing-bootstrap-url="<?php p($_['billingBootstrapUrl']); ?>"
|
|
data-billing-checkout-url="<?php p($_['billingCheckoutUrl']); ?>"
|
|
data-billing-catalog-url="<?php p($_['billingCatalogUrl']); ?>"
|
|
data-wallets-url="<?php p($_['walletsUrl']); ?>"
|
|
data-create-wallet-url="<?php p($_['createWalletUrl']); ?>"
|
|
data-register-external-auth-url="<?php p($_['registerExternalAuthUrl']); ?>"
|
|
data-mappings-url="<?php p($_['mappingsUrl']); ?>"
|
|
data-link-mapping-url="<?php p($_['linkMappingUrl']); ?>"
|
|
data-unlink-mapping-url="<?php p($_['unlinkMappingUrl']); ?>"
|
|
data-allowlist-url="<?php p($_['allowlistUrl']); ?>"
|
|
data-oidc-clients-url="<?php p($_['oidcClientsUrl']); ?>"
|
|
data-create-oidc-client-url="<?php p($_['createOidcClientUrl']); ?>"
|
|
data-update-oidc-client-url-template="<?php p($_['updateOidcClientUrlTemplate']); ?>"
|
|
data-set-oidc-client-enabled-url-template="<?php p($_['setOidcClientEnabledUrlTemplate']); ?>"
|
|
data-rotate-oidc-client-secret-url-template="<?php p($_['rotateOidcClientSecretUrlTemplate']); ?>"
|
|
data-oidc-audit-events-url="<?php p($_['oidcAuditEventsUrl']); ?>"
|
|
data-add-allowlist-url="<?php p($_['addAllowlistUrl']); ?>"
|
|
data-remove-allowlist-url="<?php p($_['removeAllowlistUrl']); ?>"
|
|
data-invites-url="<?php p($_['invitesUrl']); ?>"
|
|
data-create-invite-url="<?php p($_['createInviteUrl']); ?>"
|
|
data-revoke-invite-url="<?php p($_['revokeInviteUrl']); ?>"
|
|
data-admin-recovery-status-url="<?php p($_['adminRecoveryStatusUrl']); ?>"
|
|
data-admin-recovery-issue-code-url="<?php p($_['adminRecoveryIssueCodeUrl']); ?>"
|
|
data-admin-recovery-start-emergency-url="<?php p($_['adminRecoveryStartEmergencyUrl']); ?>"
|
|
data-qapps-json="<?php p(json_encode($settings['qappsList'] ?? [], JSON_UNESCAPED_SLASHES)); ?>"
|
|
data-qapps-enabled="<?php p(!empty($settings['qappsEnabled']) ? '1' : '0'); ?>"
|
|
data-qapps-browser-enabled="<?php p(!empty($settings['qappsFullBrowserEnabled']) ? '1' : '0'); ?>"
|
|
data-qapps-browser-address="<?php p((string)($settings['qappsFullBrowserAddress'] ?? '')); ?>"
|
|
data-qapps-debug-enabled="<?php p(!empty($settings['qappsDebugEnabled']) ? '1' : '0'); ?>"
|
|
data-qapps-advanced-unlock-settings-enabled="<?php p(!empty($settings['qappsAdvancedUnlockSettingsEnabled']) ? '1' : '0'); ?>"
|
|
data-qapps-non-admin-restrictions-enabled="<?php p($qappsNonAdminRestrictionsEnabled ? '1' : '0'); ?>"
|
|
data-qapps-non-admin-restriction-rules-json="<?php p($qappsNonAdminRestrictionRulesJson); ?>"
|
|
data-user-publishing-enabled="<?php p(!empty($settings['userPublishingEnabled']) ? '1' : '0'); ?>"
|
|
data-external-auth-env-base-url="<?php p($externalAuthBaseUrlEnv !== '' ? '1' : '0'); ?>"
|
|
data-external-auth-env-app-creds="<?php p(($externalAuthAppIdEnv !== '' && $externalAuthAppSecretEnv !== '') ? '1' : '0'); ?>"
|
|
data-external-auth-env-node-url="<?php p($externalAuthNodeUrlEnv !== '' ? '1' : '0'); ?>"
|
|
data-nextcloud-public-url="<?php p($nextcloudPublicUrl); ?>"
|
|
data-sovereign-mode="<?php p($sovereignMode); ?>"
|
|
data-mode-active="<?php p($sovereignMode); ?>"
|
|
data-billing-advanced="<?php p($scAdvancedAdminEnabled ? '1' : '0'); ?>"
|
|
>
|
|
<h2><?php p($_['title']); ?></h2>
|
|
<p class="qortal-help">
|
|
<?php if ($isPoweredMode) { ?>
|
|
Configure NuQloud for Nextcloud integration, onboarding, and app access.
|
|
<?php } else { ?>
|
|
Configure broker connectivity, setup accounts, and manage pre-linked identities for Qortal OIDC link mode.
|
|
<?php } ?>
|
|
</p>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<a class="button" href="/apps/custom_pwa/install" target="_blank" rel="noopener">Open Custom PWA Installer</a>
|
|
<a class="button" href="/apps/chd_admin/admin" target="_blank" rel="noopener">Open CHD Admin</a>
|
|
<a class="button" href="/apps/qortal_files_bridge/" target="_blank" rel="noopener">Open Files Bridge</a>
|
|
<a class="button" href="/apps/qortal_talk_bridge/" target="_blank" rel="noopener">Open Talk Bridge</a>
|
|
</div>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<label>
|
|
<input id="qortal-chd-admin-dashboard-enabled" type="checkbox" <?php if ($chdAdminDashboardEnabled) { print_unescaped('checked'); } ?>>
|
|
Add CHD Admin link to dashboard (admin users)
|
|
</label>
|
|
<button id="qortal-save-chd-admin-dashboard" class="button">Save CHD Dashboard Link</button>
|
|
</div>
|
|
|
|
<?php if (!empty($notices)) { ?>
|
|
<div class="qortal-card qortal-notices" id="qortal-setup-notices-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Setup Notices</h3>
|
|
<p class="qortal-note">These checks reflect values saved in this admin panel. Runtime container env values may differ.</p>
|
|
<ul>
|
|
<?php foreach ($notices as $notice) { ?>
|
|
<li class="qortal-notice <?php p($notice['type']); ?>"><?php p($notice['message']); ?></li>
|
|
<?php } ?>
|
|
</ul>
|
|
</div>
|
|
<?php } ?>
|
|
|
|
<div class="qortal-grid">
|
|
<label for="qortal-sovereign-mode">NuQloud Mode</label>
|
|
<div class="qortal-input-group">
|
|
<select id="qortal-sovereign-mode">
|
|
<option value="full_qortal" <?php if ($sovereignMode === 'full_qortal') { ?>selected<?php } ?>>Full Qortal Mode</option>
|
|
<option value="powered" <?php if ($sovereignMode === 'powered') { ?>selected<?php } ?>>Default</option>
|
|
</select>
|
|
<p class="qortal-note">
|
|
Powered mode is a guided mode with assistance and services provided by CHD.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-qapps-debug-enabled">Debug Mode</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-qapps-debug-enabled" type="checkbox" <?php if (!empty($settings['qappsDebugEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable debug mode
|
|
</label>
|
|
<p class="qortal-note">
|
|
Shows extra diagnostics for integration workflows, including Activity + Notifications debug data and the app viewer debug panel.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-qapps-advanced-unlock-settings-enabled">Unlock Options</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-qapps-advanced-unlock-settings-enabled" type="checkbox" <?php if (!empty($settings['qappsAdvancedUnlockSettingsEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Allow advanced account unlocking settings
|
|
</label>
|
|
<p class="qortal-note">
|
|
When disabled, users only see the password + unlock flow with the default unlock window. When enabled, users can choose a longer or shorter unlock window from their personal settings.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-qapps-non-admin-restrictions-enabled">Non-Admin Node Restrictions</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-qapps-non-admin-restrictions-enabled" type="checkbox" <?php if ($qappsNonAdminRestrictionsEnabled) { print_unescaped('checked'); } ?>>
|
|
Block or limit default sensitive node paths for non-admin users
|
|
</label>
|
|
<p class="qortal-note">
|
|
Enabled by default. Applies non-admin protections similar to your HAProxy hardening defaults.
|
|
</p>
|
|
<p class="qortal-note">
|
|
Default protected areas include <code>/lists</code> POST/DELETE, <code>/crosschain/tradebot</code>,
|
|
sensitive <code>/admin/*</code> mutation endpoints (including <code>/admin/apikey/generate</code>),
|
|
and selected fully blocked discovery/maintenance paths.
|
|
</p>
|
|
</div>
|
|
|
|
<label>Additional Restriction Rules</label>
|
|
<div class="qortal-input-group">
|
|
<div id="qortal-qapps-non-admin-rules"></div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-qapps-non-admin-rules-add" class="button" type="button">Add Rule</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Custom rules are evaluated after defaults. Use these to add project-specific non-admin API path limits.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-broker-base-url" class="qortal-full-only">Broker Base URL</label>
|
|
<div class="qortal-input-group qortal-full-only">
|
|
<textarea id="qortal-broker-base-url" rows="2" spellcheck="false" placeholder="http://broker:3000"><?php p((string)$settings['brokerBaseUrl']); ?></textarea>
|
|
<p class="qortal-note">Required broker endpoints: <code>/api/health</code> and <code>/api/qortal/health</code></p>
|
|
</div>
|
|
|
|
<label for="qortal-broker-internal-api-token" class="qortal-full-only">Broker Internal API Token</label>
|
|
<div class="qortal-input-group qortal-full-only">
|
|
<input id="qortal-broker-internal-api-token" type="password" value="<?php p($brokerInternalApiToken); ?>" placeholder="Must match BROKER_INTERNAL_API_TOKEN">
|
|
<p class="qortal-note">
|
|
Used by Nextcloud when calling broker internal APIs. Must match broker env <code>BROKER_INTERNAL_API_TOKEN</code>.
|
|
For containerized setup, prefer setting the env value in <code>.env.devprod</code>.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-external-auth-docs-url" class="qortal-full-only">External Auth Docs URL</label>
|
|
<div class="qortal-input-group qortal-full-only">
|
|
<textarea id="qortal-external-auth-docs-url" rows="2" spellcheck="false" placeholder="http://localhost:3191/docs/static/index.html"><?php p((string)$settings['externalAuthDocsUrl']); ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-access" class="button">Save Access + Branding</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Save only mode/branding/apps and user-facing access settings from this section.
|
|
</p>
|
|
|
|
<div class="qortal-card" id="qortal-setup-overview" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Setup Overview</h3>
|
|
<ul id="qortal-setup-overview-list" class="qortal-status-list"></ul>
|
|
<p id="qortal-setup-overview-note" class="qortal-note"></p>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-settings" class="button button-primary">Save All Sections</button>
|
|
<button id="qortal-refresh-setup" class="button">Refresh Setup Data</button>
|
|
<button id="qortal-test-connection" class="button">Test Broker Connection</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Save All updates all settings and attempts live broker/external-auth runtime sync.
|
|
Container env files are not changed by this action.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-v1-packaging-card" data-collapsible="1" data-collapsible-default="open">
|
|
<h3>NuQloud V1 Billing + Entitlements</h3>
|
|
<p class="qortal-note">
|
|
Billing Authority: CHD.
|
|
Ensure to sync entitlements if data is outdated. Initial sync should be automatic, but can be attempted again if issues occurred during setup.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-sc-billing-managed-by-chd">Managed By CHD</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-sc-billing-managed-by-chd" type="checkbox" <?php if ($scBillingManagedByChd) { print_unescaped('checked'); } ?>>
|
|
Enable CHD-managed package + credits sync
|
|
</label>
|
|
</div>
|
|
|
|
<label for="qortal-sc-billing-provider" class="qortal-chd-advanced">Billing Provider</label>
|
|
<select id="qortal-sc-billing-provider" class="qortal-chd-advanced">
|
|
<option value="paymenter" <?php if ($scBillingProvider === 'paymenter') { print_unescaped('selected'); } ?>>Paymenter</option>
|
|
<option value="custom" <?php if ($scBillingProvider === 'custom') { print_unescaped('selected'); } ?>>Custom API</option>
|
|
</select>
|
|
|
|
<label for="qortal-sc-billing-sync-mode" class="qortal-chd-advanced">Connector Mode</label>
|
|
<select id="qortal-sc-billing-sync-mode" class="qortal-chd-advanced">
|
|
<option value="read_only" <?php if ($scBillingSyncMode === 'read_only') { print_unescaped('selected'); } ?>>Read-only (entitlements sync only)</option>
|
|
<option value="managed" <?php if ($scBillingSyncMode === 'managed') { print_unescaped('selected'); } ?>>Managed (registration + checkout + entitlements)</option>
|
|
</select>
|
|
|
|
<label for="qortal-sc-chd-base-url" class="qortal-chd-advanced">CHD Billing Base URL</label>
|
|
<input id="qortal-sc-chd-base-url" class="qortal-chd-advanced" type="text" value="<?php p($scChdBaseUrl); ?>" placeholder="https://billing.crowetic.com">
|
|
|
|
<label for="qortal-sc-chd-sync-path" class="qortal-chd-advanced">CHD Entitlements Sync Path</label>
|
|
<input id="qortal-sc-chd-sync-path" class="qortal-chd-advanced" type="text" value="<?php p($scChdSyncPath); ?>" placeholder="/api/sovereign/v1/entitlements/sync">
|
|
|
|
<label for="qortal-sc-chd-register-path" class="qortal-chd-advanced">CHD Registration Path</label>
|
|
<input id="qortal-sc-chd-register-path" class="qortal-chd-advanced" type="text" value="<?php p($scChdRegisterPath); ?>" placeholder="/api/sovereign/v1/install/register">
|
|
|
|
<label for="qortal-sc-chd-checkout-path" class="qortal-chd-advanced">CHD Checkout Link Path</label>
|
|
<input id="qortal-sc-chd-checkout-path" class="qortal-chd-advanced" type="text" value="<?php p($scChdCheckoutPath); ?>" placeholder="/api/sovereign/v1/billing/checkout-link">
|
|
|
|
<label for="qortal-sc-chd-catalog-path" class="qortal-chd-advanced">CHD Catalog Path</label>
|
|
<input id="qortal-sc-chd-catalog-path" class="qortal-chd-advanced" type="text" value="<?php p($scChdCatalogPath); ?>" placeholder="/api/sovereign/v1/billing/catalog">
|
|
|
|
<label for="qortal-sc-chd-catalog-category-id" class="qortal-chd-advanced">Catalog Category ID</label>
|
|
<input id="qortal-sc-chd-catalog-category-id" class="qortal-chd-advanced" type="text" value="<?php p($scChdCatalogCategoryId); ?>" placeholder="NuQloud product category id">
|
|
|
|
<label for="qortal-sc-chd-catalog-audience" class="qortal-chd-advanced">Catalog Audience Key (optional)</label>
|
|
<input id="qortal-sc-chd-catalog-audience" class="qortal-chd-advanced" type="text" value="<?php p($scChdCatalogAudience); ?>" placeholder="MSP_NoBrand_User">
|
|
|
|
<label for="qortal-sc-chd-registration-token" class="qortal-chd-advanced">CHD Registration Token</label>
|
|
<input id="qortal-sc-chd-registration-token" class="qortal-chd-advanced" type="password" value="<?php p($scChdRegistrationToken); ?>" placeholder="Matches connector REGISTRATION_TOKEN">
|
|
|
|
<label for="qortal-sc-chd-instance-id" class="qortal-chd-advanced">CHD Instance ID</label>
|
|
<input id="qortal-sc-chd-instance-id" class="qortal-chd-advanced" type="text" value="<?php p($scChdInstanceId); ?>" placeholder="instance identifier">
|
|
|
|
<label for="qortal-sc-chd-instance-token" class="qortal-chd-advanced">CHD Instance Token</label>
|
|
<input id="qortal-sc-chd-instance-token" class="qortal-chd-advanced" type="password" value="<?php p($scChdInstanceToken); ?>" placeholder="Bearer token from CHD">
|
|
|
|
<label for="qortal-sc-chd-instance-secret" class="qortal-chd-advanced">CHD Instance Secret (optional)</label>
|
|
<input id="qortal-sc-chd-instance-secret" class="qortal-chd-advanced" type="password" value="<?php p($scChdInstanceSecret); ?>" placeholder="HMAC secret (optional)">
|
|
|
|
<label>Last CHD Sync</label>
|
|
<div class="qortal-input-group">
|
|
<p class="qortal-note">
|
|
Status:
|
|
<strong id="qortal-sc-chd-last-sync-status"><?php p($scChdLastSyncStatus !== '' ? $scChdLastSyncStatus : 'not synced'); ?></strong>
|
|
<?php if ($scChdLastSyncAt !== '') { ?>
|
|
• <?php p($scChdLastSyncAt); ?>
|
|
<?php } ?>
|
|
</p>
|
|
<?php if ($scChdLastSyncMessage !== '') { ?>
|
|
<p id="qortal-sc-chd-last-sync-message" class="qortal-note"><?php p($scChdLastSyncMessage); ?></p>
|
|
<?php } else { ?>
|
|
<p id="qortal-sc-chd-last-sync-message" class="qortal-note"></p>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<label for="qortal-sc-package-tier">Active Tier</label>
|
|
<select id="qortal-sc-package-tier" disabled>
|
|
<?php
|
|
$scPackageTierValue = (string)($settings['scPackageTier'] ?? 'free_core');
|
|
$scPackageTierOptions = [
|
|
'free_core' => 'Free Core',
|
|
'nuqloud_starter' => 'Starter',
|
|
'nuqloud_advanced' => 'Advanced',
|
|
'nuqloud_pro' => 'Pro',
|
|
'nuqloud_team_starter' => 'Team Starter',
|
|
'nuqloud_team_advanced' => 'Team Advanced',
|
|
'nuqloud_team_pro' => 'Team Pro',
|
|
'nuqloud_branded' => 'NuQloud Branded',
|
|
'nuqloud_branded_pro' => 'Branded Pro',
|
|
'nuqloud_branded_enterprise' => 'Branded Enterprise',
|
|
];
|
|
foreach ($scPackageTierOptions as $tierValue => $tierLabel) { ?>
|
|
<option value="<?php p($tierValue); ?>" <?php if ($scPackageTierValue === $tierValue) { print_unescaped('selected'); } ?>><?php p($tierLabel); ?></option>
|
|
<?php } ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="qortal-billing-form">
|
|
<h4>Instance-Wide Admin Shared Billing</h4>
|
|
<p class="qortal-note">
|
|
This billing information is instance-wide and shared with admins. If you do not want to use shared instance billing, purchase from the
|
|
<a href="<?php p((string)($_['accountDashboardUrl'] ?? '/apps/qortal_integration/account')); ?>">NuQloud Dashboard</a>,
|
|
which uses individualized billing per user account.
|
|
</p>
|
|
<div class="qortal-billing-profile-grid">
|
|
<div class="qortal-billing-field qortal-billing-field--wide">
|
|
<label for="qortal-sc-billing-contact-name">Shared Billing Contact Name</label>
|
|
<input id="qortal-sc-billing-contact-name" type="text" value="<?php p((string)($settings['scBillingContactName'] ?? '')); ?>" placeholder="Jane Doe">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--wide">
|
|
<label for="qortal-sc-billing-contact-email">Shared Billing Contact Email</label>
|
|
<input id="qortal-sc-billing-contact-email" type="email" value="<?php p((string)($settings['scBillingContactEmail'] ?? '')); ?>" placeholder="billing@example.com">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--wide">
|
|
<label for="qortal-sc-billing-company-name">Company / Organization</label>
|
|
<input id="qortal-sc-billing-company-name" type="text" value="<?php p((string)($settings['scBillingCompanyName'] ?? '')); ?>" placeholder="Acme, Inc.">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--wide">
|
|
<label for="qortal-sc-billing-phone">Billing Phone</label>
|
|
<input id="qortal-sc-billing-phone" type="text" value="<?php p((string)($settings['scBillingPhone'] ?? '')); ?>" placeholder="+1 555 555 5555">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--full">
|
|
<label for="qortal-sc-billing-address1">Billing Address Line 1</label>
|
|
<input id="qortal-sc-billing-address1" type="text" value="<?php p((string)($settings['scBillingAddress1'] ?? '')); ?>" placeholder="123 Main Street">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--full">
|
|
<label for="qortal-sc-billing-address2">Billing Address Line 2</label>
|
|
<input id="qortal-sc-billing-address2" type="text" value="<?php p((string)($settings['scBillingAddress2'] ?? '')); ?>" placeholder="Suite 200">
|
|
</div>
|
|
<div class="qortal-billing-field">
|
|
<label for="qortal-sc-billing-city">City</label>
|
|
<input id="qortal-sc-billing-city" type="text" value="<?php p((string)($settings['scBillingCity'] ?? '')); ?>" placeholder="Austin">
|
|
</div>
|
|
<div class="qortal-billing-field">
|
|
<label for="qortal-sc-billing-state">State / Region</label>
|
|
<input id="qortal-sc-billing-state" type="text" value="<?php p((string)($settings['scBillingState'] ?? '')); ?>" placeholder="TX">
|
|
</div>
|
|
<div class="qortal-billing-field">
|
|
<label for="qortal-sc-billing-postal-code">Postal / ZIP Code</label>
|
|
<input id="qortal-sc-billing-postal-code" type="text" value="<?php p((string)($settings['scBillingPostalCode'] ?? '')); ?>" placeholder="78701">
|
|
</div>
|
|
<div class="qortal-billing-field">
|
|
<label for="qortal-sc-billing-country">Country</label>
|
|
<input id="qortal-sc-billing-country" type="text" value="<?php p((string)($settings['scBillingCountry'] ?? '')); ?>" placeholder="US">
|
|
</div>
|
|
<div class="qortal-billing-field qortal-billing-field--full">
|
|
<label for="qortal-sc-admin-q-address">Shared NuQloud Funding Address</label>
|
|
<input id="qortal-sc-admin-q-address" type="text" value="<?php p((string)($settings['scAdminQortalAddress'] ?? '')); ?>" placeholder="Create/link your account first" readonly>
|
|
<p id="qortal-sc-account-status" class="qortal-note"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-grid">
|
|
<label for="qortal-sc-credits-balance">Credits Balance</label>
|
|
<input id="qortal-sc-credits-balance" type="number" min="0" step="1" value="<?php p((string)($settings['scCreditsBalance'] ?? '0')); ?>" disabled>
|
|
|
|
<label for="qortal-sc-monthly-credits">Included Monthly Credits</label>
|
|
<input id="qortal-sc-monthly-credits" type="number" min="0" step="1" value="<?php p((string)($settings['scMonthlyCredits'] ?? '0')); ?>" disabled>
|
|
|
|
<label for="qortal-sc-capacity-gb">Encrypted Capacity (GB)</label>
|
|
<input id="qortal-sc-capacity-gb" type="number" min="0" step="1" value="<?php p((string)($settings['scEncryptedCapacityGb'] ?? '0')); ?>" disabled>
|
|
|
|
<label for="qortal-sc-replication-target">Replication Target (nodes)</label>
|
|
<input id="qortal-sc-replication-target" type="number" min="0" step="1" value="<?php p((string)($settings['scReplicationTarget'] ?? '0')); ?>" disabled>
|
|
</div>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-packaging" class="button qortal-chd-advanced">Save Billing Connectivity</button>
|
|
<button id="qortal-bootstrap-packaging" class="button button-primary">Connect Installation + Sync</button>
|
|
<button id="qortal-test-packaging" class="button">Test Connector (No Apply)</button>
|
|
<button id="qortal-sync-packaging" class="button">Sync Entitlements From CHD</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Billing Authority: CHD. Use this section to connect the instance,
|
|
provide billing profile details, and sync entitlements.
|
|
When required values are available, initial connect/sync is attempted automatically.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-purchases-card">
|
|
<div class="qortal-card-header">
|
|
<h3>Purchases</h3>
|
|
<button id="qortal-purchases-toggle" class="button" type="button" aria-expanded="true">Hide</button>
|
|
</div>
|
|
<p id="qortal-purchases-note" class="qortal-note">
|
|
Choose subscription packages and add-ons. Your billing profile above is reused automatically.
|
|
</p>
|
|
<div id="qortal-purchases-body">
|
|
<div class="qortal-purchase-section">
|
|
<h4>Subscription Packages</h4>
|
|
<div id="qortal-subscription-cards" class="qortal-purchase-grid qortal-purchase-grid--subscriptions"></div>
|
|
</div>
|
|
<div class="qortal-purchase-section">
|
|
<h4>Add-ons</h4>
|
|
<div id="qortal-onetime-cards" class="qortal-purchase-grid qortal-purchase-grid--onetime"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-advanced-billing-settings-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Advanced Billing Settings</h3>
|
|
<p class="qortal-note">
|
|
Support and troubleshooting options. Most installations should leave this collapsed.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-sc-advanced-admin-enabled">Show Advanced Billing Inputs</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-sc-advanced-admin-enabled" type="checkbox" <?php if ($scAdvancedAdminEnabled) { print_unescaped('checked'); } ?>>
|
|
Show connector endpoint, credentials, and path overrides
|
|
</label>
|
|
</div>
|
|
|
|
<label for="qortal-sc-billing-enabled" class="qortal-chd-advanced">Dashboard Billing Visibility</label>
|
|
<div class="qortal-input-group qortal-chd-advanced">
|
|
<label>
|
|
<input id="qortal-sc-billing-enabled" type="checkbox" <?php if ($scBillingDashboardEnabled) { print_unescaped('checked'); } ?>>
|
|
Show billing and purchases on user dashboards
|
|
</label>
|
|
<p class="qortal-note">
|
|
Advanced option. This hides billing/purchase UI on dashboards but does not disconnect CHD or remove admin shared billing settings.
|
|
Use CHD Managed Billing / disconnect options for a full connector disconnect.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-msp-delegation-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>MSP Delegated Admin</h3>
|
|
<p class="qortal-note">
|
|
Default is disabled. When enabled, users in this Nextcloud group get delegated admin access for invites, initial grant queue actions, and instance billing operations.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-msp-mode-enabled">Enable Delegated MSP Admin Mode</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-msp-mode-enabled" type="checkbox" <?php if ($mspModeEnabled) { print_unescaped('checked'); } ?>>
|
|
Allow configured group to perform delegated admin operations
|
|
</label>
|
|
</div>
|
|
|
|
<label for="qortal-msp-admin-group">MSP Admin Group ID</label>
|
|
<div class="qortal-input-group">
|
|
<input id="qortal-msp-admin-group" type="text" value="<?php p($mspAdminGroup); ?>" placeholder="MSP_Admin">
|
|
<p class="qortal-note">
|
|
Use the exact Nextcloud group id. Group creation is managed separately (for example via OCC: <code>group:add MSP_Admin</code>).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-msp" class="button">Save MSP Delegation</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Saves only delegated MSP admin settings from this section.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-oidc-settings-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'NuQloud OIDC Settings' : 'Qortal OIDC Settings'); ?></h3>
|
|
<p class="qortal-note">
|
|
These values are used when generating or running the <code>user_oidc</code> provider setup only.
|
|
They do not update broker runtime env values.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-oidc-issuer-url">OIDC Issuer URL</label>
|
|
<div class="qortal-input-group">
|
|
<textarea id="qortal-oidc-issuer-url" rows="2" spellcheck="false" placeholder="http://broker:3000"><?php p((string)$settings['oidcIssuerUrl']); ?></textarea>
|
|
<p class="qortal-note">Defaults to broker base URL if left empty.</p>
|
|
</div>
|
|
|
|
<label for="qortal-oidc-client-id">OIDC Client ID</label>
|
|
<input id="qortal-oidc-client-id" type="text" value="<?php p((string)$settings['oidcClientId']); ?>" placeholder="nextcloud-local">
|
|
|
|
<label for="qortal-oidc-client-secret">OIDC Client Secret</label>
|
|
<input id="qortal-oidc-client-secret" type="password" value="<?php p((string)$settings['oidcClientSecret']); ?>" placeholder="dev-secret">
|
|
|
|
<label for="qortal-nextcloud-public-url">Nextcloud Public URL</label>
|
|
<div class="qortal-input-group">
|
|
<input id="qortal-nextcloud-public-url" type="text" value="<?php p((string)$settings['nextcloudPublicUrl']); ?>" placeholder="https://cloud.example.com">
|
|
<p class="qortal-note">Used to update trusted domains and overwrite settings when running setup.</p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-identity" class="button">Save Identity Settings</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Saves OIDC provider values and policy overrides.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-external-auth-settings-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>External Auth Configuration</h3>
|
|
<p class="qortal-note">
|
|
Store External Auth connection details here for runtime sync and env generation.
|
|
Save Settings stores these in Nextcloud and attempts live runtime sync through the broker.
|
|
If your daemon does not expose runtime settings endpoints, update env files and recreate/restart containers.
|
|
</p>
|
|
<p class="qortal-note">
|
|
Important: for bundled/containerized External Auth, set <code>QORTAL_AUTH_NODE_API_KEY</code> in
|
|
<code>.env.devprod</code> and recreate <code>external_auth</code>. The admin field below is a best-effort
|
|
runtime override and may not persist across container restarts.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-external-auth-base-url">External Auth Base URL</label>
|
|
<div class="qortal-input-group">
|
|
<input id="qortal-external-auth-base-url" type="text" value="<?php p((string)$settings['externalAuthBaseUrl']); ?>" placeholder="http://external_auth:3191">
|
|
<p class="qortal-note">Used by broker as <code>QORTAL_EXTERNAL_AUTH_BASE_URL</code>.</p>
|
|
</div>
|
|
|
|
<label for="qortal-external-auth-app-id">External Auth App ID</label>
|
|
<input id="qortal-external-auth-app-id" type="text" value="<?php p((string)$settings['externalAuthAppId']); ?>" placeholder="UUID">
|
|
|
|
<label for="qortal-external-auth-app-secret">External Auth App Secret</label>
|
|
<input id="qortal-external-auth-app-secret" type="password" value="<?php p((string)$settings['externalAuthAppSecret']); ?>" placeholder="Secret">
|
|
|
|
<label for="qortal-external-auth-app-name">External Auth App Name</label>
|
|
<input id="qortal-external-auth-app-name" type="text" value="qortal-nextcloud-integration" placeholder="qortal-nextcloud-integration">
|
|
|
|
<label></label>
|
|
<div class="qortal-input-group">
|
|
<button id="qortal-external-auth-register" class="button">Register External Auth App</button>
|
|
<p class="qortal-note">
|
|
Warning: registering a new app will replace existing credentials. If External Auth is already configured
|
|
via <code>.env</code>, this will generate a new App ID/Secret and you may lose access to existing wallets.
|
|
Backup your <code>.env</code> or <code>.env.devprod</code> first.
|
|
</p>
|
|
</div>
|
|
|
|
<label for="qortal-external-auth-node-url">Qortal Node URL (External Auth)</label>
|
|
<input id="qortal-external-auth-node-url" type="text" value="<?php p((string)$settings['externalAuthNodeUrl']); ?>" placeholder="http://qortal:12391">
|
|
|
|
<label for="qortal-external-auth-node-api-key">Qortal Node API Key (External Auth)</label>
|
|
<input id="qortal-external-auth-node-api-key" type="password" value="<?php p((string)$settings['externalAuthNodeApiKey']); ?>" placeholder="API key (optional)">
|
|
<p class="qortal-note">
|
|
If left empty, runtime sync falls back to the key in “<?php p($isPoweredMode ? 'Publisher Node + Gateway' : 'Qortal Node + Gateway'); ?>”.
|
|
For containerized setup, still set <code>QORTAL_AUTH_NODE_API_KEY</code> in <code>.env.devprod</code>.
|
|
</p>
|
|
|
|
<label for="qortal-external-auth-node-api-key-mode">Node API Key Mode</label>
|
|
<select id="qortal-external-auth-node-api-key-mode">
|
|
<option value="paths" <?php if ($externalAuthNodeApiKeyMode === 'paths') { print_unescaped('selected'); } ?>>paths (recommended)</option>
|
|
</select>
|
|
|
|
<label for="qortal-external-auth-node-api-key-paths">Node API Key Paths</label>
|
|
<input id="qortal-external-auth-node-api-key-paths" type="text" value="<?php p((string)$settings['externalAuthNodeApiKeyPaths']); ?>" placeholder="/">
|
|
<p class="qortal-note">Only used when mode is set to <code>paths</code>. Use <code>/</code> to send <code>X-API-KEY</code> on all node API calls.</p>
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-external-auth-env-generate" class="button">Generate External Auth Env Snippet</button>
|
|
</div>
|
|
<pre id="qortal-external-auth-env-output" class="qortal-status qortal-compact-status"></pre>
|
|
<p class="qortal-note">
|
|
Save Settings attempts live runtime sync through the broker. If your External Auth daemon
|
|
does not expose runtime settings endpoints, apply env files and restart with
|
|
<code>./recreate-devprod.sh --extauth</code> or
|
|
<code>docker compose up -d --build broker external_auth</code>.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-node-gateway-settings-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'Publisher Node + Gateway' : 'Qortal Node + Gateway'); ?></h3>
|
|
<p class="qortal-note">
|
|
<?php if ($isPoweredMode) { ?>
|
|
Configure the publisher node used for encrypted/chunked backup publishing and app rendering.
|
|
Gateway nodes expose a separate gateway port and do not require an API key.
|
|
<?php } else { ?>
|
|
Configure the node used for Q-App rendering and signed requests. Gateway nodes expose a separate gateway port
|
|
and do not require an API key.
|
|
<?php } ?>
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-node-url">Qortal Node URL</label>
|
|
<input id="qortal-node-url" type="text" value="<?php p((string)$settings['qortalNodeUrl']); ?>" placeholder="http://qortal:12391">
|
|
|
|
<label for="qortal-node-api-key">Qortal Node API Key</label>
|
|
<input id="qortal-node-api-key" type="password" value="<?php p((string)$settings['qortalNodeApiKey']); ?>" placeholder="API key (if required)">
|
|
|
|
<label for="qortal-gateway-url">Gateway Node URL</label>
|
|
<div class="qortal-input-group">
|
|
<input id="qortal-gateway-url" type="text" value="<?php p((string)$settings['qortalGatewayUrl']); ?>" placeholder="http://gateway:12393">
|
|
<p class="qortal-note">Use a public gateway (e.g. https://qortal.link) or your own gateway node URL.</p>
|
|
</div>
|
|
|
|
<label for="qortal-gateway-insecure">Allow insecure gateway TLS</label>
|
|
<div class="qortal-input-group">
|
|
<label>
|
|
<input id="qortal-gateway-insecure" type="checkbox" <?php if ($qortalGatewayAllowInsecure) { print_unescaped('checked'); } ?>>
|
|
Disable TLS verification for gateway proxy requests
|
|
</label>
|
|
<p class="qortal-note">
|
|
Use only if your gateway uses a self-signed certificate or the container lacks CA roots. Recommended to keep off for production.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-node-env-generate" class="button">Generate Node Env Snippet</button>
|
|
</div>
|
|
<pre id="qortal-node-env-output" class="qortal-status qortal-compact-status"></pre>
|
|
<p class="qortal-note">
|
|
When running a local node container, ensure gateway mode is enabled and expose the gateway port.
|
|
</p>
|
|
<div class="qortal-actions qortal-section-actions">
|
|
<button id="qortal-save-runtime" class="button">Save Runtime Connectivity</button>
|
|
</div>
|
|
<p class="qortal-note qortal-full-only">
|
|
Broker internal APIs require <code>BROKER_INTERNAL_API_TOKEN</code> on the broker service.
|
|
If this token changes in env, update the matching token here (or via app env <code>QORTAL_BROKER_INTERNAL_API_TOKEN</code>).
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-hidden qortal-full-only" id="qortal-setup-components">
|
|
<h3>Setup Components</h3>
|
|
<p class="qortal-note">
|
|
Generate the setup commands or run them automatically (requires <code>occ</code> access inside the Nextcloud container).
|
|
</p>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-setup-plan" class="button">Generate Setup Commands</button>
|
|
<button id="qortal-setup-occ" class="button">Run Setup (occ)</button>
|
|
</div>
|
|
<pre id="qortal-setup-result" class="qortal-status qortal-compact-status"></pre>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-oidc-policy-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Auto-Provision Policy (Broker)</h3>
|
|
<p class="qortal-note">
|
|
Read-only effective broker policy values (from env defaults plus optional admin overrides).
|
|
</p>
|
|
<div class="qortal-grid qortal-policy-grid">
|
|
<label>Policy Mode</label>
|
|
<div><code id="qortal-oidc-policy-mode">unknown</code></div>
|
|
|
|
<label>Auto-Provision Guard</label>
|
|
<div><code id="qortal-oidc-guard">unknown</code></div>
|
|
|
|
<label>Invite TTL (seconds)</label>
|
|
<div><code id="qortal-oidc-invite-ttl">-</code></div>
|
|
|
|
<label>Require Email For New Account</label>
|
|
<div><code id="qortal-oidc-require-email">unknown</code></div>
|
|
|
|
<label>Redirect Allowlist</label>
|
|
<div><code id="qortal-oidc-redirects">-</code></div>
|
|
</div>
|
|
<p id="qortal-oidc-policy-note" class="qortal-note"></p>
|
|
<div class="qortal-grid qortal-policy-grid">
|
|
<label for="qortal-oidc-policy-select">Policy Mode Override</label>
|
|
<select id="qortal-oidc-policy-select">
|
|
<option value="" <?php if ($oidcPolicyModeOverride === '') { ?>selected<?php } ?>>(use env default)</option>
|
|
<option value="link_only" <?php if ($oidcPolicyModeOverride === 'link_only') { ?>selected<?php } ?>>link_only (existing users only)</option>
|
|
<option value="auto_provision" <?php if ($oidcPolicyModeOverride === 'auto_provision') { ?>selected<?php } ?>>auto_provision (new users allowed)</option>
|
|
</select>
|
|
|
|
<label for="qortal-oidc-guard-toggle">Guard Override</label>
|
|
<select id="qortal-oidc-guard-toggle">
|
|
<option value="" <?php if ($oidcGuardOverride === '') { ?>selected<?php } ?>>(use env default)</option>
|
|
<option value="invite_or_allowlist" <?php if ($oidcGuardOverride === 'invite_or_allowlist') { ?>selected<?php } ?>>invite_or_allowlist</option>
|
|
<option value="off" <?php if ($oidcGuardOverride === 'off') { ?>selected<?php } ?>>off</option>
|
|
</select>
|
|
|
|
<label for="qortal-oidc-invite-ttl-input">Invite TTL Override (seconds)</label>
|
|
<input id="qortal-oidc-invite-ttl-input" type="number" min="60" placeholder="(env default)">
|
|
|
|
<label for="qortal-oidc-require-email-toggle">Require Email For New Account</label>
|
|
<select id="qortal-oidc-require-email-toggle">
|
|
<option value="" <?php if ($oidcRequireEmailOverride === '') { ?>selected<?php } ?>>(use env default)</option>
|
|
<option value="required" <?php if ($oidcRequireEmailOverride === 'required') { ?>selected<?php } ?>>required</option>
|
|
<option value="off" <?php if ($oidcRequireEmailOverride === 'off') { ?>selected<?php } ?>>off</option>
|
|
</select>
|
|
|
|
<label for="qortal-oidc-redirect-allowlist-input">Redirect Allowlist Override</label>
|
|
<input id="qortal-oidc-redirect-allowlist-input" type="text" placeholder="(env default)">
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-oidc-env-generate" class="button">Generate Broker Env Snippet</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Save Settings now syncs these overrides to broker runtime. Leave a field blank to keep using env defaults.
|
|
</p>
|
|
<pre id="qortal-oidc-env-output" class="qortal-status qortal-compact-status"></pre>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-identity-provider-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'NuQloud Identity Provider' : 'Qortal Identity Provider'); ?></h3>
|
|
<p class="qortal-note">
|
|
Register trusted private apps as OIDC clients. Client secrets are shown only when created or rotated.
|
|
</p>
|
|
<div class="qortal-grid qortal-identity-provider-form">
|
|
<label for="qortal-idp-template">Template</label>
|
|
<div class="qortal-input-group">
|
|
<select id="qortal-idp-template">
|
|
<option value="gitea">Gitea</option>
|
|
<option value="grafana">Grafana</option>
|
|
<option value="wikijs">Wiki.js</option>
|
|
<option value="openproject">OpenProject</option>
|
|
<option value="outline">Outline</option>
|
|
<option value="generic">Generic OIDC App</option>
|
|
</select>
|
|
<p class="qortal-note">Templates prefill common values. Always replace example domains with the app's real public URL.</p>
|
|
</div>
|
|
|
|
<label for="qortal-idp-client-id">Client ID</label>
|
|
<input id="qortal-idp-client-id" type="text" placeholder="gitea">
|
|
|
|
<label for="qortal-idp-display-name">Display Name</label>
|
|
<input id="qortal-idp-display-name" type="text" placeholder="Gitea">
|
|
|
|
<label for="qortal-idp-redirect-uris">Redirect URIs</label>
|
|
<textarea id="qortal-idp-redirect-uris" placeholder="https://git.example.com/user/oauth2/NuQloud/callback"></textarea>
|
|
|
|
<label for="qortal-idp-post-logout-redirect-uris">Post Logout Redirect URIs</label>
|
|
<textarea id="qortal-idp-post-logout-redirect-uris" placeholder="Optional, one per line"></textarea>
|
|
|
|
<label for="qortal-idp-allowed-scopes">Allowed Scopes</label>
|
|
<input id="qortal-idp-allowed-scopes" type="text" value="openid,profile,email" placeholder="openid,profile,email,groups,qortal_address">
|
|
|
|
<label for="qortal-idp-allowed-groups">Allowed Groups</label>
|
|
<input id="qortal-idp-allowed-groups" type="text" placeholder="Optional, comma-separated Nextcloud groups">
|
|
|
|
<label for="qortal-idp-require-pkce">Require PKCE</label>
|
|
<label class="qortal-inline-check">
|
|
<input id="qortal-idp-require-pkce" type="checkbox">
|
|
Require PKCE for authorization code exchange
|
|
</label>
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-idp-apply-template" class="button" type="button">Apply Template</button>
|
|
<button id="qortal-idp-save-client" class="button">Create Client</button>
|
|
<button id="qortal-idp-cancel-edit" class="button qortal-hidden" type="button">Cancel Edit</button>
|
|
<button id="qortal-idp-refresh-clients" class="button">Refresh Clients</button>
|
|
<button id="qortal-idp-refresh-audit" class="button">Refresh Audit</button>
|
|
<button id="qortal-idp-copy-secret" class="button" type="button">Copy Secret</button>
|
|
</div>
|
|
<pre id="qortal-idp-secret-output" class="qortal-status qortal-compact-status"></pre>
|
|
<pre id="qortal-idp-config-output" class="qortal-status qortal-compact-status"></pre>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Client</th>
|
|
<th>Redirect URIs</th>
|
|
<th>Scopes</th>
|
|
<th>Groups</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-idp-clients-body"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Event</th>
|
|
<th>Client</th>
|
|
<th>User</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-idp-audit-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-full-setup-options-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Full Setup Options</h3>
|
|
<p class="qortal-note">
|
|
Use one of the supported setup entry points depending on your environment.
|
|
</p>
|
|
<div class="qortal-setup-options">
|
|
<div class="qortal-setup-option">
|
|
<strong>Local Docker Dev</strong>
|
|
<pre><code>./start-dev.sh</code></pre>
|
|
</div>
|
|
<div class="qortal-setup-option">
|
|
<strong>Dev-Prod (Caddy SSL)</strong>
|
|
<pre><code>./start-devprod.sh</code></pre>
|
|
</div>
|
|
<div class="qortal-setup-option">
|
|
<strong>Dev-Prod (No SSL / External Proxy)</strong>
|
|
<pre><code>./start-devprod.sh</code></pre>
|
|
<p class="qortal-note">Choose "no" when prompted for Caddy SSL.</p>
|
|
</div>
|
|
<div class="qortal-setup-option">
|
|
<strong>VM Install (Nextcloud VM + broker containers)</strong>
|
|
<pre><code>sudo bash scripts/nextcloud-vm-install.sh</code></pre>
|
|
</div>
|
|
<div class="qortal-setup-option">
|
|
<strong>Recreate Containers (apply new env)</strong>
|
|
<pre><code>./recreate-devprod.sh</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-qapps-access-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'NuQloud Apps Access' : 'Q-Apps Access'); ?></h3>
|
|
<p class="qortal-note">
|
|
Enable app access in Nextcloud and define allowed <code>qortal://</code> app addresses.
|
|
</p>
|
|
<div class="qortal-toggles">
|
|
<label>
|
|
<input id="qortal-user-publishing-enabled" type="checkbox" <?php if (!empty($settings['userPublishingEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Expose user publishing
|
|
</label>
|
|
<label>
|
|
<input id="qortal-qapps-enabled" type="checkbox" <?php if (!empty($settings['qappsEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable apps menu
|
|
</label>
|
|
<label>
|
|
<input id="qortal-qapps-browser-enabled" type="checkbox" <?php if (!empty($settings['qappsFullBrowserEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable full browser
|
|
</label>
|
|
</div>
|
|
<p class="qortal-note">
|
|
When disabled, end users keep account/backup access but publishing-credit request, send, and name-registration actions are hidden.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-files-publish-max-size-override">Files Publish Size Override</label>
|
|
<div class="qortal-input-group">
|
|
<input
|
|
id="qortal-files-publish-max-size-override"
|
|
type="text"
|
|
placeholder="Example: 24mb"
|
|
value="<?php p($filesPublishMaxSizeOverride); ?>"
|
|
>
|
|
<p class="qortal-note">
|
|
Optional admin override for Files Bridge publish size. Leave blank to use the env default
|
|
(<code><?php p($filesPublishEnvDefaultRaw !== '' ? $filesPublishEnvDefaultRaw : 'not set'); ?></code>) or the broker ceiling.
|
|
Actual publish size is still capped by the broker request limit and PHP runtime limits.
|
|
</p>
|
|
<p class="qortal-note">
|
|
Effective publish limit: <strong><?php p($filesPublishEffectiveLabel !== '' ? $filesPublishEffectiveLabel : 'Unavailable'); ?></strong>
|
|
<?php if ($filesPublishBatchLabel !== '') { ?>
|
|
| Effective batch payload: <strong><?php p($filesPublishBatchLabel); ?></strong>
|
|
<?php } ?>
|
|
| Desired source: <strong><?php p($filesPublishDesiredSource !== '' ? $filesPublishDesiredSource : 'broker_default'); ?></strong>
|
|
</p>
|
|
<p class="qortal-note">
|
|
Broker body limit: <code><?php p($filesPublishBrokerLimitRaw !== '' ? $filesPublishBrokerLimitRaw : 'not set'); ?></code>
|
|
| PHP upload_max_filesize: <code><?php p($filesPublishPhpUploadRaw !== '' ? $filesPublishPhpUploadRaw : 'not set'); ?></code>
|
|
| PHP post_max_size: <code><?php p($filesPublishPhpPostRaw !== '' ? $filesPublishPhpPostRaw : 'not set'); ?></code>
|
|
| PHP memory_limit: <code><?php p($filesPublishPhpMemoryRaw !== '' ? $filesPublishPhpMemoryRaw : 'not set'); ?></code>
|
|
</p>
|
|
<?php if (!empty($filesPublishNotes)) { ?>
|
|
<ul class="qortal-note">
|
|
<?php foreach ($filesPublishNotes as $filesPublishNote) { ?>
|
|
<li><?php p((string)$filesPublishNote); ?></li>
|
|
<?php } ?>
|
|
</ul>
|
|
<?php } ?>
|
|
<p class="qortal-note">
|
|
Container recreation envs: <code>BROKER_REQUEST_BODY_LIMIT</code>, <code>QORTAL_FILES_PUBLISH_MAX_SIZE</code>,
|
|
<code>NEXTCLOUD_PHP_UPLOAD_MAX_FILESIZE</code>, <code>NEXTCLOUD_PHP_POST_MAX_SIZE</code>, and
|
|
<code>NEXTCLOUD_PHP_MEMORY_LIMIT</code>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-files-publish-max-count-override">Immediate Publish File Cap</label>
|
|
<div class="qortal-input-group">
|
|
<input
|
|
id="qortal-files-publish-max-count-override"
|
|
type="number"
|
|
min="1"
|
|
step="1"
|
|
placeholder="Example: 24"
|
|
value="<?php p($filesPublishMaxCountOverride); ?>"
|
|
>
|
|
<p class="qortal-note">
|
|
Optional admin override for the maximum number of eligible files that may be published in one immediate request.
|
|
Leave blank to use the env default (<code><?php p($filesPublishEnvCountRaw !== '' ? $filesPublishEnvCountRaw : 'not set'); ?></code>)
|
|
or the built-in safety default.
|
|
</p>
|
|
<p class="qortal-note">
|
|
Effective immediate publish cap: <strong><?php p($filesPublishEffectiveCountLabel !== '' ? $filesPublishEffectiveCountLabel . ' files' : 'Unavailable'); ?></strong>
|
|
| Source: <strong><?php p($filesPublishEffectiveCountSource !== '' ? $filesPublishEffectiveCountSource : 'default'); ?></strong>
|
|
</p>
|
|
<p class="qortal-note">
|
|
This cap prevents a single publish action from overrunning typical Qortal node unconfirmed transaction ceilings.
|
|
Larger selections should be split into multiple batches until queued publishing is added.
|
|
</p>
|
|
<p class="qortal-note">
|
|
Optional env override: <code>QORTAL_FILES_PUBLISH_MAX_COUNT</code>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-inline-form qortal-qapps-form">
|
|
<label for="qortal-qapps-name">App Name</label>
|
|
<input id="qortal-qapps-name" type="text" placeholder="Qortal Notes">
|
|
<label for="qortal-qapps-address">Qortal Address</label>
|
|
<input id="qortal-qapps-address" type="text" placeholder="qortal://APP/NAME">
|
|
<label for="qortal-qapps-icon-mode">Icon Mode</label>
|
|
<select id="qortal-qapps-icon-mode">
|
|
<option value="auto" selected>Auto (gateway thumbnail)</option>
|
|
<option value="custom">Custom URL</option>
|
|
<option value="name">Use Icon From Name</option>
|
|
</select>
|
|
<label for="qortal-qapps-icon-url">Icon URL (optional)</label>
|
|
<input id="qortal-qapps-icon-url" type="text" placeholder="https://gateway/arbitrary/THUMBNAIL/APP/qortal_avatar">
|
|
<label for="qortal-qapps-icon-from-name">Icon Source Name (optional)</label>
|
|
<input id="qortal-qapps-icon-from-name" type="text" placeholder="Q-Mail">
|
|
<label for="qortal-qapps-description">Description (optional)</label>
|
|
<input id="qortal-qapps-description" type="text" placeholder="Short description">
|
|
<button id="qortal-add-qapp" class="button">Add App</button>
|
|
<button id="qortal-cancel-qapp-edit" class="button qortal-hidden" type="button">Cancel Edit</button>
|
|
<button id="qortal-clear-qapps" class="button">Clear List</button>
|
|
</div>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Qortal Address</th>
|
|
<th>Icon</th>
|
|
<th>Description</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-qapps-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-initial-setup-checklist-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Initial Setup Checklist</h3>
|
|
<ol>
|
|
<li>Configure broker URL and save settings.</li>
|
|
<li>Use <strong>Refresh Setup Data</strong> and confirm broker and External Auth are healthy.</li>
|
|
<li>Create or import account(s) visible to broker app credentials.</li>
|
|
<li>Link each Qortal address to a Nextcloud user (required for <code>link_only</code> mode).</li>
|
|
<li>Verify OIDC provider in Nextcloud login page.</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-account-operations-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Account Operations</h3>
|
|
<p class="qortal-note">
|
|
Create accounts through the broker using configured External Auth app credentials.
|
|
If you link an account to a user, share the password securely with that user.
|
|
</p>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-wallet-password">Account Password</label>
|
|
<input id="qortal-wallet-password" type="password" placeholder="Required for account creation">
|
|
<label for="qortal-wallet-kdf-threads">KDF Threads</label>
|
|
<input id="qortal-wallet-kdf-threads" type="number" min="1" placeholder="Optional">
|
|
<label for="qortal-wallet-user-id">Nextcloud User ID (optional)</label>
|
|
<input id="qortal-wallet-user-id" type="text" placeholder="admin">
|
|
<button id="qortal-create-wallet" class="button">Create Account</button>
|
|
<button id="qortal-create-wallet-link" class="button">Create & Link User</button>
|
|
<button id="qortal-refresh-wallets" class="button">Refresh Accounts</button>
|
|
</div>
|
|
<pre id="qortal-wallet-create-result" class="qortal-status qortal-compact-status"></pre>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Account ID</th>
|
|
<th>Address</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-wallets-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-identity-mapping-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Identity Mapping Operations</h3>
|
|
<p class="qortal-note">
|
|
Link or unlink Qortal addresses so OIDC login can resolve identities in <code>link_only</code> mode.
|
|
</p>
|
|
<div class="qortal-inline-form qortal-link-form">
|
|
<label for="qortal-link-address">Qortal Address</label>
|
|
<input id="qortal-link-address" type="text" placeholder="Q...">
|
|
<label for="qortal-link-wallet-id">Account ID (optional)</label>
|
|
<input id="qortal-link-wallet-id" type="text" placeholder="UUID">
|
|
<label for="qortal-link-user-id">Nextcloud User ID</label>
|
|
<input id="qortal-link-user-id" type="text" placeholder="admin">
|
|
<button id="qortal-link-mapping" class="button">Link Mapping</button>
|
|
<button id="qortal-refresh-mappings" class="button">Refresh Mappings</button>
|
|
</div>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Qortal Address</th>
|
|
<th>Nextcloud User</th>
|
|
<th>Account ID</th>
|
|
<th>Status</th>
|
|
<th>Updated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-mappings-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-full-only" id="qortal-allowlist-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3>Allowlisted Qortal Addresses</h3>
|
|
<p class="qortal-note">
|
|
These addresses can be preloaded for future use. Enforcement only applies when
|
|
<code>OIDC_POLICY_MODE=auto_provision</code> and the guard is enabled.
|
|
</p>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-allowlist-address">Allowlist Qortal Address</label>
|
|
<input id="qortal-allowlist-address" type="text" placeholder="Q...">
|
|
<button id="qortal-add-allowlist" class="button">Add To Allowlist</button>
|
|
<button id="qortal-refresh-allowlist" class="button">Refresh Allowlist</button>
|
|
</div>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Qortal Address</th>
|
|
<th>Added By</th>
|
|
<th>Added</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-allowlist-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card qortal-hidden" id="qortal-invites-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'NuQloud Invite Tokens' : 'Invite Tokens'); ?></h3>
|
|
<p class="qortal-note">
|
|
<?php if ($isPoweredMode) { ?>
|
|
Generate an invite token for creating a NuQloud Create Account / Login cloud user via invite onboarding.
|
|
<?php } else { ?>
|
|
Generate invite tokens for users to paste into the Qortal login form when auto-provisioning.
|
|
<?php } ?>
|
|
</p>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-invite-expiry-hours">Invite Expiry (hours)</label>
|
|
<input id="qortal-invite-expiry-hours" type="number" min="1" placeholder="168">
|
|
<label for="qortal-invite-comment">Comment / Recipient Note</label>
|
|
<input id="qortal-invite-comment" type="text" maxlength="500" placeholder="Example: Sent to jane@example.com">
|
|
<button id="qortal-create-invite" class="button">Create Invite</button>
|
|
<button id="qortal-refresh-invites" class="button">Refresh Invites</button>
|
|
</div>
|
|
<pre id="qortal-invite-create-result" class="qortal-status qortal-compact-status"></pre>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Token</th>
|
|
<th>Comment</th>
|
|
<th>Status</th>
|
|
<th>Expires</th>
|
|
<th>Used By</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-invites-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-recovery-ops-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'NuQloud Recovery Operations' : 'Recovery Operations'); ?></h3>
|
|
<p class="qortal-note">
|
|
Support tools for users who opted into managed recovery. Recovery reveal still requires both admin one-time code and user recovery passphrase.
|
|
</p>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-recovery-target-user-id">Target Nextcloud User ID</label>
|
|
<input id="qortal-recovery-target-user-id" type="text" placeholder="alice">
|
|
<button id="qortal-recovery-status-check" class="button">Check Recovery Status</button>
|
|
</div>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-recovery-code-expiry-minutes">Code Expiry (minutes)</label>
|
|
<input id="qortal-recovery-code-expiry-minutes" type="number" min="5" max="60" value="15">
|
|
<button id="qortal-recovery-issue-code" class="button">Issue One-Time Recovery Code</button>
|
|
</div>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-recovery-emergency-hours">Emergency Window (hours)</label>
|
|
<input id="qortal-recovery-emergency-hours" type="number" min="1" max="72" value="24">
|
|
<button id="qortal-recovery-start-emergency" class="button">Start Emergency Temporary Login</button>
|
|
</div>
|
|
<pre id="qortal-recovery-admin-result" class="qortal-status qortal-compact-status"></pre>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-invite-qortal-card" data-collapsible="1" data-collapsible-default="closed">
|
|
<h3><?php p($isPoweredMode ? 'Create NuQloud Account / Login' : 'Invite Existing Qortal Users To Cloud'); ?></h3>
|
|
<p class="qortal-note">
|
|
<?php if ($isPoweredMode) { ?>
|
|
Generate an invite message and invite token to create a new cloud user via NuQloud Create Account / Login.
|
|
Use the E-Mail address you used on the payment server so the account stays connected properly to the payment system.
|
|
<?php } else { ?>
|
|
Generate a message for existing Qortal users. When auto-provisioning is enabled, an invite token is included.
|
|
In link-only mode, this message prompts users to link their Qortal account to an existing Nextcloud login.
|
|
<?php } ?>
|
|
</p>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-generate-invite-message" class="button"><?php p($isPoweredMode ? 'Generate NuQloud Invite Message' : 'Generate Invite Message'); ?></button>
|
|
<button id="qortal-copy-invite-message" class="button">Copy Message</button>
|
|
</div>
|
|
<textarea id="qortal-invite-message" rows="6" spellcheck="false" placeholder="<?php p($isPoweredMode ? 'NuQloud invite message will appear here.' : 'Invite message will appear here.'); ?>"></textarea>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-onboard-cloud-card">
|
|
<div class="qortal-card-header">
|
|
<h3>Decentralized Auth Onboarding</h3>
|
|
<button id="qortal-onboard-toggle" class="button" type="button" aria-expanded="false">Show</button>
|
|
</div>
|
|
<div id="qortal-onboard-cloud-body">
|
|
<p class="qortal-note">
|
|
This assists existing cloud users in setting up their decentralized NuQloud authentication powered by Qortal.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-notify-email-subject">Invite Subject Template</label>
|
|
<input id="qortal-notify-email-subject" type="text" value="<?php p((string)($settings['notifyEmailSubject'] ?? '')); ?>">
|
|
|
|
<label for="qortal-notify-email-body">Invite Message Template</label>
|
|
<div class="qortal-input-group">
|
|
<textarea id="qortal-notify-email-body" rows="6" spellcheck="false"><?php p((string)($settings['notifyEmailBody'] ?? '')); ?></textarea>
|
|
<p class="qortal-note">Placeholders: <code>{link}</code>, <code>{invite}</code>, <code>{user}</code>, <code>{displayName}</code>, <code>{email}</code>, <code>{cloudName}</code>, <code>{instanceName}</code>, <code>{orderId}</code>, <code>{invoiceId}</code>, <code>{packageTier}</code></p>
|
|
</div>
|
|
</div>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-notify-user-ids">Target Users (one per line)</label>
|
|
<textarea id="qortal-notify-user-ids" rows="4" spellcheck="false" placeholder="admin alice bob"></textarea>
|
|
|
|
<label for="qortal-notify-group-ids">Target Groups (optional)</label>
|
|
<textarea id="qortal-notify-group-ids" rows="2" spellcheck="false" placeholder="admins staff"></textarea>
|
|
</div>
|
|
<div class="qortal-inline-form qortal-user-search">
|
|
<label for="qortal-user-search-query">Search Users</label>
|
|
<input id="qortal-user-search-query" type="text" placeholder="Type a username">
|
|
<button id="qortal-user-search-button" class="button">Search</button>
|
|
</div>
|
|
<p class="qortal-note">
|
|
Search and add users or groups, or add one per line above for invite functionality.
|
|
</p>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User ID</th>
|
|
<th>Display Name</th>
|
|
<th>Email</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-user-search-results"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="qortal-inline-form qortal-user-search">
|
|
<label for="qortal-group-search-query">Search Groups</label>
|
|
<input id="qortal-group-search-query" type="text" placeholder="Type a group name">
|
|
<button id="qortal-group-search-button" class="button">Search</button>
|
|
</div>
|
|
<div class="qortal-table-wrap">
|
|
<table class="qortal-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Group ID</th>
|
|
<th>Display Name</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-group-search-results"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="qortal-toggles">
|
|
<label>
|
|
<input id="qortal-notify-email" type="checkbox" checked>
|
|
Send Email
|
|
</label>
|
|
<label>
|
|
<input id="qortal-notify-inapp" type="checkbox" checked>
|
|
Send In-App Notification
|
|
</label>
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-preview-email" class="button">Preview Invite Message</button>
|
|
</div>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-email-preview-subject">Invite Message Preview Subject</label>
|
|
<input id="qortal-email-preview-subject" type="text" readonly>
|
|
|
|
<label for="qortal-email-preview-body">Invite Message Preview Body</label>
|
|
<textarea id="qortal-email-preview-body" rows="6" readonly></textarea>
|
|
</div>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-send-notifications" class="button">Send Onboarding Messages</button>
|
|
</div>
|
|
<pre id="qortal-notify-result" class="qortal-status qortal-compact-status"></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="qortal-admin-feedback" class="qortal-feedback" role="status"></div>
|
|
<pre id="qortal-admin-status" class="qortal-status"></pre>
|
|
</div>
|