Compare commits

..

90 Commits

Author SHA1 Message Date
Niklas Meyer
640f535e99 Merge pull request #5019 from mailcow/staging
2023-01a
2023-01-25 16:29:22 +01:00
Niklas Meyer
05d1a974eb Merge pull request #5003 from mailcow/feat/acme-skip-ip-check
[Acme] Implemented IP Check Bypass properly
2023-01-25 16:10:11 +01:00
Niklas Meyer
99e38d81b1 Removed Integration Tests 2023-01-25 16:09:15 +01:00
FreddleSpl0it
ed7b384e24 [Web] fix queue btn showing undefined 2023-01-25 09:34:12 +01:00
FreddleSpl0it
5439ea1010 Merge branch 'staging' of https://github.com/mailcow/mailcow-dockerized into staging 2023-01-25 09:32:27 +01:00
FreddleSpl0it
b719982504 partial rollback of dockerapi 2023-01-25 09:31:22 +01:00
milkmaker
8281d3fa55 [Web] Updated lang.da-dk.json (#5020)
Co-authored-by: osos <osos@openeyes.dk>

Co-authored-by: osos <osos@openeyes.dk>
2023-01-24 20:18:17 +01:00
FreddleSpl0it
9ba65a572e [Web] add missing template var for dadmins 2023-01-24 10:13:30 +01:00
FreddleSpl0it
afddcf7f3b replace nullnull.org with fuzzy.mailcow.email 2023-01-24 09:49:49 +01:00
Niklas Meyer
294569f5c9 Merge pull request #5015 from mailcow/feat/nc-install-fix
Fix nextcloud install
2023-01-22 16:17:18 +01:00
Peter
ef6452cf55 Fix installation of nextcloud 2023-01-22 15:06:36 +01:00
renovate[bot]
9af40eba10 Update dependency nextcloud/server to v25.0.3 (#4996)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-20 15:37:12 +01:00
renovate[bot]
1b3a13ca19 Update alpine Docker tag to v3.17 (#4997)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-20 15:36:52 +01:00
Patrick Schult
71cc607de6 Merge pull request #5006 from mailcow/staging
Revert Docker Compose detection commits
2023-01-19 16:04:50 +01:00
FreddleSpl0it
2ebd8345df Revert "[Generate] Refactor compose version detection using regex"
This reverts commit 4c6f8c4f60.
2023-01-19 15:58:22 +01:00
FreddleSpl0it
f5baeb31c1 Revert "[Update.sh] Implemented optimized Regex Compose Detection"
This reverts commit a76e6b32f7.
2023-01-19 15:57:49 +01:00
DerLinkman
5abda44bc6 Merge branch 'staging' 2023-01-19 14:07:55 +01:00
DerLinkman
520d070081 [Compose] Removed OOMKillDisabled from dockerapi 2023-01-19 14:04:55 +01:00
Niklas Meyer
86beba6f5a Merge pull request #4995 from mailcow/staging
2023-01
2023-01-19 12:25:57 +01:00
Niklas Meyer
f0d9948aee Merge pull request #4991 from mailcow/feat/dovecot-2.3.20
[Dovecot] Update to 2.3.20
2023-01-19 11:31:59 +01:00
DerLinkman
8e3d2f7010 [SOGo] Update to newer 5.8.0 (fix for macOS Caldav Bug) 2023-01-19 11:28:03 +01:00
Niklas Meyer
fc1c5a505d Merge pull request #4992 from mailcow/feat/phpfpm-renovate
Update composer and allow renovate for updating Dockerfiles
2023-01-19 10:54:02 +01:00
Niklas Meyer
18cb06fbc7 Merge pull request #4993 from mailcow/feat/renovate-docker-compose
Update renovate config
2023-01-18 21:22:15 +01:00
Peter
1af785a94f Enable dependencyDashboard
Add label for PRs
Add docker-compose manager
2023-01-18 19:37:09 +01:00
Peter
7626becb38 Add regex for matchstring line in Dockerfiles 2023-01-17 19:48:42 +01:00
Peter
5d5e959729 Add regex for matchstring line in Dockerfiles
Update composer to 2.5.1
2023-01-17 19:45:32 +01:00
Niklas Meyer
49bbdd064e Merge pull request #4989 from mailcow/feat/nextcloud-script-overhaul
[Nextcloud] Updated and improved script (implemented -u and more)
2023-01-17 16:34:58 +01:00
DerLinkman
9279ee2e76 [Dovecot] Update to 2.3.20 2023-01-17 16:23:31 +01:00
DerLinkman
a76e6b32f7 [Update.sh] Implemented optimized Regex Compose Detection 2023-01-16 16:02:56 +01:00
DerLinkman
4c6f8c4f60 [Generate] Refactor compose version detection using regex 2023-01-16 15:54:29 +01:00
FreddleSpl0it
826d32413b Merge branch 'staging' of https://github.com/mailcow/mailcow-dockerized into staging 2023-01-16 15:38:48 +01:00
DerLinkman
b6799d9fcb Feature: Add developer mode option to generate_config.sh 2023-01-16 15:38:42 +01:00
FreddleSpl0it
8782304e8d [Web] show fold/unfold action if child rows exists 2023-01-16 15:38:35 +01:00
DerLinkman
9c55d46bc6 [Nextcloud] Updated and improved script (implemented -u and more) 2023-01-16 14:35:15 +01:00
FreddleSpl0it
099db33e44 [Web] disable datatable default row click listener 2023-01-16 11:41:34 +01:00
DerLinkman
5c57df4669 [Acme] Implemented IP Check Bypass properly 2023-01-16 10:10:20 +01:00
FreddleSpl0it
152431a7d7 [Web] fix Spamfilter flag fwdhosts wrong naming 2023-01-16 09:24:10 +01:00
FreddleSpl0it
36fa5dc633 [Web] fix domain admins cant delete tags 2023-01-16 09:07:28 +01:00
renovate[bot]
814f4aed15 Update thollander/actions-comment-pull-request action to v2.3.1 (#4986)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 10:05:55 +01:00
milkmaker
e990856629 [Web] Updated lang.fr-fr.json (#4972)
Co-authored-by: Frederic Ollivier <fredol@me.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

Co-authored-by: Frederic Ollivier <fredol@me.com>
2023-01-09 18:03:41 +01:00
Niklas Meyer
c97afbfa0b Merge pull request #4943 from sivn/staging
[Web] added missing unban action
2023-01-09 12:41:32 +01:00
Niklas Meyer
93b3e0302a Merge pull request #4964 from mailcow/feat/renovate-gosu
Update gosu and allow renovate for updating Dockerfiles
2023-01-09 12:40:57 +01:00
Niklas Meyer
27c87de4ed Merge pull request #4966 from mailcow/fix/bs5
BS5 UI fixes
2023-01-09 12:40:14 +01:00
DerLinkman
028ad4ceb9 changed language string (de) 2023-01-09 10:43:42 +01:00
FreddleSpl0it
e501642b8e [Web] fix mailboxtable sort by quota 2023-01-09 08:04:16 +01:00
FreddleSpl0it
7966f010a2 [Web] switch table length + filter field positions 2023-01-06 15:03:04 +01:00
FreddleSpl0it
b22f74cb59 [Web] persist table settings + fix quarantine sort 2023-01-06 13:45:52 +01:00
FreddleSpl0it
c928948b15 [Web] use saved password policy for pwgen 2023-01-06 13:18:59 +01:00
FreddleSpl0it
606eaad8f7 [Web] set correct type for routing password input 2023-01-06 12:48:37 +01:00
FreddleSpl0it
c44281f62d [Web] set domain tab default active 2023-01-06 12:43:10 +01:00
FreddleSpl0it
1e98784eee [Web] Opt-In for third party ip_check 2023-01-06 12:09:15 +01:00
FreddleSpl0it
dd9296ffc2 [Web] fix extend_sender_acl issue for domainadmins 2023-01-06 11:07:44 +01:00
FreddleSpl0it
fc0e6b6efb [Web] fix quarantine darkmode style 2023-01-06 09:21:14 +01:00
FreddleSpl0it
68f5fbf65c [Web] remove remote Google fonts from lumen theme 2023-01-06 09:11:51 +01:00
FreddleSpl0it
9727e4084f [Web] load public ip on click and add curl timeout 2023-01-06 08:40:26 +01:00
milkmaker
5c2f48e94c [Web] Updated lang.zh-cn.json (#4965)
Co-authored-by: 雨 <luotianyi@luotianyi.me>

Co-authored-by: 雨 <luotianyi@luotianyi.me>
2023-01-05 17:40:36 +01:00
Peter
cb098df743 Update gosu to 1.16
Change ENV to ARG
Add matchstring line
2023-01-04 19:10:32 +01:00
Peter
b3c54ed07a Add regex for matchstring line in Dockerfiles 2023-01-04 19:09:23 +01:00
Peter
c601eca25d Update thollander/actions-comment-pull-request action to v2.3.0 2023-01-04 18:54:19 +01:00
Patrick Schult
48a13255f3 Merge pull request #4948 from tomudding/fix/sorting-mail-configuration-datatables
Fix sorting of mail configuration DataTables
2023-01-04 13:47:22 +01:00
milkmaker
08f93c7d58 Translations update from Weblate (#4960)
* [Web] Updated lang.zh-cn.json

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

* [Web] Updated lang.en-gb.json

Co-authored-by: Peter <magic@kthx.at>

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

Co-authored-by: Peter <magic@kthx.at>

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

Co-authored-by: Stefano <stefano.vassena@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

Co-authored-by: 雨 <luotianyi@luotianyi.me>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Stefano <stefano.vassena@gmail.com>
2023-01-03 18:12:18 +01:00
Niklas Meyer
e5c9752681 Merge pull request #4956 from mailcow/feat/nextcloud-renovate
Update nextcloud helperscript to use renovate
2023-01-02 14:36:29 +01:00
Peter
afa1ed1eff Add matchstring line for regex
Update nextcloud to 25.0.2
change download URLs
2022-12-31 17:13:38 +01:00
Peter
072cbe62de Enable regex as manager
Add regex for matchstring line
2022-12-31 17:11:16 +01:00
renovate[bot]
9fe8bfadf3 Update actions/stale action to v7 (#4953)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-31 16:49:21 +01:00
renovate[bot]
75e4953070 Update mugi111/tweet-trigger-release action to v1.2 (#4952)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-31 16:49:02 +01:00
Tom Udding
de30650dc7 Sort other mailbox DataTables also descending by ID
Also removes the extra non-usable sort option.
2022-12-30 16:38:02 +01:00
Tom Udding
690c34bc1d Sort sync jobs DataTable based on ID
By setting the default column to perform the sort on, the additional
sort option for the first (hidden) column is also removed.
2022-12-30 16:22:52 +01:00
Vincent Simon
4d2e32ee40 [Web] added missing unban action 2022-12-29 18:24:15 +01:00
FreddleSpl0it
02b2988beb [Web] fix typo in SASL table logs 2022-12-27 13:56:09 +01:00
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
102 changed files with 3752 additions and 3050 deletions

22
.github/renovate.json vendored
View File

@@ -1,13 +1,31 @@
{ {
"enabled": true, "enabled": true,
"timezone": "Europe/Berlin", "timezone": "Europe/Berlin",
"dependencyDashboard": false, "dependencyDashboard": true,
"dependencyDashboardTitle": "Renovate Dashboard", "dependencyDashboardTitle": "Renovate Dashboard",
"commitBody": "Signed-off-by: milkmaker <milkmaker@mailcow.de>", "commitBody": "Signed-off-by: milkmaker <milkmaker@mailcow.de>",
"rebaseWhen": "auto", "rebaseWhen": "auto",
"labels": ["renovate"],
"assignees": [ "assignees": [
"@magiccc" "@magiccc"
], ],
"baseBranches": ["staging"], "baseBranches": ["staging"],
"enabledManagers": ["github-actions"] "enabledManagers": ["github-actions", "regex", "docker-compose"],
"ignorePaths": [
"data\/web\/inc\/lib\/vendor\/matthiasmullie\/minify\/**"
],
"regexManagers": [
{
"fileMatch": ["^helper-scripts\/nextcloud.sh$"],
"matchStrings": [
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?( extractVersion=(?<extractVersion>.*?))?\\s.*?_VERSION=(?<currentValue>.*)"
]
},
{
"fileMatch": ["(^|/)Dockerfile[^/]*$"],
"matchStrings": [
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s(ENV|ARG) .*?_VERSION=(?<currentValue>.*)\\s"
]
}
]
} }

View File

@@ -10,7 +10,7 @@ jobs:
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
steps: steps:
- name: Send message - name: Send message
uses: thollander/actions-comment-pull-request@main uses: thollander/actions-comment-pull-request@v2.3.1
with: with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }} GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: | message: |

View File

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

View File

@@ -1,63 +0,0 @@
name: mailcow Integration Tests
on:
push:
branches: [ "master", "staging" ]
workflow_dispatch:
permissions:
contents: read
jobs:
integration_tests:
runs-on: ubuntu-latest
steps:
- name: Setup Ansible
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update
sudo apt-get install python3 python3-pip git
sudo pip3 install ansible
- name: Prepair Test Environment
run: |
git clone https://github.com/mailcow/mailcow-integration-tests.git --branch $(curl -sL https://api.github.com/repos/mailcow/mailcow-integration-tests/releases/latest | jq -r '.tag_name') --single-branch .
./fork_check.sh
./ci.sh
./ci-pip-requirements.sh
env:
VAULT_PW: ${{ secrets.MAILCOW_TESTS_VAULT_PW }}
VAULT_FILE: ${{ secrets.MAILCOW_TESTS_VAULT_FILE }}
- name: Start Integration Test Server
run: |
./fork_check.sh
ansible-playbook mailcow-start-server.yml --diff
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
ANSIBLE_HOST_KEY_CHECKING: 'false'
- name: Setup Integration Test Server
run: |
./fork_check.sh
sleep 30
ansible-playbook mailcow-setup-server.yml --private-key id_ssh_rsa --diff
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
ANSIBLE_HOST_KEY_CHECKING: 'false'
- name: Run Integration Tests
run: |
./fork_check.sh
ansible-playbook mailcow-integration-tests.yml --private-key id_ssh_rsa --diff
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
ANSIBLE_HOST_KEY_CHECKING: 'false'
- name: Delete Integration Test Server
if: always()
run: |
./fork_check.sh
ansible-playbook mailcow-delete-server.yml --diff
env:
PY_COLORS: '1'
ANSIBLE_FORCE_COLOR: '1'
ANSIBLE_HOST_KEY_CHECKING: 'false'

View File

@@ -11,7 +11,7 @@ jobs:
run: | run: |
RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name') RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name')
- name: Tweet-trigger-publish-release - name: Tweet-trigger-publish-release
uses: mugi111/tweet-trigger-release@v1.1 uses: mugi111/tweet-trigger-release@v1.2
with: with:
consumer_key: ${{ secrets.CONSUMER_KEY }} consumer_key: ${{ secrets.CONSUMER_KEY }}
consumer_secret: ${{ secrets.CONSUMER_SECRET }} consumer_secret: ${{ secrets.CONSUMER_SECRET }}

View File

@@ -1,6 +1,5 @@
# mailcow: dockerized - 🐮 + 🐋 = 💕 # mailcow: dockerized - 🐮 + 🐋 = 💕
[![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/) [![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) [![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)

View File

@@ -213,11 +213,13 @@ while true; do
done done
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig') ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
# Start IP detection # Start IP detection
log_f "Detecting IP addresses..." log_f "Detecting IP addresses..."
IPV4=$(get_ipv4) IPV4=$(get_ipv4)
IPV6=$(get_ipv6) IPV6=$(get_ipv6)
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
fi
######################################### #########################################
# IP and webroot challenge verification # # IP and webroot challenge verification #

View File

@@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \
fastapi \ fastapi \
uvicorn \ uvicorn \
aiodocker \ aiodocker \
docker \
redis redis
COPY docker-entrypoint.sh /app/ COPY docker-entrypoint.sh /app/

View File

@@ -1,5 +1,6 @@
from fastapi import FastAPI, Response, Request from fastapi import FastAPI, Response, Request
import aiodocker import aiodocker
import docker
import psutil import psutil
import sys import sys
import re import re
@@ -9,11 +10,38 @@ import json
import asyncio import asyncio
import redis import redis
from datetime import datetime from datetime import datetime
import logging
from logging.config import dictConfig
log_config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(levelprefix)s %(asctime)s %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
},
"loggers": {
"api-logger": {"handlers": ["default"], "level": "INFO"},
},
}
dictConfig(log_config)
containerIds_to_update = [] containerIds_to_update = []
host_stats_isUpdating = False host_stats_isUpdating = False
app = FastAPI() app = FastAPI()
logger = logging.getLogger('api-logger')
@app.get("/host/stats") @app.get("/host/stats")
@@ -21,18 +49,15 @@ async def get_host_update_stats():
global host_stats_isUpdating global host_stats_isUpdating
if host_stats_isUpdating == False: if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats()) asyncio.create_task(get_host_stats())
host_stats_isUpdating = True host_stats_isUpdating = True
while True: while True:
if redis_client.exists('host_stats'): if redis_client.exists('host_stats'):
break break
print("wait for host_stats results")
await asyncio.sleep(1.5) await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats')) stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json") return Response(content=json.dumps(stats, indent=4), media_type="application/json")
@@ -106,14 +131,14 @@ async def post_containers(container_id : str, post_action : str, request: Reques
else: else:
api_call_method_name = '__'.join(['container_post', str(post_action) ]) api_call_method_name = '__'.join(['container_post', str(post_action) ])
docker_utils = DockerUtils(async_docker_client) docker_utils = DockerUtils(sync_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")) 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)) logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return await api_call_method(container_id, request_json) return api_call_method(container_id, request_json)
except Exception as e: except Exception as e:
print("error - container_post: %s" % str(e)) logger.error("error - container_post: %s" % str(e))
res = { res = {
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
@@ -152,398 +177,289 @@ class DockerUtils:
self.docker_client = docker_client self.docker_client = docker_client
# api call: container_post - post_action: stop # api call: container_post - post_action: stop
async def container_post__stop(self, container_id, request_json): def container_post__stop(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.stop()
await container.stop()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: start
async def container_post__start(self, container_id, request_json): def container_post__start(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.start()
await container.start()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: restart # api call: container_post - post_action: restart
async def container_post__restart(self, container_id, request_json): def container_post__restart(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.restart()
await container.restart()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: top # api call: container_post - post_action: top
async def container_post__top(self, container_id, request_json): def container_post__top(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: res = { 'type': 'success', 'msg': container.top()}
ps_exec = await container.exec("ps") return Response(content=json.dumps(res, indent=4), media_type="application/json")
async with ps_exec.start(detach=False) as stream: # api call: container_post - post_action: stats
ps_return = await stream.read_out() def container_post__stats(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
exec_details = await ps_exec.inspect() for stat in container.stats(decode=True, stream=True):
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: res = { 'type': 'success', 'msg': stat}
res = { return Response(content=json.dumps(res, indent=4), media_type="application/json")
'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 # api call: container_post - post_action: exec - cmd: mailq - task: delete
async def container_post__exec__mailq__delete(self, container_id, request_json): def container_post__exec__mailq__delete(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") 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: if filtered_qids:
flagged_qids = ['-d %s' % i for i in 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 self.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 # api call: container_post - post_action: exec - cmd: mailq - task: hold
async def container_post__exec__mailq__hold(self, container_id, request_json): def container_post__exec__mailq__hold(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") 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: if filtered_qids:
flagged_qids = ['-h %s' % i for i in 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 self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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 # api call: container_post - post_action: exec - cmd: mailq - task: cat
async def container_post__exec__mailq__cat(self, container_id, request_json): def container_post__exec__mailq__cat(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") 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: if filtered_qids:
sanitized_string = str(' '.join(filtered_qids)) sanitized_string = str(' '.join(filtered_qids));
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') if not postcat_return:
return await exec_run_handler('utf8_text_only', postcat_exec) postcat_return = 'err: invalid'
return exec_run_handler('utf8_text_only', postcat_return)
# api call: container_post - post_action: exec - cmd: mailq - task: unhold # api call: container_post - post_action: exec - cmd: mailq - task: unhold
async def container_post__exec__mailq__unhold(self, container_id, request_json): def container_post__exec__mailq__unhold(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") 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: if filtered_qids:
flagged_qids = ['-H %s' % i for i in 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 self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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 # api call: container_post - post_action: exec - cmd: mailq - task: deliver
async def container_post__exec__mailq__deliver(self, container_id, request_json): def container_post__exec__mailq__deliver(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") 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: if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids] flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): for i in flagged_qids:
if container._id == container_id: postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
for i in flagged_qids: # todo: check each exit code
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
async with postsuper_r_exec.start(detach=False) as stream: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: exec - cmd: mailq - task: list
async def container_post__exec__mailq__list(self, container_id, request_json): def container_post__exec__mailq__list(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix') return exec_run_handler('utf8_text_only', mailq_return)
return await exec_run_handler('utf8_text_only', mailq_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: flush # api call: container_post - post_action: exec - cmd: mailq - task: flush
async def container_post__exec__mailq__flush(self, container_id, request_json): def container_post__exec__mailq__flush(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix') return exec_run_handler('generic', postqueue_r)
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete # api call: container_post - post_action: exec - cmd: mailq - task: super_delete
async def container_post__exec__mailq__super_delete(self, container_id, request_json): def container_post__exec__mailq__super_delete(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"]) return exec_run_handler('generic', postsuper_r)
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan # api call: container_post - post_action: exec - cmd: system - task: fts_rescan
async def container_post__exec__system__fts_rescan(self, container_id, request_json): def container_post__exec__system__fts_rescan(self, container_id, request_json):
if 'username' in request_json: if 'username' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') if rescan_return.exit_code == 0:
async with rescan_exec.start(detach=False) as stream: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
rescan_return = await stream.read_out() return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
exec_details = await rescan_exec.inspect() res = { 'type': 'warning', 'msg': 'fts_rescan error'}
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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: if 'all' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') if rescan_return.exit_code == 0:
async with rescan_exec.start(detach=False) as stream: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
rescan_return = await stream.read_out() return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
exec_details = await rescan_exec.inspect() res = { 'type': 'warning', 'msg': 'fts_rescan error'}
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: exec - cmd: system - task: df
async def container_post__exec__system__df(self, container_id, request_json): def container_post__exec__system__df(self, container_id, request_json):
if 'dir' in request_json: if 'dir' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._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')
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') if df_return.exit_code == 0:
async with df_exec.start(detach=False) as stream: return df_return.output.decode('utf-8').rstrip()
df_return = await stream.read_out() else:
return "0,0,0,0,0,0"
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 # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
async def container_post__exec__system__mysql_upgrade(self, container_id, request_json): def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') if sql_return.exit_code == 0:
async with sql_exec.start(detach=False) as stream: matched = False
sql_return = await stream.read_out() for line in sql_return.output.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
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:
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
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
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
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
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
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
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
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
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()
matched = False
if "OK" in rspamd_password_return.data.decode('utf-8'):
matched = True matched = True
await container.restart() if matched:
res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
container.restart()
res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.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, request_json):
for container in self.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:
res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.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.output.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, request_json):
for container in self.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)
# api call: container_post - post_action: exec - cmd: reload - task: postfix
def container_post__exec__reload__postfix(self, container_id, request_json):
for container in self.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)
# api call: container_post - post_action: exec - cmd: reload - task: nginx
def container_post__exec__reload__nginx(self, container_id, request_json):
for container in self.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)
# api call: container_post - post_action: exec - cmd: sieve - task: list
def container_post__exec__sieve__list(self, container_id, request_json):
if 'username' in request_json:
for container in self.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)
# api call: container_post - post_action: exec - cmd: sieve - task: print
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 self.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)
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
def container_post__exec__maildir__cleanup(self, container_id, request_json):
if 'maildir' in request_json:
for container in self.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)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
def container_post__exec__rspamd__worker_password(self, container_id, request_json):
if 'raw' in request_json:
for container in self.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")
if matched: matched = False
res = { for line in cmd_response.split("\n"):
'type': 'success', if '$2$' in line:
'msg': 'command completed successfully' hash = line.strip()
} hash_out = re.search('\$2\$.+$', hash).group(0)
return Response(content=json.dumps(res, indent=4), media_type="application/json") rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
else: rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
res = { cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
'type': 'danger', cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
'msg': 'command did not complete' if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
} container.restart()
return Response(content=json.dumps(res, indent=4), media_type="application/json") matched = True
if matched:
res = { 'type': 'success', 'msg': 'command completed successfully' }
logger.info('success changing Rspamd password')
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
logger.error('failed changing Rspamd password')
res = { 'type': 'danger', 'msg': 'command did not complete' }
return Response(content=json.dumps(res, indent=4), media_type="application/json")
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
async def exec_run_handler(type, exec_obj): def recv_socket_data(c_socket, timeout):
async with exec_obj.start(detach=False) as stream: c_socket.setblocking(0)
exec_return = await stream.read_out() 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)
if exec_return == None:
exec_return = ""
else:
exec_return = exec_return.data.decode('utf-8')
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
except Exception as e:
logger.error("error - exec_cmd_container: %s" % str(e))
traceback.print_exc(file=sys.stdout)
def exec_run_handler(type, output):
if type == 'generic': if type == 'generic':
exec_details = await exec_obj.inspect() if output.exit_code == 0:
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: res = { 'type': 'success', 'msg': 'command completed successfully' }
res = {
"type": "success",
"msg": "command completed successfully"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
res = { res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
"type": "success",
"msg": "'command failed: " + exec_return
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only': if type == 'utf8_text_only':
return Response(content=exec_return, media_type="text/plain") return Response(content=output.output.decode('utf-8'), media_type="text/plain")
async def get_host_stats(wait=5): async def get_host_stats(wait=5):
global host_stats_isUpdating global host_stats_isUpdating
@@ -570,12 +486,10 @@ async def get_host_stats(wait=5):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
host_stats_isUpdating = False host_stats_isUpdating = False
async def get_container_stats(container_id, wait=5, stop=False): async def get_container_stats(container_id, wait=5, stop=False):
global containerIds_to_update global containerIds_to_update
@@ -598,13 +512,11 @@ async def get_container_stats(container_id, wait=5, stop=False):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
else: else:
res = { res = {
"type": "danger", "type": "danger",
"msg": "no or invalid id defined" "msg": "no or invalid id defined"
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
if stop == True: if stop == True:
@@ -615,9 +527,13 @@ async def get_container_stats(container_id, wait=5, stop=False):
await get_container_stats(container_id, wait=0, stop=True) await get_container_stats(container_id, wait=0, stop=True)
if os.environ['REDIS_SLAVEOF_IP'] != "": if os.environ['REDIS_SLAVEOF_IP'] != "":
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0) redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
else: else:
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0) redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
logger.info('DockerApi started')

View File

@@ -2,9 +2,12 @@ FROM debian:bullseye-slim
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG DOVECOT=2.3.19.1 # renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced
ARG DOVECOT=2.3.20
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
ENV LC_ALL C ENV LC_ALL C
ENV GOSU_VERSION 1.14
# Add groups and users before installing Dovecot to not break compatibility # Add groups and users before installing Dovecot to not break compatibility
RUN groupadd -g 5000 vmail \ RUN groupadd -g 5000 vmail \

View File

@@ -1,12 +1,18 @@
FROM php:8.1-fpm-alpine3.17 FROM php:8.1-fpm-alpine3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.22 # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
ENV IMAGICK_PECL 3.7.0 ARG APCU_PECL_VERSION=5.1.22
ENV MAILPARSE_PECL 3.1.4 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced
ENV MEMCACHED_PECL 3.2.0 ARG IMAGICK_PECL_VERSION=3.7.0
ENV REDIS_PECL 5.3.7 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced
ENV COMPOSER 2.4.4 ARG MAILPARSE_PECL_VERSION=3.1.4
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced
ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
ARG REDIS_PECL_VERSION=5.3.7
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
ARG COMPOSER_VERSION=2.5.1
RUN apk add -U --no-cache autoconf \ RUN apk add -U --no-cache autoconf \
aspell-dev \ aspell-dev \
@@ -55,11 +61,11 @@ RUN apk add -U --no-cache autoconf \
samba-client \ samba-client \
zlib-dev \ zlib-dev \
tzdata \ tzdata \
&& pecl install mailparse-${MAILPARSE_PECL} \ && pecl install APCu-${APCU_PECL_VERSION} \
&& pecl install redis-${REDIS_PECL} \ && pecl install imagick-${IMAGICK_PECL_VERSION} \
&& pecl install memcached-${MEMCACHED_PECL} \ && pecl install mailparse-${MAILPARSE_PECL_VERSION} \
&& pecl install APCu-${APCU_PECL} \ && pecl install memcached-${MEMCACHED_PECL_VERSION} \
&& pecl install imagick-${IMAGICK_PECL} \ && pecl install redis-${REDIS_PECL_VERSION} \
&& docker-php-ext-enable apcu imagick memcached mailparse redis \ && docker-php-ext-enable apcu imagick memcached mailparse redis \
&& pecl clear-cache \ && pecl clear-cache \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
@@ -72,7 +78,7 @@ RUN apk add -U --no-cache autoconf \
&& 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-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-configure imap --with-imap --with-imap-ssl \
&& docker-php-ext-install -j 4 imap \ && docker-php-ext-install -j 4 imap \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER} \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \
&& apk del --purge autoconf \ && apk del --purge autoconf \

View File

@@ -3,8 +3,9 @@ LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/ ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
ENV LC_ALL C ENV LC_ALL C
ENV GOSU_VERSION 1.14
# Prerequisites # Prerequisites
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \

View File

@@ -2,7 +2,8 @@ FROM solr:7.7-slim
USER root USER root
ENV GOSU_VERSION 1.11 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
COPY solr.sh / COPY solr.sh /
COPY solr-config-7.7.0.xml / COPY solr-config-7.7.0.xml /

View File

@@ -175,7 +175,7 @@ BAD_SUBJECT_00 {
type = "header"; type = "header";
header = "subject"; header = "subject";
regexp = true; regexp = true;
map = "http://nullnull.org/bad-subject-regex.txt"; map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
score = 6.0; score = 6.0;
symbols_set = ["BAD_SUBJECT_00"]; symbols_set = ["BAD_SUBJECT_00"];
} }

View File

@@ -10,9 +10,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa(); $tfa_data = get_tfa();
$fido2_data = fido2(array("action" => "get_friendly_names")); $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/site/admin.js');
$js_minifier->add('/web/js/presets/rspamd.js'); $js_minifier->add('/web/js/presets/rspamd.js');
@@ -89,7 +86,6 @@ $template_data = [
'tfa_id' => @$_SESSION['tfa_id'], 'tfa_id' => @$_SESSION['tfa_id'],
'fido2_cid' => @$_SESSION['fido2_cid'], 'fido2_cid' => @$_SESSION['fido2_cid'],
'fido2_data' => $fido2_data, 'fido2_data' => $fido2_data,
'gal' => @$_SESSION['gal'],
'api' => [ 'api' => [
'ro' => admin_api('ro', 'get'), 'ro' => admin_api('ro', 'get'),
'rw' => admin_api('rw', 'get'), 'rw' => admin_api('rw', 'get'),
@@ -107,6 +103,7 @@ $template_data = [
'rsettings' => $rsettings, 'rsettings' => $rsettings,
'rspamd_regex_maps' => $rspamd_regex_maps, 'rspamd_regex_maps' => $rspamd_regex_maps,
'logo_specs' => customize('get', 'main_logo_specs'), 'logo_specs' => customize('get', 'main_logo_specs'),
'ip_check' => customize('get', 'ip_check'),
'password_complexity' => password_complexity('get'), 'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),

View File

@@ -4,10 +4,10 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.12.0/r-2.3.0/sl-1.4.0 * https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0
* *
* Included libraries: * Included libraries:
* DataTables 1.12.0, Responsive 2.3.0, Select 1.4.0 * DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0
*/ */
@charset "UTF-8"; @charset "UTF-8";
@@ -63,7 +63,7 @@ table.dataTable thead > tr > td.sorting_desc_disabled:after {
opacity: 0.125; opacity: 0.125;
right: 10px; right: 10px;
line-height: 9px; line-height: 9px;
font-size: 0.9em; 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 > 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:before,
@@ -72,7 +72,7 @@ table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before, table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before { table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%; bottom: 50%;
content: ""; 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 > 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:after,
@@ -81,7 +81,7 @@ table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after, table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after { table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%; top: 50%;
content: ""; content: "";
} }
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, 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_asc:before,
@@ -287,6 +287,9 @@ table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
color: white; color: white;
} }
table.dataTable > tbody > tr.selected a {
color: #090a0b;
}
table.dataTable.table-striped > tbody > tr.odd > * { table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05);
} }
@@ -335,6 +338,9 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination {
white-space: nowrap; white-space: nowrap;
justify-content: flex-end; justify-content: flex-end;
} }
div.dataTables_wrapper div.dt-row {
position: relative;
}
div.dataTables_scrollHead table.dataTable { div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important; margin-bottom: 0 !important;
@@ -380,17 +386,6 @@ div.dataTables_wrapper div.dataTables_paginate {
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) { table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px; padding-right: 20px;
} }
table.dataTable.table-sm .sorting:before,
table.dataTable.table-sm .sorting_asc:before,
table.dataTable.table-sm .sorting_desc:before {
top: 5px;
right: 0.85em;
}
table.dataTable.table-sm .sorting:after,
table.dataTable.table-sm .sorting_asc:after,
table.dataTable.table-sm .sorting_desc:after {
top: 5px;
}
table.table-bordered.dataTable { table.table-bordered.dataTable {
border-right-width: 0; border-right-width: 0;
@@ -629,13 +624,13 @@ table.dataTable > tbody > tr > .selected {
background-color: rgba(13, 110, 253, 0.9); background-color: rgba(13, 110, 253, 0.9);
color: white; color: white;
} }
table.dataTable tbody td.select-checkbox, table.dataTable > tbody > tr > td.select-checkbox,
table.dataTable tbody th.select-checkbox { table.dataTable > tbody > tr > th.select-checkbox {
position: relative; position: relative;
} }
table.dataTable tbody td.select-checkbox:before, table.dataTable tbody td.select-checkbox:after, table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after,
table.dataTable tbody th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before,
table.dataTable tbody th.select-checkbox:after { table.dataTable > tbody > tr > th.select-checkbox:after {
display: block; display: block;
position: absolute; position: absolute;
top: 1.2em; top: 1.2em;
@@ -644,20 +639,20 @@ table.dataTable tbody th.select-checkbox:after {
height: 12px; height: 12px;
box-sizing: border-box; box-sizing: border-box;
} }
table.dataTable tbody td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:before,
table.dataTable tbody th.select-checkbox:before { table.dataTable > tbody > tr > th.select-checkbox:before {
content: " "; content: " ";
margin-top: -5px; margin-top: -5px;
margin-left: -6px; margin-left: -6px;
border: 1px solid black; border: 1px solid black;
border-radius: 3px; border-radius: 3px;
} }
table.dataTable tr.selected td.select-checkbox:before, table.dataTable > tbody > tr.selected > td.select-checkbox:before,
table.dataTable tr.selected th.select-checkbox:before { table.dataTable > tbody > tr.selected > th.select-checkbox:before {
border: 1px solid white; border: 1px solid white;
} }
table.dataTable tr.selected td.select-checkbox:after, table.dataTable > tbody > tr.selected > td.select-checkbox:after,
table.dataTable tr.selected th.select-checkbox:after { table.dataTable > tbody > tr.selected > th.select-checkbox:after {
content: "✓"; content: "✓";
font-size: 20px; font-size: 20px;
margin-top: -19px; margin-top: -19px;
@@ -665,12 +660,12 @@ table.dataTable tr.selected th.select-checkbox:after {
text-align: center; text-align: center;
text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;
} }
table.dataTable.compact tbody td.select-checkbox:before, table.dataTable.compact > tbody > tr > td.select-checkbox:before,
table.dataTable.compact tbody th.select-checkbox:before { table.dataTable.compact > tbody > tr > th.select-checkbox:before {
margin-top: -12px; margin-top: -12px;
} }
table.dataTable.compact tr.selected td.select-checkbox:after, table.dataTable.compact > tbody > tr.selected > td.select-checkbox:after,
table.dataTable.compact tr.selected th.select-checkbox:after { table.dataTable.compact > tbody > tr.selected > th.select-checkbox:after {
margin-top: -16px; margin-top: -16px;
} }
@@ -690,4 +685,3 @@ table.dataTable.table-sm tbody td.select-checkbox::before {
margin-top: -9px; margin-top: -9px;
} }

View File

@@ -370,3 +370,14 @@ button[aria-expanded='true'] > .caret {
.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 { .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important; background-color: #f0f0f0 !important;
} }
div.dataTables_wrapper div.dataTables_filter {
text-align: left;
}
div.dataTables_wrapper div.dataTables_length {
text-align: right;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 10px 0!important;
}

View File

@@ -199,6 +199,10 @@
display: none !important; display: none !important;
} }
div.dataTables_wrapper div.dataTables_length {
text-align: left;
}
} }
@media (max-width: 350px) { @media (max-width: 350px) {

View File

@@ -11,7 +11,86 @@
* Copyright 2011-2021 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,700;1,400&display=swap");
/* source-sans-pro-300 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-300italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-regular - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-regular.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-regular.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
:root { :root {
--bs-blue: #158cba; --bs-blue: #158cba;
--bs-indigo: #6610f2; --bs-indigo: #6610f2;

View File

@@ -358,3 +358,11 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
background: #333; background: #333;
} }
span.mail-address-item {
background-color: #333;
border-radius: 4px;
border: 1px solid #555;
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
}

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(); $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; $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'); $js_minifier->add('/web/js/site/debug.js');
// vmail df // vmail df
@@ -54,11 +59,13 @@ $template_data = [
'vmail_df' => $vmail_df, 'vmail_df' => $vmail_df,
'hostname' => $hostname, 'hostname' => $hostname,
'timezone' => $timezone, 'timezone' => $timezone,
'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'), 'license_guid' => license('guid'),
'solr_status' => $solr_status, 'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60), 'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status, 'clamd_status' => $clamd_status,
'containers' => $containers, 'containers' => $containers,
'ip_check' => customize('get', 'ip_check'),
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']), 'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -160,6 +160,25 @@ function customize($_action, $_item, $_data = null) {
'msg' => 'ui_texts' 'msg' => 'ui_texts'
); );
break; break;
case 'ip_check':
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
try {
$redis->set('IP_CHECK', $ip_check);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ip_check_opt_in_modified'
);
break;
} }
break; break;
case 'delete': case 'delete':
@@ -276,6 +295,20 @@ function customize($_action, $_item, $_data = null) {
return false; return false;
} }
break; break;
case 'ip_check':
try {
$ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0;
return $ip_check;
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
break;
} }
break; break;
} }

View File

@@ -2879,67 +2879,68 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied' 'msg' => 'extended_sender_acl_denied'
); );
return false;
} }
$extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl'])); else {
foreach ($extra_acls as $i => &$extra_acl) { $extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl']));
if (empty($extra_acl)) { foreach ($extra_acls as $i => &$extra_acl) {
continue; if (empty($extra_acl)) {
} continue;
if (substr($extra_acl, 0, 1) === "@") { }
$extra_acl = ltrim($extra_acl, '@'); if (substr($extra_acl, 0, 1) === "@") {
} $extra_acl = ltrim($extra_acl, '@');
if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) { }
$_SESSION['return'][] = array( if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) {
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
);
unset($extra_acls[$i]);
continue;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
$extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (in_array($extra_acl_domain, $domains)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) 'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
); );
unset($extra_acls[$i]); unset($extra_acls[$i]);
continue; continue;
} }
} $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
else { if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
if (in_array($extra_acl, $domains)) { $extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$_SESSION['return'][] = array( if (in_array($extra_acl_domain, $domains)) {
'type' => 'danger', $_SESSION['return'][] = array(
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'type' => 'danger',
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
); 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
unset($extra_acls[$i]); );
continue; unset($extra_acls[$i]);
continue;
}
}
else {
if (in_array($extra_acl, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
$extra_acl = '@' . $extra_acl;
} }
$extra_acl = '@' . $extra_acl;
} }
} $extra_acls = array_filter($extra_acls);
$extra_acls = array_filter($extra_acls); $extra_acls = array_values($extra_acls);
$extra_acls = array_values($extra_acls); $extra_acls = array_unique($extra_acls);
$extra_acls = array_unique($extra_acls); $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array( $stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username ':username' => $username
)); ));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username
));
}
} }
} }
if (isset($_data['sender_acl'])) { if (isset($_data['sender_acl'])) {
@@ -5171,15 +5172,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!is_array($tags)) $tags = array(); if (!is_array($tags)) $tags = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$wasModified = false; $wasModified = false;
foreach ($domains as $domain) { foreach ($domains as $domain) {
if (!is_valid_domain_name($domain)) { if (!is_valid_domain_name($domain)) {
@@ -5190,6 +5182,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach($tags as $tag){ foreach($tags as $tag){
// delete tag // delete tag

View File

@@ -234,7 +234,7 @@ if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
// Try suggest match // Try suggest match
// e.g. suggest en-gb when only en-us is provided // e.g. suggest en-gb when only en-us is provided
if (!isset($_COOKIE['mailcow_locale'])) { if (!isset($_SESSION['mailcow_locale'])) {
foreach ($lang2pref as $lang => $q) { foreach ($lang2pref as $lang => $q) {
if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) { if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)]; $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)];

View File

@@ -4,20 +4,20 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.12.0/r-2.3.0/sl-1.4.0 * https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0
* *
* Included libraries: * Included libraries:
* DataTables 1.12.0, Responsive 2.3.0, Select 1.4.0 * DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0
*/ */
/*! DataTables 1.12.0 /*! DataTables 1.13.1
* ©2008-2022 SpryMedia Ltd - datatables.net/license * ©2008-2022 SpryMedia Ltd - datatables.net/license
*/ */
/** /**
* @summary DataTables * @summary DataTables
* @description Paginate, search and order HTML tables * @description Paginate, search and order HTML tables
* @version 1.12.0 * @version 1.13.1
* @author SpryMedia Ltd * @author SpryMedia Ltd
* @contact www.datatables.net * @contact www.datatables.net
* @copyright SpryMedia Ltd. * @copyright SpryMedia Ltd.
@@ -1162,6 +1162,10 @@
$( rowOne[0] ).children('th, td').each( function (i, cell) { $( rowOne[0] ).children('th, td').each( function (i, cell) {
var col = oSettings.aoColumns[i]; var col = oSettings.aoColumns[i];
if (! col) {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
}
if ( col.mData === i ) { if ( col.mData === i ) {
var sort = a( cell, 'sort' ) || a( cell, 'order' ); var sort = a( cell, 'sort' ) || a( cell, 'order' );
var filter = a( cell, 'filter' ) || a( cell, 'search' ); var filter = a( cell, 'filter' ) || a( cell, 'search' );
@@ -3166,6 +3170,11 @@
create = nTrIn ? false : true; create = nTrIn ? false : true;
nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
if (! nTd) {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
}
nTd._DT_CellIndex = { nTd._DT_CellIndex = {
row: iRow, row: iRow,
column: i column: i
@@ -3316,10 +3325,16 @@
for ( i=0, ien=cells.length ; i<ien ; i++ ) { for ( i=0, ien=cells.length ; i<ien ; i++ ) {
column = columns[i]; column = columns[i];
column.nTf = cells[i].cell;
if ( column.sClass ) { if (column) {
$(column.nTf).addClass( column.sClass ); column.nTf = cells[i].cell;
if ( column.sClass ) {
$(column.nTf).addClass( column.sClass );
}
}
else {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
} }
} }
} }
@@ -5079,6 +5094,10 @@
_fnDraw( settings ); _fnDraw( settings );
} }
} }
else {
// No change event - paging was called, but no change
_fnCallbackFire( settings, null, 'page-nc', [settings] );
}
return changed; return changed;
} }
@@ -5348,6 +5367,7 @@
footerCopy = footer.clone().prependTo( table ); footerCopy = footer.clone().prependTo( table );
footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
footerSrcEls = footerCopy.find('tr'); footerSrcEls = footerCopy.find('tr');
footerCopy.find('[id]').removeAttr('id');
} }
// Clone the current header and footer elements and then place it into the inner table // Clone the current header and footer elements and then place it into the inner table
@@ -5355,6 +5375,7 @@
headerTrgEls = header.find('tr'); // original header is in its own table headerTrgEls = header.find('tr'); // original header is in its own table
headerSrcEls = headerCopy.find('tr'); headerSrcEls = headerCopy.find('tr');
headerCopy.find('th, td').removeAttr('tabindex'); headerCopy.find('th, td').removeAttr('tabindex');
headerCopy.find('[id]').removeAttr('id');
/* /*
@@ -8333,7 +8354,11 @@
$(document).on('plugin-init.dt', function (e, context) { $(document).on('plugin-init.dt', function (e, context) {
var api = new _Api( context ); var api = new _Api( context );
api.on( 'stateSaveParams', function ( e, settings, d ) { const namespace = 'on-plugin-init';
const stateSaveParamsEvent = `stateSaveParams.${namespace}`;
const destroyEvent = `destroy.${namespace}`;
api.on( stateSaveParamsEvent, function ( e, settings, d ) {
// This could be more compact with the API, but it is a lot faster as a simple // This could be more compact with the API, but it is a lot faster as a simple
// internal loop // internal loop
var idFn = settings.rowIdFn; var idFn = settings.rowIdFn;
@@ -8347,7 +8372,11 @@
} }
d.childRows = ids; d.childRows = ids;
}) });
api.on( destroyEvent, function () {
api.off(`${stateSaveParamsEvent} ${destroyEvent}`);
});
var loaded = api.state.loaded(); var loaded = api.state.loaded();
@@ -9668,7 +9697,7 @@
* @type string * @type string
* @default Version number * @default Version number
*/ */
DataTable.version = "1.12.0"; DataTable.version = "1.13.1";
/** /**
* Private data store, containing all of the settings objects that are * Private data store, containing all of the settings objects that are
@@ -14092,7 +14121,7 @@
* *
* @type string * @type string
*/ */
build:"bs5/dt-1.12.0/r-2.3.0/sl-1.4.0", build:"bs5/dt-1.13.1/r-2.4.0/sl-1.5.0",
/** /**
@@ -14730,7 +14759,7 @@
var classes = settings.oClasses; var classes = settings.oClasses;
var lang = settings.oLanguage.oPaginate; var lang = settings.oLanguage.oPaginate;
var aria = settings.oLanguage.oAria.paginate || {}; var aria = settings.oLanguage.oAria.paginate || {};
var btnDisplay, btnClass, counter=0; var btnDisplay, btnClass;
var attach = function( container, buttons ) { var attach = function( container, buttons ) {
var i, ien, node, button, tabIndex; var i, ien, node, button, tabIndex;
@@ -14805,7 +14834,7 @@
'class': classes.sPageButton+' '+btnClass, 'class': classes.sPageButton+' '+btnClass,
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'data-dt-idx': counter, 'data-dt-idx': button,
'tabindex': tabIndex, 'tabindex': tabIndex,
'id': idx === 0 && typeof button === 'string' ? 'id': idx === 0 && typeof button === 'string' ?
settings.sTableId +'_'+ button : settings.sTableId +'_'+ button :
@@ -14817,8 +14846,6 @@
_fnBindAction( _fnBindAction(
node, {action: button}, clickHandler node, {action: button}, clickHandler
); );
counter++;
} }
} }
} }
@@ -15163,7 +15190,7 @@
} }
} }
else if (window.luxon) { else if (window.luxon) {
dt = format dt = format && typeof d === 'string'
? window.luxon.DateTime.fromFormat( d, format ) ? window.luxon.DateTime.fromFormat( d, format )
: window.luxon.DateTime.fromISO( d ); : window.luxon.DateTime.fromISO( d );
@@ -15303,14 +15330,26 @@
} }
// Based on locale, determine standard number formatting // Based on locale, determine standard number formatting
var __thousands = ''; // Fallback for legacy browsers is US English
var __decimal = ''; var __thousands = ',';
var __decimal = '.';
if (Intl) { if (Intl) {
var num = new Intl.NumberFormat().formatToParts(1000.1); try {
var num = new Intl.NumberFormat().formatToParts(100000.1);
__thousands = num[1].value; for (var i=0 ; i<num.length ; i++) {
__decimal = num[3].value; if (num[i].type === 'group') {
__thousands = num[i].value;
}
else if (num[i].type === 'decimal') {
__decimal = num[i].value;
}
}
}
catch (e) {
// noop
}
} }
// Formatted date time detection - use by declaring the formats you are going to use // Formatted date time detection - use by declaring the formats you are going to use
@@ -15379,6 +15418,10 @@
return d; return d;
} }
if (d === '' || d === null) {
return d;
}
var negative = d < 0 ? '-' : ''; var negative = d < 0 ? '-' : '';
var flo = parseFloat( d ); var flo = parseFloat( d );
@@ -15578,14 +15621,6 @@
* 2020 SpryMedia Ltd - datatables.net/license * 2020 SpryMedia Ltd - datatables.net/license
*/ */
/**
* DataTables integration for Bootstrap 4. This requires Bootstrap 5 and
* DataTables 1.10 or newer.
*
* This file sets the defaults and adds options to DataTables to style its
* controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
* for further information.
*/
(function( factory ){ (function( factory ){
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
@@ -15597,16 +15632,22 @@
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
// Require DataTables, which attaches to jQuery, including $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
// jQuery if needed and have a $ property so we can access the require('jquery') :
// jQuery object that is used require('jquery')( root );
$ = require('datatables.net')(root, $).$;
} }
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@@ -15619,11 +15660,21 @@
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
/**
* DataTables integration for Bootstrap 5. This requires Bootstrap 5 and
* DataTables 1.10 or newer.
*
* This file sets the defaults and adds options to DataTables to style its
* controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
* for further information.
*/
/* Set the defaults for DataTables initialisation */ /* Set the defaults for DataTables initialisation */
$.extend( true, DataTable.defaults, { $.extend( true, DataTable.defaults, {
dom: dom:
"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" + "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" + "<'row dt-row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
renderer: 'bootstrap' renderer: 'bootstrap'
} ); } );
@@ -15645,7 +15696,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
var classes = settings.oClasses; var classes = settings.oClasses;
var lang = settings.oLanguage.oPaginate; var lang = settings.oLanguage.oPaginate;
var aria = settings.oLanguage.oAria.paginate || {}; var aria = settings.oLanguage.oAria.paginate || {};
var btnDisplay, btnClass, counter=0; var btnDisplay, btnClass;
var attach = function( container, buttons ) { var attach = function( container, buttons ) {
var i, ien, node, button; var i, ien, node, button;
@@ -15714,7 +15765,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
'href': '#', 'href': '#',
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'data-dt-idx': counter, 'data-dt-idx': button,
'tabindex': settings.iTabIndex, 'tabindex': settings.iTabIndex,
'class': 'page-link' 'class': 'page-link'
} ) } )
@@ -15725,13 +15776,12 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
settings.oApi._fnBindAction( settings.oApi._fnBindAction(
node, {action: button}, clickHandler node, {action: button}, clickHandler
); );
counter++;
} }
} }
} }
}; };
var hostEl = $(host);
// IE9 throws an 'unknown error' if document.activeElement is used // IE9 throws an 'unknown error' if document.activeElement is used
// inside an iframe or frame. // inside an iframe or frame.
var activeEl; var activeEl;
@@ -15741,17 +15791,26 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
// elements, focus is lost on the select button which is bad for // elements, focus is lost on the select button which is bad for
// accessibility. So we want to restore focus once the draw has // accessibility. So we want to restore focus once the draw has
// completed // completed
activeEl = $(host).find(document.activeElement).data('dt-idx'); activeEl = hostEl.find(document.activeElement).data('dt-idx');
} }
catch (e) {} catch (e) {}
var paginationEl = hostEl.children('ul.pagination');
if (paginationEl.length) {
paginationEl.empty();
}
else {
paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination');
}
attach( attach(
$(host).empty().html('<ul class="pagination"/>').children('ul'), paginationEl,
buttons buttons
); );
if ( activeEl !== undefined ) { if ( activeEl !== undefined ) {
$(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); hostEl.find('[data-dt-idx='+activeEl+']').trigger('focus');
} }
}; };
@@ -15760,14 +15819,54 @@ return DataTable;
})); }));
/*! Responsive 2.3.0 /*! Responsive 2.4.0
* 2014-2022 SpryMedia Ltd - datatables.net/license * 2014-2022 SpryMedia Ltd - datatables.net/license
*/ */
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery', 'datatables.net'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window;
}
if ( ! $ ) {
$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
}
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
/** /**
* @summary Responsive * @summary Responsive
* @description Responsive tables plug-in for DataTables * @description Responsive tables plug-in for DataTables
* @version 2.3.0 * @version 2.4.0
* @author SpryMedia Ltd (www.sprymedia.co.uk) * @author SpryMedia Ltd (www.sprymedia.co.uk)
* @contact www.sprymedia.co.uk/contact * @contact www.sprymedia.co.uk/contact
* @copyright SpryMedia Ltd. * @copyright SpryMedia Ltd.
@@ -15781,35 +15880,6 @@ return DataTable;
* *
* For details please refer to: http://www.datatables.net * For details please refer to: http://www.datatables.net
*/ */
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery', 'datatables.net'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
root = window;
}
if ( ! $ || ! $.fn.dataTable ) {
$ = require('datatables.net')(root, $).$;
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
/** /**
* Responsive is a plug-in for the DataTables library that makes use of * Responsive is a plug-in for the DataTables library that makes use of
@@ -15863,9 +15933,10 @@ var Responsive = function ( settings, opts ) {
} }
this.s = { this.s = {
dt: new DataTable.Api( settings ), childNodeStore: {},
columns: [], columns: [],
current: [] current: [],
dt: new DataTable.Api( settings )
}; };
// Check if responsive has already been initialised on this table // Check if responsive has already been initialised on this table
@@ -16070,6 +16141,63 @@ $.extend( Responsive.prototype, {
* Private methods * Private methods
*/ */
/**
* Get and store nodes from a cell - use for node moving renderers
*
* @param {*} dt DT instance
* @param {*} row Row index
* @param {*} col Column index
*/
_childNodes: function( dt, row, col ) {
var name = row+'-'+col;
if ( this.s.childNodeStore[ name ] ) {
return this.s.childNodeStore[ name ];
}
// https://jsperf.com/childnodes-array-slice-vs-loop
var nodes = [];
var children = dt.cell( row, col ).node().childNodes;
for ( var i=0, ien=children.length ; i<ien ; i++ ) {
nodes.push( children[i] );
}
this.s.childNodeStore[ name ] = nodes;
return nodes;
},
/**
* Restore nodes from the cache to a table cell
*
* @param {*} dt DT instance
* @param {*} row Row index
* @param {*} col Column index
*/
_childNodesRestore: function( dt, row, col ) {
var name = row+'-'+col;
if ( ! this.s.childNodeStore[ name ] ) {
return;
}
var node = dt.cell( row, col ).node();
var store = this.s.childNodeStore[ name ];
var parent = store[0].parentNode;
var parentChildren = parent.childNodes;
var a = [];
for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
a.push( parentChildren[i] );
}
for ( var j=0, jen=a.length ; j<jen ; j++ ) {
node.appendChild( a[j] );
}
this.s.childNodeStore[ name ] = undefined;
},
/** /**
* Calculate the visibility for the columns in a table for a given * Calculate the visibility for the columns in a table for a given
* breakpoint. The result is pre-determined based on the class logic if * breakpoint. The result is pre-determined based on the class logic if
@@ -16399,8 +16527,8 @@ $.extend( Responsive.prototype, {
: details.renderer; : details.renderer;
var res = details.display( row, update, function () { var res = details.display( row, update, function () {
return renderer( return renderer.call(
dt, row[0], that._detailsObj(row[0]) that, dt, row[0], that._detailsObj(row[0])
); );
} ); } );
@@ -16622,9 +16750,11 @@ $.extend( Responsive.prototype, {
} }
} ); } );
if ( changed ) { // Always need to update the display, regardless of if it has changed or not, so nodes
this._redrawChildren(); // can be re-inserted for listHiddenNodes
this._redrawChildren();
if ( changed ) {
// Inform listeners of the change // Inform listeners of the change
$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] ); $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
@@ -16650,6 +16780,7 @@ $.extend( Responsive.prototype, {
{ {
var dt = this.s.dt; var dt = this.s.dt;
var columns = this.s.columns; var columns = this.s.columns;
var that = this;
// Are we allowed to do auto sizing? // Are we allowed to do auto sizing?
if ( ! this.c.auto ) { if ( ! this.c.auto ) {
@@ -16663,11 +16794,11 @@ $.extend( Responsive.prototype, {
} }
// Need to restore all children. They will be reinstated by a re-render // Need to restore all children. They will be reinstated by a re-render
if ( ! $.isEmptyObject( _childNodeStore ) ) { if ( ! $.isEmptyObject( this.s.childNodeStore ) ) {
$.each( _childNodeStore, function ( key ) { $.each( this.s.childNodeStore, function ( key ) {
var idx = key.split('-'); var idx = key.split('-');
_childNodesRestore( dt, idx[0]*1, idx[1]*1 ); that._childNodesRestore( dt, idx[0]*1, idx[1]*1 );
} ); } );
} }
@@ -16787,6 +16918,7 @@ $.extend( Responsive.prototype, {
*/ */
_setColumnVis: function ( col, showHide ) _setColumnVis: function ( col, showHide )
{ {
var that = this;
var dt = this.s.dt; var dt = this.s.dt;
var display = showHide ? '' : 'none'; // empty string will remove the attr var display = showHide ? '' : 'none'; // empty string will remove the attr
@@ -16803,9 +16935,9 @@ $.extend( Responsive.prototype, {
.toggleClass('dtr-hidden', !showHide); .toggleClass('dtr-hidden', !showHide);
// If the are child nodes stored, we might need to reinsert them // If the are child nodes stored, we might need to reinsert them
if ( ! $.isEmptyObject( _childNodeStore ) ) { if ( ! $.isEmptyObject( this.s.childNodeStore ) ) {
dt.cells( null, col ).indexes().each( function (idx) { dt.cells( null, col ).indexes().each( function (idx) {
_childNodesRestore( dt, idx.row, idx.column ); that._childNodesRestore( dt, idx.row, idx.column );
} ); } );
} }
}, },
@@ -16972,52 +17104,6 @@ Responsive.display = {
}; };
var _childNodeStore = {};
function _childNodes( dt, row, col ) {
var name = row+'-'+col;
if ( _childNodeStore[ name ] ) {
return _childNodeStore[ name ];
}
// https://jsperf.com/childnodes-array-slice-vs-loop
var nodes = [];
var children = dt.cell( row, col ).node().childNodes;
for ( var i=0, ien=children.length ; i<ien ; i++ ) {
nodes.push( children[i] );
}
_childNodeStore[ name ] = nodes;
return nodes;
}
function _childNodesRestore( dt, row, col ) {
var name = row+'-'+col;
if ( ! _childNodeStore[ name ] ) {
return;
}
var node = dt.cell( row, col ).node();
var store = _childNodeStore[ name ];
var parent = store[0].parentNode;
var parentChildren = parent.childNodes;
var a = [];
for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
a.push( parentChildren[i] );
}
for ( var j=0, jen=a.length ; j<jen ; j++ ) {
node.appendChild( a[j] );
}
_childNodeStore[ name ] = undefined;
}
/** /**
* Display methods - functions which define how the hidden data should be shown * Display methods - functions which define how the hidden data should be shown
* in the table. * in the table.
@@ -17029,6 +17115,7 @@ function _childNodesRestore( dt, row, col ) {
Responsive.renderer = { Responsive.renderer = {
listHiddenNodes: function () { listHiddenNodes: function () {
return function ( api, rowIdx, columns ) { return function ( api, rowIdx, columns ) {
var that = this;
var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>'); var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>');
var found = false; var found = false;
@@ -17045,7 +17132,7 @@ Responsive.renderer = {
'</span> '+ '</span> '+
'</li>' '</li>'
) )
.append( $('<span class="dtr-data"/>').append( _childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) ) .append( $('<span class="dtr-data"/>').append( that._childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
.appendTo( ul ); .appendTo( ul );
found = true; found = true;
@@ -17229,7 +17316,7 @@ Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()
* @name Responsive.version * @name Responsive.version
* @static * @static
*/ */
Responsive.version = '2.3.0'; Responsive.version = '2.4.0';
$.fn.dataTable.Responsive = Responsive; $.fn.dataTable.Responsive = Responsive;
@@ -17256,12 +17343,12 @@ $(document).on( 'preInit.dt.dtr', function (e, settings, json) {
} ); } );
return Responsive; return DataTable;
})); }));
/*! Bootstrap 5 integration for DataTables' Responsive /*! Bootstrap 5 integration for DataTables' Responsive
* ©2021 SpryMedia Ltd - datatables.net/license * © SpryMedia Ltd - datatables.net/license
*/ */
(function( factory ){ (function( factory ){
@@ -17275,17 +17362,26 @@ return Responsive;
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
$ = require('datatables.net-bs5')(root, $).$; $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
} }
if ( ! $.fn.dataTable.Responsive ) { if ( ! $.fn.dataTable ) {
require('datatables.net-bs5')(root, $);
}
if ( ! $.fn.dataTable ) {
require('datatables.net-responsive')(root, $); require('datatables.net-responsive')(root, $);
} }
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@@ -17298,6 +17394,7 @@ return Responsive;
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
var _display = DataTable.Responsive.display; var _display = DataTable.Responsive.display;
var _original = _display.modal; var _original = _display.modal;
var _modal = $( var _modal = $(
@@ -17359,33 +17456,14 @@ _display.modal = function ( options ) {
}; };
return DataTable.Responsive; return DataTable;
})); }));
/*! Select for DataTables 1.4.0 /*! Select for DataTables 1.5.0
* 2015-2021 SpryMedia Ltd - datatables.net/license/mit * 2015-2021 SpryMedia Ltd - datatables.net/license/mit
*/ */
/**
* @summary Select for DataTables
* @description A collection of API methods, events and buttons for DataTables
* that provides selection options of the items in a DataTable
* @version 1.4.0
* @file dataTables.select.js
* @author SpryMedia Ltd (www.sprymedia.co.uk)
* @contact datatables.net/forums
* @copyright Copyright 2015-2021 SpryMedia Ltd.
*
* This source file is free software, available under the following license:
* MIT license - http://datatables.net/license/mit
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* For details please refer to: http://www.datatables.net/extensions/select
*/
(function( factory ){ (function( factory ){
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
@@ -17397,13 +17475,22 @@ return DataTable.Responsive;
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
$ = require('datatables.net')(root, $).$; $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
} }
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@@ -17416,10 +17503,11 @@ return DataTable.Responsive;
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
// Version information for debugger // Version information for debugger
DataTable.select = {}; DataTable.select = {};
DataTable.select.version = '1.4.0'; DataTable.select.version = '1.5.0';
DataTable.select.init = function ( dt ) { DataTable.select.init = function ( dt ) {
var ctx = dt.settings()[0]; var ctx = dt.settings()[0];
@@ -18688,7 +18776,6 @@ $(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
} ); } );
return DataTable.select; return DataTable;
})); }));

