mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-07-25 09:04:32 +00:00
[Imapsync] switch from order_id to prio
This commit is contained in:
@@ -13,19 +13,6 @@ 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 };
|
||||||
|
|
||||||
# Move a finished job to the back of the queue, keeping order_id contiguous (1..N).
|
|
||||||
# Called only from the parent after reaping a child, so all rotations are serialized.
|
|
||||||
sub rotate_to_back {
|
|
||||||
my ($dbh, $jid) = @_;
|
|
||||||
return unless defined $jid;
|
|
||||||
my ($cnt) = $dbh->selectrow_array("SELECT COUNT(*) FROM imapsync");
|
|
||||||
my ($old) = $dbh->selectrow_array("SELECT order_id FROM imapsync WHERE id = ?", undef, $jid);
|
|
||||||
return unless (defined $cnt && defined $old && $old < $cnt);
|
|
||||||
# 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
$run_dir="/tmp";
|
$run_dir="/tmp";
|
||||||
$dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock';
|
$dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock';
|
||||||
$lock_file = $run_dir . "/imapsync_busy";
|
$lock_file = $run_dir . "/imapsync_busy";
|
||||||
@@ -92,29 +79,24 @@ my $sth = $dbh->prepare("SELECT
|
|||||||
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(i.last_run) > i.mins_interval * 60
|
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(i.last_run) > i.mins_interval * 60
|
||||||
OR
|
OR
|
||||||
i.last_run IS NULL)
|
i.last_run IS NULL)
|
||||||
ORDER BY i.order_id ASC, i.id ASC");
|
ORDER BY i.last_run ASC, i.prio DESC, i.id ASC
|
||||||
|
LIMIT $max_parallel");
|
||||||
|
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
my @rows = @{$sth->fetchall_arrayref()};
|
my @rows = @{$sth->fetchall_arrayref()};
|
||||||
$sth->finish();
|
$sth->finish();
|
||||||
|
|
||||||
# Fork pool: run up to $max_parallel imapsync children at once, in order_id order.
|
# Only up to $max_parallel due jobs are pulled per run (LIMIT above)
|
||||||
my $active = 0;
|
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) {
|
foreach my $row (@rows) {
|
||||||
# Throttle: once max children are running, reap one before starting the next
|
# Throttle: once max children are running, reap one before starting the next
|
||||||
if ($active >= $max_parallel) {
|
if ($active >= $max_parallel) {
|
||||||
my $done = wait();
|
wait();
|
||||||
my $rc = $? >> 8;
|
|
||||||
$active-- if $active > 0;
|
$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();
|
my $pid = fork();
|
||||||
next if (!defined $pid);
|
next if (!defined $pid);
|
||||||
if ($pid != 0) { $pid_job{$pid} = @$row[0]; $active++; next; } # parent: remember pid->job, continue
|
if ($pid != 0) { $active++; next; } # parent: one more child running, continue
|
||||||
|
|
||||||
# ---- CHILD ---- own DB connection so the parent's handle stays intact across forks
|
# ---- CHILD ---- own DB connection so the parent's handle stays intact across forks
|
||||||
$dbh->{InactiveDestroy} = 1;
|
$dbh->{InactiveDestroy} = 1;
|
||||||
@@ -179,8 +161,7 @@ foreach my $row (@rows) {
|
|||||||
$dbh->disconnect();
|
$dbh->disconnect();
|
||||||
unlink $passfile1->filename if defined $passfile1;
|
unlink $passfile1->filename if defined $passfile1;
|
||||||
unlink $passfile2->filename if defined $passfile2;
|
unlink $passfile2->filename if defined $passfile2;
|
||||||
# exit 75 (EX_TEMPFAIL): did not actually sync -> parent keeps its queue position (no rotate)
|
POSIX::_exit(75); # EX_TEMPFAIL: did not actually sync (token not ready)
|
||||||
POSIX::_exit(75);
|
|
||||||
}
|
}
|
||||||
$tokenfile1 = File::Temp->new(TEMPLATE => $template);
|
$tokenfile1 = File::Temp->new(TEMPLATE => $template);
|
||||||
binmode($tokenfile1, ":utf8");
|
binmode($tokenfile1, ":utf8");
|
||||||
@@ -287,14 +268,8 @@ foreach my $row (@rows) {
|
|||||||
POSIX::_exit(0); # ---- end CHILD ----
|
POSIX::_exit(0); # ---- end CHILD ----
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parent: wait for all remaining children, rotating each finished job to the back
|
# Parent: wait for all remaining children to finish
|
||||||
while ((my $done = wait()) != -1) {
|
while (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};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$dbh->disconnect();
|
$dbh->disconnect();
|
||||||
|
|
||||||
|
|||||||
@@ -4089,6 +4089,12 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
v:
|
v:
|
||||||
type: string
|
type: string
|
||||||
|
prio:
|
||||||
|
description: >-
|
||||||
|
Priority (admin only; ignored otherwise). The runner processes due
|
||||||
|
jobs least-recently-run first, priority breaks ties (0 = normal,
|
||||||
|
higher wins).
|
||||||
|
type: number
|
||||||
delete1:
|
delete1:
|
||||||
description: Delete from source when completed
|
description: Delete from source when completed
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -4356,72 +4362,6 @@ paths:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
/api/v1/edit/imapsync_settings:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
|
|||||||
@@ -385,42 +385,27 @@ function imapsync_set_setting($name, $value) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function imapsync_set_order($id, $pos) {
|
function imapsync_run_next($ids) {
|
||||||
// Move syncjob $id to global queue position $pos (1..N), shifting only the rows between
|
// clear last_run and bump prio.
|
||||||
// the old and new position (range-shift, not a full renumber). Admin-only, transactional.
|
// Admin-only.
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if (($_SESSION['mailcow_cc_role'] ?? '') !== 'admin' || !is_numeric($id)) return false;
|
if (($_SESSION['mailcow_cc_role'] ?? '') !== 'admin') return false;
|
||||||
$pos = intval($pos);
|
if (!is_array($ids)) $ids = array($ids);
|
||||||
|
$sel = array();
|
||||||
|
foreach ($ids as $i) { if (is_numeric($i)) $sel[] = (int)$i; }
|
||||||
|
$sel = array_values(array_unique($sel));
|
||||||
|
if (empty($sel)) return false;
|
||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$in = implode(',', array_fill(0, count($sel), '?'));
|
||||||
$cnt = intval($pdo->query("SELECT COUNT(*) FROM `imapsync`")->fetchColumn());
|
// Assign each selected job a prio above the current global max, lowest-current first.
|
||||||
if ($cnt === 0) { $pdo->commit(); return false; }
|
$pdo->exec("SET @m := (SELECT COALESCE(MAX(`prio`), 0) FROM `imapsync`)");
|
||||||
if ($pos < 1) $pos = 1;
|
$pdo->exec("SET @r := 0");
|
||||||
if ($pos > $cnt) $pos = $cnt;
|
$pdo->prepare("UPDATE `imapsync` SET `prio` = @m + (@r := @r + 1)
|
||||||
$stmt = $pdo->prepare("SELECT `order_id` FROM `imapsync` WHERE `id` = :id");
|
WHERE `id` IN ($in) ORDER BY `prio` ASC, `id` ASC")->execute($sel);
|
||||||
$stmt->execute(array(':id' => $id));
|
$pdo->prepare("UPDATE `imapsync` SET `last_run` = NULL, `success` = NULL
|
||||||
$old = $stmt->fetchColumn();
|
WHERE `id` IN ($in)")->execute($sel);
|
||||||
if ($old === false) { $pdo->rollBack(); return false; }
|
return count($sel);
|
||||||
$old = intval($old);
|
|
||||||
if ($old !== $pos) {
|
|
||||||
if ($pos < $old) {
|
|
||||||
// moving up: shift the [pos, old-1] band down (+1), highest first to avoid collisions
|
|
||||||
$pdo->prepare("UPDATE `imapsync` SET `order_id` = `order_id` + 1
|
|
||||||
WHERE `order_id` >= :pos AND `order_id` < :old ORDER BY `order_id` DESC")
|
|
||||||
->execute(array(':pos' => $pos, ':old' => $old));
|
|
||||||
} else {
|
|
||||||
// moving down: shift the (old, pos] band up (-1), lowest first
|
|
||||||
$pdo->prepare("UPDATE `imapsync` SET `order_id` = `order_id` - 1
|
|
||||||
WHERE `order_id` > :old AND `order_id` <= :pos ORDER BY `order_id` ASC")
|
|
||||||
->execute(array(':old' => $old, ':pos' => $pos));
|
|
||||||
}
|
|
||||||
$pdo->prepare("UPDATE `imapsync` SET `order_id` = :pos WHERE `id` = :id")
|
|
||||||
->execute(array(':pos' => $pos, ':id' => $id));
|
|
||||||
}
|
|
||||||
$pdo->commit();
|
|
||||||
return true;
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,9 +561,8 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `source_id`, `user1`, `password1`, `mins_interval`, `delete2duplicates`, `custom_params`, `active`, `order_id`)
|
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `source_id`, `user1`, `password1`, `mins_interval`, `delete2duplicates`, `custom_params`, `active`)
|
||||||
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :source_id, :user1, :password1, :mins_interval, :delete2duplicates, :custom_params, :active,
|
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :source_id, :user1, :password1, :mins_interval, :delete2duplicates, :custom_params, :active)");
|
||||||
(SELECT COALESCE(MAX(o.`order_id`), 0) + 1 FROM (SELECT `order_id` FROM `imapsync`) o))");
|
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':user2' => $username,
|
':user2' => $username,
|
||||||
':custom_params' => $custom_params,
|
':custom_params' => $custom_params,
|
||||||
@@ -787,12 +771,10 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
break;
|
break;
|
||||||
case 'edit':
|
case 'edit':
|
||||||
switch ($_type) {
|
switch ($_type) {
|
||||||
case 'job_order':
|
case 'run_next':
|
||||||
// Set a syncjob's global queue position (admin-only). $_data = {id, order_id}
|
$ids = is_array($_data) ? ($_data['id'] ?? null) : null;
|
||||||
$id = is_array($_data) ? ($_data['id'] ?? null) : null;
|
$moved = imapsync_run_next($ids);
|
||||||
if (is_array($id)) $id = reset($id);
|
if ($moved === false) {
|
||||||
$pos = is_array($_data) ? ($_data['order_id'] ?? null) : null;
|
|
||||||
if (!is_numeric($id) || !is_numeric($pos) || imapsync_set_order($id, $pos) === false) {
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log),
|
||||||
@@ -803,7 +785,7 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log),
|
||||||
'msg' => array('imapsync_order_updated', intval($pos))
|
'msg' => array('imapsync_run_next_done', intval($moved))
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
@@ -849,6 +831,8 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
|
$maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
|
||||||
$timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1'];
|
$timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1'];
|
||||||
$timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2'];
|
$timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2'];
|
||||||
|
// prio is a global-queue concern: only admins may change it, others keep the stored value
|
||||||
|
$prio = (isset($_data['prio']) && is_numeric($_data['prio']) && ($_SESSION['mailcow_cc_role'] ?? '') === 'admin') ? intval($_data['prio']) : $is_now['prio'];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -936,6 +920,7 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`timeout2` = :timeout2,
|
`timeout2` = :timeout2,
|
||||||
`subscribeall` = :subscribeall,
|
`subscribeall` = :subscribeall,
|
||||||
`dry` = :dry,
|
`dry` = :dry,
|
||||||
|
`prio` = :prio,
|
||||||
`active` = :active
|
`active` = :active
|
||||||
WHERE `id` = :id");
|
WHERE `id` = :id");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -960,6 +945,7 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':timeout2' => $timeout2,
|
':timeout2' => $timeout2,
|
||||||
':subscribeall' => $subscribeall,
|
':subscribeall' => $subscribeall,
|
||||||
':dry' => $dry,
|
':dry' => $dry,
|
||||||
|
':prio' => $prio,
|
||||||
':active' => $active,
|
':active' => $active,
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -1162,17 +1148,9 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Read the position first so we can close the gap in the global queue afterwards
|
// prio is a sparse sort key (ties allowed), so no gap needs closing on delete.
|
||||||
$ord = $pdo->prepare("SELECT `order_id` FROM `imapsync` WHERE `id` = :id");
|
|
||||||
$ord->execute(array(':id' => $id));
|
|
||||||
$deleted_pos = $ord->fetchColumn();
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
|
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
|
||||||
$stmt->execute(array(':id' => $id));
|
$stmt->execute(array(':id' => $id));
|
||||||
if ($deleted_pos !== false) {
|
|
||||||
$pdo->prepare("UPDATE `imapsync` SET `order_id` = `order_id` - 1
|
|
||||||
WHERE `order_id` > :pos ORDER BY `order_id` ASC")
|
|
||||||
->execute(array(':pos' => intval($deleted_pos)));
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
@@ -1289,7 +1267,7 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
else {
|
else {
|
||||||
$_data = $_SESSION['mailcow_cc_username'];
|
$_data = $_SESSION['mailcow_cc_username'];
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username ORDER BY `order_id`");
|
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username ORDER BY `last_run` ASC, `prio` DESC, `id` ASC");
|
||||||
$stmt->execute(array(':username' => $_data));
|
$stmt->execute(array(':username' => $_data));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while($row = array_shift($rows)) {
|
while($row = array_shift($rows)) {
|
||||||
|
|||||||
@@ -892,7 +892,7 @@ function init_db_schema()
|
|||||||
"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",
|
"prio" => "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",
|
||||||
@@ -907,7 +907,7 @@ function init_db_schema()
|
|||||||
),
|
),
|
||||||
"key" => array(
|
"key" => array(
|
||||||
"idx_source_id" => array("source_id"),
|
"idx_source_id" => array("source_id"),
|
||||||
"idx_order_id" => array("order_id")
|
"idx_prio" => array("prio")
|
||||||
),
|
),
|
||||||
"fkey" => array(
|
"fkey" => array(
|
||||||
"fk_imapsync_source" => array(
|
"fk_imapsync_source" => array(
|
||||||
@@ -1525,13 +1525,8 @@ 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
|
// Syncjobs: seed global settings
|
||||||
$pdo->query("INSERT IGNORE INTO `imapsync_settings` (`name`, `value`) VALUES ('max_parallel', '1')");
|
$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.
|
// 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.
|
// Old values could not contain spaces (they were rejected), so a whitespace split is safe.
|
||||||
|
|||||||
@@ -2231,8 +2231,8 @@ jQuery(function($){
|
|||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.syncjobs.order_id,
|
title: lang.syncjobs.prio,
|
||||||
data: 'order_id',
|
data: 'prio',
|
||||||
responsivePriority: 3,
|
responsivePriority: 3,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -349,8 +349,8 @@ jQuery(function($){
|
|||||||
responsivePriority: 3
|
responsivePriority: 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.syncjobs.order_id,
|
title: lang.syncjobs.prio,
|
||||||
data: 'order_id',
|
data: 'prio',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 3
|
responsivePriority: 3
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2005,8 +2005,8 @@ if (isset($_GET['query'])) {
|
|||||||
break;
|
break;
|
||||||
case "syncjob":
|
case "syncjob":
|
||||||
switch ($object) {
|
switch ($object) {
|
||||||
case "order":
|
case "run_next":
|
||||||
process_edit_return(syncjob('edit', 'job_order', array_merge(array('id' => $items), $attr)));
|
process_edit_return(syncjob('edit', 'run_next', array('id' => $items)));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
process_edit_return(syncjob('edit', 'job', array_merge(array('id' => $items), $attr)));
|
process_edit_return(syncjob('edit', 'job', array_merge(array('id' => $items), $attr)));
|
||||||
|
|||||||
@@ -1197,7 +1197,7 @@
|
|||||||
"imapsync_source_deleted": "Sync-Quelle %s wurde gelöscht",
|
"imapsync_source_deleted": "Sync-Quelle %s wurde gelöscht",
|
||||||
"imapsync_source_token_refreshed": "OAuth-Token für %s wurde erneuert",
|
"imapsync_source_token_refreshed": "OAuth-Token für %s wurde erneuert",
|
||||||
"max_parallel_saved": "Sync-Job-Einstellungen gespeichert",
|
"max_parallel_saved": "Sync-Job-Einstellungen gespeichert",
|
||||||
"imapsync_order_updated": "Sync-Job auf Position %s verschoben"
|
"imapsync_run_next_done": "%s Sync-Job(s) an den Anfang der Warteschlange gesetzt"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"authenticators": "Authentikatoren",
|
"authenticators": "Authentikatoren",
|
||||||
@@ -1300,9 +1300,8 @@
|
|||||||
"custom_param_value": "Wert",
|
"custom_param_value": "Wert",
|
||||||
"custom_param_no_value": "kein Wert nötig",
|
"custom_param_no_value": "kein Wert nötig",
|
||||||
"custom_param_add": "Parameter hinzufügen",
|
"custom_param_add": "Parameter hinzufügen",
|
||||||
"order_id": "Position",
|
"prio": "Priorität",
|
||||||
"order_id_hint": "Kleinere Position läuft zuerst; Ändern sortiert die globale Warteschlange um.",
|
"prio_hint": "Fällige Jobs laufen am längsten-nicht-gelaufen zuerst; bei gleichem Stand entscheidet die höhere Priorität (0 = normal)."
|
||||||
"set_position": "Position setzen"
|
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Aktion",
|
"action": "Aktion",
|
||||||
|
|||||||
@@ -1204,7 +1204,7 @@
|
|||||||
"imapsync_source_deleted": "Sync source %s has been deleted",
|
"imapsync_source_deleted": "Sync source %s has been deleted",
|
||||||
"imapsync_source_token_refreshed": "OAuth token for %s has been refreshed",
|
"imapsync_source_token_refreshed": "OAuth token for %s has been refreshed",
|
||||||
"max_parallel_saved": "Sync job settings saved",
|
"max_parallel_saved": "Sync job settings saved",
|
||||||
"imapsync_order_updated": "Sync job moved to position %s"
|
"imapsync_run_next_done": "%s sync job(s) moved to the front of the queue"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"authenticators": "Authenticators",
|
"authenticators": "Authenticators",
|
||||||
@@ -1307,9 +1307,8 @@
|
|||||||
"custom_param_value": "Value",
|
"custom_param_value": "Value",
|
||||||
"custom_param_no_value": "no value needed",
|
"custom_param_no_value": "no value needed",
|
||||||
"custom_param_add": "Add parameter",
|
"custom_param_add": "Add parameter",
|
||||||
"order_id": "Position",
|
"prio": "Priority",
|
||||||
"order_id_hint": "Lower position runs first; setting it re-sorts the global queue.",
|
"prio_hint": "Due jobs run least-recently-run first; on a tie the higher priority wins (0 = normal)."
|
||||||
"set_position": "Set position"
|
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
|
|||||||
@@ -48,6 +48,15 @@
|
|||||||
<small class="text-muted">1-43800</small>
|
<small class="text-muted">1-43800</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if mailcow_cc_role == 'admin' %}
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2" for="prio">{{ lang.syncjobs.prio }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="number" class="form-control" name="prio" id="prio" min="0" value="{{ result.prio }}">
|
||||||
|
<small class="text-muted">{{ lang.syncjobs.prio_hint }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<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.syncjobs.subfolder2|raw }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
@@ -160,23 +169,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 %}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
<a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
||||||
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":"","success":""}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob/run_next' data-api-attr='{}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
||||||
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":"","success":""}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob/run_next' data-api-attr='{}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
|
||||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
||||||
|
|||||||
Reference in New Issue
Block a user