719 lines
33 KiB
PHP
719 lines
33 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'] ?? ''));
|
|
$externalAuthAppId = trim((string)($settings['externalAuthAppId'] ?? ''));
|
|
$externalAuthAppSecret = trim((string)($settings['externalAuthAppSecret'] ?? ''));
|
|
$externalAuthDocsUrl = trim((string)($settings['externalAuthDocsUrl'] ?? ''));
|
|
$externalAuthNodeUrl = trim((string)($settings['externalAuthNodeUrl'] ?? ''));
|
|
$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']);
|
|
|
|
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 === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth Base URL is empty. Broker wallet operations will fail until it is configured.'];
|
|
}
|
|
|
|
if ($externalAuthAppId === '' || $externalAuthAppSecret === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth App ID/Secret are missing. Broker wallet operations require these credentials.'];
|
|
}
|
|
|
|
if ($externalAuthNodeUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'External Auth Qortal Node URL is empty. Ensure the External Auth container has a Qortal node configured.'];
|
|
}
|
|
|
|
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 is set to dev-secret. Replace it before production.'];
|
|
}
|
|
|
|
if ($nextcloudPublicUrl === '') {
|
|
$notices[] = ['type' => 'warning', 'message' => 'Nextcloud Public URL is empty. Trusted domains and overwrite settings will not be updated by setup actions.'];
|
|
}
|
|
?>
|
|
|
|
<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-settings-url="<?php p($_['settingsUrl']); ?>"
|
|
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-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-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-nextcloud-public-url="<?php p($nextcloudPublicUrl); ?>"
|
|
>
|
|
<h2><?php p($_['title']); ?></h2>
|
|
<p class="qortal-help">
|
|
Configure broker connectivity, setup wallets, and manage pre-linked identities for Qortal OIDC link mode.
|
|
</p>
|
|
|
|
<?php if (!empty($notices)) { ?>
|
|
<div class="qortal-card qortal-notices">
|
|
<h3>Setup Notices</h3>
|
|
<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-broker-base-url">Broker Base URL</label>
|
|
<div class="qortal-input-group">
|
|
<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">Broker Internal API Token</label>
|
|
<div class="qortal-input-group">
|
|
<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">External Auth Docs URL</label>
|
|
<div class="qortal-input-group">
|
|
<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-toggles">
|
|
<label>
|
|
<input id="qortal-feature-qdn-backups" type="checkbox" <?php if (!empty($settings['featureQdnBackups'])) { print_unescaped('checked'); } ?>>
|
|
Enable QDN backups workflow
|
|
</label>
|
|
<label>
|
|
<input id="qortal-feature-qmail" type="checkbox" <?php if (!empty($settings['featureQmail'])) { print_unescaped('checked'); } ?>>
|
|
Enable Q-Mail workflow
|
|
</label>
|
|
</div>
|
|
|
|
<div class="qortal-actions">
|
|
<button id="qortal-save-settings" class="button button-primary">Save Settings</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 Settings updates Nextcloud app settings and attempts live broker/external-auth runtime sync.
|
|
Container env files are not changed by Save Settings.
|
|
</p>
|
|
<p class="qortal-note">
|
|
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 class="qortal-card" id="qortal-setup-overview">
|
|
<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>
|
|
|
|
<div class="qortal-card">
|
|
<h3>OIDC Provider 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>
|
|
|
|
<div class="qortal-card">
|
|
<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 “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">
|
|
<h3>Qortal Node + Gateway</h3>
|
|
<p class="qortal-note">
|
|
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.
|
|
</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>
|
|
|
|
<div class="qortal-card qortal-hidden" 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" id="qortal-oidc-policy-card">
|
|
<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">
|
|
<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">
|
|
<h3>Q-Apps Access</h3>
|
|
<p class="qortal-note">
|
|
Enable Q-Apps access in Nextcloud and define allowed <code>qortal://</code> app addresses.
|
|
</p>
|
|
<div class="qortal-toggles">
|
|
<label>
|
|
<input id="qortal-qapps-enabled" type="checkbox" <?php if (!empty($settings['qappsEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable Q-Apps menu
|
|
</label>
|
|
<label>
|
|
<input id="qortal-qapps-browser-enabled" type="checkbox" <?php if (!empty($settings['qappsFullBrowserEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable full Qortal browser
|
|
</label>
|
|
<label>
|
|
<input id="qortal-qapps-debug-enabled" type="checkbox" <?php if (!empty($settings['qappsDebugEnabled'])) { print_unescaped('checked'); } ?>>
|
|
Enable Q-Apps debug panel
|
|
</label>
|
|
</div>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-qapps-browser-address">Full Browser Address</label>
|
|
<input id="qortal-qapps-browser-address" type="text" placeholder="qortal://apps" value="<?php p((string)($settings['qappsFullBrowserAddress'] ?? '')); ?>">
|
|
</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>
|
|
</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-description">Description (optional)</label>
|
|
<input id="qortal-qapps-description" type="text" placeholder="Short description">
|
|
<button id="qortal-add-qapp" class="button">Add Q-App</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">
|
|
<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 wallet(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">
|
|
<h3>Wallet Operations</h3>
|
|
<p class="qortal-note">
|
|
Create wallets through the broker using configured External Auth app credentials.
|
|
If you link a wallet to a user, share the password securely with that user.
|
|
</p>
|
|
<div class="qortal-inline-form">
|
|
<label for="qortal-wallet-password">Wallet Password</label>
|
|
<input id="qortal-wallet-password" type="password" placeholder="Required for wallet 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 Wallet</button>
|
|
<button id="qortal-create-wallet-link" class="button">Create & Link User</button>
|
|
<button id="qortal-refresh-wallets" class="button">Refresh Wallets</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>Wallet ID</th>
|
|
<th>Address</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qortal-wallets-body"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="qortal-card">
|
|
<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">Wallet 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>Wallet 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" id="qortal-allowlist-card">
|
|
<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">
|
|
<h3>Invite Tokens</h3>
|
|
<p class="qortal-note">
|
|
Generate invite tokens for users to paste into the Qortal login form when auto-provisioning.
|
|
</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">
|
|
<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>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-invite-qortal-card">
|
|
<h3>Invite Existing Qortal Users To Cloud</h3>
|
|
<p class="qortal-note">
|
|
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.
|
|
</p>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-generate-invite-message" class="button">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="Invite message will appear here."></textarea>
|
|
</div>
|
|
|
|
<div class="qortal-card" id="qortal-onboard-cloud-card">
|
|
<h3>Onboard Cloud Users</h3>
|
|
<p class="qortal-note">
|
|
Send onboarding prompts to existing Nextcloud users. Invite tokens are not required for existing users.
|
|
</p>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-notify-email-subject">Email Subject</label>
|
|
<input id="qortal-notify-email-subject" type="text" value="<?php p((string)($settings['notifyEmailSubject'] ?? '')); ?>">
|
|
|
|
<label for="qortal-notify-email-body">Email Body</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></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>
|
|
<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>
|
|
<label>
|
|
<input id="qortal-notify-queue" type="checkbox">
|
|
Queue notifications (background job)
|
|
</label>
|
|
<label>
|
|
<input id="qortal-notify-include-invite" type="checkbox">
|
|
Include invite token (auto-provision guard)
|
|
</label>
|
|
</div>
|
|
<p class="qortal-note">Invite tokens are only required when auto-provision is enabled. Existing users do not need them.</p>
|
|
<div class="qortal-actions">
|
|
<button id="qortal-preview-email" class="button">Preview Email</button>
|
|
</div>
|
|
<div class="qortal-grid">
|
|
<label for="qortal-email-preview-subject">Email Preview Subject</label>
|
|
<input id="qortal-email-preview-subject" type="text" readonly>
|
|
|
|
<label for="qortal-email-preview-body">Email 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 Notifications</button>
|
|
</div>
|
|
<pre id="qortal-notify-result" class="qortal-status qortal-compact-status"></pre>
|
|
</div>
|
|
|
|
<div id="qortal-admin-feedback" class="qortal-feedback" role="status"></div>
|
|
<pre id="qortal-admin-status" class="qortal-status"></pre>
|
|
</div>
|