File diff suppressed because one or more lines are too long

View File

@@ -1,70 +0,0 @@
/**
* This plug-in for DataTables represents the ultimate option in extensibility
* for sorting date / time strings correctly. It uses
* [Moment.js](http://momentjs.com) to create automatic type detection and
* sorting plug-ins for DataTables based on a given format. This way, DataTables
* will automatically detect your temporal information and sort it correctly.
*
* For usage instructions, please see the DataTables blog
* post that [introduces it](//datatables.net/blog/2014-12-18).
*
* @name Ultimate Date / Time sorting
* @summary Sort date and time in any format using Moment.js
* @author [Allan Jardine](//datatables.net)
* @depends DataTables 1.10+, Moment.js 1.7+
*
* @example
* $.fn.dataTable.moment( 'HH:mm MMM D, YY' );
* $.fn.dataTable.moment( 'dddd, MMMM Do, YYYY' );
*
* $('#example').DataTable();
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
define(["jquery", "moment", "datatables.net"], factory);
} else {
factory(jQuery, moment);
}
}(function ($, moment) {
function strip (d) {
if ( typeof d === 'string' ) {
// Strip HTML tags and newline characters if possible
d = d.replace(/(<.*?>)|(\r?\n|\r)/g, '');
// Strip out surrounding white space
d = d.trim();
}
return d;
}
$.fn.dataTable.moment = function ( format, locale, reverseEmpties ) {
var types = $.fn.dataTable.ext.type;
// Add type detection
types.detect.unshift( function ( d ) {
d = strip(d);
// Null and empty values are acceptable
if ( d === '' || d === null ) {
return 'moment-'+format;
}
return moment( d, format, locale, true ).isValid() ?
'moment-'+format :
null;
} );
// Add sorting method - use an integer for the sorting
types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
d = strip(d);
return !moment(d, format, locale, true).isValid() ?
(reverseEmpties ? -Infinity : Infinity) :
parseInt( moment( d, format, locale, true ).format( 'x' ), 10 );
};
};
}));

View File

@@ -12,14 +12,22 @@ $(document).ready(function() {
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
} }
$(".generate_password").click(function( event ) { $(".generate_password").click(async function( event ) {
try {
var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
var password_policy = await password_policy.json();
random_passwd_length = password_policy.length;
} catch(err) {
var random_passwd_length = 8;
}
event.preventDefault(); event.preventDefault();
$('[data-hibp]').trigger('input'); $('[data-hibp]').trigger('input');
if (typeof($(this).closest("form").data('pwgen-length')) == "number") { if (typeof($(this).closest("form").data('pwgen-length')) == "number") {
var random_passwd = GPW.pronounceable($(this).closest("form").data('pwgen-length')) var random_passwd = GPW.pronounceable($(this).closest("form").data('pwgen-length'))
} }
else { else {
var random_passwd = GPW.pronounceable(8) var random_passwd = GPW.pronounceable(random_passwd_length)
} }
$(this).closest("form").find('[data-pwgen-field]').attr('type', 'text'); $(this).closest("form").find('[data-pwgen-field]').attr('type', 'text');
$(this).closest("form").find('[data-pwgen-field]').val(random_passwd); $(this).closest("form").find('[data-pwgen-field]').val(random_passwd);
@@ -274,11 +282,12 @@ $(document).ready(function() {
}); });
}) })
// Jquery Datatables, enable responsive plugin and date sort plugin // Jquery Datatables, enable responsive plugin
$.extend($.fn.dataTable.defaults, { $.extend($.fn.dataTable.defaults, {
responsive: true responsive: true
}); });
$.fn.dataTable.moment('dd:mm:YYYY'); // disable default datatable click listener
$(document).off('click', 'tbody>tr');
// tag boxes // tag boxes
$('.tag-box .tag-add').click(function(){ $('.tag-box .tag-add').click(function(){

View File

@@ -70,8 +70,13 @@ jQuery(function($){
} }
$('#domainadminstable').DataTable({ $('#domainadminstable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -143,8 +148,13 @@ jQuery(function($){
} }
$('#oauth2clientstable').DataTable({ $('#oauth2clientstable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -206,8 +216,13 @@ jQuery(function($){
} }
$('#adminstable').DataTable({ $('#adminstable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -272,8 +287,13 @@ jQuery(function($){
} }
$('#forwardinghoststable').DataTable({ $('#forwardinghoststable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -311,7 +331,10 @@ jQuery(function($){
{ {
title: lang.spamfilter, title: lang.spamfilter,
data: 'keep_spam', data: 'keep_spam',
defaultContent: '' defaultContent: '',
render: function(data, type){
return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>';
}
}, },
{ {
title: lang.action, title: lang.action,
@@ -330,8 +353,13 @@ jQuery(function($){
} }
$('#relayhoststable').DataTable({ $('#relayhoststable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -402,8 +430,13 @@ jQuery(function($){
} }
$('#transportstable').DataTable({ $('#transportstable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",

View File

@@ -1,3 +1,13 @@
const LOCALE = undefined;
const DATETIME_FORMAT = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
};
$(document).ready(function() { $(document).ready(function() {
// Parse seconds ago to date // Parse seconds ago to date
// Get "now" timestamp // Get "now" timestamp
@@ -7,14 +17,7 @@ $(document).ready(function() {
if (typeof started_s_ago != 'NaN') { if (typeof started_s_ago != 'NaN') {
var started_date = new Date((ts_now - started_s_ago) * 1000); var started_date = new Date((ts_now - started_s_ago) * 1000);
if (started_date instanceof Date && !isNaN(started_date)) { if (started_date instanceof Date && !isNaN(started_date)) {
var started_local_date = started_date.toLocaleDateString(undefined, { var started_local_date = started_date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
$(this).text(started_local_date); $(this).text(started_local_date);
} else { } else {
$(this).text('-'); $(this).text('-');
@@ -25,14 +28,7 @@ $(document).ready(function() {
$('.parse_date').each(function(i, parse_date) { $('.parse_date').each(function(i, parse_date) {
var started_date = new Date(Date.parse($(this).text())); var started_date = new Date(Date.parse($(this).text()));
if (typeof started_date != 'NaN') { if (typeof started_date != 'NaN') {
var started_local_date = started_date.toLocaleDateString(undefined, { var started_local_date = started_date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
$(this).text(started_local_date); $(this).text(started_local_date);
} }
}); });
@@ -55,7 +51,40 @@ $(document).ready(function() {
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag); showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
}) })
// get public ips // get public ips
get_public_ips(); $("#host_show_ip").click(function(){
$("#host_show_ip").find(".text").addClass("d-none");
$("#host_show_ip").find(".spinner-border").removeClass("d-none");
window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
// display host ips
if (data.ipv4)
$("#host_ipv4").text(data.ipv4);
if (data.ipv6)
$("#host_ipv6").text(data.ipv6);
$("#host_show_ip").addClass("d-none");
$("#host_show_ip").find(".text").removeClass("d-none");
$("#host_show_ip").find(".spinner-border").addClass("d-none");
$("#host_ipv4").removeClass("d-none");
$("#host_ipv6").removeClass("d-none");
$("#host_ipv6").removeClass("text-danger");
$("#host_ipv4").addClass("d-block");
$("#host_ipv6").addClass("d-block");
}).catch(function(error){
console.log(error);
$("#host_ipv6").removeClass("d-none");
$("#host_ipv6").addClass("d-block");
$("#host_ipv6").addClass("text-danger");
$("#host_ipv6").text(lang_debug.error_show_ip);
$("#host_show_ip").find(".text").removeClass("d-none");
$("#host_show_ip").find(".spinner-border").addClass("d-none");
});
});
update_container_stats(); update_container_stats();
}); });
jQuery(function($){ jQuery(function($){
@@ -75,6 +104,13 @@ jQuery(function($){
var table_name = $(this).data('table'); var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload(); $('#' + table_name).DataTable().ajax.reload();
}); });
function createSortableDate(td, cellData) {
$(td).attr({
"data-order": cellData,
"data-sort": cellData
});
$(td).html(convertTimestampToLocalFormat(cellData));
}
function draw_autodiscover_logs() { function draw_autodiscover_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#autodiscover_log') ) { if ($.fn.DataTable.isDataTable('#autodiscover_log') ) {
@@ -82,11 +118,19 @@ jQuery(function($){
return; return;
} }
$('#autodiscover_log').DataTable({ var table = $('#autodiscover_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-autodiscover-logs', '#autodiscover_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/autodiscover/100", url: "/api/v1/get/logs/autodiscover/100",
@@ -100,9 +144,8 @@ jQuery(function($){
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
responsivePriority: 1, responsivePriority: 1,
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -132,6 +175,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-autodiscover-logs', '#autodiscover_log');
});
} }
function draw_postfix_logs() { function draw_postfix_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -140,11 +187,19 @@ jQuery(function($){
return; return;
} }
$('#postfix_log').DataTable({ var table = $('#postfix_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-postfix-logs', '#postfix_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/postfix", url: "/api/v1/get/logs/postfix",
@@ -157,9 +212,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -175,6 +229,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-postfix-logs', '#postfix_log');
});
} }
function draw_watchdog_logs() { function draw_watchdog_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -183,11 +241,19 @@ jQuery(function($){
return; return;
} }
$('#watchdog_log').DataTable({ var table = $('#watchdog_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-watchdog-logs', '#watchdog_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/watchdog", url: "/api/v1/get/logs/watchdog",
@@ -200,9 +266,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -222,6 +287,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-watchdog-logs', '#watchdog_log');
});
} }
function draw_api_logs() { function draw_api_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -230,11 +299,19 @@ jQuery(function($){
return; return;
} }
$('#api_log').DataTable({ var table = $('#api_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-api-logs', '#api_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/api", url: "/api/v1/get/logs/api",
@@ -247,9 +324,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -276,6 +352,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-api-logs', '#api_log');
});
} }
function draw_rl_logs() { function draw_rl_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -284,11 +364,19 @@ jQuery(function($){
return; return;
} }
$('#rl_log').DataTable({ var table = $('#rl_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/ratelimited", url: "/api/v1/get/logs/ratelimited",
@@ -306,9 +394,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -368,6 +455,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log');
});
} }
function draw_ui_logs() { function draw_ui_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -376,11 +467,19 @@ jQuery(function($){
return; return;
} }
$('#ui_logs').DataTable({ var table = $('#ui_logs').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_logs');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/ui", url: "/api/v1/get/logs/ui",
@@ -393,9 +492,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -440,6 +538,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log');
});
} }
function draw_sasl_logs() { function draw_sasl_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -448,11 +550,19 @@ jQuery(function($){
return; return;
} }
$('#sasl_logs').DataTable({ var table = $('#sasl_logs').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/sasl", url: "/api/v1/get/logs/sasl",
@@ -481,13 +591,17 @@ jQuery(function($){
title: lang.login_time, title: lang.login_time,
data: 'datetime', data: 'datetime',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data.replace(/-/g, "/")); cellData = Math.floor((new Date(cellData.replace(/-/g, "/"))).getTime() / 1000);
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); createSortableDate(td, cellData)
} }
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs');
});
} }
function draw_acme_logs() { function draw_acme_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -496,11 +610,19 @@ jQuery(function($){
return; return;
} }
$('#acme_log').DataTable({ var table = $('#acme_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/acme", url: "/api/v1/get/logs/acme",
@@ -513,9 +635,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -526,6 +647,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log');
});
} }
function draw_netfilter_logs() { function draw_netfilter_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -534,11 +659,19 @@ jQuery(function($){
return; return;
} }
$('#netfilter_log').DataTable({ var table = $('#netfilter_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/netfilter", url: "/api/v1/get/logs/netfilter",
@@ -551,9 +684,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -569,6 +701,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log');
});
} }
function draw_sogo_logs() { function draw_sogo_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -577,11 +713,19 @@ jQuery(function($){
return; return;
} }
$('#sogo_log').DataTable({ var table = $('#sogo_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/sogo", url: "/api/v1/get/logs/sogo",
@@ -594,9 +738,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -612,6 +755,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log');
});
} }
function draw_dovecot_logs() { function draw_dovecot_logs() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -620,11 +767,19 @@ jQuery(function($){
return; return;
} }
$('#dovecot_log').DataTable({ var table = $('#dovecot_log').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']], order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-dovecot-logs', '#dovecot_log');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/dovecot", url: "/api/v1/get/logs/dovecot",
@@ -637,9 +792,8 @@ jQuery(function($){
title: lang.time, title: lang.time,
data: 'time', data: 'time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -655,6 +809,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-dovecot-logs', '#dovecot_log');
});
} }
function rspamd_pie_graph() { function rspamd_pie_graph() {
$.ajax({ $.ajax({
@@ -724,10 +882,19 @@ jQuery(function($){
return; return;
} }
$('#rspamd_history').DataTable({ var table = $('#rspamd_history').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[0, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-rspamd-logs', '#rspamd_history');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/logs/rspamd-history", url: "/api/v1/get/logs/rspamd-history",
@@ -738,11 +905,10 @@ jQuery(function($){
columns: [ columns: [
{ {
title: lang.time, title: lang.time,
data: 'time', data: 'unix_time',
defaultContent: '', defaultContent: '',
render: function(data, type){ createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
@@ -773,12 +939,14 @@ jQuery(function($){
{ {
title: 'Score', title: 'Score',
data: 'score', data: 'score',
defaultContent: '' defaultContent: '',
}, createdCell: function(td, cellData) {
{ $(td).attr({
title: 'Subject', "data-order": cellData.sortBy,
data: 'header_subject', "data-sort": cellData.sortBy
defaultContent: '' });
$(td).html(cellData.value);
}
}, },
{ {
title: 'Symbols', title: 'Symbols',
@@ -794,7 +962,14 @@ jQuery(function($){
{ {
title: 'Scan Time', title: 'Scan Time',
data: 'scan_time', data: 'scan_time',
defaultContent: '' defaultContent: '',
createdCell: function(td, cellData) {
$(td).attr({
"data-order": cellData.sortBy,
"data-sort": cellData.sortBy
});
$(td).html(cellData.value);
}
}, },
{ {
title: 'ID', title: 'ID',
@@ -808,6 +983,10 @@ jQuery(function($){
} }
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history');
});
} }
function process_table_data(data, table) { function process_table_data(data, table) {
if (table == 'rspamd_history') { if (table == 'rspamd_history') {
@@ -851,9 +1030,7 @@ jQuery(function($){
scan_time += ' / ' + item.time_virtual.toFixed(3); scan_time += ' / ' + item.time_virtual.toFixed(3);
} }
item.scan_time = { item.scan_time = {
"options": { "sortBy": item.time_real,
"sortValue": item.time_real
},
"value": scan_time "value": scan_time
}; };
if (item.action === 'clean' || item.action === 'no action') { if (item.action === 'clean' || item.action === 'no action') {
@@ -872,9 +1049,7 @@ jQuery(function($){
score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]"; score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
} }
item.score = { item.score = {
"options": { "sortBy": item.score,
"sortValue": item.score
},
"value": score_content "value": score_content
}; };
if (item.user == null) { if (item.user == null) {
@@ -1007,6 +1182,12 @@ jQuery(function($){
}); });
} }
}) })
function hideTableExpandCollapseBtn(tab, table){
if ($(table).hasClass('collapsed'))
$(tab).find(".table_collapse_option").show();
else
$(tab).find(".table_collapse_option").hide();
}
// detect element visibility changes // detect element visibility changes
function onVisible(element, callback) { function onVisible(element, callback) {
@@ -1226,20 +1407,6 @@ function update_container_stats(timeout=5){
// run again in n seconds // run again in n seconds
setTimeout(update_container_stats, timeout * 1000); setTimeout(update_container_stats, timeout * 1000);
} }
// get public ips
function get_public_ips(){
window.fetch("/api/v1/get/status/host/ip", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
// display host ips
if (data.ipv4)
$("#host_ipv4").text(data.ipv4);
if (data.ipv6)
$("#host_ipv6").text(data.ipv6);
});
}
// format hosts uptime seconds to readable string // format hosts uptime seconds to readable string
function formatUptime(seconds){ function formatUptime(seconds){
seconds = Number(seconds); seconds = Number(seconds);
@@ -1531,3 +1698,8 @@ function parseGithubMarkdownLinks(inputText) {
return replacedText; return replacedText;
} }
function convertTimestampToLocalFormat(timestamp) {
var date = new Date(timestamp ? timestamp * 1000 : 0);
return date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
}

View File

@@ -78,8 +78,13 @@ jQuery(function($){
} }
function draw_wl_policy_domain_table() { function draw_wl_policy_domain_table() {
$('#wl_policy_domain_table').DataTable({ $('#wl_policy_domain_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -133,8 +138,13 @@ jQuery(function($){
} }
function draw_bl_policy_domain_table() { function draw_bl_policy_domain_table() {
$('#bl_policy_domain_table').DataTable({ $('#bl_policy_domain_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",

View File

@@ -433,9 +433,17 @@ jQuery(function($){
} }
var table = $('#domain_table').DataTable({ var table = $('#domain_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#tab-domains', '#domain_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/domain/all", url: "/api/v1/get/domain/all",
@@ -610,6 +618,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-domains', '#domain_table');
});
} }
function draw_templates_domain_table() { function draw_templates_domain_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -618,11 +630,19 @@ jQuery(function($){
return; return;
} }
$('#templates_domain_table').DataTable({ var table = $('#templates_domain_table').DataTable({
responsive : true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-templates-domains', '#templates_domain_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/domain/template/all", url: "/api/v1/get/domain/template/all",
@@ -807,6 +827,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-templates-domains', '#templates_domain_table');
});
} }
function draw_mailbox_table() { function draw_mailbox_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -815,17 +839,30 @@ jQuery(function($){
return; return;
} }
$('#mailbox_table').DataTable({ var table = $('#mailbox_table').DataTable({
responsive : true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/mailbox/reduced", url: "/api/v1/get/mailbox/reduced",
dataSrc: function(json){ dataSrc: function(json){
$.each(json, function (i, item) { $.each(json, function (i, item) {
item.quota = item.quota_used + "/" + item.quota; item.quota = {
sortBy: item.quota_used,
value: item.quota
}
item.quota.value = (item.quota.value == 0 ? "∞" : humanFileSize(item.quota.value));
item.quota.value = humanFileSize(item.quota_used) + "/" + item.quota.value;
item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox);
item.last_mail_login = item.last_imap_login + '/' + item.last_pop3_login + '/' + item.last_smtp_login; item.last_mail_login = item.last_imap_login + '/' + item.last_pop3_login + '/' + item.last_smtp_login;
/* /*
@@ -930,14 +967,10 @@ jQuery(function($){
}, },
{ {
title: lang.domain_quota, title: lang.domain_quota,
data: 'quota', data: 'quota.value',
responsivePriority: 8, responsivePriority: 8,
defaultContent: '', defaultContent: '',
render: function (data, type) { orderData: 23
data = data.split("/");
var of_q = (data[1] == 0 ? "∞" : humanFileSize(data[1]));
return humanFileSize(data[0]) + " / " + of_q;
}
}, },
{ {
title: lang.last_mail_login, title: lang.last_mail_login,
@@ -1063,8 +1096,19 @@ jQuery(function($){
responsivePriority: 6, responsivePriority: 6,
defaultContent: '' defaultContent: ''
}, },
{
title: "",
data: 'quota.sortBy',
responsivePriority: 8,
defaultContent: '',
className: "d-none"
},
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
});
} }
function draw_templates_mbox_table() { function draw_templates_mbox_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1073,11 +1117,19 @@ jQuery(function($){
return; return;
} }
$('#templates_mbox_table').DataTable({ var table = $('#templates_mbox_table').DataTable({
responsive : true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-templates-mbox', '#templates_mbox_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/mailbox/template/all", url: "/api/v1/get/mailbox/template/all",
@@ -1276,6 +1328,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-templates-mbox', '#templates_mbox_table');
});
} }
function draw_resource_table() { function draw_resource_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1284,10 +1340,18 @@ jQuery(function($){
return; return;
} }
$('#resource_table').DataTable({ var table = $('#resource_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#tab-resources', '#resource_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/resource/all", url: "/api/v1/get/resource/all",
@@ -1374,6 +1438,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-resources', '#resource_table');
});
} }
function draw_bcc_table() { function draw_bcc_table() {
$.get("/api/v1/get/bcc-destination-options", function(data){ $.get("/api/v1/get/bcc-destination-options", function(data){
@@ -1410,10 +1478,19 @@ jQuery(function($){
return; return;
} }
$('#bcc_table').DataTable({ var table = $('#bcc_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#collapse-tab-bcc', '#bcc_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/bcc/all", url: "/api/v1/get/bcc/all",
@@ -1498,6 +1575,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#collapse-tab-bcc', '#bcc_table');
});
} }
function draw_recipient_map_table() { function draw_recipient_map_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1506,10 +1587,19 @@ jQuery(function($){
return; return;
} }
$('#recipient_map_table').DataTable({ var table = $('#recipient_map_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#collapse-tab-bcc-filters', '#recipient_map_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/recipient_map/all", url: "/api/v1/get/recipient_map/all",
@@ -1581,6 +1671,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#collapse-tab-bcc-filters', '#recipient_map_table');
});
} }
function draw_tls_policy_table() { function draw_tls_policy_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1589,10 +1683,19 @@ jQuery(function($){
return; return;
} }
$('#tls_policy_table').DataTable({ var table = $('#tls_policy_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-tls-policy', '#tls_policy_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/tls-policy-map/all", url: "/api/v1/get/tls-policy-map/all",
@@ -1674,6 +1777,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-tls-policy', '#tls_policy_table');
});
} }
function draw_alias_table() { function draw_alias_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1682,10 +1789,19 @@ jQuery(function($){
return; return;
} }
$('#alias_table').DataTable({ var table = $('#alias_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/alias/all", url: "/api/v1/get/alias/all",
@@ -1814,6 +1930,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
});
} }
function draw_aliasdomain_table() { function draw_aliasdomain_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1822,10 +1942,18 @@ jQuery(function($){
return; return;
} }
$('#aliasdomain_table').DataTable({ var table = $('#aliasdomain_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#tab-domain-aliases', '#aliasdomain_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/alias-domain/all", url: "/api/v1/get/alias-domain/all",
@@ -1896,6 +2024,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-domain-aliases', '#aliasdomain_table');
});
} }
function draw_sync_job_table() { function draw_sync_job_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -1904,10 +2036,19 @@ jQuery(function($){
return; return;
} }
$('#sync_job_table').DataTable({ var table = $('#sync_job_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/syncjobs/all/no_log", url: "/api/v1/get/syncjobs/all/no_log",
@@ -2035,6 +2176,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
});
} }
function draw_filter_table() { function draw_filter_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -2044,10 +2189,19 @@ jQuery(function($){
} }
var table = $('#filter_table').DataTable({ var table = $('#filter_table').DataTable({
responsive: true,
autoWidth: false, autoWidth: false,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order:[[2, 'desc']],
initComplete: function(){
hideTableExpandCollapseBtn('#tab-filters', '#filter_table');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/filters/all", url: "/api/v1/get/filters/all",
@@ -2132,8 +2286,19 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-filters', '#filter_table');
});
}; };
function hideTableExpandCollapseBtn(tab, table){
if ($(table).hasClass('collapsed'))
$(tab).find(".table_collapse_option").show();
else
$(tab).find(".table_collapse_option").hide();
}
// detect element visibility changes // detect element visibility changes
function onVisible(element, callback) { function onVisible(element, callback) {
$(document).ready(function() { $(document).ready(function() {

View File

@@ -13,10 +13,18 @@ jQuery(function($){
$('#' + table_name).DataTable().ajax.reload(); $('#' + table_name).DataTable().ajax.reload();
}); });
function draw_quarantine_table() { function draw_quarantine_table() {
$('#quarantinetable').DataTable({ var table = $('#quarantinetable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#quarantinetable');
},
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/quarantine/all", url: "/api/v1/get/quarantine/all",
@@ -97,7 +105,7 @@ jQuery(function($){
}, },
{ {
title: lang.subj, title: lang.subj,
data: 'sender', data: 'subject',
defaultContent: '' defaultContent: ''
}, },
{ {
@@ -129,9 +137,15 @@ jQuery(function($){
title: lang.received, title: lang.received,
data: 'created', data: 'created',
defaultContent: '', defaultContent: '',
render: function (data,type) { createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); $(td).attr({
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); "data-order": cellData,
"data-sort": cellData
});
var date = new Date(cellData ? cellData * 1000 : 0);
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
$(td).html(dateString);
} }
}, },
{ {
@@ -142,6 +156,10 @@ jQuery(function($){
}, },
] ]
}); });
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#quarantinetable');
});
} }
$('body').on('click', '.show_qid_info', function (e) { $('body').on('click', '.show_qid_info', function (e) {
@@ -257,4 +275,12 @@ jQuery(function($){
// Initial table drawings // Initial table drawings
draw_quarantine_table(); draw_quarantine_table();
function hideTableExpandCollapseBtn(table){
if ($(table).hasClass('collapsed'))
$(".table_collapse_option").show();
else
$(".table_collapse_option").hide();
}
}); });

View File

@@ -21,7 +21,6 @@ jQuery(function($){
url: '/api/v1/get/postcat/' + button.data('queue-id'), url: '/api/v1/get/postcat/' + button.data('queue-id'),
dataType: 'text', dataType: 'text',
complete: function (data) { complete: function (data) {
console.log(data);
$('#queue_msg_content').text(data.responseText); $('#queue_msg_content').text(data.responseText);
} }
}); });
@@ -35,8 +34,13 @@ jQuery(function($){
} }
$('#queuetable').DataTable({ $('#queuetable').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -49,7 +53,7 @@ jQuery(function($){
}); });
item.recipients = rcpts.join('<hr style="margin:1px!important">'); item.recipients = rcpts.join('<hr style="margin:1px!important">');
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.queue_show_message + '</a>' + '<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' +
'</div>'; '</div>';
}); });
return data; return data;

View File

@@ -135,8 +135,13 @@ jQuery(function($){
} }
$('#tla_table').DataTable({ $('#tla_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -216,8 +221,13 @@ jQuery(function($){
} }
$('#sync_job_table').DataTable({ $('#sync_job_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -366,8 +376,13 @@ jQuery(function($){
} }
$('#app_passwd_table').DataTable({ $('#app_passwd_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -456,8 +471,13 @@ jQuery(function($){
} }
$('#wl_policy_mailbox_table').DataTable({ $('#wl_policy_mailbox_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",
@@ -521,8 +541,13 @@ jQuery(function($){
} }
$('#bl_policy_mailbox_table').DataTable({ $('#bl_policy_mailbox_table').DataTable({
responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
ajax: { ajax: {
type: "GET", type: "GET",

View File

@@ -561,6 +561,15 @@ if (isset($_GET['query'])) {
echo '{}'; echo '{}';
} }
break; break;
default:
$password_complexity_rules = password_complexity('get');
if ($password_complexity_rules !== false) {
process_get_return($password_complexity_rules);
}
else {
echo '{}';
}
break;
} }
break; break;
@@ -1544,14 +1553,15 @@ if (isset($_GET['query'])) {
} }
else if ($extra == "ip") { else if ($extra == "ip") {
// get public ips // get public ips
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
$ipv4 = curl_exec($curl); $ipv4 = curl_exec($curl);
curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email'); curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$ipv6 = curl_exec($curl); $ipv6 = curl_exec($curl);
$ips = array( $ips = array(
"ipv4" => $ipv4, "ipv4" => $ipv4,
@@ -1913,6 +1923,9 @@ if (isset($_GET['query'])) {
case "ui_texts": case "ui_texts":
process_edit_return(customize('edit', 'ui_texts', $attr)); process_edit_return(customize('edit', 'ui_texts', $attr));
break; break;
case "ip_check":
process_edit_return(customize('edit', 'ip_check', $attr));
break;
case "self": case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") { if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr)); process_edit_return(domain_admin('edit', $attr));

View File

@@ -393,7 +393,6 @@
"toggle_all": "Marcar tots" "toggle_all": "Marcar tots"
}, },
"queue": { "queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager" "queue_manager": "Queue Manager"
}, },
"start": { "start": {

View File

@@ -889,7 +889,6 @@
"type": "Typ" "type": "Typ"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Doručit",
"queue_manager": "Správce fronty" "queue_manager": "Správce fronty"
}, },
"ratelimit": { "ratelimit": {

View File

@@ -1,6 +1,6 @@
{ {
"acl": { "acl": {
"alias_domains": "Tilføj kældenavn domæner", "alias_domains": "Tilføj domænealias",
"app_passwds": "Administrer app-adgangskoder", "app_passwds": "Administrer app-adgangskoder",
"bcc_maps": "BCC kort", "bcc_maps": "BCC kort",
"delimiter_action": "Afgrænsning handling", "delimiter_action": "Afgrænsning handling",
@@ -22,9 +22,9 @@
"spam_alias": "Midlertidige aliasser", "spam_alias": "Midlertidige aliasser",
"spam_policy": "Sortliste / hvidliste", "spam_policy": "Sortliste / hvidliste",
"spam_score": "Spam-score", "spam_score": "Spam-score",
"syncjobs": "Synkroniser job", "syncjobs": "Synkroniserings job",
"tls_policy": "TLS politik", "tls_policy": "TLS politik",
"unlimited_quota": "Ubegrænset quote for mailbokse", "unlimited_quota": "Ubegrænset plads for mailbokse",
"domain_desc": "Skift domæne beskrivelse" "domain_desc": "Skift domæne beskrivelse"
}, },
"add": { "add": {
@@ -33,7 +33,7 @@
"add": "Tilføj", "add": "Tilføj",
"add_domain_only": "Tilføj kun domæne", "add_domain_only": "Tilføj kun domæne",
"add_domain_restart": "Tilføj domæne og genstart SOGo", "add_domain_restart": "Tilføj domæne og genstart SOGo",
"alias_address": "Alias adresse (r)", "alias_address": "Alias adresse(r)",
"alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>", "alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>",
"alias_domain": "Alias-domæne", "alias_domain": "Alias-domæne",
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>", "alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
@@ -803,7 +803,6 @@
"toggle_all": "Skift alt" "toggle_all": "Skift alt"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Aflevere",
"queue_manager": "Køadministrator" "queue_manager": "Køadministrator"
}, },
"start": { "start": {

View File

@@ -204,6 +204,9 @@
"include_exclude": "Ein- und Ausschlüsse", "include_exclude": "Ein- und Ausschlüsse",
"include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.", "include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.",
"includes": "Diese Empfänger einschließen", "includes": "Diese Empfänger einschließen",
"ip_check": "IP Check",
"ip_check_disabled": "IP check ist deaktiviert. Unter dem angegebenen Pfad kann es aktiviert werden<br> <strong>System > Konfiguration > Einstellungen > UI-Anpassung</strong>",
"ip_check_opt_in": "Opt-In für die Nutzung der Drittanbieter-Dienste <strong>ipv4.mailcow.email</strong> und <strong>ipv6.mailcow.email</strong> zur Auflösung externer IP-Adressen.",
"is_mx_based": "MX-basiert", "is_mx_based": "MX-basiert",
"last_applied": "Zuletzt angewendet", "last_applied": "Zuletzt angewendet",
"license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow-GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.", "license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow-GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.",
@@ -363,6 +366,7 @@
"domain_not_empty": "Domain %s ist nicht leer", "domain_not_empty": "Domain %s ist nicht leer",
"domain_not_found": "Domain %s nicht gefunden", "domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein", "domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig", "extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain", "extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s", "fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
@@ -459,40 +463,42 @@
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s" "yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s"
}, },
"datatables": { "datatables": {
"collapse_all": "Alle Einklappen", "collapse_all": "Alle Einklappen",
"decimal": "", "decimal": "",
"emptyTable": "Keine Daten in der Tabelle vorhanden", "emptyTable": "Keine Daten in der Tabelle vorhanden",
"expand_all": "Alle Ausklappen", "expand_all": "Alle Ausklappen",
"info": "_START_ bis _END_ von _TOTAL_ Einträgen", "info": "_START_ bis _END_ von _TOTAL_ Einträgen",
"infoEmpty": "0 bis 0 von 0 Einträgen", "infoEmpty": "0 bis 0 von 0 Einträgen",
"infoFiltered": "(gefiltert von _MAX_ Einträgen)", "infoFiltered": "(gefiltert von _MAX_ Einträgen)",
"infoPostFix": "", "infoPostFix": "",
"thousands": ".", "thousands": ".",
"lengthMenu": "_MENU_ Einträge anzeigen", "lengthMenu": "_MENU_ Einträge anzeigen",
"loadingRecords": "Wird geladen...", "loadingRecords": "Wird geladen...",
"processing": "Bitte warten...", "processing": "Bitte warten...",
"search": "Suchen", "search": "Suchen",
"zeroRecords": "Keine Einträge vorhanden.", "zeroRecords": "Keine Einträge vorhanden.",
"paginate": { "paginate": {
"first": "Erste", "first": "Erste",
"previous": "Zurück", "previous": "Zurück",
"next": "Nächste", "next": "Nächste",
"last": "Letzte" "last": "Letzte"
}, },
"aria": { "aria": {
"sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren", "sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
"sortDescending": ": aktivieren, um Spalte absteigend zu sortieren" "sortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
} }
}, },
"debug": { "debug": {
"chart_this_server": "Chart (dieser Server)", "chart_this_server": "Chart (dieser Server)",
"containers_info": "Container-Information", "containers_info": "Container-Information",
"container_running": "Läuft", "container_running": "Läuft",
"container_disabled": "Container gestoppt oder deaktiviert",
"container_stopped": "Angehalten", "container_stopped": "Angehalten",
"cores": "Kerne", "cores": "Kerne",
"current_time": "Systemzeit", "current_time": "Systemzeit",
"disk_usage": "Festplattennutzung", "disk_usage": "Festplattennutzung",
"docs": "Dokumente", "docs": "Dokumente",
"error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen",
"external_logs": "Externe Logs", "external_logs": "Externe Logs",
"history_all_servers": "History (alle Server)", "history_all_servers": "History (alle Server)",
"in_memory_logs": "In-memory Logs", "in_memory_logs": "In-memory Logs",
@@ -505,6 +511,7 @@
"online_users": "Benutzer online", "online_users": "Benutzer online",
"restart_container": "Neustart", "restart_container": "Neustart",
"service": "Dienst", "service": "Dienst",
"show_ip": "Zeige öffentliche IP",
"size": "Größe", "size": "Größe",
"solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.", "solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.",
"solr_status": "Solr Status", "solr_status": "Solr Status",
@@ -941,7 +948,7 @@
"queue": { "queue": {
"delete": "Queue löschen", "delete": "Queue löschen",
"flush": "Queue flushen", "flush": "Queue flushen",
"info" : "In der Mailqueue befinden sich alle E-Mails, welche auf eine Zustellung warten. Sollte eine E-Mail eine längere Zeit innerhalb der Mailqueue stecken wird diese automatisch vom System gelöscht.<br>Die Fehlermeldung der jeweiligen Mail gibt aufschluss darüber, warum diese nicht zugestellt werden konnte", "info": "In der Mailqueue befinden sich alle E-Mails, welche auf eine Zustellung warten. Sollte eine E-Mail eine längere Zeit innerhalb der Mailqueue stecken wird diese automatisch vom System gelöscht.<br>Die Fehlermeldung der jeweiligen Mail gibt aufschluss darüber, warum diese nicht zugestellt werden konnte",
"legend": "Funktionen der Mailqueue Aktionen:", "legend": "Funktionen der Mailqueue Aktionen:",
"ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?", "ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"deliver_mail": "Ausliefern", "deliver_mail": "Ausliefern",
@@ -1000,6 +1007,7 @@
"forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt", "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
"global_filter_written": "Filterdatei wurde erfolgreich geschrieben", "global_filter_written": "Filterdatei wurde erfolgreich geschrieben",
"hash_deleted": "Hash wurde gelöscht", "hash_deleted": "Hash wurde gelöscht",
"ip_check_opt_in_modified": "IP Check wurde erfolgreich gespeichert",
"item_deleted": "Objekt %s wurde entfernt", "item_deleted": "Objekt %s wurde entfernt",
"item_released": "Objekt %s freigegeben", "item_released": "Objekt %s freigegeben",
"items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt", "items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt",

View File

@@ -206,6 +206,9 @@
"include_exclude": "Include/Exclude", "include_exclude": "Include/Exclude",
"include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed", "include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed",
"includes": "Include these recipients", "includes": "Include these recipients",
"ip_check": "IP Check",
"ip_check_disabled": "IP check is disabled. You can enable it under<br> <strong>System > Configuration > Options > Customize</strong>",
"ip_check_opt_in": "Opt-In for using third party service <strong>ipv4.mailcow.email</strong> and <strong>ipv6.mailcow.email</strong> to resolve external IP addresses.",
"is_mx_based": "MX based", "is_mx_based": "MX based",
"last_applied": "Last applied", "last_applied": "Last applied",
"license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>", "license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>",
@@ -264,6 +267,7 @@
"quota_notifications": "Quota notifications", "quota_notifications": "Quota notifications",
"quota_notifications_info": "Quota notifications are sent to users once when crossing 80% and once when crossing 95% usage.", "quota_notifications_info": "Quota notifications are sent to users once when crossing 80% and once when crossing 95% usage.",
"quota_notifications_vars": "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name", "quota_notifications_vars": "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name",
"queue_unban": "unban",
"r_active": "Active restrictions", "r_active": "Active restrictions",
"r_inactive": "Inactive restrictions", "r_inactive": "Inactive restrictions",
"r_info": "Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway. <br>You can add new elements in <code>inc/vars.local.inc.php</code> to be able to toggle them.", "r_info": "Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway. <br>You can add new elements in <code>inc/vars.local.inc.php</code> to be able to toggle them.",
@@ -363,6 +367,7 @@
"domain_not_empty": "Cannot remove non-empty domain %s", "domain_not_empty": "Cannot remove non-empty domain %s",
"domain_not_found": "Domain %s not found", "domain_not_found": "Domain %s not found",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB", "domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
"extra_acl_invalid": "External sender address \"%s\" is invalid", "extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain", "extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s", "fido2_verification_failed": "FIDO2 verification failed: %s",
@@ -462,40 +467,42 @@
"yotp_verification_failed": "Yubico OTP verification failed: %s" "yotp_verification_failed": "Yubico OTP verification failed: %s"
}, },
"datatables": { "datatables": {
"collapse_all": "Collapse All", "collapse_all": "Collapse All",
"decimal": "", "decimal": "",
"emptyTable": "No data available in table", "emptyTable": "No data available in table",
"expand_all": "Expand All", "expand_all": "Expand All",
"info": "Showing _START_ to _END_ of _TOTAL_ entries", "info": "Showing _START_ to _END_ of _TOTAL_ entries",
"infoEmpty": "Showing 0 to 0 of 0 entries", "infoEmpty": "Showing 0 to 0 of 0 entries",
"infoFiltered": "(filtered from _MAX_ total entries)", "infoFiltered": "(filtered from _MAX_ total entries)",
"infoPostFix": "", "infoPostFix": "",
"thousands": ",", "thousands": ",",
"lengthMenu": "Show _MENU_ entries", "lengthMenu": "Show _MENU_ entries",
"loadingRecords": "Loading...", "loadingRecords": "Loading...",
"processing": "Please wait...", "processing": "Please wait...",
"search": "Search:", "search": "Search:",
"zeroRecords": "No matching records found", "zeroRecords": "No matching records found",
"paginate": { "paginate": {
"first": "First", "first": "First",
"last": "Last", "last": "Last",
"next": "Next", "next": "Next",
"previous": "Previous" "previous": "Previous"
}, },
"aria": { "aria": {
"sortAscending": ": activate to sort column ascending", "sortAscending": ": activate to sort column ascending",
"sortDescending": ": activate to sort column descending" "sortDescending": ": activate to sort column descending"
} }
}, },
"debug": { "debug": {
"chart_this_server": "Chart (this server)", "chart_this_server": "Chart (this server)",
"containers_info": "Container information", "containers_info": "Container information",
"container_running": "Running", "container_running": "Running",
"container_disabled": "Container stopped or disabled",
"container_stopped": "Stopped", "container_stopped": "Stopped",
"cores": "Cores", "cores": "Cores",
"current_time": "System Time", "current_time": "System Time",
"disk_usage": "Disk usage", "disk_usage": "Disk usage",
"docs": "Docs", "docs": "Docs",
"error_show_ip": "Could not resolve the public IP addresses",
"external_logs": "External logs", "external_logs": "External logs",
"history_all_servers": "History (all servers)", "history_all_servers": "History (all servers)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
@@ -508,6 +515,7 @@
"online_users": "Users online", "online_users": "Users online",
"restart_container": "Restart", "restart_container": "Restart",
"service": "Service", "service": "Service",
"show_ip": "Show public IP",
"size": "Size", "size": "Size",
"solr_dead": "Solr is starting, disabled or died.", "solr_dead": "Solr is starting, disabled or died.",
"solr_status": "Solr status", "solr_status": "Solr status",
@@ -946,7 +954,7 @@
"queue": { "queue": {
"delete": "Delete all", "delete": "Delete all",
"flush": "Flush queue", "flush": "Flush queue",
"info" : "The mail queue contains all e-mails that are waiting for delivery. If an email is stuck in the mail queue for a long time, it is automatically deleted by the system.<br>The error message of the respective mail gives information about why the mail could not be delivered.", "info": "The mail queue contains all e-mails that are waiting for delivery. If an email is stuck in the mail queue for a long time, it is automatically deleted by the system.<br>The error message of the respective mail gives information about why the mail could not be delivered.",
"legend": "Mail queue actions functions:", "legend": "Mail queue actions functions:",
"ays": "Please confirm you want to delete all items from the current queue.", "ays": "Please confirm you want to delete all items from the current queue.",
"deliver_mail": "Deliver", "deliver_mail": "Deliver",
@@ -960,11 +968,11 @@
"unhold_mail_legend": "Releases selected mails for delivery. (Requires prior hold)" "unhold_mail_legend": "Releases selected mails for delivery. (Requires prior hold)"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Disabled", "disabled": "Disabled",
"second": "msgs / second", "second": "msgs / second",
"minute": "msgs / minute", "minute": "msgs / minute",
"hour": "msgs / hour", "hour": "msgs / hour",
"day": "msgs / day" "day": "msgs / day"
}, },
"start": { "start": {
"help": "Show/Hide help panel", "help": "Show/Hide help panel",
@@ -1012,6 +1020,7 @@
"forwarding_host_removed": "Forwarding host %s has been removed", "forwarding_host_removed": "Forwarding host %s has been removed",
"global_filter_written": "Filter was successfully written to file", "global_filter_written": "Filter was successfully written to file",
"hash_deleted": "Hash deleted", "hash_deleted": "Hash deleted",
"ip_check_opt_in_modified": "IP check was saved successfully",
"item_deleted": "Item %s successfully deleted", "item_deleted": "Item %s successfully deleted",
"item_released": "Item %s released", "item_released": "Item %s released",
"items_deleted": "Item %s successfully deleted", "items_deleted": "Item %s successfully deleted",

View File

@@ -602,7 +602,6 @@
"toggle_all": "Seleccionar todos" "toggle_all": "Seleccionar todos"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Entregar",
"queue_manager": "Administrador de cola" "queue_manager": "Administrador de cola"
}, },
"start": { "start": {

View File

@@ -686,7 +686,6 @@
"toggle_all": "Valitse kaikki" "toggle_all": "Valitse kaikki"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Toimittaa",
"queue_manager": "Jonon hallinta" "queue_manager": "Jonon hallinta"
}, },
"start": { "start": {

View File

@@ -26,7 +26,9 @@
"syncjobs": "Tâches de synchronisation", "syncjobs": "Tâches de synchronisation",
"tls_policy": "Police TLS", "tls_policy": "Police TLS",
"unlimited_quota": "Quota illimité pour les boites de courriel", "unlimited_quota": "Quota illimité pour les boites de courriel",
"domain_desc": "Modifier la description du domaine" "domain_desc": "Modifier la description du domaine",
"domain_relayhost": "Changer le relais pour un domaine",
"mailbox_relayhost": "Changer le relais dune boîte de réception"
}, },
"add": { "add": {
"activate_filter_warn": "Tous les autres filtres seront désactivés, quand activé est coché.", "activate_filter_warn": "Tous les autres filtres seront désactivés, quand activé est coché.",
@@ -103,7 +105,8 @@
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"validate": "Valider", "validate": "Valider",
"validation_success": "Validation réussie", "validation_success": "Validation réussie",
"bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici." "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
"tags": "Etiquettes"
}, },
"admin": { "admin": {
"access": "Accès", "access": "Accès",
@@ -316,7 +319,9 @@
"oauth2_add_client": "Ajouter un client OAuth2", "oauth2_add_client": "Ajouter un client OAuth2",
"password_policy": "Politique de mots de passe", "password_policy": "Politique de mots de passe",
"admins": "Administrateurs", "admins": "Administrateurs",
"api_read_only": "Accès lecture-seule" "api_read_only": "Accès lecture-seule",
"password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
"password_policy_numbers": "Doit contenir au moins un chiffre"
}, },
"danger": { "danger": {
"access_denied": "Accès refusé ou données de formulaire non valides", "access_denied": "Accès refusé ou données de formulaire non valides",
@@ -827,7 +832,6 @@
"toggle_all": "Tout basculer" "toggle_all": "Tout basculer"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Délivrer",
"queue_manager": "Gestion de la file d'attente" "queue_manager": "Gestion de la file d'attente"
}, },
"start": { "start": {

View File

@@ -216,7 +216,6 @@
"toggle_all": "Összes átkapcsolása" "toggle_all": "Összes átkapcsolása"
}, },
"queue": { "queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager" "queue_manager": "Queue Manager"
}, },
"start": { "start": {

View File

@@ -43,7 +43,7 @@
"app_name": "Nome app", "app_name": "Nome app",
"app_password": "Aggiungi la password dell'app", "app_password": "Aggiungi la password dell'app",
"automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)", "automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)",
"backup_mx_options": "Relay options", "backup_mx_options": "Opzioni di inoltro",
"comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente", "comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente",
"custom_params": "Parametri personalizzati", "custom_params": "Parametri personalizzati",
"custom_params_hint": "Corretto: --param=xy, errato: --param xy", "custom_params_hint": "Corretto: --param=xy, errato: --param xy",
@@ -303,7 +303,7 @@
"spamfilter": "Filtri spam", "spamfilter": "Filtri spam",
"subject": "Oggetto", "subject": "Oggetto",
"success": "Successo", "success": "Successo",
"sys_mails": "System mails", "sys_mails": "Mail di sistema",
"text": "Testo", "text": "Testo",
"time": "Orario", "time": "Orario",
"title": "Titolo", "title": "Titolo",
@@ -335,7 +335,8 @@
"api_read_write": "Accesso in lettura-scrittura", "api_read_write": "Accesso in lettura-scrittura",
"oauth2_apps": "App OAuth2", "oauth2_apps": "App OAuth2",
"oauth2_add_client": "Aggiungere il client OAuth2", "oauth2_add_client": "Aggiungere il client OAuth2",
"rsettings_preset_4": "Disattivare Rspamd per un dominio" "rsettings_preset_4": "Disattivare Rspamd per un dominio",
"options": "Opzioni"
}, },
"danger": { "danger": {
"access_denied": "Accesso negato o form di login non corretto", "access_denied": "Accesso negato o form di login non corretto",
@@ -364,7 +365,7 @@
"extra_acl_invalid": "External sender address \"%s\" is invalid", "extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain", "extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s", "fido2_verification_failed": "FIDO2 verification failed: %s",
"file_open_error": "File cannot be opened for writing", "file_open_error": "Il file non può essere aperto per la scrittura",
"filter_type": "Wrong filter type", "filter_type": "Wrong filter type",
"from_invalid": "Il mittente non può essere vuoto", "from_invalid": "Il mittente non può essere vuoto",
"global_filter_write_error": "Could not write filter file: %s", "global_filter_write_error": "Could not write filter file: %s",
@@ -397,7 +398,7 @@
"mailbox_quota_exceeds_domain_quota": "Lo spazio massimo supera la spazio del dominio", "mailbox_quota_exceeds_domain_quota": "Lo spazio massimo supera la spazio del dominio",
"mailbox_quota_left_exceeded": "Non c'è abbastanza spazio libero (space left: %d MiB)", "mailbox_quota_left_exceeded": "Non c'è abbastanza spazio libero (space left: %d MiB)",
"mailboxes_in_use": "Lo spazio massimo della casella deve essere maggiore o uguale a %d", "mailboxes_in_use": "Lo spazio massimo della casella deve essere maggiore o uguale a %d",
"malformed_username": "Malformed username", "malformed_username": "Nome utente non valido",
"map_content_empty": "Map content cannot be empty", "map_content_empty": "Map content cannot be empty",
"max_alias_exceeded": "Numero massimo di alias superato", "max_alias_exceeded": "Numero massimo di alias superato",
"max_mailbox_exceeded": "Numero massimo di caselle superato (%d of %d)", "max_mailbox_exceeded": "Numero massimo di caselle superato (%d of %d)",
@@ -429,18 +430,18 @@
"resource_invalid": "Il nome della risorsa non è valido", "resource_invalid": "Il nome della risorsa non è valido",
"rl_timeframe": "Rate limit time frame is incorrect", "rl_timeframe": "Rate limit time frame is incorrect",
"rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long", "rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long",
"script_empty": "Script cannot be empty", "script_empty": "Lo script non può essere vuoto",
"sender_acl_invalid": "Il valore di Sender ACL non è valido", "sender_acl_invalid": "Il valore di Sender ACL non è valido",
"set_acl_failed": "Failed to set ACL", "set_acl_failed": "Failed to set ACL",
"settings_map_invalid": "Settings map ID %s invalid", "settings_map_invalid": "Settings map ID %s invalid",
"sieve_error": "Sieve parser error: %s", "sieve_error": "Sieve parser error: %s",
"spam_learn_error": "Spam learn error: %s", "spam_learn_error": "Spam learn error: %s",
"subject_empty": "Subject must not be empty", "subject_empty": "L'oggetto non deve essere vuoto",
"target_domain_invalid": "Goto domain non è valido", "target_domain_invalid": "Goto domain non è valido",
"targetd_not_found": "Il target del dominio non è stato trovato", "targetd_not_found": "Il target del dominio non è stato trovato",
"targetd_relay_domain": "Target domain %s is a relay domain", "targetd_relay_domain": "Target domain %s is a relay domain",
"temp_error": "Temporary error", "temp_error": "Errore temporaneo",
"text_empty": "Text must not be empty", "text_empty": "Il testo non deve essere vuoto",
"tfa_token_invalid": "TFA token invalid", "tfa_token_invalid": "TFA token invalid",
"tls_policy_map_dest_invalid": "Policy destination is invalid", "tls_policy_map_dest_invalid": "Policy destination is invalid",
"tls_policy_map_entry_exists": "A TLS policy map entry \"%s\" exists", "tls_policy_map_entry_exists": "A TLS policy map entry \"%s\" exists",
@@ -448,40 +449,54 @@
"totp_verification_failed": "TOTP verification failed", "totp_verification_failed": "TOTP verification failed",
"transport_dest_exists": "Transport destination \"%s\" exists", "transport_dest_exists": "Transport destination \"%s\" exists",
"webauthn_verification_failed": "WebAuthn verification failed: %s", "webauthn_verification_failed": "WebAuthn verification failed: %s",
"unknown": "An unknown error occurred", "unknown": "Si è verificato un errore sconosciuto",
"unknown_tfa_method": "Unknown TFA method", "unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL", "unlimited_quota_acl": "Unlimited quota prohibited by ACL",
"username_invalid": "Username %s non può essere utilizzato", "username_invalid": "Il nome utente %s non può essere utilizzato",
"validity_missing": "Assegnare un periodo di validità", "validity_missing": "Assegnare un periodo di validità",
"value_missing": "Si prega di fornire tutti i valori", "value_missing": "Si prega di fornire tutti i valori",
"yotp_verification_failed": "Verifica OTP Yubico fallita: %s" "yotp_verification_failed": "Verifica OTP Yubico fallita: %s",
"demo_mode_enabled": "La modalità demo è abilitata",
"template_name_invalid": "Nome template non valido",
"template_exists": "Il template %s esiste già",
"template_id_invalid": "Il template con ID %s non è valido"
}, },
"debug": { "debug": {
"chart_this_server": "Grafico (questo server)", "chart_this_server": "Grafico (questo server)",
"containers_info": "Container information", "containers_info": "Informazioni sul container",
"disk_usage": "Uso del disco", "disk_usage": "Uso del disco",
"docs": "Docs", "docs": "Docs",
"external_logs": "External logs", "external_logs": "Log esterni",
"history_all_servers": "History (all servers)", "history_all_servers": "Cronologia (tutti i server)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"jvm_memory_solr": "JVM memory usage", "jvm_memory_solr": "JVM memory usage",
"last_modified": "Ultima modifica", "last_modified": "Ultima modifica",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Time", "login_time": "Orario",
"logs": "Logs", "logs": "Logs",
"online_users": "Users online", "online_users": "Utenti online",
"restart_container": "Riavvio", "restart_container": "Riavvio",
"service": "Servizio", "service": "Servizio",
"size": "Size", "size": "Dimensione",
"solr_dead": "Solr is starting, disabled or died.", "solr_dead": "Solr sta partendo, è disabilitato o morto.",
"solr_status": "Stato Solr", "solr_status": "Stato Solr",
"started_at": "Started at", "started_at": "Iniziato alle",
"started_on": "Started on", "started_on": "Iniziato",
"static_logs": "Static logs", "static_logs": "Log statici",
"success": "Successo", "success": "Successo",
"system_containers": "System & Containers", "system_containers": "Sistema & Containers",
"uptime": "Tempo di attività", "uptime": "Tempo di attività",
"username": "Username" "username": "Nome utente",
"container_disabled": "Container arrestato o disattivato",
"update_available": "È disponibile un aggiornamento",
"container_running": "In esecuzione",
"container_stopped": "Arrestato",
"cores": "Cores",
"current_time": "Orario di sistema",
"memory": "Memoria",
"timezone": "Fuso orario",
"no_update_available": "Il sistema è aggiornato all'ultima versione",
"update_failed": "Impossibile verificare la presenza di un aggiornamento"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Valore letto dal record A/AAAA. Questo è supportato finché il record punta alla risorsa corretta.", "cname_from_a": "Valore letto dal record A/AAAA. Questo è supportato finché il record punta alla risorsa corretta.",
@@ -514,7 +529,7 @@
"delete1": "Elimina dalla sorgente al termine", "delete1": "Elimina dalla sorgente al termine",
"delete2": "Delete messages on destination that are not on source", "delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Elimina duplicati nella destinazione", "delete2duplicates": "Elimina duplicati nella destinazione",
"delete_ays": "Please confirm the deletion process.", "delete_ays": "Si prega di confermare il processo di eliminazione.",
"description": "Descrizione", "description": "Descrizione",
"disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)", "disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)",
"domain": "Modifica dominio", "domain": "Modifica dominio",
@@ -527,12 +542,12 @@
"exclude": "Escludi oggetti (regex)", "exclude": "Escludi oggetti (regex)",
"extended_sender_acl": "External sender addresses", "extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.", "extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"force_pw_update": "Force password update at next login", "force_pw_update": "Forza l'aggiornamento della password al prossimo accesso",
"force_pw_update_info": "Questo utente potrà accedere solo a %s.", "force_pw_update_info": "Questo utente potrà accedere solo a %s.",
"full_name": "Nome completo", "full_name": "Nome completo",
"gal": "Global Address List", "gal": "Global Address List",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>", "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
"generate": "generate", "generate": "crea",
"grant_types": "Grant types", "grant_types": "Grant types",
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inattivo", "inactive": "Inattivo",
@@ -549,7 +564,7 @@
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.", "mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
"mins_interval": "Intervallo (min)", "mins_interval": "Intervallo (min)",
"multiple_bookings": "Prenotazioni multiple", "multiple_bookings": "Prenotazioni multiple",
"nexthop": "Next hop", "nexthop": "Prossimo hop",
"password": "Password", "password": "Password",
"password_repeat": "Conferma password (riscrivi)", "password_repeat": "Conferma password (riscrivi)",
"previous": "Pagina precedente", "previous": "Pagina precedente",
@@ -561,9 +576,9 @@
"pushover_sender_array": "Only consider the following sender email addresses <small>(comma-separated)</small>", "pushover_sender_array": "Only consider the following sender email addresses <small>(comma-separated)</small>",
"pushover_sender_regex": "Consider the following sender regex", "pushover_sender_regex": "Consider the following sender regex",
"pushover_text": "Notification text", "pushover_text": "Notification text",
"pushover_title": "Notification title", "pushover_title": "Titolo della notifica",
"pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)", "pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)",
"pushover_verify": "Verify credentials", "pushover_verify": "Verifica credenziali",
"quota_mb": "Spazio (MiB)", "quota_mb": "Spazio (MiB)",
"quota_warning_bcc": "Quota warning BCC", "quota_warning_bcc": "Quota warning BCC",
"quota_warning_bcc_info": "Warnings will be sent as separate copies to the following recipients. The subject will be suffixed by the corresponding username in brackets, for example: <code>Quota warning (user@example.com)</code>.", "quota_warning_bcc_info": "Warnings will be sent as separate copies to the following recipients. The subject will be suffixed by the corresponding username in brackets, for example: <code>Quota warning (user@example.com)</code>.",
@@ -582,42 +597,44 @@
"sender_acl": "Consenti di inviare come", "sender_acl": "Consenti di inviare come",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>",
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Short description", "sieve_desc": "Breve descrizione",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)", "skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_visible": "Alias is visible in SOGo", "sogo_visible": "L'alias è visibile in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.", "sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses", "spam_alias": "Create or change time limited alias addresses",
"spam_filter": "Spam filter", "spam_filter": "Spam filter",
"spam_policy": "Add or remove items to white-/blacklist", "spam_policy": "Aggiungi o rimuovi elementi dalla whitelist/blacklist",
"spam_score": "Set a custom spam score", "spam_score": "Imposta un punteggio spam personalizzato",
"subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>", "subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>",
"syncjob": "Modifica sincronizzazione", "syncjob": "Modifica sincronizzazione",
"target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>", "target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>",
"target_domain": "Target dominio", "target_domain": "Target dominio",
"timeout1": "Timeout for connection to remote host", "timeout1": "Timeout per la connessione all'host remoto",
"timeout2": "Timeout for connection to local host", "timeout2": "Timeout per la connessione all'host remoto",
"title": "Modifica oggetto", "title": "Modifica oggetto",
"unchanged_if_empty": "Se immutato lasciare vuoto", "unchanged_if_empty": "Se immutato lasciare vuoto",
"username": "Username", "username": "Nome utente",
"validate_save": "Convalida e salva", "validate_save": "Convalida e salva",
"pushover": "Pushover", "pushover": "Pushover",
"sogo_access_info": "Il single-sign-on dall'interno dell'interfaccia di posta rimane funzionante. Questa impostazione non influisce sull'accesso a tutti gli altri servizi né cancella o modifica il profilo SOGo esistente dell'utente.", "sogo_access_info": "Il single-sign-on dall'interno dell'interfaccia di posta rimane funzionante. Questa impostazione non influisce sull'accesso a tutti gli altri servizi né cancella o modifica il profilo SOGo esistente dell'utente.",
"none_inherit": "Nessuno / Eredita", "none_inherit": "Nessuno / Eredita",
"sogo_access": "Concedere l'accesso diretto a SOGo", "sogo_access": "Concedere l'accesso diretto a SOGo",
"acl": "ACL (autorizzazione)", "acl": "ACL (autorizzazione)",
"app_passwd_protocols": "Protocolli consentiti per la password dell'app" "app_passwd_protocols": "Protocolli consentiti per la password dell'app",
"last_modified": "Ultima modifica",
"pushover_sound": "Suono"
}, },
"fido2": { "fido2": {
"confirm": "Confirm", "confirm": "Conferma",
"fido2_auth": "Login with FIDO2", "fido2_auth": "Login with FIDO2",
"fido2_success": "Device successfully registered", "fido2_success": "Dispositivo registrato con successo",
"fido2_validation_failed": "Validation failed", "fido2_validation_failed": "Validazione fallita",
"fn": "Friendly name", "fn": "Nome descrittivo",
"known_ids": "Known IDs", "known_ids": "ID conosciuti",
"none": "Disabled", "none": "Disabilitato",
"register_status": "Registration status", "register_status": "Stato di registrazione",
"rename": "Rename", "rename": "Rinominare",
"set_fido2": "Register FIDO2 device", "set_fido2": "Register FIDO2 device",
"set_fn": "Set friendly name", "set_fn": "Set friendly name",
"start_fido2_validation": "Start FIDO2 validation", "start_fido2_validation": "Start FIDO2 validation",
@@ -641,13 +658,14 @@
"header": { "header": {
"administration": "Amministrazione", "administration": "Amministrazione",
"apps": "App", "apps": "App",
"debug": "Informazioni di sistema", "debug": "Informazioni",
"email": "E-Mail", "email": "E-Mail",
"mailcow_config": "Configurazione", "mailcow_config": "Configurazione",
"quarantine": "Quarantena", "quarantine": "Quarantena",
"restart_netfilter": "Riavvia netfilter", "restart_netfilter": "Riavvia netfilter",
"restart_sogo": "Riavvia SOGo", "restart_sogo": "Riavvia SOGo",
"user_settings": "Impostazioni utente" "user_settings": "Impostazioni utente",
"mailcow_system": "Sistema"
}, },
"info": { "info": {
"awaiting_tfa_confirmation": "In attesa di conferma TFA", "awaiting_tfa_confirmation": "In attesa di conferma TFA",
@@ -661,7 +679,7 @@
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.", "mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
"other_logins": "Key login", "other_logins": "Key login",
"password": "Password", "password": "Password",
"username": "Username" "username": "Nome utente"
}, },
"mailbox": { "mailbox": {
"action": "Azione", "action": "Azione",
@@ -733,7 +751,7 @@
"inactive": "Inattivo", "inactive": "Inattivo",
"insert_preset": "Insert example preset \"%s\"", "insert_preset": "Insert example preset \"%s\"",
"kind": "Tipo", "kind": "Tipo",
"last_mail_login": "Last mail login", "last_mail_login": "Ultimo accesso alla posta",
"last_modified": "Ultima modifica", "last_modified": "Ultima modifica",
"last_pw_change": "Ultima modifica della password", "last_pw_change": "Ultima modifica della password",
"last_run": "Ultima esecuzione", "last_run": "Ultima esecuzione",
@@ -828,7 +846,15 @@
"sender": "Mittente", "sender": "Mittente",
"all_domains": "Tutti i domini", "all_domains": "Tutti i domini",
"recipient": "Destinatario", "recipient": "Destinatario",
"syncjob_EX_OK": "Successo" "syncjob_EX_OK": "Successo",
"add_template": "Aggiungi template",
"force_pw_update": "Forza il cambio della password al prossimo accesso",
"relay_unknown": "Inoltra a caselle di posta sconosciute",
"mailbox_templates": "Template della mailbox",
"domain_templates": "Template di dominio",
"gal": "Elenco indirizzi globale",
"templates": "Template",
"template": "Template"
}, },
"oauth2": { "oauth2": {
"access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.", "access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.",
@@ -847,7 +873,7 @@
"confirm_delete": "Conferma l'eliminazione di questo elemento.", "confirm_delete": "Conferma l'eliminazione di questo elemento.",
"danger": "Pericolo", "danger": "Pericolo",
"deliver_inbox": "Consegna nella posta in arrivo", "deliver_inbox": "Consegna nella posta in arrivo",
"disabled_by_config": "The current system configuration disables the quarantine functionality. Please set \"retentions per mailbox\" and a \"maximum size\" for quarantine elements.", "disabled_by_config": "L'attuale configurazione del sistema disabilita la funzionalità di quarantena. Imposta \"conservazioni per casella di posta\" e \"dimensione massima\" per gli elementi di quarantena.",
"download_eml": "Download (.eml)", "download_eml": "Download (.eml)",
"empty": "Nessun risultato", "empty": "Nessun risultato",
"high_danger": "Alto", "high_danger": "Alto",
@@ -893,8 +919,18 @@
"type": "Tipologia" "type": "Tipologia"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Consegna", "queue_manager": "Gestore code",
"queue_manager": "Gestore code" "delete": "Cancella tutto",
"ays": "Conferma che desideri eliminare tutti gli elementi dalla coda corrente.",
"info": "La coda di posta contiene tutte le e-mail in attesa di consegna. Se un'e-mail rimane a lungo nella coda di posta, viene automaticamente cancellata dal sistema.<br>Il messaggio di errore della rispettiva e-mail fornisce informazioni sul motivo per cui non è stato possibile consegnarla.",
"deliver_mail_legend": "Tenta di riconsegnare i messaggi selezionati.",
"hold_mail": "Blocca",
"flush": "Svuota la coda",
"deliver_mail": "Consegna",
"show_message": "Mostra messaggio",
"unhold_mail": "Sblocca",
"hold_mail_legend": "Blocca le mail selezionate. (Previene ulteriori tentativi di consegna)",
"legend": "Funzioni delle azioni della coda di posta:"
}, },
"start": { "start": {
"help": "Mostra/Nascondi pannello di aiuto", "help": "Mostra/Nascondi pannello di aiuto",
@@ -979,7 +1015,10 @@
"verified_totp_login": "Verified TOTP login", "verified_totp_login": "Verified TOTP login",
"verified_webauthn_login": "Verified WebAuthn login", "verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login", "verified_yotp_login": "Verified Yubico OTP login",
"domain_add_dkim_available": "Esisteva già una chiave DKIM" "domain_add_dkim_available": "Esisteva già una chiave DKIM",
"template_added": "Aggiunto template %s",
"template_modified": "Le modifiche al template %s sono state salvate",
"template_removed": "Il template con ID %s è stato cancellato"
}, },
"tfa": { "tfa": {
"api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>", "api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>",
@@ -1143,7 +1182,7 @@
"tls_enforce_in": "Imponi TLS in ingresso", "tls_enforce_in": "Imponi TLS in ingresso",
"tls_enforce_out": "Imponi TLS in uscita", "tls_enforce_out": "Imponi TLS in uscita",
"tls_policy": "Politica di crittografia", "tls_policy": "Politica di crittografia",
"tls_policy_warning": "<strong>Attenzione:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.", "tls_policy_warning": "<strong>Attenzione:</strong> Se decidi di applicare il trasferimento di posta crittografato, potresti perdere le email.<br />I messaggi che non soddisfano la politica verranno respinti con un hard fail dal sistema di posta.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.",
"user_settings": "Impostazioni utente", "user_settings": "Impostazioni utente",
"username": "Nome utente", "username": "Nome utente",
"verify": "Verifica", "verify": "Verifica",
@@ -1167,7 +1206,8 @@
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto", "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati", "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
"with_app_password": "con password dell'app", "with_app_password": "con password dell'app",
"direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile." "direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile.",
"pushover_sound": "Suono"
}, },
"warning": { "warning": {
"cannot_delete_self": "Cannot delete logged in user", "cannot_delete_self": "Cannot delete logged in user",
@@ -1188,5 +1228,29 @@
"second": "messaggi / secondo", "second": "messaggi / secondo",
"hour": "messaggi / ora", "hour": "messaggi / ora",
"day": "messaggi / giorno" "day": "messaggi / giorno"
},
"datatables": {
"infoFiltered": "(filtrato da _MAX_ voci totali)",
"collapse_all": "Comprimi tutto",
"emptyTable": "Nessun dato disponibile nella tabella",
"expand_all": "Espandi tutto",
"info": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci",
"infoEmpty": "Visualizzazione da 0 a 0 di 0 voci",
"thousands": ".",
"loadingRecords": "Caricamento...",
"processing": "Attendere prego...",
"search": "Ricerca:",
"zeroRecords": "Nessuna corrispondenza trovata",
"paginate": {
"first": "Prima",
"last": "Ultima",
"next": "Prossima",
"previous": "Precedente"
},
"lengthMenu": "Mostra _MENU_ voci",
"aria": {
"sortAscending": ": attivare l'ordinamento crescente delle colonne",
"sortDescending": ": attivare l'ordinamento decrescente delle colonne"
}
} }
} }

View File

@@ -777,7 +777,6 @@
"toggle_all": "선택 반전" "toggle_all": "선택 반전"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Deliver",
"queue_manager": "대기열 관리자" "queue_manager": "대기열 관리자"
}, },
"start": { "start": {

View File

@@ -400,7 +400,6 @@
"toggle_all": "Pārslēgt visu" "toggle_all": "Pārslēgt visu"
}, },
"queue": { "queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager" "queue_manager": "Queue Manager"
}, },
"start": { "start": {

View File

@@ -815,7 +815,6 @@
"toggle_all": "Selecteer alles" "toggle_all": "Selecteer alles"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Lever af",
"queue_manager": "Queue manager" "queue_manager": "Queue manager"
}, },
"start": { "start": {

View File

@@ -285,7 +285,6 @@
"toggle_all": "Zaznacz wszystkie" "toggle_all": "Zaznacz wszystkie"
}, },
"queue": { "queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager" "queue_manager": "Queue Manager"
}, },
"start": { "start": {

View File

@@ -193,7 +193,6 @@
"remove": "Remover" "remove": "Remover"
}, },
"queue": { "queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager" "queue_manager": "Queue Manager"
}, },
"start": { "start": {

View File

@@ -895,7 +895,6 @@
"toggle_all": "Comută toate" "toggle_all": "Comută toate"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Livrează",
"queue_manager": "Manager de coadă" "queue_manager": "Manager de coadă"
}, },
"ratelimit": { "ratelimit": {

View File

@@ -893,7 +893,6 @@
"type": "Тип" "type": "Тип"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Доставить",
"queue_manager": "Очередь на отправку" "queue_manager": "Очередь на отправку"
}, },
"ratelimit": { "ratelimit": {

View File

@@ -895,7 +895,6 @@
"type": "Typ" "type": "Typ"
}, },
"queue": { "queue": {
"queue_deliver_mail": "Doručiť",
"queue_manager": "Správca fronty" "queue_manager": "Správca fronty"
}, },
"ratelimit": { "ratelimit": {

View File

@@ -896,7 +896,6 @@
"table_size_show_n": "Відображати %s полів" "table_size_show_n": "Відображати %s полів"
}, },
"queue": { "queue": {
"queue_hold_mail": "Поставити на утримання",
"queue_manager": "Черга на відправлення" "queue_manager": "Черга на відправлення"
}, },
"ratelimit": { "ratelimit": {

View File

@@ -31,7 +31,7 @@
"unlimited_quota": "无限邮箱容量配额" "unlimited_quota": "无限邮箱容量配额"
}, },
"add": { "add": {
"activate_filter_warn": "当 \"启用\" 选项被勾选后,所有其他过滤器都会被禁用", "activate_filter_warn": "当“启用”选项被勾选后,其它所有的过滤器都会被禁用",
"active": "启用", "active": "启用",
"add": "添加", "add": "添加",
"add_domain_only": "只添加域名", "add_domain_only": "只添加域名",
@@ -208,7 +208,7 @@
"includes": "包括这些收件人", "includes": "包括这些收件人",
"is_mx_based": "基于 MX 记录", "is_mx_based": "基于 MX 记录",
"last_applied": "最后应用的条目", "last_applied": "最后应用的条目",
"license_info": "你不需要获取证书便可以使用此项目,但是获取证书可以帮助此项目进一步发展。<br>在这里<a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">注册</a>你的 GUID或者为你的 Mailcow 安装<a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">购买</a>支持服务。", "license_info": "使用并不需要许可证,但获得许可证能够帮助此项目进一步发展。<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"订购 SAL\">在这里注册你的 GUID </a>或者<a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"订购支持服务\">为你的 Mailcow 安装购买支持服务。</a>",
"link": "链接", "link": "链接",
"loading": "请等待...", "loading": "请等待...",
"login_time": "登录时间", "login_time": "登录时间",
@@ -257,7 +257,6 @@
"quarantine_release_format_att": "附件", "quarantine_release_format_att": "附件",
"quarantine_release_format_raw": "原件 (未修改)", "quarantine_release_format_raw": "原件 (未修改)",
"quarantine_retention_size": "每个邮箱保留隔离项目数:<br><small>0 表示 <b>禁用</b>。</small>", "quarantine_retention_size": "每个邮箱保留隔离项目数:<br><small>0 表示 <b>禁用</b>。</small>",
"queue_manager": "队列管理",
"quota_notification_html": "通知邮件模板:<br><small>留空则恢复默认模板。</small>", "quota_notification_html": "通知邮件模板:<br><small>留空则恢复默认模板。</small>",
"quota_notification_sender": "通知邮件发件人", "quota_notification_sender": "通知邮件发件人",
"quota_notification_subject": "通知邮件主题", "quota_notification_subject": "通知邮件主题",
@@ -336,7 +335,8 @@
"username": "用户名", "username": "用户名",
"validate_license_now": "通过证书服务器验证 GUID", "validate_license_now": "通过证书服务器验证 GUID",
"verify": "验证", "verify": "验证",
"yes": "&#10003;" "yes": "&#10003;",
"options": "选项"
}, },
"danger": { "danger": {
"access_denied": "访问被拒绝或者表单数据无效", "access_denied": "访问被拒绝或者表单数据无效",
@@ -403,7 +403,7 @@
"max_alias_exceeded": "超出最大别名数", "max_alias_exceeded": "超出最大别名数",
"max_mailbox_exceeded": "超出最大邮箱数 (%d / %d)", "max_mailbox_exceeded": "超出最大邮箱数 (%d / %d)",
"max_quota_in_use": "邮箱数必须大于等于 %d MiB", "max_quota_in_use": "邮箱数必须大于等于 %d MiB",
"maxquota_empty": "每个邮箱的最大配额必须不为0", "maxquota_empty": "每个邮箱的最大配额必须不为 0 。",
"mysql_error": "MySQL 错误: %s", "mysql_error": "MySQL 错误: %s",
"network_host_invalid": "网络或主机无效: %s", "network_host_invalid": "网络或主机无效: %s",
"next_hop_interferes": "%s 与下一跳 %s 冲突", "next_hop_interferes": "%s 与下一跳 %s 冲突",
@@ -455,7 +455,8 @@
"username_invalid": "用户名 %s 无法使用", "username_invalid": "用户名 %s 无法使用",
"validity_missing": "请设置有效期", "validity_missing": "请设置有效期",
"value_missing": "请填入所有值", "value_missing": "请填入所有值",
"yotp_verification_failed": "Yubico OTP 认证失败: %s" "yotp_verification_failed": "Yubico OTP 认证失败: %s",
"template_exists": "模板 %s 已存在"
}, },
"debug": { "debug": {
"chart_this_server": "图表 (此服务器)", "chart_this_server": "图表 (此服务器)",
@@ -474,7 +475,7 @@
"restart_container": "重启", "restart_container": "重启",
"service": "服务", "service": "服务",
"size": "大小", "size": "大小",
"solr_dead": "Solr 在启动中、已关闭或已停止", "solr_dead": "Solr 在启动中、已关闭或已停止",
"solr_status": "Solr 状态", "solr_status": "Solr 状态",
"started_at": "开始于", "started_at": "开始于",
"started_on": "启动于", "started_on": "启动于",
@@ -482,10 +483,14 @@
"success": "成功", "success": "成功",
"system_containers": "系统和容器", "system_containers": "系统和容器",
"uptime": "运行时间", "uptime": "运行时间",
"username": "用户名" "username": "用户名",
"container_disabled": "容器已被停止或禁用",
"container_running": "运行中",
"cores": "核心数",
"memory": "内存"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "虽然此记录为 A/AAAA 类型,但只要记录指向正确的资源便可以被支持", "cname_from_a": "来自 A/AAAA 记录的值。但只要记录指向正确的资源即可。",
"dns_records": "DNS 记录", "dns_records": "DNS 记录",
"dns_records_24hours": "请注意 DNS 记录的更改可能需要24小时才可以使此页面的当前状态显示正确。此页面为你提供了一个可以便捷查询如何配置 DNS 记录以及检查你的 DNS 记录是否正确的方式。", "dns_records_24hours": "请注意 DNS 记录的更改可能需要24小时才可以使此页面的当前状态显示正确。此页面为你提供了一个可以便捷查询如何配置 DNS 记录以及检查你的 DNS 记录是否正确的方式。",
"dns_records_data": "正确数据", "dns_records_data": "正确数据",
@@ -502,7 +507,7 @@
"advanced_settings": "高级设置", "advanced_settings": "高级设置",
"alias": "编辑别名", "alias": "编辑别名",
"allow_from_smtp": "只允许这些 IP 使用 <b>SMTP</b>", "allow_from_smtp": "只允许这些 IP 使用 <b>SMTP</b>",
"allow_from_smtp_info": "留空以允许所有发送者。<br>IPv4/IPv6地址网络", "allow_from_smtp_info": "留空以允许所有发送者。<br>IPv4/IPv6 地址网络",
"allowed_protocols": "允许的协议", "allowed_protocols": "允许的协议",
"app_name": "应用名称", "app_name": "应用名称",
"app_passwd": "应用密码", "app_passwd": "应用密码",
@@ -630,7 +635,7 @@
"delete_these_items": "请确认对以下对象 ID 的更改", "delete_these_items": "请确认对以下对象 ID 的更改",
"hibp_check": "使用 haveibeenpwned.com 网站检查密码", "hibp_check": "使用 haveibeenpwned.com 网站检查密码",
"hibp_nok": "匹配到密码!存在潜在的使用危险!", "hibp_nok": "匹配到密码!存在潜在的使用危险!",
"hibp_ok": "未匹配到密码", "hibp_ok": "未找到匹配的记录。",
"loading": "请等待...", "loading": "请等待...",
"nothing_selected": "未选择", "nothing_selected": "未选择",
"restart_container": "重启容器", "restart_container": "重启容器",
@@ -686,7 +691,7 @@
"aliases": "别名", "aliases": "别名",
"all_domains": "全部域名", "all_domains": "全部域名",
"allow_from_smtp": "只允许这些 IP 使用 <b>SMTP</b>", "allow_from_smtp": "只允许这些 IP 使用 <b>SMTP</b>",
"allow_from_smtp_info": "留空以允许所有发送者<br>IPv4/IPv6地址或网络", "allow_from_smtp_info": "留空以允许所有发送者<br>IPv4/IPv6 地址或网络",
"allowed_protocols": "允许用户直接访问的协议 (不会影响应用的密码协议)", "allowed_protocols": "允许用户直接访问的协议 (不会影响应用的密码协议)",
"backup_mx": "中继域名", "backup_mx": "中继域名",
"bcc": "BCC", "bcc": "BCC",
@@ -740,7 +745,7 @@
"last_run_reset": "下一次运行", "last_run_reset": "下一次运行",
"mailbox": "邮箱", "mailbox": "邮箱",
"mailbox_defaults": "默认设置", "mailbox_defaults": "默认设置",
"mailbox_defaults_info": "配置新邮箱的默认设置", "mailbox_defaults_info": "配置新邮箱的默认设置",
"mailbox_defquota": "默认邮箱大小", "mailbox_defquota": "默认邮箱大小",
"mailbox_quota": "最大邮箱大小", "mailbox_quota": "最大邮箱大小",
"mailboxes": "邮箱", "mailboxes": "邮箱",
@@ -821,7 +826,12 @@
"username": "用户名", "username": "用户名",
"waiting": "等待中", "waiting": "等待中",
"weekly": "每周", "weekly": "每周",
"yes": "&#10003;" "yes": "&#10003;",
"domain_templates": "域名模板",
"mailbox_templates": "邮箱模板",
"gal": "全局地址列表",
"max_aliases": "最大别名数",
"max_mailboxes": "最大可能的邮箱数"
}, },
"oauth2": { "oauth2": {
"access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权", "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权",
@@ -866,7 +876,7 @@
"refresh": "刷新", "refresh": "刷新",
"rejected": "已拒绝", "rejected": "已拒绝",
"release": "移除", "release": "移除",
"release_body": "我们已在此消息中将你的消息作为 eml 附件文件", "release_body": "我们已将你的消息作为 eml 文件附在此消息中。",
"release_subject": "存在潜在危险的隔离文件 %s", "release_subject": "存在潜在危险的隔离文件 %s",
"remove": "删除", "remove": "删除",
"rewrite_subject": "重写主题", "rewrite_subject": "重写主题",
@@ -886,8 +896,8 @@
"type": "类型" "type": "类型"
}, },
"queue": { "queue": {
"queue_deliver_mail": "递送", "queue_manager": "队列管理器",
"queue_manager": "队列管理器" "delete": "全部删除"
}, },
"ratelimit": { "ratelimit": {
"disabled": "禁用", "disabled": "禁用",
@@ -1181,5 +1191,18 @@
"quota_exceeded_scope": "域名配额超标: 此域名下现在只能创建无限容量的邮箱。", "quota_exceeded_scope": "域名配额超标: 此域名下现在只能创建无限容量的邮箱。",
"session_token": "表单字段无效: Token 不匹配", "session_token": "表单字段无效: Token 不匹配",
"session_ua": "表单字段无效: User-Agent 校验错误" "session_ua": "表单字段无效: User-Agent 校验错误"
},
"datatables": {
"info": "正从 _TOTAL_ 个条目中显示 _START_ 到 _END_ 条目",
"collapse_all": "全部折叠",
"expand_all": "全部展开",
"infoEmpty": "正从共 0 个条目中显示从 0 到 0 条目",
"processing": "请稍等...",
"search": "搜索:",
"paginate": {
"first": "第一页",
"last": "最后一页",
"previous": "上一页"
}
} }
} }

View File

@@ -257,7 +257,6 @@
"quarantine_release_format_att": "如附件", "quarantine_release_format_att": "如附件",
"quarantine_release_format_raw": "未修改的原始信件", "quarantine_release_format_raw": "未修改的原始信件",
"quarantine_retention_size": "每個信箱的隔離保留上限:<br><small>0 表示 <b>停用</b>。</small>", "quarantine_retention_size": "每個信箱的隔離保留上限:<br><small>0 表示 <b>停用</b>。</small>",
"queue_manager": "佇列管理",
"quota_notification_html": "通知信模版:<br><small>留空來重設為預設模版。</small>", "quota_notification_html": "通知信模版:<br><small>留空來重設為預設模版。</small>",
"quota_notification_sender": "通知信寄件人", "quota_notification_sender": "通知信寄件人",
"quota_notification_subject": "通知信主旨", "quota_notification_subject": "通知信主旨",

View File

@@ -33,6 +33,20 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<legend style="padding-top:20px" unselectable="on">{{ lang.admin.ip_check }}</legend><hr />
<div id="ip_check">
<form class="form" data-id="ip_check" role="form" method="post">
<div class="mb-4">
<input class="form-check-input" type="checkbox" value="1" name="ip_check_opt_in" id="ip_check_opt_in" {% if ip_check == 1 %}checked{% endif %}>
<label class="form-check-label" for="ip_check_opt_in">
{{ lang.admin.ip_check_opt_in|raw }}
</label>
</div>
<p><div class="btn-group">
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected" data-item="admin" data-id="ip_check" data-reload="no" data-api-url='edit/ip_check' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div></p>
</form>
</div>
<legend>{{ lang.admin.app_links }}</legend><hr /> <legend>{{ lang.admin.app_links }}</legend><hr />
<p class="text-muted">{{ lang.admin.merged_vars_hint|raw }}</p> <p class="text-muted">{{ lang.admin.merged_vars_hint|raw }}</p>
<form class="form-inline" data-id="app_links" role="form" method="post"> <form class="form-inline" data-id="app_links" role="form" method="post">

View File

@@ -36,7 +36,7 @@
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="rlyhost_password">{{ lang.admin.password }}</label> <label for="rlyhost_password">{{ lang.admin.password }}</label>
<input class="form-control" id="rlyhost_password" name="password"> <input class="form-control" id="rlyhost_password" name="password" type="password">
</div> </div>
<button class="btn btn-sm d-block d-sm-inline btn-success" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.admin.add }}</button> <button class="btn btn-sm d-block d-sm-inline btn-success" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.admin.add }}</button>
</form> </form>
@@ -86,7 +86,7 @@
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="transport_password">{{ lang.admin.password }}</label> <label for="transport_password">{{ lang.admin.password }}</label>
<input class="form-control" id="transport_password" name="password"> <input class="form-control" id="transport_password" name="password" type="password">
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label> <label>

View File

@@ -80,7 +80,9 @@
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li><a href="/queue" class="dropdown-item {% if is_uri('queue') %}active{% endif %}">{{ lang.queue.queue_manager }}</a></li> <li><a href="/queue" class="dropdown-item {% if is_uri('queue') %}active{% endif %}">{{ lang.queue.queue_manager }}</a></li>
{% endif %} {% endif %}
{% if mailcow_cc_role == 'admin' %}
<li><a href="#" class="dropdown-item" data-bs-toggle="modal" data-container="sogo-mailcow" data-bs-target="#RestartContainer">{{ lang.header.restart_sogo }}</a></li> <li><a href="#" class="dropdown-item" data-bs-toggle="modal" data-container="sogo-mailcow" data-bs-target="#RestartContainer">{{ lang.header.restart_sogo }}</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@@ -52,8 +52,18 @@
<tr> <tr>
<td>IPs</td> <td>IPs</td>
<td class="text-break"> <td class="text-break">
<span class="d-block" id="host_ipv4">-</span> {% if ip_check == 1 %}
<span class="d-block" id="host_ipv6">-</span> <span class="d-none" id="host_ipv4">-</span>
<span class="d-none mb-2" id="host_ipv6">-</span>
<button class="d-block btn btn-primary btn-sm" id="host_show_ip">
<span class="text">{{ lang.debug.show_ip }}</span>
<div class="spinner-border spinner-border-sm d-none" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</button>
{% else %}
<span class="d-block">{{ lang.admin.ip_check_disabled|raw }}</span>
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -160,8 +170,14 @@
<div class="d-flex p-2 list-group-header"> <div class="d-flex p-2 list-group-header">
<div> <div>
<span class="fw-bold">solr-mailcow</span> <span class="fw-bold">solr-mailcow</span>
{% if containers["solr-mailcow"].State.Running == 1 %}
<span class="d-block d-md-inline">({{ containers["solr-mailcow"].Config.Image }})</span> <span class="d-block d-md-inline">({{ containers["solr-mailcow"].Config.Image }})</span>
{% endif %}
{% if containers["solr-mailcow"].State.Running == 1 %}
<small class="d-block">({{ lang.debug.started_on }} <span class="parse_date">{{ containers["solr-mailcow"].State.StartedAtHR }}</span>)</small> <small class="d-block">({{ lang.debug.started_on }} <span class="parse_date">{{ containers["solr-mailcow"].State.StartedAtHR }}</span>)</small>
{% elseif containers["solr-mailcow"].State.Running != 1 %}
<small class="d-block">{{ lang.debug.container_disabled }}</small>
{% endif %}
{% if containers["solr-mailcow"].State.Running == 1 %} {% if containers["solr-mailcow"].State.Running == 1 %}
<span class="badge fs-7 bg-success loader" style="min-width:100px"> <span class="badge fs-7 bg-success loader" style="min-width:100px">
{{ lang.debug.container_running }} {{ lang.debug.container_running }}
@@ -169,19 +185,22 @@
<span class="loader-dot">.</span> <span class="loader-dot">.</span>
<span class="loader-dot">.</span> <span class="loader-dot">.</span>
</span> </span>
{% elseif containers["solr-mailcow"].State %} {% elseif containers["solr-mailcow"].State.Running != 1 %}
<span class="badge fs-7 bg-danger" style="min-width:100px"> <span class="badge fs-7 bg-danger" style="min-width:100px">
{{ lang.debug.container_stopped }} {{ lang.debug.container_stopped }}
<i class="bi-x ms-1"></i> <i class="bi-x ms-1"></i>
</span> </span>
{% endif %} {% endif %}
</div> </div>
{% if containers["solr-mailcow"].State.Running == 1 %}
<div class="mt-auto ms-auto"> <div class="mt-auto ms-auto">
<button class="btn btn-light" type="button" data-bs-toggle="collapse" data-bs-target="#solr-mailcowCollapse" aria-expanded="false" aria-controls="solr-mailcowCollapse"> <button class="btn btn-light" type="button" data-bs-toggle="collapse" data-bs-target="#solr-mailcowCollapse" aria-expanded="false" aria-controls="solr-mailcowCollapse">
<i class="bi bi-caret-down-fill caret"></i> <i class="bi bi-caret-down-fill caret"></i>
</button> </button>
</div> </div>
{% endif %}
</div> </div>
{% if containers["solr-mailcow"].State.Running == 1 %}
<div class="collapse p-0 list-group-details container-details-collapse" id="solr-mailcowCollapse" data-id="{{ containers["solr-mailcow"].Id }}"> <div class="collapse p-0 list-group-details container-details-collapse" id="solr-mailcowCollapse" data-id="{{ containers["solr-mailcow"].Id }}">
<div class="row p-2 pt-4"> <div class="row p-2 pt-4">
<div class="col-sm-3"> <div class="col-sm-3">
@@ -238,6 +257,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
@@ -330,9 +350,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="postfix_log" data-table="postfix_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="postfix_log" data-table="postfix_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="postfix_log" data-table="postfix_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="postfix_log" data-table="postfix_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="postfix_log" class="table table-striped dt-responsive w-100"></table> <table id="postfix_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -353,9 +373,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="mailcow_ui" data-table="ui_logs" data-log-url="ui" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="mailcow_ui" data-table="ui_logs" data-log-url="ui" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="mailcow_ui" data-table="ui_logs" data-log-url="ui" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="mailcow_ui" data-table="ui_logs" data-log-url="ui" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="ui_logs" data-table="ui_logs" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="ui_logs" data-table="ui_logs" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="ui_logs" data-table="ui_logs" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="ui_logs" data-table="ui_logs" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="ui_logs" class="table table-striped dt-responsive w-100"></table> <table id="ui_logs" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -376,9 +396,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="sasl_log_table" data-table="sasl_logs" data-log-url="ui" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="sasl_log_table" data-table="sasl_logs" data-log-url="ui" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="sasl_log_table" data-table="sasl_logs" data-log-url="ui" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="sasl_log_table" data-table="sasl_logs" data-log-url="ui" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="sasl_logs" data-table="sasl_logs" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sasl_logs" data-table="sasl_logs" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="sasl_logs" data-table="sasl_logs" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sasl_logs" data-table="sasl_logs" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="sasl_logs" class="table table-striped dt-responsive w-100"></table> <table id="sasl_logs" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -399,9 +419,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="dovecot_log" data-table="dovecot_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="dovecot_log" data-table="dovecot_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="dovecot_log" data-table="dovecot_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="dovecot_log" data-table="dovecot_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="dovecot_log" class="table table-striped dt-responsive w-100"></table> <table id="dovecot_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -422,9 +442,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="sogo_log" data-table="sogo_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sogo_log" data-table="sogo_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="sogo_log" data-table="sogo_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sogo_log" data-table="sogo_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="sogo_log" class="table table-striped dt-responsive w-100"></table> <table id="sogo_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -445,9 +465,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="netfilter_log" data-log-url="netfilter" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="netfilter_log" data-log-url="netfilter" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="netfilter_log" data-log-url="netfilter" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="netfilter_log" data-log-url="netfilter" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="netfilter_log" data-table="netfilter_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="netfilter_log" data-table="netfilter_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="netfilter_log" data-table="netfilter_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="netfilter_log" data-table="netfilter_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="netfilter_log" class="table table-striped dt-responsive w-100"></table> <table id="netfilter_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -473,9 +493,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd_history" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd_history" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd_history" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd_history" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="rspamd_history" data-table="rspamd_history" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="rspamd_history" data-table="rspamd_history" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="rspamd_history" data-table="rspamd_history" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="rspamd_history" data-table="rspamd_history" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="rspamd_history" class="table table-striped dt-responsive w-100"></table> <table id="rspamd_history" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -496,9 +516,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="autodiscover_log" data-table="autodiscover_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="autodiscover_log" data-table="autodiscover_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="autodiscover_log" data-table="autodiscover_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="autodiscover_log" data-table="autodiscover_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="autodiscover_log" class="table table-striped dt-responsive w-100"></table> <table id="autodiscover_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -519,9 +539,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="watchdog_log" data-table="watchdog_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="watchdog_log" data-table="watchdog_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="watchdog_log" data-table="watchdog_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="watchdog_log" data-table="watchdog_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="watchdog_log" class="table table-striped dt-responsive w-100"></table> <table id="watchdog_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -542,9 +562,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="acme_log" data-table="acme_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="acme_log" data-table="acme_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="acme_log" data-table="acme_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="acme_log" data-table="acme_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="acme_log" class="table table-striped dt-responsive w-100"></table> <table id="acme_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -565,9 +585,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="api_log" data-table="api_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="api_log" data-table="api_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="api_log" data-table="api_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="api_log" data-table="api_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<table id="api_log" class="table table-striped dt-responsive w-100"></table> <table id="api_log" class="table table-striped dt-responsive w-100"></table>
</div> </div>
@@ -588,9 +608,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="100" href="#">+ 100</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="100" href="#">+ 100</a></li>
<li><a class="dropdown-item add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="1000" href="#">+ 1000</a></li> <li><a class="dropdown-item add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="1000" href="#">+ 1000</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="rl_log" data-table="rl_log" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="rl_log" data-table="rl_log" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="rl_log" data-table="rl_log" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="rl_log" data-table="rl_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<p class="text-muted">{{ lang.admin.hash_remove_info }}</p> <p class="text-muted">{{ lang.admin.hash_remove_info }}</p>
<table id="rl_log" class="table table-striped dt-responsive w-100"></table> <table id="rl_log" class="table table-striped dt-responsive w-100"></table>

View File

@@ -200,8 +200,10 @@
{% if sender_acl_handles.external_sender_aliases %} {% if sender_acl_handles.external_sender_aliases %}
{% set ext_sender_acl = sender_acl_handles.external_sender_aliases|join(', ') %} {% set ext_sender_acl = sender_acl_handles.external_sender_aliases|join(', ') %}
{% endif %} {% endif %}
<input type="text" class="form-control" name="extended_sender_acl" value="{{ ext_sender_acl }}" placeholder="user1@example.com, user2@example.org, @example.com, ..."> {% if acl.extend_sender_acl and acl.extend_sender_acl == 1 %}
<small class="text-muted">{{ lang.edit.extended_sender_acl_info|raw }}</small> <input type="text" class="form-control" name="extended_sender_acl" value="{{ ext_sender_acl }}" placeholder="user1@example.com, user2@example.org, @example.com, ...">
<small class="text-muted">{{ lang.edit.extended_sender_acl_info|raw }}</small>
{% endif %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@@ -274,7 +276,7 @@
</div> </div>
<div class="col-sm-10"> <div class="col-sm-10">
<p class="text-muted">{{ lang.user.pushover_info|format(mailbox)|raw }}</p> <p class="text-muted">{{ lang.user.pushover_info|format(mailbox)|raw }}</p>
<p class="text-muted">{{ lang.edit.pushover_vars|raw }}: <code>{SUBJECT}</code>, <code>{SENDER}</code>, <code>{SENDER_ADDRESS}</code>, <code>{SENDER_NAME}</code>, <code>{TO_NAME}</code>, <code>{TO_ADDRESS}, <code>{MSG_ID}</code></p> <p class="text-muted">{{ lang.edit.pushover_vars|raw }}: <code>{SUBJECT}</code>, <code>{SENDER}</code>, <code>{SENDER_ADDRESS}</code>, <code>{SENDER_NAME}</code>, <code>{TO_NAME}</code>, <code>{TO_ADDRESS}</code>, <code>{MSG_ID}</code></p>
<div class="row"> <div class="row">
<div class="col-sm-6 mb-2"> <div class="col-sm-6 mb-2">
<label for="token">API Token/Key (Application)</label> <label for="token">API Token/Key (Application)</label>

View File

@@ -4,18 +4,18 @@
<div id="mail-content" class="responsive-tabs"> <div id="mail-content" class="responsive-tabs">
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a> <a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li> <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
<li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li> <li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li>
</ul> </ul>
</li> </li>
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailboxes }}</a> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailboxes }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-mailboxes" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mailboxes">{{ lang.mailbox.mailboxes }}</button></li> <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-mailboxes" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mailboxes">{{ lang.mailbox.mailboxes }}</button></li>
<li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-mbox" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-mbox">{{ lang.mailbox.templates }}</button></li> <li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-mbox" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-mbox">{{ lang.mailbox.templates }}</button></li>
</ul> </ul>
</li> </li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-resources" role="tab" data-bs-toggle="tab" data-bs-target="#tab-resources">{{ lang.mailbox.resources }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-resources" role="tab" data-bs-toggle="tab" data-bs-target="#tab-resources">{{ lang.mailbox.resources }}</button></li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">

View File

@@ -23,9 +23,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">{{ lang.mailbox.bcc_to_rcpt }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">{{ lang.mailbox.bcc_to_rcpt }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="bcc_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="bcc_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="bcc_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="bcc_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addBCCModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_bcc_entry }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addBCCModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_bcc_entry }}</a>
</div> </div>
@@ -44,9 +44,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">{{ lang.mailbox.bcc_to_rcpt }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">{{ lang.mailbox.bcc_to_rcpt }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="bcc_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="bcc_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="bcc_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="bcc_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addBCCModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_bcc_entry }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addBCCModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_bcc_entry }}</a>
</div> </div>
@@ -74,9 +74,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="recipient_map_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="recipient_map_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="recipient_map_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="recipient_map_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addRecipientMapModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_recipient_map_entry }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addRecipientMapModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_recipient_map_entry }}</a>
</div> </div>
@@ -92,9 +92,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="recipient_map_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="recipient_map_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="recipient_map_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="recipient_map_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addRecipientMapModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_recipient_map_entry }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addRecipientMapModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_recipient_map_entry }}</a>
</div> </div>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-domain-aliases" role="tabpanel" aria-labelledby="tab-domain-aliases"> <div role="tabpanel" class="tab-pane fade" id="tab-domain-aliases" role="tabpanel" aria-labelledby="tab-domain-aliases">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domain-aliases" data-bs-toggle="collapse" aria-controls="ollapse-tab-domain-aliases"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domain-aliases" data-bs-toggle="collapse" aria-controls="collapse-tab-domain-aliases">
{{ lang.mailbox.domain_aliases }} <span class="badge bg-info table-lines"></span> {{ lang.mailbox.domain_aliases }} <span class="badge bg-info table-lines"></span>
</button> </button>
<span class="d-none d-md-block">{{ lang.mailbox.domain_aliases }} <span class="badge bg-info table-lines"></span></span> <span class="d-none d-md-block">{{ lang.mailbox.domain_aliases }} <span class="badge bg-info table-lines"></span></span>
@@ -20,9 +20,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="aliasdomain_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="aliasdomain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="aliasdomain_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="aliasdomain_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-acl="{{ acl.alias_domains }}" data-bs-toggle="modal" data-bs-target="#addAliasDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain_alias }}</a> <a class="btn btn-sm btn-success" href="#" data-acl="{{ acl.alias_domains }}" data-bs-toggle="modal" data-bs-target="#addAliasDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain_alias }}</a>
</div> </div>
@@ -37,9 +37,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="aliasdomain_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="aliasdomain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="aliasdomain_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="aliasdomain_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-acl="{{ acl.alias_domains }}" data-bs-toggle="modal" data-bs-target="#addAliasDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain_alias }}</a> <a class="btn btn-sm btn-success" href="#" data-acl="{{ acl.alias_domains }}" data-bs-toggle="modal" data-bs-target="#addAliasDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain_alias }}</a>
</div> </div>

View File

@@ -22,10 +22,10 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="domain" data-api-url='delete/domain' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="domain" data-api-url='delete/domain' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="domain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="domain_table">{{ lang.datatables.collapse_all }}</a></li>
{% endif %} {% endif %}
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="domain_table">{{ lang.datatables.expand_all }}</a></li>
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="domain_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain }}</a>
@@ -43,10 +43,10 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="domain" data-api-url='delete/domain' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="domain" data-api-url='delete/domain' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
{% endif %} {% endif %}
<li><a class="dropdown-item" data-datatables-expand="domain_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="domain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="domain_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="domain_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<button class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain }}</button> <button class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_domain }}</button>

View File

@@ -23,9 +23,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#">{{ lang.mailbox.set_postfilter }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#">{{ lang.mailbox.set_postfilter }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-text="{{ lang.user.eas_reset }}?" data-id="filter_item" data-api-url='delete/filter' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-text="{{ lang.user.eas_reset }}?" data-id="filter_item" data-api-url='delete/filter' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="filter_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="filter_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="filter_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="filter_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addFilterModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_filter }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addFilterModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_filter }}</a>
</div> </div>
@@ -44,9 +44,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#">{{ lang.mailbox.set_postfilter }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#">{{ lang.mailbox.set_postfilter }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-text="{{ lang.user.eas_reset }}?" data-id="filter_item" data-api-url='delete/filter' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-text="{{ lang.user.eas_reset }}?" data-id="filter_item" data-api-url='delete/filter' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="filter_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="filter_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="filter_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="filter_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addFilterModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_filter }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addFilterModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_filter }}</a>
</div> </div>

View File

@@ -16,9 +16,9 @@
<a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li class="dropdown-header">{{ lang.mailbox.mailbox }}</li> <li class="dropdown-header">{{ lang.mailbox.mailbox }}</li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
@@ -64,8 +64,8 @@
<a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailbox }}</a> <a class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailbox }}</a>
@@ -130,9 +130,9 @@
<a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li class="dropdown-header">{{ lang.mailbox.mailbox }}</li> <li class="dropdown-header">{{ lang.mailbox.mailbox }}</li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
@@ -178,8 +178,8 @@
<a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="mailbox_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailbox }}</a> <a class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailbox }}</a>

View File

@@ -20,9 +20,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="alias" data-api-url='delete/alias' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="alias" data-api-url='delete/alias' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="alias_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="alias_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="alias_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="alias_table">{{ lang.datatables.collapse_all }}</a></li>
{% if not skip_sogo %} {% if not skip_sogo %}
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"sogo_visible":"1"}' href="#">{{ lang.mailbox.sogo_visible_y }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"sogo_visible":"1"}' href="#">{{ lang.mailbox.sogo_visible_y }}</a></li>
@@ -44,9 +44,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="alias" data-api-url='delete/alias' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="alias" data-api-url='delete/alias' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="alias_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="alias_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="alias_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="alias_table">{{ lang.datatables.collapse_all }}</a></li>
{% if not skip_sogo %} {% if not skip_sogo %}
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"sogo_visible":"1"}' href="#">{{ lang.mailbox.sogo_visible_y }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"sogo_visible":"1"}' href="#">{{ lang.mailbox.sogo_visible_y }}</a></li>

View File

@@ -20,9 +20,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="resource" data-api-url='delete/resource' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="resource" data-api-url='delete/resource' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="resource_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="resource_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="resource_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="resource_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addResourceModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_resource }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addResourceModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_resource }}</a>
</div> </div>
@@ -41,9 +41,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="resource" data-api-url='delete/resource' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="resource" data-api-url='delete/resource' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="resource_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="resource_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="resource_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="resource_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addResourceModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_resource }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addResourceModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_resource }}</a>
</div> </div>

View File

@@ -22,9 +22,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>
@@ -41,9 +41,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>

View File

@@ -18,9 +18,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
@@ -36,9 +36,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}

View File

@@ -18,9 +18,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
@@ -36,9 +36,9 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}
<li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% if mailcow_cc_role == 'admin' %} {% if mailcow_cc_role == 'admin' %}

View File

@@ -20,9 +20,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="tls-policy-map" data-api-url='delete/tls-policy-map' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="tls-policy-map" data-api-url='delete/tls-policy-map' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="tls_policy_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="tls_policy_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="tls_policy_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="tls_policy_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addTLSPolicyMapAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_tls_policy_map }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addTLSPolicyMapAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_tls_policy_map }}</a>
</div> </div>
@@ -38,9 +38,9 @@
<li><a class="dropdown-item" data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="tls-policy-map" data-api-url='delete/tls-policy-map' href="#">{{ lang.mailbox.remove }}</a></li> <li><a class="dropdown-item" data-action="delete_selected" data-id="tls-policy-map" data-api-url='delete/tls-policy-map' href="#">{{ lang.mailbox.remove }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-datatables-expand="tls_policy_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="tls_policy_table">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="tls_policy_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="tls_policy_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addTLSPolicyMapAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_tls_policy_map }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addTLSPolicyMapAdmin"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_tls_policy_map }}</a>
</div> </div>

View File

@@ -16,9 +16,9 @@
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a> <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.quarantine.quick_actions }}</a> <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.quarantine.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.collapse_all }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#">{{ lang.quarantine.deliver_inbox }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#">{{ lang.quarantine.deliver_inbox }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#">{{ lang.quarantine.learn_spam_delete }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#">{{ lang.quarantine.learn_spam_delete }}</a></li>
@@ -43,9 +43,9 @@
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a> <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.quarantine.quick_actions }}</a> <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.quarantine.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" data-datatables-expand="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.expand_all }}</a></li>
<li><a class="dropdown-item" data-datatables-collapse="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="quarantinetable" data-table="quarantinetable" href="#">{{ lang.datatables.collapse_all }}</a></li>
<li><hr class="dropdown-divider"></li> <li class="table_collapse_option"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#">{{ lang.quarantine.deliver_inbox }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#">{{ lang.quarantine.deliver_inbox }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#">{{ lang.quarantine.learn_spam_delete }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#">{{ lang.quarantine.learn_spam_delete }}</a></li>

View File

@@ -20,6 +20,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
'tfa_data' => $tfa_data, 'tfa_data' => $tfa_data,
'fido2_data' => $fido2_data, 'fido2_data' => $fido2_data,
'lang_user' => json_encode($lang['user']), 'lang_user' => json_encode($lang['user']),
'lang_datatables' => json_encode($lang['datatables']),
]; ];
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {

View File

@@ -106,7 +106,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: mailcow/phpfpm:1.81 image: mailcow/phpfpm:1.82
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
- redis-mailcow - redis-mailcow
@@ -169,7 +169,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:1.113 image: mailcow/sogo:1.114
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@@ -216,7 +216,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: mailcow/dovecot:1.21 image: mailcow/dovecot:1.22
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
dns: dns:
@@ -389,7 +389,7 @@ services:
acme-mailcow: acme-mailcow:
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
image: mailcow/acme:1.83 image: mailcow/acme:1.84
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@@ -510,11 +510,10 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:2.0 image: mailcow/dockerapi:2.01
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always
oom_kill_disable: true
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:

View File

@@ -58,6 +58,12 @@ else
exit 1 exit 1
fi fi
### If generate_config.sh is started with --dev or -d it will not check out nightly or master branch and will keep on the current branch
if [[ ${1} == "--dev" || ${1} == "-d" ]]; then
SKIP_BRANCH=y
else
SKIP_BRANCH=n
fi
if [ -f mailcow.conf ]; then if [ -f mailcow.conf ]; then
read -r -p "A config file exists and will be overwritten, are you sure you want to continue? [y/N] " response read -r -p "A config file exists and will be overwritten, are you sure you want to continue? [y/N] " response
@@ -135,32 +141,44 @@ else
SKIP_SOLR=n SKIP_SOLR=n
fi fi
echo "Which branch of mailcow do you want to use?" if [[ ${SKIP_BRANCH} != y ]]; then
echo "" echo "Which branch of mailcow do you want to use?"
echo "Available Branches:" echo ""
echo "- master branch (stable updates) | default, recommended [1]" echo "Available Branches:"
echo "- nightly branch (unstable updates, testing) | not-production ready [2]" echo "- master branch (stable updates) | default, recommended [1]"
sleep 1 echo "- nightly branch (unstable updates, testing) | not-production ready [2]"
sleep 1
while [ -z "${MAILCOW_BRANCH}" ]; do while [ -z "${MAILCOW_BRANCH}" ]; do
read -r -p "Choose the Branch with it´s number [1/2] " branch read -r -p "Choose the Branch with it´s number [1/2] " branch
case $branch in case $branch in
[2]) [2])
MAILCOW_BRANCH="nightly" MAILCOW_BRANCH="nightly"
;;
*)
MAILCOW_BRANCH="master"
;; ;;
*) esac
MAILCOW_BRANCH="master" done
;;
esac git fetch --all
done git checkout -f $git_branch
elif [[ ${SKIP_BRANCH} == y ]]; then
echo -e "\033[33mEnabled Dev Mode.\033[0m"
echo -e "\033[33mNot checking out a different branch!\033[0m"
MAILCOW_BRANCH=$(git rev-parse --short $(git rev-parse @{upstream}))
else
echo -e "\033[31mCould not determine branch input..."
echo -e "\033[31mExiting."
exit 1
fi
if [ ! -z "${MAILCOW_BRANCH}" ]; then if [ ! -z "${MAILCOW_BRANCH}" ]; then
git_branch=${MAILCOW_BRANCH} git_branch=${MAILCOW_BRANCH}
fi fi
git fetch --all
git checkout -f $git_branch
[ ! -f ./data/conf/rspamd/override.d/worker-controller-password.inc ] && echo '# Placeholder' > ./data/conf/rspamd/override.d/worker-controller-password.inc [ ! -f ./data/conf/rspamd/override.d/worker-controller-password.inc ] && echo '# Placeholder' > ./data/conf/rspamd/override.d/worker-controller-password.inc
cat << EOF > mailcow.conf cat << EOF > mailcow.conf
@@ -427,18 +445,37 @@ echo "Copying snake-oil certificate..."
cp -n -d data/assets/ssl-example/*.pem data/assets/ssl/ cp -n -d data/assets/ssl-example/*.pem data/assets/ssl/
# Set app_info.inc.php # Set app_info.inc.php
if [ ${git_branch} == "master" ]; then case ${git_branch} in
mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) master)
elif [ ${git_branch} == "nightly" ]; then mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`)
mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream})) ;;
mailcow_last_git_version="" nightly)
else mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
mailcow_git_version=$(git rev-parse --short HEAD) mailcow_last_git_version=""
mailcow_last_git_version="" ;;
fi *)
mailcow_git_version=$(git rev-parse --short HEAD)
mailcow_last_git_version=""
;;
esac
# if [ ${git_branch} == "master" ]; then
# mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`)
# elif [ ${git_branch} == "nightly" ]; then
# mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
# mailcow_last_git_version=""
# else
# mailcow_git_version=$(git rev-parse --short HEAD)
# mailcow_last_git_version=""
# fi
if [[ $SKIP_BRANCH != "y" ]]; then
mailcow_git_commit=$(git rev-parse origin/${git_branch}) mailcow_git_commit=$(git rev-parse origin/${git_branch})
mailcow_git_commit_date=$(git log -1 --format=%ci @{upstream} ) mailcow_git_commit_date=$(git log -1 --format=%ci @{upstream} )
else
mailcow_git_commit=$(git rev-parse ${git_branch})
mailcow_git_commit_date=$(git log -1 --format=%ci @{upstream} )
git_branch=$(git rev-parse --abbrev-ref HEAD)
fi
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo '<?php' > data/web/inc/app_info.inc.php echo '<?php' > data/web/inc/app_info.inc.php

View File

@@ -26,6 +26,6 @@ services:
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
mysql-mailcow: mysql-mailcow:
image: alpine:3.10 image: alpine:3.17
command: /bin/true command: /bin/true
restart: "no" restart: "no"

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