Compare commits

..

337 Commits

Author SHA1 Message Date
Niklas Meyer
3f1a5af88b Merge pull request #4927 from mailcow/staging
2022-12b
2022-12-27 13:02:44 +01:00
Niklas Meyer
850fd85d4d Merge pull request #4925 from tomudding/fix/datatables-crashing-with-non-english-locale
[WEB] Update DataTables to v1.13.1 and fix crash for non-English locales
2022-12-27 13:01:17 +01:00
milkmaker
24acd42589 Translations update from Weblate (#4926)
* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.fr-fr.json

[Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: Clément Hampaï <clement.hampai@cypressxt.net>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

Co-authored-by: Clément Hampaï <clement.hampai@cypressxt.net>
2022-12-26 20:06:49 +01:00
Tom Udding
eaa0dea63b [WEB] Update DataTables to v1.13.1 and fix crash for non-English locales
This newer version of DataTables includes a fix for improper access
to localisation information from `Intl.NumberFormat`. This improper access
lead to datatables not being created.
2022-12-26 17:35:49 +01:00
DerLinkman
dd50bbca9b Merge branch 'staging' 2022-12-26 16:01:31 +01:00
DerLinkman
f3f5471ef7 [Web] Removed double Sender Entry in RSPAMD Logs 2022-12-26 15:56:23 +01:00
Niklas Meyer
516c8ea66c Merge pull request #4923 from mailcow/staging
2022-12a
2022-12-26 14:35:37 +01:00
DerLinkman
48310034e5 [Compose Updater] Corrected syntax errors 2022-12-26 14:33:15 +01:00
Niklas Meyer
be35a88f8c Merge pull request #4916 from tomy0000000/patch-1
[web] 🛠 fix: Locale decision algorithm
2022-12-26 14:15:43 +01:00
Niklas Meyer
e67b512499 Merge pull request #4914 from tomudding/fix/datatables-not-ordering-datetimes-correctly
Fix sorting dates and missing Rspamd attributes in datatables
2022-12-26 14:07:14 +01:00
FreddleSpl0it
0cf59159cd [Web] fix SAL display 2022-12-26 12:03:51 +01:00
FreddleSpl0it
e7a929a947 [Web] add missing </code> tag in edit/mailbox.twig 2022-12-26 11:35:18 +01:00
DerLinkman
dabf4d4383 [UI] Show Restart SOGo only when permission = admin 2022-12-25 14:44:00 +01:00
Tomy Hsieh
13bdd4ad0b 🛠 fix: Locale decision algorithm 2022-12-25 16:56:43 +08:00
DerLinkman
3281b97ea9 [UI] Removed solr informations if container is disabled 2022-12-24 23:25:52 +01:00
DerLinkman
8070db96e9 [UI] Fixed Wrong Table content in Qurantine (sender instead of subject) 2022-12-24 22:25:42 +01:00
Tom Udding
82c80a9682 Make default ordering of Rspamd table consistent 2022-12-24 18:29:46 +01:00
Tom Udding
136cc2e3ff Fix missing score and scan time Rspamd logs 2022-12-24 18:18:28 +01:00
Tom Udding
eefce62f01 Fix incorrect datetime for Rspamd logs 2022-12-24 18:10:57 +01:00
Tom Udding
240b2c63f6 Fix timestamps not sorting in datatables
Timestamps retrieved from the API were always converted to a browser
local format. The format specified for moment.js added in
5160eff294 did not work because of this.

Additionally, the format specified used `dd` which looks for two letter
days, such as "Mo", "Tu", "We", etc. Furthermore, `mm` is used for
minutes, not months.

Because the locale formatted datetime can vary a lot, it is not easy to
get this into moment.js to enable the sorting of datetimes in the
datatables. In other words, there is no conversion from an
`Intl.DateTimeFormat` specifier string to moment.js. Adding many
`$.fn.dataTable.moment(format);` with different `format`s is not useful.

I have fixed this rewriting how the timestamps from the API are added
to the tables. It still uses the locale of the browser, because not
everyone wants to use ISO 8601, but no longer requires moment.js (which
has been removed).

Two data attributes are added to the `td`s of the timestamps:
- `data-order`
- `data-sort`

The values of these are the timestamps as returned by the server, which
are very easily sorted (as they are just UNIX timestamps). Then, when
creating the cell in the table, it will be converted to what the locale
of the browser specified (this has not changed).
2022-12-24 17:35:31 +01:00
Niklas Meyer
355da03fba Merge pull request #4910 from mailcow/staging
2022-12 The Bootstrap 5 Update
2022-12-24 13:49:28 +01:00
milkmaker
55d57c552d Translations update from Weblate (#4909)
* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-24 11:48:08 +01:00
Niklas Meyer
a56e5eb2fe Merge pull request #4906 from mailcow/weblate-translated 2022-12-24 10:38:09 +01:00
milkmaker
e7817fab78 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
714b8417f4 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
ffb68c8848 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
7a5be0ccbf [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
c2927af554 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
24cea0cf22 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
f4351c119f [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
f40b6b5b65 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
bff96eb1ae [Web] Updated lang.sv-se.json
[Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: Filip <filipborglandgren@live.se>
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
de9564c4c9 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
614aa1e49e [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
b368c299d9 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
01a61d4e62 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
174fcd7167 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
dca85f2ffb [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
8ad5acb020 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
287118c3a7 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
78621a1f50 [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:23 +00:00
milkmaker
116859e0ba [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:22 +00:00
milkmaker
c4827e908c [Web] Language file updated by 'Cleanup translation files' addon
Co-authored-by: milkmaker <milkmaker@mailcow.de>
2022-12-23 20:45:22 +00:00
moo
41d56a867a Merge remote-tracking branch 'origin/feature/bootstrap5' into staging 2022-12-23 16:46:00 +01:00
Niklas Meyer
9f4dd1d172 Merge pull request #4905 from mailcow/feature/clamav-1.0
[Clamd] Update to 1.0
2022-12-23 16:30:40 +01:00
moo
948d23f56d [Clamd] Update to 1.0 2022-12-23 16:28:52 +01:00
Niklas Meyer
50e9a3ec8a Merge pull request #4835 from VermiumSifell/master
✏️ Fixed invalid regexs for banning.
2022-12-23 16:10:32 +01:00
Niklas Meyer
0dbd6be010 Merge pull request #4899 from mhupfauer/patch-1
Update bulk_header.map
2022-12-23 16:10:04 +01:00
Niklas Meyer
2b4189b1a4 Merge pull request #4900 from ethrgeist/chore/fix-github-template
fix incorrect render value
2022-12-23 16:08:42 +01:00
Niklas Meyer
4bf81975dc Merge pull request #4888 from mailcow/feature/helper-scripts_nextcloud25
[Nextcloud] Update to 25 + purge fix (DB)
2022-12-23 16:07:28 +01:00
Niklas Meyer
ea040f4412 Merge pull request #4903 from Der-Jan/msgidpushover
Add Message-ID to pushover
2022-12-23 16:07:03 +01:00
Niklas Meyer
c246648949 Merge pull request #4901 from ethrgeist/chore/docker-compose-cleanup
Chore/docker compose cleanup
2022-12-23 16:06:11 +01:00
Niklas Meyer
125aaa5b7d Merge pull request #4904 from mailcow/feature/alpine-3.17
Update Base Images to Alpine 3.17
2022-12-23 16:05:24 +01:00
DerLinkman
aa7888c37d Updated DB Schemata + reverted escape HTML of alert boxes 2022-12-23 14:47:27 +01:00
Der-Jan
f1e1232849 Add Message-ID to pushover 2022-12-21 10:39:14 +01:00
Peter
bb7c7bcff6 Install renovate 2022-12-18 22:40:21 +01:00
knuth
e5cf35aff8 fix unicode char 2022-12-16 14:17:17 +01:00
knuth
65585e286d use GitHub redirect for newest version 2022-12-16 14:16:46 +01:00
knuth
99bcfb8c4b fix incorrect render value 2022-12-16 14:09:39 +01:00
knuth
d98fd74968 use GitHub for newest docker-compose release 2022-12-16 13:58:15 +01:00
knuth
7875185e1f fix unicode char 2022-12-16 13:57:37 +01:00
knuth
a8d50955ee Use built in compose 2022-12-16 13:57:13 +01:00
knuth
bfd5329363 docker comes with compose 2022-12-16 13:57:01 +01:00
FreddleSpl0it
ea1eb48596 show version modal only on master 2022-12-16 09:48:33 +01:00
FreddleSpl0it
f1bb23ba2a fix darkmode toggle 2022-12-16 09:40:20 +01:00
FreddleSpl0it
5160eff294 add datatables date sort plugin & rename js files 2022-12-14 08:13:56 +01:00
mhupfauer
118984dfff Update bulk_header.map
AWeber is a massive Mail as a Service provider which is used by many legitimate corporations and should not be handled negatively by default.
2022-12-13 22:38:45 +01:00
Niklas Meyer
87214fef70 Update tweet-trigger-publish-release.yml 2022-12-13 15:16:47 +01:00
Niklas Meyer
f1f9626b5b Merge pull request #4898 from mailcow/staging
2022-11b
2022-12-13 15:15:46 +01:00
DerLinkman
3a13c93022 [SOGo] Updated to newer SOGo 5.8.0 (CalDav Issue fix) 2022-12-13 12:38:15 +01:00
DerLinkman
83bd66db98 [Update.sh] Increased Timeout for online status check 2022-12-13 11:52:04 +01:00
DerLinkman
13175b4e6c Updated README.md 2022-12-12 16:29:33 +01:00
Niklas Meyer
ecefbf2166 Merge pull request #4894 from mailcow/staging
2022-11a
2022-12-12 16:11:23 +01:00
Niklas Meyer
a763dda068 Update tweet-trigger-publish-release.yml 2022-12-12 16:09:13 +01:00
Niklas Meyer
698b2bf988 Merge pull request #4883 from schwindelbub/master
Update lang.de-de.json
2022-12-12 15:42:12 +01:00
DerLinkman
a71cc759f6 Renamed some Lang Classes + Added some new Strings 2022-12-12 11:58:40 +01:00
Kristian Feldsam
802d304579 Revert "[Dovecot] Disable imapsync job, when auth details are wrong. Fixes #4276 (#4540)" Closes #4711
This reverts commit d4e829465b.

Signed-off-by: Kristian Feldsam <feldsam@gmail.com>

# Conflicts:
#	docker-compose.yml
2022-12-12 11:41:30 +01:00
DerLinkman
faf8da1365 [RSPAMD] Implemented new Password change Script 2022-12-12 10:51:31 +01:00
DerLinkman
ce546e8a90 Merge branch 'feature/bootstrap5' of https://github.com/mailcow/mailcow-dockerized into feature/bootstrap5 2022-12-12 10:49:02 +01:00
DerLinkman
f4731eecdb Cleanup + Language Fixes 2022-12-12 10:49:00 +01:00
FreddleSpl0it
6704377402 [Web] escape more html data 2022-12-09 16:10:10 +01:00
DerLinkman
827cb00837 [DockerAPI] Tagged as 2.0 (rewrite) 2022-12-08 16:09:20 +01:00
DerLinkman
299a342a62 [Nextcloud] Update to 25 + purge fix (DB) 2022-12-08 15:57:24 +01:00
Schwindelhub
8614d63ace Update lang.de-de.json
Corrected "Leerzeichen in Komposita".
2022-12-03 21:21:00 +01:00
DerLinkman
77f04d10c7 Update Base Images to Alpine 3.17 2022-12-01 23:02:03 +01:00
Niklas Meyer
6d8c978d17 Merge pull request #4882 from mailcow/staging
2022-11 Update
2022-12-01 21:22:41 +01:00
DerLinkman
d55994b66a Merge branch 'staging' into nightly 2022-12-01 21:06:19 +01:00
Niklas Meyer
ff4f2ae0b6 Merge pull request #4859 from mitch-geht-ab/updatesh-proxy-caplty
switch update.sh/check_online_status() from ping to curl to make it proxy compatible
2022-11-30 17:39:21 +01:00
DerLinkman
0b00f15811 Added additional Check for Docker Hub 2022-11-30 17:37:33 +01:00
Niklas Meyer
bed5218550 Merge pull request #4877 from mailcow/feature/sogo-5.8.0
Update SOGo to 5.8.0 nightly
2022-11-30 17:20:21 +01:00
DerLinkman
86b67a9a7b Updated mailcow/sogo to 1.112 2022-11-30 17:13:39 +01:00
FreddleSpl0it
73370de1f9 Update SOGo to 5.8.0 nightly 2022-11-30 11:08:38 +01:00
milkmaker
524aba0964 [Web] Updated lang.sk-sk.json (#4873)
Co-authored-by: Lukáš Matula <lukas@gbely.net>

Co-authored-by: Lukáš Matula <lukas@gbely.net>
2022-11-25 19:52:37 +01:00
Patrick Schult
64528d8e0e Merge pull request #4845 from mailcow/feature/rspamd-3.4
[Rspamd] Update to 3.4 (fix of 3.3 Bug)
2022-11-24 11:45:29 +01:00
Patrick Schult
31b5faa729 Merge pull request #4868 from mailcow/feat/build-backup-image
Add new action Build mailcow backup image
2022-11-24 11:30:24 +01:00
Patrick Schult
17977a2fff Merge pull request #4864 from bluewalk/pushover-improvements
Pushover improvements
2022-11-24 11:28:28 +01:00
Patrick Schult
3e007eeaae Merge pull request #4860 from yhdsl/master
Update Simplified Chinese Translation
2022-11-24 11:26:46 +01:00
Niklas Meyer
a96b209e1b Merge pull request #4870 from mailcow/staging
Automatic PR to nightly from 2022-11-23T12:47:06Z
2022-11-23 15:35:09 +01:00
Niklas Meyer
05b897f43e Merge pull request #4848 from nathanielmom/compose-fix
change 'return 1' to 'exit 1'
2022-11-23 15:29:38 +01:00
Niklas Meyer
738dcac60d Merge pull request #4855 from BigMichi1/polish_phpfpm_dockerfile
[PHP] Polish phpfpm dockerfile
2022-11-23 15:18:23 +01:00
Niklas Meyer
b3bbeee5e2 Merge pull request #4844 from mailcow/feature/php-8.1
[PHP] Update to 8.1
2022-11-23 15:17:55 +01:00
Niklas Meyer
782eae4d4c Merge pull request #4869 from benpro/patch-1
Fixy comment typo
2022-11-23 15:16:48 +01:00
Benoît S
f2f5e212f5 Fixy comment typo 2022-11-23 22:10:57 +09:00
Peter
ff7102468e [Helper] Backup and restore: Use latest tag for image 2022-11-22 18:38:38 +01:00
Peter
118cb1017a Add new action Build mailcow backup image 2022-11-22 18:37:15 +01:00
bluewalk
360bb6f306 Split name and address for TO-variables 2022-11-20 10:42:44 +01:00
bluewalk
d8e314db1a Fixed issue with subdomain senders + added TO variable and allow new lines in text using \n 2022-11-19 15:32:48 +01:00
bluewalk
fd14c51f85 Removed regex as we have the address from the header 2022-11-18 17:29:31 +01:00
bluewalk
57a5a9baeb Updated DB version and make sure default sound is "pushover" when null 2022-11-17 21:14:44 +01:00
bluewalk
65c74c75c7 Added SENDER_ADDRESS and SENDER_NAME as variables for messages 2022-11-17 21:01:18 +01:00
bluewalk
e82f3b3975 Added SENDER_ADDRESS and SENDER_NAME as variables for messages 2022-11-17 21:01:18 +01:00
Niklas Meyer
d7323213b8 Merge pull request #4865 from mailcow/staging
Automatic PR to nightly from 2022-11-17T17:56:06Z
2022-11-17 20:55:57 +01:00
DerLinkman
fbc33da734 Merge branch 'master' into staging 2022-11-17 20:54:46 +01:00
DerLinkman
210815d4cf Merge branch 'master' into staging 2022-11-17 20:54:13 +01:00
Niklas Meyer
1e672ae349 Update FUNDING.yml 2022-11-17 20:49:13 +01:00
DerLinkman
19fabd0e64 Merge branch 'feature/bootstrap5' into nightly 2022-11-17 11:12:55 +01:00
FreddleSpl0it
ef392ef6ba add demo_mode for mailcow ui 2022-11-17 08:36:03 +01:00
DerLinkman
046e658984 Use @MAGICCC Version of action 2022-11-16 18:42:20 +01:00
DerLinkman
a46db9e0df Fixed typo in tweet action 2022-11-16 18:39:37 +01:00
DerLinkman
17f3cc3ad8 Optimized/Fixed Tweet action 2022-11-16 18:34:22 +01:00
DerLinkman
3236a10cf5 Updated tweet action (again) 2022-11-16 18:19:12 +01:00
DerLinkman
a4eb6d5f1b Update Release Tweet action 2022-11-16 18:15:45 +01:00
DerLinkman
a09661fc83 Merge branch 'feature/bootstrap5' into nightly 2022-11-16 18:00:32 +01:00
FreddleSpl0it
f52ab69a5b change default template creation 2022-11-16 15:29:39 +01:00
Niklas Meyer
9d1b620dcf Merge pull request #4861 from mailcow/staging 2022-11-16 13:30:10 +01:00
FreddleSpl0it
3ebd801b3d remove whats new modal & add changelog modal 2022-11-16 12:12:23 +01:00
Peter
05181f1888 Update issue template 2022-11-15 19:44:07 +01:00
Peter
6875baf64c Update issue template 2022-11-15 19:43:03 +01:00
FreddleSpl0it
0cdb1e638d change git_project_url var for base.twig 2022-11-15 16:25:05 +01:00
FreddleSpl0it
da415e5c6b [Dockerapi] define matched var before use 2022-11-15 16:12:07 +01:00
Peter
c46a1c1e2f [GH-Actions][actionpr] Update to v0.5.3 2022-11-14 22:51:19 +01:00
Link Steve
b79a1530fb Update Simplified Chinese Translation 2022-11-14 21:18:37 +08:00
thomas
a6a7ab45f8 switch update.sh/check_online_status() from ping to curl to make it proxy ready 2022-11-13 07:34:18 +01:00
FreddleSpl0it
c8f69ffe77 show created_on, last_modified for domain, mailbox 2022-11-11 09:22:58 +01:00
FreddleSpl0it
79982e0e8d add template feature for domains and mailboxes 2022-11-10 16:22:18 +01:00
Michael Cramer
bc937ed2db [PHP] Polish dockerfile
includes also #4839 because of --with-avif for gd configure command (is not available in 8.0)

contains the following adjustments:
- upgrade APCu to 5.1.22
- use PECL package for mailparse instead of git clone (3.1.4 is the latest one available and sice then no changes on master branch)
- split PECL commands into separate ones (according to https://hub.docker.com/_/php this is the recommended way)
- add missing configure options for gd extension to include webp, xpm and avif
- specify composer version to be installed
- cleanup more dev dependencies
2022-11-08 09:45:25 +01:00
Niklas Meyer
8ca028eb2e Merge pull request #4847 from mailcow/staging
Automatic PR to nightly from 2022-11-06T20:27:08Z
2022-11-07 14:12:55 +01:00
Nathaniel Mom
df17e6b75e change 'return 1' to 'exit 1' 2022-11-07 09:27:22 +10:00
Niklas Meyer
f880e1834d Merge pull request #4846 from jorisdrenth/staging
Add undocumented /api/v1/get/mailbox/all/domain.tld endpoint to docs
2022-11-06 22:02:03 +01:00
DerLinkman
4dd1b97e38 [PHP] Update to 8.1 2022-11-06 15:52:30 +01:00
Niklas Meyer
074e3fcd6e Merge pull request #4843 from mailcow/staging
Automatic PR to nightly from 2022-11-06T14:21:03Z
2022-11-06 15:38:01 +01:00
Joris Drenth
aeb433cc39 Add undocumented /api/v1/get/mailbox/all/domain.tld endpoint to documentation 2022-11-06 00:25:38 +01:00
Niklas Meyer
eb9d360c0a Merge pull request #4837 from mailcow/feat/action-check_prs_if_on_staging
Add new action Check PRs if on staging
2022-11-04 16:02:30 +01:00
Peter
abfad4e025 Add new action Check PRs if on staging 2022-11-03 19:58:06 +01:00
FreddleSpl0it
3f40fada1b edit page for default domain and mailbox settings 2022-11-03 07:25:18 +01:00
Vermium Sifell
a9871d05b2 ✏️ Fixed invalid regexs for banning 2022-11-02 23:42:37 +01:00
FreddleSpl0it
39e46d2e0b querySelector fails when id starts with digits 2022-11-02 14:09:45 +01:00
DerLinkman
e9091cbb8c [Rspamd] Update to 3.4 (fix of 3.3 Bug) 2022-11-02 10:32:56 +01:00
Niklas Meyer
cb340d78e1 Merge pull request #4827 from mailcow/staging
2022-10a
2022-10-26 13:06:09 +02:00
Niklas Meyer
996b2db514 Merge pull request #4826 from mailcow/staging
Automatic PR to nightly from 2022-10-26T07:46:18Z
2022-10-26 12:57:17 +02:00
Niklas Meyer
548d7b9833 Merge pull request #4825 from mailcow/fix/qitems
Fix Error parsing Quarantine Items
2022-10-26 12:55:10 +02:00
Niklas Meyer
96dbbf4db6 Merge pull request #4823 from mailcow/feature/rspamd-downgrade
[RSPAMD] Downgrade to 3.2 (stable)
2022-10-26 12:44:36 +02:00
DerLinkman
4f14462af7 [RSPAMD] Downgrade to 3.2 (stable) 2022-10-26 12:33:52 +02:00
FreddleSpl0it
1e08b4ece6 fix encoding failures of parsed text_plain mail 2022-10-26 12:33:22 +02:00
Niklas Meyer
177ebe26de Merge pull request #4822 from mailcow/staging
Automatic PR to nightly from 2022-10-26T07:46:18Z
2022-10-26 11:15:51 +02:00
Niklas Meyer
6fd9efc30a Merge pull request #4769 from ro78/patch-1
Update base.twig to escape simple quote
2022-10-26 11:14:37 +02:00
Niklas Meyer
da72184fda Merge pull request #4821 from mailcow/staging
Automatic PR to nightly from 2022-10-26T07:46:18Z
2022-10-26 11:05:58 +02:00
Niklas Meyer
6f212a41d8 Merge pull request #4820 from mailcow/feature/netfilter-compose
[Compose] Use new (patched) Netfilter Image
2022-10-26 11:04:36 +02:00
DerLinkman
52314d1a35 [Compose] Use new (patched) Netfilter Image 2022-10-26 11:03:02 +02:00
Niklas Meyer
3028a18a37 Merge pull request #4819 from mailcow/staging
2022-10
2022-10-25 14:16:05 +02:00
DerLinkman
a2b31cb28d Merge branch 'staging' into nightly 2022-10-25 12:25:34 +02:00
Niklas Meyer
26a5fcf989 Merge pull request #4815 from ethrgeist:bump-redis-7
[redis] Bump Redis to version 7
2022-10-25 12:20:30 +02:00
Niklas Meyer
509086ef54 Merge pull request #4816 from mailcow/feature/rspamd-3.3
[RSPAMD] Update to 3.3
2022-10-25 11:42:34 +02:00
Niklas Meyer
963510ed22 Merge pull request #4806 from mailcow/feature/pigz-backup
[Backup] Swapped PIGZ instead of gzip (allow Threading)
2022-10-25 11:05:40 +02:00
DerLinkman
2c0f8cda50 [RSPAMD] Update to 3.3 2022-10-25 10:35:23 +02:00
DerLinkman
50d2671d75 Fixed leading / warning removal 2022-10-25 10:06:53 +02:00
DerLinkman
b73d879f3c Removed thread prompt again. Added notice message 2022-10-25 09:55:29 +02:00
Knuth
725a5fe5b9 Bump Redis to version 7 2022-10-25 09:47:03 +02:00
DerLinkman
65ca42ca42 Restored Thread Prompt due to implementation in restore 2022-10-24 15:10:15 +02:00
DerLinkman
b22ff59f7b Added PIGZ for Restoring as well. 2022-10-24 12:28:41 +02:00
DerLinkman
58527857d9 Removed debug message 2022-10-21 11:54:23 +02:00
DerLinkman
6306c4555c Removed Thread Prompt and set default value to 1 Thread 2022-10-21 11:48:29 +02:00
Peter
922603f906 Rename turkish language file for #4657 2022-10-20 17:57:13 +02:00
Niklas Meyer
f8d45de749 Merge pull request #4657 from tomy0000000:master
🌐 Add Traditional Chinese Translation
2022-10-20 11:27:58 +02:00
DerLinkman
b6760e19b7 Merge branch 'feature/bootstrap5' into nightly 2022-10-20 11:12:17 +02:00
Niklas Meyer
6ce25f38e1 Merge pull request #4808 from mailcow/feature/language-change
Backport Language Changer (+ Chinese Translation) to BS5
2022-10-20 11:10:12 +02:00
DerLinkman
5cb7f726bc Fixed changes due to BS5 Classes 2022-10-20 11:07:56 +02:00
DerLinkman
a334f33b35 Merge PR 4657 into language-change 2022-10-20 10:58:51 +02:00
DerLinkman
cb1602c2de Fix English Flag rendering 2022-10-19 16:09:22 +02:00
DerLinkman
b503271aba Use Translateable strings in Debug Page 2022-10-19 15:57:57 +02:00
DerLinkman
008e5651f8 Merge branch 'feature/bootstrap5' into nightly 2022-10-19 11:36:25 +02:00
DerLinkman
5e3aab12a7 Restored original Container length + Corrected Image size on Debug Page 2022-10-19 11:36:07 +02:00
DerLinkman
8026b6c874 Swapped PIGZ instead of gzip 2022-10-19 11:15:12 +02:00
DerLinkman
51b80f6fa1 Merge branch 'feature/bootstrap5' into nightly 2022-10-18 14:20:55 +02:00
DerLinkman
75fdeb2843 Fixed queue message error 2022-10-18 14:19:51 +02:00
DerLinkman
2b1d927de4 Merge branch 'feature/bootstrap5' into nightly 2022-10-18 11:36:28 +02:00
DerLinkman
e5d788497a Rearranged Queue Manager + Ukraine Flag fix 2022-10-18 11:34:48 +02:00
Niklas Meyer
b173e2ef86 Merge pull request #4795 from mailcow/staging 2022-10-12 18:34:11 +02:00
Peter
042676fff7 [GH-Actions][actionpr] Update to v0.5.1 2022-10-12 18:27:30 +02:00
Peter
44d53146af [GH-Actions][stale] Update to v6.0.1 2022-10-12 18:26:39 +02:00
moo
3d48c2427a Merge branch 'feature/bootstrap5' into nightly 2022-10-12 15:38:11 +02:00
FreddleSpl0it
a9046d8f35 remove max-height from debug logo 2022-10-12 15:37:03 +02:00
moo
b4a1b81aec Merge branch 'feature/bootstrap5' into nightly 2022-10-12 15:12:58 +02:00
FreddleSpl0it
90eb0ea27a make transportstable responsive 2022-10-12 09:28:03 +02:00
FreddleSpl0it
4f01b9fd25 use ui_texts.title_name for host_stats card-header 2022-10-12 09:20:57 +02:00
FreddleSpl0it
174b5c8f7f add goto previous page btn to top 2022-10-11 19:20:49 +02:00
FreddleSpl0it
3912fcb238 shift get_public_ips to json_api.php 2022-10-11 17:40:46 +02:00
FreddleSpl0it
ef70457a48 shift datatable css to new file 2022-10-11 11:48:46 +02:00
FreddleSpl0it
8c4dbaec4f rework datatables 2022-10-11 11:41:06 +02:00
FreddleSpl0it
645e8f426c shift datatable child toggle function to api.js 2022-10-11 11:35:07 +02:00
Niklas Meyer
bae1d1c047 Merge pull request #4790 from mailcow/staging
Automatic PR to nightly from 2022-10-09T11:34:14Z
2022-10-09 17:27:53 +02:00
Niklas Meyer
ce4fb069d5 Merge pull request #4789 from mailcow/feat/prtonightlyaction
Update PR to nightly template
2022-10-09 17:26:17 +02:00
Peter
9444000d46 Use milkmaker as PR author
Use template to be able to use get_diff var
2022-10-09 17:02:28 +02:00
Peter
eacd9ac240 Add pr_to_nighty_template.yml 2022-10-09 17:01:21 +02:00
Tomy Hsieh
cf38d6ca69 🛠 fix: Improve language preference algo 2022-10-06 23:22:54 +08:00
Tomy Hsieh
905993d66e 🛠 fix: Language detection 2022-10-06 22:21:12 +08:00
DerLinkman
b8656763ec Merge branch 'staging' into nightly 2022-10-06 14:25:39 +02:00
DerLinkman
0d7fe2e347 Some corrections to pr action 2022-10-06 14:14:07 +02:00
DerLinkman
33bd871a63 Corrected some PR Action Code 2022-10-06 14:06:18 +02:00
DerLinkman
772122b255 Added auto PR for nightly builds 2022-10-06 14:04:27 +02:00
Niklas Meyer
9fb346751c Merge pull request #4724 from mnin/master
[Netfilter] Fix creating endless SNAT rules for ipv4
2022-10-06 12:18:23 +02:00
FreddleSpl0it
10e560c5b2 fix set rspamd worker password 2022-10-01 15:56:45 +02:00
Niklas Meyer
cb058e91a3 Merge pull request #4772 from mailcow/staging
Update Twig to 3.4.3
2022-09-30 12:25:05 +02:00
DerLinkman
ba9f2bc376 Update Twig to 3.4.3 2022-09-30 12:21:31 +02:00
FreddleSpl0it
fb7e234120 move guid to debug.php 2022-09-30 11:38:43 +02:00
FreddleSpl0it
27e7407407 Update Twig to 3.4.3 2022-09-30 11:03:05 +02:00
Romain
623397d20a Update base.twig to escape simple quote
Update base.twig to escape simple quote
See issue https://github.com/mailcow/mailcow-dockerized/issues/4718
2022-09-30 10:32:15 +02:00
Tomy Hsieh
7d46de33d8 Merge from upstream branch 'staging'
# Conflicts:
#	data/web/inc/vars.inc.php
2022-09-30 16:03:49 +08:00
DerLinkman
8c80cecdfb Merge remote-tracking branch 'origin/staging' into nightly 2022-09-27 21:41:21 +02:00
FreddleSpl0it
e53f431273 Merge remote-tracking branch 'origin/feature/bootstrap5' into nightly 2022-09-27 14:43:20 +02:00
FreddleSpl0it
8e0ee67108 add sieve access toggle to mass-actions-mailbox 2022-09-27 12:32:14 +02:00
FreddleSpl0it
3d82d9af1b add loading animation for container charts 2022-09-27 12:31:02 +02:00
FreddleSpl0it
8a0bd23985 fix some layout issues 2022-09-27 12:30:10 +02:00
FreddleSpl0it
4387e4764f display public ips on debug page 2022-09-26 12:19:51 +02:00
FreddleSpl0it
3b8e17c21f fix layout issues 2022-09-26 11:23:04 +02:00
DerLinkman
e74af0db89 Merge branch 'staging' into nightly 2022-09-08 12:35:51 +02:00
DerLinkman
5ff62d8f22 Merge branch 'staging' into nightly 2022-09-02 10:25:28 +02:00
DerLinkman
4427173a6c Revert "Before update on 2022-09-01_20_20_45"
This reverts commit db66fe33fa.
2022-09-02 09:57:17 +02:00
DerLinkman
db66fe33fa Before update on 2022-09-01_20_20_45 2022-09-01 20:21:39 +02:00
DerLinkman
cf5fa96a93 Merge branch 'staging' into nightly 2022-09-01 13:57:39 +02:00
DerLinkman
9e76b6ee70 Merge branch 'master' into nightly 2022-08-31 10:39:27 +02:00
FreddleSpl0it
45e97b3753 [BS5] fix merging bugs 2022-08-30 15:59:16 +02:00
DerLinkman
ecc16c69e6 Merge branch 'nightly' into feature/bootstrap5 2022-08-29 14:37:25 +02:00
FreddleSpl0it
77e6124b00 [BS5] move showWhatsNewModal 2022-08-23 14:24:10 +02:00
FreddleSpl0it
a3ddb58566 [BS5]responsive fixes 2022-08-23 12:43:23 +02:00
FreddleSpl0it
be7252f620 [BS5] update async dockerapi 2022-08-23 11:57:05 +02:00
Martin Wilhelmi
f34d3620b1 Remove trailing whitespaces 2022-08-22 22:16:01 +02:00
Martin Wilhelmi
70e99447f9 Fix adding same SNAT rule endless to the ipv4 POSTROUTING chain 2022-08-22 22:15:56 +02:00
FreddleSpl0it
db8af3d1e0 [BS5] use fastapi and aiodocker for dockerapi 2022-08-22 16:14:04 +02:00
FreddleSpl0it
7f70b0f703 [BS5] add container disk and network stats 2022-08-22 16:08:01 +02:00
FreddleSpl0it
2e10cc8e79 [BS5] fix RsettingsModal 2022-08-22 16:02:35 +02:00
FreddleSpl0it
047d9143c0 showWhatsNew modal remove default show class 2022-08-22 16:01:57 +02:00
FreddleSpl0it
a7a0eef125 [BS5] poll host stats if tab is active 2022-08-11 16:11:13 +02:00
FreddleSpl0it
5d35af9d69 [BS5] rework network and disk io 2022-08-10 16:16:36 +02:00
FreddleSpl0it
ea21bca7df [BS5] adjust host stats 2022-08-10 10:56:10 +02:00
FreddleSpl0it
a3c0737ba8 [BS5] add host statistics 2022-08-09 20:29:33 +02:00
Tomy Hsieh
7b57b3392c switch to IETF language tag 2022-08-09 15:44:09 +08:00
Tomy Hsieh
492451bfee Tailor translation
`quarantine`, `success`
2022-08-09 15:10:44 +08:00
FreddleSpl0it
9747995510 [BS5] redirect to /debug after login 2022-08-08 13:24:29 +02:00
FreddleSpl0it
ad9112010f [BS5] center container restart button text 2022-08-08 13:23:57 +02:00
FreddleSpl0it
a4ec2d1d86 [BS5] adjust whats new modal 2022-08-08 12:53:52 +02:00
FreddleSpl0it
b7f07951f6 [BS5] fix mobile navbar flex-direction 2022-08-08 12:53:25 +02:00
FreddleSpl0it
0e3363e61c [BS5] add additional info to app_info.inc.php 2022-08-08 12:52:42 +02:00
Tomy Hsieh
a1e8077f45 Tailor translation
`user`
2022-08-02 18:53:58 +08:00
Tomy Hsieh
956e4e2aa7 Tailor translation
`mailbox`
2022-08-01 08:20:02 +08:00
Tomy Hsieh
c2e0a275e1 Tailor translation
`edit`, `fido2`
2022-07-16 22:02:58 +08:00
Tomy Hsieh
514079fe96 Tailor translation
`danger`
2022-07-11 02:44:31 +08:00
FreddleSpl0it
e26d5b8ba5 [BS5] add spacing 2022-07-08 15:47:35 +02:00
FreddleSpl0it
8987ebca36 [BS5] add whats new modal after update 2022-07-08 15:47:21 +02:00
FreddleSpl0it
d3cd21956a [BS5] rearrange nav items 2022-07-08 15:46:14 +02:00
FreddleSpl0it
b149da28c8 [BS5] minor fixes 2022-07-08 11:32:46 +02:00
FreddleSpl0it
e5cb2dd00e [BS5] adjust restart container btn 2022-07-08 11:31:48 +02:00
FreddleSpl0it
c9b883dff5 [BS5] fix datatables 2022-07-08 11:31:08 +02:00
FreddleSpl0it
ad43253a90 [BS5] add rspamd logo change to darkmode toggle 2022-07-08 11:28:58 +02:00
FreddleSpl0it
979de67c2b [BS5] update bootstrap-select to v1.14.0-beta3 2022-07-08 11:28:27 +02:00
FreddleSpl0it
8416caf798 [BS5] add light and dark rspamd logo 2022-07-08 11:27:34 +02:00
FreddleSpl0it
80d9dfe420 [BS5] adjust css 2022-07-08 11:27:09 +02:00
Tomy Hsieh
27e9210d52 Tailor translation
`admin`
2022-07-07 22:07:02 +08:00
FreddleSpl0it
8a49b50f33 [BS5] fix minor issues 2022-07-06 16:55:31 +02:00
Tomy Hsieh
1b6e5b7116 Tailor translation
`add`
2022-07-06 13:33:18 +08:00
Tomy Hsieh
0dab215e27 Tailor translation
`debug`, `diagnostics`, `tfa`
2022-07-04 23:00:22 +08:00
Tomy Hsieh
6ec136e63f Tailor translation
`footer`, `header`, `info`, `login`, `oath2`, `ratelimit`, `start`, `warning`
2022-07-03 09:17:09 +08:00
Tomy Hsieh
bba5671eaf Tailor translation: acl 2022-07-03 01:18:34 +08:00
Tomy Hsieh
88d7593d89 Switch language key
zh_Hans -> zh-cn
zh_Hant -> zh-tw
2022-07-02 17:01:50 +08:00
Tomy Hsieh
bd23b80d45 Translation: Spacing
Add spaces between halfwidth and fullwidth characters
2022-07-02 13:06:13 +08:00
Tomy Hsieh
f96e0c4071 Adding Traditional Chinese Translation 2022-07-02 11:29:37 +08:00
FreddleSpl0it
cbd8e40f14 [BS5] fix 2fa 2022-07-01 16:44:24 +02:00
FreddleSpl0it
2d2c033ba7 [BS5] add spacing 2022-07-01 16:21:02 +02:00
FreddleSpl0it
496c68d2af [BS5] datatables handle null values 2022-07-01 16:20:39 +02:00
FreddleSpl0it
c505943e8b [BS5] fix user auth responsive tab 2022-07-01 10:51:58 +02:00
FreddleSpl0it
2841c09c1f [BS5] fix user auth responsive tab 2022-07-01 10:44:21 +02:00
FreddleSpl0it
18444bd284 [BS5] fix minor issues 2022-06-28 07:21:26 +02:00
FreddleSpl0it
9d3a89d362 [BS5] add darkmode 2022-06-28 07:20:46 +02:00
FreddleSpl0it
2d0ab4a1b8 [BS5] make container status more visible 2022-06-24 16:04:12 +02:00
FreddleSpl0it
2fec7ccd58 [BS5] make container status more visible 2022-06-24 15:55:52 +02:00
FreddleSpl0it
052959f435 [BS5] remove ui theme selector - add darkmode toggler 2022-06-23 16:34:58 +02:00
FreddleSpl0it
560df58bb4 [BS5] fix minor issues 2022-06-15 16:34:49 +02:00
DerLinkman
5629d47cb6 Merge branch 'pr/FreddleSpl0it/4527' into feature/bootstrap5 2022-06-15 11:22:59 +02:00
FreddleSpl0it
37b4ff811d [BS5] add theme selector 2022-06-14 16:31:21 +02:00
FreddleSpl0it
7384aab2f4 [BS5] fix minor issues 2022-06-14 15:52:59 +02:00
FreddleSpl0it
4ce05d4d4d [BS5] datatables add responsivePrio and adjust className 2022-06-08 16:21:15 +02:00
FreddleSpl0it
fdb56de0a8 [BS5] jquery datatable - add classes to action column 2022-06-08 15:31:10 +02:00
FreddleSpl0it
df56d73cec [BS5] jquery datatable - add classes to action column 2022-06-08 15:28:45 +02:00
FreddleSpl0it
4ba0e155f3 [BS5] fix guid collapse stutter 2022-06-08 12:16:45 +02:00
FreddleSpl0it
304655f7ff [BS5] remember last nav pill - revert id var 2022-06-08 12:12:12 +02:00
FreddleSpl0it
6210f06bc0 [BS5] adjust admin refresh_table function 2022-06-08 12:06:14 +02:00
FreddleSpl0it
09ae37410e [BS5] jquery datatables disable orderable checkboxes 2022-06-08 12:03:54 +02:00
FreddleSpl0it
351c803623 [BS5] change onVisible querySelectors to table id 2022-06-08 11:49:05 +02:00
FreddleSpl0it
6a027b70e7 [BS5] replace footable with jquery datatables (edit.twig) 2022-06-08 11:19:20 +02:00
FreddleSpl0it
cdd2adbc73 [BS5] remember last nav pill fix 2022-06-07 15:28:28 +02:00
FreddleSpl0it
cb6a5d4069 [BS5] add responsive tabs and more 2022-06-06 20:38:24 +02:00
FreddleSpl0it
f13530d8a1 [BS5] Replace FooTable with jquery Datatables 2022-06-03 15:45:36 +02:00
FreddleSpl0it
8a86fa491e [BS5] Replace FooTable with jquery Datatables 2022-05-20 12:03:12 +02:00
FreddleSpl0it
3e6a241c69 [BS5] Replace FooTable with jquery Datatables 2022-05-19 21:29:01 +02:00
FreddleSpl0it
160dceff3e [BS5] Replace FooTable with jquery Datatables 2022-05-19 15:06:18 +02:00
FreddleSpl0it
0ece065cb0 [BS5] Replace FooTable with jquery Datatables 2022-05-17 14:08:22 +02:00
FreddleSpl0it
fb7e00c158 [BS5] Replace FooTable with jquery Datatables 2022-05-16 11:26:49 +02:00
FreddleSpl0it
a0567beee5 [BS5] Replace FooTable with jquery Datatables 2022-05-13 14:16:32 +02:00
FreddleSpl0it
96c8e01a3b [BS5] change spinner icons 2022-04-14 10:22:06 +02:00
FreddleSpl0it
88bd7bff1e [BS5] fix card header btns 2022-04-13 21:45:00 +02:00
FreddleSpl0it
7075b9f0c0 [BS5] mobile navbar fix 2022-04-13 21:34:00 +02:00
FreddleSpl0it
b19666f7e0 [BS5] add layout spacing 2022-04-13 16:37:52 +02:00
FreddleSpl0it
e663f3db72 [BS5] change form-group 2022-04-13 14:07:07 +02:00
FreddleSpl0it
fd1ffdba80 [BS5] layout fixes 2022-04-13 12:36:59 +02:00
FreddleSpl0it
a12538b3a8 [BS5] layout fixes 2022-04-13 12:35:04 +02:00
FreddleSpl0it
cdff1ba37b [BS5] update bootstrap-select to v1.14beta 2022-04-13 12:34:14 +02:00
FreddleSpl0it
f6a51f6b6f [BS5] add gridjs lib 2022-04-13 12:32:48 +02:00
FreddleSpl0it
45dd0611d9 [BS5] fix card classes 2022-04-01 08:26:37 +02:00
FreddleSpl0it
e62069d3db [BS5] change bootstrap in js 2022-04-01 08:25:47 +02:00
FreddleSpl0it
5088636d5f [BS5] change bootstrap responsive 2022-04-01 08:02:11 +02:00
FreddleSpl0it
003d70990e [BS5] change bootstrap general 2022-04-01 07:33:01 +02:00
FreddleSpl0it
051d08b499 [BS5] bug fixes 2022-03-31 20:16:44 +02:00
FreddleSpl0it
b980e7af29 [BS5] change bootstrap responsive 2022-03-31 15:24:10 +02:00
FreddleSpl0it
4d0799dead [BS5] change bootstrap general 2022-03-31 13:24:18 +02:00
FreddleSpl0it
d3dca1ddc2 [BS5] change bootstrap general 2022-03-31 12:57:33 +02:00
FreddleSpl0it
9b8039440c [BS5] change bootstrap general 2022-03-31 12:41:50 +02:00
FreddleSpl0it
eea5c9df2f [BS5] change bootstrap panels/cards 2022-03-31 11:37:14 +02:00
FreddleSpl0it
aa9aff800c [BS5] change bootstrap dropdowns 2022-03-31 10:09:25 +02:00
FreddleSpl0it
0b3b5e230b [BS5] change bootstrap data-attributes 2022-03-30 13:04:54 +02:00
FreddleSpl0it
db0d91beb1 [BS5] change bootstrap tabs 2022-03-30 12:54:38 +02:00
FreddleSpl0it
4758033445 [BS5] change bootstrap tabs 2022-03-30 12:39:18 +02:00
FreddleSpl0it
1e48fb8cda [BS5] change jquery $(window).load 2022-03-30 08:45:24 +02:00
FreddleSpl0it
1d8da117d6 [BS5] change bootstrap navbar 2022-03-30 08:39:38 +02:00
FreddleSpl0it
635fa795d2 [BS5] move init frontend block 2022-03-30 07:55:52 +02:00
FreddleSpl0it
c1792df819 [BS5] include dependencies 2022-03-30 07:54:07 +02:00
FreddleSpl0it
36944f8073 [BS5] remove dependencies 2022-03-30 07:08:24 +02:00
FreddleSpl0it
4d59cb0351 [BS5] remove u2f-api.js 2022-03-29 09:41:11 +02:00
243 changed files with 63744 additions and 8958 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
custom: https://mailcow.github.io/mailcow-dockerized-docs/#help-mailcow
custom: ["https://www.servercow.de/mailcow?lang=en#sal"]

View File

@@ -7,8 +7,8 @@ body:
label: Contribution guidelines
description: Please read the contribution guidelines before proceeding.
options:
- label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree
required: true
- label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree
required: true
- type: checkboxes
attributes:
label: I've found a bug and checked that ...
@@ -26,70 +26,132 @@ body:
attributes:
label: Description
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
render: plain text
validations:
required: true
- type: textarea
attributes:
label: Logs
description: Please take a look at the [official documentation](https://mailcow.github.io/mailcow-dockerized-docs/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks.
render: bash
label: "Logs:"
description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks."
render: plain text
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Please describe the steps to reproduce the bug. Screenshots can be added, if helpful.
label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
render: plain text
placeholder: |-
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
- type: markdown
attributes:
label: System information
description: In this stage we would kindly ask you to attach general system information about your setup.
value: |-
| Question | Answer |
| --- | --- |
| My operating system | I_DO_REPLY_HERE |
| Is Apparmor, SELinux or similar active? | I_DO_REPLY_HERE |
| Virtualization technology (KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported** | I_DO_REPLY_HERE |
| Server/VM specifications (Memory, CPU Cores) | I_DO_REPLY_HERE |
| Docker version (`docker version`) | I_DO_REPLY_HERE |
| docker-compose version (`docker-compose version`) | I_DO_REPLY_HERE |
| mailcow version (```git describe --tags `git rev-list --tags --max-count=1` ```) | I_DO_REPLY_HERE |
| Reverse proxy (custom solution) | I_DO_REPLY_HERE |
Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:
```
YOUR OUTPUT GOES HERE
```
All third-party firewalls and custom iptables rules are unsupported. **Please check the Docker docs about how to use Docker with your own ruleset**. Nevertheless, iptabels output can help us to help you:
iptables -L -vn:
```
YOUR OUTPUT GOES HERE
```
ip6tables -L -vn:
```
YOUR OUTPUT GOES HERE
```
iptables -L -vn -t nat:
```
YOUR OUTPUT GOES HERE
```
ip6tables -L -vn -t nat:
```
YOUR OUTPUT GOES HERE
```
DNS problems? Please run `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network) and post the output:
```
YOUR OUTPUT GOES HERE
```
value: |
## System information
### In this stage we would kindly ask you to attach general system information about your setup.
- type: dropdown
attributes:
label: "Which branch are you using?"
description: "#### `git rev-parse --abbrev-ref HEAD`"
multiple: false
options:
- master
- nightly
validations:
required: true
- type: input
attributes:
label: "Operating System:"
placeholder: "e.g. Ubuntu 22.04 LTS"
validations:
required: true
- type: input
attributes:
label: "Server/VM specifications:"
placeholder: "Memory, CPU Cores"
validations:
required: true
- type: input
attributes:
label: "Is Apparmor, SELinux or similar active?"
placeholder: "yes/no"
validations:
required: true
- type: input
attributes:
label: "Virtualization technology:"
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**"
validations:
required: true
- type: input
attributes:
label: "Docker version:"
description: "#### `docker version`"
placeholder: "20.10.21"
validations:
required: true
- type: input
attributes:
label: "docker-compose version or docker compose version:"
description: "#### `docker-compose version` or `docker compose version`"
placeholder: "v2.12.2"
validations:
required: true
- type: input
attributes:
label: "mailcow version:"
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
placeholder: "2022-08"
validations:
required: true
- type: input
attributes:
label: "Reverse proxy:"
placeholder: "e.g. Nginx/Traefik"
validations:
required: true
- type: textarea
attributes:
label: "Logs of git diff:"
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of iptables -L -vn:"
description: "#### Output of `iptables -L -vn`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of ip6tables -L -vn:"
description: "#### Output of `ip6tables -L -vn`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of iptables -L -vn -t nat:"
description: "#### Output of `iptables -L -vn -t nat`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of ip6tables -L -vn -t nat:"
description: "#### Output of `ip6tables -L -vn -t nat`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "DNS check:"
description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)"
render: plain text
validations:
required: true

View File

@@ -0,0 +1,13 @@
## :memo: Brief description
<!-- Diff summary - START -->
<!-- Diff summary - END -->
## :computer: Commits
<!-- Diff commits - START -->
<!-- Diff commits - END -->
## :file_folder: Modified files
<!-- Diff files - START -->
<!-- Diff files - END -->

13
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"enabled": true,
"timezone": "Europe/Berlin",
"dependencyDashboard": false,
"dependencyDashboardTitle": "Renovate Dashboard",
"commitBody": "Signed-off-by: milkmaker <milkmaker@mailcow.de>",
"rebaseWhen": "auto",
"assignees": [
"@magiccc"
],
"baseBranches": ["staging"],
"enabledManagers": ["github-actions"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -0,0 +1,33 @@
name: Check PRs if on staging
on:
pull_request_target:
types: [opened, edited]
permissions: {}
jobs:
is_not_staging:
runs-on: ubuntu-latest
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
steps:
- name: Send message
uses: thollander/actions-comment-pull-request@main
with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: |
Thanks for contributing!
I noticed that you didn't select `staging` as your base branch. Please change the base branch to `staging`.
See the attached picture on how to change the base branch to `staging`:
![check_prs_if_on_staging.png](https://raw.githubusercontent.com/mailcow/mailcow-dockerized/master/.github/workflows/assets/check_prs_if_on_staging.png)
- name: Fail #we want to see failed checks in the PR
if: ${{ success() }} #set exit code to 1 even if commenting somehow failed
run: exit 1
is_staging:
runs-on: ubuntu-latest
if: github.event.pull_request.base.ref == 'staging' #check if the target branch is staging
steps:
- name: Success
run: exit 0

View File

@@ -14,7 +14,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale Issues and Pull Requests 🗑️
uses: actions/stale@v6.0.0
uses: actions/stale@v6.0.1
with:
repo-token: ${{ secrets.STALE_ACTION_PAT }}
days-before-stale: 60

View File

@@ -33,13 +33,11 @@ jobs:
run: |
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
sudo service docker start
sudo curl -L https://github.com/docker/compose/releases/download/v$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Prepair Image Builds
run: |
cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml
- name: Build Docker Images
run: |
docker-compose build ${image}
docker compose build ${image}
env:
image: ${{ matrix.images }}

25
.github/workflows/pr_to_nightly.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Create PR to merge to nightly from staging
on:
push:
branches:
- staging
jobs:
action-pull-request:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run the Action
uses: devops-infra/action-pull-request@v0.5.3
with:
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
assignee: DerLinkman
source_branch: staging
target_branch: nightly
reviewer: DerLinkman
label: upstream
template: .github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
get_diff: true

View File

@@ -0,0 +1,34 @@
name: Build mailcow backup image
on:
schedule:
# At 00:00 on Sunday
- cron: "0 0 * * 0"
workflow_dispatch: # Allow to run workflow manually
jobs:
docker_image_build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }}
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: data/Dockerfiles/backup/Dockerfile
push: true
tags: mailcow/backup:latest

View File

@@ -4,9 +4,12 @@ on:
types: [published]
jobs:
build:
tweet:
runs-on: ubuntu-latest
steps:
- name: "Get Release Tag"
run: |
RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name')
- name: Tweet-trigger-publish-release
uses: mugi111/tweet-trigger-release@v1.1
with:
@@ -14,4 +17,4 @@ jobs:
consumer_secret: ${{ secrets.CONSUMER_SECRET }}
access_token_key: ${{ secrets.ACCESS_TOKEN_KEY }}
access_token_secret: ${{ secrets.ACCESS_TOKEN_SECRET }}
tweet_body: 'A new mailcow-dockerized Release has been Released on GitHub! Checkout our GitHub Page for the latest Release: github.com/mailcow/mailcow-dockerized/releases/latest'
tweet_body: 'A new mailcow update has just been released! Checkout the GitHub Page for changelog and more informations: https://github.com/mailcow/mailcow-dockerized/releases/latest'

View File

@@ -1,7 +1,5 @@
# mailcow: dockerized - 🐮 + 🐋 = 💕
## We stand with 🇺🇦
[![Mailcow Integration Tests](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml/badge.svg?branch=master)](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml)
[![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/)
[![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email)
@@ -36,3 +34,9 @@ Telegram desktop clients are available for [multiple platforms](https://desktop.
**Important**: mailcow makes use of various open-source software. Please assure you agree with their license before using mailcow.
Any part of mailcow itself is released under **GNU General Public License, Version 3**.
mailcow is a registered word mark of The Infrastructure Company GmbH, Parkstr. 42, 47877 Willich, Germany.
The project is managed and maintained by The Infrastructure Company GmbH.
Originated from @andryyy (André)

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

@@ -0,0 +1,3 @@
FROM debian:bullseye-slim
RUN apt update && apt install pigz

View File

@@ -1,4 +1,4 @@
FROM clamav/clamav:0.105.1_base
FROM clamav/clamav:1.0_base
LABEL maintainer "André Peters <andre.peters@servercow.de>"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
@@ -8,11 +8,14 @@ RUN apk add --update --no-cache python3 \
py3-pip \
openssl \
tzdata \
py3-psutil \
&& pip3 install --upgrade pip \
docker \
flask \
flask-restful
fastapi \
uvicorn \
aiodocker \
redis
COPY docker-entrypoint.sh /app/
COPY dockerapi.py /app/
CMD ["python3", "-u", "/app/dockerapi.py"]
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]

View File

@@ -0,0 +1,9 @@
#!/bin/bash
`openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout /app/dockerapi_key.pem \
-out /app/dockerapi_cert.pem \
-subj /CN=dockerapi/O=mailcow \
-addext subjectAltName=DNS:dockerapi`
`uvicorn --host 0.0.0.0 --port 443 --ssl-certfile=/app/dockerapi_cert.pem --ssl-keyfile=/app/dockerapi_key.pem dockerapi:app`

View File

@@ -1,419 +1,623 @@
#!/usr/bin/env python3
from flask import Flask
from flask_restful import Resource, Api
from flask import jsonify
from flask import Response
from flask import request
from threading import Thread
import docker
import uuid
import signal
from fastapi import FastAPI, Response, Request
import aiodocker
import psutil
import sys
import re
import time
import os
import re
import sys
import ssl
import socket
import subprocess
import traceback
import json
import asyncio
import redis
from datetime import datetime
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__)
api = Api(app)
class containers_get(Resource):
def get(self):
containers = {}
containerIds_to_update = []
host_stats_isUpdating = False
app = FastAPI()
@app.get("/host/stats")
async def get_host_update_stats():
global host_stats_isUpdating
if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats())
host_stats_isUpdating = True
while True:
if redis_client.exists('host_stats'):
break
print("wait for host_stats results")
await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
@app.get("/containers/{container_id}/json")
async def get_container(container_id : str):
if container_id and container_id.isalnum():
try:
for container in docker_client.containers.list(all=True):
containers.update({container.attrs['Id']: container.attrs})
return containers
for container in (await async_docker_client.containers.list()):
if container._id == container_id:
container_info = await container.show()
return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
res = {
"type": "danger",
"msg": "no container found"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
except Exception as e:
return jsonify(type='danger', msg=str(e))
res = {
"type": "danger",
"msg": str(e)
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
"type": "danger",
"msg": "no or invalid id defined"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
class container_get(Resource):
def get(self, container_id):
if container_id and container_id.isalnum():
try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
return container.attrs
except Exception as e:
return jsonify(type='danger', msg=str(e))
else:
return jsonify(type='danger', msg='no or invalid id defined')
@app.get("/containers/json")
async def get_containers():
containers = {}
try:
for container in (await async_docker_client.containers.list()):
container_info = await container.show()
containers.update({container_info['Id']: container_info})
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
except Exception as e:
res = {
"type": "danger",
"msg": str(e)
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
class container_post(Resource):
def post(self, container_id, post_action):
if container_id and container_id.isalnum() and post_action:
try:
"""Dispatch container_post api call"""
if post_action == 'exec':
if not request.json or not 'cmd' in request.json:
return jsonify(type='danger', msg='cmd is missing')
if not request.json or not 'task' in request.json:
return jsonify(type='danger', msg='task is missing')
@app.post("/containers/{container_id}/{post_action}")
async def post_containers(container_id : str, post_action : str, request: Request):
try :
request_json = await request.json()
except Exception as err:
request_json = {}
api_call_method_name = '__'.join(['container_post', str(post_action), str(request.json['cmd']), str(request.json['task']) ])
else:
api_call_method_name = '__'.join(['container_post', str(post_action) ])
if container_id and container_id.isalnum() and post_action:
try:
"""Dispatch container_post api call"""
if post_action == 'exec':
if not request_json or not 'cmd' in request_json:
res = {
"type": "danger",
"msg": "cmd is missing"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if not request_json or not 'task' in request_json:
res = {
"type": "danger",
"msg": "task is missing"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
api_call_method = getattr(self, api_call_method_name, lambda container_id: jsonify(type='danger', msg='container_post - unknown api call'))
api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
else:
api_call_method_name = '__'.join(['container_post', str(post_action) ])
docker_utils = DockerUtils(async_docker_client)
api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return api_call_method(container_id)
except Exception as e:
print("error - container_post: %s" % str(e))
return jsonify(type='danger', msg=str(e))
print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return await api_call_method(container_id, request_json)
except Exception as e:
print("error - container_post: %s" % str(e))
res = {
"type": "danger",
"msg": str(e)
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
return jsonify(type='danger', msg='invalid container id or missing action')
else:
res = {
"type": "danger",
"msg": "invalid container id or missing action"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
@app.post("/container/{container_id}/stats/update")
async def post_container_update_stats(container_id : str):
global containerIds_to_update
# start update task for container if no task is running
if container_id not in containerIds_to_update:
asyncio.create_task(get_container_stats(container_id))
containerIds_to_update.append(container_id)
while True:
if redis_client.exists(container_id + '_stats'):
break
await asyncio.sleep(1.5)
stats = json.loads(redis_client.get(container_id + '_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
class DockerUtils:
def __init__(self, docker_client):
self.docker_client = docker_client
# api call: container_post - post_action: stop
def container_post__stop(self, container_id):
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.stop()
return jsonify(type='success', msg='command completed successfully')
async def container_post__stop(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.stop()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: start
def container_post__start(self, container_id):
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.start()
return jsonify(type='success', msg='command completed successfully')
async def container_post__start(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.start()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: restart
def container_post__restart(self, container_id):
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.restart()
return jsonify(type='success', msg='command completed successfully')
async def container_post__restart(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.restart()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: top
def container_post__top(self, container_id):
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
return jsonify(type='success', msg=container.top())
async def container_post__top(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
ps_exec = await container.exec("ps")
async with ps_exec.start(detach=False) as stream:
ps_return = await stream.read_out()
# api call: container_post - post_action: stats
def container_post__stats(self, container_id):
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
for stat in container.stats(decode=True, stream=True):
return jsonify(type='success', msg=stat )
exec_details = await ps_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': ps_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': ''
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: delete
def container_post__exec__mailq__delete(self, container_id):
if 'items' in request.json:
async def container_post__exec__mailq__delete(self, container_id, request_json):
if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items'])
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-d %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids));
sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return exec_run_handler('generic', postsuper_r)
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: hold
def container_post__exec__mailq__hold(self, container_id):
if 'items' in request.json:
async def container_post__exec__mailq__hold(self, container_id, request_json):
if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items'])
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-h %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids));
sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return exec_run_handler('generic', postsuper_r)
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: cat
def container_post__exec__mailq__cat(self, container_id):
if 'items' in request.json:
async def container_post__exec__mailq__cat(self, container_id, request_json):
if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items'])
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
sanitized_string = str(' '.join(filtered_qids));
sanitized_string = str(' '.join(filtered_qids))
for container in docker_client.containers.list(filters={"id": container_id}):
postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
if not postcat_return:
postcat_return = 'err: invalid'
return exec_run_handler('utf8_text_only', postcat_return)
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
return await exec_run_handler('utf8_text_only', postcat_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: unhold
def container_post__exec__mailq__unhold(self, container_id):
if 'items' in request.json:
async def container_post__exec__mailq__unhold(self, container_id, request_json):
if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items'])
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-H %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids));
sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return exec_run_handler('generic', postsuper_r)
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: deliver
def container_post__exec__mailq__deliver(self, container_id):
if 'items' in request.json:
async def container_post__exec__mailq__deliver(self, container_id, request_json):
if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items'])
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in docker_client.containers.list(filters={"id": container_id}):
for i in flagged_qids:
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
# todo: check each exit code
return jsonify(type='success', msg=str("Scheduled immediate delivery"))
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
for i in flagged_qids:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
async with postsuper_r_exec.start(detach=False) as stream:
postsuper_r_return = await stream.read_out()
# todo: check each exit code
res = {
'type': 'success',
'msg': 'Scheduled immediate delivery'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: list
def container_post__exec__mailq__list(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
return exec_run_handler('utf8_text_only', mailq_return)
async def container_post__exec__mailq__list(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
return await exec_run_handler('utf8_text_only', mailq_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: flush
def container_post__exec__mailq__flush(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
return exec_run_handler('generic', postqueue_r)
async def container_post__exec__mailq__flush(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
def container_post__exec__mailq__super_delete(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
return exec_run_handler('generic', postsuper_r)
async def container_post__exec__mailq__super_delete(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
def container_post__exec__system__fts_rescan(self, container_id):
if 'username' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail')
if rescan_return.exit_code == 0:
return jsonify(type='success', msg='fts_rescan: rescan triggered')
else:
return jsonify(type='warning', msg='fts_rescan error')
async def container_post__exec__system__fts_rescan(self, container_id, request_json):
if 'username' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
async with rescan_exec.start(detach=False) as stream:
rescan_return = await stream.read_out()
if 'all' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
if rescan_return.exit_code == 0:
return jsonify(type='success', msg='fts_rescan: rescan triggered')
else:
return jsonify(type='warning', msg='fts_rescan error')
exec_details = await rescan_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': 'fts_rescan: rescan triggered'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if 'all' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
async with rescan_exec.start(detach=False) as stream:
rescan_return = await stream.read_out()
exec_details = await rescan_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': 'fts_rescan: rescan triggered'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: df
def container_post__exec__system__df(self, container_id):
if 'dir' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request.json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
if df_return.exit_code == 0:
return df_return.output.decode('utf-8').rstrip()
else:
return "0,0,0,0,0,0"
async def container_post__exec__system__df(self, container_id, request_json):
if 'dir' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
async with df_exec.start(detach=False) as stream:
df_return = await stream.read_out()
print(df_return)
print(await df_exec.inspect())
exec_details = await df_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
return df_return.data.decode('utf-8').rstrip()
else:
return "0,0,0,0,0,0"
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
def container_post__exec__system__mysql_upgrade(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
if sql_return.exit_code == 0:
matched = False
for line in sql_return.output.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
matched = True
if matched:
return jsonify(type='success', msg='mysql_upgrade: already upgraded', text=sql_return.output.decode('utf-8'))
async def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
async with sql_exec.start(detach=False) as stream:
sql_return = await stream.read_out()
exec_details = await sql_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
matched = False
for line in sql_return.data.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
matched = True
if matched:
res = {
'type': 'success',
'msg': 'mysql_upgrade: already upgraded',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
await container.restart()
res = {
'type': 'warning',
'msg': 'mysql_upgrade: upgrade was applied',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
container.restart()
return jsonify(type='warning', msg='mysql_upgrade: upgrade was applied', text=sql_return.output.decode('utf-8'))
else:
return jsonify(type='error', msg='mysql_upgrade: error running command', text=sql_return.output.decode('utf-8'))
res = {
'type': 'error',
'msg': 'mysql_upgrade: error running command',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
if sql_return.exit_code == 0:
return jsonify(type='info', msg='mysql_tzinfo_to_sql: command completed successfully', text=sql_return.output.decode('utf-8'))
else:
return jsonify(type='error', msg='mysql_tzinfo_to_sql: error running command', text=sql_return.output.decode('utf-8'))
async def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
async with sql_exec.start(detach=False) as stream:
sql_return = await stream.read_out()
exec_details = await sql_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'info',
'msg': 'mysql_tzinfo_to_sql: command completed successfully',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'error',
'msg': 'mysql_tzinfo_to_sql: error running command',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
def container_post__exec__reload__dovecot(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
return exec_run_handler('generic', reload_return)
async def container_post__exec__reload__dovecot(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: postfix
def container_post__exec__reload__postfix(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
return exec_run_handler('generic', reload_return)
async def container_post__exec__reload__postfix(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: nginx
def container_post__exec__reload__nginx(self, container_id):
for container in docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
return exec_run_handler('generic', reload_return)
async def container_post__exec__reload__nginx(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: list
def container_post__exec__sieve__list(self, container_id):
if 'username' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"])
return exec_run_handler('utf8_text_only', sieve_return)
async def container_post__exec__sieve__list(self, container_id, request_json):
if 'username' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: print
def container_post__exec__sieve__print(self, container_id):
if 'username' in request.json and 'script_name' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"]
sieve_return = container.exec_run(cmd)
return exec_run_handler('utf8_text_only', sieve_return)
async def container_post__exec__sieve__print(self, container_id, request_json):
if 'username' in request_json and 'script_name' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
sieve_exec = await container.exec(cmd)
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
def container_post__exec__maildir__cleanup(self, container_id):
if 'maildir' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
sane_name = re.sub(r'\W+', '', request.json['maildir'])
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
maildir_cleanup = container.exec_run(cmd, user='vmail')
return exec_run_handler('generic', maildir_cleanup)
async def container_post__exec__maildir__cleanup(self, container_id, request_json):
if 'maildir' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sane_name = re.sub(r'\W+', '', request_json['maildir'])
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
maildir_cleanup_exec = await container.exec(cmd, user='vmail')
return await exec_run_handler('generic', maildir_cleanup_exec)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
def container_post__exec__rspamd__worker_password(self, container_id):
if 'raw' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}):
cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
matched = False
for line in cmd_response.split("\n"):
if '$2$' in line:
hash = line.strip()
hash_out = re.search('\$2\$.+$', hash).group(0)
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
if 'raw' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
rspamd_password_exec = await container.exec(cmd, user='_rspamd')
async with rspamd_password_exec.start(detach=False) as stream:
rspamd_password_return = await stream.read_out()
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
matched = False
if "OK" in rspamd_password_return.data.decode('utf-8'):
matched = True
await container.restart()
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
container.restart()
matched = True
if matched:
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': 'command did not complete'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if matched:
return jsonify(type='success', msg='command completed successfully')
else:
return jsonify(type='danger', msg='command did not complete')
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
def recv_socket_data(c_socket, timeout):
c_socket.setblocking(0)
total_data=[];
data='';
begin=time.time()
while True:
if total_data and time.time()-begin > timeout:
break
elif time.time()-begin > timeout*2:
break
try:
data = c_socket.recv(8192)
if data:
total_data.append(data.decode('utf-8'))
#change the beginning time for measurement
begin=time.time()
else:
#sleep for sometime to indicate a gap
time.sleep(0.1)
break
except:
pass
return ''.join(total_data)
async def exec_run_handler(type, exec_obj):
async with exec_obj.start(detach=False) as stream:
exec_return = await stream.read_out()
try :
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
if not cmd.endswith("\n"):
cmd = cmd + "\n"
socket.send(cmd.encode('utf-8'))
data = recv_socket_data(socket, timeout)
socket.close()
return data
if exec_return == None:
exec_return = ""
else:
exec_return = exec_return.data.decode('utf-8')
except Exception as e:
print("error - exec_cmd_container: %s" % str(e))
traceback.print_exc(file=sys.stdout)
def exec_run_handler(type, output):
if type == 'generic':
if output.exit_code == 0:
return jsonify(type='success', msg='command completed successfully')
if type == 'generic':
exec_details = await exec_obj.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
"type": "success",
"msg": "command completed successfully"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
return jsonify(type='danger', msg='command failed: ' + output.output.decode('utf-8'))
res = {
"type": "success",
"msg": "'command failed: " + exec_return
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only':
r = Response(response=output.output.decode('utf-8'), status=200, mimetype="text/plain")
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r
return Response(content=exec_return, media_type="text/plain")
class GracefulKiller:
kill_now = False
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
async def get_host_stats(wait=5):
global host_stats_isUpdating
def exit_gracefully(self, signum, frame):
self.kill_now = True
def create_self_signed_cert():
process = subprocess.Popen(
"openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout /app/dockerapi_key.pem -out /app/dockerapi_cert.pem -subj /CN=dockerapi/O=mailcow -addext subjectAltName=DNS:dockerapi".split(),
stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=False
)
process.wait()
def startFlaskAPI():
create_self_signed_cert()
try:
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.check_hostname = False
ctx.load_cert_chain(certfile='/app/dockerapi_cert.pem', keyfile='/app/dockerapi_key.pem')
except:
print ("Cannot initialize TLS, retrying in 5s...")
time.sleep(5)
app.run(debug=False, host='0.0.0.0', port=443, threaded=True, ssl_context=ctx)
system_time = datetime.now()
host_stats = {
"cpu": {
"cores": psutil.cpu_count(),
"usage": psutil.cpu_percent()
},
"memory": {
"total": psutil.virtual_memory().total,
"usage": psutil.virtual_memory().percent,
"swap": psutil.swap_memory()
},
"uptime": time.time() - psutil.boot_time(),
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S")
}
api.add_resource(containers_get, '/containers/json')
api.add_resource(container_get, '/containers/<string:container_id>/json')
api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
redis_client.set('host_stats', json.dumps(host_stats), ex=10)
except Exception as e:
res = {
"type": "danger",
"msg": str(e)
}
print(json.dumps(res, indent=4))
if __name__ == '__main__':
api_thread = Thread(target=startFlaskAPI)
api_thread.daemon = True
api_thread.start()
killer = GracefulKiller()
while True:
time.sleep(1)
if killer.kill_now:
break
print ("Stopping dockerapi-mailcow")
await asyncio.sleep(wait)
host_stats_isUpdating = False
async def get_container_stats(container_id, wait=5, stop=False):
global containerIds_to_update
if container_id and container_id.isalnum():
try:
for container in (await async_docker_client.containers.list()):
if container._id == container_id:
res = await container.stats(stream=False)
if redis_client.exists(container_id + '_stats'):
stats = json.loads(redis_client.get(container_id + '_stats'))
else:
stats = []
stats.append(res[0])
if len(stats) > 3:
del stats[0]
redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
except Exception as e:
res = {
"type": "danger",
"msg": str(e)
}
print(json.dumps(res, indent=4))
else:
res = {
"type": "danger",
"msg": "no or invalid id defined"
}
print(json.dumps(res, indent=4))
await asyncio.sleep(wait)
if stop == True:
# update task was called second time, stop
containerIds_to_update.remove(container_id)
else:
# call update task a second time
await get_container_stats(container_id, wait=0, stop=True)
if os.environ['REDIS_SLAVEOF_IP'] != "":
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
else:
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')

View File

@@ -51,8 +51,8 @@ sub sig_handler {
die "sig_handler received signal, preparing to exit...\n";
};
open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>;
open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>;
close $file;
my ($master_user, $master_pass) = split /:/, $creds;
my $sth = $dbh->prepare("SELECT id,
@@ -166,17 +166,11 @@ while ($row = $sth->fetchrow_arrayref()) {
$success = 1;
}
$keep_job_active = 1;
if (defined $exit_status && $exit_status eq "EXIT_AUTHENTICATION_FAILURE_USER1") {
$keep_job_active = 0;
}
$update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ?, active = ? WHERE id = ?");
$update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ? WHERE id = ?");
$update->bind_param( 1, ${stdout} );
$update->bind_param( 2, ${success} );
$update->bind_param( 3, ${exit_status} );
$update->bind_param( 4, ${keep_job_active} );
$update->bind_param( 5, ${id} );
$update->bind_param( 4, ${id} );
$update->execute();
} catch {
$update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?");

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV XTABLES_LIBDIR /usr/lib/xtables

View File

@@ -97,9 +97,9 @@ def refreshF2bregex():
f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
f2bregex[6] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
f2bregex[7] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[8] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
@@ -252,7 +252,7 @@ def permBan(net, unban=False):
if rule not in chain.rules and not unban:
logCrit('Add host/network %s to blacklist' % net)
chain.insert_rule(rule)
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
elif rule in chain.rules and unban:
logCrit('Remove host/network %s from blacklist' % net)
chain.delete_rule(rule)
@@ -267,7 +267,7 @@ def permBan(net, unban=False):
if rule not in chain.rules and not unban:
logCrit('Add host/network %s to blacklist' % net)
chain.insert_rule(rule)
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
elif rule in chain.rules and unban:
logCrit('Remove host/network %s from blacklist' % net)
chain.delete_rule(rule)
@@ -346,6 +346,8 @@ def snat4(snat_target):
rule.dst = '!' + rule.src
target = rule.create_target("SNAT")
target.to_source = snat_target
match = rule.create_match("comment")
match.comment = f'{int(round(time.time()))}'
return rule
while not quit_now:
@@ -356,19 +358,26 @@ def snat4(snat_target):
table.refresh()
chain = iptc.Chain(table, 'POSTROUTING')
table.autocommit = False
if get_snat4_rule() not in chain.rules:
logCrit('Added POSTROUTING rule for source network %s to SNAT target %s' % (get_snat4_rule().src, snat_target))
chain.insert_rule(get_snat4_rule())
table.commit()
else:
for position, item in enumerate(chain.rules):
if item == get_snat4_rule():
if position != 0:
chain.delete_rule(get_snat4_rule())
table.commit()
new_rule = get_snat4_rule()
for position, rule in enumerate(chain.rules):
match = all((
new_rule.get_src() == rule.get_src(),
new_rule.get_dst() == rule.get_dst(),
new_rule.target.parameters == rule.target.parameters,
new_rule.target.name == rule.target.name
))
if position == 0:
if not match:
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
chain.insert_rule(new_rule)
else:
if match:
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
chain.delete_rule(rule)
table.commit()
table.autocommit = True
except:
print('Error running SNAT4, retrying...')
print('Error running SNAT4, retrying...')
def snat6(snat_target):
global lock
@@ -402,7 +411,7 @@ def snat6(snat_target):
table.commit()
table.autocommit = True
except:
print('Error running SNAT6, retrying...')
print('Error running SNAT6, retrying...')
def autopurge():
while not quit_now:
@@ -468,7 +477,7 @@ def whitelistUpdate():
if Counter(new_whitelist) != Counter(WHITELIST):
WHITELIST = new_whitelist
logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
def blacklistUpdate():
global quit_now
@@ -479,7 +488,7 @@ def blacklistUpdate():
new_blacklist = []
if list:
new_blacklist = genNetworkList(list)
if Counter(new_blacklist) != Counter(BLACKLIST):
if Counter(new_blacklist) != Counter(BLACKLIST):
addban = set(new_blacklist).difference(BLACKLIST)
delban = set(BLACKLIST).difference(new_blacklist)
BLACKLIST = new_blacklist
@@ -490,7 +499,7 @@ def blacklistUpdate():
if delban:
for net in delban:
permBan(net=net, unban=True)
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
def initChain():
# Is called before threads start, no locking

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
WORKDIR /app

View File

@@ -1,12 +1,12 @@
FROM php:8.0-fpm-alpine3.16
FROM php:8.1-fpm-alpine3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.21
ENV APCU_PECL 5.1.22
ENV IMAGICK_PECL 3.7.0
# Mailparse is pulled from master branch
#ENV MAILPARSE_PECL 3.0.2
ENV MAILPARSE_PECL 3.1.4
ENV MEMCACHED_PECL 3.2.0
ENV REDIS_PECL 5.3.7
ENV COMPOSER 2.4.4
RUN apk add -U --no-cache autoconf \
aspell-dev \
@@ -18,6 +18,7 @@ RUN apk add -U --no-cache autoconf \
freetype-dev \
g++ \
git \
gettext \
gettext-dev \
gmp-dev \
gnupg \
@@ -27,8 +28,11 @@ RUN apk add -U --no-cache autoconf \
imagemagick-dev \
imap-dev \
jq \
libavif \
libavif-dev \
libjpeg-turbo \
libjpeg-turbo-dev \
libmemcached \
libmemcached-dev \
libpng \
libpng-dev \
@@ -38,7 +42,9 @@ RUN apk add -U --no-cache autoconf \
libtool \
libwebp-dev \
libxml2-dev \
libxpm \
libxpm-dev \
libzip \
libzip-dev \
make \
mysql-client \
@@ -49,22 +55,24 @@ RUN apk add -U --no-cache autoconf \
samba-client \
zlib-dev \
tzdata \
&& git clone https://github.com/php/pecl-mail-mailparse \
&& cd pecl-mail-mailparse \
&& pecl install package.xml \
&& cd .. \
&& rm -r pecl-mail-mailparse \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
&& pecl install mailparse-${MAILPARSE_PECL} \
&& pecl install redis-${REDIS_PECL} \
&& pecl install memcached-${MEMCACHED_PECL} \
&& pecl install APCu-${APCU_PECL} \
&& pecl install imagick-${IMAGICK_PECL} \
&& docker-php-ext-enable apcu imagick memcached mailparse redis \
&& pecl clear-cache \
&& docker-php-ext-configure intl \
&& docker-php-ext-configure exif \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \
--with-jpeg=/usr/include/ \
--with-webp \
--with-xpm \
--with-avif \
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \
&& docker-php-ext-install -j 4 imap \
&& curl --silent --show-error https://getcomposer.org/installer | php \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER} \
&& mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \
&& apk del --purge autoconf \
@@ -72,15 +80,21 @@ RUN apk add -U --no-cache autoconf \
cyrus-sasl-dev \
freetype-dev \
g++ \
gettext-dev \
icu-dev \
imagemagick-dev \
imap-dev \
libavif-dev \
libjpeg-turbo-dev \
libmemcached-dev \
libpng-dev \
libressl-dev \
libwebp-dev \
libxml2-dev \
libxpm-dev \
libzip-dev \
make \
openldap-dev \
pcre-dev \
zlib-dev

View File

@@ -26,6 +26,7 @@ RUN apt-get update && apt-get install -y \
COPY settings.conf /etc/rspamd/settings.conf
COPY metadata_exporter.lua /usr/share/rspamd/plugins/metadata_exporter.lua
COPY set_worker_password.sh /set_worker_password.sh
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@@ -0,0 +1,12 @@
#!/bin/bash
password_file='/etc/rspamd/override.d/worker-controller-password.inc'
password_hash=`/usr/bin/rspamadm pw -e -p $1`
echo 'enable_password = "'$password_hash'";' > $password_file
if grep -q "$password_hash" "$password_file"; then
echo "OK"
else
echo "ERROR"
fi

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16
FROM alpine:3.17
LABEL maintainer "André Peters <andre.peters@servercow.de>"
# Installation

View File

@@ -3,7 +3,6 @@
/.*episerver.*/i
/.*supergewinne.*/i
/List-Unsubscribe.*nbps\.eu/i
/X-Mailer: AWeber.*/i
/.*regiofinder.*/i
/.*EmailSocket.*/i
/List-Unsubscribe:.*respread.*/i

View File

@@ -16,8 +16,7 @@ rules {
backend = "http";
url = "http://nginx:9081/pushover.php";
selector = "mailcow_rcpt";
# Only return msgid, do not parse the full message
formatter = "msgid";
formatter = "json";
meta_headers = true;
}
}

View File

@@ -47,12 +47,14 @@ if (!function_exists('getallheaders')) {
}
$headers = getallheaders();
$json_body = json_decode(file_get_contents('php://input'));
$qid = $headers['X-Rspamd-Qid'];
$rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $headers['X-Rspamd-From'];
$ip = $headers['X-Rspamd-Ip'];
$subject = $headers['X-Rspamd-Subject'];
$messageid= $json_body->message_id;
$priority = 0;
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
@@ -65,6 +67,20 @@ if (is_array($symbols_array)) {
}
}
$sender_address = $json_body->header_from[0];
$sender_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
$sender_address = $matches['address'];
$sender_name = trim($matches['name'], '"\' ');
}
$to_address = $json_body->header_to[0];
$to_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$to_address = $matches['address'];
$to_name = trim($matches['name'], '"\' ');
}
$rcpt_final_mailboxes = array();
// Loop through all rcpts
@@ -229,9 +245,16 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
$post_fields = array(
"token" => $api_data['token'],
"user" => $api_data['key'],
"title" => sprintf("%s", str_replace(array('{SUBJECT}', '{SENDER}'), array($subject, $sender), $title)),
"title" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title)
),
"priority" => $priority,
"message" => sprintf("%s", str_replace(array('{SUBJECT}', '{SENDER}'), array($subject, $sender), $text))
"message" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text)
),
"sound" => $attributes['sound'] ?? "pushover"
);
if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) {
$post_fields['expire'] = 600;

View File

@@ -13,12 +13,12 @@
Please check the logs or contact support if the error persists.</p>
<h2>Quick debugging</h2>
<p>Check Nginx and PHP logs:</p>
<pre>docker-compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
<pre>docker compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
<p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p>
<pre>source mailcow.conf ; docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
<pre>source mailcow.conf ; docker compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
<p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p>
<pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre>
<pre>docker-compose down --volumes ; docker-compose up -d</pre>
<pre>docker compose down --volumes ; docker compose up -d</pre>
<p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p>
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
</body>

View File

@@ -10,9 +10,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
$fido2_data = fido2(array("action" => "get_friendly_names"));
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/admin.js');
$js_minifier->add('/web/js/presets/rspamd.js');
@@ -83,15 +80,12 @@ foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) {
];
}
$template = 'admin.twig';
$template_data = [
'tfa_data' => $tfa_data,
'tfa_id' => @$_SESSION['tfa_id'],
'fido2_cid' => @$_SESSION['fido2_cid'],
'fido2_data' => $fido2_data,
'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'),
'api' => [
'ro' => admin_api('ro', 'get'),
'rw' => admin_api('rw', 'get'),
@@ -112,6 +106,7 @@ $template_data = [
'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -3349,6 +3349,7 @@ paths:
evaluate_x_prio: "0"
key: 21e8918e1jksdjcpis712
only_x_prio: "0"
sound: "pushover"
senders: ""
senders_regex: ""
text: ""
@@ -3392,6 +3393,7 @@ paths:
evaluate_x_prio: "0"
key: 21e8918e1jksdjcpis712
only_x_prio: "0"
sound: "pushover"
senders: ""
senders_regex: ""
text: ""
@@ -3413,6 +3415,9 @@ paths:
only_x_prio:
description: Only send push for prio mails
type: number
sound:
description: Set notification sound
type: string
senders:
description: Only send push for emails from these senders
type: string
@@ -5501,6 +5506,60 @@ paths:
attr:
spam_score: "8,15"
summary: Edit mailbox spam filter score
"/api/v1/get/mailbox/all/{domain}":
get:
parameters:
- description: name of domain
in: path
name: domain
required: false
schema:
type: string
- description: e.g. api-key-string
example: api-key-string
in: header
name: X-API-Key
required: false
schema:
type: string
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- active: "1"
attributes:
force_pw_update: "0"
mailbox_format: "maildir:"
quarantine_notification: never
sogo_access: "1"
tls_enforce_in: "0"
tls_enforce_out: "0"
domain: domain3.tld
is_relayed: 0
local_part: info
max_new_quota: 10737418240
messages: 0
name: Full name
percent_class: success
percent_in_use: 0
quota: 3221225472
quota_used: 0
rl: false
spam_aliases: 0
username: info@domain3.tld
tags: ["tag1", "tag2"]
description: OK
headers: {}
tags:
- Mailboxes
description: You can list all mailboxes existing in system for a specific domain.
operationId: Get mailboxes of a domain
summary: Get mailboxes of a domain
tags:
- name: Domains

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
@media (max-width:1050px){.navbar-header{float:none}.navbar-left,.navbar-nav,.navbar-right{float:none!important}.navbar-toggle{display:block}.navbar-collapse{border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-collapse.collapse{display:none!important}.navbar-nav{margin-top:7.5px}.navbar-nav>li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}}
@media (max-width:1050px){.navbar-header{float:none}.navbar-left,.navbar-nav,.navbar-right{float:none!important}.navbar-toggle{display:block}.navbar-collapse{border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-nav{margin-top:7.5px}.navbar-nav>li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}}

View File

@@ -0,0 +1,487 @@
/*!
* Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2021 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
@-webkit-keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
@-o-keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
@keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
select.bs-select-hidden,
.bootstrap-select > select.bs-select-hidden,
select.selectpicker {
display: none !important;
}
.bootstrap-select {
width: 220px \0;
/*IE9 and below*/
vertical-align: middle;
}
.bootstrap-select > .dropdown-toggle {
position: relative;
width: 100%;
text-align: right;
white-space: nowrap;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.bootstrap-select > .dropdown-toggle:after {
margin-top: -1px;
}
.bootstrap-select > .dropdown-toggle.bs-placeholder,
.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
color: #999;
}
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active {
color: rgba(255, 255, 255, 0.5);
}
.bootstrap-select > select {
position: absolute !important;
bottom: 0;
left: 50%;
display: block !important;
width: 0.5px !important;
height: 100% !important;
padding: 0 !important;
opacity: 0 !important;
border: none;
z-index: 0 !important;
}
.bootstrap-select > select.mobile-device {
top: 0;
left: 0;
display: block !important;
width: 100% !important;
z-index: 2 !important;
}
.has-error .bootstrap-select .dropdown-toggle,
.error .bootstrap-select .dropdown-toggle,
.bootstrap-select.is-invalid .dropdown-toggle,
.was-validated .bootstrap-select select:invalid + .dropdown-toggle {
border-color: #b94a48;
}
.bootstrap-select.is-valid .dropdown-toggle,
.was-validated .bootstrap-select select:valid + .dropdown-toggle {
border-color: #28a745;
}
.bootstrap-select.fit-width {
width: auto !important;
}
.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
width: 220px;
}
.bootstrap-select > select.mobile-device:focus + .dropdown-toggle,
.bootstrap-select .dropdown-toggle:focus {
outline: thin dotted #333333 !important;
outline: 5px auto -webkit-focus-ring-color !important;
outline-offset: -2px;
}
.bootstrap-select.form-control {
margin-bottom: 0;
padding: 0;
border: none;
height: auto;
}
:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) {
width: 100%;
}
.bootstrap-select.form-control.input-group-btn {
float: none;
z-index: auto;
}
.form-inline .bootstrap-select,
.form-inline .bootstrap-select.form-control:not([class*="col-"]) {
width: auto;
}
.bootstrap-select:not(.input-group-btn),
.bootstrap-select[class*="col-"] {
float: none;
display: inline-block;
margin-left: 0;
}
.bootstrap-select.dropdown-menu-right,
.bootstrap-select[class*="col-"].dropdown-menu-right,
.row .bootstrap-select[class*="col-"].dropdown-menu-right {
float: right;
}
.form-inline .bootstrap-select,
.form-horizontal .bootstrap-select,
.form-group .bootstrap-select {
margin-bottom: 0;
}
.form-group-lg .bootstrap-select.form-control,
.form-group-sm .bootstrap-select.form-control {
padding: 0;
}
.form-group-lg .bootstrap-select.form-control .dropdown-toggle,
.form-group-sm .bootstrap-select.form-control .dropdown-toggle {
height: 100%;
font-size: inherit;
line-height: inherit;
border-radius: inherit;
}
.bootstrap-select.form-control-sm .dropdown-toggle,
.bootstrap-select.form-control-lg .dropdown-toggle {
font-size: inherit;
line-height: inherit;
border-radius: inherit;
}
.bootstrap-select.form-control-sm .dropdown-toggle {
padding: 0.25rem 0.5rem;
}
.bootstrap-select.form-control-lg .dropdown-toggle {
padding: 0.5rem 1rem;
}
.form-inline .bootstrap-select .form-control {
width: 100%;
}
.bootstrap-select.disabled,
.bootstrap-select > .disabled {
cursor: not-allowed;
}
.bootstrap-select.disabled:focus,
.bootstrap-select > .disabled:focus {
outline: none !important;
}
.bootstrap-select.bs-container {
position: absolute;
top: 0;
left: 0;
height: 0 !important;
padding: 0 !important;
}
.bootstrap-select.bs-container .dropdown-menu {
z-index: 1060;
}
.bootstrap-select .dropdown-toggle .filter-option {
position: static;
top: 0;
left: 0;
float: left;
height: 100%;
width: 100%;
text-align: left;
overflow: hidden;
-webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
}
.bs3.bootstrap-select .dropdown-toggle .filter-option {
padding-right: inherit;
}
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option {
position: absolute;
padding-top: inherit;
padding-bottom: inherit;
padding-left: inherit;
float: none;
}
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner {
padding-right: inherit;
}
.bootstrap-select .dropdown-toggle .filter-option-inner-inner {
overflow: hidden;
}
.bootstrap-select .dropdown-toggle .filter-expand {
width: 0 !important;
float: left;
opacity: 0 !important;
overflow: hidden;
}
.bootstrap-select .dropdown-toggle .caret {
position: absolute;
top: 50%;
right: 12px;
margin-top: -2px;
vertical-align: middle;
}
.bootstrap-select .dropdown-toggle .bs-select-clear-selected {
position: relative;
display: block;
margin-right: 5px;
text-align: center;
}
.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected {
padding-right: inherit;
}
.bootstrap-select .dropdown-toggle .bs-select-clear-selected span {
position: relative;
top: -webkit-calc(((-1em / 1.5) + 1ex) / 2);
top: calc(((-1em / 1.5) + 1ex) / 2);
pointer-events: none;
}
.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected span {
top: auto;
}
.bootstrap-select .dropdown-toggle.bs-placeholder .bs-select-clear-selected {
display: none;
}
.input-group .bootstrap-select.form-control .dropdown-toggle {
border-radius: inherit;
}
.bootstrap-select[class*="col-"] .dropdown-toggle {
width: 100%;
}
.bootstrap-select .dropdown-menu {
min-width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bootstrap-select .dropdown-menu > .inner:focus {
outline: none !important;
}
.bootstrap-select .dropdown-menu.inner {
position: static;
float: none;
border: 0;
padding: 0;
margin: 0;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.bootstrap-select .dropdown-menu li {
position: relative;
}
.bootstrap-select .dropdown-menu li.active small {
color: rgba(255, 255, 255, 0.5) !important;
}
.bootstrap-select .dropdown-menu li.disabled a {
cursor: not-allowed;
}
.bootstrap-select .dropdown-menu li a {
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.bootstrap-select .dropdown-menu li a.opt {
position: relative;
padding-left: 2.25em;
}
.bootstrap-select .dropdown-menu li a span.check-mark {
display: none;
}
.bootstrap-select .dropdown-menu li a span.text {
display: inline-block;
}
.bootstrap-select .dropdown-menu li small {
padding-left: 0.5em;
}
.bootstrap-select .dropdown-menu .notify {
position: absolute;
bottom: 5px;
width: 96%;
margin: 0 2%;
min-height: 26px;
padding: 3px 5px;
background: #f5f5f5;
border: 1px solid #e3e3e3;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
pointer-events: none;
opacity: 0.9;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bootstrap-select .dropdown-menu .notify.fadeOut {
-webkit-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
-o-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
animation: 300ms linear 750ms forwards bs-notify-fadeOut;
}
.bootstrap-select .no-results {
padding: 3px;
background: #f5f5f5;
margin: 0 5px;
white-space: nowrap;
}
.bootstrap-select.fit-width .dropdown-toggle .filter-option {
position: static;
display: inline;
padding: 0;
}
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner {
display: inline;
}
.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before {
content: '\00a0';
}
.bootstrap-select.fit-width .dropdown-toggle .caret {
position: static;
top: auto;
margin-top: -1px;
}
.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark {
position: absolute;
display: inline-block;
right: 15px;
top: 5px;
}
.bootstrap-select.show-tick .dropdown-menu li a span.text {
margin-right: 34px;
}
.bootstrap-select .bs-ok-default:after {
content: '';
display: block;
width: 0.5em;
height: 1em;
border-style: solid;
border-width: 0 0.26em 0.26em 0;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle {
z-index: 1061;
}
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before {
content: '';
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid rgba(204, 204, 204, 0.2);
position: absolute;
bottom: -4px;
left: 9px;
display: none;
}
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after {
content: '';
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
position: absolute;
bottom: -4px;
left: 10px;
display: none;
}
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before {
bottom: auto;
top: -4px;
border-top: 7px solid rgba(204, 204, 204, 0.2);
border-bottom: 0;
}
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after {
bottom: auto;
top: -4px;
border-top: 6px solid white;
border-bottom: 0;
}
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before {
right: 12px;
left: auto;
}
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after {
right: 13px;
left: auto;
}
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before,
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after {
display: block;
}
.bs-searchbox,
.bs-actionsbox,
.bs-donebutton {
padding: 4px 8px;
}
.bs-actionsbox {
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bs-actionsbox .btn-group {
display: block;
}
.bs-actionsbox .btn-group button {
width: 50%;
}
.bs-donebutton {
float: left;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bs-donebutton .btn-group {
display: block;
}
.bs-donebutton .btn-group button {
width: 100%;
}
.bs-searchbox + .bs-actionsbox {
padding: 0 8px 4px;
}
.bs-searchbox .form-control {
margin-bottom: 0;
width: 100%;
float: none;
}
/*# sourceMappingURL=bootstrap-select.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -1,323 +0,0 @@
table.footable-details,
table.footable > thead > tr.footable-filtering > th div.form-group {
margin-bottom: 0;
}
table.footable,
table.footable-details {
position: relative;
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
table.footable-hide-fouc {
display: none;
}
table > tbody > tr > td > span.footable-toggle {
margin-right: 8px;
opacity: 0.3;
}
table > tbody > tr > td > span.footable-toggle.last-column {
margin-left: 8px;
float: right;
}
table.table-condensed > tbody > tr > td > span.footable-toggle {
margin-right: 5px;
}
table.footable-details > tbody > tr > th:nth-child(1) {
min-width: 40px;
width: 120px;
}
table.footable-details > tbody > tr > td:nth-child(2) {
word-break: break-all;
}
table.footable-details > tbody > tr:first-child > td,
table.footable-details > tbody > tr:first-child > th,
table.footable-details > tfoot > tr:first-child > td,
table.footable-details > tfoot > tr:first-child > th,
table.footable-details > thead > tr:first-child > td,
table.footable-details > thead > tr:first-child > th {
border-top-width: 0;
}
table.footable-details.table-bordered > tbody > tr:first-child > td,
table.footable-details.table-bordered > tbody > tr:first-child > th,
table.footable-details.table-bordered > tfoot > tr:first-child > td,
table.footable-details.table-bordered > tfoot > tr:first-child > th,
table.footable-details.table-bordered > thead > tr:first-child > td,
table.footable-details.table-bordered > thead > tr:first-child > th {
border-top-width: 1px;
}
div.footable-loader {
vertical-align: middle;
text-align: center;
height: 300px;
position: relative;
}
div.footable-loader > span.fooicon {
display: inline-block;
opacity: 0.3;
font-size: 30px;
line-height: 32px;
width: 32px;
height: 32px;
margin-top: -16px;
margin-left: -16px;
position: absolute;
top: 50%;
left: 50%;
-webkit-animation: fooicon-spin-r 2s infinite linear;
animation: fooicon-spin-r 2s infinite linear;
}
table.footable > tbody > tr.footable-empty > td {
vertical-align: middle;
text-align: center;
font-size: 30px;
}
table.footable > tbody > tr > td,
table.footable > tbody > tr > th {
display: none;
}
table.footable > tbody > tr.footable-detail-row > td,
table.footable > tbody > tr.footable-detail-row > th,
table.footable > tbody > tr.footable-empty > td,
table.footable > tbody > tr.footable-empty > th {
display: table-cell;
}
@-webkit-keyframes fooicon-spin-r {
0% {
-webkit-transform: rotate(0);
transform: rotate(0);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fooicon-spin-r {
0% {
-webkit-transform: rotate(0);
transform: rotate(0);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
.fooicon {
position: relative;
top: 0px;
display: inline-block;
font-family: "bootstrap-icons" !important;
font-style: normal;
font-weight: 400;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@-moz-document url-prefix() {
.fooicon {
top: 2px;
}
}
.fooicon:after,
.fooicon:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.fooicon-loader:before {
content: "\f130";
}
.fooicon-plus:before {
content: "\f4fc";
}
.fooicon-minus:before {
content: "\f2e8";
}
.fooicon-search:before {
content: "\f52a";
}
.fooicon-remove:before {
content: "\f62a";
}
.fooicon-sort:before {
content: "\f3c6";
}
.fooicon-sort-asc:before {
content: "\f575";
}
.fooicon-sort-desc:before {
content: "\f57b";
}
.fooicon-pencil:before {
content: "\f4c9";
}
.fooicon-trash:before {
content: "\f62a";
}
.fooicon-eye-close:before {
content: "\f33f";
}
.fooicon-flash:before {
content: "\f46e";
}
.fooicon-cog:before {
content: "\f3e2";
}
.fooicon-stats:before {
content: "\f359";
}
table.footable > thead > tr.footable-filtering > th {
border-bottom-width: 1px;
font-weight: 400;
}
.footable-filtering-external.footable-filtering-right,
table.footable.footable-filtering-right > thead > tr.footable-filtering > th,
table.footable > thead > tr.footable-filtering > th {
text-align: right;
}
.footable-filtering-external.footable-filtering-left,
table.footable.footable-filtering-left > thead > tr.footable-filtering > th {
text-align: left;
}
.footable-filtering-external.footable-filtering-center,
.footable-paging-external.footable-paging-center,
table.footable-paging-center > tfoot > tr.footable-paging > td,
table.footable.footable-filtering-center > thead > tr.footable-filtering > th,
table.footable > tfoot > tr.footable-paging > td {
text-align: center;
}
table.footable > thead > tr.footable-filtering > th div.form-group + div.form-group {
margin-top: 5px;
}
table.footable > thead > tr.footable-filtering > th div.input-group {
width: 100%;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox,
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox {
margin: 0;
display: block;
position: relative;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox > label,
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox > label {
display: block;
padding-left: 20px;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox input[type="checkbox"],
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox input[type="checkbox"] {
position: absolute;
margin-left: -20px;
}
@media (min-width: 768px) {
table.footable > thead > tr.footable-filtering > th div.input-group {
width: auto;
}
table.footable > thead > tr.footable-filtering > th div.form-group {
margin-left: 2px;
margin-right: 2px;
}
table.footable > thead > tr.footable-filtering > th div.form-group + div.form-group {
margin-top: 0;
}
}
table.footable > tbody > tr > td.footable-sortable,
table.footable > tbody > tr > th.footable-sortable,
table.footable > tfoot > tr > td.footable-sortable,
table.footable > tfoot > tr > th.footable-sortable,
table.footable > thead > tr > td.footable-sortable,
table.footable > thead > tr > th.footable-sortable {
position: relative;
padding-right: 30px;
cursor: pointer;
}
td.footable-sortable > span.fooicon,
th.footable-sortable > span.fooicon {
position: absolute;
right: 6px;
top: 50%;
margin-top: -7px;
opacity: 0;
transition: opacity 0.3s ease-in;
}
td.footable-sortable.footable-asc > span.fooicon,
td.footable-sortable.footable-desc > span.fooicon,
td.footable-sortable:hover > span.fooicon,
th.footable-sortable.footable-asc > span.fooicon,
th.footable-sortable.footable-desc > span.fooicon,
th.footable-sortable:hover > span.fooicon {
opacity: 1;
}
table.footable-sorting-disabled td.footable-sortable.footable-asc > span.fooicon,
table.footable-sorting-disabled td.footable-sortable.footable-desc > span.fooicon,
table.footable-sorting-disabled td.footable-sortable:hover > span.fooicon,
table.footable-sorting-disabled th.footable-sortable.footable-asc > span.fooicon,
table.footable-sorting-disabled th.footable-sortable.footable-desc > span.fooicon,
table.footable-sorting-disabled th.footable-sortable:hover > span.fooicon {
opacity: 0;
visibility: hidden;
}
.footable-paging-external ul.pagination,
table.footable > tfoot > tr.footable-paging > td > ul.pagination {
margin: 10px 0 0;
}
.footable-paging-external span.label,
table.footable > tfoot > tr.footable-paging > td > span.label {
display: inline-block;
margin: 0 0 10px;
padding: 4px 10px;
}
.footable-paging-external.footable-paging-left,
table.footable-paging-left > tfoot > tr.footable-paging > td {
text-align: left;
}
.footable-paging-external.footable-paging-right,
table.footable-editing-right td.footable-editing,
table.footable-editing-right tr.footable-editing,
table.footable-paging-right > tfoot > tr.footable-paging > td {
text-align: right;
}
ul.pagination > li.footable-page {
display: none;
}
ul.pagination > li.footable-page.visible {
display: inline;
}
td.footable-editing {
width: 90px;
max-width: 90px;
}
table.footable-editing-no-delete td.footable-editing,
table.footable-editing-no-edit td.footable-editing,
table.footable-editing-no-view td.footable-editing {
width: 70px;
max-width: 70px;
}
table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-view td.footable-editing {
width: 50px;
max-width: 50px;
}
table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing {
width: 0;
max-width: 0;
display: none !important;
}
table.footable-editing-left td.footable-editing,
table.footable-editing-left tr.footable-editing {
text-align: left;
}
table.footable-editing button.footable-add,
table.footable-editing button.footable-hide,
table.footable-editing-show button.footable-show,
table.footable-editing.footable-editing-always-show button.footable-hide,
table.footable-editing.footable-editing-always-show button.footable-show,
table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing {
display: none;
}
table.footable-editing.footable-editing-always-show button.footable-add,
table.footable-editing.footable-editing-show button.footable-add,
table.footable-editing.footable-editing-show button.footable-hide {
display: inline-block;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,687 @@
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0
*
* Included libraries:
* DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0
*/
@charset "UTF-8";
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
}
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
table.dataTable thead > tr > td.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting_asc_disabled,
table.dataTable thead > tr > td.sorting_desc_disabled {
cursor: pointer;
position: relative;
padding-right: 26px;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
position: absolute;
display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.8em;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "▲";
}
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%;
content: "▼";
}
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:after {
opacity: 0.6;
}
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before {
display: none;
}
table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after,
div.dataTables_scrollBody table.dataTable thead > tr > td:before,
div.dataTables_scrollBody table.dataTable thead > tr > td:after {
display: none;
}
div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 2px;
}
div.dataTables_processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
div.dataTables_processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
height: 13px;
border-radius: 50%;
background: rgba(13, 110, 253, 0.9);
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
div.dataTables_processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@keyframes datatables-loader-1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes datatables-loader-3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes datatables-loader-2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
table.dataTable tfoot td {
text-align: left;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
/*! Bootstrap 5 integration for DataTables
*
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
table.dataTable {
clear: both;
margin-top: 6px !important;
margin-bottom: 6px !important;
max-width: none !important;
border-collapse: separate !important;
border-spacing: 0;
}
table.dataTable td,
table.dataTable th {
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
table.dataTable > tbody > tr {
background-color: transparent;
}
table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable > tbody > tr.selected a {
color: #090a0b;
}
table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05);
}
table.dataTable.table-striped > tbody > tr.odd.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.075);
}
table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
}
div.dataTables_wrapper div.dataTables_length label {
font-weight: normal;
text-align: left;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_length select {
width: auto;
display: inline-block;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: right;
}
div.dataTables_wrapper div.dataTables_filter label {
font-weight: normal;
white-space: nowrap;
text-align: left;
}
div.dataTables_wrapper div.dataTables_filter input {
margin-left: 0.5em;
display: inline-block;
width: auto;
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 0.85em;
}
div.dataTables_wrapper div.dataTables_paginate {
margin: 0;
white-space: nowrap;
text-align: right;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin: 2px 0;
white-space: nowrap;
justify-content: flex-end;
}
div.dataTables_wrapper div.dt-row {
position: relative;
}
div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table > thead .sorting:before,
div.dataTables_scrollBody > table > thead .sorting_asc:before,
div.dataTables_scrollBody > table > thead .sorting_desc:before,
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
}
div.dataTables_scrollBody > table > tbody tr:first-child th,
div.dataTables_scrollBody > table > tbody tr:first-child td {
border-top: none;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
box-sizing: content-box;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
margin-top: 0 !important;
border-top: none;
}
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
text-align: center;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
justify-content: center !important;
}
}
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px;
}
table.table-bordered.dataTable {
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
}
div.dataTables_scrollHead table.table-bordered {
border-bottom-width: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
padding-left: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
padding-right: 0;
}
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 {
cursor: default !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
display: none !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
position: relative;
padding-left: 30px;
cursor: pointer;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
top: 50%;
left: 5px;
height: 1em;
width: 1em;
margin-top: -9px;
display: block;
position: absolute;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #0d6efd;
}
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before {
content: "-";
background-color: #d33333;
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control {
padding-left: 27px;
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control:before {
left: 4px;
height: 14px;
width: 14px;
border-radius: 14px;
line-height: 14px;
text-indent: 3px;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control,
table.dataTable.dtr-column > tbody > tr > th.dtr-control,
table.dataTable.dtr-column > tbody > tr > td.control,
table.dataTable.dtr-column > tbody > tr > th.control {
position: relative;
cursor: pointer;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > td.control:before,
table.dataTable.dtr-column > tbody > tr > th.control:before {
top: 50%;
left: 50%;
height: 0.8em;
width: 0.8em;
margin-top: -0.5em;
margin-left: -0.5em;
display: block;
position: absolute;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #0d6efd;
}
table.dataTable.dtr-column > tbody > tr.parent td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent td.control:before,
table.dataTable.dtr-column > tbody > tr.parent th.control:before {
content: "-";
background-color: #d33333;
}
table.dataTable > tbody > tr.child {
padding: 0.5em 1em;
}
table.dataTable > tbody > tr.child:hover {
background: transparent !important;
}
table.dataTable > tbody > tr.child ul.dtr-details {
display: inline-block;
list-style-type: none;
margin: 0;
padding: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li {
border-bottom: 1px solid #efefef;
padding: 0.5em 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:first-child {
padding-top: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child {
border-bottom: none;
}
table.dataTable > tbody > tr.child span.dtr-title {
display: inline-block;
min-width: 75px;
font-weight: bold;
}
div.dtr-modal {
position: fixed;
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 100;
padding: 10em 1em;
}
div.dtr-modal div.dtr-modal-display {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 50%;
height: 50%;
overflow: auto;
margin: auto;
z-index: 102;
overflow: auto;
background-color: #f5f5f7;
border: 1px solid black;
border-radius: 0.5em;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}
div.dtr-modal div.dtr-modal-content {
position: relative;
padding: 1em;
}
div.dtr-modal div.dtr-modal-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border: 1px solid #eaeaea;
background-color: #f9f9f9;
text-align: center;
border-radius: 3px;
cursor: pointer;
z-index: 12;
}
div.dtr-modal div.dtr-modal-close:hover {
background-color: #eaeaea;
}
div.dtr-modal div.dtr-modal-background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
background: rgba(0, 0, 0, 0.6);
}
@media screen and (max-width: 767px) {
div.dtr-modal div.dtr-modal-display {
width: 95%;
}
}
div.dtr-bs-modal table.table tr:first-child td {
border-top: none;
}
table.dataTable.table-bordered th.dtr-control.dtr-hidden + *,
table.dataTable.table-bordered td.dtr-control.dtr-hidden + * {
border-left-width: 1px;
}
table.dataTable > tbody > tr > .selected {
background-color: rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable > tbody > tr > td.select-checkbox,
table.dataTable > tbody > tr > th.select-checkbox {
position: relative;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after,
table.dataTable > tbody > tr > th.select-checkbox:before,
table.dataTable > tbody > tr > th.select-checkbox:after {
display: block;
position: absolute;
top: 1.2em;
left: 50%;
width: 12px;
height: 12px;
box-sizing: border-box;
}
table.dataTable > tbody > tr > td.select-checkbox:before,
table.dataTable > tbody > tr > th.select-checkbox:before {
content: " ";
margin-top: -5px;
margin-left: -6px;
border: 1px solid black;
border-radius: 3px;
}
table.dataTable > tbody > tr.selected > td.select-checkbox:before,
table.dataTable > tbody > tr.selected > th.select-checkbox:before {
border: 1px solid white;
}
table.dataTable > tbody > tr.selected > td.select-checkbox:after,
table.dataTable > tbody > tr.selected > th.select-checkbox:after {
content: "✓";
font-size: 20px;
margin-top: -19px;
margin-left: -6px;
text-align: center;
text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;
}
table.dataTable.compact > tbody > tr > td.select-checkbox:before,
table.dataTable.compact > tbody > tr > th.select-checkbox:before {
margin-top: -12px;
}
table.dataTable.compact > tbody > tr.selected > td.select-checkbox:after,
table.dataTable.compact > tbody > tr.selected > th.select-checkbox:after {
margin-top: -16px;
}
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0.5em;
}
@media screen and (max-width: 640px) {
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0;
display: block;
}
}
table.dataTable.table-sm tbody td.select-checkbox::before {
margin-top: -9px;
}

View File

@@ -1 +0,0 @@
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}

View File

@@ -1,7 +1,7 @@
@font-face {
font-family: "bootstrap-icons";
src: url("/fonts/bootstrap-icons.woff2?45695e8b569b2b0178db2713ca47065c") format("woff2"),
url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff");
src: url("/fonts/bootstrap-icons.woff2?524846017b983fc8ded9325d94ed40f3") format("woff2"),
url("/fonts/bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff");
}
.bi::before,
@@ -19,6 +19,7 @@ url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff
-moz-osx-font-smoothing: grayscale;
}
.bi-123::before { content: "\f67f"; }
.bi-alarm-fill::before { content: "\f101"; }
.bi-alarm::before { content: "\f102"; }
.bi-align-bottom::before { content: "\f103"; }
@@ -1425,3 +1426,279 @@ url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff
.bi-webcam-fill::before { content: "\f67c"; }
.bi-webcam::before { content: "\f67d"; }
.bi-yin-yang::before { content: "\f67e"; }
.bi-bandaid-fill::before { content: "\f680"; }
.bi-bandaid::before { content: "\f681"; }
.bi-bluetooth::before { content: "\f682"; }
.bi-body-text::before { content: "\f683"; }
.bi-boombox::before { content: "\f684"; }
.bi-boxes::before { content: "\f685"; }
.bi-dpad-fill::before { content: "\f686"; }
.bi-dpad::before { content: "\f687"; }
.bi-ear-fill::before { content: "\f688"; }
.bi-ear::before { content: "\f689"; }
.bi-envelope-check-1::before { content: "\f68a"; }
.bi-envelope-check-fill::before { content: "\f68b"; }
.bi-envelope-check::before { content: "\f68c"; }
.bi-envelope-dash-1::before { content: "\f68d"; }
.bi-envelope-dash-fill::before { content: "\f68e"; }
.bi-envelope-dash::before { content: "\f68f"; }
.bi-envelope-exclamation-1::before { content: "\f690"; }
.bi-envelope-exclamation-fill::before { content: "\f691"; }
.bi-envelope-exclamation::before { content: "\f692"; }
.bi-envelope-plus-fill::before { content: "\f693"; }
.bi-envelope-plus::before { content: "\f694"; }
.bi-envelope-slash-1::before { content: "\f695"; }
.bi-envelope-slash-fill::before { content: "\f696"; }
.bi-envelope-slash::before { content: "\f697"; }
.bi-envelope-x-1::before { content: "\f698"; }
.bi-envelope-x-fill::before { content: "\f699"; }
.bi-envelope-x::before { content: "\f69a"; }
.bi-explicit-fill::before { content: "\f69b"; }
.bi-explicit::before { content: "\f69c"; }
.bi-git::before { content: "\f69d"; }
.bi-infinity::before { content: "\f69e"; }
.bi-list-columns-reverse::before { content: "\f69f"; }
.bi-list-columns::before { content: "\f6a0"; }
.bi-meta::before { content: "\f6a1"; }
.bi-mortorboard-fill::before { content: "\f6a2"; }
.bi-mortorboard::before { content: "\f6a3"; }
.bi-nintendo-switch::before { content: "\f6a4"; }
.bi-pc-display-horizontal::before { content: "\f6a5"; }
.bi-pc-display::before { content: "\f6a6"; }
.bi-pc-horizontal::before { content: "\f6a7"; }
.bi-pc::before { content: "\f6a8"; }
.bi-playstation::before { content: "\f6a9"; }
.bi-plus-slash-minus::before { content: "\f6aa"; }
.bi-projector-fill::before { content: "\f6ab"; }
.bi-projector::before { content: "\f6ac"; }
.bi-qr-code-scan::before { content: "\f6ad"; }
.bi-qr-code::before { content: "\f6ae"; }
.bi-quora::before { content: "\f6af"; }
.bi-quote::before { content: "\f6b0"; }
.bi-robot::before { content: "\f6b1"; }
.bi-send-check-fill::before { content: "\f6b2"; }
.bi-send-check::before { content: "\f6b3"; }
.bi-send-dash-fill::before { content: "\f6b4"; }
.bi-send-dash::before { content: "\f6b5"; }
.bi-send-exclamation-1::before { content: "\f6b6"; }
.bi-send-exclamation-fill::before { content: "\f6b7"; }
.bi-send-exclamation::before { content: "\f6b8"; }
.bi-send-fill::before { content: "\f6b9"; }
.bi-send-plus-fill::before { content: "\f6ba"; }
.bi-send-plus::before { content: "\f6bb"; }
.bi-send-slash-fill::before { content: "\f6bc"; }
.bi-send-slash::before { content: "\f6bd"; }
.bi-send-x-fill::before { content: "\f6be"; }
.bi-send-x::before { content: "\f6bf"; }
.bi-send::before { content: "\f6c0"; }
.bi-steam::before { content: "\f6c1"; }
.bi-terminal-dash-1::before { content: "\f6c2"; }
.bi-terminal-dash::before { content: "\f6c3"; }
.bi-terminal-plus::before { content: "\f6c4"; }
.bi-terminal-split::before { content: "\f6c5"; }
.bi-ticket-detailed-fill::before { content: "\f6c6"; }
.bi-ticket-detailed::before { content: "\f6c7"; }
.bi-ticket-fill::before { content: "\f6c8"; }
.bi-ticket-perforated-fill::before { content: "\f6c9"; }
.bi-ticket-perforated::before { content: "\f6ca"; }
.bi-ticket::before { content: "\f6cb"; }
.bi-tiktok::before { content: "\f6cc"; }
.bi-window-dash::before { content: "\f6cd"; }
.bi-window-desktop::before { content: "\f6ce"; }
.bi-window-fullscreen::before { content: "\f6cf"; }
.bi-window-plus::before { content: "\f6d0"; }
.bi-window-split::before { content: "\f6d1"; }
.bi-window-stack::before { content: "\f6d2"; }
.bi-window-x::before { content: "\f6d3"; }
.bi-xbox::before { content: "\f6d4"; }
.bi-ethernet::before { content: "\f6d5"; }
.bi-hdmi-fill::before { content: "\f6d6"; }
.bi-hdmi::before { content: "\f6d7"; }
.bi-usb-c-fill::before { content: "\f6d8"; }
.bi-usb-c::before { content: "\f6d9"; }
.bi-usb-fill::before { content: "\f6da"; }
.bi-usb-plug-fill::before { content: "\f6db"; }
.bi-usb-plug::before { content: "\f6dc"; }
.bi-usb-symbol::before { content: "\f6dd"; }
.bi-usb::before { content: "\f6de"; }
.bi-boombox-fill::before { content: "\f6df"; }
.bi-displayport-1::before { content: "\f6e0"; }
.bi-displayport::before { content: "\f6e1"; }
.bi-gpu-card::before { content: "\f6e2"; }
.bi-memory::before { content: "\f6e3"; }
.bi-modem-fill::before { content: "\f6e4"; }
.bi-modem::before { content: "\f6e5"; }
.bi-motherboard-fill::before { content: "\f6e6"; }
.bi-motherboard::before { content: "\f6e7"; }
.bi-optical-audio-fill::before { content: "\f6e8"; }
.bi-optical-audio::before { content: "\f6e9"; }
.bi-pci-card::before { content: "\f6ea"; }
.bi-router-fill::before { content: "\f6eb"; }
.bi-router::before { content: "\f6ec"; }
.bi-ssd-fill::before { content: "\f6ed"; }
.bi-ssd::before { content: "\f6ee"; }
.bi-thunderbolt-fill::before { content: "\f6ef"; }
.bi-thunderbolt::before { content: "\f6f0"; }
.bi-usb-drive-fill::before { content: "\f6f1"; }
.bi-usb-drive::before { content: "\f6f2"; }
.bi-usb-micro-fill::before { content: "\f6f3"; }
.bi-usb-micro::before { content: "\f6f4"; }
.bi-usb-mini-fill::before { content: "\f6f5"; }
.bi-usb-mini::before { content: "\f6f6"; }
.bi-cloud-haze2::before { content: "\f6f7"; }
.bi-device-hdd-fill::before { content: "\f6f8"; }
.bi-device-hdd::before { content: "\f6f9"; }
.bi-device-ssd-fill::before { content: "\f6fa"; }
.bi-device-ssd::before { content: "\f6fb"; }
.bi-displayport-fill::before { content: "\f6fc"; }
.bi-mortarboard-fill::before { content: "\f6fd"; }
.bi-mortarboard::before { content: "\f6fe"; }
.bi-terminal-x::before { content: "\f6ff"; }
.bi-arrow-through-heart-fill::before { content: "\f700"; }
.bi-arrow-through-heart::before { content: "\f701"; }
.bi-badge-sd-fill::before { content: "\f702"; }
.bi-badge-sd::before { content: "\f703"; }
.bi-bag-heart-fill::before { content: "\f704"; }
.bi-bag-heart::before { content: "\f705"; }
.bi-balloon-fill::before { content: "\f706"; }
.bi-balloon-heart-fill::before { content: "\f707"; }
.bi-balloon-heart::before { content: "\f708"; }
.bi-balloon::before { content: "\f709"; }
.bi-box2-fill::before { content: "\f70a"; }
.bi-box2-heart-fill::before { content: "\f70b"; }
.bi-box2-heart::before { content: "\f70c"; }
.bi-box2::before { content: "\f70d"; }
.bi-braces-asterisk::before { content: "\f70e"; }
.bi-calendar-heart-fill::before { content: "\f70f"; }
.bi-calendar-heart::before { content: "\f710"; }
.bi-calendar2-heart-fill::before { content: "\f711"; }
.bi-calendar2-heart::before { content: "\f712"; }
.bi-chat-heart-fill::before { content: "\f713"; }
.bi-chat-heart::before { content: "\f714"; }
.bi-chat-left-heart-fill::before { content: "\f715"; }
.bi-chat-left-heart::before { content: "\f716"; }
.bi-chat-right-heart-fill::before { content: "\f717"; }
.bi-chat-right-heart::before { content: "\f718"; }
.bi-chat-square-heart-fill::before { content: "\f719"; }
.bi-chat-square-heart::before { content: "\f71a"; }
.bi-clipboard-check-fill::before { content: "\f71b"; }
.bi-clipboard-data-fill::before { content: "\f71c"; }
.bi-clipboard-fill::before { content: "\f71d"; }
.bi-clipboard-heart-fill::before { content: "\f71e"; }
.bi-clipboard-heart::before { content: "\f71f"; }
.bi-clipboard-minus-fill::before { content: "\f720"; }
.bi-clipboard-plus-fill::before { content: "\f721"; }
.bi-clipboard-pulse::before { content: "\f722"; }
.bi-clipboard-x-fill::before { content: "\f723"; }
.bi-clipboard2-check-fill::before { content: "\f724"; }
.bi-clipboard2-check::before { content: "\f725"; }
.bi-clipboard2-data-fill::before { content: "\f726"; }
.bi-clipboard2-data::before { content: "\f727"; }
.bi-clipboard2-fill::before { content: "\f728"; }
.bi-clipboard2-heart-fill::before { content: "\f729"; }
.bi-clipboard2-heart::before { content: "\f72a"; }
.bi-clipboard2-minus-fill::before { content: "\f72b"; }
.bi-clipboard2-minus::before { content: "\f72c"; }
.bi-clipboard2-plus-fill::before { content: "\f72d"; }
.bi-clipboard2-plus::before { content: "\f72e"; }
.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
.bi-clipboard2-pulse::before { content: "\f730"; }
.bi-clipboard2-x-fill::before { content: "\f731"; }
.bi-clipboard2-x::before { content: "\f732"; }
.bi-clipboard2::before { content: "\f733"; }
.bi-emoji-kiss-fill::before { content: "\f734"; }
.bi-emoji-kiss::before { content: "\f735"; }
.bi-envelope-heart-fill::before { content: "\f736"; }
.bi-envelope-heart::before { content: "\f737"; }
.bi-envelope-open-heart-fill::before { content: "\f738"; }
.bi-envelope-open-heart::before { content: "\f739"; }
.bi-envelope-paper-fill::before { content: "\f73a"; }
.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
.bi-envelope-paper-heart::before { content: "\f73c"; }
.bi-envelope-paper::before { content: "\f73d"; }
.bi-filetype-aac::before { content: "\f73e"; }
.bi-filetype-ai::before { content: "\f73f"; }
.bi-filetype-bmp::before { content: "\f740"; }
.bi-filetype-cs::before { content: "\f741"; }
.bi-filetype-css::before { content: "\f742"; }
.bi-filetype-csv::before { content: "\f743"; }
.bi-filetype-doc::before { content: "\f744"; }
.bi-filetype-docx::before { content: "\f745"; }
.bi-filetype-exe::before { content: "\f746"; }
.bi-filetype-gif::before { content: "\f747"; }
.bi-filetype-heic::before { content: "\f748"; }
.bi-filetype-html::before { content: "\f749"; }
.bi-filetype-java::before { content: "\f74a"; }
.bi-filetype-jpg::before { content: "\f74b"; }
.bi-filetype-js::before { content: "\f74c"; }
.bi-filetype-jsx::before { content: "\f74d"; }
.bi-filetype-key::before { content: "\f74e"; }
.bi-filetype-m4p::before { content: "\f74f"; }
.bi-filetype-md::before { content: "\f750"; }
.bi-filetype-mdx::before { content: "\f751"; }
.bi-filetype-mov::before { content: "\f752"; }
.bi-filetype-mp3::before { content: "\f753"; }
.bi-filetype-mp4::before { content: "\f754"; }
.bi-filetype-otf::before { content: "\f755"; }
.bi-filetype-pdf::before { content: "\f756"; }
.bi-filetype-php::before { content: "\f757"; }
.bi-filetype-png::before { content: "\f758"; }
.bi-filetype-ppt-1::before { content: "\f759"; }
.bi-filetype-ppt::before { content: "\f75a"; }
.bi-filetype-psd::before { content: "\f75b"; }
.bi-filetype-py::before { content: "\f75c"; }
.bi-filetype-raw::before { content: "\f75d"; }
.bi-filetype-rb::before { content: "\f75e"; }
.bi-filetype-sass::before { content: "\f75f"; }
.bi-filetype-scss::before { content: "\f760"; }
.bi-filetype-sh::before { content: "\f761"; }
.bi-filetype-svg::before { content: "\f762"; }
.bi-filetype-tiff::before { content: "\f763"; }
.bi-filetype-tsx::before { content: "\f764"; }
.bi-filetype-ttf::before { content: "\f765"; }
.bi-filetype-txt::before { content: "\f766"; }
.bi-filetype-wav::before { content: "\f767"; }
.bi-filetype-woff::before { content: "\f768"; }
.bi-filetype-xls-1::before { content: "\f769"; }
.bi-filetype-xls::before { content: "\f76a"; }
.bi-filetype-xml::before { content: "\f76b"; }
.bi-filetype-yml::before { content: "\f76c"; }
.bi-heart-arrow::before { content: "\f76d"; }
.bi-heart-pulse-fill::before { content: "\f76e"; }
.bi-heart-pulse::before { content: "\f76f"; }
.bi-heartbreak-fill::before { content: "\f770"; }
.bi-heartbreak::before { content: "\f771"; }
.bi-hearts::before { content: "\f772"; }
.bi-hospital-fill::before { content: "\f773"; }
.bi-hospital::before { content: "\f774"; }
.bi-house-heart-fill::before { content: "\f775"; }
.bi-house-heart::before { content: "\f776"; }
.bi-incognito::before { content: "\f777"; }
.bi-magnet-fill::before { content: "\f778"; }
.bi-magnet::before { content: "\f779"; }
.bi-person-heart::before { content: "\f77a"; }
.bi-person-hearts::before { content: "\f77b"; }
.bi-phone-flip::before { content: "\f77c"; }
.bi-plugin::before { content: "\f77d"; }
.bi-postage-fill::before { content: "\f77e"; }
.bi-postage-heart-fill::before { content: "\f77f"; }
.bi-postage-heart::before { content: "\f780"; }
.bi-postage::before { content: "\f781"; }
.bi-postcard-fill::before { content: "\f782"; }
.bi-postcard-heart-fill::before { content: "\f783"; }
.bi-postcard-heart::before { content: "\f784"; }
.bi-postcard::before { content: "\f785"; }
.bi-search-heart-fill::before { content: "\f786"; }
.bi-search-heart::before { content: "\f787"; }
.bi-sliders2-vertical::before { content: "\f788"; }
.bi-sliders2::before { content: "\f789"; }
.bi-trash3-fill::before { content: "\f78a"; }
.bi-trash3::before { content: "\f78b"; }
.bi-valentine::before { content: "\f78c"; }
.bi-valentine2::before { content: "\f78d"; }
.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
.bi-wrench-adjustable-circle::before { content: "\f78f"; }
.bi-wrench-adjustable::before { content: "\f790"; }
.bi-filetype-json::before { content: "\f791"; }
.bi-filetype-pptx::before { content: "\f792"; }
.bi-filetype-xlsx::before { content: "\f793"; }

View File

@@ -63,6 +63,17 @@
.navbar-nav {
margin: 0;
}
.navbar-nav .nav-item {
flex-direction: column;
display: flex;
padding: 0 10px !important;
}
.navbar-nav .nav-link {
height: 44px;
display: flex;
align-items: center;
padding: 0 10px !important;
}
.navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-top .navbar-collapse {
max-height: 1000px
@@ -75,6 +86,12 @@
display: inline-block;
font-size: inherit;
}
.btn-group-xs > .btn, .btn-xs {
padding: .25rem .4rem;
font-size: .875rem;
line-height: 1rem;
border-radius: .2rem;
}
.icon-spin {
animation-name: spin;
animation-duration: 2000ms;
@@ -105,13 +122,22 @@
transform: rotate(359deg);
}
}
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
.footable-sortable {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
@keyframes blink {
50% {
color: transparent
}
}
.loader-dot {
animation: 1s blink infinite
}
.loader-dot:nth-child(2) {
animation-delay: 250ms
}
.loader-dot:nth-child(3) {
animation-delay: 500ms
}
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
/* Fix modal moving content left */
body.modal-open {
overflow: inherit;
@@ -166,19 +192,11 @@ legend {
top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.7;
}
#top {
padding-top: 70px;
}
.bootstrap-select.btn-group .no-results {
display: none;
}
.dropdown-desc {
display: block;
padding: 3px 10px;
clear: both;
font-weight: bold;
color: #5a5a5a;
white-space: nowrap;
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(197, 197, 197) !important;
}
.haveibeenpwned {
cursor: pointer;
@@ -206,7 +224,7 @@ legend {
flex-direction: column;
}
.footer .version {
margin-left: auto;
margin-left: auto;
margin-top: 20px;
}
.slave-info {
@@ -225,15 +243,8 @@ legend {
.btn-input-missing:active:hover,
.btn-input-missing:active:focus {
color: #000 !important;
background-color: #ff4136;
border-color: #ff291c;
}
table.footable>tbody>tr.footable-empty>td {
font-style:italic;
font-size: 1rem;
}
table>tbody>tr>td>span.footable-toggle {
opacity: 0.75;
background-color: #ff2f24 !important;
border-color: #e21207 !important;
}
.navbar-nav > li {
font-size: 1rem !important;
@@ -260,18 +271,11 @@ code {
margin-right: 5px;
}
.list-group-item.webauthn-authenticator-selection,
.list-group-item.totp-authenticator-selection,
.list-group-item.yubi_otp-authenticator-selection {
border-radius: 0px !important;
}
.pending-tfa-collapse {
padding: 10px;
background: #fbfbfb;
border: 1px solid #ededed;
min-height: 110px;
.dropdown-header {
font-weight: 600;
}
.tag-box {
display: flex;
flex-wrap: wrap;
@@ -295,7 +299,7 @@ code {
}
.tag-input {
margin-left: 10px;
border: 0;
border: 0 !important;
flex: 1;
height: 24px;
min-width: 150px;
@@ -308,3 +312,61 @@ code {
align-items: center;
display: inline-flex;
}
#dnstable {
overflow-x: auto!important;
}
.well {
border: 1px solid #dfdfdf;
background-color: #f9f9f9;
padding: 10px;
}
.btn-check-label {
color: #555;
}
.caret {
transform: rotate(0deg);
}
a[aria-expanded='true'] > .caret,
button[aria-expanded='true'] > .caret {
transform: rotate(-180deg);
}
.list-group-details {
background: #fff;
}
.list-group-header {
background: #f7f7f7;
}
.bg-primary, .alert-primary, .btn-primary {
background-color: #0F688D !important;
border-color: #0d526d !important;
}
.bg-info, .alert-info, .btn-info {
background-color: #148DBC !important;
border-color: #127ea8 !important;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(137 137 137)!important;
}
.progress {
background-color: #d5d5d5;
}
.btn-outline-secondary:hover {
background-color: #f0f0f0;
}
.btn.btn-outline-secondary {
border-color: #cfcfcf !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;
}

View File

@@ -1,7 +1,3 @@
.space20 {
margin-bottom: 20px;
}
.btn-xs-lg>.lang-sm:after {
margin-left: 4px;
}
@@ -10,110 +6,67 @@
max-width: 350px;
}
.panel-login .apps .btn {
.card-login .apps .btn {
width: auto;
float: left;
margin-right: 10px;
margin-top: auto;
}
.panel-login .apps .btn:hover {
.card-login .apps .btn:hover {
margin-top: 1px !important;
border-bottom-width: 3px;
}
.responsive-tabs .nav-tabs {
display: none;
}
.dataTables_paginate.paging_simple_numbers .pagination {
display: flex;
flex-wrap: wrap;
}
@media (min-width: 768px) {
.responsive-tabs .nav-tabs {
display: flex;
}
.responsive-tabs .card .card-body.collapse {
display: block;
}
}
@media (max-width: 767px) {
.panel-login .apps .btn {
.responsive-tabs .tab-pane {
display: block !important;
opacity: 1;
}
.card-login .apps .btn {
width: 100%;
float: none;
margin-bottom: 10px;
}
.panel-login .apps .btn {
.card-login .apps .btn {
border-bottom-width: 4px;
}
.media-clearfix::after {
clear: both;
box-sizing: border-box;
}
.media-clearfix::before {
display: table;
content: " ";
box-sizing: border-box;
}
.xs-show {
display: block !important;
}
.js-tabcollapse-panel-group .panel{
border: none;
box-shadow: none;
}
.js-tabcollapse-panel-group .panel-body {
padding: 10px 0;
}
.js-tabcollapse-panel-group .js-tabcollapse-panel-body .panel-body {
padding: 0;
}
.js-tabcollapse-panel-body .panel-heading {
display: none;
}
.js-tabcollapse-panel-body .well,
.panel-body .form-inline.well {
border: none;
padding: 0;
margin: 0;
box-shadow: none;
background-color: #fff;
}
.js-tabcollapse-panel-heading {
display: block;
height: 37px;
line-height: 37px;
text-indent: 15px;
}
.js-tabcollapse-panel-heading:hover {
text-decoration: none;
}
.js-tabcollapse-panel-heading {
position: relative;
}
.js-tabcollapse-panel-heading:after {
content: '';
display: block;
position: absolute;
top: 17px;
right: 17px;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-bottom: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
.js-tabcollapse-panel-heading.collapsed:after {
border-bottom: none;
border-top: 4px dashed;
}
.recent-login-success {
font-size: 14px;
margin-top: 10px !important;
font-size: 14px;
margin-top: 10px !important;
}
.pull-xs-right {
float: right !important;
}
.pull-xs-right .dropdown-menu {
right: 0;
left: auto;
right: 0;
left: auto;
}
.text-xs-left {
text-align: left;
@@ -125,36 +78,29 @@
font-weight: normal;
text-align: justify;
}
.help-block {
text-align: justify;
}
.btn.visible-xs-block {
.btn.d-block {
width: 100%;
float: none;
white-space: normal;
}
.btn-group.footable-actions .btn.btn-xs-half,
.btn.visible-xs-block.btn-xs-half {
.btn.btn-xs-half,
.btn.d-block.btn-xs-half {
width: 50%;
float: left;
}
.btn-group.footable-actions .btn.btn-xs-third,
.btn.visible-xs-block.btn-xs-third {
.btn.btn-xs-third,
.btn.d-block.btn-xs-third {
width: 33.33%;
float: left;
}
.btn-group.footable-actions .btn.btn-xs-quart,
.btn.visible-xs-block.btn-xs-quart {
.btn.btn-xs-quart,
.btn.d-block.btn-xs-quart {
width: 25%;
float: left;
}
.btn.visible-xs-block.btn-sm,
.btn.d-block.btn-sm,
.btn-xs-lg {
padding: 15px 16px 13px;
line-height: 15px;
padding: .5rem 1rem;
line-height: 20px;
}
.input-xs-lg {
height: 47px;
height: 47px;
padding: 13px 16px;
}
.btn-group:not(.input-group-btn) {
@@ -167,27 +113,27 @@
.btn-group.nowrap .dropdown-menu {
width: 100%;
}
.panel-login .btn-group {
.card-login .btn-group {
display: block;
}
.mass-actions-user .btn-group {
float: none;
}
div[class^='mass-actions'] .dropdown-menu,
.panel-xs-lg .dropdown-menu,
.card-xs-lg .dropdown-menu,
.dropdown-menu.login {
width: 100%;
}
div[class^='mass-actions'] .btn-group .dropdown-menu {
top: 50%;
top: 50%;
}
div[class^='mass-actions'] .btn-group .btn-group .dropdown-menu,
div.mass-actions-quarantine .btn-group .dropdown-menu,
.panel-xs-lg .dropdown-menu {
.card-xs-lg .dropdown-menu {
top: 100%;
}
div[class^='mass-actions'] .dropdown-menu>li>a,
.panel-xs-lg .dropdown-menu>li>a,
.card-xs-lg .dropdown-menu>li>a,
.dropdown-menu.login>li>a {
padding: 8px 20px;
}
@@ -195,9 +141,6 @@
font-size: 14px;
font-weight: bold;
}
.space20 {
margin-bottom: 10px;
}
.top100 {
top: 100% !important;
}
@@ -210,34 +153,17 @@
.btn-xs-lg>.lang-sm:after {
top: 1px;
}
table.footable>tfoot>tr.footable-paging>td {
text-align: left;
}
.footable-first-visible {
min-width: 55px;
}
table>tbody>tr>td>span.footable-toggle {
font-size: 24px;
margin-right: 14px !important;
}
table>tbody>tr>td>span.footable-toggle + input {
position: absolute;
left: 38px;
}
.pagination {
margin-bottom: 5px;
}
tr.footable-filtering>th>form {
width: 270px;
}
.mass-actions-mailbox {
padding: 0;
}
.panel-xs-lg .panel-heading {
.card-xs-lg .card-header {
height: 66px;
line-height: 47px;
}
.panel-xs-lg .btn-group .btn {
.card-xs-lg .btn-group .btn {
padding-right: 5px;
padding-left: 5px;
}
@@ -250,55 +176,29 @@
.bootstrap-select {
max-width: 100%;
}
.img-responsive {
margin: 0 auto;
}
.btn-group.footable-actions {
position: absolute;
width: 90vw !important;
left: 0;
height: 36px;
margin-top: -8px;
}
.btn-group.footable-actions .btn {
padding: 10px 16px 7px;
line-height: 15px;
display: block;
width: 100%;
}
.btn-group.footable-actions:after {
content: "";
display: block;
clear: both;
}
.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text {
margin-right: 14px;
white-space: normal;
}
.clearfix {
flex-basis: 100%;
height: 0;
}
.btn-group > .btn-group {
flex-basis: 100%;
}
.btn-group .btn {
display: flex !important;
align-items: center;
justify-content: center;
display: flex !important;
align-items: center;
justify-content: center;
}
.btn-group .btn i {
margin-right: 5px;
}
.btn-group .btn .caret {
margin-left: 5px;
}
.panel-login .btn-group .btn {
.card-login .btn-group .btn {
display: block !important;
}
.panel-login .clearfix {
height: auto;
.dt-sm-head-hidden .dtr-title {
display: none !important;
}
}
@media (max-width: 350px) {
@@ -306,3 +206,9 @@
max-width: 250px;
}
}
@media (min-width: 1400px) {
.container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
max-width: 1600px;
}
}

View File

@@ -0,0 +1,80 @@
.dataTables_info {
margin: 15px 0 !important;
padding: 0px !important;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 15px 0 !important;
}
.dtr-details {
width: 100%;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #F2F2F2;
}
td.child>ul>li {
display: flex;
}
table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(0, 0, 0, 0.129);
padding: 0.5em 0;
}
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;
}
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;
border-radius: 2px !important;
color: #fff;
height: 1em;
width: 1em;
line-height: 1.25em;
border-radius: 0px;
box-shadow: none;
font-size: 14px;
transition: 0.5s all;
}
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;
}
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: #fbfbfb;
}
table.dataTable.table-striped>tbody>tr>td {
vertical-align: middle;
}
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
margin-top: 7px;
}
td.dtr-col-lg {
min-width: 350px;
word-break: break-word;
}
td.dtr-col-md {
min-width: 250px;
word-break: break-word;
}
td.dtr-col-sm {
min-width: 125px;
word-break: break-word;
}
.dt-data-w100 .dtr-data {
width: 100%;
}
li .dtr-data {
word-break: break-all;
flex: 1;
padding-left: 5px;
padding-right: 5px;
}
table.dataTable>tbody>tr.child span.dtr-title {
width: 30%;
max-width: 250px;
}

View File

@@ -25,7 +25,6 @@ body.modal-open {
}
.mass-actions-admin {
user-select: none;
padding:10px 0 10px 0;
}
.inputMissingAttr {
border-color: #FF4136;

View File

@@ -26,7 +26,6 @@
}
.mass-actions-debug {
user-select: none;
padding:10px 0 10px 10px;
}
.inputMissingAttr {
border-color: #FF4136;

View File

@@ -21,7 +21,6 @@
}
.mass-actions-user {
user-select: none;
padding:10px 0 10px 0;
}
.inputMissingAttr {
border-color: #FF4136;

View File

@@ -32,7 +32,6 @@
}
.mass-actions-mailbox {
user-select: none;
padding:10px 0 10px 10px;
}
.inputMissingAttr {
border-color: #FF4136;

View File

@@ -35,7 +35,6 @@
.mass-actions-quarantine {
user-select: none;
padding: 10px;
}
.inputMissingAttr {

View File

@@ -21,7 +21,6 @@
}
.mass-actions-user {
user-select: none;
padding:10px 0;
}
.inputMissingAttr {
border-color: #FF4136;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,360 @@
body {
background-color: #414141;
color: #e0e0e0;
}
.card {
border: 1px solid #1c1c1c;
background-color: #3a3a3a;
}
legend {
color: #f5f5f5;
}
.card-header {
color: #bbb;
background-color: #2c2c2c;
border-color: transparent;
}
.btn-secondary, .paginate_button, .page-link, .btn-light {
color: #fff !important;
background-color: #7a7a7a !important;
border-color: #5c5c5c !important;
}
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
border-color: #7a7a7a !important;
}
.alert-secondary {
color: #fff !important;
background-color: #7a7a7a !important;
border-color: #5c5c5c !important;
}
.bg-secondary {
color: #fff !important;
background-color: #7a7a7a !important;
}
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
color: #fff;
}
.page-item.active .page-link {
background-color: #158cba !important;
border-color: #127ba3 !important;
}
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: #7a7a7a;
border-color: #5c5c5c !important;
color: #fff;
}
.btn-secondary:disabled, .btn-secondary.disabled {
border-color: #7a7a7a !important;
}
.modal-content {
background-color: #414141;
}
.modal-header {
border-bottom: 1px solid #161616;
}
.modal-title {
color: white;
}
.modal .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
.navbar.bg-light {
background-color: #222222 !important;
border-color: #181818;
}
.nav-link {
color: #ccc !important;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
background: none;
}
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
border-bottom-color: #414141;
}
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: #ccc !important;
}
.dropdown-menu {
background-color: #585858;
border: 1px solid #333;
}
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
color: #fafafa;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder:active, .bootstrap-select>.dropdown-toggle.bs-placeholder:focus, .bootstrap-select>.dropdown-toggle.bs-placeholder:hover {
color: #fff;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: #d4d4d4 !important;
}
tbody tr {
color: #555;
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
color: #ccc;
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
color: #ccc;
}
.list-group-item {
background-color: #333;
border: 1px solid #555;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #333;
}
table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(255, 255, 255, 0.13);
}
tbody tr {
color: #ccc;
}
.label.label-last-login {
color: #ccc !important;
background-color: #555 !important;
}
.progress {
background-color: #555;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
color: #ccc;
}
div.numberedtextarea-number {
color: #999;
}
.well {
border: 1px solid #555;
background-color: #333;
}
pre {
color: #ccc;
background-color: #333;
border: 1px solid #555;
}
input.form-control, textarea.form-control {
color: #e2e2e2 !important;
background-color: #555 !important;
border: 1px solid #999;
}
input.form-control:focus, textarea.form-control {
background-color: #555 !important;
}
input.form-control:disabled, textarea.form-disabled {
color: #a8a8a8 !important;
background-color: #6e6e6e !important;
}
.input-group-addon {
color: #ccc;
background-color: #555 !important;
border: 1px solid #999;
}
.input-group-text {
color: #ccc;
background-color: #242424;
}
.list-group-item {
color: #ccc;
}
.dropdown-item {
color: #ccc;
}
.dropdown-item:hover {
color: #616161 !important;
}
.dropdown-item.active:hover {
color: #fff !important;
background-color: #31b1e4;
}
.form-select {
color: #e2e2e2!important;
background-color: #555!important;
border: 1px solid #999;
}
.responsive-tabs .card-header button[data-bs-toggle="collapse"] {
color: #c7c7c7;
}
.navbar-toggler {
color: #fff !important;
}
.table-secondary {
--bs-table-bg: #7a7a7a;
--bs-table-striped-bg: #e4e4e4;
--bs-table-striped-color: #000;
--bs-table-active-bg: #d8d8d8;
--bs-table-active-color: #000;
--bs-table-hover-bg: #dedede;
--bs-table-hover-color: #000;
color: #000;
border-color: #d8d8d8;
}
.table-light {
--bs-table-bg: #f6f6f6;
--bs-table-striped-bg: #eaeaea;
--bs-table-striped-color: #000;
--bs-table-active-bg: #dddddd;
--bs-table-active-color: #000;
--bs-table-hover-bg: #e4e4e4;
--bs-table-hover-color: #000;
color: #000;
border-color: #dddddd;
}
.form-control-plaintext {
color: #e0e0e0;
}
.breadcrumb {
color: #fff !important;
background-color: #7a7a7a !important;
border-color: #5c5c5c !important;
}
a {
color: #6fc7e9;
text-decoration: underline;
}
a:hover {
color: #3daedb;
}
.breadcrumb-item.active {
color: #ccc;
}
.list-group-item.disabled, .list-group-item:disabled {
color: #fff;
background-color: #a8a8a8 !important;
border-color: #a8a8a8;
}
.card.bg-light .card-title {
color: #000 !important;
}
.card.bg-light .card-text {
color: #000;
}
.accordion-item {
background-color: #3a3a3a;
}
.accordion-button {
color: #bbb;
background-color: #2c2c2c;
}
.accordion-button:not(.collapsed) {
color: #6fc7e9;
background-color: #2c2c2c;
}
.accordion-button::after {
background-image: none;
}
.accordion-button:not(.collapsed)::after {
background-image: none;
}
.toast-header {
background-color: #4c4c4c;
}
.toast-body {
background-color: #626262;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff !important;
}
.tag-box {
background-color: #555;
border: 1px solid #999;
}
.tag-input {
color: #fff;
background-color: #555;
}
.tag-add {
color: #ccc;
}
.tag-add:hover {
color: #d1d1d1;
}
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;
}
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: #fff !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;
}
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: #444444;
}
.btn-check-label {
color: #fff;
}
.btn-outline-secondary:hover {
background-color: #c3c3c3;
}
.btn.btn-outline-secondary {
color: #fff !important;
border-color: #7a7a7a !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: #9b9b9b !important;
}
.btn-input-missing,
.btn-input-missing:hover,
.btn-input-missing:active,
.btn-input-missing:focus,
.btn-input-missing:active:hover,
.btn-input-missing:active:focus {
color: #fff !important;
background-color: #ff2f24 !important;
border-color: #e21207 !important;
}
.inputMissingAttr {
border-color: #FF4136 !important;
}
.list-group-details {
background: #444444;
}
.list-group-header {
background: #333;
}

View File

@@ -11,6 +11,11 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$solr_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_SOLR"])) ? false : solr_status();
$clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true;
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/debug.js');
// vmail df
@@ -44,15 +49,25 @@ foreach ($containers as $container => $container_info) {
$containers[$container]['State']['StartedAtHR'] = $started;
}
// get mailcow data
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig';
$template_data = [
'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df,
'hostname' => $hostname,
'timezone' => $timezone,
'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'),
'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status,
'containers' => $containers,
'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']),
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -38,24 +38,46 @@ if (isset($_SESSION['mailcow_cc_role'])) {
$template = 'edit/admin.twig';
$template_data = ['admin' => $admin];
}
elseif (isset($_GET['domain']) &&
is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) {
$domain = $_GET["domain"];
$result = mailbox('get', 'domain_details', $domain);
$quota_notification_bcc = quota_notification_bcc('get', $domain);
$rl = ratelimit('get', 'domain', $domain);
$rlyhosts = relayhost('get');
$template = 'edit/domain.twig';
elseif (isset($_GET['domain'])) {
if (is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) {
// edit domain
$domain = $_GET["domain"];
$result = mailbox('get', 'domain_details', $domain);
$quota_notification_bcc = quota_notification_bcc('get', $domain);
$rl = ratelimit('get', 'domain', $domain);
$rlyhosts = relayhost('get');
$template = 'edit/domain.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'domain' => $domain,
'quota_notification_bcc' => $quota_notification_bcc,
'rl' => $rl,
'rlyhosts' => $rlyhosts,
'dkim' => dkim('details', $domain),
'domain_details' => $result,
];
}
}
elseif (isset($_GET["template"])){
$domain_template = mailbox('get', 'domain_templates', $_GET["template"]);
if ($domain_template){
$template_data = [
'acl' => $_SESSION['acl'],
'domain' => $domain,
'quota_notification_bcc' => $quota_notification_bcc,
'rl' => $rl,
'rlyhosts' => $rlyhosts,
'dkim' => dkim('details', $domain),
'domain_details' => $result,
'template' => $domain_template
];
$template = 'edit/domain-templates.twig';
$result = true;
}
else {
$mailbox_template = mailbox('get', 'mailbox_templates', $_GET["template"]);
if ($mailbox_template){
$template_data = [
'template' => $mailbox_template
];
$template = 'edit/mailbox-templates.twig';
$result = true;
}
}
}
elseif (isset($_GET['oauth2client']) &&
is_numeric($_GET["oauth2client"]) &&
@@ -79,29 +101,32 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'dkim' => dkim('details', $alias_domain),
];
}
elseif (isset($_GET['mailbox']) && filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
$mailbox = html_entity_decode(rawurldecode($_GET["mailbox"]));
$result = mailbox('get', 'mailbox_details', $mailbox);
$rl = ratelimit('get', 'mailbox', $mailbox);
$pushover_data = pushover('get', $mailbox);
$quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
$quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
$get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
$rlyhosts = relayhost('get');
$template = 'edit/mailbox.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'mailbox' => $mailbox,
'rl' => $rl,
'pushover_data' => $pushover_data,
'quarantine_notification' => $quarantine_notification,
'quarantine_category' => $quarantine_category,
'get_tls_policy' => $get_tls_policy,
'rlyhosts' => $rlyhosts,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result
];
elseif (isset($_GET['mailbox'])){
if(filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
// edit mailbox
$mailbox = html_entity_decode(rawurldecode($_GET["mailbox"]));
$result = mailbox('get', 'mailbox_details', $mailbox);
$rl = ratelimit('get', 'mailbox', $mailbox);
$pushover_data = pushover('get', $mailbox);
$quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
$quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
$get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
$rlyhosts = relayhost('get');
$template = 'edit/mailbox.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'mailbox' => $mailbox,
'rl' => $rl,
'pushover_data' => $pushover_data,
'quarantine_notification' => $quarantine_notification,
'quarantine_category' => $quarantine_category,
'get_tls_policy' => $get_tls_policy,
'rlyhosts' => $rlyhosts,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result
];
}
}
elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) {
$relayhost = intval($_GET["relayhost"]);
@@ -189,5 +214,6 @@ $js_minifier->add('/web/js/site/pwgen.js');
$template_data['result'] = $result;
$template_data['return_to'] = $_SESSION['return_to'];
$template_data['lang_user'] = json_encode($lang['user']);
$template_data['lang_datatables'] = json_encode($lang['datatables']);
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -436,7 +436,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
}
?>
</table>
<a id='download-zonefile' class="btn btn-sm btn-default visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<script>
var zonefile_dl_link = document.getElementById('download-zonefile');
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));

View File

@@ -127,6 +127,7 @@ elseif (!empty($_GET['id']) && ctype_alnum($_GET['id'])) {
$data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']);
// Get text/plain content
$data['text_plain'] = $mail_parser->getMessageBody('text');
if (!json_encode($data['text_plain'])) $data['text_plain'] = '';
// Get html content and convert to text
$data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html'));
if (empty($data['text_plain']) && empty($data['text_html'])) {

View File

@@ -2,8 +2,18 @@
function customize($_action, $_item, $_data = null) {
global $redis;
global $lang;
switch ($_action) {
case 'add':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -72,6 +82,15 @@ function customize($_action, $_item, $_data = null) {
}
break;
case 'edit':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -116,6 +135,7 @@ function customize($_action, $_item, $_data = null) {
$ui_announcement_text = $_data['ui_announcement_text'];
$ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false;
$ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0);
try {
$redis->set('TITLE_NAME', htmlspecialchars($title_name));
$redis->set('MAIN_NAME', htmlspecialchars($main_name));
@@ -143,6 +163,15 @@ function customize($_action, $_item, $_data = null) {
}
break;
case 'delete':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',

View File

@@ -1,6 +1,7 @@
<?php
function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) {
global $DOCKER_TIMEOUT;
global $redis;
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' ));
// We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway
@@ -32,6 +33,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
}
}
return false;
break;
case 'containers':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
@@ -51,6 +53,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
if (strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) {
$out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']);
}
}
}
@@ -94,6 +97,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
unset($container['Config']['Env']);
$out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']);
}
}
}
@@ -103,6 +107,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
unset($container['Config']['Env']);
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State'];
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Config'] = $decoded_response['Config'];
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($decoded_response['Id']);
}
}
}
@@ -146,5 +151,46 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
}
}
break;
case 'container_stats':
if (empty($service_name)){
return false;
}
$container_id = $service_name;
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/container/' . $container_id . '/stats/update');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
$stats = json_decode($response, true);
if (!empty($stats)) return $stats;
}
return false;
break;
case 'host_stats':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/host/stats');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
$stats = json_decode($response, true);
if (!empty($stats)) return $stats;
}
return false;
break;
}
}

View File

@@ -251,7 +251,7 @@ function password_check($password1, $password2) {
return true;
}
function last_login($action, $username, $sasl_limit_days = 7) {
function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
global $pdo;
global $redis;
$sasl_limit_days = intval($sasl_limit_days);
@@ -319,8 +319,11 @@ function last_login($action, $username, $sasl_limit_days = 7) {
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :username
AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET 1');
$stmt->execute(array(':username' => $username));
AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset');
$stmt->execute(array(
':username' => $username,
':offset' => $ui_offset
));
$ui = $stmt->fetch(PDO::FETCH_ASSOC);
}
else {

View File

@@ -1020,6 +1020,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (empty($name)) {
$name = $local_part;
}
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
}
$active = intval($_data['active']);
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
@@ -1200,10 +1207,63 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain,
':active' => $active
));
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
$_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$stmt = $pdo->prepare("INSERT INTO `user_acl`
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
$stmt->execute(array(
':username' => $username,
':spam_alias' => $_data['spam_alias'],
':tls_policy' => $_data['tls_policy'],
':spam_score' => $_data['spam_score'],
':spam_policy' => $_data['spam_policy'],
':delimiter_action' => $_data['delimiter_action'],
':syncjobs' => $_data['syncjobs'],
':eas_reset' => $_data['eas_reset'],
':sogo_profile_reset' => $_data['sogo_profile_reset'],
':pushover' => $_data['pushover'],
':quarantine' => $_data['quarantine'],
':quarantine_attachments' => $_data['quarantine_attachments'],
':quarantine_notification' => $_data['quarantine_notification'],
':quarantine_category' => $_data['quarantine_category'],
':app_passwds' => $_data['app_passwds']
));
}
else {
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
}
if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
ratelimit('edit', 'mailbox', array(
'object' => $username,
'rl_frame' => $_data['rl_frame'],
'rl_value' => $_data['rl_value']
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1322,6 +1382,190 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('resource_added', htmlspecialchars($name))
);
break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (empty($_data["template"])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_name_invalid'
);
return false;
}
// check if template name exists, return false
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "domain",
":template" => $_data["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => array('template_exists', $_data["template"])
);
return false;
}
// check attributes
$attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400;
$attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10;
$attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576;
$attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576;
$attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576;
$attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
// save template
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "domain",
":template" => $_data["template"],
":attributes" => json_encode($attr)
));
// success
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_added', $_data["template"])
);
return true;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (empty($_data["template"])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_name_invalid'
);
return false;
}
// check if template name exists, return false
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "mailbox",
":template" => $_data["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => array('template_exists', $_data["template"])
);
return false;
}
// check attributes
$attr = array();
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
}
else {
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
}
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
} else {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = 1;
$attr['acl_tls_policy'] = 1;
$attr['acl_spam_score'] = 1;
$attr['acl_spam_policy'] = 1;
$attr['acl_delimiter_action'] = 1;
$attr['acl_syncjobs'] = 0;
$attr['acl_eas_reset'] = 1;
$attr['acl_sogo_profile_reset'] = 0;
$attr['acl_pushover'] = 1;
$attr['acl_quarantine'] = 1;
$attr['acl_quarantine_attachments'] = 1;
$attr['acl_quarantine_notification'] = 1;
$attr['acl_quarantine_category'] = 1;
$attr['acl_app_passwds'] = 1;
}
// save template
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "mailbox",
":template" => $_data["template"],
":attributes" => json_encode($attr)
));
// success
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_added', $_data["template"])
);
return true;
break;
}
break;
case 'edit':
@@ -2472,6 +2716,79 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
}
break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
$is_now = mailbox("get", "domain_templates", $id);
if (empty($is_now) ||
$is_now["type"] != "domain"){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_id_invalid'
);
continue;
}
// check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template
$_data["template"] = $is_now["template"];
}
else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
}
// check attributes
$attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0;
$attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0;
$attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0;
$attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0;
$attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0;
$attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
// update template
$stmt = $pdo->prepare("UPDATE `templates`
SET `template` = :template, `attributes` = :attributes
WHERE id = :id");
$stmt->execute(array(
":id" => $id ,
":template" => $_data["template"] ,
":attributes" => json_encode($attr)
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_modified', $_data["template"])
);
return true;
break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
@@ -2814,6 +3131,110 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
$is_now = mailbox("get", "mailbox_templates", $id);
if (empty($is_now) ||
$is_now["type"] != "mailbox"){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_id_invalid'
);
continue;
}
// check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template
$_data["template"] = $is_now["template"];
}
else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
}
// check attributes
$attr = array();
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
}
else {
foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key];
}
}
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
} else {
foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key];
}
}
// update template
$stmt = $pdo->prepare("UPDATE `templates`
SET `template` = :template, `attributes` = :attributes
WHERE id = :id");
$stmt->execute(array(
":id" => $id ,
":template" => $_data["template"] ,
":attributes" => json_encode($attr)
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_modified', $_data["template"])
);
return true;
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
@@ -3606,6 +4027,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailboxes`,
`defquota`,
`maxquota`,
`created`,
`modified`,
`quota`,
`relayhost`,
`relay_all_recipients`,
@@ -3678,6 +4101,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$domaindata['relay_all_recipients_int'] = $row['relay_all_recipients'];
$domaindata['relay_unknown_only'] = $row['relay_unknown_only'];
$domaindata['relay_unknown_only_int'] = $row['relay_unknown_only'];
$domaindata['created'] = $row['created'];
$domaindata['modified'] = $row['modified'];
$stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias`
WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))
AND `address` NOT IN (
@@ -3711,6 +4136,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $domaindata;
break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type");
$stmt->execute(array(
":id" => $_data,
":type" => "domain"
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
return false;
}
$row["attributes"] = json_decode($row["attributes"], true);
return $row;
}
else {
$stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)){
return false;
}
foreach($rows as $key => $row){
$rows[$key]["attributes"] = json_decode($row["attributes"], true);
}
return $rows;
}
break;
case 'mailbox_details':
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
@@ -3725,6 +4187,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`domain`,
`mailbox`.`local_part`,
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`quota2`.`bytes`,
`attributes`,
`quota2`.`messages`
@@ -3743,6 +4207,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`domain`,
`mailbox`.`local_part`,
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`quota2replica`.`bytes`,
`attributes`,
`quota2replica`.`messages`
@@ -3769,6 +4235,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['attributes'] = json_decode($row['attributes'], true);
$mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['created'] = $row['created'];
$mailboxdata['modified'] = $row['modified'];
if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info";
@@ -3856,6 +4324,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type");
$stmt->execute(array(
":id" => $_data,
":type" => "mailbox"
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
return false;
}
$row["attributes"] = json_decode($row["attributes"], true);
return $row;
}
else {
$stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)){
return false;
}
foreach($rows as $key => $row){
$rows[$key]["attributes"] = json_decode($row["attributes"], true);
}
return $rows;
}
break;
case 'resource_details':
$resourcedata = array();
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
@@ -4224,6 +4729,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
// delete template
$stmt = $pdo->prepare("DELETE FROM `templates`
WHERE id = :id AND type = :type AND NOT template = :template");
$stmt->execute(array(
":id" => $id,
":type" => "domain",
":template" => "Default"
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_removed', htmlspecialchars($id))
);
return true;
}
break;
case 'alias':
if (!is_array($_data['id'])) {
$ids = array();
@@ -4518,6 +5059,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
// delete template
$stmt = $pdo->prepare("DELETE FROM `templates`
WHERE id = :id AND type = :type AND NOT template = :template");
$stmt->execute(array(
":id" => $id,
":type" => "mailbox",
":template" => "Default"
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_removed'
);
return true;
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();

View File

@@ -51,6 +51,7 @@ function pushover($_action, $_data = null) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio'];
$only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio'];
$sound = (isset($_data['sound'])) ? $_data['sound'] : $is_now['sound'];
}
else {
$_SESSION['return'][] = array(
@@ -101,7 +102,8 @@ function pushover($_action, $_data = null) {
$po_attributes = json_encode(
array(
'evaluate_x_prio' => strval(intval($evaluate_x_prio)),
'only_x_prio' => strval(intval($only_x_prio))
'only_x_prio' => strval(intval($only_x_prio)),
'sound' => strval($sound)
)
);
$stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`)

View File

@@ -39,7 +39,6 @@ $globalVariables = [
'dual_login' => @$_SESSION['dual-login'],
'ui_texts' => $UI_TEXTS,
'css_path' => '/cache/'.basename($CSSPath),
'theme' => strtolower(trim($DEFAULT_THEME)),
'logo' => customize('get', 'main_logo'),
'available_languages' => $AVAILABLE_LANGUAGES,
'lang' => $lang,
@@ -49,6 +48,7 @@ $globalVariables = [
'app_links' => customize('get', 'app_links'),
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => $_SERVER['REQUEST_URI'],
'last_login' => last_login('get', $_SESSION['mailcow_cc_username'], 7, 0)['ui']['time']
];
foreach ($globalVariables as $globalVariableName => $globalVariableValue) {

View File

@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "25072022_2300";
$db_version = "23122022_1445";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -225,6 +225,22 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"templates" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"template" => "VARCHAR(255) NOT NULL",
"type" => "VARCHAR(255) NOT NULL",
"attributes" => "JSON",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"domain" => array(
// Todo: Move some attributes to json
"cols" => array(
@@ -1264,6 +1280,7 @@ function init_db_schema() {
$pdo->query("UPDATE `pushover` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;");
$pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.evaluate_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.evaluate_x_prio') IS NULL;");
$pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.only_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.only_x_prio') IS NULL;");
$pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.sound', \"pushover\") WHERE JSON_VALUE(`attributes`, '$.sound') IS NULL;");
// mailbox
$pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.passwd_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.passwd_update') IS NULL;");
@@ -1292,6 +1309,95 @@ function init_db_schema() {
// Fix domain_admins
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
// add default templates
$default_domain_template = array(
"template" => "Default",
"type" => "domain",
"attributes" => array(
"tags" => array(),
"max_num_aliases_for_domain" => 400,
"max_num_mboxes_for_domain" => 10,
"def_quota_for_mbox" => 3072 * 1048576,
"max_quota_for_mbox" => 10240 * 1048576,
"max_quota_for_domain" => 10240 * 1048576,
"rl_frame" => "s",
"rl_value" => "",
"active" => 1,
"gal" => 1,
"backupmx" => 0,
"relay_all_recipients" => 0,
"relay_unknown_only" => 0,
"dkim_selector" => "dkim",
"key_size" => 2048,
"max_quota_for_domain" => 10240 * 1048576,
)
);
$default_mailbox_template = array(
"template" => "Default",
"type" => "mailbox",
"attributes" => array(
"tags" => array(),
"quota" => 0,
"quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']),
"quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']),
"rl_frame" => "s",
"rl_value" => "",
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
"active" => 1,
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
"tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
"imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']),
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
"acl_spam_alias" => 1,
"acl_tls_policy" => 1,
"acl_spam_score" => 1,
"acl_spam_policy" => 1,
"acl_delimiter_action" => 1,
"acl_syncjobs" => 0,
"acl_eas_reset" => 1,
"acl_sogo_profile_reset" => 0,
"acl_pushover" => 1,
"acl_quarantine" => 1,
"acl_quarantine_attachments" => 1,
"acl_quarantine_notification" => 1,
"acl_quarantine_category" => 1,
"acl_app_passwds" => 1,
)
);
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "domain",
":template" => $default_domain_template["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "domain",
":template" => $default_domain_template["template"],
":attributes" => json_encode($default_domain_template["attributes"])
));
}
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "mailbox",
":template" => $default_mailbox_template["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "mailbox",
":template" => $default_mailbox_template["template"],
":attributes" => json_encode($default_mailbox_template["attributes"])
));
}
if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL;
} else {

View File

@@ -1604,16 +1604,16 @@
},
{
"name": "twig/twig",
"version": "v3.3.8",
"version": "v3.4.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c"
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58",
"shasum": ""
},
"require": {
@@ -1628,7 +1628,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
"dev-master": "3.4-dev"
}
},
"autoload": {
@@ -1664,7 +1664,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.8"
"source": "https://github.com/twigphp/Twig/tree/v3.4.3"
},
"funding": [
{
@@ -1676,7 +1676,7 @@
"type": "tidelift"
}
],
"time": "2022-02-04T06:59:48+00:00"
"time": "2022-09-28T08:42:51+00:00"
},
{
"name": "yubico/u2flib-server",
@@ -1728,5 +1728,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

View File

@@ -2,6 +2,11 @@
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b::getLoader();

View File

@@ -21,12 +21,14 @@ use Composer\Semver\VersionParser;
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
@@ -37,7 +39,7 @@ class InstalledVersions
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
@@ -241,7 +243,7 @@ class InstalledVersions
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
@@ -255,7 +257,7 @@ class InstalledVersions
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@@ -278,7 +280,7 @@ class InstalledVersions
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
@@ -301,7 +303,7 @@ class InstalledVersions
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
@@ -311,7 +313,7 @@ class InstalledVersions
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{

View File

@@ -2,7 +2,7 @@
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@@ -2,15 +2,15 @@
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
'04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php',

View File

@@ -2,7 +2,7 @@
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@@ -2,7 +2,7 @@
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@@ -25,38 +25,15 @@ class ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::getInitializer($loader));
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
$includeFiles = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files;
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire873464e4bd965a3168f133248b1b218b($fileIdentifier, $file);
}

View File

@@ -8,10 +8,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
'04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php',

View File

@@ -1654,17 +1654,17 @@
},
{
"name": "twig/twig",
"version": "v3.3.8",
"version_normalized": "3.3.8.0",
"version": "v3.4.3",
"version_normalized": "3.4.3.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c"
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58",
"shasum": ""
},
"require": {
@@ -1676,11 +1676,11 @@
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
},
"time": "2022-02-04T06:59:48+00:00",
"time": "2022-09-28T08:42:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
"dev-master": "3.4-dev"
}
},
"installation-source": "dist",
@@ -1717,7 +1717,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.8"
"source": "https://github.com/twigphp/Twig/tree/v3.4.3"
},
"funding": [
{

View File

@@ -1,49 +1,49 @@
<?php return array(
'root' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'dev_requirement' => false,
),
'bshaffer/oauth2-server-php' => array(
'pretty_version' => 'v1.11.1',
'version' => '1.11.1.0',
'reference' => '5a0c8000d4763b276919e2106f54eddda6bc50fa',
'type' => 'library',
'install_path' => __DIR__ . '/../bshaffer/oauth2-server-php',
'aliases' => array(),
'reference' => '5a0c8000d4763b276919e2106f54eddda6bc50fa',
'dev_requirement' => false,
),
'ddeboer/imap' => array(
'pretty_version' => '1.13.1',
'version' => '1.13.1.0',
'reference' => '8b772d04b1deadb5df13782fb78c4b648f77496e',
'type' => 'library',
'install_path' => __DIR__ . '/../ddeboer/imap',
'aliases' => array(),
'reference' => '8b772d04b1deadb5df13782fb78c4b648f77496e',
'dev_requirement' => false,
),
'directorytree/ldaprecord' => array(
'pretty_version' => 'v2.10.1',
'version' => '2.10.1.0',
'reference' => 'bf512d9af7a7b0e2ed7a666ab29cefdd027bee88',
'type' => 'library',
'install_path' => __DIR__ . '/../directorytree/ldaprecord',
'aliases' => array(),
'reference' => 'bf512d9af7a7b0e2ed7a666ab29cefdd027bee88',
'dev_requirement' => false,
),
'exorus/php-mime-mail-parser' => array(
@@ -55,28 +55,28 @@
'illuminate/contracts' => array(
'pretty_version' => 'v9.3.0',
'version' => '9.3.0.0',
'reference' => 'bf4b3c254c49d28157645d01e4883b5951b1e1d0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/contracts',
'aliases' => array(),
'reference' => 'bf4b3c254c49d28157645d01e4883b5951b1e1d0',
'dev_requirement' => false,
),
'matthiasmullie/minify' => array(
'pretty_version' => '1.3.66',
'version' => '1.3.66.0',
'reference' => '45fd3b0f1dfa2c965857c6d4a470bea52adc31a6',
'type' => 'library',
'install_path' => __DIR__ . '/../matthiasmullie/minify',
'aliases' => array(),
'reference' => '45fd3b0f1dfa2c965857c6d4a470bea52adc31a6',
'dev_requirement' => false,
),
'matthiasmullie/path-converter' => array(
'pretty_version' => '1.1.3',
'version' => '1.1.3.0',
'reference' => 'e7d13b2c7e2f2268e1424aaed02085518afa02d9',
'type' => 'library',
'install_path' => __DIR__ . '/../matthiasmullie/path-converter',
'aliases' => array(),
'reference' => 'e7d13b2c7e2f2268e1424aaed02085518afa02d9',
'dev_requirement' => false,
),
'messaged/php-mime-mail-parser' => array(
@@ -88,136 +88,136 @@
'mustangostang/spyc' => array(
'pretty_version' => '0.6.3',
'version' => '0.6.3.0',
'reference' => '4627c838b16550b666d15aeae1e5289dd5b77da0',
'type' => 'library',
'install_path' => __DIR__ . '/../mustangostang/spyc',
'aliases' => array(),
'reference' => '4627c838b16550b666d15aeae1e5289dd5b77da0',
'dev_requirement' => false,
),
'nesbot/carbon' => array(
'pretty_version' => '2.57.0',
'version' => '2.57.0.0',
'reference' => '4a54375c21eea4811dbd1149fe6b246517554e78',
'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(),
'reference' => '4a54375c21eea4811dbd1149fe6b246517554e78',
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'dev_requirement' => false,
),
'php-mime-mail-parser/php-mime-mail-parser' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '9d09a017f3f103fec8456211a4a538b80e0eca0d',
'type' => 'library',
'install_path' => __DIR__ . '/../php-mime-mail-parser/php-mime-mail-parser',
'aliases' => array(),
'reference' => '9d09a017f3f103fec8456211a4a538b80e0eca0d',
'dev_requirement' => false,
),
'phpmailer/phpmailer' => array(
'pretty_version' => 'v6.6.0',
'version' => '6.6.0.0',
'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1',
'type' => 'library',
'install_path' => __DIR__ . '/../phpmailer/phpmailer',
'aliases' => array(),
'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1',
'dev_requirement' => false,
),
'psr/container' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001',
'dev_requirement' => false,
),
'psr/simple-cache' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'reference' => '8707bf3cea6f710bf6ef05491234e3ab06f6432a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'reference' => '8707bf3cea6f710bf6ef05491234e3ab06f6432a',
'dev_requirement' => false,
),
'robthree/twofactorauth' => array(
'pretty_version' => '1.8.1',
'version' => '1.8.1.0',
'reference' => '5afcb45282f1c75562a48d479ecd1732c9bdb11b',
'type' => 'library',
'install_path' => __DIR__ . '/../robthree/twofactorauth',
'aliases' => array(),
'reference' => '5afcb45282f1c75562a48d479ecd1732c9bdb11b',
'dev_requirement' => false,
),
'soundasleep/html2text' => array(
'pretty_version' => '0.5.0',
'version' => '0.5.0.0',
'reference' => 'cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad',
'type' => 'library',
'install_path' => __DIR__ . '/../soundasleep/html2text',
'aliases' => array(),
'reference' => 'cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad',
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '30885182c981ab175d4d034db0f6f469898070ab',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'reference' => '30885182c981ab175d4d034db0f6f469898070ab',
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '57b712b08eddb97c762a8caa32c84e037892d2e9',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'reference' => '57b712b08eddb97c762a8caa32c84e037892d2e9',
'dev_requirement' => false,
),
'symfony/translation' => array(
'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0',
'reference' => 'e69501c71107cc3146b32aaa45f4edd0c3427875',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(),
'reference' => 'e69501c71107cc3146b32aaa45f4edd0c3427875',
'dev_requirement' => false,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'reference' => '1b6ea5a7442af5a12dba3dbd6d71034b5b234e77',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
'reference' => '1b6ea5a7442af5a12dba3dbd6d71034b5b234e77',
'dev_requirement' => false,
),
'symfony/translation-implementation' => array(
@@ -229,37 +229,37 @@
'symfony/var-dumper' => array(
'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0',
'reference' => '60d6a756d5f485df5e6e40b337334848f79f61ce',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(),
'reference' => '60d6a756d5f485df5e6e40b337334848f79f61ce',
'dev_requirement' => false,
),
'tightenco/collect' => array(
'pretty_version' => 'v8.83.2',
'version' => '8.83.2.0',
'reference' => 'd9c66d586ec2d216d8a31283d73f8df1400cc722',
'type' => 'library',
'install_path' => __DIR__ . '/../tightenco/collect',
'aliases' => array(),
'reference' => 'd9c66d586ec2d216d8a31283d73f8df1400cc722',
'dev_requirement' => false,
),
'twig/twig' => array(
'pretty_version' => 'v3.3.8',
'version' => '3.3.8.0',
'pretty_version' => 'v3.4.3',
'version' => '3.4.3.0',
'reference' => 'c38fd6b0b7f370c198db91ffd02e23b517426b58',
'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(),
'reference' => '972d8604a92b7054828b539f2febb0211dd5945c',
'dev_requirement' => false,
),
'yubico/u2flib-server' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '55d813acf68212ad2cadecde07551600d6971939',
'type' => 'library',
'install_path' => __DIR__ . '/../yubico/u2flib-server',
'aliases' => array(),
'reference' => '55d813acf68212ad2cadecde07551600d6971939',
'dev_requirement' => false,
),
),

View File

@@ -1,4 +1,4 @@
/doc/** export-ignore
/extra/** export-ignore
/tests export-ignore
/doc/ export-ignore
/extra/ export-ignore
/tests/ export-ignore
/phpunit.xml.dist export-ignore

View File

@@ -9,6 +9,9 @@ on:
env:
SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1
permissions:
contents: read
jobs:
tests:
name: "PHP ${{ matrix.php-version }}"
@@ -25,36 +28,23 @@ jobs:
- '7.4'
- '8.0'
- '8.1'
composer-options: ['']
experimental: [false]
steps:
- name: "Checkout code"
uses: actions/checkout@v2.3.3
uses: actions/checkout@v2
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@2.7.0
uses: shivammathur/setup-php@v2
with:
coverage: "none"
php-version: ${{ matrix.php-version }}
ini-values: memory_limit=-1
tools: composer:v2
- name: "Add PHPUnit matcher"
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: "Set composer cache directory"
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: "Cache composer"
uses: actions/cache@v2.1.2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer-
- run: composer install ${{ matrix.composer-options }}
- run: composer install
- name: "Install PHPUnit"
run: vendor/bin/simple-phpunit install
@@ -92,35 +82,22 @@ jobs:
- 'extra/markdown-extra'
- 'extra/string-extra'
- 'extra/twig-extra-bundle'
composer-options: ['']
experimental: [false]
steps:
- name: "Checkout code"
uses: actions/checkout@v2.3.3
uses: actions/checkout@v2
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@2.7.0
uses: shivammathur/setup-php@v2
with:
coverage: "none"
php-version: ${{ matrix.php-version }}
ini-values: memory_limit=-1
tools: composer:v2
- name: "Add PHPUnit matcher"
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: "Set composer cache directory"
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: "Cache composer"
uses: actions/cache@v2.1.2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-${{ matrix.extension }}-${{ hashFiles('composer.json') }}
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-${{ matrix.extension }}-
- run: composer install
- name: "Install PHPUnit"
@@ -129,10 +106,6 @@ jobs:
- name: "PHPUnit version"
run: vendor/bin/simple-phpunit --version
- if: matrix.extension == 'extra/markdown-extra' && matrix.php-version == '8.0'
working-directory: ${{ matrix.extension}}
run: composer config platform.php 7.4.99
- name: "Composer install"
working-directory: ${{ matrix.extension}}
run: composer install
@@ -140,6 +113,7 @@ jobs:
- name: "Run tests"
working-directory: ${{ matrix.extension}}
run: ../../vendor/bin/simple-phpunit
#
# Drupal does not support Twig 3 now!
#
@@ -160,10 +134,10 @@ jobs:
#
# steps:
# - name: "Checkout code"
# uses: actions/checkout@v2.3.3
# uses: actions/checkout@v2
#
# - name: "Install PHP with extensions"
# uses: shivammathur/setup-php@2.7.0
# uses: shivammathur/setup-php@2
# with:
# coverage: "none"
# extensions: "gd, pdo_sqlite"

View File

@@ -4,8 +4,12 @@ on:
pull_request:
push:
branches:
- '2.x'
- '3.x'
permissions:
contents: read
jobs:
build:
name: "Build"
@@ -16,32 +20,32 @@ jobs:
- name: "Checkout code"
uses: actions/checkout@v2
- name: "Set up Python 3.7"
uses: actions/setup-python@v1
- name: "Set-up PHP"
uses: shivammathur/setup-php@v2
with:
python-version: '3.7' # Semantic version range syntax or exact version of a Python version
php-version: 8.1
coverage: none
tools: "composer:v2"
- name: "Display Python version"
run: python -c "import sys; print(sys.version)"
- name: Get composer cache directory
id: composercache
working-directory: doc/_build
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: "Install Sphinx dependencies"
run: sudo apt-get install python-dev build-essential
- name: "Cache pip"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: "Install Sphinx + requirements via pip"
working-directory: "doc"
run: pip install -r _build/.requirements.txt
- name: "Install dependencies"
working-directory: doc/_build
run: composer install --prefer-dist --no-progress
- name: "Build documentation"
working-directory: "doc"
run: make -C _build SPHINXOPTS="-nqW -j auto" html
- name: "Build the docs"
working-directory: doc/_build
run: php build.php --disable-cache
doctor-rst:
name: "DOCtor-RST"

View File

@@ -1,3 +1,5 @@
/doc/_build/vendor
/doc/_build/output
/composer.lock
/phpunit.xml
/vendor

View File

@@ -1,6 +1,36 @@
# 3.4.3 (2022-09-28)
* Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)
# 3.4.2 (2022-08-12)
* Allow inherited magic method to still run with calling class
* Fix CallExpression::reflectCallable() throwing TypeError
* Fix typo in naming (currency_code)
# 3.4.1 (2022-05-17)
* Fix optimizing non-public named closures
# 3.4.0 (2022-05-22)
* Add support for named closures
# 3.3.10 (2022-04-06)
* Enable bytecode invalidation when auto_reload is enabled
# 3.3.9 (2022-03-25)
* Fix custom escapers when using multiple Twig environments
* Add support for "constant('class', object)"
* Do not reuse internally generated variable names during parsing
# 3.3.8 (2022-02-04)
* Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter
* Fix deprecation notice on `round`
* Fix call to deprecated `convertToHtml` method
# 3.3.7 (2022-01-03)

View File

@@ -44,7 +44,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
"dev-master": "3.4-dev"
}
}
}

View File

@@ -38,11 +38,11 @@ use Twig\TokenParser\TokenParserInterface;
*/
class Environment
{
public const VERSION = '3.3.8';
public const VERSION_ID = 30308;
public const VERSION = '3.4.3';
public const VERSION_ID = 30403;
public const MAJOR_VERSION = 3;
public const MINOR_VERSION = 3;
public const RELEASE_VERSION = 8;
public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 3;
public const EXTRA_VERSION = '';
private $charset;
@@ -228,7 +228,7 @@ class Environment
{
if (\is_string($cache)) {
$this->originalCache = $cache;
$this->cache = new FilesystemCache($cache);
$this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0);
} elseif (false === $cache) {
$this->originalCache = $cache;
$this->cache = new NullCache();

View File

@@ -485,7 +485,7 @@ class ExpressionParser
}
}
} else {
throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
}
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {

View File

@@ -1359,6 +1359,10 @@ function twig_source(Environment $env, $name, $ignoreMissing = false)
function twig_constant($constant, $object = null)
{
if (null !== $object) {
if ('class' === $constant) {
return \get_class($object);
}
$constant = \get_class($object).'::'.$constant;
}
@@ -1376,6 +1380,10 @@ function twig_constant($constant, $object = null)
function twig_constant_is_defined($constant, $object = null)
{
if (null !== $object) {
if ('class' === $constant) {
return true;
}
$constant = \get_class($object).'::'.$constant;
}

View File

@@ -387,13 +387,8 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char
return rawurlencode($string);
default:
static $escapers;
if (null === $escapers) {
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
}
if (isset($escapers[$strategy])) {
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
if (array_key_exists($strategy, $escapers)) {
return $escapers[$strategy]($env, $string, $charset);
}

View File

@@ -91,11 +91,11 @@ final class SandboxExtension extends AbstractExtension
}
}
public function checkPropertyAllowed($obj, $method, int $lineno = -1, Source $source = null): void
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null): void
{
if ($this->isSandboxed()) {
try {
$this->policy->checkPropertyAllowed($obj, $method);
$this->policy->checkPropertyAllowed($obj, $property);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);

View File

@@ -183,9 +183,9 @@ class FilesystemLoader implements LoaderInterface
}
try {
$this->validateName($name);
list($namespace, $shortname) = $this->parseName($name);
$this->validateName($shortname);
} catch (LoaderError $e) {
if (!$throw) {
return null;

View File

@@ -24,19 +24,20 @@ abstract class CallExpression extends AbstractExpression
{
$callable = $this->getAttribute('callable');
$closingParenthesis = false;
$isArray = false;
if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable);
} else {
list($r, $callable) = $this->reflectCallable($callable);
if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
if ($r->isStatic()) {
[$r, $callable] = $this->reflectCallable($callable);
if (\is_string($callable)) {
$compiler->raw($callable);
} elseif (\is_array($callable) && \is_string($callable[0])) {
if (!$r instanceof \ReflectionMethod || $r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
}
} elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) {
} elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) {
$class = \get_class($callable[0]);
if (!$compiler->getEnvironment()->hasExtension($class)) {
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
@@ -47,17 +48,11 @@ abstract class CallExpression extends AbstractExpression
$compiler->raw(sprintf('->%s', $callable[1]));
} else {
$closingParenthesis = true;
$isArray = true;
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
$compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
}
}
$this->compileArguments($compiler, $isArray);
if ($closingParenthesis) {
$compiler->raw(')');
}
$this->compileArguments($compiler);
}
protected function compileArguments(Compiler $compiler, $isArray = false): void
@@ -244,10 +239,7 @@ abstract class CallExpression extends AbstractExpression
private function getCallableParameters($callable, bool $isVariadic): array
{
list($r) = $this->reflectCallable($callable);
if (null === $r) {
return [[], false];
}
[$r, , $callableName] = $this->reflectCallable($callable);
$parameters = $r->getParameters();
if ($this->hasNode('node')) {
@@ -274,11 +266,6 @@ abstract class CallExpression extends AbstractExpression
array_pop($parameters);
$isPhpVariadic = true;
} else {
$callableName = $r->name;
if ($r instanceof \ReflectionMethod) {
$callableName = $r->getDeclaringClass()->name.'::'.$callableName;
}
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
@@ -292,29 +279,41 @@ abstract class CallExpression extends AbstractExpression
return $this->reflector;
}
if (\is_array($callable)) {
if (!method_exists($callable[0], $callable[1])) {
// __call()
return [null, []];
}
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (\is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
$callable = [$callable, '__invoke'];
} elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$class = substr($callable, 0, $pos);
$method = substr($callable, $pos + 2);
if (!method_exists($class, $method)) {
// __staticCall()
return [null, []];
}
$r = new \ReflectionMethod($callable);
$callable = [$class, $method];
} else {
$r = new \ReflectionFunction($callable);
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
}
return $this->reflector = [$r, $callable];
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
$r = new \ReflectionMethod($callable[0], $callable[1]);
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
}
$checkVisibility = $callable instanceof \Closure;
try {
$closure = \Closure::fromCallable($callable);
} catch (\TypeError $e) {
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
}
$r = new \ReflectionFunction($closure);
if (false !== strpos($r->name, '{closure}')) {
return $this->reflector = [$r, $callable, 'Closure'];
}
if ($object = $r->getClosureThis()) {
$callable = [$object, $r->name];
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
} elseif ($class = $r->getClosureScopeClass()) {
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
} else {
$callable = $callableName = $r->name;
}
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
$callable = $r->getClosure();
}
return $this->reflector = [$r, $callable, $callableName];
}
}

View File

@@ -58,7 +58,7 @@ class Parser
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
{
$vars = get_object_vars($this);
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']);
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']);
$this->stack[] = $vars;
// node visitors
@@ -78,7 +78,6 @@ class Parser
$this->blockStack = [];
$this->importedSymbols = [[]];
$this->embeddedTemplates = [];
$this->varNameSalt = 0;
try {
$body = $this->subparse($test, $dropNeedle);

View File

@@ -19,17 +19,27 @@ namespace Twig\Sandbox;
interface SecurityPolicyInterface
{
/**
* @param string[] $tags
* @param string[] $filters
* @param string[] $functions
*
* @throws SecurityError
*/
public function checkSecurity($tags, $filters, $functions): void;
/**
* @param object $obj
* @param string $method
*
* @throws SecurityNotAllowedMethodError
*/
public function checkMethodAllowed($obj, $method): void;
/**
* @param object $obj
* @param string $property
*
* @throws SecurityNotAllowedPropertyError
*/
public function checkPropertyAllowed($obj, $method): void;
public function checkPropertyAllowed($obj, $property): void;
}

View File

@@ -2,6 +2,8 @@
// check for development mode
$DEV_MODE = (getenv('DEV_MODE') == 'y');
// check for demo mode
$DEMO_MODE = (getenv('DEMO_MODE') == 'y');
// Slave does not serve UI
/* if (!preg_match('/y|yes/i', getenv('MASTER'))) {
@@ -44,21 +46,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/CSSminifierExtended.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/array_merge_real.php';
// Minify JS
use MatthiasMullie\Minify;
$js_minifier = new JSminifierExtended();
$js_dir = array_diff(scandir('/web/js/build'), array('..', '.'));
foreach ($js_dir as $js_file) {
$js_minifier->add('/web/js/build/' . $js_file);
}
// Minify CSS
$css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
foreach ($css_dir as $css_file) {
$css_minifier->add('/web/css/build/' . $css_file);
}
// U2F API + T/HOTP API
// u2f - deprecated, should be removed
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
@@ -195,9 +182,65 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
// Set language
if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$header_lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
if (array_key_exists($header_lang, $AVAILABLE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $header_lang;
// regex inspired from @GabrielAnderson on http://stackoverflow.com/questions/6038236/http-accept-language
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})*)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
$langs = $lang_parse[1];
$ranks = $lang_parse[4];
// (create an associative array 'language' => 'preference')
$lang2pref = array();
for ($i=0; $i<count($langs); $i++) {
$lang2pref[strtolower($langs[$i])] = (float) (!empty($ranks[$i]) ? $ranks[$i] : 1);
}
// (comparison function for uksort)
$cmpLangs = function ($a, $b) use ($lang2pref) {
if ($lang2pref[$a] > $lang2pref[$b])
return -1;
elseif ($lang2pref[$a] < $lang2pref[$b])
return 1;
elseif (strlen($a) > strlen($b))
return -1;
elseif (strlen($a) < strlen($b))
return 1;
else
return 0;
};
// sort the languages by prefered language and by the most specific region
uksort($lang2pref, $cmpLangs);
// generate language array without the region part
$AVAILABLE_BASE_LANGUAGES=array();
foreach ($AVAILABLE_LANGUAGES as $code => $lang) {
$base_code = substr($code, 0, 2);
if (!array_key_exists($base_code, $AVAILABLE_BASE_LANGUAGES)) {
$AVAILABLE_BASE_LANGUAGES[$base_code] = $code;
}
}
// Find a perfect match or partial match
// Match en-gb or en
foreach ($lang2pref as $lang => $q) {
if (array_key_exists($lang, $AVAILABLE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $lang;
break;
} elseif (array_key_exists($lang, $AVAILABLE_BASE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[$lang];
break;
}
}
// Try suggest match
// e.g. suggest en-gb when only en-us is provided
if (!isset($_SESSION['mailcow_locale'])) {
foreach ($lang2pref as $lang => $q) {
if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)];
break;
}
}
}
}
else {
@@ -215,13 +258,14 @@ if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $AVAILABLE_LANGUAGES
/*
* load language
*/
$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.json'), true);
$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en-gb.json'), true);
$langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json';
if(file_exists($langFile)) {
$lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true));
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php';
@@ -256,4 +300,29 @@ if (isset($_SESSION['mailcow_cc_role'])) {
// }
acl('to_session');
}
// init frontend
// Minify JS
use MatthiasMullie\Minify;
$js_minifier = new JSminifierExtended();
$js_dir = array_diff(scandir('/web/js/build'), array('..', '.'));
// Minify CSS
$css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
// get customized ui data
$UI_TEXTS = customize('get', 'ui_texts');
// minify bootstrap theme
if (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');
// minify css build files
foreach ($css_dir as $css_file) {
$css_minifier->add('/web/css/build/' . $css_file);
}
// minify js build files
foreach ($js_dir as $js_file) {
$js_minifier->add('/web/js/build/' . $js_file);
}

View File

@@ -76,41 +76,41 @@ $autodiscover_config = array(
$DETECT_LANGUAGE = true;
// Change default language
$DEFAULT_LANG = 'en';
$DEFAULT_LANG = 'en-gb';
// Available languages
// https://www.iso.org/obp/ui/#search
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
// https://en.wikipedia.org/wiki/IETF_language_tag
$AVAILABLE_LANGUAGES = array(
'cs' => 'Čeština (Czech)',
'da' => 'Danish (Dansk)',
'de' => 'Deutsch (German)',
'en' => 'English',
'es' => 'Español (Spanish)',
'fi' => 'Suomi (Finish)',
'fr' => 'Français (French)',
'hu' => 'Magyar (Hungarian)',
'it' => 'Italiano (Italian)',
'ko' => '한국어 (Korean)',
'lv' => 'latviešu (Latvian)',
'nl' => 'Nederlands (Dutch)',
'pl' => 'Język Polski (Polish)',
'pt' => 'Português (Portuguese)',
'ro' => 'Română (Romanian)',
'ru' => 'Pусский (Russian)',
'sk' => 'Slovenčina (Slovak)',
'sv' => 'Svenska (Swedish)',
'tr' => 'Türkçe (Turkish)',
'uk' => 'Українська (Ukrainian)',
'zh' => '中文 (Chinese)'
// 'ca-es' => 'Català (Catalan)',
'cs-cz' => 'Čeština (Czech)',
'da-dk' => 'Danish (Dansk)',
'de-de' => 'Deutsch (German)',
'en-gb' => 'English',
'es-es' => 'Español (Spanish)',
'fi-fi' => 'Suomi (Finish)',
'fr-fr' => 'Français (French)',
'hu-hu' => 'Magyar (Hungarian)',
'it-it' => 'Italiano (Italian)',
'ko-kr' => '한국어 (Korean)',
'lv-lv' => 'latviešu (Latvian)',
'nl-nl' => 'Nederlands (Dutch)',
'pl-pl' => 'Język Polski (Polish)',
'pt-pt' => 'Português (Portuguese)',
'ro-ro' => 'Română (Romanian)',
'ru-ru' => 'Pусский (Russian)',
'sk-sk' => 'Slovenčina (Slovak)',
'sv-se' => 'Svenska (Swedish)',
'tr-tr' => 'Türkçe (Turkish)',
'uk-ua' => 'Українська (Ukrainian)',
'zh-cn' => '简体中文 (Simplified Chinese)',
'zh-tw' => '繁體中文 (Traditional Chinese)',
);
// Change theme (default: lumen)
// Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone,
// simplex, slate, spacelab, superhero, united, yeti
// See https://bootswatch.com/
// WARNING: Only lumen is loaded locally. Enabling any other theme, will download external sources.
$DEFAULT_THEME = 'lumen';
// default theme is lumen
// additional themes can be found here: https://bootswatch.com/
// copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css
$UI_THEME = "lumen";
// Show DKIM private keys - false by default
$SHOW_DKIM_PRIV_KEYS = false;

View File

@@ -8,7 +8,7 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) {
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin');
header('Location: /debug');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {

Some files were not shown because too many files have changed in this diff Show More