2026-03-05 09:42:39 -05:00
<!doctype html>
2026-02-20 12:27:52 +01:00
< html lang = "en" >
2026-03-05 09:42:39 -05:00
< head >
< meta charset = "UTF-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
2026-05-30 01:06:32 +02:00
<!-- Declares the page supports both modes. Switching between them is
driven by the CSS `color-scheme` property (via the `data-color-scheme`
attribute on <html> set by theme-init.js), not by mutating this tag. -->
< meta name = "color-scheme" content = "light dark" />
2026-03-05 09:42:39 -05:00
< title > OxiCloud — Admin Panel</ title >
2026-05-19 17:23:38 +02:00
< link rel = "icon" type = "image/svg+xml" href = "/logo/logo-plain.svg" >
2026-03-06 20:54:18 -05:00
< script src = "/js/core/theme-init.js" ></ script >
2026-03-05 09:42:39 -05:00
< link rel = "stylesheet" href = "/css/main.css" />
< link rel = "stylesheet" href = "/css/views/admin.css" />
</ head >
< body >
< div class = "admin-header" >
< div class = "admin-header-left" >
< a href = "/" class = "admin-logo-link link-reset-flex" >
< div class = "admin-logo" >
< svg viewBox = "0 0 500 500" >
< path
d = "M345 310c32 0 58-26 58-58s-26-58-58-58c-6.2 0-12 0.9-17.5 2.7C318 166 289 143 255 143c-34.3 0-63.1 22.6-73 53.7C176.9 195.7 171 195 165 195c-32 0-58 26-58 58s26 58 58 58h180z"
/>
</ svg >
</ div >
< span class = "admin-title-text" > OxiCloud</ span >
</ a >
2026-03-09 00:08:34 +01:00
< span class = "admin-title-separator" > · < span data-i18n = "admin.tab_dashboard" > Admin</ span ></ span >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "admin-header-right" >
< a href = "/"
2026-03-09 00:08:34 +01:00
>< i class = "fas fa-arrow-left" ></ i > < span data-i18n = "admin.back_to_app" > Back to OxiCloud</ span ></ a
2026-03-05 09:42:39 -05:00
>
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div class = "admin-container" >
2026-03-09 00:08:34 +01:00
< div id = "loading" >< i class = "fas fa-circle-notch" ></ i > < span data-i18n = "admin.loading" > Loading…</ span ></ div >
2026-03-05 09:42:39 -05:00
< div id = "access-denied" >
< div class = "access-icon" >< i class = "fas fa-lock" ></ i ></ div >
2026-03-09 00:08:34 +01:00
< h2 data-i18n = "admin.access_denied" > Access Denied</ h2 >
< p data-i18n = "admin.access_denied_desc" > Administrator privileges required to access this panel.</ p >
< a href = "/login" >< i class = "fas fa-sign-in-alt" ></ i > < span data-i18n = "admin.sign_in" > Sign in</ span ></ a >
2026-03-05 09:42:39 -05:00
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div id = "main-content" >
< div class = "admin-tabs" >
< button
class = "admin-tab active"
2026-03-06 20:54:18 -05:00
id = "tab-btn-dashboard"
2026-03-05 09:42:39 -05:00
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-chart-pie" ></ i > < span data-i18n = "admin.tab_dashboard" > Dashboard</ span >
2026-03-05 09:42:39 -05:00
</ button >
2026-03-06 20:54:18 -05:00
< button class = "admin-tab" id = "tab-btn-users" >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-users" ></ i > < span data-i18n = "admin.tab_users" > Users</ span >
2026-03-05 09:42:39 -05:00
</ button >
2026-03-06 20:54:18 -05:00
< button class = "admin-tab" id = "tab-btn-oidc" >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-key" ></ i > < span data-i18n = "admin.tab_oidc" > SSO / OIDC</ span >
2026-03-05 09:42:39 -05:00
</ button >
2026-04-14 21:33:38 +02:00
< button class = "admin-tab" id = "tab-btn-storage" >
< i class = "fas fa-database" ></ i > < span data-i18n = "admin.tab_storage" > Storage</ span >
</ button >
2026-06-02 00:35:45 +02:00
< button class = "admin-tab" id = "tab-btn-smtp" >
< i class = "fas fa-envelope" ></ i > < span data-i18n = "admin.tab_smtp" > SMTP</ span >
</ button >
2026-03-05 09:42:39 -05:00
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div id = "tab-dashboard" class = "tab-content active" >
< div class = "stats-grid" >
< div class = "stat-card" >
< div
class = "stat-value text-blue"
id = "ds-total-users"
>
—
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.total_users" > Total Users</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div
class = "stat-value text-green"
id = "ds-active-users"
>
—
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.active_users" > Active Users</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div
class = "stat-value text-blue"
id = "ds-admin-users"
>
—
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.admins" > Admins</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-version" > —</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.version" > Version</ div >
2026-03-05 09:42:39 -05:00
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div class = "admin-card" >
2026-03-09 00:08:34 +01:00
< h2 >< i class = "fas fa-hdd" ></ i > < span data-i18n = "admin.storage_overview" > Storage Overview</ span ></ h2 >
2026-03-05 09:42:39 -05:00
< div class = "stats-grid" >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-used" > —</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.used" > Used</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-quota" > —</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.total_quota" > Total Quota</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-usage-pct" >
—
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.usage_pct" > Usage %</ div >
2026-03-05 09:42:39 -05:00
</ div >
</ div >
< div class = "progress-bar" >
< div
class = "progress-fill green width-zero"
id = "ds-bar"
></ div >
</ div >
< div class = "mt-14" >
< div class = "stats-grid" >
< div class = "stat-card warn" id = "ds-warn-card" >
< div
class = "stat-value text-orange"
id = "ds-over80"
>
0
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.users_over_80" >
2026-03-05 09:42:39 -05:00
Users > 80% quota
</ div >
</ div >
< div
class = "stat-card danger"
id = "ds-danger-card"
>
< div
class = "stat-value text-red"
id = "ds-overquota"
>
0
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.users_over_quota" >
2026-03-05 09:42:39 -05:00
Users over quota
</ div >
</ div >
</ div >
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div class = "admin-card" >
2026-03-09 00:08:34 +01:00
< h2 >< i class = "fas fa-server" ></ i > < span data-i18n = "admin.system" > System</ span ></ h2 >
2026-03-05 09:42:39 -05:00
< div class = "stats-grid" >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-auth" > —</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.auth_label" > Auth</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-oidc" > —</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.oidc_label" > OIDC</ div >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "stat-card" >
< div class = "stat-value" id = "ds-quotas-flag" >
—
</ div >
2026-03-09 00:08:34 +01:00
< div class = "stat-label" data-i18n = "admin.quotas_label" > Quotas</ div >
2026-03-05 09:42:39 -05:00
</ div >
</ div >
< div class = "toggle-row toggle-row-strong" >
< label
>< i
class = "fas fa-user-plus icon-muted-right"
></ i >
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.allow_registration" > Allow public self-registration</ span ></ label
2026-03-05 09:42:39 -05:00
>
< label class = "switch"
>< input
type = "checkbox"
id = "ds-registration"
2026-03-09 00:08:34 +01:00
checked />< span
2026-03-05 09:42:39 -05:00
class = "slider"
></ span
></ label >
</ div >
< div class = "warning" id = "registration-warning" >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-exclamation-triangle" ></ i > < span data-i18n = "admin.registration_warning" > Public registration is disabled. Only admins can create new users.</ span >
2026-03-05 09:42:39 -05:00
</ div >
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div id = "tab-users" class = "tab-content" >
< div class = "admin-card" >
< h2 class = "h2-space-between" >
< span
2026-03-09 00:08:34 +01:00
>< i class = "fas fa-users-cog" ></ i > < span data-i18n = "admin.user_management" > User Management</ span ></ span
2026-03-05 09:42:39 -05:00
>< button
class = "btn btn-primary"
2026-03-06 20:54:18 -05:00
id = "btn-create-user"
2026-03-05 09:42:39 -05:00
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-user-plus" ></ i > < span data-i18n = "admin.create_user" > Create User</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ h2 >
< div class = "table-wrap" >
< table >
< thead >
< tr >
2026-03-09 00:08:34 +01:00
< th data-i18n = "admin.col_user" > User</ th >
< th data-i18n = "admin.col_role" > Role</ th >
< th data-i18n = "admin.col_auth" > Auth</ th >
< th data-i18n = "admin.col_status" > Status</ th >
< th data-i18n = "admin.col_storage" > Storage</ th >
< th data-i18n = "admin.col_last_login" > Last Login</ th >
< th data-i18n = "admin.col_actions" > Actions</ th >
2026-03-05 09:42:39 -05:00
</ tr >
</ thead >
< tbody id = "users-tbody" >
< tr >
< td
colspan = "7"
class = "table-loading-cell"
>
< i
class = "fas fa-spinner fa-spin"
></ i >
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.loading_users" > Loading users…</ span >
2026-03-05 09:42:39 -05:00
</ td >
</ tr >
</ tbody >
</ table >
</ div >
< div class = "pagination" >
< span id = "users-info" > —</ span >
< div class = "flex-gap-6" >
< button
class = "btn btn-sm btn-secondary"
id = "prev-btn"
disabled
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-chevron-left" ></ i > < span data-i18n = "admin.prev" > Prev</ span >
2026-03-05 09:42:39 -05:00
</ button >
< button
class = "btn btn-sm btn-secondary"
id = "next-btn"
>
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.next" > Next</ span > < i class = "fas fa-chevron-right" ></ i >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
</ div >
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-05 09:42:39 -05:00
< div id = "tab-oidc" class = "tab-content" >
< div class = "admin-card" >
< h2 >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-shield-alt" ></ i > < span data-i18n = "admin.sso_title" > Single Sign-On (OIDC / SSO)</ span >
2026-03-05 09:42:39 -05:00
</ h2 >
< div class = "toggle-row" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.enable_sso" > Enable SSO Authentication</ label >
2026-03-05 09:42:39 -05:00
< label class = "switch"
>< input
type = "checkbox"
id = "oidc-enabled" />< span
class = "slider"
></ span
></ label >
</ div >
< div id = "oidc-form" >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.provider_name" > Provider Name</ span >
2026-03-05 09:42:39 -05:00
< span id = "badge-provider_name" ></ span
></ label >
< input
type = "text"
id = "provider-name"
placeholder = "e.g., Authentik, Keycloak"
/>
</ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.issuer_url" > Issuer URL</ span >
2026-03-05 09:42:39 -05:00
< span id = "badge-issuer_url" ></ span
></ label >
< input
type = "url"
id = "issuer-url"
placeholder = "https://auth.example.com/application/o/oxicloud/"
/>
2026-03-09 00:08:34 +01:00
< small data-i18n = "admin.issuer_url_hint"
2026-03-05 09:42:39 -05:00
> OpenID Connect issuer URL of your identity
provider</ small
>
</ div >
< div class = "oidc-discover-wrap" >
< button
class = "btn btn-secondary btn-sm"
id = "discover-btn"
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-search" ></ i > < span data-i18n = "admin.auto_discover" > Auto-discover</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
< div id = "discovery-result" ></ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.client_id" > Client ID</ span > < span id = "badge-client_id" ></ span
2026-03-05 09:42:39 -05:00
></ label >
< input
type = "text"
id = "client-id"
placeholder = "oxicloud"
/>
</ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.client_secret" > Client Secret</ span >
2026-03-05 09:42:39 -05:00
< span id = "badge-client_secret" ></ span
></ label >
< input
type = "password"
id = "client-secret"
placeholder = "Leave empty to keep current value"
2026-03-09 00:08:34 +01:00
data-i18n-placeholder = "admin.client_secret_placeholder"
2026-03-05 09:42:39 -05:00
/>
< small id = "secret-hint"
>< i
class = "fas fa-check-circle secret-icon"
></ i >
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.secret_configured" > A client secret is already configured</ span ></ small
2026-03-05 09:42:39 -05:00
>
</ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.callback_url" > Callback URL</ span >
< small class = "small-muted" data-i18n = "admin.callback_url_hint"
2026-03-05 09:42:39 -05:00
> (register in your IdP)</ small
></ label
>
< div class = "readonly-field" >
< span id = "callback-url" > —</ span
>< button
2026-03-06 20:54:18 -05:00
id = "btn-copy-callback"
2026-03-05 09:42:39 -05:00
title = "Copy"
>
< i class = "fas fa-copy" ></ i >
</ button >
</ div >
</ div >
< details >
< summary >
< i
class = "fas fa-sliders-h summary-icon-right"
></ i >
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.advanced_settings" > Advanced Settings</ span >
2026-03-05 09:42:39 -05:00
</ summary >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.scopes" > Scopes</ span > < span id = "badge-scopes" ></ span >
2026-03-05 09:42:39 -05:00
></ label >
< input
type = "text"
id = "scopes"
placeholder = "openid profile email"
/>
</ div >
< div class = "toggle-row" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.auto_provision"
2026-03-05 09:42:39 -05:00
> Auto-provision users on first
login</ label
>
< label class = "switch"
>< input
type = "checkbox"
id = "auto-provision"
checked />< span
class = "slider"
></ span
></ label >
</ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.admin_groups" > Admin Groups</ span >
< span id = "badge-admin_groups" ></ span >
2026-03-05 09:42:39 -05:00
></ label >
< input
type = "text"
id = "admin-groups"
placeholder = "e.g., oxicloud-admins"
/>
2026-03-09 00:08:34 +01:00
< small data-i18n = "admin.admin_groups_hint"
2026-03-05 09:42:39 -05:00
> Comma-separated OIDC group names that
map to admin role</ small
>
</ div >
< div class = "toggle-row" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.disable_password"
2026-03-05 09:42:39 -05:00
> Disable password login (OIDC
only)</ label
>
< label class = "switch"
>< input
type = "checkbox"
id = "disable-password" />< span
class = "slider"
></ span
></ label >
</ div >
< div class = "warning" id = "password-warning" >
< i class = "fas fa-exclamation-triangle" ></ i >
2026-03-09 00:08:34 +01:00
< span data-i18n = "admin.password_warning" > This will prevent ALL password-based logins!</ span >
2026-03-05 09:42:39 -05:00
</ div >
</ details >
< div class = "oidc-actions" >
< button
class = "btn btn-secondary"
2026-03-06 20:54:18 -05:00
id = "btn-test-oidc"
2026-03-05 09:42:39 -05:00
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-vial" ></ i > < span data-i18n = "admin.test_btn" > Test</ span >
2026-03-05 09:42:39 -05:00
</ button >
< button
class = "btn btn-primary"
id = "save-btn"
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-save" ></ i > < span data-i18n = "admin.save_btn" > Save</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
< div id = "oidc-status" class = "alert" ></ div >
</ div >
</ div >
</ div >
2026-04-14 21:33:38 +02:00
<!-- ════════ Storage tab ════════ -->
< div id = "tab-storage" class = "tab-content" >
< div class = "admin-card" >
< h2 >
< i class = "fas fa-database" ></ i > < span data-i18n = "admin.storage_title" > Storage Backend</ span >
</ h2 >
<!-- Current status -->
< div class = "storage-status-grid" >
< div class = "storage-stat" >
< span class = "storage-stat__label" data-i18n = "admin.storage_current_backend" > Active Backend</ span >
< span class = "storage-stat__value" id = "storage-current-backend" > —</ span >
</ div >
< div class = "storage-stat" >
< span class = "storage-stat__label" data-i18n = "admin.storage_total_blobs" > Total Blobs</ span >
< span class = "storage-stat__value" id = "storage-total-blobs" > —</ span >
</ div >
< div class = "storage-stat" >
< span class = "storage-stat__label" data-i18n = "admin.storage_total_size" > Total Size</ span >
< span class = "storage-stat__value" id = "storage-total-size" > —</ span >
</ div >
< div class = "storage-stat" >
< span class = "storage-stat__label" data-i18n = "admin.storage_dedup_ratio" > Dedup Ratio</ span >
< span class = "storage-stat__value" id = "storage-dedup-ratio" > —</ span >
</ div >
</ div >
<!-- Backend selector -->
< div class = "storage-backend-selector" >
< label class = "storage-backend-option" >
< input type = "radio" name = "storage-backend" value = "local" checked />
< span class = "storage-backend-option__card" >
< i class = "fas fa-hdd" ></ i >
< span data-i18n = "admin.storage_local" > Local Filesystem</ span >
</ span >
</ label >
< label class = "storage-backend-option" >
< input type = "radio" name = "storage-backend" value = "s3" />
< span class = "storage-backend-option__card" >
< i class = "fas fa-cloud" ></ i >
< span data-i18n = "admin.storage_s3" > S3-Compatible</ span >
</ span >
</ label >
</ div >
<!-- S3 form (shown when S3 is selected) -->
< div id = "storage-s3-form" class = "storage-form" style = "display:none" >
<!-- Provider preset -->
< div class = "form-group" >
< label data-i18n = "admin.storage_provider_preset" > Provider Preset</ label >
< select id = "storage-preset" >
< option value = "custom" data-i18n = "admin.storage_preset_custom" > Custom</ option >
< option value = "aws" > Amazon S3</ option >
< option value = "backblaze" > Backblaze B2</ option >
< option value = "cloudflare-r2" > Cloudflare R2</ option >
< option value = "minio" > MinIO</ option >
< option value = "digitalocean" > DigitalOcean Spaces</ option >
< option value = "wasabi" > Wasabi</ option >
</ select >
</ div >
<!-- Endpoint URL -->
< div class = "form-group" >
< label >< span data-i18n = "admin.storage_endpoint_url" > Endpoint URL</ span >
< span id = "badge-s3_endpoint_url" ></ span ></ label >
< input type = "url" id = "storage-endpoint-url" placeholder = "https://s3.amazonaws.com" />
< small data-i18n = "admin.storage_endpoint_hint" > Leave empty for Amazon S3 default</ small >
</ div >
<!-- Bucket -->
< div class = "form-group" >
< label >< span data-i18n = "admin.storage_bucket" > Bucket</ span >
< span id = "badge-s3_bucket" ></ span ></ label >
< input type = "text" id = "storage-bucket" placeholder = "my-oxicloud-bucket" />
</ div >
<!-- Region -->
< div class = "form-group" >
< label >< span data-i18n = "admin.storage_region" > Region</ span >
< span id = "badge-s3_region" ></ span ></ label >
< input type = "text" id = "storage-region" placeholder = "us-east-1" />
</ div >
<!-- Access Key -->
< div class = "form-group" >
< label >< span data-i18n = "admin.storage_access_key" > Access Key ID</ span >
< span id = "badge-s3_access_key" ></ span ></ label >
< input type = "text" id = "storage-access-key" placeholder = "AKIA..." />
</ div >
<!-- Secret Key -->
< div class = "form-group" >
< label >< span data-i18n = "admin.storage_secret_key" > Secret Access Key</ span >
< span id = "badge-s3_secret_key" ></ span ></ label >
< input type = "password" id = "storage-secret-key" placeholder = "Leave empty to keep current value" />
< small id = "storage-secret-hint" style = "display:none" >
< i class = "fas fa-check-circle secret-icon" ></ i >
< span data-i18n = "admin.storage_secret_configured" > A secret key is already configured</ span >
</ small >
</ div >
<!-- Force Path Style -->
< div class = "toggle-row" >
< label data-i18n = "admin.storage_path_style" > Force Path Style</ label >
< label class = "switch" >
< input type = "checkbox" id = "storage-path-style" />< span class = "slider" ></ span >
</ label >
</ div >
< small data-i18n = "admin.storage_path_style_hint" > Required for MinIO and some S3-compatible providers</ small >
</ div >
<!-- Action buttons -->
< div class = "storage-actions" >
< button class = "btn btn-secondary" id = "btn-test-storage" >
< i class = "fas fa-vial" ></ i > < span data-i18n = "admin.storage_test_connection" > Test Connection</ span >
</ button >
< button class = "btn btn-primary" id = "btn-save-storage" >
< i class = "fas fa-save" ></ i > < span data-i18n = "admin.storage_save" > Save</ span >
</ button >
</ div >
< div id = "storage-status" class = "alert" ></ div >
<!-- Migration section -->
< div class = "storage-migration-section" >
< h3 >< i class = "fas fa-exchange-alt" ></ i > < span data-i18n = "admin.storage_migration" > Backend Migration</ span ></ h3 >
<!-- Status badge -->
< div class = "migration-status-row" >
< span data-i18n = "admin.migration_status_label" > Status:</ span >
< span id = "migration-status-badge" class = "badge badge-migration badge-migration--idle" > Idle</ span >
</ div >
<!-- Progress bar (hidden when idle) -->
< div id = "migration-progress-section" style = "display:none" >
< div class = "migration-progress-bar" >
< div class = "migration-progress-bar__fill" id = "migration-progress-fill" style = "width:0%" ></ div >
</ div >
< div class = "migration-progress-text" >
< span id = "migration-progress-label" > 0 / 0 blobs (0%)</ span >
< span id = "migration-throughput" ></ span >
</ div >
< div class = "migration-progress-text" >
< span id = "migration-bytes-label" ></ span >
< span id = "migration-eta" ></ span >
</ div >
</ div >
<!-- Failed blobs (expandable) -->
< details id = "migration-failed-section" style = "display:none" >
< summary >< i class = "fas fa-exclamation-triangle" ></ i > < span id = "migration-failed-count" > 0</ span > < span data-i18n = "admin.migration_failed_blobs" > failed blobs</ span ></ summary >
< pre id = "migration-failed-list" class = "migration-failed-list" ></ pre >
</ details >
<!-- Action buttons -->
< div class = "migration-actions" >
< button class = "btn btn-primary" id = "btn-start-migration" >
< i class = "fas fa-play" ></ i > < span data-i18n = "admin.migration_start" > Start Migration</ span >
</ button >
< button class = "btn btn-secondary" id = "btn-pause-migration" style = "display:none" >
< i class = "fas fa-pause" ></ i > < span data-i18n = "admin.migration_pause" > Pause</ span >
</ button >
< button class = "btn btn-primary" id = "btn-resume-migration" style = "display:none" >
< i class = "fas fa-play" ></ i > < span data-i18n = "admin.migration_resume" > Resume</ span >
</ button >
< button class = "btn btn-secondary" id = "btn-verify-migration" style = "display:none" >
< i class = "fas fa-check-double" ></ i > < span data-i18n = "admin.migration_verify" > Verify Integrity</ span >
</ button >
< button class = "btn btn-primary" id = "btn-complete-migration" style = "display:none" >
< i class = "fas fa-flag-checkered" ></ i > < span data-i18n = "admin.migration_complete" > Finalize</ span >
</ button >
</ div >
< div id = "migration-verify-result" style = "display:none" ></ div >
< div id = "migration-status-msg" class = "alert" style = "display:none" ></ div >
</ div >
</ div >
</ div >
2026-06-02 00:35:45 +02:00
<!-- ════════ SMTP tab ════════ -->
< div id = "tab-smtp" class = "tab-content" >
< div class = "admin-card" >
< h2 >
< i class = "fas fa-envelope" ></ i > < span data-i18n = "admin.smtp_title" > Outbound Email (SMTP)</ span >
</ h2 >
< p class = "muted" data-i18n = "admin.smtp_intro" >
SMTP is configured exclusively via environment variables (OXICLOUD_SMTP_*). The values below are read from the running server — to change them, edit the environment and restart OxiCloud.
</ p >
< table class = "smtp-info-table" >
< tbody >
< tr >
< th data-i18n = "admin.smtp_enabled_label" > Status</ th >
< td id = "smtp-enabled" > —</ td >
</ tr >
< tr >
< th > OXICLOUD_SMTP_HOST</ th >
< td id = "smtp-host" > —</ td >
</ tr >
< tr >
< th > OXICLOUD_SMTP_PORT</ th >
< td id = "smtp-port" > —</ td >
</ tr >
< tr >
< th > OXICLOUD_SMTP_TLS</ th >
< td id = "smtp-tls" > —</ td >
</ tr >
< tr >
< th > OXICLOUD_SMTP_FROM</ th >
< td id = "smtp-from" > —</ td >
</ tr >
< tr >
< th > OXICLOUD_SMTP_USER</ th >
< td id = "smtp-user-state" > —</ td >
</ tr >
</ tbody >
</ table >
< h3 class = "mt-14" >
< i class = "fas fa-paper-plane" ></ i > < span data-i18n = "admin.smtp_test_title" > Send a test email</ span >
</ h3 >
< p class = "muted" data-i18n = "admin.smtp_test_intro" >
Sends a hardcoded diagnostic message to the recipient below and reports the SMTP server's response so you can correlate it with your relay logs.
</ p >
< div class = "form-group" >
< label for = "smtp-test-to" data-i18n = "admin.smtp_test_to" > Recipient address</ label >
< input
id = "smtp-test-to"
type = "email"
placeholder = "alice@example.com"
autocomplete = "off"
/>
</ div >
< button id = "btn-smtp-test" class = "btn btn-primary" >
< i class = "fas fa-paper-plane" ></ i > < span data-i18n = "admin.smtp_send_test" > Send test email</ span >
</ button >
< div id = "smtp-test-result" class = "alert" style = "display:none; margin-top:14px;" ></ div >
</ div >
</ div >
2026-03-05 09:42:39 -05:00
</ div >
2026-02-20 12:27:52 +01:00
</ div >
2026-03-05 09:42:39 -05:00
< div id = "quota-modal" class = "modal-overlay hidden" >
< div class = "modal" >
< h3 >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-box modal-title-icon" ></ i > < span data-i18n = "admin.quota_modal_title" > Update Storage Quota</ span >
2026-03-05 09:42:39 -05:00
</ h3 >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label >< span data-i18n = "admin.quota_user_label" > User:</ span > < strong id = "qm-username" ></ strong ></ label >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.new_quota" > New Quota</ label >
2026-03-05 09:42:39 -05:00
< div class = "quota-input-row" >
< input
type = "number"
id = "qm-value"
min = "0"
step = "1"
class = "flex-2"
/>
< select id = "qm-unit" class = "select-qm-unit" >
< option value = "1073741824" > GB</ option >
< option value = "1048576" > MB</ option >
< option value = "1099511627776" > TB</ option >
</ select >
</ div >
2026-03-09 00:08:34 +01:00
< small data-i18n = "admin.quota_unlimited_hint" > Set to 0 for unlimited</ small >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "modal-actions" >
< button
class = "btn btn-secondary"
2026-03-06 20:54:18 -05:00
id = "btn-close-quota"
2026-03-09 00:08:34 +01:00
data-i18n = "admin.cancel"
2026-03-05 09:42:39 -05:00
>
Cancel
</ button >
2026-03-06 20:54:18 -05:00
< button class = "btn btn-primary" id = "btn-save-quota" >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-save" ></ i > < span data-i18n = "admin.save_btn" > Save</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
2026-02-20 12:27:52 +01:00
</ div >
</ div >
2026-03-05 09:42:39 -05:00
< div id = "create-user-modal" class = "modal-overlay hidden" >
< div class = "modal" >
< h3 >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-user-plus modal-title-icon" ></ i > < span data-i18n = "admin.create_user_title" > Create New User</ span >
2026-03-05 09:42:39 -05:00
</ h3 >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label >< span data-i18n = "admin.username_label" > Username</ span > *</ label >
2026-03-05 09:42:39 -05:00
< input
type = "text"
id = "cu-username"
placeholder = "johndoe"
2026-03-09 00:08:34 +01:00
data-i18n-placeholder = "admin.username_placeholder"
2026-03-05 09:42:39 -05:00
minlength = "3"
maxlength = "32"
/>
2026-03-09 00:08:34 +01:00
< small data-i18n = "admin.username_hint" > 3– 32 characters</ small >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label >< span data-i18n = "admin.password_label" > Password</ span > *</ label >
2026-03-05 09:42:39 -05:00
< input
type = "password"
id = "cu-password"
placeholder = "Min 8 characters"
2026-03-09 00:08:34 +01:00
data-i18n-placeholder = "admin.password_placeholder"
2026-03-05 09:42:39 -05:00
minlength = "8"
/>
</ div >
< div class = "form-group" >
< label
2026-03-09 00:08:34 +01:00
>< span data-i18n = "admin.email_label" > Email</ span >
< small class = "small-muted" data-i18n = "admin.email_optional" > (optional)</ small ></ label
2026-03-05 09:42:39 -05:00
>
< input
type = "text"
id = "cu-email"
placeholder = "user@example.com (auto-generated if empty)"
2026-03-09 00:08:34 +01:00
data-i18n-placeholder = "admin.email_placeholder"
2026-03-05 09:42:39 -05:00
/>
</ div >
< div class = "form-row" >
< div class = "form-group flex-1" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.role_label" > Role</ label >
2026-03-05 09:42:39 -05:00
< select id = "cu-role" class = "select-cu-role" >
2026-03-09 00:08:34 +01:00
< option value = "user" data-i18n = "admin.role_user" > User</ option >
< option value = "admin" data-i18n = "admin.role_admin" > Admin</ option >
2026-03-05 09:42:39 -05:00
</ select >
</ div >
< div class = "form-group flex-1" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.quota_label" > Quota</ label >
2026-03-05 09:42:39 -05:00
< div class = "quota-row" >
< input
type = "number"
id = "cu-quota-value"
min = "0"
step = "1"
value = "1"
class = "flex-1"
/>
< select
id = "cu-quota-unit"
class = "select-cu-quota-unit"
>
< option value = "1073741824" > GB</ option >
< option value = "1048576" > MB</ option >
< option value = "1099511627776" > TB</ option >
</ select >
</ div >
</ div >
</ div >
< div id = "cu-error" class = "alert alert-no-margin" ></ div >
< div class = "modal-actions" >
< button
class = "btn btn-secondary"
2026-03-06 20:54:18 -05:00
id = "btn-close-create-user"
2026-03-09 00:08:34 +01:00
data-i18n = "admin.cancel"
2026-03-05 09:42:39 -05:00
>
Cancel
</ button >
< button
class = "btn btn-primary"
id = "cu-submit"
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-user-plus" ></ i > < span data-i18n = "admin.create_user" > Create</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
</ div >
2026-02-20 12:27:52 +01:00
</ div >
2026-03-05 09:42:39 -05:00
< div id = "reset-pw-modal" class = "modal-overlay hidden" >
< div class = "modal" >
< h3 >
2026-03-09 00:08:34 +01:00
< i class = "fas fa-key modal-title-icon" ></ i > < span data-i18n = "admin.reset_pw_title" > Reset Password</ span >
2026-03-05 09:42:39 -05:00
</ h3 >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label >< span data-i18n = "admin.quota_user_label" > User:</ span > < strong id = "rp-username" ></ strong ></ label >
2026-03-05 09:42:39 -05:00
</ div >
< div class = "form-group" >
2026-03-09 00:08:34 +01:00
< label data-i18n = "admin.new_password_label" > New Password</ label >
2026-03-05 09:42:39 -05:00
< input
type = "password"
id = "rp-password"
placeholder = "Min 8 characters"
minlength = "8"
/>
</ div >
< div id = "rp-error" class = "alert alert-no-margin" ></ div >
< div class = "modal-actions" >
< button
class = "btn btn-secondary"
2026-03-06 20:54:18 -05:00
id = "btn-close-reset-pw"
2026-03-09 00:08:34 +01:00
data-i18n = "admin.cancel"
2026-03-05 09:42:39 -05:00
>
Cancel
</ button >
< button
class = "btn btn-primary"
id = "rp-submit"
>
2026-03-09 00:08:34 +01:00
< i class = "fas fa-save" ></ i > < span data-i18n = "admin.reset_btn" > Reset</ span >
2026-03-05 09:42:39 -05:00
</ button >
</ div >
</ div >
</ div >
2026-02-20 12:27:52 +01:00
2026-03-09 00:08:34 +01:00
<!-- Confirm modal -->
< div id = "confirm-modal" class = "modal-overlay hidden" >
< div class = "modal modal-confirm" >
< h3 id = "confirm-title" >< i class = "fas fa-exclamation-circle modal-title-icon" ></ i > < span data-i18n = "admin.confirm_action" > Confirm Action</ span ></ h3 >
< p id = "confirm-message" ></ p >
< div class = "modal-actions" >
< button class = "btn btn-secondary" id = "confirm-cancel" data-i18n = "admin.confirm_no" > Cancel</ button >
< button class = "btn btn-danger" id = "confirm-yes" data-i18n = "admin.confirm_yes" > Confirm</ button >
</ div >
</ div >
</ div >
2026-04-13 15:09:10 +02:00
< script type = "module" src = "/js/views/admin/admin.js" ></ script >
2026-03-05 09:42:39 -05:00
</ body >
2026-02-20 12:27:52 +01:00
</ html >