settings.html
811 lignes · 51704 octets
{% extends "base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block navbar %} {% include "components/navbar.html" %} {% endblock %} {% block content %} <div class="container mx-auto px-4 py-8"> <!-- Breadcrumb --> <div class="text-sm breadcrumbs mb-6"> <ul> <li><a href="/">{{ breadcrumb_home }}</a></li> <li><a href="/admin">{{ breadcrumb_admin }}</a></li> <li>{{ breadcrumb_settings }}</li> </ul> </div> <!-- Page Header --> <div class="mb-8"> <h1 class="text-3xl font-bold mb-2">⚙️ {{ title }}</h1> <p class="text-base-content/70">{{ subtitle }}</p> </div> <form id="settingsForm" method="POST" action="/admin/settings" class="space-y-6 max-w-6xl"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <!-- General Settings Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg> {{ general }} </h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <!-- App Name --> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ app_name_label }}</span> </label> <input type="text" name="app_name" value="{{ settings.app_name }}" class="input input-bordered w-full" > </div> <!-- App Domain --> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ app_domain_label }}</span> </label> <input type="text" name="app_domain" value="{{ settings.app_domain }}" class="input input-bordered w-full" placeholder="{{ app_domain_placeholder }}" > </div> <!-- App Description --> <div class="form-control md:col-span-2"> <label class="label"> <span class="label-text font-semibold">{{ app_description_label }}</span> </label> <textarea name="app_description" class="textarea textarea-bordered w-full" rows="2" >{{ settings.app_description }}</textarea> </div> <!-- Allow Registration --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="allow_registration" value="true" class="checkbox checkbox-primary" {% if settings.allow_registration == "true" %}checked{% endif %} > <div> <span class="label-text font-semibold">{{ allow_registration_label }}</span> <span class="label-text-alt block">{{ allow_registration_desc }}</span> </div> </label> </div> <!-- Validation email requise --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="validation_email_requise" value="true" class="checkbox checkbox-primary" {% if settings.validation_email_requise == "true" %}checked{% endif %} > <div> <span class="label-text font-semibold">{{ email_validation_required_label }}</span> <span class="label-text-alt block">{{ email_validation_required_desc }}</span> </div> </label> </div> </div> </div> </div> <!-- Password Security Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg> {{ password_security }} </h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <!-- Min Length --> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ password_min_length }}</span> </label> <input type="number" name="password_min_length" value="{{ settings.password_min_length }}" class="input input-bordered w-full" min="6" max="128" > <label class="label"> <span class="label-text-alt">{{ password_min_length_hint }}</span> </label> </div> <!-- Complexity Requirements --> <div class="space-y-2"> <label class="label"> <span class="label-text font-semibold">{{ password_complexity }}</span> </label> <div class="form-control"> <label class="label cursor-pointer justify-start gap-3 py-1"> <input type="checkbox" name="password_require_uppercase" value="true" class="checkbox checkbox-sm" {% if settings.password_require_uppercase == "true" %}checked{% endif %} > <span class="label-text">{{ require_uppercase }}</span> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-3 py-1"> <input type="checkbox" name="password_require_lowercase" value="true" class="checkbox checkbox-sm" {% if settings.password_require_lowercase == "true" %}checked{% endif %} > <span class="label-text">{{ require_lowercase }}</span> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-3 py-1"> <input type="checkbox" name="password_require_digits" value="true" class="checkbox checkbox-sm" {% if settings.password_require_digits == "true" %}checked{% endif %} > <span class="label-text">{{ require_digits }}</span> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-3 py-1"> <input type="checkbox" name="password_require_special" value="true" class="checkbox checkbox-sm" {% if settings.password_require_special == "true" %}checked{% endif %} > <span class="label-text">{{ require_special }}</span> </label> </div> </div> <!-- Password change email confirmation --> <div class="form-control mt-4"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="password_change_require_email" value="true" class="toggle toggle-primary" {% if settings.password_change_require_email == "true" %}checked{% endif %} > <div> <span class="label-text font-medium">{{ password_change_require_email }}</span> <p class="text-xs text-base-content/60 mt-0.5">{{ password_change_require_email_desc }}</p> </div> </label> </div> <!-- Password Expiration --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="password_expiration_enabled" value="true" class="checkbox checkbox-primary" id="expirationEnabled" {% if settings.password_expiration_enabled == "true" %}checked{% endif %} > <div> <span class="label-text font-semibold">{{ password_expiration }}</span> <span class="label-text-alt block">{{ password_expiration_desc }}</span> </div> </label> </div> <div id="expirationSettings" class="space-y-3"> <div class="form-control"> <label class="label"> <span class="label-text">{{ expiration_days }}</span> </label> <input type="number" name="password_expiration_days" value="{{ settings.password_expiration_days }}" class="input input-bordered input-sm w-full" min="0" max="365" > </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-3"> <input type="checkbox" name="password_expiration_alert_enabled" value="true" class="checkbox checkbox-sm" {% if settings.password_expiration_alert_enabled == "true" %}checked{% endif %} > <span class="label-text">{{ expiration_alert }}</span> </label> </div> <div class="form-control"> <label class="label"> <span class="label-text">{{ expiration_alert_days }}</span> </label> <input type="number" name="password_expiration_alert_days" value="{{ settings.password_expiration_alert_days_before }}" class="input input-bordered input-sm w-full" min="0" max="30" > </div> </div> </div> </div> </div> <!-- Two-Factor Authentication Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg> Authentification à deux facteurs </h2> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="totp_force_enabled" value="true" class="toggle toggle-primary" {% if settings.totp_force_enabled == "true" %}checked{% endif %} /> <div> <span class="label-text font-semibold">Forcer le 2FA pour tous les utilisateurs</span> <p class="text-xs text-base-content/60 mt-1">Lorsqu'activé, tous les utilisateurs devront configurer l'authentification à deux facteurs avant de pouvoir accéder à l'application.</p> </div> </label> </div> </div> </div> <!-- Audit Logging Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg> {{ audit_logging }} </h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <!-- Log Level --> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ log_level }}</span> </label> <select name="audit_log_level" class="select select-bordered w-full" > <option value="DEBUG" {% if settings.audit_log_level == "DEBUG" %}selected{% endif %}>{{ log_level_debug }}</option> <option value="INFO" {% if settings.audit_log_level == "INFO" %}selected{% endif %}>{{ log_level_info }}</option> <option value="WARN" {% if settings.audit_log_level == "WARN" %}selected{% endif %}>{{ log_level_warn }}</option> <option value="ERROR" {% if settings.audit_log_level == "ERROR" %}selected{% endif %}>{{ log_level_error }}</option> </select> <label class="label"> <span class="label-text-alt">{{ log_level_hint }}</span> </label> </div> <!-- Retention --> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ log_retention }}</span> </label> <input type="number" name="audit_log_retention_days" value="{{ settings.audit_log_retention_days }}" class="input input-bordered w-full" min="1" max="365" > <label class="label"> <span class="label-text-alt">{{ log_retention_hint }}</span> </label> </div> </div> <div class="alert alert-info mt-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span class="text-sm">{{ audit_info_message }}</span> </div> </div> </div> <!-- OAuth / SSO Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg> {{ oauth_sso_title }} </h2> <div class="space-y-4"> <!-- Global OAuth Toggle --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="oauth_enabled" value="true" class="toggle toggle-primary" id="oauthEnabled" {% if settings.oauth_enabled == "true" %}checked{% endif %}> <div> <span class="label-text font-semibold">{{ oauth_enabled_label }}</span> <span class="label-text-alt block">{{ oauth_enabled_desc }}</span> </div> </label> </div> <!-- OAuth global options (shown when OAuth is enabled) --> <div id="oauthGlobalOptions" class="space-y-4"> <!-- Base redirect URL --> <div class="form-control"> <label class="label"><span class="label-text font-semibold">{{ oauth_redirect_url_label }}</span></label> <input type="text" name="oauth_redirect_base_url" value="{{ settings.oauth_redirect_base_url }}" placeholder="https://myapp.example.com" class="input input-bordered w-full"> <label class="label"><span class="label-text-alt">{{ oauth_redirect_url_desc }}</span></label> </div> <!-- Auto-register & Link existing toggles --> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="oauth_auto_register" value="true" class="toggle toggle-secondary" {% if settings.oauth_auto_register == "true" %}checked{% endif %}> <div> <span class="label-text font-semibold">{{ oauth_auto_register_label }}</span> <span class="label-text-alt block">{{ oauth_auto_register_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="oauth_link_existing_account" value="true" class="toggle toggle-secondary" {% if settings.oauth_link_existing_account == "true" %}checked{% endif %}> <div> <span class="label-text font-semibold">{{ oauth_link_existing_label }}</span> <span class="label-text-alt block">{{ oauth_link_existing_desc }}</span> </div> </label> </div> </div> <div class="divider my-2"></div> <!-- ===== Google ===== --> <div class="collapse collapse-arrow bg-base-200 rounded-box" id="collapseGoogle"> <input type="checkbox" class="peer oauth-provider-collapse" data-provider="google" {% if settings.oauth_google_enabled == "true" %}checked{% endif %} /> <div class="collapse-title flex items-center gap-3"> <svg class="h-5 w-5" viewBox="0 0 24 24"><path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/><path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg> <span class="font-semibold">{{ oauth_google_label }}</span> <span class="badge badge-sm badge-outline ml-1">{{ oauth_google_desc }}</span> <input type="checkbox" name="oauth_google_enabled" value="true" class="toggle toggle-primary toggle-sm ml-auto oauth-provider-toggle" data-provider="google" {% if settings.oauth_google_enabled == "true" %}checked{% endif %} onclick="event.stopPropagation()"> </div> <div class="collapse-content space-y-3"> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_client_id }}</span></label> <input type="text" name="oauth_google_client_id" value="{{ settings.oauth_google_client_id }}" placeholder="xxxx.apps.googleusercontent.com" class="input input-bordered input-sm w-full"> </div> <div class="form-control"> <label class="label"> <span class="label-text">{{ oauth_client_secret }}</span> {% if settings.oauth_google_has_secret %} <span class="badge badge-success badge-sm gap-1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ oauth_secret_configured }} </span> {% else %} <span class="badge badge-warning badge-sm">{{ oauth_secret_not_configured }}</span> {% endif %} </label> <input type="password" name="oauth_google_client_secret" placeholder="{% if settings.oauth_google_has_secret %}{{ oauth_secret_change }}{% else %}GOCSPX-...{% endif %}" class="input input-bordered input-sm w-full" autocomplete="off"> </div> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_redirect_uri }}</span></label> <div class="join w-full"> <input type="text" readonly value="{{ settings.oauth_redirect_base_url }}/api/v1/auth/oauth/google/callback" class="input input-bordered input-sm join-item w-full bg-base-200 font-mono text-xs" id="googleRedirectUri"> <button type="button" class="btn btn-sm join-item btn-outline copy-uri-btn" data-target="googleRedirectUri">{{ oauth_copy_uri }}</button> </div> </div> </div> </div> <!-- ===== GitHub ===== --> <div class="collapse collapse-arrow bg-base-200 rounded-box" id="collapseGithub"> <input type="checkbox" class="peer oauth-provider-collapse" data-provider="github" {% if settings.oauth_github_enabled == "true" %}checked{% endif %} /> <div class="collapse-title flex items-center gap-3"> <svg class="h-5 w-5" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.83 2.81 1.3 3.5 1 .1-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6.02 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3z"/></svg> <span class="font-semibold">{{ oauth_github_label }}</span> <span class="badge badge-sm badge-outline ml-1">{{ oauth_github_desc }}</span> <input type="checkbox" name="oauth_github_enabled" value="true" class="toggle toggle-primary toggle-sm ml-auto oauth-provider-toggle" data-provider="github" {% if settings.oauth_github_enabled == "true" %}checked{% endif %} onclick="event.stopPropagation()"> </div> <div class="collapse-content space-y-3"> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_client_id }}</span></label> <input type="text" name="oauth_github_client_id" value="{{ settings.oauth_github_client_id }}" placeholder="Iv1.xxxxxx" class="input input-bordered input-sm w-full"> </div> <div class="form-control"> <label class="label"> <span class="label-text">{{ oauth_client_secret }}</span> {% if settings.oauth_github_has_secret %} <span class="badge badge-success badge-sm gap-1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ oauth_secret_configured }} </span> {% else %} <span class="badge badge-warning badge-sm">{{ oauth_secret_not_configured }}</span> {% endif %} </label> <input type="password" name="oauth_github_client_secret" placeholder="{% if settings.oauth_github_has_secret %}{{ oauth_secret_change }}{% else %}ghp_...{% endif %}" class="input input-bordered input-sm w-full" autocomplete="off"> </div> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_redirect_uri }}</span></label> <div class="join w-full"> <input type="text" readonly value="{{ settings.oauth_redirect_base_url }}/api/v1/auth/oauth/github/callback" class="input input-bordered input-sm join-item w-full bg-base-200 font-mono text-xs" id="githubRedirectUri"> <button type="button" class="btn btn-sm join-item btn-outline copy-uri-btn" data-target="githubRedirectUri">{{ oauth_copy_uri }}</button> </div> </div> </div> </div> <!-- ===== Discord ===== --> <div class="collapse collapse-arrow bg-base-200 rounded-box" id="collapseDiscord"> <input type="checkbox" class="peer oauth-provider-collapse" data-provider="discord" {% if settings.oauth_discord_enabled == "true" %}checked{% endif %} /> <div class="collapse-title flex items-center gap-3"> <svg class="h-5 w-5" viewBox="0 0 24 24"><path fill="currentColor" d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg> <span class="font-semibold">{{ oauth_discord_label }}</span> <span class="badge badge-sm badge-outline ml-1">{{ oauth_discord_desc }}</span> <input type="checkbox" name="oauth_discord_enabled" value="true" class="toggle toggle-primary toggle-sm ml-auto oauth-provider-toggle" data-provider="discord" {% if settings.oauth_discord_enabled == "true" %}checked{% endif %} onclick="event.stopPropagation()"> </div> <div class="collapse-content space-y-3"> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_client_id }}</span></label> <input type="text" name="oauth_discord_client_id" value="{{ settings.oauth_discord_client_id }}" placeholder="123456789012345678" class="input input-bordered input-sm w-full"> </div> <div class="form-control"> <label class="label"> <span class="label-text">{{ oauth_client_secret }}</span> {% if settings.oauth_discord_has_secret %} <span class="badge badge-success badge-sm gap-1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ oauth_secret_configured }} </span> {% else %} <span class="badge badge-warning badge-sm">{{ oauth_secret_not_configured }}</span> {% endif %} </label> <input type="password" name="oauth_discord_client_secret" placeholder="{% if settings.oauth_discord_has_secret %}{{ oauth_secret_change }}{% endif %}" class="input input-bordered input-sm w-full" autocomplete="off"> </div> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_redirect_uri }}</span></label> <div class="join w-full"> <input type="text" readonly value="{{ settings.oauth_redirect_base_url }}/api/v1/auth/oauth/discord/callback" class="input input-bordered input-sm join-item w-full bg-base-200 font-mono text-xs" id="discordRedirectUri"> <button type="button" class="btn btn-sm join-item btn-outline copy-uri-btn" data-target="discordRedirectUri">{{ oauth_copy_uri }}</button> </div> </div> </div> </div> <!-- ===== Microsoft ===== --> <div class="collapse collapse-arrow bg-base-200 rounded-box" id="collapseMicrosoft"> <input type="checkbox" class="peer oauth-provider-collapse" data-provider="microsoft" {% if settings.oauth_microsoft_enabled == "true" %}checked{% endif %} /> <div class="collapse-title flex items-center gap-3"> <svg class="h-5 w-5" viewBox="0 0 24 24"><path fill="#f25022" d="M1 1h10v10H1z"/><path fill="#00a4ef" d="M1 13h10v10H1z"/><path fill="#7fba00" d="M13 1h10v10H13z"/><path fill="#ffb900" d="M13 13h10v10H13z"/></svg> <span class="font-semibold">{{ oauth_microsoft_label }}</span> <span class="badge badge-sm badge-outline ml-1">{{ oauth_microsoft_desc }}</span> <input type="checkbox" name="oauth_microsoft_enabled" value="true" class="toggle toggle-primary toggle-sm ml-auto oauth-provider-toggle" data-provider="microsoft" {% if settings.oauth_microsoft_enabled == "true" %}checked{% endif %} onclick="event.stopPropagation()"> </div> <div class="collapse-content space-y-3"> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_client_id }}</span></label> <input type="text" name="oauth_microsoft_client_id" value="{{ settings.oauth_microsoft_client_id }}" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" class="input input-bordered input-sm w-full"> </div> <div class="form-control"> <label class="label"> <span class="label-text">{{ oauth_client_secret }}</span> {% if settings.oauth_microsoft_has_secret %} <span class="badge badge-success badge-sm gap-1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ oauth_secret_configured }} </span> {% else %} <span class="badge badge-warning badge-sm">{{ oauth_secret_not_configured }}</span> {% endif %} </label> <input type="password" name="oauth_microsoft_client_secret" placeholder="{% if settings.oauth_microsoft_has_secret %}{{ oauth_secret_change }}{% endif %}" class="input input-bordered input-sm w-full" autocomplete="off"> </div> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_tenant }}</span></label> <input type="text" name="oauth_microsoft_tenant" value="{{ settings.oauth_microsoft_tenant }}" placeholder="common" class="input input-bordered input-sm w-full"> </div> <div class="form-control"> <label class="label"><span class="label-text">{{ oauth_redirect_uri }}</span></label> <div class="join w-full"> <input type="text" readonly value="{{ settings.oauth_redirect_base_url }}/api/v1/auth/oauth/microsoft/callback" class="input input-bordered input-sm join-item w-full bg-base-200 font-mono text-xs" id="microsoftRedirectUri"> <button type="button" class="btn btn-sm join-item btn-outline copy-uri-btn" data-target="microsoftRedirectUri">{{ oauth_copy_uri }}</button> </div> </div> </div> </div> </div> <!-- Restart warning --> <div class="alert alert-warning mt-4"> <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg> <span class="text-sm">{{ oauth_restart_warning }}</span> </div> <div class="alert alert-info mt-2"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span class="text-sm">{{ oauth_env_info }}</span> </div> </div> </div> </div> <!-- Audience Measurement Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title text-xl mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg> {{ analytics_title }} </h2> <p class="text-sm text-base-content/70 mb-4">{{ analytics_desc }}</p> {% if settings.analytics_configured %} <!-- Script URL: read-only, it feeds the CSP which is frozen at startup. --> <div class="form-control mb-4"> <label class="label"> <span class="label-text font-semibold">{{ analytics_script_url_label }}</span> </label> <input type="text" class="input input-bordered w-full font-mono text-sm" value="{{ settings.analytics_script_url }}" readonly disabled /> <label class="label"> <span class="label-text-alt text-base-content/60">{{ analytics_script_url_desc }}</span> </label> </div> <div class="form-control mb-4"> <label class="label cursor-pointer justify-start gap-4"> <input type="checkbox" name="analytics_enabled" value="true" class="toggle toggle-primary" {% if settings.analytics_enabled == "true" %}checked{% endif %} /> <div> <span class="label-text font-semibold">{{ analytics_enabled_label }}</span> <p class="text-xs text-base-content/60 mt-1">{{ analytics_enabled_desc }}</p> </div> </label> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ analytics_site_id_label }}</span> </label> <input type="text" name="analytics_site_id" class="input input-bordered w-full" value="{{ settings.analytics_site_id }}" /> <label class="label"> <span class="label-text-alt text-base-content/60">{{ analytics_site_id_desc }}</span> </label> </div> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ analytics_attributes_label }}</span> </label> <input type="text" name="analytics_attributes" class="input input-bordered w-full font-mono text-sm" value="{{ settings.analytics_attributes }}" /> <label class="label"> <span class="label-text-alt text-base-content/60">{{ analytics_attributes_desc }}</span> </label> </div> </div> {% else %} <div class="alert alert-info"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span class="text-sm">{{ analytics_not_configured }}</span> </div> {% endif %} </div> </div> <!-- Action Buttons --> <div class="card bg-base-200 shadow-xl"> <div class="card-body"> <div class="flex justify-between items-center"> <div> <h3 class="font-semibold">{{ save_changes }}</h3> <p class="text-sm text-base-content/70">{{ save_changes_desc }}</p> </div> <div class="flex gap-2"> <a href="/admin" class="btn btn-ghost">{{ cancel }}</a> <button type="submit" class="btn btn-primary"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ save_button }} </button> </div> </div> </div> </div> </form> </div> <script> const expirationCheckbox = document.getElementById('expirationEnabled'); const expirationSettings = document.getElementById('expirationSettings'); function toggleExpirationSettings() { if (expirationCheckbox.checked) { expirationSettings.classList.remove('hidden'); } else { expirationSettings.classList.add('hidden'); } } if (expirationCheckbox) { expirationCheckbox.addEventListener('change', toggleExpirationSettings); toggleExpirationSettings(); } const oauthCheckbox = document.getElementById('oauthEnabled'); const oauthGlobalOptions = document.getElementById('oauthGlobalOptions'); function toggleOauthSection() { if (oauthCheckbox && oauthGlobalOptions) { if (oauthCheckbox.checked) { oauthGlobalOptions.classList.remove('hidden', 'opacity-50'); oauthGlobalOptions.style.pointerEvents = ''; } else { oauthGlobalOptions.classList.add('hidden'); } } } if (oauthCheckbox) { oauthCheckbox.addEventListener('change', toggleOauthSection); toggleOauthSection(); } document.querySelectorAll('.copy-uri-btn').forEach(function(btn) { btn.addEventListener('click', function() { var target = document.getElementById(this.dataset.target); if (target) { navigator.clipboard.writeText(target.value).then(function() { btn.textContent = '✓'; setTimeout(function() { btn.textContent = '{{ oauth_copy_uri }}'; }, 1500); }); } }); }); document.getElementById('settingsForm').addEventListener('submit', async function(e) { e.preventDefault(); const formData = new FormData(this); const submitBtn = this.querySelector('button[type="submit"]'); submitBtn.disabled = true; submitBtn.innerHTML = '<span class="loading loading-spinner"></span> {{ saving }}'; try { const response = await fetch('/admin/settings', { method: 'PUT', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': '{{ csrf_token }}', }, body: new URLSearchParams(formData) }); const result = await response.json(); if (response.ok) { alert('✅ {{ save_success }}'); window.location.reload(); } else { alert('❌ {{ save_error_prefix }} : ' + (result.error || result.message || '')); submitBtn.disabled = false; submitBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ save_button }}'; } } catch (error) { console.error('Error:', error); alert('❌ {{ network_error }}'); submitBtn.disabled = false; submitBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ save_button }}'; } }); </script> {% endblock %}
GitRust