mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-07-17 22:14:53 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffbc37a00c |
@@ -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@v10.3.0
|
uses: actions/stale@v10.2.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run the Action
|
- name: Run the Action
|
||||||
uses: devops-infra/action-pull-request@v1.2.1
|
uses: devops-infra/action-pull-request@v1.0.2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
|||||||
@@ -5,25 +5,34 @@ use LockFile::Simple qw(lock trylock unlock);
|
|||||||
use Proc::ProcessTable;
|
use Proc::ProcessTable;
|
||||||
use Data::Dumper qw(Dumper);
|
use Data::Dumper qw(Dumper);
|
||||||
use IPC::Run 'run';
|
use IPC::Run 'run';
|
||||||
use JSON::PP;
|
|
||||||
use File::Temp;
|
use File::Temp;
|
||||||
use Try::Tiny;
|
use Try::Tiny;
|
||||||
use POSIX ();
|
|
||||||
use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
|
use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
|
||||||
|
|
||||||
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
|
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
|
||||||
|
my $t = Proc::ProcessTable->new;
|
||||||
|
my $imapsync_running = grep { $_->{cmndline} =~ /imapsync\s/i } @{$t->table};
|
||||||
|
if ($imapsync_running ge 1)
|
||||||
|
{
|
||||||
|
print "imapsync is active, exiting...";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
# Move a finished job to the back of the queue, keeping order_id contiguous (1..N).
|
sub qqw($) {
|
||||||
# Called only from the parent after reaping a child, so all rotations are serialized.
|
my @params = ();
|
||||||
sub rotate_to_back {
|
my @values = split(/(?=--)/, $_[0]);
|
||||||
my ($dbh, $jid) = @_;
|
foreach my $val (@values) {
|
||||||
return unless defined $jid;
|
my @tmpparam = split(/ /, $val, 2);
|
||||||
my ($cnt) = $dbh->selectrow_array("SELECT COUNT(*) FROM imapsync");
|
foreach my $tmpval (@tmpparam) {
|
||||||
my ($old) = $dbh->selectrow_array("SELECT order_id FROM imapsync WHERE id = ?", undef, $jid);
|
if ($tmpval ne '') {
|
||||||
return unless (defined $cnt && defined $old && $old < $cnt);
|
push @params, $tmpval;
|
||||||
# close the gap left behind, then place this job last
|
}
|
||||||
$dbh->do("UPDATE imapsync SET order_id = order_id - 1 WHERE order_id > ? ORDER BY order_id ASC", undef, $old);
|
}
|
||||||
$dbh->do("UPDATE imapsync SET order_id = ? WHERE id = ?", undef, $cnt, $jid);
|
}
|
||||||
|
foreach my $val (@params) {
|
||||||
|
$val=trim($val);
|
||||||
|
}
|
||||||
|
return @params;
|
||||||
}
|
}
|
||||||
|
|
||||||
$run_dir="/tmp";
|
$run_dir="/tmp";
|
||||||
@@ -37,16 +46,6 @@ $dbh = DBI->connect($dsn, $ENV{'DBUSER'}, $ENV{'DBPASS'}, {
|
|||||||
});
|
});
|
||||||
$dbh->do("UPDATE imapsync SET is_running = 0");
|
$dbh->do("UPDATE imapsync SET is_running = 0");
|
||||||
|
|
||||||
# Max concurrent imapsync processes (admin-configurable, stored in MySQL). Fallback 1.
|
|
||||||
my $max_parallel = 1;
|
|
||||||
my ($mp) = $dbh->selectrow_array("SELECT value FROM imapsync_settings WHERE name = 'max_parallel'");
|
|
||||||
$max_parallel = int($mp) if defined($mp) && int($mp) >= 1;
|
|
||||||
|
|
||||||
# Global per-process bandwidth cap in bytes/s (0 = off). Applied to every imapsync run below.
|
|
||||||
my $global_bw = 0;
|
|
||||||
my ($gbw) = $dbh->selectrow_array("SELECT value FROM imapsync_settings WHERE name = 'max_bytes_per_second'");
|
|
||||||
$global_bw = int($gbw) if defined($gbw) && int($gbw) > 0;
|
|
||||||
|
|
||||||
sub sig_handler {
|
sub sig_handler {
|
||||||
# Send die to force exception in "run"
|
# Send die to force exception in "run"
|
||||||
die "sig_handler received signal, preparing to exit...\n";
|
die "sig_handler received signal, preparing to exit...\n";
|
||||||
@@ -56,78 +55,47 @@ open my $file, '<', "/etc/sogo/sieve.creds";
|
|||||||
my $creds = <$file>;
|
my $creds = <$file>;
|
||||||
close $file;
|
close $file;
|
||||||
my ($master_user, $master_pass) = split /:/, $creds;
|
my ($master_user, $master_pass) = split /:/, $creds;
|
||||||
# Source-based schema: join imapsync_source to read host/port/encryption/auth-type
|
my $sth = $dbh->prepare("SELECT id,
|
||||||
my $sth = $dbh->prepare("SELECT
|
user1,
|
||||||
i.id,
|
user2,
|
||||||
i.user1,
|
host1,
|
||||||
i.user2,
|
authmech1,
|
||||||
s.host1,
|
password1,
|
||||||
s.auth_type,
|
exclude,
|
||||||
i.password1,
|
port1,
|
||||||
i.exclude,
|
enc1,
|
||||||
s.port1,
|
delete2duplicates,
|
||||||
s.enc1,
|
maxage,
|
||||||
i.delete2duplicates,
|
subfolder2,
|
||||||
i.maxage,
|
delete1,
|
||||||
i.subfolder2,
|
delete2,
|
||||||
i.delete1,
|
automap,
|
||||||
i.delete2,
|
skipcrossduplicates,
|
||||||
i.automap,
|
maxbytespersecond,
|
||||||
i.skipcrossduplicates,
|
custom_params,
|
||||||
i.maxbytespersecond,
|
subscribeall,
|
||||||
i.custom_params,
|
timeout1,
|
||||||
i.subscribeall,
|
timeout2,
|
||||||
i.timeout1,
|
dry
|
||||||
i.timeout2,
|
FROM imapsync
|
||||||
i.dry,
|
WHERE active = 1
|
||||||
s.oauth_access_token,
|
AND is_running = 0
|
||||||
s.oauth_token_expires,
|
|
||||||
s.oauth_flow,
|
|
||||||
i.source_id
|
|
||||||
FROM imapsync i JOIN imapsync_source s ON i.source_id = s.id
|
|
||||||
WHERE i.active = 1
|
|
||||||
AND s.active = 1
|
|
||||||
AND i.is_running = 0
|
|
||||||
AND (
|
AND (
|
||||||
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(i.last_run) > i.mins_interval * 60
|
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60
|
||||||
OR
|
OR
|
||||||
i.last_run IS NULL)
|
last_run IS NULL)
|
||||||
ORDER BY i.order_id ASC, i.id ASC");
|
ORDER BY last_run");
|
||||||
|
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
my @rows = @{$sth->fetchall_arrayref()};
|
my $row;
|
||||||
$sth->finish();
|
|
||||||
|
|
||||||
# Fork pool: run up to $max_parallel imapsync children at once, in order_id order.
|
while ($row = $sth->fetchrow_arrayref()) {
|
||||||
my $active = 0;
|
|
||||||
my %pid_job; # child pid => syncjob id, so the parent can rotate it to the back when it finishes
|
|
||||||
foreach my $row (@rows) {
|
|
||||||
# Throttle: once max children are running, reap one before starting the next
|
|
||||||
if ($active >= $max_parallel) {
|
|
||||||
my $done = wait();
|
|
||||||
my $rc = $? >> 8;
|
|
||||||
$active-- if $active > 0;
|
|
||||||
if (defined $pid_job{$done}) {
|
|
||||||
rotate_to_back($dbh, $pid_job{$done}) if $rc == 0; # rotate only jobs that actually ran
|
|
||||||
delete $pid_job{$done};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
my $pid = fork();
|
|
||||||
next if (!defined $pid);
|
|
||||||
if ($pid != 0) { $pid_job{$pid} = @$row[0]; $active++; next; } # parent: remember pid->job, continue
|
|
||||||
|
|
||||||
# ---- CHILD ---- own DB connection so the parent's handle stays intact across forks
|
|
||||||
$dbh->{InactiveDestroy} = 1;
|
|
||||||
$dbh = DBI->connect($dsn, $ENV{'DBUSER'}, $ENV{'DBPASS'}, {
|
|
||||||
mysql_auto_reconnect => 1,
|
|
||||||
mysql_enable_utf8mb4 => 1
|
|
||||||
});
|
|
||||||
|
|
||||||
$id = @$row[0];
|
$id = @$row[0];
|
||||||
$user1 = @$row[1];
|
$user1 = @$row[1];
|
||||||
$user2 = @$row[2];
|
$user2 = @$row[2];
|
||||||
$host1 = @$row[3];
|
$host1 = @$row[3];
|
||||||
$auth_type = @$row[4];
|
$authmech1 = @$row[4];
|
||||||
$password1 = @$row[5];
|
$password1 = @$row[5];
|
||||||
$exclude = @$row[6];
|
$exclude = @$row[6];
|
||||||
$port1 = @$row[7];
|
$port1 = @$row[7];
|
||||||
@@ -145,79 +113,21 @@ foreach my $row (@rows) {
|
|||||||
$timeout1 = @$row[19];
|
$timeout1 = @$row[19];
|
||||||
$timeout2 = @$row[20];
|
$timeout2 = @$row[20];
|
||||||
$dry = @$row[21];
|
$dry = @$row[21];
|
||||||
$oauth_access_token = @$row[22];
|
|
||||||
$oauth_token_expires = @$row[23];
|
|
||||||
$oauth_flow = @$row[24];
|
|
||||||
$source_id = @$row[25];
|
|
||||||
|
|
||||||
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
|
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
|
||||||
|
|
||||||
my $template = $run_dir . '/imapsync.XXXXXXX';
|
my $template = $run_dir . '/imapsync.XXXXXXX';
|
||||||
my $passfile1 = File::Temp->new(TEMPLATE => $template);
|
my $passfile1 = File::Temp->new(TEMPLATE => $template);
|
||||||
my $passfile2 = File::Temp->new(TEMPLATE => $template);
|
my $passfile2 = File::Temp->new(TEMPLATE => $template);
|
||||||
my $tokenfile1;
|
|
||||||
|
|
||||||
binmode( $passfile1, ":utf8" );
|
binmode( $passfile1, ":utf8" );
|
||||||
|
|
||||||
# Auth-mode-specific argument set
|
|
||||||
my @auth_args;
|
|
||||||
if (defined($auth_type) && $auth_type eq 'XOAUTH2') {
|
|
||||||
# authorization_code sources use a per-user token (keyed by source + user1),
|
|
||||||
# not the shared source-level token used by client_credentials.
|
|
||||||
if (defined($oauth_flow) && $oauth_flow eq 'authorization_code') {
|
|
||||||
my $tsth = $dbh->prepare("SELECT access_token, token_expires FROM imapsync_source_oauth_token WHERE source_id=? AND username=?");
|
|
||||||
$tsth->execute($source_id, $user1);
|
|
||||||
my $trow = $tsth->fetchrow_arrayref();
|
|
||||||
$oauth_access_token = $trow ? @$trow[0] : undef;
|
|
||||||
$oauth_token_expires = $trow ? @$trow[1] : undef;
|
|
||||||
}
|
|
||||||
# Skip sync if cached token is missing or about to expire (<2 min)
|
|
||||||
if (!defined($oauth_access_token) || $oauth_access_token eq ''
|
|
||||||
|| !defined($oauth_token_expires) || $oauth_token_expires - time() < 120) {
|
|
||||||
my $upd = $dbh->prepare("UPDATE imapsync SET returned_text=?, success=0, exit_status='OAUTH_TOKEN_NOT_READY', last_run=NOW(), is_running=0 WHERE id=?");
|
|
||||||
$upd->execute("OAuth access token for the configured source is missing or expires too soon; refresh cron will retry.", $id);
|
|
||||||
$dbh->disconnect();
|
|
||||||
unlink $passfile1->filename if defined $passfile1;
|
|
||||||
unlink $passfile2->filename if defined $passfile2;
|
|
||||||
# exit 75 (EX_TEMPFAIL): did not actually sync -> parent keeps its queue position (no rotate)
|
|
||||||
POSIX::_exit(75);
|
|
||||||
}
|
|
||||||
$tokenfile1 = File::Temp->new(TEMPLATE => $template);
|
|
||||||
binmode($tokenfile1, ":utf8");
|
|
||||||
print $tokenfile1 "$oauth_access_token\n";
|
|
||||||
@auth_args = ("--authmech1", "XOAUTH2",
|
|
||||||
"--oauthaccesstoken1", $tokenfile1->filename);
|
|
||||||
} else {
|
|
||||||
print $passfile1 "$password1\n";
|
print $passfile1 "$password1\n";
|
||||||
@auth_args = ("--passfile1", $passfile1->filename);
|
|
||||||
# imapsync auto-picks PLAIN; only emit --authmech1 for LOGIN/CRAM-MD5
|
|
||||||
if (defined($auth_type) && $auth_type ne 'PLAIN') {
|
|
||||||
push @auth_args, "--authmech1", $auth_type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print $passfile2 trim($master_pass) . "\n";
|
print $passfile2 trim($master_pass) . "\n";
|
||||||
|
|
||||||
# Structured custom params: JSON array of {o,v}. Build one argv token per pair as "--opt=value"
|
my @custom_params_a = qqw($custom_params);
|
||||||
# (value glued to its option) so a value can never become a separate option; passed via
|
|
||||||
# IPC::Run list form (execvp, no shell). Only allowlisted option names are stored (validated on write).
|
|
||||||
my @custom_params_a;
|
|
||||||
my $cp_pairs = eval { decode_json(defined($custom_params) && $custom_params ne '' ? $custom_params : '[]') };
|
|
||||||
if (ref($cp_pairs) eq 'ARRAY') {
|
|
||||||
for my $p (@$cp_pairs) {
|
|
||||||
next unless ref($p) eq 'HASH' && defined $p->{o} && $p->{o} ne '';
|
|
||||||
(my $ov = defined($p->{v}) ? $p->{v} : '') =~ s/\x00//g;
|
|
||||||
push @custom_params_a, ($ov eq '' ? "--$p->{o}" : "--$p->{o}=$ov");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
my $custom_params_ref = \@custom_params_a;
|
my $custom_params_ref = \@custom_params_a;
|
||||||
|
|
||||||
# Effective per-process bandwidth cap (bytes/s): per-job value, capped by the global limit.
|
|
||||||
my $eff_bw = int($maxbytespersecond);
|
|
||||||
if ($global_bw > 0) {
|
|
||||||
$eff_bw = ($eff_bw > 0 && $eff_bw < $global_bw) ? $eff_bw : $global_bw;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $generated_cmds = [ "/usr/local/bin/imapsync",
|
my $generated_cmds = [ "/usr/local/bin/imapsync",
|
||||||
"--tmpdir", "/tmp",
|
"--tmpdir", "/tmp",
|
||||||
"--nofoldersizes",
|
"--nofoldersizes",
|
||||||
@@ -227,7 +137,7 @@ foreach my $row (@rows) {
|
|||||||
($exclude eq "" ? () : ("--exclude", $exclude)),
|
($exclude eq "" ? () : ("--exclude", $exclude)),
|
||||||
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
||||||
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
||||||
($eff_bw <= 0 ? () : ('--maxbytespersecond', $eff_bw)),
|
($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)),
|
||||||
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
|
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
|
||||||
($subscribeall ne "1" ? () : ('--subscribeall')),
|
($subscribeall ne "1" ? () : ('--subscribeall')),
|
||||||
($delete1 ne "1" ? () : ('--delete')),
|
($delete1 ne "1" ? () : ('--delete')),
|
||||||
@@ -237,7 +147,7 @@ foreach my $row (@rows) {
|
|||||||
(!defined($enc1) ? () : ($enc1)),
|
(!defined($enc1) ? () : ($enc1)),
|
||||||
"--host1", $host1,
|
"--host1", $host1,
|
||||||
"--user1", $user1,
|
"--user1", $user1,
|
||||||
@auth_args,
|
"--passfile1", $passfile1->filename,
|
||||||
"--port1", $port1,
|
"--port1", $port1,
|
||||||
"--host2", "localhost",
|
"--host2", "localhost",
|
||||||
"--user2", $user2 . '*' . trim($master_user),
|
"--user2", $user2 . '*' . trim($master_user),
|
||||||
@@ -277,25 +187,10 @@ foreach my $row (@rows) {
|
|||||||
$update->execute();
|
$update->execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
$dbh->disconnect();
|
|
||||||
# Remove this child's own temp files, then _exit so END/DESTROY are skipped — this keeps
|
|
||||||
# the parent's LockFile lock and DB handle intact (a plain exit() would run autoclean and
|
|
||||||
# release the shared lock while the parent + siblings are still working).
|
|
||||||
unlink $passfile1->filename if defined $passfile1;
|
|
||||||
unlink $passfile2->filename if defined $passfile2;
|
|
||||||
unlink $tokenfile1->filename if defined $tokenfile1;
|
|
||||||
POSIX::_exit(0); # ---- end CHILD ----
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parent: wait for all remaining children, rotating each finished job to the back
|
|
||||||
while ((my $done = wait()) != -1) {
|
|
||||||
my $rc = $? >> 8;
|
|
||||||
if (defined $pid_job{$done}) {
|
|
||||||
rotate_to_back($dbh, $pid_job{$done}) if $rc == 0; # rotate only jobs that actually ran
|
|
||||||
delete $pid_job{$done};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sth->finish();
|
||||||
$dbh->disconnect();
|
$dbh->disconnect();
|
||||||
|
|
||||||
$lockmgr->unlock($lock_file);
|
$lockmgr->unlock($lock_file);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nginx:1.30.2-alpine
|
FROM nginx:alpine
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# SOGo built from source to enable security patch application
|
# SOGo built from source to enable security patch application
|
||||||
# Repository: https://github.com/Alinto/sogo
|
# Repository: https://github.com/Alinto/sogo
|
||||||
# Version: SOGo-5.12.8
|
# Version: SOGo-5.12.4
|
||||||
#
|
#
|
||||||
# Applied security patches:
|
# Applied security patches:
|
||||||
# -
|
# -
|
||||||
@@ -12,8 +12,8 @@ FROM debian:bookworm
|
|||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG SOGO_VERSION=SOGo-5.12.8
|
ARG SOGO_VERSION=SOGo-5.12.5
|
||||||
ARG SOPE_VERSION=SOPE-5.12.8
|
ARG SOPE_VERSION=SOPE-5.12.5
|
||||||
# Security patches to apply (space-separated commit hashes)
|
# Security patches to apply (space-separated commit hashes)
|
||||||
ARG SOGO_SECURITY_PATCHES=""
|
ARG SOGO_SECURITY_PATCHES=""
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ FROM alpine:3.23
|
|||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# install unbound from alpine:edge to get security patches
|
|
||||||
RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main unbound
|
|
||||||
|
|
||||||
# install other packages from regular alpine stable repo
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
curl \
|
curl \
|
||||||
bind-tools \
|
bind-tools \
|
||||||
coreutils \
|
coreutils \
|
||||||
|
unbound \
|
||||||
bash \
|
bash \
|
||||||
openssl \
|
openssl \
|
||||||
drill \
|
drill \
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once(__DIR__ . '/../web/inc/vars.inc.php');
|
|
||||||
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
|
|
||||||
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
|
|
||||||
|
|
||||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
|
||||||
$opt = [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
PDO::ATTR_EMULATE_PREPARES => false,
|
|
||||||
];
|
|
||||||
try {
|
|
||||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
fwrite(STDERR, "DB connect failed: " . $e->getMessage() . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$redis = new Redis();
|
|
||||||
try {
|
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
|
||||||
} else {
|
|
||||||
$redis->connect('redis-mailcow', 6379);
|
|
||||||
}
|
|
||||||
$redis->auth(getenv("REDISPASS"));
|
|
||||||
} catch (Exception $e) {
|
|
||||||
fwrite(STDERR, "Redis connect failed: " . $e->getMessage() . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function logMsg($priority, $message, $task = "imapsync OAuth refresh") {
|
|
||||||
global $redis;
|
|
||||||
$redis->lPush('CRON_LOG', json_encode(array(
|
|
||||||
"time" => time(),
|
|
||||||
"priority" => $priority,
|
|
||||||
"task" => $task,
|
|
||||||
"message" => $message,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../web/inc/functions.inc.php';
|
|
||||||
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
|
|
||||||
require_once __DIR__ . '/../web/inc/sessions.inc.php';
|
|
||||||
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
|
|
||||||
require_once __DIR__ . '/../web/inc/functions.syncjob.inc.php';
|
|
||||||
|
|
||||||
// File-lock to prevent concurrent runs
|
|
||||||
$lock_file = '/tmp/imapsync-oauth-refresh.lock';
|
|
||||||
if (file_exists($lock_file)) {
|
|
||||||
$pid = (int)trim(@file_get_contents($lock_file));
|
|
||||||
if ($pid > 0 && posix_kill($pid, 0)) {
|
|
||||||
logMsg("info", "Previous refresh still running (pid=$pid), exiting");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
@unlink($lock_file);
|
|
||||||
}
|
|
||||||
file_put_contents($lock_file, getmypid());
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1) client_credentials sources: refresh the shared source-level token (expires < 5 min or none)
|
|
||||||
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `imapsync_source`
|
|
||||||
WHERE `active` = 1 AND `auth_type` = 'XOAUTH2' AND `oauth_flow` = 'client_credentials'
|
|
||||||
AND (`oauth_token_expires` IS NULL OR `oauth_token_expires` < :soon)");
|
|
||||||
$stmt->execute(array(':soon' => time() + 300));
|
|
||||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $src) {
|
|
||||||
$ok = imapsync_source_refresh_token_internal($src['id']);
|
|
||||||
if ($ok) {
|
|
||||||
logMsg("info", "Refreshed token for source '" . $src['name'] . "' (id=" . $src['id'] . ")");
|
|
||||||
} else {
|
|
||||||
$e = $pdo->prepare("SELECT `oauth_last_refresh_error` FROM `imapsync_source` WHERE `id` = :id");
|
|
||||||
$e->execute(array(':id' => $src['id']));
|
|
||||||
logMsg("warning", "Refresh failed for source '" . $src['name'] . "' (id=" . $src['id'] . "): " . $e->fetchColumn());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) authorization_code sources: refresh each per-user token via its stored refresh_token
|
|
||||||
$stmt = $pdo->prepare("SELECT t.`source_id`, t.`username`, s.`name`
|
|
||||||
FROM `imapsync_source_oauth_token` t
|
|
||||||
JOIN `imapsync_source` s ON s.`id` = t.`source_id`
|
|
||||||
WHERE s.`active` = 1 AND s.`auth_type` = 'XOAUTH2' AND s.`oauth_flow` = 'authorization_code'
|
|
||||||
AND t.`refresh_token` <> ''
|
|
||||||
AND (t.`token_expires` IS NULL OR t.`token_expires` < :soon)");
|
|
||||||
$stmt->execute(array(':soon' => time() + 300));
|
|
||||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $tok) {
|
|
||||||
$ok = imapsync_source_refresh_user_token_internal($tok['source_id'], $tok['username']);
|
|
||||||
if ($ok) {
|
|
||||||
logMsg("info", "Refreshed user token '" . $tok['username'] . "' on source '" . $tok['name'] . "' (id=" . $tok['source_id'] . ")");
|
|
||||||
} else {
|
|
||||||
$e = $pdo->prepare("SELECT `last_refresh_error` FROM `imapsync_source_oauth_token` WHERE `source_id` = :sid AND `username` = :user");
|
|
||||||
$e->execute(array(':sid' => $tok['source_id'], ':user' => $tok['username']));
|
|
||||||
logMsg("warning", "User-token refresh failed for '" . $tok['username'] . "' on source '" . $tok['name'] . "' (id=" . $tok['source_id'] . "): " . $e->fetchColumn());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
@unlink($lock_file);
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Fri May 1 00:43:37 UTC 2026
|
# Whitelist generated by Postwhite v3.4 on Wed Apr 1 00:33:31 UTC 2026
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 2249 total rules
|
# 2246 total rules
|
||||||
2a00:1450:4000::/36 permit
|
2a00:1450:4000::/36 permit
|
||||||
2a00:1450:4864::/56 permit
|
|
||||||
2a01:111:f400::/48 permit
|
2a01:111:f400::/48 permit
|
||||||
2a01:111:f403:2800::/53 permit
|
2a01:111:f403:2800::/53 permit
|
||||||
2a01:111:f403:8000::/51 permit
|
2a01:111:f403:8000::/51 permit
|
||||||
@@ -32,7 +31,6 @@
|
|||||||
2a02:a60:0:5::/64 permit
|
2a02:a60:0:5::/64 permit
|
||||||
2a0f:f640::/56 permit
|
2a0f:f640::/56 permit
|
||||||
2c0f:fb50:4000::/36 permit
|
2c0f:fb50:4000::/36 permit
|
||||||
2c0f:fb50:4864::/56 permit
|
|
||||||
2.207.151.32/27 permit
|
2.207.151.32/27 permit
|
||||||
2.207.151.53 permit
|
2.207.151.53 permit
|
||||||
2.207.217.30 permit
|
2.207.217.30 permit
|
||||||
@@ -62,8 +60,8 @@
|
|||||||
8.40.222.0/23 permit
|
8.40.222.0/23 permit
|
||||||
8.40.222.250/31 permit
|
8.40.222.250/31 permit
|
||||||
12.130.86.238 permit
|
12.130.86.238 permit
|
||||||
13.107.213.40 permit
|
13.107.213.38 permit
|
||||||
13.107.246.40 permit
|
13.107.246.38 permit
|
||||||
13.108.16.0/20 permit
|
13.108.16.0/20 permit
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
13.110.209.0/24 permit
|
13.110.209.0/24 permit
|
||||||
@@ -281,7 +279,6 @@
|
|||||||
50.56.130.221 permit
|
50.56.130.221 permit
|
||||||
50.56.130.222 permit
|
50.56.130.222 permit
|
||||||
50.112.246.219 permit
|
50.112.246.219 permit
|
||||||
51.83.17.38 permit
|
|
||||||
52.1.14.157 permit
|
52.1.14.157 permit
|
||||||
52.5.230.59 permit
|
52.5.230.59 permit
|
||||||
52.6.74.205 permit
|
52.6.74.205 permit
|
||||||
@@ -1681,7 +1678,6 @@
|
|||||||
169.148.144.0/25 permit
|
169.148.144.0/25 permit
|
||||||
169.148.144.10 permit
|
169.148.144.10 permit
|
||||||
169.148.146.0/23 permit
|
169.148.146.0/23 permit
|
||||||
169.148.174.10 permit
|
|
||||||
169.148.175.3 permit
|
169.148.175.3 permit
|
||||||
169.148.179.3 permit
|
169.148.179.3 permit
|
||||||
169.148.188.0/24 permit
|
169.148.188.0/24 permit
|
||||||
@@ -2231,7 +2227,6 @@
|
|||||||
2001:748:400:3301::4 permit
|
2001:748:400:3301::4 permit
|
||||||
2404:6800:4000::/36 permit
|
2404:6800:4000::/36 permit
|
||||||
2404:6800:4864::/56 permit
|
2404:6800:4864::/56 permit
|
||||||
2603:1061:14:72::1 permit
|
|
||||||
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
|
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
|
||||||
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
||||||
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
||||||
@@ -2248,6 +2243,8 @@
|
|||||||
2620:10d:c09c:400::8:1 permit
|
2620:10d:c09c:400::8:1 permit
|
||||||
2620:119:50c0:207::/64 permit
|
2620:119:50c0:207::/64 permit
|
||||||
2620:119:50c0:207::215 permit
|
2620:119:50c0:207::215 permit
|
||||||
|
2620:1ec:46::38 permit
|
||||||
|
2620:1ec:bdf::38 permit
|
||||||
2800:3f0:4000::/36 permit
|
2800:3f0:4000::/36 permit
|
||||||
2800:3f0:4864::/56 permit
|
2800:3f0:4864::/56 permit
|
||||||
49.12.4.251 permit # checks.mailcow.email
|
49.12.4.251 permit # checks.mailcow.email
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ $template_data = [
|
|||||||
'ip_check' => customize('get', 'ip_check'),
|
'ip_check' => customize('get', 'ip_check'),
|
||||||
'custom_login' => customize('get', 'custom_login'),
|
'custom_login' => customize('get', 'custom_login'),
|
||||||
'password_complexity' => password_complexity('get'),
|
'password_complexity' => password_complexity('get'),
|
||||||
'imapsync_settings' => imapsync_get_settings(),
|
|
||||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||||
'cors_settings' => $cors_settings,
|
'cors_settings' => $cors_settings,
|
||||||
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
|
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
|
||||||
|
|||||||
+3
-634
@@ -1520,7 +1520,7 @@ paths:
|
|||||||
timeout1: "600"
|
timeout1: "600"
|
||||||
timeout2: "600"
|
timeout2: "600"
|
||||||
exclude: "(?i)spam|(?i)junk"
|
exclude: "(?i)spam|(?i)junk"
|
||||||
custom_params: '[{"o":"dry","v":""}]'
|
custom_params: "--dry"
|
||||||
delete2duplicates: "1"
|
delete2duplicates: "1"
|
||||||
delete1: "1"
|
delete1: "1"
|
||||||
delete2: "0"
|
delete2: "0"
|
||||||
@@ -1569,13 +1569,7 @@ paths:
|
|||||||
description: exclude objects (regex)
|
description: exclude objects (regex)
|
||||||
type: string
|
type: string
|
||||||
custom_params:
|
custom_params:
|
||||||
description: >-
|
description: custom parameters
|
||||||
JSON array of allowlisted imapsync options as
|
|
||||||
option/value pairs, e.g.
|
|
||||||
`[{"o":"folder","v":"INBOX"},{"o":"dry","v":""}]`. Only
|
|
||||||
option names from `GET /get/syncjob_options` are accepted;
|
|
||||||
values may contain any character and are passed as a single
|
|
||||||
argument.
|
|
||||||
type: string
|
type: string
|
||||||
delete2duplicates:
|
delete2duplicates:
|
||||||
description: delete duplicates on destination (--delete2duplicates)
|
description: delete duplicates on destination (--delete2duplicates)
|
||||||
@@ -1599,151 +1593,6 @@ paths:
|
|||||||
description: enables or disables the sync job
|
description: enables or disables the sync job
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
/api/v1/add/syncjob_source:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- syncjob
|
|
||||||
- add
|
|
||||||
- source
|
|
||||||
- name: Example import
|
|
||||||
description: ""
|
|
||||||
host1: imap.example.org
|
|
||||||
port1: 993
|
|
||||||
enc1: SSL
|
|
||||||
auth_type: PLAIN
|
|
||||||
active: 1
|
|
||||||
scope: all
|
|
||||||
msg:
|
|
||||||
- imapsync_source_added
|
|
||||||
- Example import
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Create a reusable IMAP sync source. A sync job binds to a source by id;
|
|
||||||
the source carries host/port/encryption and either static credentials
|
|
||||||
(PLAIN/LOGIN/CRAM-MD5 — supplied per-job) or an OAuth2 token endpoint
|
|
||||||
(XOAUTH2 — token cached on the source row).
|
|
||||||
operationId: Create sync job source
|
|
||||||
summary: Create sync job source
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
name: Example import
|
|
||||||
description: ""
|
|
||||||
host1: imap.example.org
|
|
||||||
port1: "993"
|
|
||||||
enc1: SSL
|
|
||||||
auth_type: PLAIN
|
|
||||||
active: "1"
|
|
||||||
scope: all
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
description: source name, unique per owner
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
description: free text shown in the UI
|
|
||||||
type: string
|
|
||||||
host1:
|
|
||||||
description: remote IMAP host
|
|
||||||
type: string
|
|
||||||
port1:
|
|
||||||
description: remote IMAP port (1-65535)
|
|
||||||
type: integer
|
|
||||||
enc1:
|
|
||||||
description: TLS / SSL / PLAIN
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- TLS
|
|
||||||
- SSL
|
|
||||||
- PLAIN
|
|
||||||
auth_type:
|
|
||||||
description: authentication mechanism
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- PLAIN
|
|
||||||
- LOGIN
|
|
||||||
- CRAM-MD5
|
|
||||||
- XOAUTH2
|
|
||||||
oauth_flow:
|
|
||||||
description: XOAUTH2 grant type (default client_credentials)
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- client_credentials
|
|
||||||
- authorization_code
|
|
||||||
oauth_token_endpoint:
|
|
||||||
description: required when auth_type is XOAUTH2 (token URL)
|
|
||||||
type: string
|
|
||||||
oauth_authorize_endpoint:
|
|
||||||
description: required when oauth_flow is authorization_code (authorize URL)
|
|
||||||
type: string
|
|
||||||
oauth_userinfo_endpoint:
|
|
||||||
description: optional identity endpoint for authorization_code
|
|
||||||
type: string
|
|
||||||
oauth_client_id:
|
|
||||||
description: required when auth_type is XOAUTH2
|
|
||||||
type: string
|
|
||||||
oauth_client_secret:
|
|
||||||
description: required when auth_type is XOAUTH2
|
|
||||||
type: string
|
|
||||||
oauth_scope:
|
|
||||||
description: required when auth_type is XOAUTH2
|
|
||||||
type: string
|
|
||||||
oauth_extra_params:
|
|
||||||
description: optional JSON object merged into the token request body
|
|
||||||
type: string
|
|
||||||
active:
|
|
||||||
description: enables or disables the source
|
|
||||||
type: boolean
|
|
||||||
scope:
|
|
||||||
description: >-
|
|
||||||
admin/domainadmin — source visibility. client_credentials
|
|
||||||
XOAUTH2 sources are always forced to a private scope.
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- all
|
|
||||||
- domain
|
|
||||||
- user
|
|
||||||
domains:
|
|
||||||
description: target domains when scope is domain
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
users:
|
|
||||||
description: target users when scope is user
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
/api/v1/add/tls-policy-map:
|
/api/v1/add/tls-policy-map:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
@@ -2916,62 +2765,6 @@ paths:
|
|||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
summary: Delete sync job
|
summary: Delete sync job
|
||||||
/api/v1/delete/syncjob_source:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- syncjob
|
|
||||||
- delete
|
|
||||||
- source
|
|
||||||
- id:
|
|
||||||
- "3"
|
|
||||||
msg:
|
|
||||||
- imapsync_source_deleted
|
|
||||||
- Example import
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Delete one or more sync job sources by id. Sources still referenced by
|
|
||||||
a sync job cannot be deleted (FK ON DELETE RESTRICT) — delete or
|
|
||||||
repoint the dependent jobs first.
|
|
||||||
operationId: Delete sync job source
|
|
||||||
summary: Delete sync job source
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
- "3"
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
/api/v1/delete/tls-policy-map:
|
/api/v1/delete/tls-policy-map:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
@@ -4069,13 +3862,7 @@ paths:
|
|||||||
etc.)
|
etc.)
|
||||||
type: boolean
|
type: boolean
|
||||||
custom_params:
|
custom_params:
|
||||||
description: >-
|
description: Custom parameters passed to imapsync command
|
||||||
JSON array of allowlisted imapsync options as
|
|
||||||
option/value pairs, e.g.
|
|
||||||
`[{"o":"folder","v":"INBOX"},{"o":"dry","v":""}]`. Only
|
|
||||||
option names from `GET /get/syncjob_options` are
|
|
||||||
accepted; values may contain any character and are
|
|
||||||
passed as a single argument.
|
|
||||||
type: string
|
type: string
|
||||||
delete1:
|
delete1:
|
||||||
description: Delete from source when completed
|
description: Delete from source when completed
|
||||||
@@ -4144,335 +3931,6 @@ paths:
|
|||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
summary: Update sync job
|
summary: Update sync job
|
||||||
/api/v1/edit/syncjob_source:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- syncjob
|
|
||||||
- edit
|
|
||||||
- source
|
|
||||||
- id:
|
|
||||||
- "3"
|
|
||||||
name: Example import
|
|
||||||
host1: imap.example.org
|
|
||||||
port1: 993
|
|
||||||
msg:
|
|
||||||
- imapsync_source_modified
|
|
||||||
- Example import
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Update one or more sync job sources. Fields not provided are left
|
|
||||||
unchanged. For XOAUTH2 sources, an empty `oauth_client_secret`
|
|
||||||
preserves the stored value.
|
|
||||||
operationId: Update sync job source
|
|
||||||
summary: Update sync job source
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
items:
|
|
||||||
- "3"
|
|
||||||
attr:
|
|
||||||
name: Example import
|
|
||||||
description: ""
|
|
||||||
host1: imap.example.org
|
|
||||||
port1: "993"
|
|
||||||
enc1: SSL
|
|
||||||
auth_type: PLAIN
|
|
||||||
active: "1"
|
|
||||||
properties:
|
|
||||||
attr:
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
host1:
|
|
||||||
type: string
|
|
||||||
port1:
|
|
||||||
type: integer
|
|
||||||
enc1:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- TLS
|
|
||||||
- SSL
|
|
||||||
- PLAIN
|
|
||||||
auth_type:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- PLAIN
|
|
||||||
- LOGIN
|
|
||||||
- CRAM-MD5
|
|
||||||
- XOAUTH2
|
|
||||||
oauth_flow:
|
|
||||||
description: XOAUTH2 grant type
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- client_credentials
|
|
||||||
- authorization_code
|
|
||||||
oauth_token_endpoint:
|
|
||||||
type: string
|
|
||||||
oauth_authorize_endpoint:
|
|
||||||
description: required when oauth_flow is authorization_code
|
|
||||||
type: string
|
|
||||||
oauth_userinfo_endpoint:
|
|
||||||
description: optional identity endpoint for authorization_code
|
|
||||||
type: string
|
|
||||||
oauth_client_id:
|
|
||||||
type: string
|
|
||||||
oauth_client_secret:
|
|
||||||
description: empty keeps the previously stored secret
|
|
||||||
type: string
|
|
||||||
oauth_scope:
|
|
||||||
type: string
|
|
||||||
oauth_extra_params:
|
|
||||||
type: string
|
|
||||||
active:
|
|
||||||
type: boolean
|
|
||||||
scope:
|
|
||||||
description: source visibility (admin/domainadmin)
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- all
|
|
||||||
- domain
|
|
||||||
- user
|
|
||||||
domains:
|
|
||||||
description: target domains when scope is domain
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
users:
|
|
||||||
description: target users when scope is user
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
items:
|
|
||||||
description: list of source ids to update
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
/api/v1/edit/syncjob_source/refresh_token:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- syncjob
|
|
||||||
- edit
|
|
||||||
- refresh_token
|
|
||||||
- id:
|
|
||||||
- "3"
|
|
||||||
msg:
|
|
||||||
- imapsync_source_token_refreshed
|
|
||||||
- Example import
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Force an OAuth2 access-token refresh for a XOAUTH2 source. Performs a
|
|
||||||
Client-Credentials grant against the source's `oauth_token_endpoint`
|
|
||||||
and caches the new access token + expiry on the source row. On failure,
|
|
||||||
the error is recorded in `oauth_last_refresh_error`.
|
|
||||||
operationId: Refresh sync job source token
|
|
||||||
summary: Refresh sync job source token
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
items:
|
|
||||||
- "3"
|
|
||||||
properties:
|
|
||||||
items:
|
|
||||||
description: list of source ids to refresh
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
/api/v1/edit/syncjob/order:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- syncjob
|
|
||||||
- edit
|
|
||||||
- job_order
|
|
||||||
msg:
|
|
||||||
- imapsync_order_updated
|
|
||||||
- "1"
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Move a sync job to a global queue position (admin only). Positions are
|
|
||||||
contiguous (1..N) across all sync jobs; setting a job to a position
|
|
||||||
shifts the others accordingly. The runner processes jobs in ascending
|
|
||||||
order, up to the configured `max_parallel` at a time, and rotates a job
|
|
||||||
to the back after it runs.
|
|
||||||
operationId: Set sync job queue position
|
|
||||||
summary: Set sync job queue position
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
items: "3"
|
|
||||||
attr:
|
|
||||||
order_id: "1"
|
|
||||||
properties:
|
|
||||||
attr:
|
|
||||||
properties:
|
|
||||||
order_id:
|
|
||||||
description: target position (1..N)
|
|
||||||
type: number
|
|
||||||
type: object
|
|
||||||
items:
|
|
||||||
description: sync job id to move
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
/api/v1/edit/imapsync_settings:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- imapsync_settings
|
|
||||||
- edit
|
|
||||||
msg:
|
|
||||||
- max_parallel_saved
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Update global sync job settings (admin only). `max_parallel` is the
|
|
||||||
number of imapsync processes that may run concurrently; `max_kb_per_second`
|
|
||||||
is a per-process bandwidth cap in KB/s (0 = unlimited), stored internally
|
|
||||||
in bytes/s.
|
|
||||||
operationId: Update sync job settings
|
|
||||||
summary: Update sync job settings
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
attr:
|
|
||||||
max_parallel: "4"
|
|
||||||
max_kb_per_second: "2000"
|
|
||||||
properties:
|
|
||||||
attr:
|
|
||||||
properties:
|
|
||||||
max_parallel:
|
|
||||||
description: Max. concurrent sync processes (>= 1)
|
|
||||||
type: number
|
|
||||||
max_kb_per_second:
|
|
||||||
description: Bandwidth limit per process in KB/s (0 = unlimited)
|
|
||||||
type: number
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
/api/v1/edit/user-acl:
|
/api/v1/edit/user-acl:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
@@ -6171,95 +5629,6 @@ paths:
|
|||||||
description: You can list all syn jobs existing in system.
|
description: You can list all syn jobs existing in system.
|
||||||
operationId: Get sync jobs
|
operationId: Get sync jobs
|
||||||
summary: Get sync jobs
|
summary: Get sync jobs
|
||||||
"/api/v1/get/syncjob_source/{id}":
|
|
||||||
get:
|
|
||||||
parameters:
|
|
||||||
- description: source id, or `all` to list every source visible to the session
|
|
||||||
example: all
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
- description: e.g. api-key-string
|
|
||||||
example: api-key-string
|
|
||||||
in: header
|
|
||||||
name: X-API-Key
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- id: 3
|
|
||||||
name: Example import
|
|
||||||
description: ""
|
|
||||||
created_by: ""
|
|
||||||
scope: all
|
|
||||||
host1: imap.example.org
|
|
||||||
port1: 993
|
|
||||||
enc1: SSL
|
|
||||||
auth_type: PLAIN
|
|
||||||
oauth_token_endpoint: null
|
|
||||||
oauth_client_id: null
|
|
||||||
oauth_client_secret: ""
|
|
||||||
oauth_scope: null
|
|
||||||
oauth_extra_params: null
|
|
||||||
oauth_access_token: ""
|
|
||||||
oauth_token_expires: null
|
|
||||||
oauth_last_refresh_error: null
|
|
||||||
active: 1
|
|
||||||
created: "2026-06-11 09:00:00"
|
|
||||||
modified: "2026-06-11 09:00:00"
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Returns a single sync job source by id, or all sources visible to the
|
|
||||||
caller when `id` is `all`. Secrets (`oauth_client_secret`,
|
|
||||||
`oauth_access_token`) are blanked unless requested with internal
|
|
||||||
flags — they are never returned over the JSON API.
|
|
||||||
operationId: Get sync job sources
|
|
||||||
summary: Get sync job sources
|
|
||||||
/api/v1/get/syncjob_options:
|
|
||||||
get:
|
|
||||||
parameters:
|
|
||||||
- description: e.g. api-key-string
|
|
||||||
example: api-key-string
|
|
||||||
in: header
|
|
||||||
name: X-API-Key
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
response:
|
|
||||||
value:
|
|
||||||
- dry
|
|
||||||
- folder
|
|
||||||
- delete2folders
|
|
||||||
- maxsize
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Sync jobs
|
|
||||||
description: >-
|
|
||||||
Returns the allowlist of imapsync option names accepted in a sync job's
|
|
||||||
`custom_params`. Used by the UI to populate the option autocompletion.
|
|
||||||
operationId: Get sync job options
|
|
||||||
summary: Get sync job options
|
|
||||||
"/api/v1/get/tls-policy-map/{id}":
|
"/api/v1/get/tls-policy-map/{id}":
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
+1
-7
@@ -204,15 +204,9 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
|||||||
if (isset($_GET['syncjob']) &&
|
if (isset($_GET['syncjob']) &&
|
||||||
is_numeric($_GET['syncjob'])) {
|
is_numeric($_GET['syncjob'])) {
|
||||||
$id = $_GET["syncjob"];
|
$id = $_GET["syncjob"];
|
||||||
$result = syncjob('get', 'job', $id);
|
$result = mailbox('get', 'syncjob_details', $id);
|
||||||
$template = 'edit/syncjob.twig';
|
$template = 'edit/syncjob.twig';
|
||||||
}
|
}
|
||||||
elseif (isset($_GET['syncjob_source']) &&
|
|
||||||
is_numeric($_GET['syncjob_source'])) {
|
|
||||||
$id = $_GET["syncjob_source"];
|
|
||||||
$result = syncjob('get', 'source', array('id' => $id, 'with_secret' => true));
|
|
||||||
$template = 'edit/imapsync_source.twig';
|
|
||||||
}
|
|
||||||
elseif (isset($_GET['filter']) &&
|
elseif (isset($_GET['filter']) &&
|
||||||
is_numeric($_GET['filter'])) {
|
is_numeric($_GET['filter'])) {
|
||||||
$id = $_GET["filter"];
|
$id = $_GET["filter"];
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
|
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
|
||||||
if ($details = syncjob('get', 'job', intval($_GET['id']))) {
|
if ($details = mailbox('get', 'syncjob_details', intval($_GET['id']))) {
|
||||||
echo (empty($details['log'])) ? '-' : $details['log'];
|
echo (empty($details['log'])) ? '-' : $details['log'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ $globalVariables = [
|
|||||||
'lang_fido2' => json_encode($lang['fido2']),
|
'lang_fido2' => json_encode($lang['fido2']),
|
||||||
'lang_success' => json_encode($lang['success']),
|
'lang_success' => json_encode($lang['success']),
|
||||||
'lang_danger' => json_encode($lang['danger']),
|
'lang_danger' => json_encode($lang['danger']),
|
||||||
'lang_syncjobs' => json_encode($lang['syncjobs']),
|
|
||||||
'docker_timeout' => $DOCKER_TIMEOUT,
|
'docker_timeout' => $DOCKER_TIMEOUT,
|
||||||
'session_lifetime' => (int)$SESSION_LIFETIME,
|
'session_lifetime' => (int)$SESSION_LIFETIME,
|
||||||
'csrf_token' => $_SESSION['CSRF']['TOKEN'],
|
'csrf_token' => $_SESSION['CSRF']['TOKEN'],
|
||||||
|
|||||||
@@ -299,6 +299,192 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'msg' => array('mailbox_modified', $username)
|
'msg' => array('mailbox_modified', $username)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'syncjob':
|
||||||
|
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$username = $_data['username'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($_SESSION['mailcow_cc_role'] == "user") {
|
||||||
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'no_user_defined'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$active = intval($_data['active']);
|
||||||
|
$subscribeall = intval($_data['subscribeall']);
|
||||||
|
$delete2duplicates = intval($_data['delete2duplicates']);
|
||||||
|
$delete1 = intval($_data['delete1']);
|
||||||
|
$delete2 = intval($_data['delete2']);
|
||||||
|
$timeout1 = intval($_data['timeout1']);
|
||||||
|
$timeout2 = intval($_data['timeout2']);
|
||||||
|
$skipcrossduplicates = intval($_data['skipcrossduplicates']);
|
||||||
|
$automap = intval($_data['automap']);
|
||||||
|
$dry = intval($_data['dry']);
|
||||||
|
$port1 = $_data['port1'];
|
||||||
|
$host1 = strtolower($_data['host1']);
|
||||||
|
$password1 = $_data['password1'];
|
||||||
|
$exclude = $_data['exclude'];
|
||||||
|
$maxage = $_data['maxage'];
|
||||||
|
$maxbytespersecond = $_data['maxbytespersecond'];
|
||||||
|
$subfolder2 = $_data['subfolder2'];
|
||||||
|
$user1 = $_data['user1'];
|
||||||
|
$mins_interval = $_data['mins_interval'];
|
||||||
|
$enc1 = $_data['enc1'];
|
||||||
|
$custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']);
|
||||||
|
|
||||||
|
// validate custom params
|
||||||
|
foreach (explode('-', $custom_params) as $param){
|
||||||
|
if(empty($param)) continue;
|
||||||
|
|
||||||
|
// extract option
|
||||||
|
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||||
|
else $param = rtrim($param, ' ');
|
||||||
|
// remove first char if first char is -
|
||||||
|
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||||
|
|
||||||
|
if (str_contains($param, ' ')) {
|
||||||
|
// bad char
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'bad character SPACE'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if param is whitelisted
|
||||||
|
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||||
|
// bad option
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'bad option '. $param
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($subfolder2)) {
|
||||||
|
$subfolder2 = "";
|
||||||
|
}
|
||||||
|
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$maxage = "0";
|
||||||
|
}
|
||||||
|
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$timeout1 = "600";
|
||||||
|
}
|
||||||
|
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$timeout2 = "600";
|
||||||
|
}
|
||||||
|
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
|
||||||
|
$maxbytespersecond = "0";
|
||||||
|
}
|
||||||
|
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if (!is_valid_domain_name($host1)) {
|
||||||
|
// $_SESSION['return'][] = array(
|
||||||
|
// 'type' => 'danger',
|
||||||
|
// 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
// 'msg' => 'access_denied'
|
||||||
|
// );
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (@preg_match("/" . $exclude . "/", null) === false) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT '1' FROM `imapsync`
|
||||||
|
WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1");
|
||||||
|
$stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1));
|
||||||
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
if ($num_results != 0) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('object_exists', htmlspecialchars($host1 . ' / ' . $user1))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`)
|
||||||
|
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':user2' => $username,
|
||||||
|
':custom_params' => $custom_params,
|
||||||
|
':exclude' => $exclude,
|
||||||
|
':maxage' => $maxage,
|
||||||
|
':delete1' => $delete1,
|
||||||
|
':delete2' => $delete2,
|
||||||
|
':timeout1' => $timeout1,
|
||||||
|
':timeout2' => $timeout2,
|
||||||
|
':automap' => $automap,
|
||||||
|
':skipcrossduplicates' => $skipcrossduplicates,
|
||||||
|
':maxbytespersecond' => $maxbytespersecond,
|
||||||
|
':subscribeall' => $subscribeall,
|
||||||
|
':dry' => $dry,
|
||||||
|
':subfolder2' => $subfolder2,
|
||||||
|
':host1' => $host1,
|
||||||
|
':authmech1' => 'PLAIN',
|
||||||
|
':user1' => $user1,
|
||||||
|
':password1' => $password1,
|
||||||
|
':mins_interval' => $mins_interval,
|
||||||
|
':port1' => $port1,
|
||||||
|
':enc1' => $enc1,
|
||||||
|
':delete2duplicates' => $delete2duplicates,
|
||||||
|
':active' => $active,
|
||||||
|
));
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('mailbox_modified', $username)
|
||||||
|
);
|
||||||
|
break;
|
||||||
case 'domain':
|
case 'domain':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -2079,6 +2265,202 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'syncjob':
|
||||||
|
if (!is_array($_data['id'])) {
|
||||||
|
$ids = array();
|
||||||
|
$ids[] = $_data['id'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ids = $_data['id'];
|
||||||
|
}
|
||||||
|
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$is_now = mailbox('get', 'syncjob_details', $id, array('with_password'));
|
||||||
|
if (!empty($is_now)) {
|
||||||
|
$username = $is_now['user2'];
|
||||||
|
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
|
||||||
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
|
$last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run'];
|
||||||
|
$success = (isset($_data['success'])) ? NULL : $is_now['success'];
|
||||||
|
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
|
||||||
|
$subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall'];
|
||||||
|
$dry = (isset($_data['dry'])) ? intval($_data['dry']) : $is_now['dry'];
|
||||||
|
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
|
||||||
|
$delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
|
||||||
|
$automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap'];
|
||||||
|
$skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates'];
|
||||||
|
$port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
|
||||||
|
$password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
|
||||||
|
$host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
|
||||||
|
$subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
|
||||||
|
$enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
|
||||||
|
$mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
|
||||||
|
$exclude = (isset($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
|
||||||
|
$custom_params = (isset($_data['custom_params'])) ? $_data['custom_params'] : $is_now['custom_params'];
|
||||||
|
$maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
|
||||||
|
$maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
|
||||||
|
$timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1'];
|
||||||
|
$timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate custom params
|
||||||
|
foreach (explode('-', $custom_params) as $param){
|
||||||
|
if(empty($param)) continue;
|
||||||
|
|
||||||
|
// extract option
|
||||||
|
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||||
|
else $param = rtrim($param, ' ');
|
||||||
|
// remove first char if first char is -
|
||||||
|
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||||
|
|
||||||
|
if (str_contains($param, ' ')) {
|
||||||
|
// bad char
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'bad character SPACE'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if param is whitelisted
|
||||||
|
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||||
|
// bad option
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'bad option '. $param
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($subfolder2)) {
|
||||||
|
$subfolder2 = "";
|
||||||
|
}
|
||||||
|
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$maxage = "0";
|
||||||
|
}
|
||||||
|
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$timeout1 = "600";
|
||||||
|
}
|
||||||
|
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
|
||||||
|
$timeout2 = "600";
|
||||||
|
}
|
||||||
|
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
|
||||||
|
$maxbytespersecond = "0";
|
||||||
|
}
|
||||||
|
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!is_valid_domain_name($host1)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (@preg_match("/" . $exclude . "/", null) === false) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1,
|
||||||
|
`delete2` = :delete2,
|
||||||
|
`automap` = :automap,
|
||||||
|
`skipcrossduplicates` = :skipcrossduplicates,
|
||||||
|
`maxage` = :maxage,
|
||||||
|
`maxbytespersecond` = :maxbytespersecond,
|
||||||
|
`subfolder2` = :subfolder2,
|
||||||
|
`exclude` = :exclude,
|
||||||
|
`host1` = :host1,
|
||||||
|
`last_run` = :last_run,
|
||||||
|
`success` = :success,
|
||||||
|
`user1` = :user1,
|
||||||
|
`password1` = :password1,
|
||||||
|
`mins_interval` = :mins_interval,
|
||||||
|
`port1` = :port1,
|
||||||
|
`enc1` = :enc1,
|
||||||
|
`delete2duplicates` = :delete2duplicates,
|
||||||
|
`custom_params` = :custom_params,
|
||||||
|
`timeout1` = :timeout1,
|
||||||
|
`timeout2` = :timeout2,
|
||||||
|
`subscribeall` = :subscribeall,
|
||||||
|
`dry` = :dry,
|
||||||
|
`active` = :active
|
||||||
|
WHERE `id` = :id");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':delete1' => $delete1,
|
||||||
|
':delete2' => $delete2,
|
||||||
|
':automap' => $automap,
|
||||||
|
':skipcrossduplicates' => $skipcrossduplicates,
|
||||||
|
':id' => $id,
|
||||||
|
':exclude' => $exclude,
|
||||||
|
':maxage' => $maxage,
|
||||||
|
':maxbytespersecond' => $maxbytespersecond,
|
||||||
|
':subfolder2' => $subfolder2,
|
||||||
|
':host1' => $host1,
|
||||||
|
':user1' => $user1,
|
||||||
|
':password1' => $password1,
|
||||||
|
':last_run' => $last_run,
|
||||||
|
':success' => $success,
|
||||||
|
':mins_interval' => $mins_interval,
|
||||||
|
':port1' => $port1,
|
||||||
|
':enc1' => $enc1,
|
||||||
|
':delete2duplicates' => $delete2duplicates,
|
||||||
|
':custom_params' => $custom_params,
|
||||||
|
':timeout1' => $timeout1,
|
||||||
|
':timeout2' => $timeout2,
|
||||||
|
':subscribeall' => $subscribeall,
|
||||||
|
':dry' => $dry,
|
||||||
|
':active' => $active,
|
||||||
|
));
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('mailbox_modified', $username)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'filter':
|
case 'filter':
|
||||||
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
|
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -4193,6 +4575,68 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
case 'syncjob_details':
|
||||||
|
$syncjobdetails = array();
|
||||||
|
if (!is_numeric($_data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($_extra) && in_array('no_log', $_extra)) {
|
||||||
|
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")');
|
||||||
|
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($field = array_shift($fields)) {
|
||||||
|
$shown_fields[] = $field['Field'];
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",
|
||||||
|
`active`
|
||||||
|
FROM `imapsync` WHERE id = :id");
|
||||||
|
}
|
||||||
|
elseif (isset($_extra) && in_array('with_password', $_extra)) {
|
||||||
|
$stmt = $pdo->prepare("SELECT *,
|
||||||
|
`active`
|
||||||
|
FROM `imapsync` WHERE id = :id");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")');
|
||||||
|
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($field = array_shift($fields)) {
|
||||||
|
$shown_fields[] = $field['Field'];
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",
|
||||||
|
`active`
|
||||||
|
FROM `imapsync` WHERE id = :id");
|
||||||
|
}
|
||||||
|
$stmt->execute(array(':id' => $_data));
|
||||||
|
$syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!empty($syncjobdetails['returned_text'])) {
|
||||||
|
$syncjobdetails['log'] = $syncjobdetails['returned_text'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$syncjobdetails['log'] = '';
|
||||||
|
}
|
||||||
|
unset($syncjobdetails['returned_text']);
|
||||||
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $syncjobdetails;
|
||||||
|
break;
|
||||||
|
case 'syncjobs':
|
||||||
|
$syncjobdata = array();
|
||||||
|
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$_data = $_SESSION['mailcow_cc_username'];
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username");
|
||||||
|
$stmt->execute(array(':username' => $_data));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$syncjobdata[] = $row['id'];
|
||||||
|
}
|
||||||
|
return $syncjobdata;
|
||||||
|
break;
|
||||||
case 'spam_score':
|
case 'spam_score':
|
||||||
$curl = curl_init();
|
$curl = curl_init();
|
||||||
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
|
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
|
||||||
@@ -5014,6 +5458,46 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
switch ($_type) {
|
switch ($_type) {
|
||||||
|
case 'syncjob':
|
||||||
|
if (!is_array($_data['id'])) {
|
||||||
|
$ids = array();
|
||||||
|
$ids[] = $_data['id'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ids = $_data['id'];
|
||||||
|
}
|
||||||
|
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id");
|
||||||
|
$stmt->execute(array(':id' => $id));
|
||||||
|
$user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2'];
|
||||||
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
|
||||||
|
$stmt->execute(array(':id' => $id));
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => array('deleted_syncjob', $id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'filter':
|
case 'filter':
|
||||||
if (!is_array($_data['id'])) {
|
if (!is_array($_data['id'])) {
|
||||||
$ids = array();
|
$ids = array();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ function init_db_schema()
|
|||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "17072026_1000";
|
$db_version = "19022026_1220";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
@@ -754,145 +754,35 @@ function init_db_schema()
|
|||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
),
|
),
|
||||||
"imapsync_source" => array(
|
|
||||||
"cols" => array(
|
|
||||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
|
||||||
"name" => "VARCHAR(100) NOT NULL",
|
|
||||||
"description" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
|
||||||
"created_by" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
|
||||||
"scope" => "ENUM('all','domain','user') NOT NULL DEFAULT 'user'",
|
|
||||||
"host1" => "VARCHAR(255) NOT NULL",
|
|
||||||
"port1" => "SMALLINT UNSIGNED NOT NULL",
|
|
||||||
"enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
|
|
||||||
"auth_type" => "ENUM('PLAIN','LOGIN','CRAM-MD5','XOAUTH2') DEFAULT 'PLAIN'",
|
|
||||||
"oauth_flow" => "ENUM('client_credentials','authorization_code') NOT NULL DEFAULT 'client_credentials'",
|
|
||||||
"oauth_token_endpoint" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_authorize_endpoint" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_userinfo_endpoint" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_client_id" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_client_secret" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_scope" => "VARCHAR(500) DEFAULT NULL",
|
|
||||||
"oauth_extra_params" => "TEXT DEFAULT NULL",
|
|
||||||
"oauth_access_token" => "TEXT DEFAULT NULL",
|
|
||||||
"oauth_token_expires" => "BIGINT UNSIGNED DEFAULT NULL",
|
|
||||||
"oauth_last_refresh_error" => "TEXT DEFAULT NULL",
|
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
|
||||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
|
||||||
),
|
|
||||||
"keys" => array(
|
|
||||||
"primary" => array(
|
|
||||||
"" => array("id")
|
|
||||||
),
|
|
||||||
"unique" => array(
|
|
||||||
"uniq_creator_name" => array("created_by", "name")
|
|
||||||
),
|
|
||||||
"key" => array(
|
|
||||||
"idx_created_by" => array("created_by"),
|
|
||||||
"idx_scope" => array("scope")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
|
||||||
),
|
|
||||||
"imapsync_source_domain" => array(
|
|
||||||
"cols" => array(
|
|
||||||
"source_id" => "INT NOT NULL",
|
|
||||||
"domain" => "VARCHAR(255) NOT NULL"
|
|
||||||
),
|
|
||||||
"keys" => array(
|
|
||||||
"primary" => array(
|
|
||||||
"" => array("source_id", "domain")
|
|
||||||
),
|
|
||||||
"key" => array(
|
|
||||||
"idx_domain" => array("domain")
|
|
||||||
),
|
|
||||||
"fkey" => array(
|
|
||||||
"fk_imapsync_source_domain" => array(
|
|
||||||
"col" => "source_id",
|
|
||||||
"ref" => "imapsync_source.id",
|
|
||||||
"delete" => "CASCADE",
|
|
||||||
"update" => "NO ACTION"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
|
||||||
),
|
|
||||||
"imapsync_source_user" => array(
|
|
||||||
"cols" => array(
|
|
||||||
"source_id" => "INT NOT NULL",
|
|
||||||
"username" => "VARCHAR(255) NOT NULL"
|
|
||||||
),
|
|
||||||
"keys" => array(
|
|
||||||
"primary" => array(
|
|
||||||
"" => array("source_id", "username")
|
|
||||||
),
|
|
||||||
"key" => array(
|
|
||||||
"idx_username" => array("username")
|
|
||||||
),
|
|
||||||
"fkey" => array(
|
|
||||||
"fk_imapsync_source_user" => array(
|
|
||||||
"col" => "source_id",
|
|
||||||
"ref" => "imapsync_source.id",
|
|
||||||
"delete" => "CASCADE",
|
|
||||||
"update" => "NO ACTION"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
|
||||||
),
|
|
||||||
"imapsync_source_oauth_token" => array(
|
|
||||||
"cols" => array(
|
|
||||||
"source_id" => "INT NOT NULL",
|
|
||||||
"username" => "VARCHAR(255) NOT NULL",
|
|
||||||
"access_token" => "TEXT DEFAULT NULL",
|
|
||||||
"refresh_token" => "TEXT DEFAULT NULL",
|
|
||||||
"token_expires" => "BIGINT UNSIGNED DEFAULT NULL",
|
|
||||||
"last_refresh_error" => "TEXT DEFAULT NULL",
|
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
|
|
||||||
),
|
|
||||||
"keys" => array(
|
|
||||||
"primary" => array(
|
|
||||||
"" => array("source_id", "username")
|
|
||||||
),
|
|
||||||
"fkey" => array(
|
|
||||||
"fk_imapsync_source_oauth_token" => array(
|
|
||||||
"col" => "source_id",
|
|
||||||
"ref" => "imapsync_source.id",
|
|
||||||
"delete" => "CASCADE",
|
|
||||||
"update" => "NO ACTION"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
|
||||||
),
|
|
||||||
"imapsync" => array(
|
"imapsync" => array(
|
||||||
"cols" => array(
|
"cols" => array(
|
||||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||||
"user2" => "VARCHAR(255) NOT NULL",
|
"user2" => "VARCHAR(255) NOT NULL",
|
||||||
"source_id" => "INT NOT NULL",
|
"host1" => "VARCHAR(255) NOT NULL",
|
||||||
|
"authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'",
|
||||||
"regextrans2" => "VARCHAR(255) DEFAULT ''",
|
"regextrans2" => "VARCHAR(255) DEFAULT ''",
|
||||||
"authmd51" => "TINYINT(1) NOT NULL DEFAULT 0",
|
"authmd51" => "TINYINT(1) NOT NULL DEFAULT 0",
|
||||||
"domain2" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
"domain2" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||||
"subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
"subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||||
"user1" => "VARCHAR(255) NOT NULL",
|
"user1" => "VARCHAR(255) NOT NULL",
|
||||||
"password1" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
"password1" => "VARCHAR(255) NOT NULL",
|
||||||
"exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
|
"exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
|
||||||
"maxage" => "SMALLINT NOT NULL DEFAULT '0'",
|
"maxage" => "SMALLINT NOT NULL DEFAULT '0'",
|
||||||
"mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'",
|
"mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'",
|
||||||
"maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'",
|
"maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'",
|
||||||
|
"port1" => "SMALLINT UNSIGNED NOT NULL",
|
||||||
|
"enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
|
||||||
"delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
"delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||||
"delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"custom_params" => "TEXT",
|
"custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''",
|
||||||
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
|
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||||
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
|
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||||
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||||
"dry" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"dry" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"order_id" => "INT NOT NULL DEFAULT 0",
|
|
||||||
"returned_text" => "LONGTEXT",
|
"returned_text" => "LONGTEXT",
|
||||||
"last_run" => "TIMESTAMP NULL DEFAULT NULL",
|
"last_run" => "TIMESTAMP NULL DEFAULT NULL",
|
||||||
"success" => "TINYINT(1) UNSIGNED DEFAULT NULL",
|
"success" => "TINYINT(1) UNSIGNED DEFAULT NULL",
|
||||||
@@ -904,32 +794,6 @@ function init_db_schema()
|
|||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
"" => array("id")
|
"" => array("id")
|
||||||
),
|
|
||||||
"key" => array(
|
|
||||||
"idx_source_id" => array("source_id"),
|
|
||||||
"idx_order_id" => array("order_id")
|
|
||||||
),
|
|
||||||
"fkey" => array(
|
|
||||||
"fk_imapsync_source" => array(
|
|
||||||
"col" => "source_id",
|
|
||||||
"ref" => "imapsync_source.id",
|
|
||||||
"delete" => "RESTRICT",
|
|
||||||
"update" => "NO ACTION"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
|
||||||
),
|
|
||||||
"imapsync_settings" => array(
|
|
||||||
"cols" => array(
|
|
||||||
"name" => "VARCHAR(64) NOT NULL",
|
|
||||||
"value" => "VARCHAR(255) NOT NULL",
|
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
|
|
||||||
),
|
|
||||||
"keys" => array(
|
|
||||||
"primary" => array(
|
|
||||||
"" => array("name")
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
@@ -1319,33 +1183,6 @@ function init_db_schema()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate imapsync to source-based schema
|
|
||||||
if ($table == 'imapsync') {
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'imapsync'");
|
|
||||||
if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) != 0) {
|
|
||||||
$stmt = $pdo->query("SHOW COLUMNS FROM `imapsync` LIKE 'host1'");
|
|
||||||
$has_legacy_cols = (count($stmt->fetchAll(PDO::FETCH_ASSOC)) != 0);
|
|
||||||
$stmt = $pdo->query("SHOW COLUMNS FROM `imapsync` LIKE 'source_id'");
|
|
||||||
$has_source_id = (count($stmt->fetchAll(PDO::FETCH_ASSOC)) != 0);
|
|
||||||
|
|
||||||
if ($has_legacy_cols && !$has_source_id) {
|
|
||||||
$pdo->exec("INSERT IGNORE INTO `imapsync_source` (`name`, `created_by`, `scope`, `host1`, `port1`, `enc1`, `auth_type`, `active`)
|
|
||||||
SELECT CONCAT('legacy-', `host1`, '-', `port1`, '-', `enc1`) AS name,
|
|
||||||
'' AS created_by, 'all' AS scope, `host1`, `port1`, `enc1`, `authmech1`, 1
|
|
||||||
FROM `imapsync`
|
|
||||||
GROUP BY `host1`, `port1`, `enc1`, `authmech1`");
|
|
||||||
$pdo->exec("ALTER TABLE `imapsync` ADD COLUMN `source_id` INT DEFAULT NULL");
|
|
||||||
$pdo->exec("UPDATE `imapsync` i
|
|
||||||
JOIN `imapsync_source` s
|
|
||||||
ON s.`host1` = i.`host1` AND s.`port1` = i.`port1`
|
|
||||||
AND s.`enc1` = i.`enc1` AND s.`auth_type` = i.`authmech1`
|
|
||||||
AND s.`created_by` = '' AND s.`scope` = 'all'
|
|
||||||
SET i.`source_id` = s.`id`");
|
|
||||||
$pdo->exec("ALTER TABLE `imapsync` MODIFY COLUMN `source_id` INT NOT NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
|
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
if ($num_results != 0) {
|
if ($num_results != 0) {
|
||||||
@@ -1525,36 +1362,6 @@ function init_db_schema()
|
|||||||
// Migrate webauthn tfa
|
// Migrate webauthn tfa
|
||||||
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
|
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
|
||||||
|
|
||||||
// Syncjobs: seed global settings + one-time sequential order_id for pre-existing jobs
|
|
||||||
$pdo->query("INSERT IGNORE INTO `imapsync_settings` (`name`, `value`) VALUES ('max_parallel', '1')");
|
|
||||||
if (intval($pdo->query("SELECT COUNT(*) FROM `imapsync`")->fetchColumn()) > 0
|
|
||||||
&& intval($pdo->query("SELECT MAX(`order_id`) FROM `imapsync`")->fetchColumn()) === 0) {
|
|
||||||
$pdo->query("SET @r := 0");
|
|
||||||
$pdo->query("UPDATE `imapsync` SET `order_id` = (@r := @r + 1) ORDER BY `id`");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Syncjobs: migrate custom_params from raw imapsync string to structured JSON pairs.
|
|
||||||
// Old values could not contain spaces (they were rejected), so a whitespace split is safe.
|
|
||||||
$allow = $GLOBALS['IMAPSYNC_OPTIONS']['whitelist'];
|
|
||||||
$cp_upd = $pdo->prepare("UPDATE `imapsync` SET `custom_params` = :cp WHERE `id` = :id");
|
|
||||||
foreach ($pdo->query("SELECT `id`, `custom_params` FROM `imapsync`")->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
|
||||||
$cp = trim((string)$r['custom_params']);
|
|
||||||
if ($cp !== '' && $cp[0] === '[') continue; // already migrated (JSON)
|
|
||||||
$pairs = array();
|
|
||||||
if ($cp !== '') {
|
|
||||||
$tokens = preg_split('/\s+/', $cp, -1, PREG_SPLIT_NO_EMPTY);
|
|
||||||
for ($i = 0; $i < count($tokens); $i++) {
|
|
||||||
if (strpos($tokens[$i], '--') !== 0) continue;
|
|
||||||
$opt = ltrim($tokens[$i], '-'); $val = '';
|
|
||||||
if (strpos($opt, '=') !== false) { list($opt, $val) = explode('=', $opt, 2); }
|
|
||||||
elseif ($i + 1 < count($tokens) && strpos($tokens[$i + 1], '--') !== 0) { $val = $tokens[++$i]; }
|
|
||||||
if (!in_array(strtolower($opt), $allow)) continue; // drop options no longer allowed
|
|
||||||
$pairs[] = array('o' => strtolower($opt), 'v' => $val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$cp_upd->execute(array(':cp' => json_encode($pairs), ':id' => $r['id']));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject admin if not exists
|
// Inject admin if not exists
|
||||||
$stmt = $pdo->query("SELECT NULL FROM `admin`");
|
$stmt = $pdo->query("SELECT NULL FROM `admin`");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|||||||
@@ -285,7 +285,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.syncjob.inc.php';
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ $AVAILABLE_LANGUAGES = array(
|
|||||||
'sv-se' => 'Svenska (Swedish)',
|
'sv-se' => 'Svenska (Swedish)',
|
||||||
'tr-tr' => 'Türkçe (Turkish)',
|
'tr-tr' => 'Türkçe (Turkish)',
|
||||||
'uk-ua' => 'Українська (Ukrainian)',
|
'uk-ua' => 'Українська (Ukrainian)',
|
||||||
'uz-uz' => 'Oʻzbekcha (Uzbek)',
|
|
||||||
'vi-vn' => 'Tiếng Việt (Vietnamese)',
|
'vi-vn' => 'Tiếng Việt (Vietnamese)',
|
||||||
'zh-cn' => '简体中文 (Simplified Chinese)',
|
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||||
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||||
@@ -285,7 +284,11 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'whitelist' => array(
|
'whitelist' => array(
|
||||||
'abort',
|
'abort',
|
||||||
'authmd51',
|
'authmd51',
|
||||||
|
'authmd52',
|
||||||
|
'authmech1',
|
||||||
|
'authmech2',
|
||||||
'authuser1',
|
'authuser1',
|
||||||
|
'authuser2',
|
||||||
'debug',
|
'debug',
|
||||||
'debugcontent',
|
'debugcontent',
|
||||||
'debugcrossduplicates',
|
'debugcrossduplicates',
|
||||||
@@ -293,15 +296,20 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'debugfolders',
|
'debugfolders',
|
||||||
'debugimap',
|
'debugimap',
|
||||||
'debugimap1',
|
'debugimap1',
|
||||||
|
'debugimap2',
|
||||||
'debugmemory',
|
'debugmemory',
|
||||||
'debugssl',
|
'debugssl',
|
||||||
'delete1emptyfolders',
|
'delete1emptyfolders',
|
||||||
'delete2folders',
|
'delete2folders',
|
||||||
'disarmreadreceipts',
|
'disarmreadreceipts',
|
||||||
'domain1',
|
'domain1',
|
||||||
|
'domain2',
|
||||||
'domino1',
|
'domino1',
|
||||||
|
'domino2',
|
||||||
|
'dry',
|
||||||
'errorsmax',
|
'errorsmax',
|
||||||
'exchange1',
|
'exchange1',
|
||||||
|
'exchange2',
|
||||||
'exitwhenover',
|
'exitwhenover',
|
||||||
'expunge1',
|
'expunge1',
|
||||||
'f1f2',
|
'f1f2',
|
||||||
@@ -311,6 +319,7 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'folderlast',
|
'folderlast',
|
||||||
'folderrec',
|
'folderrec',
|
||||||
'gmail1',
|
'gmail1',
|
||||||
|
'gmail2',
|
||||||
'idatefromheader',
|
'idatefromheader',
|
||||||
'include',
|
'include',
|
||||||
'inet4',
|
'inet4',
|
||||||
@@ -320,7 +329,10 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'justfoldersizes',
|
'justfoldersizes',
|
||||||
'justlogin',
|
'justlogin',
|
||||||
'keepalive1',
|
'keepalive1',
|
||||||
|
'keepalive2',
|
||||||
'log',
|
'log',
|
||||||
|
'logdir',
|
||||||
|
'logfile',
|
||||||
'maxbytesafter',
|
'maxbytesafter',
|
||||||
'maxlinelength',
|
'maxlinelength',
|
||||||
'maxmessagespersecond',
|
'maxmessagespersecond',
|
||||||
@@ -330,24 +342,45 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'minsize',
|
'minsize',
|
||||||
'noabletosearch',
|
'noabletosearch',
|
||||||
'noabletosearch1',
|
'noabletosearch1',
|
||||||
|
'noabletosearch2',
|
||||||
'noexpunge1',
|
'noexpunge1',
|
||||||
|
'noexpunge2',
|
||||||
'nofoldersizesatend',
|
'nofoldersizesatend',
|
||||||
'noid',
|
'noid',
|
||||||
'nolog',
|
'nolog',
|
||||||
'nomixfolders',
|
'nomixfolders',
|
||||||
'noresyncflags',
|
'noresyncflags',
|
||||||
'nossl1',
|
'nossl1',
|
||||||
|
'nossl2',
|
||||||
'nosyncacls',
|
'nosyncacls',
|
||||||
'notls1',
|
'notls1',
|
||||||
|
'notls2',
|
||||||
|
'nouidexpunge2',
|
||||||
'nousecache',
|
'nousecache',
|
||||||
|
'oauthaccesstoken1',
|
||||||
|
'oauthaccesstoken2',
|
||||||
|
'oauthdirect1',
|
||||||
|
'oauthdirect2',
|
||||||
'office1',
|
'office1',
|
||||||
|
'office2',
|
||||||
|
'pidfile',
|
||||||
|
'pidfilelocking',
|
||||||
'prefix1',
|
'prefix1',
|
||||||
|
'prefix2',
|
||||||
|
'proxyauth1',
|
||||||
|
'proxyauth2',
|
||||||
'resyncflags',
|
'resyncflags',
|
||||||
'resynclabels',
|
'resynclabels',
|
||||||
'search',
|
'search',
|
||||||
'search1',
|
'search1',
|
||||||
|
'search2',
|
||||||
'sep1',
|
'sep1',
|
||||||
|
'sep2',
|
||||||
|
'showpasswords',
|
||||||
'skipemptyfolders',
|
'skipemptyfolders',
|
||||||
|
'ssl2',
|
||||||
|
'sslargs1',
|
||||||
|
'sslargs2',
|
||||||
'subfolder1',
|
'subfolder1',
|
||||||
'subscribe',
|
'subscribe',
|
||||||
'subscribed',
|
'subscribed',
|
||||||
@@ -358,6 +391,7 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'tests',
|
'tests',
|
||||||
'testslive',
|
'testslive',
|
||||||
'testslive6',
|
'testslive6',
|
||||||
|
'tls2',
|
||||||
'truncmess',
|
'truncmess',
|
||||||
'usecache',
|
'usecache',
|
||||||
'useheader',
|
'useheader',
|
||||||
|
|||||||
@@ -414,219 +414,3 @@ function copyToClipboard(id) {
|
|||||||
navigator.clipboard.writeText(copyText.value);
|
navigator.clipboard.writeText(copyText.value);
|
||||||
mailcow_alert_box(lang.copy_to_clipboard, "success");
|
mailcow_alert_box(lang.copy_to_clipboard, "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== IMAP sync sources: shared helpers + delegated listeners =====
|
|
||||||
// Used by the mailbox/user/edit pages (each still owns its own draw_imapsync_source_table).
|
|
||||||
// Kept here because 013-mailcow.js is loaded globally, so the logic lives in one place.
|
|
||||||
|
|
||||||
// Human-readable visibility label for a source row (used by the per-page source tables)
|
|
||||||
function imapsyncScopeDisplay(item) {
|
|
||||||
if (item.scope === 'all') return '<i class="bi bi-globe"></i> ' + lang.syncjobs.source_scope_all;
|
|
||||||
if (item.scope === 'domain') return escapeHtml(lang.syncjobs.source_scope_domain + ': ' + (item.domains || []).join(', '));
|
|
||||||
var us = item.users || [];
|
|
||||||
if (us.length === 0) return escapeHtml(item.created_by || '');
|
|
||||||
return escapeHtml(lang.syncjobs.source_scope_user + ': ' + us.join(', '));
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Re)build every imapsync-source dropdown from the ACL-filtered API (fresh on modal open)
|
|
||||||
function populateImapsyncSourceSelects() {
|
|
||||||
$.get("/api/v1/get/syncjob_source/all", function(sources) {
|
|
||||||
$('select.imapsync-source-select').each(function() {
|
|
||||||
var $sel = $(this);
|
|
||||||
var keepId = $sel.data('current-source-id');
|
|
||||||
$sel.empty();
|
|
||||||
$sel.append('<option value="">' + (lang.syncjobs ? lang.syncjobs.source_select : '') + '</option>');
|
|
||||||
$.each(sources, function(_, src) {
|
|
||||||
var label = src.name + ' — ' + src.host1 + ':' + src.port1 + ' (' + src.auth_type + ')';
|
|
||||||
if (src.scope === 'all') label += ' [' + (lang.syncjobs ? lang.syncjobs.source_scope_all : 'all') + ']';
|
|
||||||
var $opt = $('<option/>').val(src.id).text(label)
|
|
||||||
.data('auth-type', src.auth_type).data('oauth-flow', src.oauth_flow);
|
|
||||||
if (keepId && parseInt(keepId) === parseInt(src.id)) $opt.attr('selected', 'selected');
|
|
||||||
$sel.append($opt);
|
|
||||||
});
|
|
||||||
if (typeof $sel.selectpicker === 'function') {
|
|
||||||
$sel.selectpicker('destroy');
|
|
||||||
$sel.selectpicker();
|
|
||||||
}
|
|
||||||
$sel.trigger('change');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate a scope domain/user multiselect from the ACL-filtered endpoint (preserves existing options)
|
|
||||||
function fillImapsyncScopeSelect($sel, url, field) {
|
|
||||||
$.get('/api/v1/get/' + url, function(rows) {
|
|
||||||
var have = {};
|
|
||||||
$sel.find('option').each(function() { have[$(this).val()] = true; });
|
|
||||||
$.each(rows, function(_, r) {
|
|
||||||
var v = r[field];
|
|
||||||
if (!v || have[v]) return;
|
|
||||||
$sel.append($('<option/>').val(v).text(v));
|
|
||||||
});
|
|
||||||
// Rebuild the bootstrap-select wrapper (destroy + re-init)
|
|
||||||
if (typeof $sel.selectpicker === 'function') {
|
|
||||||
$sel.selectpicker('destroy');
|
|
||||||
$sel.selectpicker();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source editor: OAuth block visibility + authorization_code-only fields + redirect URI
|
|
||||||
function imapsyncSyncSourceOauthToggle($form) {
|
|
||||||
var isOauth = $form.find('select.imapsync-source-auth-type').val() === 'XOAUTH2';
|
|
||||||
var isAuthCode = $form.find('select.imapsync-source-oauth-flow').val() === 'authorization_code';
|
|
||||||
$form.find('.imapsync-source-oauth-block').toggle(isOauth);
|
|
||||||
$form.find('.imapsync-source-authcode-block').toggle(isOauth && isAuthCode);
|
|
||||||
$form.find('.imapsync-source-redirect-uri').val(window.location.origin + '/syncjob-oauth');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowlisted imapsync option names for the custom-params selects. Loaded once and cached.
|
|
||||||
var imapsyncCpOptions = null;
|
|
||||||
function imapsyncCpEnsureOptions(cb) {
|
|
||||||
if (imapsyncCpOptions !== null) { cb(); return; }
|
|
||||||
$.get("/api/v1/get/syncjob_options", function(opts) {
|
|
||||||
imapsyncCpOptions = Array.isArray(opts) ? opts : [];
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append one row to an editor; option is a bootstrap-select (not free-text), value is arbitrary.
|
|
||||||
function imapsyncCpAddRow($editor, o, v) {
|
|
||||||
var $row = $(
|
|
||||||
'<div class="input-group mb-2 imapsync-cp-row">' +
|
|
||||||
'<select class="form-control imapsync-cp-opt" data-width="35%"></select>' +
|
|
||||||
'<input type="text" class="form-control imapsync-cp-val">' +
|
|
||||||
'<button type="button" class="btn btn-secondary imapsync-cp-remove"><i class="bi bi-trash"></i></button>' +
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
var $sel = $row.find('.imapsync-cp-opt')
|
|
||||||
.attr('title', (lang.syncjobs && lang.syncjobs.custom_param_option) || 'option');
|
|
||||||
(imapsyncCpOptions || []).forEach(function(name) {
|
|
||||||
$sel.append($('<option></option>').attr('value', name).text(name));
|
|
||||||
});
|
|
||||||
// keep a stored value that is no longer allowlisted visible instead of silently dropping it
|
|
||||||
if (o && (imapsyncCpOptions || []).indexOf(o) === -1) {
|
|
||||||
$sel.append($('<option></option>').attr('value', o).text(o));
|
|
||||||
}
|
|
||||||
$sel.val(o || '');
|
|
||||||
$row.find('.imapsync-cp-val').attr('placeholder', (lang.syncjobs && lang.syncjobs.custom_param_value) || 'value').val(v || '');
|
|
||||||
$editor.find('.imapsync-cp-rows').append($row);
|
|
||||||
$sel.selectpicker();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the rows of an editor from its hidden JSON value.
|
|
||||||
function imapsyncCpBuild($editor) {
|
|
||||||
var $hidden = $editor.siblings('.imapsync-cp-value');
|
|
||||||
$editor.find('.imapsync-cp-opt').selectpicker('destroy');
|
|
||||||
$editor.find('.imapsync-cp-rows').empty();
|
|
||||||
var pairs = [];
|
|
||||||
try { pairs = JSON.parse($hidden.val() || '[]'); } catch (e) { pairs = []; }
|
|
||||||
if (!Array.isArray(pairs)) pairs = [];
|
|
||||||
pairs.forEach(function(p) {
|
|
||||||
if (p && typeof p === 'object') imapsyncCpAddRow($editor, p.o, p.v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-serialize an editor's rows back into its hidden JSON value (option name required).
|
|
||||||
function imapsyncCpSerialize($editor) {
|
|
||||||
var out = [];
|
|
||||||
$editor.find('.imapsync-cp-row').each(function() {
|
|
||||||
var o = $.trim($(this).find('.imapsync-cp-opt').val() || '').replace(/^-+/, '');
|
|
||||||
if (o === '') return;
|
|
||||||
out.push({ o: o, v: $(this).find('.imapsync-cp-val').val() || '' });
|
|
||||||
});
|
|
||||||
$editor.siblings('.imapsync-cp-value').val(JSON.stringify(out));
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
// Custom-params editor: keep the hidden JSON in sync with the rows
|
|
||||||
$(document).on('change', '.imapsync-cp-opt', function() {
|
|
||||||
imapsyncCpSerialize($(this).closest('.imapsync-cp-editor'));
|
|
||||||
});
|
|
||||||
$(document).on('input', '.imapsync-cp-val', function() {
|
|
||||||
imapsyncCpSerialize($(this).closest('.imapsync-cp-editor'));
|
|
||||||
});
|
|
||||||
$(document).on('click', '.imapsync-cp-add', function() {
|
|
||||||
var $editor = $(this).closest('.imapsync-cp-editor');
|
|
||||||
imapsyncCpEnsureOptions(function() { imapsyncCpAddRow($editor); });
|
|
||||||
});
|
|
||||||
$(document).on('click', '.imapsync-cp-remove', function() {
|
|
||||||
var $editor = $(this).closest('.imapsync-cp-editor');
|
|
||||||
$(this).closest('.imapsync-cp-row').find('.imapsync-cp-opt').selectpicker('destroy');
|
|
||||||
$(this).closest('.imapsync-cp-row').remove();
|
|
||||||
imapsyncCpSerialize($editor);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Edit page (syncjob.twig): build rows from the stored value on load
|
|
||||||
imapsyncCpEnsureOptions(function() {
|
|
||||||
$('.imapsync-cp-editor').each(function() { imapsyncCpBuild($(this)); });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Syncjob form: toggle password row / OAuth "connect" button by the selected source's flow
|
|
||||||
$(document).on('change', 'select.imapsync-source-select', function() {
|
|
||||||
var $sel = $(this);
|
|
||||||
var $opt = $sel.find('option:selected');
|
|
||||||
var $form = $sel.closest('form');
|
|
||||||
var isOauth = ($opt.data('auth-type') === 'XOAUTH2');
|
|
||||||
var isAuthCode = isOauth && ($opt.data('oauth-flow') === 'authorization_code');
|
|
||||||
$form.find('.password1-row').toggle(!isOauth);
|
|
||||||
$form.find('input[name="password1"]').prop('required', !isOauth);
|
|
||||||
$form.find('.oauth-source-hint').toggle(isOauth);
|
|
||||||
$form.find('.imapsync-oauth-connect-row').toggle(isAuthCode);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Source editor: OAuth block + authorization_code fields
|
|
||||||
$(document).on('change', 'select.imapsync-source-auth-type, select.imapsync-source-oauth-flow', function() {
|
|
||||||
imapsyncSyncSourceOauthToggle($(this).closest('form'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Source editor: scope -> show + populate the domain/user pickers
|
|
||||||
$(document).on('change', 'select.imapsync-source-scope', function() {
|
|
||||||
var $form = $(this).closest('form');
|
|
||||||
var scope = $(this).val();
|
|
||||||
$form.find('.imapsync-source-domains-row').toggle(scope === 'domain');
|
|
||||||
$form.find('.imapsync-source-users-row').toggle(scope === 'user');
|
|
||||||
if (scope === 'domain') fillImapsyncScopeSelect($form.find('.imapsync-source-domains'), 'domain/all', 'domain_name');
|
|
||||||
if (scope === 'user') fillImapsyncScopeSelect($form.find('.imapsync-source-users'), 'mailbox/all', 'username');
|
|
||||||
});
|
|
||||||
|
|
||||||
// "Connect with the provider": open the per-user OAuth popup for the selected source
|
|
||||||
$(document).on('click', '.imapsync-oauth-connect-btn', function() {
|
|
||||||
var $form = $(this).closest('form');
|
|
||||||
var sourceId = $form.find('select.imapsync-source-select').val();
|
|
||||||
if (!sourceId) return;
|
|
||||||
window.open('/syncjob-oauth?action=start&source_id=' + encodeURIComponent(sourceId),
|
|
||||||
'syncjob_oauth', 'width=600,height=700');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Result from the OAuth popup: fill + lock user1, mark connected (or show the error)
|
|
||||||
window.addEventListener('message', function(ev) {
|
|
||||||
if (ev.origin !== window.location.origin || !ev.data || ev.data.type !== 'syncjob_oauth') return;
|
|
||||||
$('.imapsync-oauth-connect-row:visible').each(function() {
|
|
||||||
var $form = $(this).closest('form');
|
|
||||||
var $status = $form.find('.imapsync-oauth-connect-status');
|
|
||||||
if (ev.data.ok) {
|
|
||||||
$form.find('input[name="user1"]').val(ev.data.user1).prop('readonly', true);
|
|
||||||
$status.removeClass('text-danger').addClass('text-success')
|
|
||||||
.text((lang.syncjobs ? lang.syncjobs.syncjob_oauth_connected : 'Connected') + ': ' + ev.data.user1);
|
|
||||||
} else {
|
|
||||||
$status.removeClass('text-success').addClass('text-danger').text(ev.data.error || 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add-source modal opens: init scope pickers + OAuth block (auth_type may be cached-form preselected)
|
|
||||||
$(document).on('shown.bs.modal', '#addImapsyncSourceModal', function() {
|
|
||||||
$(this).find('select.imapsync-source-scope').trigger('change');
|
|
||||||
imapsyncSyncSourceOauthToggle($(this).find('form'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add-syncjob modal opens: (re)load the source dropdown from the API
|
|
||||||
$(document).on('shown.bs.modal', '#addSyncJobModalAdmin, #addSyncJobModal', function() {
|
|
||||||
populateImapsyncSourceSelects();
|
|
||||||
var $modal = $(this);
|
|
||||||
imapsyncCpEnsureOptions(function() {
|
|
||||||
$modal.find('.imapsync-cp-editor').each(function() { imapsyncCpBuild($(this)); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -240,15 +240,4 @@ jQuery(function($){
|
|||||||
// Draw Table if tab is active
|
// Draw Table if tab is active
|
||||||
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
|
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
|
||||||
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table());
|
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table());
|
||||||
|
|
||||||
// initialise imapsync-source form on load.
|
|
||||||
if ($('select.imapsync-source-oauth-flow').length) {
|
|
||||||
imapsyncSyncSourceOauthToggle($('select.imapsync-source-oauth-flow').closest('form'));
|
|
||||||
}
|
|
||||||
if ($('select.imapsync-source-scope').length) {
|
|
||||||
$('select.imapsync-source-scope').trigger('change');
|
|
||||||
}
|
|
||||||
if ($('select.imapsync-source-select').length) {
|
|
||||||
populateImapsyncSourceSelects();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2157,7 +2157,7 @@ jQuery(function($){
|
|||||||
"tr" +
|
"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>>",
|
||||||
language: lang_datatables,
|
language: lang_datatables,
|
||||||
order: [[3, 'asc']],
|
order: [[2, 'desc']],
|
||||||
initComplete: function(){
|
initComplete: function(){
|
||||||
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
|
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
|
||||||
},
|
},
|
||||||
@@ -2173,7 +2173,7 @@ jQuery(function($){
|
|||||||
} else {
|
} else {
|
||||||
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
|
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
|
||||||
}
|
}
|
||||||
item.server_w_port = escapeHtml(item.user1) + '@' + escapeHtml(item.source_name || '?') + ' (' + escapeHtml(item.source_host || '?') + ':' + escapeHtml(item.source_port || '?') + ')';
|
item.server_w_port = escapeHtml(item.user1) + '@' + escapeHtml(item.host1) + ':' + escapeHtml(item.port1);
|
||||||
item.action = '<div class="btn-group">' +
|
item.action = '<div class="btn-group">' +
|
||||||
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||||
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||||
@@ -2193,10 +2193,10 @@ jQuery(function($){
|
|||||||
} else {
|
} else {
|
||||||
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||||
}
|
}
|
||||||
if (lang.syncjobs['syncjob_'+item.exit_status]) {
|
if (lang['syncjob_'+item.exit_status]) {
|
||||||
item.exit_status = lang.syncjobs['syncjob_'+item.exit_status];
|
item.exit_status = lang['syncjob_'+item.exit_status];
|
||||||
} else if (item.success != '-') {
|
} else if (item.success != '-') {
|
||||||
item.exit_status = lang.syncjobs.syncjob_check_log;
|
item.exit_status = lang.syncjob_check_log;
|
||||||
}
|
}
|
||||||
item.exit_status = item.success + ' ' + item.exit_status;
|
item.exit_status = item.success + ' ' + item.exit_status;
|
||||||
});
|
});
|
||||||
@@ -2228,12 +2228,6 @@ jQuery(function($){
|
|||||||
responsivePriority: 3,
|
responsivePriority: 3,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: lang.syncjobs.order_id,
|
|
||||||
data: 'order_id',
|
|
||||||
responsivePriority: 3,
|
|
||||||
defaultContent: ''
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: lang.owner,
|
title: lang.owner,
|
||||||
data: 'user2',
|
data: 'user2',
|
||||||
@@ -2251,7 +2245,7 @@ jQuery(function($){
|
|||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.syncjobs.syncjob_last_run_result,
|
title: lang.syncjob_last_run_result,
|
||||||
data: 'exit_status',
|
data: 'exit_status',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
@@ -2471,65 +2465,6 @@ jQuery(function($){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw_imapsync_source_table() {
|
|
||||||
if ($.fn.DataTable.isDataTable('#imapsync_source_table')) {
|
|
||||||
$('#imapsync_source_table').DataTable().columns.adjust().responsive.recalc();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('#imapsync_source_table').DataTable({
|
|
||||||
responsive: true,
|
|
||||||
processing: true,
|
|
||||||
serverSide: false,
|
|
||||||
stateSave: true,
|
|
||||||
pageLength: pagination_size,
|
|
||||||
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,
|
|
||||||
ajax: {
|
|
||||||
type: "GET",
|
|
||||||
url: "/api/v1/get/syncjob_source/all",
|
|
||||||
dataSrc: function(json) {
|
|
||||||
$.each(json, function(i, item) {
|
|
||||||
item.endpoint = escapeHtml(item.host1 + ':' + item.port1 + ' (' + item.enc1 + ')');
|
|
||||||
item.auth_display = escapeHtml(item.auth_type);
|
|
||||||
if (item.auth_type === 'XOAUTH2') {
|
|
||||||
if (item.oauth_token_expires && item.oauth_token_expires * 1000 > Date.now()) {
|
|
||||||
item.auth_display += ' <span class="badge bg-success">' + lang.syncjobs.source_token_status + ': ' + new Date(item.oauth_token_expires * 1000).toLocaleString() + '</span>';
|
|
||||||
} else if (item.oauth_last_refresh_error) {
|
|
||||||
item.auth_display += ' <span class="badge bg-danger" title="' + escapeHtml(item.oauth_last_refresh_error) + '">' + lang.syncjobs.source_token_error + '</span>';
|
|
||||||
} else {
|
|
||||||
item.auth_display += ' <span class="badge bg-warning">' + lang.waiting + '</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.visibility_display = imapsyncScopeDisplay(item);
|
|
||||||
if (item.can_edit) {
|
|
||||||
item.action = '<div class="btn-group">' +
|
|
||||||
'<a href="/edit/syncjob_source/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
|
||||||
'<a href="#" data-action="delete_selected" data-id="single-syncjob_source" data-api-url="delete/syncjob_source" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
|
||||||
'</div>';
|
|
||||||
} else {
|
|
||||||
item.action = '<span class="text-muted">-</span>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{ title: 'ID', data: 'id', defaultContent: '' },
|
|
||||||
{ title: lang.syncjobs.source_name, data: 'name', defaultContent: '', render: (d) => escapeHtml(d || '') },
|
|
||||||
{ title: lang.syncjobs.source_scope, data: 'visibility_display', defaultContent: '' },
|
|
||||||
{ title: 'Endpoint', data: 'endpoint', defaultContent: '' },
|
|
||||||
{ title: lang.syncjobs.source_auth_type, data: 'auth_display', defaultContent: '' },
|
|
||||||
{ title: lang.active, data: 'active', defaultContent: '',
|
|
||||||
render: function(d) { return d == 1 ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>'; }},
|
|
||||||
{ title: lang.action, data: 'action', defaultContent: '', className: 'dt-text-right' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shared imapsync helpers + delegated listeners live in js/build/013-mailcow.js
|
|
||||||
// (draw_imapsync_source_table stays per-page and calls the global imapsyncScopeDisplay).
|
|
||||||
|
|
||||||
// Load only if the tab is visible
|
// Load only if the tab is visible
|
||||||
onVisible("[id^=domain_table]", () => draw_domain_table());
|
onVisible("[id^=domain_table]", () => draw_domain_table());
|
||||||
onVisible("[id^=templates_domain_table]", () => draw_templates_domain_table());
|
onVisible("[id^=templates_domain_table]", () => draw_templates_domain_table());
|
||||||
@@ -2539,7 +2474,6 @@ jQuery(function($){
|
|||||||
onVisible("[id^=alias_table]", () => draw_alias_table());
|
onVisible("[id^=alias_table]", () => draw_alias_table());
|
||||||
onVisible("[id^=aliasdomain_table]", () => draw_aliasdomain_table());
|
onVisible("[id^=aliasdomain_table]", () => draw_aliasdomain_table());
|
||||||
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
||||||
onVisible("[id^=imapsync_source_table]", () => draw_imapsync_source_table());
|
|
||||||
onVisible("[id^=filter_table]", () => draw_filter_table());
|
onVisible("[id^=filter_table]", () => draw_filter_table());
|
||||||
onVisible("[id^=bcc_table]", () => draw_bcc_table());
|
onVisible("[id^=bcc_table]", () => draw_bcc_table());
|
||||||
onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table());
|
onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table());
|
||||||
|
|||||||
@@ -102,21 +102,18 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
data: 'id',
|
data: 'id',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.qid,
|
title: lang.qid,
|
||||||
data: 'qid',
|
data: 'qid',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.sender,
|
title: lang.sender,
|
||||||
data: 'sender',
|
data: 'sender',
|
||||||
className: 'senders-mw220',
|
className: 'senders-mw220',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.subj,
|
title: lang.subj,
|
||||||
@@ -131,8 +128,7 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.rcpt,
|
title: lang.rcpt,
|
||||||
data: 'rcpt',
|
data: 'rcpt',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.danger,
|
title: lang.danger,
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ jQuery(function($){
|
|||||||
url: "/api/v1/get/mailq/all",
|
url: "/api/v1/get/mailq/all",
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + escapeHtml(item.queue_id) + '" />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||||
rcpts = $.map(item.recipients, function(i) {
|
rcpts = $.map(item.recipients, function(i) {
|
||||||
return escapeHtml(i);
|
return escapeHtml(i);
|
||||||
});
|
});
|
||||||
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="' + escapeHtml(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.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;
|
||||||
@@ -79,14 +79,12 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: 'QID',
|
title: 'QID',
|
||||||
data: 'queue_id',
|
data: 'queue_id',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Queue',
|
title: 'Queue',
|
||||||
data: 'queue_name',
|
data: 'queue_name',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.arrival_time,
|
title: lang_admin.arrival_time,
|
||||||
@@ -108,8 +106,7 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang_admin.sender,
|
title: lang_admin.sender,
|
||||||
data: 'sender',
|
data: 'sender',
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
render: $.fn.dataTable.render.text()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.recipients,
|
title: lang_admin.recipients,
|
||||||
|
|||||||
@@ -272,7 +272,6 @@ jQuery(function($){
|
|||||||
"tr" +
|
"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>>",
|
||||||
language: lang_datatables,
|
language: lang_datatables,
|
||||||
order: [[3, 'asc']],
|
|
||||||
ajax: {
|
ajax: {
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
|
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
|
||||||
@@ -285,7 +284,7 @@ jQuery(function($){
|
|||||||
} else {
|
} else {
|
||||||
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
|
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
|
||||||
}
|
}
|
||||||
item.server_w_port = escapeHtml(item.user1 + '@' + (item.source_name || '?') + ' (' + (item.source_host || '?') + ':' + (item.source_port || '?') + ')');
|
item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1);
|
||||||
if (acl_data.syncjobs === 1) {
|
if (acl_data.syncjobs === 1) {
|
||||||
item.action = '<div class="btn-group">' +
|
item.action = '<div class="btn-group">' +
|
||||||
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||||
@@ -311,10 +310,10 @@ jQuery(function($){
|
|||||||
} else {
|
} else {
|
||||||
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||||
}
|
}
|
||||||
if (lang.syncjobs['syncjob_'+item.exit_status]) {
|
if (lang['syncjob_'+item.exit_status]) {
|
||||||
item.exit_status = lang.syncjobs['syncjob_'+item.exit_status];
|
item.exit_status = lang['syncjob_'+item.exit_status];
|
||||||
} else if (item.success != '-') {
|
} else if (item.success != '-') {
|
||||||
item.exit_status = lang.syncjobs.syncjob_check_log;
|
item.exit_status = lang.syncjob_check_log;
|
||||||
}
|
}
|
||||||
item.exit_status = item.success + ' ' + item.exit_status;
|
item.exit_status = item.success + ' ' + item.exit_status;
|
||||||
});
|
});
|
||||||
@@ -346,12 +345,6 @@ jQuery(function($){
|
|||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 3
|
responsivePriority: 3
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: lang.syncjobs.order_id,
|
|
||||||
data: 'order_id',
|
|
||||||
defaultContent: '',
|
|
||||||
responsivePriority: 3
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Server',
|
title: 'Server',
|
||||||
data: 'server_w_port',
|
data: 'server_w_port',
|
||||||
@@ -369,7 +362,7 @@ jQuery(function($){
|
|||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.syncjobs.syncjob_last_run_result,
|
title: lang.syncjob_last_run_result,
|
||||||
data: 'exit_status',
|
data: 'exit_status',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
@@ -701,71 +694,11 @@ jQuery(function($){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw_imapsync_source_table() {
|
|
||||||
if ($.fn.DataTable.isDataTable('#imapsync_source_table')) {
|
|
||||||
$('#imapsync_source_table').DataTable().columns.adjust().responsive.recalc();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('#imapsync_source_table').DataTable({
|
|
||||||
responsive: true,
|
|
||||||
processing: true,
|
|
||||||
serverSide: false,
|
|
||||||
stateSave: true,
|
|
||||||
pageLength: pagination_size,
|
|
||||||
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,
|
|
||||||
ajax: {
|
|
||||||
type: "GET",
|
|
||||||
url: "/api/v1/get/syncjob_source/all",
|
|
||||||
dataSrc: function(json) {
|
|
||||||
$.each(json, function(i, item) {
|
|
||||||
item.endpoint = escapeHtml(item.host1 + ':' + item.port1 + ' (' + item.enc1 + ')');
|
|
||||||
item.auth_display = escapeHtml(item.auth_type);
|
|
||||||
if (item.auth_type === 'XOAUTH2') {
|
|
||||||
if (item.oauth_token_expires && item.oauth_token_expires * 1000 > Date.now()) {
|
|
||||||
item.auth_display += ' <span class="badge bg-success">' + lang.syncjobs.source_token_status + ': ' + new Date(item.oauth_token_expires * 1000).toLocaleString() + '</span>';
|
|
||||||
} else if (item.oauth_last_refresh_error) {
|
|
||||||
item.auth_display += ' <span class="badge bg-danger" title="' + escapeHtml(item.oauth_last_refresh_error) + '">' + lang.syncjobs.source_token_error + '</span>';
|
|
||||||
} else {
|
|
||||||
item.auth_display += ' <span class="badge bg-warning">' + lang.waiting + '</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.visibility_display = imapsyncScopeDisplay(item);
|
|
||||||
if (acl_data.syncjobs === 1 && item.can_edit) {
|
|
||||||
item.action = '<div class="btn-group">' +
|
|
||||||
'<a href="/edit/syncjob_source/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
|
||||||
'<a href="#" data-action="delete_selected" data-id="single-syncjob_source" data-api-url="delete/syncjob_source" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
|
||||||
'</div>';
|
|
||||||
} else {
|
|
||||||
item.action = '<span class="text-muted">-</span>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{ title: 'ID', data: 'id', defaultContent: '' },
|
|
||||||
{ title: lang.syncjobs.source_name, data: 'name', defaultContent: '', render: (d) => escapeHtml(d || '') },
|
|
||||||
{ title: lang.syncjobs.source_scope, data: 'visibility_display', defaultContent: '' },
|
|
||||||
{ title: 'Endpoint', data: 'endpoint', defaultContent: '' },
|
|
||||||
{ title: lang.syncjobs.source_auth_type, data: 'auth_display', defaultContent: '' },
|
|
||||||
{ title: lang.active, data: 'active', defaultContent: '',
|
|
||||||
render: function(d) { return d == 1 ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>'; }},
|
|
||||||
{ title: lang.action, data: 'action', defaultContent: '', className: 'dt-text-right' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shared imapsync helpers + delegated listeners live in js/build/013-mailcow.js
|
|
||||||
// (draw_imapsync_source_table stays per-page and calls the global imapsyncScopeDisplay).
|
|
||||||
|
|
||||||
// Load only if the tab is visible
|
// Load only if the tab is visible
|
||||||
onVisible("[id^=tla_table]", () => draw_tla_table());
|
onVisible("[id^=tla_table]", () => draw_tla_table());
|
||||||
onVisible("[id^=bl_policy_mailbox_table]", () => draw_bl_policy_mailbox_table());
|
onVisible("[id^=bl_policy_mailbox_table]", () => draw_bl_policy_mailbox_table());
|
||||||
onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table());
|
onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table());
|
||||||
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
||||||
onVisible("[id^=imapsync_source_table]", () => draw_imapsync_source_table());
|
|
||||||
onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table());
|
onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table());
|
||||||
onVisible("[id^=recent-logins]", () => last_logins('get'));
|
onVisible("[id^=recent-logins]", () => last_logins('get'));
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-80
@@ -317,10 +317,7 @@ if (isset($_GET['query'])) {
|
|||||||
process_add_return(admin('add', $attr));
|
process_add_return(admin('add', $attr));
|
||||||
break;
|
break;
|
||||||
case "syncjob":
|
case "syncjob":
|
||||||
process_add_return(syncjob('add', 'job', $attr));
|
process_add_return(mailbox('add', 'syncjob', $attr));
|
||||||
break;
|
|
||||||
case "syncjob_source":
|
|
||||||
process_add_return(syncjob('add', 'source', $attr));
|
|
||||||
break;
|
break;
|
||||||
case "bcc":
|
case "bcc":
|
||||||
process_add_return(bcc('add', $attr));
|
process_add_return(bcc('add', $attr));
|
||||||
@@ -1049,37 +1046,6 @@ if (isset($_GET['query'])) {
|
|||||||
}
|
}
|
||||||
process_get_return($data);
|
process_get_return($data);
|
||||||
break;
|
break;
|
||||||
case "syncjob_options":
|
|
||||||
// allowlisted imapsync option names, for the custom-params datalist in the UI
|
|
||||||
process_get_return($GLOBALS['IMAPSYNC_OPTIONS']['whitelist']);
|
|
||||||
break;
|
|
||||||
case "syncjob_source":
|
|
||||||
switch ($object) {
|
|
||||||
case "all":
|
|
||||||
$ids = syncjob('get', 'sources');
|
|
||||||
$data = array();
|
|
||||||
if (is_array($ids)) {
|
|
||||||
foreach ($ids as $sid) {
|
|
||||||
$row = syncjob('get', 'source', array('id' => $sid));
|
|
||||||
if ($row) $data[] = $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process_get_return($data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (is_numeric($object)) {
|
|
||||||
$row = syncjob('get', 'source', array('id' => $object));
|
|
||||||
if ($row) {
|
|
||||||
process_get_return($row);
|
|
||||||
} else {
|
|
||||||
echo '{}';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo '{}';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "syncjobs":
|
case "syncjobs":
|
||||||
switch ($object) {
|
switch ($object) {
|
||||||
case "all":
|
case "all":
|
||||||
@@ -1089,14 +1055,14 @@ if (isset($_GET['query'])) {
|
|||||||
$mailboxes = mailbox('get', 'mailboxes', $domain);
|
$mailboxes = mailbox('get', 'mailboxes', $domain);
|
||||||
if (!empty($mailboxes)) {
|
if (!empty($mailboxes)) {
|
||||||
foreach ($mailboxes as $mailbox) {
|
foreach ($mailboxes as $mailbox) {
|
||||||
$syncjobs = syncjob('get', 'jobs', $mailbox);
|
$syncjobs = mailbox('get', 'syncjobs', $mailbox);
|
||||||
if (!empty($syncjobs)) {
|
if (!empty($syncjobs)) {
|
||||||
foreach ($syncjobs as $sj) {
|
foreach ($syncjobs as $syncjob) {
|
||||||
if (isset($extra)) {
|
if (isset($extra)) {
|
||||||
$details = syncjob('get', 'job', $sj, explode(',', $extra));
|
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$details = syncjob('get', 'job', $sj);
|
$details = mailbox('get', 'syncjob_details', $syncjob);
|
||||||
}
|
}
|
||||||
if ($details) {
|
if ($details) {
|
||||||
$data[] = $details;
|
$data[] = $details;
|
||||||
@@ -1117,14 +1083,14 @@ if (isset($_GET['query'])) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$syncjobs = syncjob('get', 'jobs', $object);
|
$syncjobs = mailbox('get', 'syncjobs', $object);
|
||||||
if (!empty($syncjobs)) {
|
if (!empty($syncjobs)) {
|
||||||
foreach ($syncjobs as $sj) {
|
foreach ($syncjobs as $syncjob) {
|
||||||
if (isset($extra)) {
|
if (isset($extra)) {
|
||||||
$details = syncjob('get', 'job', $sj, explode(',', $extra));
|
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$details = syncjob('get', 'job', $sj);
|
$details = mailbox('get', 'syncjob_details', $syncjob);
|
||||||
}
|
}
|
||||||
if ($details) {
|
if ($details) {
|
||||||
$data[] = $details;
|
$data[] = $details;
|
||||||
@@ -1776,10 +1742,7 @@ if (isset($_GET['query'])) {
|
|||||||
process_delete_return(rsettings('delete', array('id' => $items)));
|
process_delete_return(rsettings('delete', array('id' => $items)));
|
||||||
break;
|
break;
|
||||||
case "syncjob":
|
case "syncjob":
|
||||||
process_delete_return(syncjob('delete', 'job', array('id' => $items)));
|
process_delete_return(mailbox('delete', 'syncjob', array('id' => $items)));
|
||||||
break;
|
|
||||||
case "syncjob_source":
|
|
||||||
process_delete_return(syncjob('delete', 'source', array('id' => $items)));
|
|
||||||
break;
|
break;
|
||||||
case "filter":
|
case "filter":
|
||||||
process_delete_return(mailbox('delete', 'filter', array('id' => $items)));
|
process_delete_return(mailbox('delete', 'filter', array('id' => $items)));
|
||||||
@@ -2005,39 +1968,7 @@ if (isset($_GET['query'])) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "syncjob":
|
case "syncjob":
|
||||||
switch ($object) {
|
process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
|
||||||
case "order":
|
|
||||||
process_edit_return(syncjob('edit', 'job_order', array_merge(array('id' => $items), $attr)));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
process_edit_return(syncjob('edit', 'job', array_merge(array('id' => $items), $attr)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "imapsync_settings":
|
|
||||||
$mp = intval($attr['max_parallel'] ?? 0);
|
|
||||||
// UI sends KB/s; store bytes/s (runner + per-job field are bytes/s)
|
|
||||||
$bw = max(0, intval($attr['max_kb_per_second'] ?? 0)) * 1024;
|
|
||||||
if ($mp < 1) {
|
|
||||||
$_SESSION['return'][] = array('type' => 'danger', 'log' => array('imapsync_settings', 'edit', $attr), 'msg' => 'imapsync_max_parallel_invalid');
|
|
||||||
process_edit_return(false);
|
|
||||||
} elseif (imapsync_set_setting('max_parallel', $mp) && imapsync_set_setting('max_bytes_per_second', $bw)) {
|
|
||||||
$_SESSION['return'][] = array('type' => 'success', 'log' => array('imapsync_settings', 'edit'), 'msg' => 'max_parallel_saved');
|
|
||||||
process_edit_return(true);
|
|
||||||
} else {
|
|
||||||
$_SESSION['return'][] = array('type' => 'danger', 'log' => array('imapsync_settings', 'edit'), 'msg' => 'access_denied');
|
|
||||||
process_edit_return(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "syncjob_source":
|
|
||||||
switch ($object) {
|
|
||||||
case "refresh_token":
|
|
||||||
process_edit_return(syncjob('edit', 'refresh_token', array('id' => $items)));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
process_edit_return(syncjob('edit', 'source', array_merge(array('id' => $items), $attr)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "filter":
|
case "filter":
|
||||||
process_edit_return(mailbox('edit', 'filter', array_merge(array('id' => $items), $attr)));
|
process_edit_return(mailbox('edit', 'filter', array_merge(array('id' => $items), $attr)));
|
||||||
|
|||||||
@@ -1,17 +1,5 @@
|
|||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"login_as": "E-poçt qutusu istifadəçisi olaraq daxil ol",
|
"login_as": "E-poçt qutusu istifadəçisi olaraq daxil ol"
|
||||||
"alias_domains": "Alternativ domenlər əlavə et",
|
|
||||||
"app_passwds": "Tətbiq parollarını idarə et",
|
|
||||||
"bcc_maps": "BCC yönləndirmə xəritələri",
|
|
||||||
"delimiter_action": "Ayırıcı əməliyyatı",
|
|
||||||
"domain_desc": "Domen təsvirini dəyiş",
|
|
||||||
"domain_relayhost": "Domen üçün relay serveri dəyiş",
|
|
||||||
"eas_reset": "EAS cihazlarını sıfırla",
|
|
||||||
"extend_sender_acl": "Göndərən ACL-ni xarici ünvanlarla genişləndirməyə icazə ver",
|
|
||||||
"filters": "Filtrlər",
|
|
||||||
"mailbox_relayhost": "E-poçt qutusu üçün relay serveri dəyiş",
|
|
||||||
"prohibited": "ACL tərəfindən məhdudlaşdırılıb",
|
|
||||||
"protocol_access": "Protokol girişini dəyiş"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,16 +44,24 @@
|
|||||||
"app_name": "App-Name",
|
"app_name": "App-Name",
|
||||||
"app_password": "App-Passwort hinzufügen",
|
"app_password": "App-Passwort hinzufügen",
|
||||||
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
|
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
|
||||||
|
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
||||||
"backup_mx_options": "Relay-Optionen",
|
"backup_mx_options": "Relay-Optionen",
|
||||||
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
|
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
|
||||||
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
|
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
|
||||||
|
"custom_params": "Eigene Parameter",
|
||||||
|
"custom_params_hint": "Richtig: --param=xy, falsch: --param xy",
|
||||||
|
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server.",
|
||||||
|
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf dem Quell-Server vorhanden sind.",
|
||||||
|
"delete2duplicates": "Lösche Duplikate im Ziel",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"destination": "Ziel",
|
"destination": "Ziel",
|
||||||
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen",
|
"domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen",
|
||||||
"domain_quota_m": "Domain-Speicherplatz gesamt (MiB)",
|
"domain_quota_m": "Domain-Speicherplatz gesamt (MiB)",
|
||||||
|
"dry": "Synchronisation simulieren",
|
||||||
"enc_method": "Verschlüsselung",
|
"enc_method": "Verschlüsselung",
|
||||||
|
"exclude": "Elemente ausschließen (Regex)",
|
||||||
"full_name": "Vor- und Nachname",
|
"full_name": "Vor- und Nachname",
|
||||||
"gal": "Globales Adressbuch",
|
"gal": "Globales Adressbuch",
|
||||||
"gal_info": "Das globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich! <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>",
|
"gal_info": "Das globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich! <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>",
|
||||||
@@ -72,6 +80,7 @@
|
|||||||
"mailbox_username": "Benutzername (linker Teil der E-Mail-Adresse)",
|
"mailbox_username": "Benutzername (linker Teil der E-Mail-Adresse)",
|
||||||
"max_aliases": "Max. mögliche Aliasse",
|
"max_aliases": "Max. mögliche Aliasse",
|
||||||
"max_mailboxes": "Max. mögliche Mailboxen",
|
"max_mailboxes": "Max. mögliche Mailboxen",
|
||||||
|
"mins_interval": "Abrufintervall (Minuten)",
|
||||||
"multiple_bookings": "Mehrfaches Buchen möglich",
|
"multiple_bookings": "Mehrfaches Buchen möglich",
|
||||||
"nexthop": "Next Hop",
|
"nexthop": "Next Hop",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
@@ -91,9 +100,15 @@
|
|||||||
"select_domain": "Bitte zuerst eine Domain auswählen",
|
"select_domain": "Bitte zuerst eine Domain auswählen",
|
||||||
"sieve_desc": "Kurze Beschreibung",
|
"sieve_desc": "Kurze Beschreibung",
|
||||||
"sieve_type": "Filtertyp",
|
"sieve_type": "Filtertyp",
|
||||||
|
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
|
||||||
|
"subscribeall": "Alle synchronisierten Ordner abonnieren",
|
||||||
|
"syncjob": "Sync-Job hinzufügen",
|
||||||
|
"syncjob_hint": "Passwörter werden unverschlüsselt abgelegt!",
|
||||||
"target_address": "Ziel-Adresse(n)",
|
"target_address": "Ziel-Adresse(n)",
|
||||||
"target_address_info": "<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>",
|
"target_address_info": "<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>",
|
||||||
"target_domain": "Ziel-Domain",
|
"target_domain": "Ziel-Domain",
|
||||||
|
"timeout1": "Timeout für Verbindung zum Remote-Host",
|
||||||
|
"timeout2": "Timeout für Verbindung zum lokalen Host",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"validate": "Validieren",
|
"validate": "Validieren",
|
||||||
"validation_success": "Erfolgreich validiert",
|
"validation_success": "Erfolgreich validiert",
|
||||||
@@ -135,7 +150,7 @@
|
|||||||
"arrival_time": "Ankunftszeit (Serverzeit)",
|
"arrival_time": "Ankunftszeit (Serverzeit)",
|
||||||
"authed_user": "Auth. Benutzer",
|
"authed_user": "Auth. Benutzer",
|
||||||
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
|
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
|
||||||
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels kennzeichnen aktive permanente Sperren durch Denylisting.",
|
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Allowlist-Einträge.",
|
||||||
"change_logo": "Logo ändern",
|
"change_logo": "Logo ändern",
|
||||||
"configuration": "Konfiguration",
|
"configuration": "Konfiguration",
|
||||||
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
||||||
@@ -397,12 +412,7 @@
|
|||||||
"logo_dark_label": "Invertiert für den Darkmode",
|
"logo_dark_label": "Invertiert für den Darkmode",
|
||||||
"logo_normal_label": "Normal",
|
"logo_normal_label": "Normal",
|
||||||
"user_link": "Nutzer-Link",
|
"user_link": "Nutzer-Link",
|
||||||
"filter": "Filter",
|
"filter": "Filter"
|
||||||
"syncjobs": "Sync Jobs",
|
|
||||||
"syncjob_max_parallel": "Max. parallele Sync-Prozesse",
|
|
||||||
"syncjob_max_parallel_info": "Wie viele Sync-Jobs gleichzeitig laufen dürfen.",
|
|
||||||
"syncjob_max_kb_per_second": "Bandbreiten-Limit pro Prozess (KB/s)",
|
|
||||||
"syncjob_max_kb_per_second_info": "0 = unbegrenzt. Kappt jeden Sync-Prozess; ein per-Job-Wert wird hierdurch begrenzt. Gesamt ≈ dieser Wert × parallele Prozesse."
|
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
||||||
@@ -549,24 +559,7 @@
|
|||||||
"template_exists": "Vorlage %s existiert bereits",
|
"template_exists": "Vorlage %s existiert bereits",
|
||||||
"template_id_invalid": "Vorlagen-ID %s ungültig",
|
"template_id_invalid": "Vorlagen-ID %s ungültig",
|
||||||
"template_name_invalid": "Name der Vorlage ungültig",
|
"template_name_invalid": "Name der Vorlage ungültig",
|
||||||
"required_data_missing": "Die benötigten Daten: %s fehlen",
|
"required_data_missing": "Die benötigten Daten: %s fehlen"
|
||||||
"imapsync_source_in_use": "Sync-Quelle %s wird noch von einem Sync-Job verwendet und kann nicht gelöscht werden",
|
|
||||||
"imapsync_source_token_refresh_failed": "OAuth-Token-Erneuerung für %s fehlgeschlagen",
|
|
||||||
"imapsync_source_scope_empty": "Für die gewählte Sichtbarkeit mindestens eine Domain wählen",
|
|
||||||
"imapsync_source_unavailable": "Die gewählte Sync-Quelle ist nicht verfügbar",
|
|
||||||
"imapsync_source_oauth_connect_required": "Vor dem Speichern dieses Sync-Jobs beim Provider anmelden",
|
|
||||||
"imapsync_source_name_invalid": "Ungültiger Name der Sync-Quelle",
|
|
||||||
"imapsync_source_host_invalid": "Ungültiger Host",
|
|
||||||
"imapsync_source_port_invalid": "Ungültiger Port",
|
|
||||||
"imapsync_source_enc_invalid": "Ungültige Verschlüsselungsmethode",
|
|
||||||
"imapsync_source_auth_invalid": "Ungültige Authentifizierungsmethode",
|
|
||||||
"imapsync_custom_param_invalid": "Eigene Parameter enthalten eine nicht erlaubte imapsync-Option",
|
|
||||||
"imapsync_source_oauth_required": "OAuth2 erfordert Token-Endpoint, Client-ID, Client-Secret und Scope",
|
|
||||||
"imapsync_source_oauth_endpoint_invalid": "Ungültige OAuth2-Endpoint-URL",
|
|
||||||
"imapsync_source_oauth_extra_invalid": "OAuth2-Extra-Parameter müssen gültiges JSON sein",
|
|
||||||
"imapsync_source_invalid": "Ungültige Daten der Sync-Quelle",
|
|
||||||
"imapsync_source_oauth_invalid": "Ungültige OAuth2-Konfiguration",
|
|
||||||
"imapsync_max_parallel_invalid": "Anzahl paralleler Prozesse muss mindestens 1 sein"
|
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"collapse_all": "Alle Einklappen",
|
"collapse_all": "Alle Einklappen",
|
||||||
@@ -655,6 +648,7 @@
|
|||||||
"app_name": "App-Name",
|
"app_name": "App-Name",
|
||||||
"app_passwd": "App-Passwörter",
|
"app_passwd": "App-Passwörter",
|
||||||
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
|
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
|
||||||
|
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
||||||
"backup_mx_options": "Relay-Optionen",
|
"backup_mx_options": "Relay-Optionen",
|
||||||
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
|
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
|
||||||
"client_id": "Client-ID",
|
"client_id": "Client-ID",
|
||||||
@@ -662,6 +656,9 @@
|
|||||||
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
|
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
|
||||||
"created_on": "Erstellt am",
|
"created_on": "Erstellt am",
|
||||||
"custom_attributes": "benutzerdefinierte Attribute",
|
"custom_attributes": "benutzerdefinierte Attribute",
|
||||||
|
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
|
||||||
|
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
|
||||||
|
"delete2duplicates": "Lösche Duplikate im Ziel",
|
||||||
"delete_ays": "Soll der Löschvorgang wirklich ausgeführt werden?",
|
"delete_ays": "Soll der Löschvorgang wirklich ausgeführt werden?",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
||||||
@@ -685,6 +682,7 @@
|
|||||||
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
||||||
"edit_alias_domain": "Alias-Domain bearbeiten",
|
"edit_alias_domain": "Alias-Domain bearbeiten",
|
||||||
"encryption": "Verschlüsselung",
|
"encryption": "Verschlüsselung",
|
||||||
|
"exclude": "Elemente ausschließen (Regex)",
|
||||||
"extended_sender_acl": "Externe Absenderadressen",
|
"extended_sender_acl": "Externe Absenderadressen",
|
||||||
"extended_sender_acl_info": "Der DKIM-Domainkey der externen Absenderdomain sollte in diesen Server importiert werden, falls vorhanden.<br>\r\n Wird SPF verwendet, muss diesem Server der Versand gestattet werden.<br>\r\n Wird eine Domain oder Alias-Domain zu diesem Server hinzugefügt, die sich mit der externen Absenderadresse überschneidet, wird der externe Absender hier entfernt.<br>\r\n Ein Eintrag @domain.tld erlaubt den Versand als *@domain.tld",
|
"extended_sender_acl_info": "Der DKIM-Domainkey der externen Absenderdomain sollte in diesen Server importiert werden, falls vorhanden.<br>\r\n Wird SPF verwendet, muss diesem Server der Versand gestattet werden.<br>\r\n Wird eine Domain oder Alias-Domain zu diesem Server hinzugefügt, die sich mit der externen Absenderadresse überschneidet, wird der externe Absender hier entfernt.<br>\r\n Ein Eintrag @domain.tld erlaubt den Versand als *@domain.tld",
|
||||||
"force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
|
"force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
|
||||||
@@ -715,7 +713,10 @@
|
|||||||
"max_aliases": "Max. Aliasse",
|
"max_aliases": "Max. Aliasse",
|
||||||
"max_mailboxes": "Max. Mailboxanzahl",
|
"max_mailboxes": "Max. Mailboxanzahl",
|
||||||
"max_quota": "Max. Größe per Mailbox (MiB)",
|
"max_quota": "Max. Größe per Mailbox (MiB)",
|
||||||
|
"maxage": "Maximales Alter in Tagen einer Nachricht, die kopiert werden soll<br><small>(0 = alle Nachrichten kopieren)</small>",
|
||||||
|
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
|
||||||
"mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.",
|
"mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.",
|
||||||
|
"mins_interval": "Intervall (min)",
|
||||||
"mta_sts": "MTA-STS",
|
"mta_sts": "MTA-STS",
|
||||||
"mta_sts_info": "<a href='https://de.wikipedia.org/wiki/STARTTLS#MTA-STS' target='_blank'>MTA-STS</a> ist ein Standard, der den E-Mail-Versand zwischen Mailservern zwingt, TLS mit gültigen Zertifikaten zu verwenden. <br>Er wird verwendet, wenn <a target='_blank' href='https://de.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> aufgrund fehlender oder nicht unterstützter DNSSEC nicht möglich ist.<br><b>Hinweis</b>: Wenn die empfangende Domain DANE mit DNSSEC unterstützt, wird DANE <b>immer</b> bevorzugt – MTA-STS fungiert nur als Fallback.",
|
"mta_sts_info": "<a href='https://de.wikipedia.org/wiki/STARTTLS#MTA-STS' target='_blank'>MTA-STS</a> ist ein Standard, der den E-Mail-Versand zwischen Mailservern zwingt, TLS mit gültigen Zertifikaten zu verwenden. <br>Er wird verwendet, wenn <a target='_blank' href='https://de.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> aufgrund fehlender oder nicht unterstützter DNSSEC nicht möglich ist.<br><b>Hinweis</b>: Wenn die empfangende Domain DANE mit DNSSEC unterstützt, wird DANE <b>immer</b> bevorzugt – MTA-STS fungiert nur als Fallback.",
|
||||||
"mta_sts_version": "Version",
|
"mta_sts_version": "Version",
|
||||||
@@ -766,6 +767,7 @@
|
|||||||
"sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
|
"sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
|
||||||
"sieve_desc": "Kurze Beschreibung",
|
"sieve_desc": "Kurze Beschreibung",
|
||||||
"sieve_type": "Filtertyp",
|
"sieve_type": "Filtertyp",
|
||||||
|
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
|
||||||
"sogo_access": "Direktes weiterleiten an SOGo",
|
"sogo_access": "Direktes weiterleiten an SOGo",
|
||||||
"sogo_access_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet.",
|
"sogo_access_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet.",
|
||||||
"sogo_visible": "Alias in SOGo sichtbar",
|
"sogo_visible": "Alias in SOGo sichtbar",
|
||||||
@@ -774,8 +776,12 @@
|
|||||||
"spam_filter": "Spamfilter",
|
"spam_filter": "Spamfilter",
|
||||||
"spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten",
|
"spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten",
|
||||||
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
|
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
|
||||||
|
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
|
||||||
|
"syncjob": "Sync-Job bearbeiten",
|
||||||
"target_address": "Ziel-Adresse(n)",
|
"target_address": "Ziel-Adresse(n)",
|
||||||
"target_domain": "Ziel-Domain",
|
"target_domain": "Ziel-Domain",
|
||||||
|
"timeout1": "Timeout für Verbindung zum Remote-Host",
|
||||||
|
"timeout2": "Timeout für Verbindung zum lokalen Host",
|
||||||
"title": "Objekt bearbeiten",
|
"title": "Objekt bearbeiten",
|
||||||
"unchanged_if_empty": "Unverändert, wenn leer",
|
"unchanged_if_empty": "Unverändert, wenn leer",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
@@ -989,6 +995,7 @@
|
|||||||
"spam_aliases": "Spam-Alias",
|
"spam_aliases": "Spam-Alias",
|
||||||
"stats": "Statistik",
|
"stats": "Statistik",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
"sync_jobs": "Synchronisationen",
|
||||||
"table_size": "Tabellengröße",
|
"table_size": "Tabellengröße",
|
||||||
"table_size_show_n": "Zeige %s Einträge",
|
"table_size_show_n": "Zeige %s Einträge",
|
||||||
"target_address": "Ziel-Adresse",
|
"target_address": "Ziel-Adresse",
|
||||||
@@ -1016,8 +1023,17 @@
|
|||||||
"open_logs": "Öffne Logs",
|
"open_logs": "Öffne Logs",
|
||||||
"recipient": "Empfänger",
|
"recipient": "Empfänger",
|
||||||
"sender": "Sender",
|
"sender": "Sender",
|
||||||
|
"syncjob_EX_OK": "Erfolg",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
|
||||||
|
"syncjob_check_log": "Logs überprüfen",
|
||||||
"all_domains": "Alle Domains",
|
"all_domains": "Alle Domains",
|
||||||
"catch_all": "Catch-All",
|
"catch_all": "Catch-All",
|
||||||
|
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
|
||||||
"iam": "Identitätsanbieter"
|
"iam": "Identitätsanbieter"
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
@@ -1191,13 +1207,7 @@
|
|||||||
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
|
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
|
||||||
"verified_totp_login": "TOTP-Anmeldung verifiziert",
|
"verified_totp_login": "TOTP-Anmeldung verifiziert",
|
||||||
"verified_webauthn_login": "WebAuthn-Anmeldung verifiziert",
|
"verified_webauthn_login": "WebAuthn-Anmeldung verifiziert",
|
||||||
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert",
|
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
|
||||||
"imapsync_source_added": "Sync-Quelle %s wurde hinzugefügt",
|
|
||||||
"imapsync_source_modified": "Sync-Quelle %s wurde gespeichert",
|
|
||||||
"imapsync_source_deleted": "Sync-Quelle %s wurde gelöscht",
|
|
||||||
"imapsync_source_token_refreshed": "OAuth-Token für %s wurde erneuert",
|
|
||||||
"max_parallel_saved": "Sync-Job-Einstellungen gespeichert",
|
|
||||||
"imapsync_order_updated": "Sync-Job auf Position %s verschoben"
|
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"authenticators": "Authentikatoren",
|
"authenticators": "Authentikatoren",
|
||||||
@@ -1232,73 +1242,6 @@
|
|||||||
"setup_required": "Ihr Konto erfordert Zwei-Faktor-Authentifizierung. Bitte richten Sie eine 2FA-Methode ein, um fortzufahren.",
|
"setup_required": "Ihr Konto erfordert Zwei-Faktor-Authentifizierung. Bitte richten Sie eine 2FA-Methode ein, um fortzufahren.",
|
||||||
"cancel_setup": "Abbrechen und abmelden"
|
"cancel_setup": "Abbrechen und abmelden"
|
||||||
},
|
},
|
||||||
"syncjobs": {
|
|
||||||
"sources_panel": "Sync-Quellen",
|
|
||||||
"sources_hint": "Sync-Quellen definieren IMAP-Server (mit ihrer Authentifizierungsmethode), die von Sync-Jobs wiederverwendet werden können. Definiere jede Quelle einmal und wähle sie dann beim Anlegen eines Sync-Jobs aus dem Dropdown aus.",
|
|
||||||
"source_add": "Quelle hinzufügen",
|
|
||||||
"source": "Quelle",
|
|
||||||
"source_name": "Name",
|
|
||||||
"source_description": "Beschreibung",
|
|
||||||
"source_scope": "Sichtbarkeit",
|
|
||||||
"source_scope_all": "Alle Nutzer",
|
|
||||||
"source_scope_domain": "Bestimmte Domains",
|
|
||||||
"source_scope_user": "Bestimmte Nutzer",
|
|
||||||
"source_domains": "Domains",
|
|
||||||
"source_users": "Nutzer",
|
|
||||||
"source_select": "Quelle wählen",
|
|
||||||
"source_auth_type": "Authentifizierung",
|
|
||||||
"source_oauth_block": "OAuth2-Konfiguration",
|
|
||||||
"source_token_status": "Token gültig bis",
|
|
||||||
"source_token_error": "Letzter Refresh-Fehler",
|
|
||||||
"source_refresh_now": "Token jetzt erneuern",
|
|
||||||
"source_secret_unchanged_hint": "Leer lassen, um das gespeicherte Secret beizubehalten.",
|
|
||||||
"oauth_source_hint": "Authentifizierung über OAuth2 — kein Passwort erforderlich.",
|
|
||||||
"source_oauth_flow": "OAuth2-Flow",
|
|
||||||
"source_oauth_flow_app": "App (Client Credentials)",
|
|
||||||
"source_oauth_flow_user": "Benutzer-Login (Authorization Code)",
|
|
||||||
"source_authorize_endpoint": "Authorize-Endpoint",
|
|
||||||
"source_userinfo_endpoint": "Userinfo-Endpoint",
|
|
||||||
"source_redirect_uri": "Redirect-URI",
|
|
||||||
"source_redirect_uri_hint": "Diese URI in der Provider-App (z.B. Azure) eintragen.",
|
|
||||||
"syncjob_oauth_connect": "Beim Provider anmelden",
|
|
||||||
"syncjob_oauth_connected": "Verbunden",
|
|
||||||
"sync_jobs": "Sync Jobs",
|
|
||||||
"syncjob_check_log": "Logs überprüfen",
|
|
||||||
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
|
|
||||||
"syncjob_EX_OK": "Erfolg",
|
|
||||||
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
|
||||||
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
|
||||||
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
|
|
||||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
|
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
|
|
||||||
"add": "Sync-Job hinzufügen",
|
|
||||||
"add_hint": "Passwörter werden unverschlüsselt abgelegt!",
|
|
||||||
"edit": "Sync-Job bearbeiten",
|
|
||||||
"create": "Neuen Sync-Job erstellen",
|
|
||||||
"dry": "Synchronisation simulieren",
|
|
||||||
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
|
|
||||||
"mins_interval": "Abrufintervall (Minuten)",
|
|
||||||
"maxage": "Maximales Alter in Tagen einer Nachricht, die kopiert werden soll<br><small>(0 = alle Nachrichten kopieren)</small>",
|
|
||||||
"exclude": "Elemente ausschließen (Regex)",
|
|
||||||
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
|
||||||
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server.",
|
|
||||||
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf dem Quell-Server vorhanden sind.",
|
|
||||||
"delete2duplicates": "Lösche Duplikate im Ziel",
|
|
||||||
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
|
|
||||||
"subscribeall": "Alle synchronisierten Ordner abonnieren",
|
|
||||||
"timeout1": "Timeout für Verbindung zum Remote-Host",
|
|
||||||
"timeout2": "Timeout für Verbindung zum lokalen Host",
|
|
||||||
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
|
|
||||||
"custom_params": "Eigene Parameter",
|
|
||||||
"custom_params_hint": "Pro Zeile eine imapsync-Option. Es werden nur erlaubte Optionen akzeptiert; der Wert darf beliebige Zeichen enthalten (Leerzeichen, Regex, …) und wird als ein einzelnes Argument übergeben.",
|
|
||||||
"custom_param_option": "Option",
|
|
||||||
"custom_param_value": "Wert (optional)",
|
|
||||||
"custom_param_add": "Parameter hinzufügen",
|
|
||||||
"order_id": "Position",
|
|
||||||
"order_id_hint": "Kleinere Position läuft zuerst; Ändern sortiert die globale Warteschlange um.",
|
|
||||||
"set_position": "Position setzen"
|
|
||||||
},
|
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Aktion",
|
"action": "Aktion",
|
||||||
"active": "Aktiv",
|
"active": "Aktiv",
|
||||||
@@ -1329,6 +1272,7 @@
|
|||||||
"clear_recent_successful_connections": "Alle erfolgreichen Verbindungen bereinigen",
|
"clear_recent_successful_connections": "Alle erfolgreichen Verbindungen bereinigen",
|
||||||
"client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen",
|
"client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen",
|
||||||
"create_app_passwd": "Erstelle App-Passwort",
|
"create_app_passwd": "Erstelle App-Passwort",
|
||||||
|
"create_syncjob": "Neuen Sync-Job erstellen",
|
||||||
"created_on": "Erstellt am",
|
"created_on": "Erstellt am",
|
||||||
"daily": "Täglich",
|
"daily": "Täglich",
|
||||||
"day": "Tag",
|
"day": "Tag",
|
||||||
@@ -1436,6 +1380,7 @@
|
|||||||
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
||||||
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
|
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
"sync_jobs": "Sync Jobs",
|
||||||
"tag_handling": "Umgang mit getaggten E-Mails steuern",
|
"tag_handling": "Umgang mit getaggten E-Mails steuern",
|
||||||
"tag_help_example": "Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org",
|
"tag_help_example": "Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org",
|
||||||
"tag_help_explain": "Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt (\"INBOX/Facebook\").<br>\r\nIn Betreff: Der Name des Tags wird dem Betreff angefügt, etwa \"[Facebook] Meine Neuigkeiten\".",
|
"tag_help_explain": "Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt (\"INBOX/Facebook\").<br>\r\nIn Betreff: Der Name des Tags wird dem Betreff angefügt, etwa \"[Facebook] Meine Neuigkeiten\".",
|
||||||
@@ -1460,7 +1405,16 @@
|
|||||||
"with_app_password": "mit App-Passwort",
|
"with_app_password": "mit App-Passwort",
|
||||||
"year": "Jahr",
|
"year": "Jahr",
|
||||||
"years": "Jahren",
|
"years": "Jahren",
|
||||||
|
"syncjob_EX_OK": "Erfolg",
|
||||||
"open_logs": "Öffne Logs",
|
"open_logs": "Öffne Logs",
|
||||||
|
"syncjob_check_log": "Logs überprüfen",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
|
||||||
|
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
|
||||||
"pushover_sound": "Ton"
|
"pushover_sound": "Ton"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@@ -44,16 +44,24 @@
|
|||||||
"app_name": "App name",
|
"app_name": "App name",
|
||||||
"app_password": "Add app password",
|
"app_password": "Add app password",
|
||||||
"app_passwd_protocols": "Allowed protocols for app password",
|
"app_passwd_protocols": "Allowed protocols for app password",
|
||||||
|
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
||||||
"backup_mx_options": "Relay options",
|
"backup_mx_options": "Relay options",
|
||||||
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
|
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
|
||||||
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
|
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
|
||||||
|
"custom_params": "Custom parameters",
|
||||||
|
"custom_params_hint": "Right: --param=xy, wrong: --param xy",
|
||||||
|
"delete1": "Delete from source when completed",
|
||||||
|
"delete2": "Delete messages on destination that are not on source",
|
||||||
|
"delete2duplicates": "Delete duplicates on destination",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"destination": "Destination",
|
"destination": "Destination",
|
||||||
"disable_login": "Disallow login (incoming mail is still accepted)",
|
"disable_login": "Disallow login (incoming mail is still accepted)",
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"domain_matches_hostname": "Domain %s matches hostname",
|
"domain_matches_hostname": "Domain %s matches hostname",
|
||||||
"domain_quota_m": "Total domain quota (MiB)",
|
"domain_quota_m": "Total domain quota (MiB)",
|
||||||
|
"dry": "Simulate synchronization",
|
||||||
"enc_method": "Encryption method",
|
"enc_method": "Encryption method",
|
||||||
|
"exclude": "Exclude objects (regex)",
|
||||||
"full_name": "Full name",
|
"full_name": "Full name",
|
||||||
"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>",
|
||||||
@@ -72,6 +80,7 @@
|
|||||||
"mailbox_username": "Username (left part of an email address)",
|
"mailbox_username": "Username (left part of an email address)",
|
||||||
"max_aliases": "Max. possible aliases",
|
"max_aliases": "Max. possible aliases",
|
||||||
"max_mailboxes": "Max. possible mailboxes",
|
"max_mailboxes": "Max. possible mailboxes",
|
||||||
|
"mins_interval": "Polling interval (minutes)",
|
||||||
"multiple_bookings": "Multiple bookings",
|
"multiple_bookings": "Multiple bookings",
|
||||||
"nexthop": "Next hop",
|
"nexthop": "Next hop",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
@@ -91,10 +100,16 @@
|
|||||||
"select_domain": "Please select a domain first",
|
"select_domain": "Please select a domain first",
|
||||||
"sieve_desc": "Short description",
|
"sieve_desc": "Short description",
|
||||||
"sieve_type": "Filter type",
|
"sieve_type": "Filter type",
|
||||||
|
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
|
||||||
|
"subscribeall": "Subscribe all folders",
|
||||||
|
"syncjob": "Add sync job",
|
||||||
|
"syncjob_hint": "Be aware that passwords need to be saved plain-text!",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"target_address": "Goto addresses",
|
"target_address": "Goto addresses",
|
||||||
"target_address_info": "<small>Full email address/es (comma-separated).</small>",
|
"target_address_info": "<small>Full email address/es (comma-separated).</small>",
|
||||||
"target_domain": "Target domain",
|
"target_domain": "Target domain",
|
||||||
|
"timeout1": "Timeout for connection to remote host",
|
||||||
|
"timeout2": "Timeout for connection to local host",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"validate": "Validate",
|
"validate": "Validate",
|
||||||
"validation_success": "Validated successfully"
|
"validation_success": "Validated successfully"
|
||||||
@@ -397,12 +412,7 @@
|
|||||||
"user_quicklink": "Hide Quicklink to User Login Page",
|
"user_quicklink": "Hide Quicklink to User Login Page",
|
||||||
"validate_license_now": "Validate GUID against license server",
|
"validate_license_now": "Validate GUID against license server",
|
||||||
"verify": "Verify",
|
"verify": "Verify",
|
||||||
"yes": "✓",
|
"yes": "✓"
|
||||||
"syncjobs": "Sync jobs",
|
|
||||||
"syncjob_max_parallel": "Max. parallel sync processes",
|
|
||||||
"syncjob_max_parallel_info": "How many sync jobs may run at the same time.",
|
|
||||||
"syncjob_max_kb_per_second": "Bandwidth limit per process (KB/s)",
|
|
||||||
"syncjob_max_kb_per_second_info": "0 = unlimited. Caps every sync process; a per-job value is capped by this. Total ≈ this × max parallel."
|
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Access denied or invalid form data",
|
"access_denied": "Access denied or invalid form data",
|
||||||
@@ -549,24 +559,7 @@
|
|||||||
"validity_missing": "Please assign a period of validity",
|
"validity_missing": "Please assign a period of validity",
|
||||||
"value_missing": "Please provide all values",
|
"value_missing": "Please provide all values",
|
||||||
"version_invalid": "Version %s is invalid",
|
"version_invalid": "Version %s is invalid",
|
||||||
"yotp_verification_failed": "Yubico OTP verification failed: %s",
|
"yotp_verification_failed": "Yubico OTP verification failed: %s"
|
||||||
"imapsync_source_in_use": "Sync source %s is still used by a sync job and cannot be deleted",
|
|
||||||
"imapsync_source_token_refresh_failed": "OAuth token refresh for %s failed",
|
|
||||||
"imapsync_source_scope_empty": "Select at least one domain for the chosen visibility",
|
|
||||||
"imapsync_source_unavailable": "The selected sync source is unavailable",
|
|
||||||
"imapsync_source_oauth_connect_required": "Sign in with the provider before saving this sync job",
|
|
||||||
"imapsync_source_name_invalid": "Invalid sync source name",
|
|
||||||
"imapsync_source_host_invalid": "Invalid host",
|
|
||||||
"imapsync_source_port_invalid": "Invalid port",
|
|
||||||
"imapsync_source_enc_invalid": "Invalid encryption method",
|
|
||||||
"imapsync_source_auth_invalid": "Invalid authentication method",
|
|
||||||
"imapsync_custom_param_invalid": "Custom parameters contain a disallowed imapsync option",
|
|
||||||
"imapsync_source_oauth_required": "OAuth2 requires token endpoint, client ID, client secret and scope",
|
|
||||||
"imapsync_source_oauth_endpoint_invalid": "Invalid OAuth2 endpoint URL",
|
|
||||||
"imapsync_source_oauth_extra_invalid": "OAuth2 extra parameters must be valid JSON",
|
|
||||||
"imapsync_source_invalid": "Invalid sync source data",
|
|
||||||
"imapsync_source_oauth_invalid": "Invalid OAuth2 configuration",
|
|
||||||
"imapsync_max_parallel_invalid": "Parallel process count must be at least 1"
|
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"collapse_all": "Collapse All",
|
"collapse_all": "Collapse All",
|
||||||
@@ -655,6 +648,7 @@
|
|||||||
"app_name": "App name",
|
"app_name": "App name",
|
||||||
"app_passwd": "App password",
|
"app_passwd": "App password",
|
||||||
"app_passwd_protocols": "Allowed protocols for app password",
|
"app_passwd_protocols": "Allowed protocols for app password",
|
||||||
|
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
||||||
"backup_mx_options": "Relay options",
|
"backup_mx_options": "Relay options",
|
||||||
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
|
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
|
||||||
"client_id": "Client ID",
|
"client_id": "Client ID",
|
||||||
@@ -662,6 +656,9 @@
|
|||||||
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
|
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
|
||||||
"created_on": "Created on",
|
"created_on": "Created on",
|
||||||
"custom_attributes": "Custom attributes",
|
"custom_attributes": "Custom attributes",
|
||||||
|
"delete1": "Delete from source when completed",
|
||||||
|
"delete2": "Delete messages on destination that are not on source",
|
||||||
|
"delete2duplicates": "Delete duplicates on destination",
|
||||||
"delete_ays": "Please confirm the deletion process.",
|
"delete_ays": "Please confirm the deletion process.",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"disable_login": "Disallow login (incoming mail is still accepted)",
|
"disable_login": "Disallow login (incoming mail is still accepted)",
|
||||||
@@ -685,6 +682,7 @@
|
|||||||
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
||||||
"edit_alias_domain": "Edit Alias domain",
|
"edit_alias_domain": "Edit Alias domain",
|
||||||
"encryption": "Encryption",
|
"encryption": "Encryption",
|
||||||
|
"exclude": "Exclude objects (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": "Force password update at next login",
|
||||||
@@ -715,7 +713,10 @@
|
|||||||
"max_aliases": "Max. aliases",
|
"max_aliases": "Max. aliases",
|
||||||
"max_mailboxes": "Max. possible mailboxes",
|
"max_mailboxes": "Max. possible mailboxes",
|
||||||
"max_quota": "Max. quota per mailbox (MiB)",
|
"max_quota": "Max. quota per mailbox (MiB)",
|
||||||
|
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
|
||||||
|
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
|
||||||
"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": "Interval (min)",
|
||||||
"mta_sts": "MTA-STS",
|
"mta_sts": "MTA-STS",
|
||||||
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> is a standard that enforces email delivery between mail servers to use TLS with valid certificates. <br>It is used when <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> is not possible due to missing or unsupported DNSSEC.<br><b>Note</b>: If the receiving domain supports DANE with DNSSEC, DANE is <b>always</b> preferred – MTA-STS only acts as a fallback.",
|
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> is a standard that enforces email delivery between mail servers to use TLS with valid certificates. <br>It is used when <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> is not possible due to missing or unsupported DNSSEC.<br><b>Note</b>: If the receiving domain supports DANE with DNSSEC, DANE is <b>always</b> preferred – MTA-STS only acts as a fallback.",
|
||||||
"mta_sts_version": "Version",
|
"mta_sts_version": "Version",
|
||||||
@@ -767,6 +768,7 @@
|
|||||||
"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": "Short description",
|
||||||
"sieve_type": "Filter type",
|
"sieve_type": "Filter type",
|
||||||
|
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
|
||||||
"sogo_access": "Direct forwarding to SOGo",
|
"sogo_access": "Direct forwarding to SOGo",
|
||||||
"sogo_access_info": "After logging in, the user is automatically redirected to SOGo.",
|
"sogo_access_info": "After logging in, the user is automatically redirected to SOGo.",
|
||||||
"sogo_visible": "Alias is visible in SOGo",
|
"sogo_visible": "Alias is visible in SOGo",
|
||||||
@@ -775,8 +777,12 @@
|
|||||||
"spam_filter": "Spam filter",
|
"spam_filter": "Spam filter",
|
||||||
"spam_policy": "Add or remove items to allow-/denylist",
|
"spam_policy": "Add or remove items to allow-/denylist",
|
||||||
"spam_score": "Set a custom spam score",
|
"spam_score": "Set a custom spam score",
|
||||||
|
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
|
||||||
|
"syncjob": "Edit sync job",
|
||||||
"target_address": "Goto address/es <small>(comma-separated)</small>",
|
"target_address": "Goto address/es <small>(comma-separated)</small>",
|
||||||
"target_domain": "Target domain",
|
"target_domain": "Target domain",
|
||||||
|
"timeout1": "Timeout for connection to remote host",
|
||||||
|
"timeout2": "Timeout for connection to local host",
|
||||||
"title": "Edit object",
|
"title": "Edit object",
|
||||||
"unchanged_if_empty": "If unchanged leave blank",
|
"unchanged_if_empty": "If unchanged leave blank",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
@@ -997,6 +1003,16 @@
|
|||||||
"spam_aliases": "Temp. alias",
|
"spam_aliases": "Temp. alias",
|
||||||
"stats": "Statistics",
|
"stats": "Statistics",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
"sync_jobs": "Sync jobs",
|
||||||
|
"syncjob_check_log": "Check log",
|
||||||
|
"syncjob_last_run_result": "Last run result",
|
||||||
|
"syncjob_EX_OK": "Success",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
|
||||||
"table_size": "Table size",
|
"table_size": "Table size",
|
||||||
"table_size_show_n": "Show %s items",
|
"table_size_show_n": "Show %s items",
|
||||||
"target_address": "Goto address",
|
"target_address": "Goto address",
|
||||||
@@ -1198,13 +1214,7 @@
|
|||||||
"verified_fido2_login": "Verified FIDO2 login",
|
"verified_fido2_login": "Verified FIDO2 login",
|
||||||
"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"
|
||||||
"imapsync_source_added": "Sync source %s has been added",
|
|
||||||
"imapsync_source_modified": "Sync source %s has been saved",
|
|
||||||
"imapsync_source_deleted": "Sync source %s has been deleted",
|
|
||||||
"imapsync_source_token_refreshed": "OAuth token for %s has been refreshed",
|
|
||||||
"max_parallel_saved": "Sync job settings saved",
|
|
||||||
"imapsync_order_updated": "Sync job moved to position %s"
|
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"authenticators": "Authenticators",
|
"authenticators": "Authenticators",
|
||||||
@@ -1239,73 +1249,6 @@
|
|||||||
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
||||||
"yubi_otp": "Yubico OTP authentication"
|
"yubi_otp": "Yubico OTP authentication"
|
||||||
},
|
},
|
||||||
"syncjobs": {
|
|
||||||
"sources_panel": "Sync Sources",
|
|
||||||
"sources_hint": "Sync sources define IMAP servers (and their authentication method) that sync jobs can reuse. Define each source once, then pick it from the dropdown when creating sync jobs.",
|
|
||||||
"source_add": "Add source",
|
|
||||||
"source": "Source",
|
|
||||||
"source_name": "Name",
|
|
||||||
"source_description": "Description",
|
|
||||||
"source_scope": "Visibility",
|
|
||||||
"source_scope_all": "All users",
|
|
||||||
"source_scope_domain": "Specific domains",
|
|
||||||
"source_scope_user": "Specific users",
|
|
||||||
"source_domains": "Domains",
|
|
||||||
"source_users": "Users",
|
|
||||||
"source_select": "Select source",
|
|
||||||
"source_auth_type": "Authentication",
|
|
||||||
"source_oauth_block": "OAuth2 configuration",
|
|
||||||
"source_token_status": "Token valid until",
|
|
||||||
"source_token_error": "Last refresh error",
|
|
||||||
"source_refresh_now": "Refresh token now",
|
|
||||||
"source_secret_unchanged_hint": "Leave blank to keep the stored secret unchanged.",
|
|
||||||
"oauth_source_hint": "Authentication via OAuth2 — no password required.",
|
|
||||||
"source_oauth_flow": "OAuth2 flow",
|
|
||||||
"source_oauth_flow_app": "App (client credentials)",
|
|
||||||
"source_oauth_flow_user": "User login (authorization code)",
|
|
||||||
"source_authorize_endpoint": "Authorize endpoint",
|
|
||||||
"source_userinfo_endpoint": "Userinfo endpoint",
|
|
||||||
"source_redirect_uri": "Redirect URI",
|
|
||||||
"source_redirect_uri_hint": "Register this URI in the provider app (e.g. Azure).",
|
|
||||||
"syncjob_oauth_connect": "Sign in with the provider",
|
|
||||||
"syncjob_oauth_connected": "Connected",
|
|
||||||
"sync_jobs": "Sync jobs",
|
|
||||||
"syncjob_check_log": "Check log",
|
|
||||||
"syncjob_last_run_result": "Last run result",
|
|
||||||
"syncjob_EX_OK": "Success",
|
|
||||||
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
|
|
||||||
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
|
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
|
|
||||||
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
|
|
||||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
|
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
|
|
||||||
"add": "Add sync job",
|
|
||||||
"add_hint": "Be aware that passwords need to be saved plain-text!",
|
|
||||||
"edit": "Edit sync job",
|
|
||||||
"create": "Create new sync job",
|
|
||||||
"dry": "Simulate synchronization",
|
|
||||||
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
|
|
||||||
"mins_interval": "Polling interval (minutes)",
|
|
||||||
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
|
|
||||||
"exclude": "Exclude objects (regex)",
|
|
||||||
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
|
|
||||||
"delete1": "Delete from source when completed",
|
|
||||||
"delete2": "Delete messages on destination that are not on source",
|
|
||||||
"delete2duplicates": "Delete duplicates on destination",
|
|
||||||
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
|
|
||||||
"subscribeall": "Subscribe all folders",
|
|
||||||
"timeout1": "Timeout for connection to remote host",
|
|
||||||
"timeout2": "Timeout for connection to local host",
|
|
||||||
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
|
|
||||||
"custom_params": "Custom parameters",
|
|
||||||
"custom_params_hint": "Add one imapsync option per row. Only allowlisted options are accepted; the value may contain any character (spaces, regex, …) and is passed as a single argument.",
|
|
||||||
"custom_param_option": "Option",
|
|
||||||
"custom_param_value": "Value (optional)",
|
|
||||||
"custom_param_add": "Add parameter",
|
|
||||||
"order_id": "Position",
|
|
||||||
"order_id_hint": "Lower position runs first; setting it re-sorts the global queue.",
|
|
||||||
"set_position": "Set position"
|
|
||||||
},
|
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
@@ -1336,6 +1279,7 @@
|
|||||||
"clear_recent_successful_connections": "Clear seen successful connections",
|
"clear_recent_successful_connections": "Clear seen successful connections",
|
||||||
"client_configuration": "Show configuration guides for email clients and smartphones",
|
"client_configuration": "Show configuration guides for email clients and smartphones",
|
||||||
"create_app_passwd": "Create app password",
|
"create_app_passwd": "Create app password",
|
||||||
|
"create_syncjob": "Create new sync job",
|
||||||
"created_on": "Created on",
|
"created_on": "Created on",
|
||||||
"daily": "Daily",
|
"daily": "Daily",
|
||||||
"day": "day",
|
"day": "day",
|
||||||
@@ -1445,6 +1389,16 @@
|
|||||||
"spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
"spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
||||||
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
|
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
"sync_jobs": "Sync jobs",
|
||||||
|
"syncjob_check_log": "Check log",
|
||||||
|
"syncjob_last_run_result": "Last run result",
|
||||||
|
"syncjob_EX_OK": "Success",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
|
||||||
"tag_handling": "Set handling for tagged mail",
|
"tag_handling": "Set handling for tagged mail",
|
||||||
"tag_help_example": "Example for a tagged email address: me<b>+Facebook</b>@example.org",
|
"tag_help_example": "Example for a tagged email address: me<b>+Facebook</b>@example.org",
|
||||||
"tag_help_explain": "In subfolder: a new subfolder named after the tag will be created below INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: the tags name will be prepended to the mails subject, example: \"[Facebook] My News\".",
|
"tag_help_explain": "In subfolder: a new subfolder named after the tag will be created below INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: the tags name will be prepended to the mails subject, example: \"[Facebook] My News\".",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"domain_relayhost": "Mainīt domēna relayhost",
|
"domain_relayhost": "Mainīt domēna relayhost",
|
||||||
"eas_reset": "EAS ierīču atiestatīšana",
|
"eas_reset": "EAS ierīču atiestatīšana",
|
||||||
"extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm",
|
"extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm",
|
||||||
"login_as": "Pieteikties kā pastkastes lietotājam",
|
"login_as": "Pieteikšanās kā pastkastes lietotājam",
|
||||||
"mailbox_relayhost": "Pasta kastītes relayhost maiņa",
|
"mailbox_relayhost": "Pasta kastītes relayhost maiņa",
|
||||||
"prohibited": "Aizliegts ar ACL",
|
"prohibited": "Aizliegts ar ACL",
|
||||||
"protocol_access": "Protokola piekļuves maiņa",
|
"protocol_access": "Protokola piekļuves maiņa",
|
||||||
@@ -91,12 +91,12 @@
|
|||||||
"activate_api": "Aktivizēt API",
|
"activate_api": "Aktivizēt API",
|
||||||
"active": "Aktīvs",
|
"active": "Aktīvs",
|
||||||
"add": "Pievienot",
|
"add": "Pievienot",
|
||||||
"add_domain_admin": "Pievienot domēna pārvaldītāju",
|
"add_domain_admin": "Pievienot domēna administratoru",
|
||||||
"add_forwarding_host": "Pievienot pāradresācijas hostu",
|
"add_forwarding_host": "Pievienot pāradresācijas hostu",
|
||||||
"add_relayhost": "Pievienot Relayhost",
|
"add_relayhost": "Pievienot Relayhost",
|
||||||
"add_row": "Pievienot rindu",
|
"add_row": "Pievienot rindu",
|
||||||
"admin": "Pārvaldītājs",
|
"admin": "Administrators",
|
||||||
"admin_details": "Labot informāciju par pārvaldītāju",
|
"admin_details": "Labot administratora detaļas",
|
||||||
"admin_domains": "Domēna uzdevumi",
|
"admin_domains": "Domēna uzdevumi",
|
||||||
"api_allow_from": "Atļaut API piekļuvi no šīm IP",
|
"api_allow_from": "Atļaut API piekļuvi no šīm IP",
|
||||||
"api_key": "API atslēga",
|
"api_key": "API atslēga",
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
"dkim_keys": "ARC/DKIM atslēgas",
|
"dkim_keys": "ARC/DKIM atslēgas",
|
||||||
"dkim_private_key": "Privāta atslēga",
|
"dkim_private_key": "Privāta atslēga",
|
||||||
"domain": "Domēns",
|
"domain": "Domēns",
|
||||||
"domain_admins": "Domēna pārvaldītāji",
|
"domain_admins": "Domēna administratori",
|
||||||
"edit": "Labot",
|
"edit": "Labot",
|
||||||
"empty": "Nav iznākuma",
|
"empty": "Nav iznākuma",
|
||||||
"f2b_ban_time": "Aizlieguma laiks (s)",
|
"f2b_ban_time": "Aizlieguma laiks (s)",
|
||||||
@@ -188,14 +188,7 @@
|
|||||||
"quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:<br><small>Noklusējums ir 9999.0</small>",
|
"quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:<br><small>Noklusējums ir 9999.0</small>",
|
||||||
"options": "Iespējas",
|
"options": "Iespējas",
|
||||||
"password_reset_settings": "Paroļu atkopes iestatījumi",
|
"password_reset_settings": "Paroļu atkopes iestatījumi",
|
||||||
"password_settings": "Paroļu iestatījumi",
|
"password_settings": "Paroļu iestatījumi"
|
||||||
"add_admin": "Pievienot pārvaldītāju",
|
|
||||||
"admins": "Pārvaldītāji",
|
|
||||||
"admins_ldap": "LDAP pārvaldītāji",
|
|
||||||
"admin_quicklink": "Paslēpt ātro saiti uz pārvaldītāju pieteikšanās lapu",
|
|
||||||
"domain_admin": "Domēna pārvaldītājs",
|
|
||||||
"domainadmin_quicklink": "Paslēpt ātro saiti uz domēna pārvaldītāju pieteikšanās lapu",
|
|
||||||
"user_quicklink": "Paslēpt ātro saiti uz lietotāju pieteikšanās lapu"
|
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Piekļuve liegta, vai nepareizi dati",
|
"access_denied": "Piekļuve liegta, vai nepareizi dati",
|
||||||
@@ -251,10 +244,7 @@
|
|||||||
"app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs",
|
"app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs",
|
||||||
"img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu",
|
"img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu",
|
||||||
"img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu",
|
"img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu",
|
||||||
"version_invalid": "Versija %s ir nederīga",
|
"version_invalid": "Versija %s ir nederīga"
|
||||||
"generic_server_error": "Atgadījās neparedzēta servera kļūda. Lūgums sazināties ar pārvaldītāju.",
|
|
||||||
"password_reset_na": "Paroļu atkope šobrīd nav pieejama. Lūgums sazināties ar pārvaldītāju.",
|
|
||||||
"recovery_email_failed": "Nevarēja nosūtīt atkopes e-pasta ziņojumu. Lūgums sazināties ar pārvaldītāju."
|
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.",
|
"cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.",
|
||||||
@@ -276,7 +266,7 @@
|
|||||||
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
|
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
|
||||||
"description": "Apraksts",
|
"description": "Apraksts",
|
||||||
"domain": "Labot domēnu",
|
"domain": "Labot domēnu",
|
||||||
"domain_admin": "Labot domēna pārvaldītāju",
|
"domain_admin": "Labot domēna administratoru",
|
||||||
"domain_quota": "Domēna kvota",
|
"domain_quota": "Domēna kvota",
|
||||||
"domains": "Domēni",
|
"domains": "Domēni",
|
||||||
"dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)",
|
"dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)",
|
||||||
@@ -339,8 +329,7 @@
|
|||||||
"app_passwd": "Lietotnes parole",
|
"app_passwd": "Lietotnes parole",
|
||||||
"mta_sts_version": "Versija",
|
"mta_sts_version": "Versija",
|
||||||
"mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai <code>STSv1</code>.",
|
"mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai <code>STSv1</code>.",
|
||||||
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>",
|
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>"
|
||||||
"admin": "Labot pārvaldītāju"
|
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Atcelt",
|
"cancel": "Atcelt",
|
||||||
@@ -373,14 +362,7 @@
|
|||||||
"username": "Lietotājvārds",
|
"username": "Lietotājvārds",
|
||||||
"fido2_webauthn": "FIDO/WebAuthn pieteikšanās",
|
"fido2_webauthn": "FIDO/WebAuthn pieteikšanās",
|
||||||
"mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.",
|
"mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.",
|
||||||
"other_logins": "vai pieteikties ar",
|
"other_logins": "vai pieslēgties ar"
|
||||||
"forgot_password": "> Aizmirsta parole?",
|
|
||||||
"login_linkstext": "Nepareiza pieteikšanās?",
|
|
||||||
"login_domainadmintext": "Pieteikties kā domēna pārvaldītājam",
|
|
||||||
"login_admintext": "Pieteikties kā pārvaldītājam",
|
|
||||||
"login_user": "Lietotu pieteikšanās",
|
|
||||||
"login_dadmin": "Domēna pārvaldītāju pieteikšanās",
|
|
||||||
"login_admin": "Pārvaldītāju pieteikšanās"
|
|
||||||
},
|
},
|
||||||
"mailbox": {
|
"mailbox": {
|
||||||
"action": "Rīcība",
|
"action": "Rīcība",
|
||||||
@@ -415,7 +397,7 @@
|
|||||||
"description": "Apraksts",
|
"description": "Apraksts",
|
||||||
"dkim_key_length": "DKIM atslēgas garums (bits)",
|
"dkim_key_length": "DKIM atslēgas garums (bits)",
|
||||||
"domain": "Domēns",
|
"domain": "Domēns",
|
||||||
"domain_admins": "Domēna pārvaldītāji",
|
"domain_admins": "Domēna administratori",
|
||||||
"domain_aliases": "Domēna aizstājvārdi",
|
"domain_aliases": "Domēna aizstājvārdi",
|
||||||
"domain_quota": "Kvota",
|
"domain_quota": "Kvota",
|
||||||
"domain_quota_total": "Kopējais domēna ierobežojums",
|
"domain_quota_total": "Kopējais domēna ierobežojums",
|
||||||
@@ -525,7 +507,7 @@
|
|||||||
"imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.<br>\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu."
|
"imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.<br>\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"admin_modified": "Pārvaldītāja izmaiņas tika saglabātas",
|
"admin_modified": "Izmaiņas administrātoram ir saglabātas",
|
||||||
"alias_added": "Aizstājadrese %s (%d) tika pievienota",
|
"alias_added": "Aizstājadrese %s (%d) tika pievienota",
|
||||||
"alias_domain_removed": "Aizstājdomēns %s tika noņemts",
|
"alias_domain_removed": "Aizstājdomēns %s tika noņemts",
|
||||||
"alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas",
|
"alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas",
|
||||||
@@ -536,9 +518,9 @@
|
|||||||
"dkim_added": "DKIM atslēga saglabāta",
|
"dkim_added": "DKIM atslēga saglabāta",
|
||||||
"dkim_removed": "DKIM atslēga %s ir noņemta",
|
"dkim_removed": "DKIM atslēga %s ir noņemta",
|
||||||
"domain_added": "Pievienots domēns %s",
|
"domain_added": "Pievienots domēns %s",
|
||||||
"domain_admin_added": "Tika pievienots domēna pārvaldītājs %s",
|
"domain_admin_added": "Domēna administrātors %s pievienots",
|
||||||
"domain_admin_modified": "Domēna pārvaldītāja %s izmaiņas tika saglabātas",
|
"domain_admin_modified": "Izmaiņas domēna administrātoram %s ir saglabātas",
|
||||||
"domain_admin_removed": "Tika noņemts domēna pārvaldītājs %s",
|
"domain_admin_removed": "Domēna administrators %s tika noņemts",
|
||||||
"domain_modified": "Izmaiņas domēnam %s ir saglabātas",
|
"domain_modified": "Izmaiņas domēnam %s ir saglabātas",
|
||||||
"domain_removed": "Domēns %s ir noņemts",
|
"domain_removed": "Domēns %s ir noņemts",
|
||||||
"eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas",
|
"eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas",
|
||||||
@@ -566,9 +548,7 @@
|
|||||||
"verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās",
|
"verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās",
|
||||||
"app_passwd_removed": "Noņemta lietotnes parole ar Id %s",
|
"app_passwd_removed": "Noņemta lietotnes parole ar Id %s",
|
||||||
"app_passwd_added": "Pievienota jauna lietotnes parole",
|
"app_passwd_added": "Pievienota jauna lietotnes parole",
|
||||||
"f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts.",
|
"f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts."
|
||||||
"admin_added": "Tika pievienots pārvaldītājs %s",
|
|
||||||
"admin_removed": "Tika noņemts pārvaldītājs %s"
|
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
|
"api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
|
||||||
@@ -589,8 +569,7 @@
|
|||||||
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
|
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
|
||||||
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.",
|
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.",
|
||||||
"yubi_otp": "Yubico OTP autentifikators",
|
"yubi_otp": "Yubico OTP autentifikators",
|
||||||
"authenticators": "Autentificētāji",
|
"authenticators": "Autentificētāji"
|
||||||
"u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu."
|
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Rīcība",
|
"action": "Rīcība",
|
||||||
@@ -722,8 +701,7 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.",
|
"domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.",
|
||||||
"dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus",
|
"dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus",
|
||||||
"is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais",
|
"is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais"
|
||||||
"no_active_admin": "Nevar deaktivēt pēdējo aktīvo pārvaldītāju"
|
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2."
|
"access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2."
|
||||||
|
|||||||
@@ -27,8 +27,7 @@
|
|||||||
"tls_policy": "Versleutelingsbeleid",
|
"tls_policy": "Versleutelingsbeleid",
|
||||||
"unlimited_quota": "Onbeperkte quota voor mailboxen",
|
"unlimited_quota": "Onbeperkte quota voor mailboxen",
|
||||||
"domain_desc": "Wijzig domeinbeschrijving",
|
"domain_desc": "Wijzig domeinbeschrijving",
|
||||||
"pw_reset": "Toegang om mailcow gebruikers wachtwoord te resetten",
|
"pw_reset": "Toegang om mailcow gebruikers wachtwoord te resetten"
|
||||||
"domain_relayhost": "Verander relayhost voor een domein"
|
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"activate_filter_warn": "Alle andere filters worden gedeactiveerd zolang deze geactiveerd is.",
|
"activate_filter_warn": "Alle andere filters worden gedeactiveerd zolang deze geactiveerd is.",
|
||||||
|
|||||||
@@ -189,7 +189,7 @@
|
|||||||
"api_info": "API jest w trakcie prac. Dokumentację można znaleźć pod adresem <a href=\"/api\">/api</a>",
|
"api_info": "API jest w trakcie prac. Dokumentację można znaleźć pod adresem <a href=\"/api\">/api</a>",
|
||||||
"api_key": "klucz API",
|
"api_key": "klucz API",
|
||||||
"api_read_only": "Dostęp tylko do odczytu",
|
"api_read_only": "Dostęp tylko do odczytu",
|
||||||
"api_read_write": "Dostęp do odczytu i zapisu",
|
"api_read_write": "Dostęp tylko do odczytu",
|
||||||
"api_skip_ip_check": "Pomiń sprawdzenie IP dla API",
|
"api_skip_ip_check": "Pomiń sprawdzenie IP dla API",
|
||||||
"app_hide": "Ukryj dla logowania",
|
"app_hide": "Ukryj dla logowania",
|
||||||
"app_links": "Linki aplikacji",
|
"app_links": "Linki aplikacji",
|
||||||
@@ -1226,7 +1226,7 @@
|
|||||||
"decimal": ".",
|
"decimal": ".",
|
||||||
"emptyTable": "Brak danych w tabeli",
|
"emptyTable": "Brak danych w tabeli",
|
||||||
"expand_all": "Rozszerz wszystko",
|
"expand_all": "Rozszerz wszystko",
|
||||||
"info": "Wyświetlanie od _START_ do _END_ z _TOTAL_ wpisów",
|
"info": "Wyświetlanie od START do END z TOTAL wpisów",
|
||||||
"infoEmpty": "Wyświetlanie od 0 do 0 z 0 wpisów",
|
"infoEmpty": "Wyświetlanie od 0 do 0 z 0 wpisów",
|
||||||
"infoFiltered": "(filtrowane z _MAX_ suma wpisów)",
|
"infoFiltered": "(filtrowane z _MAX_ suma wpisów)",
|
||||||
"thousands": ",",
|
"thousands": ",",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"add_domain_only": "Adicionar somente domínio",
|
"add_domain_only": "Adicionar somente domínio",
|
||||||
"add_domain_restart": "Adicionar domínio e reiniciar o SoGo",
|
"add_domain_restart": "Adicionar domínio e reiniciar o SoGo",
|
||||||
"alias_address": "Endereço (s) de alias",
|
"alias_address": "Endereço (s) de alias",
|
||||||
"alias_address_info": "<small>Endereço(s) de e-mail completo(s) ou @example.com, para capturar todas as mensagens de um domínio (separadas por vírgula). <b>Apenas domínios do Mailcow</b>.</small>",
|
"alias_address_info": "<small>Endereço/s de e-mail completo ou @example .com, para capturar todas as mensagens de um domínio (separadas por vírgula). <b> somente domínios mailcow</b>.</small>",
|
||||||
"alias_domain": "Domínio de alias",
|
"alias_domain": "Domínio de alias",
|
||||||
"alias_domain_info": "<small>Somente nomes de domínio válidos (separados por vírgula).</small>",
|
"alias_domain_info": "<small>Somente nomes de domínio válidos (separados por vírgula).</small>",
|
||||||
"app_name": "Nome do aplicativo",
|
"app_name": "Nome do aplicativo",
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
"logo_dark_label": "Invertido para o modo escuro",
|
"logo_dark_label": "Invertido para o modo escuro",
|
||||||
"configuration": "Configuração",
|
"configuration": "Configuração",
|
||||||
"convert_html_to_text": "Converter HTML em texto sem formatação",
|
"convert_html_to_text": "Converter HTML em texto sem formatação",
|
||||||
"copy_to_clipboard": "Copiado para a área de transferência!",
|
"copy_to_clipboard": "Text copied to clipboard!",
|
||||||
"cors_settings": "Configurações do CORS",
|
"cors_settings": "Configurações do CORS",
|
||||||
"credentials_transport_warning": "<b>Aviso</b>: Adicionar uma nova entrada no mapa de transporte atualizará as credenciais de todas as entradas com uma coluna correspondente do próximo salto.",
|
"credentials_transport_warning": "<b>Aviso</b>: Adicionar uma nova entrada no mapa de transporte atualizará as credenciais de todas as entradas com uma coluna correspondente do próximo salto.",
|
||||||
"customer_id": "ID do cliente",
|
"customer_id": "ID do cliente",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"includes": "Inclua esses destinatários",
|
"includes": "Inclua esses destinatários",
|
||||||
"ip_check": "Verificação de IP",
|
"ip_check": "Verificação de IP",
|
||||||
"ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em <br><strong>Sistema > Configuração > Opções > Personalizar</strong>",
|
"ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em <br><strong>Sistema > Configuração > Opções > Personalizar</strong>",
|
||||||
"ip_check_opt_in": "Opte por usar o serviço de terceiros <strong>ipv4.mailcow.email</strong> e <strong>ipv6.mailcow.email</strong> para resolver endereços IP externos.",
|
"ip_check_opt_in": "Opte por usar o serviço de terceiros <strong>ipv4.mailcow.email.</strong> e <strong>ipv6.mailcow.email</strong> para resolver endereços IP externos.",
|
||||||
"is_mx_based": "Baseado em MX",
|
"is_mx_based": "Baseado em MX",
|
||||||
"last_applied": "Aplicado pela última vez",
|
"last_applied": "Aplicado pela última vez",
|
||||||
"license_info": "Uma licença não é necessária, mas ajuda no desenvolvimento.<br><a href=\"https://www.servercow.de/mailcow? Lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registre seu GUID aqui</a> ou <a href=\"https://www.servercow.de/mailcow? Lang=en#support\" target=\"_blank\" alt=\"Support order\">comprar suporte para sua instalação de mailcow.</a>",
|
"license_info": "Uma licença não é necessária, mas ajuda no desenvolvimento.<br><a href=\"https://www.servercow.de/mailcow? Lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registre seu GUID aqui</a> ou <a href=\"https://www.servercow.de/mailcow? Lang=en#support\" target=\"_blank\" alt=\"Support order\">comprar suporte para sua instalação de mailcow.</a>",
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"oauth2_add_client": "Adicionar cliente OAuth2",
|
"oauth2_add_client": "Adicionar cliente OAuth2",
|
||||||
"oauth2_client_id": "ID do cliente",
|
"oauth2_client_id": "ID do cliente",
|
||||||
"oauth2_client_secret": "Segredo do cliente",
|
"oauth2_client_secret": "Segredo do cliente",
|
||||||
"oauth2_info": "A implementação do OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.<br>\nO servidor também emite automaticamente novos tokens de atualização após um token de atualização ter sido usado.<br>\nO escopo padrão é <i>profile</i>. Somente usuários de caixa de correio podem ser autenticados com o OAuth2. Se o parâmetro de escopo for omitido, o padrão será <i>profile</i>.<br>\nO parâmetro <i>state</i> deve ser enviado pelo cliente como parte da solicitação de autorização.<br><br>\nCaminhos para solicitações à API OAuth2: <br>\n\n<ul>\n <li>Ponto de extremidade de autorização: <code>/oauth/authorize</code></li>\n <li>Endpoint do token: <code>/oauth/token</code></li>\n <li>Página de recursos: <code>/oauth/profile</code></li>\n</ul>\n\nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas impedirá a renovação do token.<br><br>\nA revogação dos tokens de cliente causará o encerramento imediato de todas as sessões ativas. Todos os clientes precisarão se autenticar novamente.",
|
"oauth2_info": "A implementação OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.<br>\nO servidor também emite automaticamente novos tokens de atualização, depois que um token de atualização foi usado.<br><br>\n• O escopo padrão é <i>perfil</i>. Somente usuários com caixa de e-mail podem ser autenticados contra o OAuth2. Se o parâmetro de escopo for omitido, ele voltará para <i>perfil</i>.<br>\nCaminhos para solicitações OAuth2 API: <br>\n<ul>\n<li>Endpoint de autorização: <code>/oauth/authorize</code></li>\n<li>Endpoint token: <code>/oauth/token</code></li>\n<li>Página de recursos: <code>/oauth/profile</code></li>\n</ul>\nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas eles não renovarão seu token.<br><br>\nA revogação dos tokens do cliente causará o término imediato de todas as sessões ativas. Todos os clientes precisam se autenticar novamente.",
|
||||||
"oauth2_redirect_uri": "URI de redirecionamento",
|
"oauth2_redirect_uri": "URI de redirecionamento",
|
||||||
"oauth2_renew_secret": "Gere um novo segredo de cliente",
|
"oauth2_renew_secret": "Gere um novo segredo de cliente",
|
||||||
"oauth2_revoke_tokens": "Revogar todos os tokens do cliente",
|
"oauth2_revoke_tokens": "Revogar todos os tokens do cliente",
|
||||||
@@ -730,7 +730,7 @@
|
|||||||
"pushover_verify": "Verifique as credenciais",
|
"pushover_verify": "Verifique as credenciais",
|
||||||
"quota_mb": "Cota (MiB)",
|
"quota_mb": "Cota (MiB)",
|
||||||
"quota_warning_bcc": "Aviso de cota BCC",
|
"quota_warning_bcc": "Aviso de cota BCC",
|
||||||
"quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será precedido pelo nome de usuário correspondente entre colchetes, por exemplo: <code>Aviso de cota (user@example.com)</code>.",
|
"quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será sufixado pelo nome de usuário correspondente entre colchetes, por exemplo: <code>Aviso de cota (</code>user@example.com).",
|
||||||
"ratelimit": "Limite de taxa",
|
"ratelimit": "Limite de taxa",
|
||||||
"redirect_uri": "URL de redirecionamento/retorno de chamada",
|
"redirect_uri": "URL de redirecionamento/retorno de chamada",
|
||||||
"relay_all": "Retransmita todos os destinatários",
|
"relay_all": "Retransmita todos os destinatários",
|
||||||
@@ -759,7 +759,7 @@
|
|||||||
"spam_score": "Defina uma pontuação de spam personalizada",
|
"spam_score": "Defina uma pontuação de spam personalizada",
|
||||||
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
|
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
|
||||||
"syncjob": "Editar tarefa de sincronização",
|
"syncjob": "Editar tarefa de sincronização",
|
||||||
"target_address": "Ir para o(s) endereço(s) <small>(separados por vírgula)</small>",
|
"target_address": "<small>Ir para endereço/es (separados por vírgula)</small>",
|
||||||
"target_domain": "Domínio de destino",
|
"target_domain": "Domínio de destino",
|
||||||
"timeout1": "Tempo limite para conexão com o host remoto",
|
"timeout1": "Tempo limite para conexão com o host remoto",
|
||||||
"timeout2": "Tempo limite para conexão com o host local",
|
"timeout2": "Tempo limite para conexão com o host local",
|
||||||
@@ -807,7 +807,7 @@
|
|||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"confirm_delete": "Confirme a exclusão",
|
"confirm_delete": "Confirme a exclusão",
|
||||||
"delete_now": "Excluir agora",
|
"delete_now": "Excluir agora",
|
||||||
"delete_these_items": "Por favor, confirme as alterações feitas no seguinte ID de objeto.",
|
"delete_these_items": "Confirme suas alterações no seguinte ID de objeto",
|
||||||
"hibp_check": "Verifique em haveibeenpwned.com",
|
"hibp_check": "Verifique em haveibeenpwned.com",
|
||||||
"hibp_nok": "Combinado! Essa é uma senha potencialmente perigosa!",
|
"hibp_nok": "Combinado! Essa é uma senha potencialmente perigosa!",
|
||||||
"hibp_ok": "Nenhuma combinação encontrada.",
|
"hibp_ok": "Nenhuma combinação encontrada.",
|
||||||
@@ -1393,7 +1393,7 @@
|
|||||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
|
||||||
"tag_handling": "Definir o tratamento para e-mails marcados",
|
"tag_handling": "Definir o tratamento para e-mails marcados",
|
||||||
"tag_help_example": "Exemplo de endereço de e-mail com tag: eu+Facebook</b>@exemplo.org",
|
"tag_help_example": "Exemplo de um endereço de e-mail marcado: me <b>+Facebook</b> @example .org",
|
||||||
"tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.",
|
"tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.",
|
||||||
"tag_in_none": "Não faça nada",
|
"tag_in_none": "Não faça nada",
|
||||||
"tag_in_subfolder": "Na subpasta",
|
"tag_in_subfolder": "Na subpasta",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -268,10 +268,10 @@
|
|||||||
"includes": "Bao gồm những người nhận này",
|
"includes": "Bao gồm những người nhận này",
|
||||||
"ip_check": "Kiểm tra IP",
|
"ip_check": "Kiểm tra IP",
|
||||||
"ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong<br> <strong>Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh</strong>",
|
"ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong<br> <strong>Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh</strong>",
|
||||||
"ip_check_opt_in": "Đăng ký tham gia sử dụng dịch vụ bên thứ ba dùng <strong>ipv4.mailcow.email</strong> và <strong>ipv6.mailcow.email</strong> để phân giải địa chỉ IP bên ngoài.",
|
"ip_check_opt_in": "Chọn tham gia sử dụng dịch vụ bên thứ ba <strong>ipv4.mailcow.email</strong> và <strong>ipv6.mailcow.email</strong> để phân giải địa chỉ IP bên ngoài.",
|
||||||
"is_mx_based": "Dựa trên MX",
|
"is_mx_based": "Dựa trên MX",
|
||||||
"last_applied": "Áp dụng lần cuối",
|
"last_applied": "Áp dụng lần cuối",
|
||||||
"license_info": "Giấy phép tuy không bắt buộc nhưng giúp phát triển thêm.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Đặt hàng SAL\">Đăng ký GUID của bạn tại đây</a> hoặc <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Đặt hàng hỗ trợ\">mua hỗ trợ cho cài đặt mailcow của bạn.</a>",
|
"license_info": "Giấy phép không bắt buộc nhưng giúp phát triển thêm.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Đặt hàng SAL\">Đăng ký GUID của bạn tại đây</a> hoặc <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Đặt hàng hỗ trợ\">mua hỗ trợ cho cài đặt mailcow của bạn.</a>",
|
||||||
"link": "Liên kết",
|
"link": "Liên kết",
|
||||||
"loading": "Vui lòng đợi...",
|
"loading": "Vui lòng đợi...",
|
||||||
"login_time": "Thời gian đăng nhập",
|
"login_time": "Thời gian đăng nhập",
|
||||||
@@ -556,9 +556,7 @@
|
|||||||
"validity_missing": "Vui lòng gán thời hạn hiệu lực",
|
"validity_missing": "Vui lòng gán thời hạn hiệu lực",
|
||||||
"value_missing": "Vui lòng cung cấp tất cả các giá trị",
|
"value_missing": "Vui lòng cung cấp tất cả các giá trị",
|
||||||
"version_invalid": "Phiên bản %s không hợp lệ",
|
"version_invalid": "Phiên bản %s không hợp lệ",
|
||||||
"yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s",
|
"yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s"
|
||||||
"tfa_removal_blocked": "Xác thực hai yếu tố không thể bị xóa vì đây là yêu cầu bắt buộc đối với tài khoản của bạn.",
|
|
||||||
"quarantine_category_invalid": "Danh mục cách ly phải là một trong các loại sau : add_header, reject, all."
|
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"collapse_all": "Thu gọn tất cả",
|
"collapse_all": "Thu gọn tất cả",
|
||||||
@@ -581,9 +579,7 @@
|
|||||||
"aria": {
|
"aria": {
|
||||||
"sortAscending": ": kích hoạt để sắp xếp cột tăng dần",
|
"sortAscending": ": kích hoạt để sắp xếp cột tăng dần",
|
||||||
"sortDescending": ": kích hoạt để sắp xếp cột giảm dần"
|
"sortDescending": ": kích hoạt để sắp xếp cột giảm dần"
|
||||||
},
|
}
|
||||||
"decimal": ".",
|
|
||||||
"thousands": ","
|
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"architecture": "Kiến trúc",
|
"architecture": "Kiến trúc",
|
||||||
@@ -697,19 +693,6 @@
|
|||||||
"internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.",
|
"internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.",
|
||||||
"kind": "Loại",
|
"kind": "Loại",
|
||||||
"last_modified": "Sửa đổi lần cuối",
|
"last_modified": "Sửa đổi lần cuối",
|
||||||
"lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)",
|
"lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)"
|
||||||
"sender_allowed": "Cho phép gửi đi bằng bí danh",
|
|
||||||
"sender_allowed_info": "Nếu bị vô hiệu hóa, bí danh này chỉ có thể nhận thư. Sử dụng ACL của người gửi để ghi đè và cấp quyền gửi cho các hộp thư cụ thể.",
|
|
||||||
"mailbox": "Chỉnh sửa hộp thư",
|
|
||||||
"mailbox_quota_def": "Hạn mức hộp thư mặc định",
|
|
||||||
"mailbox_relayhost_info": "Chỉ áp dụng cho hộp thư và các bí danh trực tiếp, thao tác này sẽ ghi đè lên máy chủ chuyển tiếp tên miền.",
|
|
||||||
"mailbox_rename": "Đổi tên hộp thư",
|
|
||||||
"mailbox_rename_agree": "Tôi đã tạo bản sao lưu.",
|
|
||||||
"mailbox_rename_warning": "QUAN TRỌNG! Hãy tạo bản sao lưu trước khi đổi tên hộp thư.",
|
|
||||||
"mailbox_rename_alias": "Tạo tự động bí danh",
|
|
||||||
"mailbox_rename_title": "Tên mới của hộp thư cục bộ",
|
|
||||||
"max_aliases": "Số lượng Bí danh tối đa",
|
|
||||||
"max_mailboxes": "Số lượng hộp thư tối đa có thể có",
|
|
||||||
"max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ if (isset($_GET['app_password'])) {
|
|||||||
$attr['protocols'][] = 'dav_access';
|
$attr['protocols'][] = 'dav_access';
|
||||||
}
|
}
|
||||||
app_passwd("add", $attr);
|
app_passwd("add", $attr);
|
||||||
|
$password = htmlspecialchars($password, ENT_NOQUOTES);
|
||||||
} else {
|
} else {
|
||||||
$app_password = false;
|
$app_password = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Per-user OAuth2 authorization_code flow for IMAP sync sources.
|
|
||||||
// Opened as a popup from the sync-job modal:
|
|
||||||
// ?action=start&source_id=N -> redirect the logged-in user to the provider consent screen
|
|
||||||
// (callback, ?code&state) -> exchange code, store the per-user token, message the opener
|
|
||||||
// The resulting token is bound to the authenticated remote identity, which fixes syncjob user1.
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
|
||||||
|
|
||||||
function syncjob_oauth_redirect_uri() {
|
|
||||||
// Must match the redirect URI registered at the provider.
|
|
||||||
return 'https://' . $_SERVER['HTTP_HOST'] . '/syncjob-oauth';
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncjob_oauth_finish($ok, $payload) {
|
|
||||||
// Renders a tiny page that hands the result back to the opener modal and closes the popup.
|
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
|
||||||
$json = json_encode(array_merge(array('type' => 'syncjob_oauth', 'ok' => $ok), $payload));
|
|
||||||
echo '<!doctype html><meta charset="utf-8"><title>OAuth</title><body><script>'
|
|
||||||
. 'try { if (window.opener) window.opener.postMessage(' . $json . ', window.location.origin); } catch (e) {}'
|
|
||||||
. 'window.close();'
|
|
||||||
. 'document.body.innerText = ' . json_encode($ok ? 'Connected. You can close this window.' : 'Authentication failed. You can close this window.') . ';'
|
|
||||||
. '</script></body>';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be an authenticated session; users additionally need the syncjobs ACL.
|
|
||||||
if (empty($_SESSION['mailcow_cc_username']) || empty($_SESSION['mailcow_cc_role'])) {
|
|
||||||
header('Location: /');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if ($_SESSION['mailcow_cc_role'] == 'user' && ($_SESSION['acl']['syncjobs'] ?? '0') != '1') {
|
|
||||||
syncjob_oauth_finish(false, array('error' => 'access_denied'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$action = isset($_GET['action']) ? $_GET['action'] : ((isset($_GET['code']) || isset($_GET['error'])) ? 'callback' : '');
|
|
||||||
|
|
||||||
if ($action == 'start') {
|
|
||||||
$source_id = intval($_GET['source_id'] ?? 0);
|
|
||||||
// get/source enforces visibility for the current session
|
|
||||||
$source = syncjob('get', 'source', array('id' => $source_id, 'with_secret' => true));
|
|
||||||
if (!$source || $source['auth_type'] != 'XOAUTH2' || $source['oauth_flow'] != 'authorization_code') {
|
|
||||||
syncjob_oauth_finish(false, array('error' => 'source_unavailable'));
|
|
||||||
}
|
|
||||||
$state = bin2hex(random_bytes(16));
|
|
||||||
$_SESSION['syncjob_oauth2state'] = $state;
|
|
||||||
$_SESSION['syncjob_oauth_source_id'] = $source_id;
|
|
||||||
header('Location: ' . imapsync_source_oauth_authorize_url($source, syncjob_oauth_redirect_uri(), $state));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- callback ---
|
|
||||||
if (isset($_GET['error'])) {
|
|
||||||
syncjob_oauth_finish(false, array('error' => substr((string)$_GET['error'], 0, 200)));
|
|
||||||
}
|
|
||||||
// CSRF: state must match the one stored at 'start'
|
|
||||||
if (empty($_GET['code']) || empty($_GET['state']) || !hash_equals((string)($_SESSION['syncjob_oauth2state'] ?? ''), (string)$_GET['state'])) {
|
|
||||||
syncjob_oauth_finish(false, array('error' => 'invalid_state'));
|
|
||||||
}
|
|
||||||
$source_id = intval($_SESSION['syncjob_oauth_source_id'] ?? 0);
|
|
||||||
unset($_SESSION['syncjob_oauth2state'], $_SESSION['syncjob_oauth_source_id']);
|
|
||||||
|
|
||||||
$source = syncjob('get', 'source', array('id' => $source_id, 'with_secret' => true));
|
|
||||||
if (!$source || $source['auth_type'] != 'XOAUTH2' || $source['oauth_flow'] != 'authorization_code') {
|
|
||||||
syncjob_oauth_finish(false, array('error' => 'source_unavailable'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = imapsync_source_token_post($source['oauth_token_endpoint'], array(
|
|
||||||
'grant_type' => 'authorization_code',
|
|
||||||
'client_id' => $source['oauth_client_id'],
|
|
||||||
'client_secret' => $source['oauth_client_secret'],
|
|
||||||
'code' => $_GET['code'],
|
|
||||||
'redirect_uri' => syncjob_oauth_redirect_uri(),
|
|
||||||
'scope' => $source['oauth_scope'],
|
|
||||||
));
|
|
||||||
if (isset($token['error'])) {
|
|
||||||
syncjob_oauth_finish(false, array('error' => $token['error']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$email = imapsync_source_oauth_identity($source, $token);
|
|
||||||
if (!$email) {
|
|
||||||
syncjob_oauth_finish(false, array('error' => 'no_identity'));
|
|
||||||
}
|
|
||||||
|
|
||||||
imapsync_source_store_user_token($source_id, $email, $token);
|
|
||||||
syncjob_oauth_finish(true, array('source_id' => $source_id, 'user1' => $email));
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-f2b" aria-selected="false" aria-controls="tab-config-f2b" role="tab" data-bs-toggle="tab">{{ lang.admin.f2b_parameters }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-f2b" aria-selected="false" aria-controls="tab-config-f2b" role="tab" data-bs-toggle="tab">{{ lang.admin.f2b_parameters }}</button></li>
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-quarantine" aria-selected="false" aria-controls="tab-config-quarantine" role="tab" data-bs-toggle="tab">{{ lang.admin.quarantine }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-quarantine" aria-selected="false" aria-controls="tab-config-quarantine" role="tab" data-bs-toggle="tab">{{ lang.admin.quarantine }}</button></li>
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-quota" aria-selected="false" aria-controls="tab-config-quota" role="tab" data-bs-toggle="tab">{{ lang.admin.quota_notifications }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-quota" aria-selected="false" aria-controls="tab-config-quota" role="tab" data-bs-toggle="tab">{{ lang.admin.quota_notifications }}</button></li>
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-syncjob" aria-selected="false" aria-controls="tab-config-syncjob" role="tab" data-bs-toggle="tab">{{ lang.admin.syncjobs }}</button></li>
|
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-rsettings" aria-selected="false" aria-controls="tab-config-rsettings" role="tab" data-bs-toggle="tab">{{ lang.admin.rspamd_settings_map }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-rsettings" aria-selected="false" aria-controls="tab-config-rsettings" role="tab" data-bs-toggle="tab">{{ lang.admin.rspamd_settings_map }}</button></li>
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-password-settings" aria-selected="false" aria-controls="tab-config-password-settings" role="tab" data-bs-toggle="tab">{{ lang.admin.password_settings }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-password-settings" aria-selected="false" aria-controls="tab-config-password-settings" role="tab" data-bs-toggle="tab">{{ lang.admin.password_settings }}</button></li>
|
||||||
<li><button class="dropdown-item" data-bs-target="#tab-config-customize" aria-selected="false" aria-controls="tab-config-customize" role="tab" data-bs-toggle="tab">{{ lang.admin.customize }}</button></li>
|
<li><button class="dropdown-item" data-bs-target="#tab-config-customize" aria-selected="false" aria-controls="tab-config-customize" role="tab" data-bs-toggle="tab">{{ lang.admin.customize }}</button></li>
|
||||||
@@ -52,7 +51,6 @@
|
|||||||
{% include 'admin/tab-config-f2b.twig' %}
|
{% include 'admin/tab-config-f2b.twig' %}
|
||||||
{% include 'admin/tab-config-quarantine.twig' %}
|
{% include 'admin/tab-config-quarantine.twig' %}
|
||||||
{% include 'admin/tab-config-quota.twig' %}
|
{% include 'admin/tab-config-quota.twig' %}
|
||||||
{% include 'admin/tab-config-syncjob.twig' %}
|
|
||||||
{% include 'admin/tab-config-rsettings.twig' %}
|
{% include 'admin/tab-config-rsettings.twig' %}
|
||||||
{% include 'admin/tab-config-customize.twig' %}
|
{% include 'admin/tab-config-customize.twig' %}
|
||||||
{% include 'admin/tab-config-password-settings.twig' %}
|
{% include 'admin/tab-config-password-settings.twig' %}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<div class="tab-pane fade" id="tab-config-syncjob" role="tabpanel" aria-labelledby="tab-config-syncjob">
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header d-flex fs-5">
|
|
||||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-syncjob" data-bs-toggle="collapse" aria-controls="collapse-tab-config-syncjob">
|
|
||||||
{{ lang.admin.syncjobs }}
|
|
||||||
</button>
|
|
||||||
<span class="d-none d-md-block">{{ lang.admin.syncjobs }}</span>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-tab-config-syncjob" class="card-body collapse" data-bs-parent="#admin-content">
|
|
||||||
<form class="form" role="form" data-id="imapsync_settings" method="post">
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label for="max_parallel">{{ lang.admin.syncjob_max_parallel }}:</label>
|
|
||||||
<input type="number" class="form-control" id="max_parallel" name="max_parallel" min="1" max="1000" value="{{ imapsync_settings.max_parallel }}">
|
|
||||||
<small class="text-muted">{{ lang.admin.syncjob_max_parallel_info }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label for="max_kb_per_second">{{ lang.admin.syncjob_max_kb_per_second }}:</label>
|
|
||||||
<input type="number" class="form-control" id="max_kb_per_second" name="max_kb_per_second" min="0" max="1220000" value="{{ (imapsync_settings.max_bytes_per_second / 1024)|round }}">
|
|
||||||
<small class="text-muted">{{ lang.admin.syncjob_max_kb_per_second_info }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<a type="button" class="btn btn-sm d-block d-sm-inline btn-success" data-action="edit_selected"
|
|
||||||
data-item="imapsync_settings"
|
|
||||||
data-id="imapsync_settings"
|
|
||||||
data-api-url='edit/imapsync_settings'
|
|
||||||
data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.user.save_changes }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
var lang_user = {{ lang_user|raw }};
|
var lang_user = {{ lang_user|raw }};
|
||||||
var lang_admin = {{ lang_admin|raw }};
|
var lang_admin = {{ lang_admin|raw }};
|
||||||
var lang_datatables = {{ lang_datatables|raw }};
|
var lang_datatables = {{ lang_datatables|raw }};
|
||||||
var lang = {syncjobs: {{ lang_syncjobs|raw }}};
|
|
||||||
var csrf_token = '{{ csrf_token }}';
|
var csrf_token = '{{ csrf_token }}';
|
||||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||||
var table_for_domain = '{{ domain }}';
|
var table_for_domain = '{{ domain }}';
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-2" for="script_data">Script:</label>
|
<label class="control-label col-sm-2" for="script_data">Script:</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required>{{ result.script_data }}</textarea>
|
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required>{{ result.script_data|raw }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
{% extends 'edit.twig' %}
|
|
||||||
|
|
||||||
{% block inner_content %}
|
|
||||||
{% if result %}
|
|
||||||
<h4 class="mb-4">{{ lang.syncjobs.source }}: {{ result.name }}</h4>
|
|
||||||
<form class="form-horizontal" data-id="edit_imapsync_source" role="form" method="post">
|
|
||||||
<input type="hidden" value="0" name="active">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="name">{{ lang.syncjobs.source_name }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="name" id="name" maxlength="100" value="{{ result.name }}" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="description">{{ lang.syncjobs.source_description }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="description" id="description" maxlength="255" value="{{ result.description }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin' %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="scope">{{ lang.syncjobs.source_scope }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-scope" name="scope" id="scope">
|
|
||||||
{% if mailcow_cc_role == 'admin' %}
|
|
||||||
<option value="all"{% if result.scope == 'all' %} selected{% endif %}>{{ lang.syncjobs.source_scope_all }}</option>
|
|
||||||
{% endif %}
|
|
||||||
<option value="domain"{% if result.scope == 'domain' %} selected{% endif %}>{{ lang.syncjobs.source_scope_domain }}</option>
|
|
||||||
<option value="user"{% if result.scope == 'user' %} selected{% endif %}>{{ lang.syncjobs.source_scope_user }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 imapsync-source-domains-row" style="display:{% if result.scope == 'domain' %}flex{% else %}none{% endif %};">
|
|
||||||
<label class="control-label col-sm-2" for="domains">{{ lang.syncjobs.source_domains }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-domains" name="domains[]" data-live-search="true" title="{{ lang.syncjobs.source_domains }}" multiple>
|
|
||||||
{% for d in result.domains %}<option value="{{ d }}" selected>{{ d }}</option>{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 imapsync-source-users-row" style="display:{% if result.scope == 'user' %}flex{% else %}none{% endif %};">
|
|
||||||
<label class="control-label col-sm-2" for="users">{{ lang.syncjobs.source_users }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-users" name="users[]" data-live-search="true" title="{{ lang.syncjobs.source_users }}" multiple>
|
|
||||||
{% for u in result.users %}<option value="{{ u }}" selected>{{ u }}</option>{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-muted">{{ lang.syncjobs.source_scope }}: <code>{{ lang.syncjobs['source_scope_' ~ result.scope] }}</code></p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="host1">{{ lang.add.hostname }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="host1" id="host1" value="{{ result.host1 }}" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="port1">{{ lang.add.port }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="{{ result.port1 }}" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="enc1">{{ lang.add.enc_method }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select name="enc1" id="enc1">
|
|
||||||
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
|
|
||||||
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
|
|
||||||
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<label class="control-label col-sm-2" for="auth_type">{{ lang.syncjobs.source_auth_type }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-auth-type" name="auth_type" id="auth_type" required>
|
|
||||||
<option value="PLAIN"{% if result.auth_type == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
|
||||||
<option value="LOGIN"{% if result.auth_type == 'LOGIN' %} selected{% endif %}>LOGIN</option>
|
|
||||||
<option value="CRAM-MD5"{% if result.auth_type == 'CRAM-MD5' %} selected{% endif %}>CRAM-MD5</option>
|
|
||||||
<option value="XOAUTH2"{% if result.auth_type == 'XOAUTH2' %} selected{% endif %}>XOAUTH2 (OAuth2)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="imapsync-source-oauth-block" style="display:{% if result.auth_type == 'XOAUTH2' %}block{% else %}none{% endif %};">
|
|
||||||
<legend class="fs-5">{{ lang.syncjobs.source_oauth_block }}</legend>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_flow">{{ lang.syncjobs.source_oauth_flow }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-oauth-flow" name="oauth_flow" id="oauth_flow">
|
|
||||||
<option value="client_credentials"{% if result.oauth_flow != 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_app }}</option>
|
|
||||||
<option value="authorization_code"{% if result.oauth_flow == 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_user }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_token_endpoint">Token-Endpoint</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_token_endpoint" id="oauth_token_endpoint" value="{{ result.oauth_token_endpoint }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="imapsync-source-authcode-block" style="display:{% if result.oauth_flow == 'authorization_code' %}block{% else %}none{% endif %};">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_authorize_endpoint">{{ lang.syncjobs.source_authorize_endpoint }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_authorize_endpoint" id="oauth_authorize_endpoint" value="{{ result.oauth_authorize_endpoint }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_userinfo_endpoint">{{ lang.syncjobs.source_userinfo_endpoint }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_userinfo_endpoint" id="oauth_userinfo_endpoint" value="{{ result.oauth_userinfo_endpoint }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2">{{ lang.syncjobs.source_redirect_uri }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control imapsync-source-redirect-uri" readonly value="">
|
|
||||||
<small class="text-muted">{{ lang.syncjobs.source_redirect_uri_hint }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_client_id">Client-ID</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_client_id" id="oauth_client_id" value="{{ result.oauth_client_id }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_client_secret">Client-Secret</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="password" class="form-control" name="oauth_client_secret" id="oauth_client_secret" placeholder="••••••••">
|
|
||||||
<small class="text-muted">{{ lang.syncjobs.source_secret_unchanged_hint }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_scope">Scope</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_scope" id="oauth_scope" value="{{ result.oauth_scope }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_extra_params">Extra-Params (JSON)</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_extra_params" id="oauth_extra_params" value="{{ result.oauth_extra_params }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<p class="mb-1">
|
|
||||||
{{ lang.syncjobs.source_token_status }}:
|
|
||||||
{% if result.oauth_token_expires %}
|
|
||||||
<code>{{ result.oauth_token_expires|date('Y-m-d H:i:s') }}</code>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% if result.oauth_last_refresh_error %}
|
|
||||||
<p class="text-danger">{{ lang.syncjobs.source_token_error }}: <code>{{ result.oauth_last_refresh_error }}</code></p>
|
|
||||||
{% endif %}
|
|
||||||
<button type="button" class="btn btn-sm btn-secondary" data-action="edit_selected" data-id="edit_imapsync_source" data-item="{{ result.id }}" data-api-url='edit/syncjob_source/refresh_token' data-api-attr='{}'>{{ lang.syncjobs.source_refresh_now }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<div class="form-check">
|
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_imapsync_source" data-item="{{ result.id }}" data-api-url='edit/syncjob_source' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
{{ parent() }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block inner_content %}
|
{% block inner_content %}
|
||||||
{% if result %}
|
{% if result %}
|
||||||
<h4 class="mb-4">{{ lang.syncjobs.edit }}</h4>
|
<h4 class="mb-4">{{ lang.edit.syncjob }}</h4>
|
||||||
<form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
|
<form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
|
||||||
<input type="hidden" value="0" name="delete2duplicates">
|
<input type="hidden" value="0" name="delete2duplicates">
|
||||||
<input type="hidden" value="0" name="delete1">
|
<input type="hidden" value="0" name="delete1">
|
||||||
@@ -12,16 +12,16 @@
|
|||||||
<input type="hidden" value="0" name="active">
|
<input type="hidden" value="0" name="active">
|
||||||
<input type="hidden" value="0" name="subscribeall">
|
<input type="hidden" value="0" name="subscribeall">
|
||||||
<input type="hidden" value="0" name="dry">
|
<input type="hidden" value="0" name="dry">
|
||||||
<div class="row mb-4">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="source_id">{{ lang.syncjobs.source }}</label>
|
<label class="control-label col-sm-2" for="host1">{{ lang.edit.hostname }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="imapsync-source-select" name="source_id" id="source_id"
|
<input type="text" class="form-control" name="host1" id="host1" value="{{ result.host1 }}">
|
||||||
data-live-search="true"
|
</div>
|
||||||
data-current-source-id="{{ result.source_id }}"
|
</div>
|
||||||
data-current-source-auth-type="{{ result.source_auth_type }}" required>
|
<div class="row mb-4">
|
||||||
<option value="{{ result.source_id }}" data-auth-type="{{ result.source_auth_type }}" data-oauth-flow="{{ result.source_oauth_flow }}" selected>{{ result.source_name }} ({{ result.source_host }}:{{ result.source_port }} / {{ result.source_auth_type }})</option>
|
<label class="control-label col-sm-2" for="port1">Port</label>
|
||||||
</select>
|
<div class="col-sm-10">
|
||||||
<small class="text-muted oauth-source-hint" style="display:{% if result.source_auth_type == 'XOAUTH2' %}block{% else %}none{% endif %};">{{ lang.syncjobs.oauth_source_hint }}</small>
|
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="{{ result.port1 }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
@@ -30,122 +30,122 @@
|
|||||||
<input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}">
|
<input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2 imapsync-oauth-connect-row" style="display:{% if result.source_oauth_flow == 'authorization_code' %}flex{% else %}none{% endif %};">
|
<div class="row mb-4">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn">{{ lang.syncjobs.syncjob_oauth_connect }}</button>
|
|
||||||
<span class="imapsync-oauth-connect-status ms-2"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4 password1-row" style="display:{% if result.source_auth_type == 'XOAUTH2' %}none{% else %}flex{% endif %};">
|
|
||||||
<label class="control-label col-sm-2" for="password1">{{ lang.edit.password }}</label>
|
<label class="control-label col-sm-2" for="password1">{{ lang.edit.password }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="password" class="form-control" name="password1" id="password1" value="{{ result.password1 }}">
|
<input type="password" class="form-control" name="password1" id="password1" value="{{ result.password1 }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="mins_interval">{{ lang.syncjobs.mins_interval }}</label>
|
<label class="control-label col-sm-2" for="enc1">{{ lang.edit.encryption }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select id="enc1" name="enc1">
|
||||||
|
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
|
||||||
|
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
|
||||||
|
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2" for="mins_interval">{{ lang.edit.mins_interval }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required>
|
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required>
|
||||||
<small class="text-muted">1-43800</small>
|
<small class="text-muted">1-43800</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label>
|
<label class="control-label col-sm-2" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="{{ result.subfolder2 }}">
|
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="{{ result.subfolder2 }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="maxage">{{ lang.syncjobs.maxage|raw }}</label>
|
<label class="control-label col-sm-2" for="maxage">{{ lang.edit.maxage|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="{{ result.maxage }}">
|
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="{{ result.maxage }}">
|
||||||
<small class="text-muted">0-32000</small>
|
<small class="text-muted">0-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label>
|
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="{{ result.maxbytespersecond }}">
|
<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="{{ result.maxbytespersecond }}">
|
||||||
<small class="text-muted">0-125000000</small>
|
<small class="text-muted">0-125000000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="timeout1">{{ lang.syncjobs.timeout1 }}</label>
|
<label class="control-label col-sm-2" for="timeout1">{{ lang.add.timeout1 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="{{ result.timeout1 }}">
|
<input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="{{ result.timeout1 }}">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="timeout2">{{ lang.syncjobs.timeout2 }}</label>
|
<label class="control-label col-sm-2" for="timeout2">{{ lang.add.timeout2 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="{{ result.timeout2 }}">
|
<input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="{{ result.timeout2 }}">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="exclude">{{ lang.syncjobs.exclude }}</label>
|
<label class="control-label col-sm-2" for="exclude">{{ lang.edit.exclude }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="exclude" id="exclude" value="{{ result.exclude }}">
|
<input type="text" class="form-control" name="exclude" id="exclude" value="{{ result.exclude }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-2">{{ lang.syncjobs.custom_params }}</label>
|
<label class="control-label col-sm-2" for="custom_params">{{ lang.add.custom_params }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="imapsync-cp-editor">
|
<input type="text" class="form-control" name="custom_params" id="custom_params" value="{{ result.custom_params }}" placeholder="--some-param=xy --other-param=yx">
|
||||||
<div class="imapsync-cp-rows"></div>
|
<small class="text-muted">{{ lang.add.custom_params_hint }}</small>
|
||||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="{{ result.custom_params }}">
|
|
||||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.edit.delete2duplicates }} (--delete2duplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete1 }} (--delete1)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.edit.delete1 }} (--delete1)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2 }} (--delete2)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.edit.delete2 }} (--delete2)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.syncjobs.automap }} (--automap)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.edit.automap }} (--automap)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.edit.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.add.subscribeall }} (--subscribeall)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.syncjobs.dry }} (--dry)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.add.dry }} (--dry)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,23 +162,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% if mailcow_cc_role == 'admin' %}
|
|
||||||
<hr>
|
|
||||||
<form class="form-horizontal" data-id="editsyncjob_order" role="form" method="post">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="order_id">{{ lang.syncjobs.order_id }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="number" class="form-control" name="order_id" id="order_id" min="1" value="{{ result.order_id }}">
|
|
||||||
<small class="text-muted">{{ lang.syncjobs.order_id_hint }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-primary" data-action="edit_selected" data-id="editsyncjob_order" data-item="{{ result.id }}" data-api-url='edit/syncjob/order' data-api-attr='{}' href="#">{{ lang.syncjobs.set_position }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-domain-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domain-aliases">{{ lang.mailbox.domain_aliases }}</button></li>
|
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-domain-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domain-aliases">{{ lang.mailbox.domain_aliases }}</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.mailbox.sync_jobs }}</button></li>
|
||||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
|
||||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
|
||||||
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
|
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
|
||||||
@@ -67,7 +67,6 @@
|
|||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
var acl = '{{ acl_json|raw }}';
|
var acl = '{{ acl_json|raw }}';
|
||||||
var lang = {{ lang_mailbox|raw }};
|
var lang = {{ lang_mailbox|raw }};
|
||||||
lang.syncjobs = {{ lang_syncjobs|raw }};
|
|
||||||
var lang_rl = {{ lang_rl|raw }};
|
var lang_rl = {{ lang_rl|raw }};
|
||||||
var lang_edit = {{ lang_edit|raw }};
|
var lang_edit = {{ lang_edit|raw }};
|
||||||
var lang_datatables = {{ lang_datatables|raw }};
|
var lang_datatables = {{ lang_datatables|raw }};
|
||||||
|
|||||||
@@ -1,35 +1,10 @@
|
|||||||
<div class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs">
|
<div class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs">
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header d-flex fs-5">
|
|
||||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-imapsync-sources" data-bs-toggle="collapse" aria-controls="collapse-tab-imapsync-sources">
|
|
||||||
{{ lang.syncjobs.sources_panel }} <span class="badge bg-info table-lines"></span>
|
|
||||||
</button>
|
|
||||||
<span class="d-none d-md-block">{{ lang.syncjobs.sources_panel }} <span class="badge bg-info table-lines"></span></span>
|
|
||||||
<div class="btn-group ms-auto d-flex">
|
|
||||||
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_imapsync_source_table" data-table="imapsync_source_table">{{ lang.admin.refresh }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-tab-imapsync-sources" class="card-body collapse" data-bs-parent="#mail-content">
|
|
||||||
<p class="text-muted">{{ lang.syncjobs.sources_hint }}</p>
|
|
||||||
<div class="mass-actions-mailbox mb-4 d-none d-sm-block">
|
|
||||||
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
|
|
||||||
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table id="imapsync_source_table" class="table table-striped dt-responsive w-100"></table>
|
|
||||||
<div class="mass-actions-mailbox mt-4">
|
|
||||||
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
|
|
||||||
<a class="btn btn-sm btn-xs-lg btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs">
|
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs">
|
||||||
{{ lang.syncjobs.sync_jobs }} <span class="badge bg-info table-lines"></span>
|
{{ lang.mailbox.sync_jobs }} <span class="badge bg-info table-lines"></span>
|
||||||
</button>
|
</button>
|
||||||
<span class="d-none d-md-block">{{ lang.syncjobs.sync_jobs }} <span class="badge bg-info table-lines"></span></span>
|
<span class="d-none d-md-block">{{ lang.mailbox.sync_jobs }} <span class="badge bg-info table-lines"></span></span>
|
||||||
|
|
||||||
<div class="btn-group ms-auto d-flex">
|
<div class="btn-group ms-auto d-flex">
|
||||||
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_sync_job_table" data-table="sync_job_table">{{ lang.admin.refresh }}</button>
|
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_sync_job_table" data-table="sync_job_table">{{ lang.admin.refresh }}</button>
|
||||||
@@ -51,7 +26,7 @@
|
|||||||
<li class="table_collapse_option"><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 class="table_collapse_option"><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.syncjobs.create }}</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>
|
||||||
</div>
|
</div>
|
||||||
<table id="sync_job_table" class="table table-striped dt-responsive w-100"></table>
|
<table id="sync_job_table" class="table table-striped dt-responsive w-100"></table>
|
||||||
@@ -70,7 +45,7 @@
|
|||||||
<li class="table_collapse_option"><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 class="table_collapse_option"><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-xs-lg btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a>
|
<a class="btn btn-sm btn-xs-lg 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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
<!-- add imapsync source modal (shared between user and admin views) -->
|
|
||||||
<div class="modal fade" id="addImapsyncSourceModal" tabindex="-1" role="dialog" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-xl">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title">{{ lang.syncjobs.source_add }}</h3>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_imapsync_source">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="name">{{ lang.syncjobs.source_name }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="name" maxlength="100" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="description">{{ lang.syncjobs.source_description }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="description" maxlength="255">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin' %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="scope">{{ lang.syncjobs.source_scope }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-scope" name="scope">
|
|
||||||
{% if mailcow_cc_role == 'admin' %}
|
|
||||||
<option value="all">{{ lang.syncjobs.source_scope_all }}</option>
|
|
||||||
{% endif %}
|
|
||||||
<option value="domain">{{ lang.syncjobs.source_scope_domain }}</option>
|
|
||||||
<option value="user" selected>{{ lang.syncjobs.source_scope_user }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 imapsync-source-domains-row" style="display:none;">
|
|
||||||
<label class="control-label col-sm-2" for="domains">{{ lang.syncjobs.source_domains }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-domains" name="domains[]" data-live-search="true" title="{{ lang.syncjobs.source_domains }}" multiple></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 imapsync-source-users-row" style="display:none;">
|
|
||||||
<label class="control-label col-sm-2" for="users">{{ lang.syncjobs.source_users }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-users" name="users[]" data-live-search="true" title="{{ lang.syncjobs.source_users }}" multiple></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="host1">{{ lang.add.hostname }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="host1" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="port1">{{ lang.add.port }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="number" class="form-control" name="port1" min="1" max="65535" value="993" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="enc1">{{ lang.add.enc_method }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select name="enc1" title="{{ lang.add.select }}" required>
|
|
||||||
<option value="SSL" selected>SSL</option>
|
|
||||||
<option value="TLS">STARTTLS</option>
|
|
||||||
<option value="PLAIN">PLAIN</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<label class="control-label col-sm-2" for="auth_type">{{ lang.syncjobs.source_auth_type }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-auth-type" name="auth_type" required>
|
|
||||||
<option value="PLAIN" selected>PLAIN</option>
|
|
||||||
<option value="LOGIN">LOGIN</option>
|
|
||||||
<option value="CRAM-MD5">CRAM-MD5</option>
|
|
||||||
<option value="XOAUTH2">XOAUTH2 (OAuth2)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="imapsync-source-oauth-block" style="display:none;">
|
|
||||||
<legend class="fs-5">{{ lang.syncjobs.source_oauth_block }}</legend>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_flow">{{ lang.syncjobs.source_oauth_flow }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select class="form-control imapsync-source-oauth-flow" name="oauth_flow">
|
|
||||||
<option value="client_credentials" selected>{{ lang.syncjobs.source_oauth_flow_app }}</option>
|
|
||||||
<option value="authorization_code">{{ lang.syncjobs.source_oauth_flow_user }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_token_endpoint">Token-Endpoint</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_token_endpoint" placeholder="https://provider.example.com/oauth2/token">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="imapsync-source-authcode-block" style="display:none;">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_authorize_endpoint">{{ lang.syncjobs.source_authorize_endpoint }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_authorize_endpoint" placeholder="https://provider.example.com/oauth2/authorize">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_userinfo_endpoint">{{ lang.syncjobs.source_userinfo_endpoint }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="url" class="form-control" name="oauth_userinfo_endpoint" placeholder="https://provider.example.com/oauth2/userinfo">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2">{{ lang.syncjobs.source_redirect_uri }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control imapsync-source-redirect-uri" readonly value="">
|
|
||||||
<small class="text-muted">{{ lang.syncjobs.source_redirect_uri_hint }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_client_id">Client-ID</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_client_id">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_client_secret">Client-Secret</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="password" class="form-control" name="oauth_client_secret">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_scope">Scope</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_scope" placeholder="openid email offline_access">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<label class="control-label col-sm-2" for="oauth_extra_params">Extra-Params (JSON)</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" name="oauth_extra_params" placeholder='{"resource":"https://provider.example.com"}'>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<div class="form-check">
|
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.add.active }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="add_imapsync_source" data-api-url='add/syncjob_source' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -937,11 +937,11 @@
|
|||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 class="modal-title">{{ lang.syncjobs.add }}</h3>
|
<h3 class="modal-title">{{ lang.add.syncjob }}</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p class="text-muted">{{ lang.syncjobs.add_hint }}</p>
|
<p class="text-muted">{{ lang.add.syncjob_hint }}</p>
|
||||||
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob">
|
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="username">{{ lang.add.username }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="username">{{ lang.add.username }}</label>
|
||||||
@@ -954,12 +954,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="source_id">{{ lang.syncjobs.source_select }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="host1">{{ lang.add.hostname }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="imapsync-source-select" data-live-search="true" name="source_id" title="{{ lang.syncjobs.source_select }}" required>
|
<input type="text" class="form-control" name="host1" required>
|
||||||
{# options are loaded via the API on modal open (populateImapsyncSourceSelects) #}
|
</div>
|
||||||
</select>
|
</div>
|
||||||
<small class="text-muted oauth-source-hint" style="display:none;">{{ lang.syncjobs.oauth_source_hint }}</small>
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2 text-sm-end" for="port1">{{ lang.add.port }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required>
|
||||||
|
<small class="text-muted">1-65535</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
@@ -968,122 +972,122 @@
|
|||||||
<input type="text" class="form-control" name="user1" required>
|
<input type="text" class="form-control" name="user1" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2 imapsync-oauth-connect-row" style="display:none;">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn">{{ lang.syncjobs.syncjob_oauth_connect }}</button>
|
|
||||||
<span class="imapsync-oauth-connect-status ms-2"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 password1-row">
|
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="password1">{{ lang.add.password }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="password1">{{ lang.add.password }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="password" class="form-control" name="password1">
|
<input type="password" class="form-control" name="password1" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="mins_interval">{{ lang.syncjobs.mins_interval }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="enc1">{{ lang.add.enc_method }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select name="enc1" title="{{ lang.add.select }}" required>
|
||||||
|
<option value="SSL" selected>SSL</option>
|
||||||
|
<option value="TLS">STARTTLS</option>
|
||||||
|
<option value="PLAIN">PLAIN</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2 text-sm-end" for="mins_interval">{{ lang.add.mins_interval }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
|
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
|
||||||
<small class="text-muted">1-43800</small>
|
<small class="text-muted">1-43800</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="subfolder2" value="">
|
<input type="text" class="form-control" name="subfolder2" value="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="maxage">{{ lang.syncjobs.maxage|raw }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="maxage">{{ lang.edit.maxage|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
|
<input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
|
||||||
<small class="text-muted">0-32000</small>
|
<small class="text-muted">0-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
|
<input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
|
||||||
<small class="text-muted">0-125000000</small>
|
<small class="text-muted">0-125000000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="timeout1">{{ lang.syncjobs.timeout1 }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="timeout1">{{ lang.edit.timeout1 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
|
<input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="timeout2">{{ lang.syncjobs.timeout2 }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="timeout2">{{ lang.edit.timeout2 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
|
<input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2 text-sm-end" for="exclude">{{ lang.syncjobs.exclude }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="exclude">{{ lang.add.exclude }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
|
<input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-2 text-sm-end">{{ lang.syncjobs.custom_params }}</label>
|
<label class="control-label col-sm-2 text-sm-end" for="custom_params">{{ lang.add.custom_params }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="imapsync-cp-editor">
|
<input type="text" class="form-control" name="custom_params" placeholder="--some-param=xy --other-param=yx">
|
||||||
<div class="imapsync-cp-rows"></div>
|
<small class="text-muted">{{ lang.add.custom_params_hint }}</small>
|
||||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="">
|
|
||||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.add.delete2duplicates }} (--delete2duplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.syncjobs.delete1 }} (--delete1)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.add.delete1 }} (--delete1)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.syncjobs.delete2 }} (--delete2)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.add.delete2 }} (--delete2)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.syncjobs.automap }} (--automap)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.add.automap }} (--automap)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.add.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.add.subscribeall }} (--subscribeall)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="dry"> {{ lang.syncjobs.dry }} (--dry)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="dry"> {{ lang.add.dry }} (--dry)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1343,5 +1347,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- DNS info modal -->
|
</div><!-- DNS info modal -->
|
||||||
|
|
||||||
{% include "modals/imapsync_source.twig" %}
|
|
||||||
|
|||||||
@@ -31,19 +31,23 @@
|
|||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 class="modal-title">{{ lang.syncjobs.add }}</h3>
|
<h3 class="modal-title">{{ lang.add.syncjob }}</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{ lang.syncjobs.add_hint }}</p>
|
<p>{{ lang.add.syncjob_hint }}</p>
|
||||||
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
|
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
|
||||||
<div class="row mb-4">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="source_id">{{ lang.syncjobs.source_select }}</label>
|
<label class="control-label col-sm-2" for="host1">{{ lang.add.hostname }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="imapsync-source-select" data-live-search="true" name="source_id" title="{{ lang.syncjobs.source_select }}" required>
|
<input type="text" class="form-control" name="host1" required>
|
||||||
{# options are loaded via the API on modal open (populateImapsyncSourceSelects) #}
|
</div>
|
||||||
</select>
|
</div>
|
||||||
<small class="text-muted oauth-source-hint" style="display:none;">{{ lang.syncjobs.oauth_source_hint }}</small>
|
<div class="row mb-4">
|
||||||
|
<label class="control-label col-sm-2" for="port1">{{ lang.add.port }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required>
|
||||||
|
<small class="text-muted">1-65535</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
@@ -52,122 +56,121 @@
|
|||||||
<input type="text" class="form-control" name="user1" required>
|
<input type="text" class="form-control" name="user1" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2 imapsync-oauth-connect-row" style="display:none;">
|
<div class="row mb-4">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
|
||||||
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn">{{ lang.syncjobs.syncjob_oauth_connect }}</button>
|
|
||||||
<span class="imapsync-oauth-connect-status ms-2"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4 password1-row">
|
|
||||||
<label class="control-label col-sm-2" for="password1">{{ lang.add.password }}</label>
|
<label class="control-label col-sm-2" for="password1">{{ lang.add.password }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="password" class="form-control" name="password1" data-hibp="true">
|
<input type="password" class="form-control" name="password1" data-hibp="true" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="mins_interval">{{ lang.syncjobs.mins_interval }}</label>
|
<label class="control-label col-sm-2" for="enc1">{{ lang.add.enc_method }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select name="enc1" title="{{ lang.add.select }}" required>
|
||||||
|
<option value="SSL" selected>SSL</option>
|
||||||
|
<option value="TLS">STARTTLS</option>
|
||||||
|
<option value="PLAIN">PLAIN</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2" for="mins_interval">{{ lang.add.mins_interval }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
|
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
|
||||||
<small class="text-muted">1-43800</small>
|
<small class="text-muted">1-43800</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label>
|
<label class="control-label col-sm-2" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="subfolder2" value="">
|
<input type="text" class="form-control" name="subfolder2" value="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="maxage">{{ lang.syncjobs.maxage|raw }}</label>
|
<label class="control-label col-sm-2" for="maxage">{{ lang.edit.maxage|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
|
<input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
|
||||||
<small class="text-muted">0-32000</small>
|
<small class="text-muted">0-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label>
|
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
|
<input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
|
||||||
<small class="text-muted">0-125000000</small>
|
<small class="text-muted">0-125000000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="timeout1">{{ lang.syncjobs.timeout1 }}</label>
|
<label class="control-label col-sm-2" for="timeout1">{{ lang.edit.timeout1 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
|
<input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="timeout2">{{ lang.syncjobs.timeout2 }}</label>
|
<label class="control-label col-sm-2" for="timeout2">{{ lang.edit.timeout2 }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
|
<input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
|
||||||
<small class="text-muted">1-32000</small>
|
<small class="text-muted">1-32000</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="exclude">{{ lang.syncjobs.exclude }}</label>
|
<label class="control-label col-sm-2" for="exclude">{{ lang.add.exclude }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
|
<input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-2">{{ lang.syncjobs.custom_params }}</label>
|
<label class="control-label col-sm-2" for="custom_params">{{ lang.add.custom_params }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="imapsync-cp-editor">
|
<input type="text" class="form-control" name="custom_params" placeholder="--delete2folders --otheroption">
|
||||||
<div class="imapsync-cp-rows"></div>
|
|
||||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="">
|
|
||||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.add.delete2duplicates }} (--delete2duplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.syncjobs.delete1 }} (--delete1)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.add.delete1 }} (--delete1)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.syncjobs.delete2 }} (--delete2)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.add.delete2 }} (--delete2)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.syncjobs.automap }} (--automap)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.add.automap }} (--automap)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.add.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.add.subscribeall }} (--subscribeall)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label><input type="checkbox" class="form-check-input" value="1" name="dry" checked> {{ lang.syncjobs.dry }} (--dry)</label>
|
<label><input type="checkbox" class="form-check-input" value="1" name="dry" checked> {{ lang.add.dry }} (--dry)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +191,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- add sync job modal -->
|
</div><!-- add sync job modal -->
|
||||||
{% include 'modals/imapsync_source.twig' %}
|
|
||||||
<!-- app passwd modal -->
|
<!-- app passwd modal -->
|
||||||
<div class="modal fade" id="addAppPasswdModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" id="addAppPasswdModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if acl.syncjobs == 1 %}
|
{% if acl.syncjobs == 1 %}
|
||||||
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.user.sync_jobs }}</button></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if acl.app_passwds == 1 %}
|
{% if acl.app_passwds == 1 %}
|
||||||
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li>
|
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li>
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
<div class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs">
|
<div class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs">
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header d-flex fs-5">
|
|
||||||
<span class="d-none d-md-block">{{ lang.syncjobs.sources_panel }}</span>
|
|
||||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-imapsync-sources" data-bs-toggle="collapse" aria-controls="collapse-tab-imapsync-sources">
|
|
||||||
{{ lang.syncjobs.sources_panel }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-tab-imapsync-sources" class="card-body collapse" data-bs-parent="#user-content">
|
|
||||||
<p class="text-muted">{{ lang.syncjobs.sources_hint }}</p>
|
|
||||||
<div class="mass-actions-user mb-4">
|
|
||||||
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
|
|
||||||
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table id="imapsync_source_table" class="table table-striped dt-responsive w-100"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs">
|
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs">
|
||||||
{{ lang.syncjobs.sync_jobs }}
|
{{ lang.user.sync_jobs }}
|
||||||
</button>
|
</button>
|
||||||
<span class="d-none d-md-block">{{ lang.syncjobs.sync_jobs }}</span>
|
<span class="d-none d-md-block">{{ lang.user.sync_jobs }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content">
|
<div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content">
|
||||||
<div class="mass-actions-user mb-4">
|
<div class="mass-actions-user mb-4">
|
||||||
@@ -40,7 +23,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a>
|
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +44,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a>
|
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
var acl = '{{ acl_json|raw }}';
|
var acl = '{{ acl_json|raw }}';
|
||||||
var lang = {{ lang_user|raw }};
|
var lang = {{ lang_user|raw }};
|
||||||
lang.syncjobs = {{ lang_syncjobs|raw }};
|
|
||||||
var csrf_token = '{{ csrf_token }}';
|
var csrf_token = '{{ csrf_token }}';
|
||||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||||
var mailcow_cc_username = '{{ mailcow_cc_username }}';
|
var mailcow_cc_username = '{{ mailcow_cc_username }}';
|
||||||
|
|||||||
+4
-7
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
image: ghcr.io/mailcow/unbound:1.25.1-1
|
image: ghcr.io/mailcow/unbound:1.25
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
- SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n}
|
- SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n}
|
||||||
@@ -194,16 +194,13 @@ services:
|
|||||||
ofelia.job-exec.phpfpm_ldap_sync.schedule: "0 * * * * *"
|
ofelia.job-exec.phpfpm_ldap_sync.schedule: "0 * * * * *"
|
||||||
ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true"
|
ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true"
|
||||||
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\""
|
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\""
|
||||||
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.schedule: "0 * * * * *"
|
|
||||||
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.no-overlap: "true"
|
|
||||||
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.command: "/bin/bash -c \"php /crons/imapsync-oauth-refresh.php || exit 0\""
|
|
||||||
networks:
|
networks:
|
||||||
mailcow-network:
|
mailcow-network:
|
||||||
aliases:
|
aliases:
|
||||||
- phpfpm
|
- phpfpm
|
||||||
|
|
||||||
sogo-mailcow:
|
sogo-mailcow:
|
||||||
image: ghcr.io/mailcow/sogo:5.12.8-1
|
image: ghcr.io/mailcow/sogo:5.12.5-3
|
||||||
environment:
|
environment:
|
||||||
- DBNAME=${DBNAME}
|
- DBNAME=${DBNAME}
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
@@ -255,7 +252,7 @@ services:
|
|||||||
- sogo
|
- sogo
|
||||||
|
|
||||||
dovecot-mailcow:
|
dovecot-mailcow:
|
||||||
image: ghcr.io/mailcow/dovecot:2.3.21.1-3
|
image: ghcr.io/mailcow/dovecot:2.3.21.1-2
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql-mailcow
|
- mysql-mailcow
|
||||||
- netfilter-mailcow
|
- netfilter-mailcow
|
||||||
@@ -422,7 +419,7 @@ services:
|
|||||||
- php-fpm-mailcow
|
- php-fpm-mailcow
|
||||||
- sogo-mailcow
|
- sogo-mailcow
|
||||||
- rspamd-mailcow
|
- rspamd-mailcow
|
||||||
image: ghcr.io/mailcow/nginx:1.30.2-1
|
image: ghcr.io/mailcow/nginx:1.06
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user