[Web] Update Bootstrap to 5.3.8, add theme support

This commit is contained in:
FreddleSpl0it committed 2026-10-15 14:43:06 +02:00
1 parent 8cd231c9b9
commit 571c1b5b29
18 files changed
+4120 -3095

No files matched your search

+2
View File
@@ -115,6 +115,8 @@ $template_data = [
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
'ip_check' => customize('get', 'ip_check'),
'custom_login' => customize('get', 'custom_login'),
'ui_themes' => customize('get', 'ui_themes'),
'ui_theme' => customize('get', 'ui_theme'),
'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings,
+5 -5
View File
@@ -17,15 +17,15 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
background-color: #5e5e5e;
background-color: var(--mc-dtr-hover-bg);
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
table.dataTable td.dt-control:before {
background-color: #979797 !important;
border: 1.5px solid #616161 !important;
background-color: var(--mc-dtr-bg) !important;
border: 1.5px solid var(--mc-dtr-border) !important;
border-radius: 2px !important;
color: #fff;
color: var(--mc-dtr-color);
height: 1em;
width: 1em;
line-height: 1.25em;
@@ -37,7 +37,7 @@ table.dataTable td.dt-control:before {
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
table.dataTable td.dt-control:before {
background-color: #979797 !important;
background-color: var(--mc-dtr-active-bg) !important;
}
table.dataTable.table-striped>tbody>tr>td {
vertical-align: middle;
+59 -32
View File
@@ -34,11 +34,41 @@
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
}
/* theme tokens, override in css/themes/<name>/theme.css */
:root {
--mc-font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
--mc-font-size: 10.5pt;
--mc-body-bg: #fbfbfb;
--mc-primary: #0F688D;
--mc-primary-border: #0d526d;
--mc-info: #148DBC;
--mc-info-border: #127ea8;
--mc-border: #dfdfdf;
--mc-surface: #fff;
--mc-surface-alt: #f7f7f7;
--mc-muted: #959595;
--mc-hr: #ccc;
--mc-progress-bg: #d5d5d5;
--mc-placeholder: rgb(137 137 137);
--mc-btn-outline-color: #000000;
--mc-btn-outline-border: #cfcfcf;
--mc-btn-outline-hover-bg: #f0f0f0;
--mc-btn-outline-active-bg: #f0f0f0;
--mc-btn-light-active-color: #fff;
--mc-btn-light-active-bg: #555;
--mc-btn-light-active-border: #4d4d4d;
--mc-dtr-bg: #979797;
--mc-dtr-border: #616161;
--mc-dtr-color: #fff;
--mc-dtr-hover-bg: #5e5e5e;
--mc-dtr-active-bg: #979797;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fbfbfb;
background-color: var(--mc-body-bg);
}
#maxmsgsize { min-width: 80px; }
#slider1 .slider-selection {
@@ -86,17 +116,17 @@ body {
max-height: 1000px
}
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
border-color: #dfdfdf;
border-color: var(--mc-border);
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
border-color: #dfdfdf;
border-bottom: 1px solid #ffffff;
border-color: var(--mc-border);
border-bottom: 1px solid var(--mc-surface);
}
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
border-color: #dfdfdf;
border-color: var(--mc-border);
}
.nav-tabs {
border-bottom: 1px solid #dfdfdf;
border-bottom: 1px solid var(--mc-border);
}
.bi {
display: inline-block;
@@ -164,13 +194,13 @@ body.modal-open {
padding-right: inherit !important;
}
body {
font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 10.5pt;
font-family: var(--mc-font-family);
font-size: var(--mc-font-size);
line-height: 1.5;
}
html {
font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 10.5pt;
font-family: var(--mc-font-family);
font-size: var(--mc-font-size);
line-height: 1.5;
}
#mailcow-alert {
@@ -206,7 +236,7 @@ legend {
cursor: not-allowed;
}
.overlay {
background: #fff;
background: var(--mc-surface);
position: absolute;
z-index: 10000;
top: 0; right: 0; bottom: 0; left: 0;
@@ -215,9 +245,6 @@ legend {
.bootstrap-select.btn-group .no-results {
display: none;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(197, 197, 197) !important;
}
.haveibeenpwned {
cursor: pointer;
-webkit-user-select: none;
@@ -239,7 +266,7 @@ legend {
.footer {
margin-top: 27px;
margin-bottom: 20px;
color: #959595;
color: var(--mc-muted);
display: flex;
flex-direction: column;
}
@@ -337,7 +364,7 @@ code {
overflow-x: auto!important;
}
.well {
border: 1px solid #dfdfdf;
border: 1px solid var(--mc-border);
background-color: #f9f9f9;
padding: 10px;
}
@@ -356,47 +383,47 @@ button[aria-expanded='true'] > .caret {
}
.list-group-details {
background: #fff;
background: var(--mc-surface);
}
.list-group-header {
background: #f7f7f7;
background: var(--mc-surface-alt);
}
.bg-primary, .alert-primary, .btn-primary {
background-color: #0F688D !important;
border-color: #0d526d !important;
background-color: var(--mc-primary) !important;
border-color: var(--mc-primary-border) !important;
}
.bg-info, .alert-info, .btn-info {
background-color: #148DBC !important;
border-color: #127ea8 !important;
background-color: var(--mc-info) !important;
border-color: var(--mc-info-border) !important;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(137 137 137)!important;
color: var(--mc-placeholder) !important;
}
.progress {
height: 16px;
background-color: #d5d5d5;
background-color: var(--mc-progress-bg);
}
.btn-outline-secondary:hover {
background-color: #f0f0f0;
background-color: var(--mc-btn-outline-hover-bg);
}
.btn.btn-outline-secondary {
color: #000000 !important;
border-color: #cfcfcf !important;
color: var(--mc-btn-outline-color) !important;
border-color: var(--mc-btn-outline-border) !important;
}
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important;
background-color: var(--mc-btn-outline-active-bg) !important;
}
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: #fff;
background-color: #555;
color: var(--mc-btn-light-active-color);
background-color: var(--mc-btn-light-active-bg);
background-image: none;
border-color: #4d4d4d;
border-color: var(--mc-btn-light-active-border);
}
.btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus,
.btn-check:focus+.btn-light, .btn-light:focus {
@@ -423,7 +450,7 @@ button[aria-expanded='true'] > .caret {
.hr-title::after {
content: "";
flex: 1;
border-bottom: 1px solid #ccc;
border-bottom: 1px solid var(--mc-hr);
}
.hr-title:not(:empty)::before {
File diff suppressed because it is too large. Load diff
+155 -114
View File
@@ -1,62 +1,103 @@
/* theme tokens, override in css/themes/<name>/dark.css */
:root {
--mc-body-bg: #1c1c1e;
--mc-body-color: #f2f2f7;
--mc-surface: #2c2c2e;
--mc-surface-alt: #282828;
--mc-text: #ccc;
--mc-text-soft: #bbb;
--mc-muted: #8e8e93;
--mc-border: #555;
--mc-tab-border: #444444;
--mc-link: #6fc7e9;
--mc-link-hover: #3daedb;
--mc-btn-bg: #5e5e5e;
--mc-btn-border: #4c4c4e;
--mc-input-bg: #424242;
--mc-input-color: #e2e2e2;
--mc-input-border: #999;
--mc-dropdown-bg: #424242;
--mc-table-bg: #3a3a3a;
--mc-table-stripe: #282828;
--mc-table-stripe-row: #424242;
--mc-table-hover: #343434;
--mc-table-border: #5c5c5c;
--mc-progress-bg: #555;
--mc-placeholder: #d4d4d4;
--mc-btn-outline-color: #e0e0e0;
--mc-btn-outline-border: #7a7a7a;
--mc-btn-outline-hover-bg: #5c5c5c;
--mc-btn-outline-active-bg: #7a7a7a;
--mc-btn-light-active-color: #f2f2f7;
--mc-btn-light-active-bg: #242424;
--mc-btn-light-active-border: #1c1c1e;
--mc-btn-dark-bg: #242424;
--mc-input-disabled-bg: #6e6e6e;
--mc-input-disabled-color: #a8a8a8;
--mc-list-header-bg: #444;
--mc-list-details-bg: #555;
--mc-chip-bg: #444;
--mc-table-child-bg: #414141;
--mc-dtr-bg: #7a7a7a;
--mc-dtr-border: #5c5c5c;
--mc-dtr-color: #e0e0e0;
--mc-dtr-hover-bg: #7a7a7a;
--mc-dtr-active-bg: #949494;
}
body {
background-color: #1c1c1e;
color: #f2f2f7;
background-color: var(--mc-body-bg);
color: var(--mc-body-color);
}
.card {
border: 1px solid #2c2c2e;
background-color: #2c2c2e;
border: 1px solid var(--mc-surface);
background-color: var(--mc-surface);
}
legend {
color: #f2f2f7;
color: var(--mc-body-color);
}
.card-header {
color: #8e8e93;
background-color: #1c1c1e;
color: var(--mc-muted);
background-color: var(--mc-body-bg);
border-color: transparent;
}
.card-body {
--bs-card-color: #bbb;
--bs-card-color: var(--mc-text-soft);
}
.btn-secondary, .paginate_button, .page-link, .btn-light {
color: #f2f2f7 !important;
background-color: #5e5e5e !important;
border-color: #4c4c4e !important;
color: var(--mc-body-color) !important;
background-color: var(--mc-btn-bg) !important;
border-color: var(--mc-btn-border) !important;
}
.btn-dark {
color: #f2f2f7 !important;
background-color: #242424 !important;
border-color: #1c1c1e !important;
}
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: #444444;
border-color: #4c4c4e !important;
color: #f2f2f7;
color: var(--mc-body-color) !important;
background-color: var(--mc-btn-dark-bg) !important;
border-color: var(--mc-body-bg) !important;
}
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
border-color: #5e5e5e !important;
border-color: var(--mc-btn-bg) !important;
}
.alert-secondary {
color: #f2f2f7 !important;
background-color: #5e5e5e !important;
border-color: #4c4c4e !important;
color: var(--mc-body-color) !important;
background-color: var(--mc-btn-bg) !important;
border-color: var(--mc-btn-border) !important;
}
.bg-secondary {
color: #f2f2f7 !important;
background-color: #5e5e5e !important;
color: var(--mc-body-color) !important;
background-color: var(--mc-btn-bg) !important;
}
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
color: #f2f2f7;
color: var(--mc-body-color);
}
.page-item.active .page-link {
@@ -65,18 +106,18 @@ legend {
}
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: #5e5e5e;
border-color: #4c4c4e !important;
color: #f2f2f7;
background-color: var(--mc-btn-bg);
border-color: var(--mc-btn-border) !important;
color: var(--mc-body-color);
}
.btn-secondary:disabled, .btn-secondary.disabled {
border-color: #5e5e5e !important;
border-color: var(--mc-btn-bg) !important;
}
.modal-content {
--bs-modal-color: #bbb;
background-color: #2c2c2e;
--bs-modal-color: var(--mc-text-soft);
background-color: var(--mc-surface);
}
.modal-header {
@@ -84,7 +125,7 @@ legend {
}
.modal-title {
color: #bbb;
color: var(--mc-text-soft);
}
.modal .btn-close {
@@ -92,12 +133,12 @@ legend {
}
.navbar.bg-light {
background-color: #1c1c1e !important;
border-color: #2c2c2e;
background-color: var(--mc-body-bg) !important;
border-color: var(--mc-surface);
}
.nav-link {
color: #8e8e93 !important;
color: var(--mc-muted) !important;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
@@ -105,23 +146,23 @@ legend {
}
.nav-tabs, .nav-tabs .nav-link {
border-color: #444444 !important;
border-color: var(--mc-tab-border) !important;
}
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
border-bottom-color: #1c1c1e !important;
border-bottom-color: var(--mc-body-bg) !important;
}
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active {
border-bottom-color: #2c2c2e !important;
border-bottom-color: var(--mc-surface) !important;
}
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: #f2f2f7 !important;
color: var(--mc-body-color) !important;
}
.dropdown-menu {
background-color: #424242;
background-color: var(--mc-dropdown-bg);
border: 1px solid #282828;
}
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
@@ -132,79 +173,79 @@ legend {
color: #fff;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: #d4d4d4 !important;
color: var(--mc-placeholder) !important;
}
tbody tr {
color: #ccc;
color: var(--mc-text);
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
color: #ccc;
color: var(--mc-text);
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
color: #ccc;
color: var(--mc-text);
}
.list-group-item {
background-color: #282828;
border: 1px solid #555;
background-color: var(--mc-surface-alt);
border: 1px solid var(--mc-border);
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #424242;
background-color: var(--mc-table-stripe-row);
}
table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(255, 255, 255, 0.13);
}
.label.label-last-login {
color: #ccc !important;
color: var(--mc-text) !important;
background-color: #555 !important;
}
.progress {
background-color: #555;
background-color: var(--mc-progress-bg);
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
color: #ccc;
color: var(--mc-text);
}
div.numberedtextarea-number {
color: #999;
}
.well {
border: 1px solid #555;
background-color: #282828;
border: 1px solid var(--mc-border);
background-color: var(--mc-surface-alt);
}
pre {
color: #ccc;
background-color: #282828;
border: 1px solid #555;
color: var(--mc-text);
background-color: var(--mc-surface-alt);
border: 1px solid var(--mc-border);
}
.form-control {
background-color: transparent;
}
input.form-control, textarea.form-control {
color: #e2e2e2 !important;
background-color: #424242 !important;
border: 1px solid #999;
color: var(--mc-input-color) !important;
background-color: var(--mc-input-bg) !important;
border: 1px solid var(--mc-input-border);
}
input.form-control:focus, textarea.form-control {
background-color: #424242 !important;
background-color: var(--mc-input-bg) !important;
}
input.form-control:disabled, textarea.form-disabled {
color: #a8a8a8 !important;
background-color: #6e6e6e !important;
color: var(--mc-input-disabled-color) !important;
background-color: var(--mc-input-disabled-bg) !important;
}
.input-group-addon {
color: #ccc;
background-color: #424242 !important;
border: 1px solid #999;
color: var(--mc-text);
background-color: var(--mc-input-bg) !important;
border: 1px solid var(--mc-input-border);
}
.input-group-text {
color: #ccc;
color: var(--mc-text);
background-color: #1c1c1c;
}
.list-group-item {
color: #ccc;
color: var(--mc-text);
}
.dropdown-item {
color: #ccc;
color: var(--mc-text);
}
.dropdown-item:hover {
color: #616161 !important;
@@ -214,9 +255,9 @@ input.form-control:disabled, textarea.form-disabled {
background-color: #007aff;
}
.form-select {
color: #e2e2e2!important;
background-color: #424242!important;
border: 1px solid #999;
color: var(--mc-input-color)!important;
background-color: var(--mc-input-bg)!important;
border: 1px solid var(--mc-input-border);
}
.responsive-tabs .card-header button[data-bs-toggle="collapse"] {
@@ -240,15 +281,15 @@ input.form-control:disabled, textarea.form-disabled {
a {
color: #6fc7e9;
color: var(--mc-link);
text-decoration: underline;
}
a:hover {
color: #3daedb;
color: var(--mc-link-hover);
}
.breadcrumb-item.active {
color: #ccc;
color: var(--mc-text);
}
@@ -273,11 +314,11 @@ a:hover {
background-color: #3a3a3a;
}
.accordion-button {
color: #bbb;
color: var(--mc-text-soft);
background-color: #2c2c2c;
}
.accordion-button:not(.collapsed) {
color: #6fc7e9;
color: var(--mc-link);
background-color: #2c2c2c;
}
.accordion-button::after {
@@ -300,15 +341,15 @@ a:hover {
}
.tag-box {
background-color: #282828;
border: 1px solid #555;
background-color: var(--mc-surface-alt);
border: 1px solid var(--mc-border);
}
.tag-input {
color: #fff;
background-color: #282828;
background-color: var(--mc-surface-alt);
}
.tag-add {
color: #ccc;
color: var(--mc-text);
}
.tag-add:hover {
color: #d1d1d1;
@@ -318,19 +359,19 @@ a:hover {
color: #fff;
}
.btn-outline-secondary:hover {
background-color: #5c5c5c;
background-color: var(--mc-btn-outline-hover-bg);
}
.btn.btn-outline-secondary {
color: #e0e0e0 !important;
border-color: #7a7a7a !important;
color: var(--mc-btn-outline-color) !important;
border-color: var(--mc-btn-outline-border) !important;
}
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #7a7a7a !important;
background-color: var(--mc-btn-outline-active-bg) !important;
}
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: #f2f2f7 !important;
background-color: #242424 !important;
border-color: #1c1c1e !important;
color: var(--mc-btn-light-active-color) !important;
background-color: var(--mc-btn-light-active-bg) !important;
border-color: var(--mc-btn-light-active-border) !important;
}
.btn-input-missing,
.btn-input-missing:hover,
@@ -348,16 +389,16 @@ a:hover {
}
.list-group-details {
background: #555;
background: var(--mc-list-details-bg);
}
.list-group-header {
background: #444;
background: var(--mc-list-header-bg);
}
span.mail-address-item {
background-color: #444;
background-color: var(--mc-chip-bg);
border-radius: 4px;
border: 1px solid #555;
border: 1px solid var(--mc-border);
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
@@ -365,52 +406,52 @@ span.mail-address-item {
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
background-color: #7a7a7a !important;
background-color: var(--mc-dtr-hover-bg) !important;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
background-color: #7a7a7a !important;
border: 1.5px solid #5c5c5c !important;
color: #e0e0e0 !important;
background-color: var(--mc-dtr-bg) !important;
border: 1.5px solid var(--mc-dtr-border) !important;
color: var(--mc-dtr-color) !important;
}
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
background-color: #949494;
background-color: var(--mc-dtr-active-bg);
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
background-color: #414141;
background-color: var(--mc-table-child-bg);
}
table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: #ccc !important;
color: var(--mc-text) !important;
}
.table-secondary {
--bs-table-bg: #282828;
--bs-table-striped-bg: #343434;
--bs-table-striped-color: #f2f2f7;
--bs-table-striped-color: var(--mc-body-color);
--bs-table-active-bg: #4c4c4c;
--bs-table-active-color: #f2f2f7;
--bs-table-active-color: var(--mc-body-color);
--bs-table-hover-bg: #3a3a3a;
--bs-table-hover-color: #f2f2f7;
color: #ccc;
--bs-table-hover-color: var(--mc-body-color);
color: var(--mc-text);
border-color: #3a3a3a;
}
.table-light {
--bs-table-bg: #3a3a3a;
--bs-table-striped-bg: #444444;
--bs-table-striped-color: #f2f2f7;
--bs-table-striped-color: var(--mc-body-color);
--bs-table-active-bg: #5c5c5c;
--bs-table-active-color: #f2f2f7;
--bs-table-active-color: var(--mc-body-color);
--bs-table-hover-bg: #4c4c4c;
--bs-table-hover-color: #f2f2f7;
color: #ccc;
--bs-table-hover-color: var(--mc-body-color);
color: var(--mc-text);
border-color: #4c4c4c;
}
@@ -430,23 +471,23 @@ table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
.table-striped>tbody>tr:nth-of-type(odd)>td,
.table-striped>tbody>tr:nth-of-type(odd)>th {
background-color: #282828;
background-color: var(--mc-table-stripe);
}
.table-hover>tbody>tr:hover {
background-color: #343434;
background-color: var(--mc-table-hover);
}
.table>:not(caption)>*>* {
border-color: #5c5c5c;
--bs-table-color-state:#bbb;
--bs-table-bg: #3a3a3a;
border-color: var(--mc-table-border);
--bs-table-color-state:var(--mc-text-soft);
--bs-table-bg: var(--mc-table-bg);
}
.text-muted {
--bs-secondary-color: #8e8e93;
--bs-secondary-color: var(--mc-muted);
}
input::placeholder {
color: #8e8e93 !important;
color: var(--mc-muted) !important;
}
.form-select {
+68
View File
@@ -233,6 +233,38 @@ function customize($_action, $_item, $_data = null) {
'msg' => 'custom_login_modified'
);
break;
case 'ui_theme':
$ui_theme = is_array($_data['ui_theme']) ? (string)reset($_data['ui_theme']) : (string)$_data['ui_theme'];
if ($ui_theme !== '' && !array_key_exists($ui_theme, customize('get', 'ui_themes'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ui_theme_invalid'
);
return false;
}
try {
if ($ui_theme === '') {
$redis->del('UI_THEME');
}
else {
$redis->set('UI_THEME', $ui_theme);
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ui_theme_modified'
);
break;
}
break;
case 'delete':
@@ -402,6 +434,42 @@ function customize($_action, $_item, $_data = null) {
return false;
}
break;
case 'ui_themes':
// theme folders in css/themes/<name>/ with a theme.css
$themes = array();
foreach (glob('/web/css/themes/*/theme.css') as $theme_css) {
$theme_dir = dirname($theme_css);
$theme = basename($theme_dir);
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $theme)) {
continue;
}
$meta = json_decode(@file_get_contents($theme_dir . '/theme.json'), true);
$themes[$theme] = array(
'name' => (!empty($meta['name'])) ? $meta['name'] : $theme,
'description' => (!empty($meta['description'])) ? $meta['description'] : '',
'dark' => file_exists($theme_dir . '/dark.css')
);
}
ksort($themes);
return $themes;
break;
case 'ui_theme':
try {
$ui_theme = $redis->get('UI_THEME');
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
if (empty($ui_theme) || !preg_match('/^[a-zA-Z0-9_-]+$/', $ui_theme) || !file_exists('/web/css/themes/' . $ui_theme . '/theme.css')) {
return '';
}
return $ui_theme;
break;
}
break;
}
+15
View File
@@ -22,6 +22,9 @@ if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) {
if ($_SERVER['REQUEST_URI'] == '/') {
$css_minifier->add('/web/css/site/index.css');
}
if (file_exists('/web/css/themes/'.$UI_THEME.'/theme.css')) {
$css_minifier->add('/web/css/themes/'.$UI_THEME.'/theme.css');
}
$hash = $css_minifier->getDataHash();
$CSSPath = '/tmp/' . $hash . '.css';
@@ -30,6 +33,17 @@ if(!file_exists($CSSPath)) {
cleanupCSS($hash);
}
// dark mode bundle, core dark mode + theme dark.css
$dark_minifier = new CSSminifierExtended('/web/css/themes/mailcow-darkmode.css');
if (file_exists('/web/css/themes/'.$UI_THEME.'/dark.css')) {
$dark_minifier->add('/web/css/themes/'.$UI_THEME.'/dark.css');
}
$DarkCSSPath = '/tmp/' . $hash . '-' . $dark_minifier->getDataHash() . '.css';
if(!file_exists($DarkCSSPath)) {
$dark_minifier->minify($DarkCSSPath);
}
$dark_css_path = '/cache/' . basename($DarkCSSPath);
$mailcow_apps_processed = $MAILCOW_APPS;
$app_links = customize('get', 'app_links');
$app_links_processed = $app_links;
@@ -77,6 +91,7 @@ $globalVariables = [
'dual_login' => @$_SESSION['dual-login'],
'ui_texts' => $UI_TEXTS,
'css_path' => '/cache/'.basename($CSSPath),
'dark_css_path' => $dark_css_path,
'logo' => customize('get', 'main_logo'),
'logo_dark' => customize('get', 'main_logo_dark'),
'available_languages' => $AVAILABLE_LANGUAGES,
+7 -1
View File
@@ -319,10 +319,16 @@ $css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
// get customized ui data
$UI_TEXTS = customize('get', 'ui_texts');
// theme selected in admin UI overrides $UI_THEME
if ($ui_theme_selected = customize('get', 'ui_theme')) {
$UI_THEME = $ui_theme_selected;
}
// minify bootstrap theme
if (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css'))
if (file_exists('/web/css/themes/'.$UI_THEME.'/bootstrap.css'))
$css_minifier->add('/web/css/themes/'.$UI_THEME.'/bootstrap.css');
elseif (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css'))
$css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css');
else
$css_minifier->add('/web/css/themes/lumen-bootstrap.css');
+2
View File
@@ -121,6 +121,8 @@ $AVAILABLE_LANGUAGES = array(
// default theme is lumen
// additional themes can be found here: https://bootswatch.com/
// copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css
// or use a theme folder data/web/css/themes/{THEME-NAME}/ (theme.css, optional dark.css, bootstrap.css, theme.json)
// theme folders can be selected in System > Configuration > Customize, which overrides $UI_THEME
$UI_THEME = "lumen";
// Show DKIM private keys - false by default
+3 -3
View File
@@ -1,7 +1,7 @@
/*!
* Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;s<o;s++){const o=t[n[s]];if(o.originalHandler===e&&o.delegationSelector===i)return o}return null}function S(t,e,i){const n="string"==typeof e,s=n?i:e;let o=P(t);return k.has(o)||(o=t),[n,s,o]}function N(t,e,i,n,s){if("string"!=typeof e||!t)return;if(i||(i=n,n=null),C.test(e)){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!Line truncated
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),s=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),o=t=>{t.dispatchEvent(new Event(i))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=t=>r(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,l=t=>{if(!r(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},_=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,b=(t,e,n=!0)=>{if(!n)return void _(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),_(t))};e.addEventListener(i,a),setTimeout(()=>{r||o(e)},s)},v=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,A=/::\d+$/,E={};let T=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},O=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=x(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function L(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function S(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return O.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=S(e,i,n);if(e in C){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=k(t),c=l[a]||(l[a]={}),h=L(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function $(t,e,i,n,s){const o=L(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&$(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(w,""),C[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=k(t),c=l[rLine truncated
//# sourceMappingURL=bootstrap.bundle.min.js.map
+4 -1
View File
@@ -124,6 +124,9 @@ $(document).ready(function() {
var lastTab = localStorage.getItem(key);
if (lastTab) {
$('[data-bs-target="#' + lastTab + '"]').click();
var dropdown = $('[data-bs-target="#' + lastTab + '"]').closest('.dropdown');
dropdown.find('.dropdown-menu').removeClass('show');
dropdown.find('.dropdown-toggle').attr('aria-expanded', 'false');
var tab = $('[id^="' + lastTab + '"]');
$(tab).find('.card-body.collapse:first').collapse('show');
}
@@ -347,7 +350,7 @@ $(document).ready(function() {
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
localStorage.setItem('mailcow_theme', 'light');
}else{
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="' + mailcow_dark_css + '">');
$('#dark-mode-toggle').prop('checked', true);
$('.main-logo').addClass('d-none');
$('.main-logo-dark').removeClass('d-none');
+3
View File
@@ -2038,6 +2038,9 @@ if (isset($_GET['query'])) {
case "custom_login":
process_edit_return(customize('edit', 'custom_login', $attr));
break;
case "ui_theme":
process_edit_return(customize('edit', 'ui_theme', $attr));
break;
case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr));
+7
View File
@@ -395,6 +395,11 @@
"ui_header_announcement_type_info": "Info",
"ui_header_announcement_type_warning": "Wichtig",
"ui_texts": "UI-Label und Texte",
"ui_theme": "Theme",
"ui_theme_dark": "Enthält eine Darkmode-Variante",
"ui_theme_default": "Standard (mailcow)",
"ui_theme_info": "Themes sind Ordner in <code>data/web/css/themes/&lt;name&gt;/</code> mit einer <code>theme.css</code>. Optional: <code>dark.css</code> (Darkmode), <code>bootstrap.css</code> (ersetzt das Bootstrap-Basistheme) und <code>theme.json</code> (<code>name</code>, <code>description</code>).",
"ui_theme_none": "Keine Themes gefunden.",
"unban_pending": "ausstehend",
"unchanged_if_empty": "Unverändert, wenn leer",
"upload": "Hochladen",
@@ -550,6 +555,7 @@
"webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden",
"webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt",
"webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto",
"ui_theme_invalid": "Theme nicht gefunden",
"unknown": "Ein unbekannter Fehler trat auf",
"unknown_tfa_method": "Unbekannte TFA-Methode",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
@@ -1206,6 +1212,7 @@
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
"ui_texts": "Änderungen an UI-Texten",
"ui_theme_modified": "Theme wurde erfolgreich gespeichert",
"upload_success": "Datei wurde erfolgreich hochgeladen",
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
"verified_totp_login": "TOTP-Anmeldung verifiziert",
+7
View File
@@ -405,6 +405,11 @@
"ui_header_announcement_type_info": "Info",
"ui_header_announcement_type_warning": "Important",
"ui_texts": "UI labels and texts",
"ui_theme": "Theme",
"ui_theme_dark": "Includes a dark mode variant",
"ui_theme_default": "Default (mailcow)",
"ui_theme_info": "Themes are folders in <code>data/web/css/themes/&lt;name&gt;/</code> containing a <code>theme.css</code>. Optional: <code>dark.css</code> (dark mode), <code>bootstrap.css</code> (replaces the Bootstrap base theme) and <code>theme.json</code> (<code>name</code>, <code>description</code>).",
"ui_theme_none": "No themes found.",
"unban_pending": "unban pending",
"unchanged_if_empty": "If unchanged leave blank",
"upload": "Upload",
@@ -554,6 +559,7 @@
"webauthn_authenticator_failed": "The selected authenticator was not found",
"webauthn_publickey_failed": "No public key was stored for the selected authenticator",
"webauthn_username_failed": "The selected authenticator belongs to another account",
"ui_theme_invalid": "Theme not found",
"unknown": "An unknown error occurred",
"unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@@ -1214,6 +1220,7 @@
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"ui_texts": "Saved changes to UI texts",
"ui_theme_modified": "Theme was saved successfully",
"upload_success": "File uploaded successfully",
"verified_fido2_login": "Verified FIDO2 login",
"verified_totp_login": "Verified TOTP login",
@@ -7,7 +7,33 @@
<span class="d-none d-md-block">{{ lang.admin.customize }}</span>
</div>
<div id="collapse-tab-config-customize" class="card-body collapse" data-bs-parent="#admin-content">
<legend><i class="bi bi-file-image"></i> {{ lang.admin.change_logo }}</legend><hr />
<legend><i class="bi bi-palette"></i> {{ lang.admin.ui_theme }}</legend><hr />
<p class="text-muted">{{ lang.admin.ui_theme_info|raw }}</p>
<form class="form" data-id="ui_theme" role="form" method="post">
<div class="row">
<div class="col-12 col-md-6 col-lg-4">
<p><select data-width="100%" id="ui_theme" name="ui_theme" class="selectpicker show-tick">
<option value="" {% if not ui_theme %}selected{% endif %}>{{ lang.admin.ui_theme_default }}</option>
{% for key, theme in ui_themes %}
<option value="{{ key }}" {% if ui_theme == key %}selected{% endif %}>{{ theme.name }}</option>
{% endfor %}
</select></p>
</div>
</div>
{% if ui_themes %}
<ul class="list-unstyled text-muted small mb-4">
{% for key, theme in ui_themes %}
<li><b>{{ theme.name }}</b> <code>{{ key }}</code>{% if theme.dark %} <i class="bi bi-moon-fill" title="{{ lang.admin.ui_theme_dark }}"></i>{% endif %}{% if theme.description %} - {{ theme.description }}{% endif %}</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small mb-4">{{ lang.admin.ui_theme_none }}</p>
{% endif %}
<p><div class="btn-group">
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected" data-item="admin" data-id="ui_theme" data-api-url='edit/ui_theme' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div></p>
</form>
<legend style="padding-top:20px"><i class="bi bi-file-image"></i> {{ lang.admin.change_logo }}</legend><hr />
<p class="text-muted">{{ lang.admin.logo_info }}</p>
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
<div class="mb-4">
+2 -1
View File
@@ -10,6 +10,7 @@
<link rel="stylesheet" href="{{ css_path }}">
<script>
var mailcow_dark_css = '{{ dark_css_path }}';
// check if darkmode is preferred by OS or set by localStorage
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && localStorage.getItem("mailcow_theme") !== "light" ||
localStorage.getItem("mailcow_theme") === "dark") {
@@ -18,7 +19,7 @@
link.id = 'dark-mode-theme';
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '/css/themes/mailcow-darkmode.css';
link.href = mailcow_dark_css;
head.appendChild(link);
}
</script>
+2 -2
View File
@@ -7,12 +7,12 @@
<li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a>
<ul class="dropdown-menu">
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
<li><button class="dropdown-item active" aria-selected="true" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li>
</ul>
</li>
{% else %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link active" aria-selected="true" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
{% endif %}
{% if mailcow_cc_role == 'admin' %}
<li class="nav-item dropdown" role="presentation">