This commit is contained in:
Kilian Ries 2026-07-16 08:57:52 +02:00 committed by GitHub
commit 4b00a3524e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 293 additions and 71 deletions

View file

@ -1,69 +1,175 @@
<html>
<head>
<meta name="x-apple-disable-message-reformatting" />
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Quarantine notification</title>
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<style>
body {
font-family: Helvetica, Arial, Sans-Serif;
}
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 20px;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
vertical-align: top;
}
td.fixed {
white-space: nowrap;
}
th {
background-color: #56B04C;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
/* mobile devices */
@media all and (max-width: 480px) {
.mob {
display: none;
}
}
:root { color-scheme: light dark; supported-color-schemes: light dark; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table { border-collapse: collapse !important; }
img { border: 0; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }
a { text-decoration: none; }
.msg-card td { word-break: break-word; }
/* Mobile */
@media only screen and (max-width: 600px) {
.container { width: 100% !important; }
.px { padding-left: 20px !important; padding-right: 20px !important; }
.btn-a { display: block !important; text-align: center !important; }
.btn-cell { display: block !important; width: 100% !important; padding: 4px 0 !important; }
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
body, .bg-page { background-color: #16181d !important; }
.bg-card { background-color: #21242b !important; }
.text-main { color: #e7e9ee !important; }
.text-muted { color: #a4a9b4 !important; }
.border-card { border-color: #343842 !important; }
.divider { border-color: #343842 !important; }
.btn-primary-cell { background-color: #3a4350 !important; }
.btn-secondary-cell { background-color: #2c313a !important; }
.btn-secondary-cell a { color: #e7e9ee !important; }
}
</style>
</head>
<body>
<p>Hi {{username}}!<br>
{% if counter == 1 %}
There is 1 new message waiting in quarantine:<br>
{% else %}
There are {{counter}} new messages waiting in quarantine:<br>
{% endif %}
<table>
<tr><th>Subject</th><th>Sender</th><th class="mob">Score</th><th class="mob">Action</th><th class="mob">Arrived on</th>{% if quarantine_acl == 1 %}<th>Actions</th>{% endif %}</tr>
{% for line in meta|reverse %}
</head>
<body class="bg-page" style="margin:0; padding:0; background-color:#f4f5f7;">
<div style="display:none; max-height:0; overflow:hidden; mso-hide:all; font-size:1px; line-height:1px; color:#f4f5f7;">
{% if counter == 1 %}1 new message is waiting in your quarantine.{% else %}{{ counter }} new messages are waiting in your quarantine.{% endif %}
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
</div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" class="bg-page" style="background-color:#f4f5f7;">
<tr>
<td>{{ line.subject|e }}</td>
<td>{{ line.sender|e }}</td>
<td class="mob">{{ line.score }}</td>
{% if line.action == "reject" %}
<td class="mob">Rejected</td>
{% else %}
<td class="mob">Sent to Junk folder</td>
{% endif %}
<td class="mob">{{ line.created }}</td>
{% if quarantine_acl == 1 %}
{% if line.action == "reject" %}
<td class="fixed"><a href="https://{{ hostname }}/qhandler/release/{{ line.qhash }}">Release to inbox</a> | <a href="https://{{ hostname }}/qhandler/delete/{{ line.qhash }}">delete</a></td>
{% else %}
<td class="fixed"><a href="https://{{ hostname }}/qhandler/release/{{ line.qhash }}">Send copy to inbox</a> | <a href="https://{{ hostname }}/qhandler/delete/{{ line.qhash }}">delete</a></td>
{% endif %}
{% endif %}
<td align="center" style="padding:24px 12px;">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="container" style="width:600px; max-width:600px;">
<!-- Header -->
<tr>
<td style="padding:0;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td class="px bg-card border-card" style="border-radius:12px 12px 0 0; background-color:#ffffff; border-bottom:1px solid #e4e7ec; padding:24px 32px;">
<span class="text-main" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; font-size:18px; font-weight:700; color:#1f2430; letter-spacing:0.2px;">
&#9993;&nbsp; Quarantine notification
</span>
</td>
</tr>
</table>
</td>
</tr>
<!-- Body -->
<tr>
<td class="bg-card" style="background-color:#ffffff; border-radius:0 0 12px 12px; padding:32px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<!-- Greeting -->
<tr>
<td class="px" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; padding-bottom:8px;">
<p class="text-main" style="margin:0 0 6px 0; font-size:16px; color:#1f2430;">Hi {{ username }},</p>
<p class="text-muted" style="margin:0; font-size:15px; line-height:22px; color:#5b6472;">
{% if counter == 1 %}
There is <b>1 new message</b> waiting in your quarantine.
{% else %}
There are <b>{{ counter }} new messages</b> waiting in your quarantine.
{% endif %}
</p>
</td>
</tr>
<!-- Messages -->
{% for line in meta|reverse %}
<tr>
<td style="padding-top:18px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" class="msg-card bg-card border-card" style="background-color:#ffffff; border:1px solid #e4e7ec; border-radius:10px;">
<tr>
<td style="padding:16px 18px; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
<!-- Subject + status -->
<p class="text-main" style="margin:0 0 10px 0; font-size:15px; font-weight:600; line-height:21px; color:#1f2430;">{{ line.subject|e }}</p>
{% if line.action == "reject" %}
<span style="display:inline-block; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.4px; color:#ffffff; background-color:#d64545; border-radius:6px; padding:3px 9px;">Rejected</span>
{% else %}
<span style="display:inline-block; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.4px; color:#ffffff; background-color:#c2891b; border-radius:6px; padding:3px 9px;">Sent to Junk</span>
{% endif %}
<span class="text-muted" style="display:inline-block; font-size:12px; color:#8a93a2; padding-left:8px;">Score {{ line.score }}</span>
<!-- Meta -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-top:12px;">
<tr>
<td class="text-muted" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; font-size:13px; line-height:20px; color:#5b6472;">
<span class="text-muted" style="color:#8a93a2;">From</span>&nbsp; {{ line.sender|e }}<br>
<span class="text-muted" style="color:#8a93a2;">Arrived</span>&nbsp; {{ line.created }}
</td>
</tr>
</table>
{% if quarantine_acl == 1 %}
<!-- Actions -->
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-top:14px;">
<tr>
<td class="btn-cell" style="padding-right:8px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;">
<tr>
<td align="center" class="btn-primary-cell" style="border-radius:8px; background-color:#2b333f;">
<a class="btn-a" href="https://{{ hostname }}/qhandler/release/{{ line.qhash }}" target="_blank" style="display:inline-block; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; font-size:14px; font-weight:600; color:#ffffff; padding:10px 18px; border-radius:8px;">{% if line.action == "reject" %}Release to inbox{% else %}Send copy to inbox{% endif %}</a>
</td>
</tr>
</table>
</td>
<td class="btn-cell">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;">
<tr>
<td align="center" class="btn-secondary-cell" style="border-radius:8px; background-color:#eceef1;">
<a class="btn-a" href="https://{{ hostname }}/qhandler/delete/{{ line.qhash }}" target="_blank" style="display:inline-block; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; font-size:14px; font-weight:600; color:#2b333f; padding:10px 18px; border-radius:8px;">Delete</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
{% endif %}
</td>
</tr>
</table>
</td>
</tr>
{% endfor %}
<!-- Footer note -->
<tr>
<td class="px" style="padding-top:24px;">
<hr class="divider" style="border:0; border-top:1px solid #e4e7ec; margin:0 0 16px 0;">
<p class="text-muted" style="margin:0; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; font-size:12px; line-height:18px; color:#8a93a2;">
This is an automated message from your mail server ({{ hostname }}). You receive it because quarantine notifications are enabled for your mailbox.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
{% endfor %}
</table>
</p>
</body>
</table>
</body>
</html>

View file

@ -1,4 +1,29 @@
<?php
// Resolve the default quarantine setting for new mailboxes.
// Order of precedence: server-wide default (Redis, set in the admin quarantine
// settings) -> compile-time default ($MAILBOX_DEFAULT_ATTRIBUTES in vars.inc.php).
// $key is either 'notification' or 'category'.
function get_quarantine_default($key) {
global $redis, $MAILBOX_DEFAULT_ATTRIBUTES;
$redis_keys = array(
'notification' => 'Q_DEF_NOTIFICATION',
'category' => 'Q_DEF_CATEGORY'
);
$attr_keys = array(
'notification' => 'quarantine_notification',
'category' => 'quarantine_category'
);
if (!isset($redis_keys[$key])) {
return null;
}
try {
$value = $redis->Get($redis_keys[$key]);
}
catch (RedisException $e) {
$value = false;
}
return ($value !== false && $value !== '') ? $value : $MAILBOX_DEFAULT_ATTRIBUTES[$attr_keys[$key]];
}
function mailbox($_action, $_type, $_data = null, $_extra = null) {
global $pdo;
global $redis;
@ -1109,8 +1134,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
$dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval(get_quarantine_default('notification'));
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval(get_quarantine_default('category'));
// Validate quarantine_category
if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) {
$_SESSION['return'][] = array(
@ -1740,8 +1765,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']);
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval(get_quarantine_default('notification'));
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval(get_quarantine_default('category'));
// Validate quarantine_category
if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) {
$_SESSION['return'][] = array(

View file

@ -6,6 +6,7 @@ function quarantine($_action, $_data = null) {
global $pdo;
global $redis;
global $lang;
global $MAILBOX_DEFAULT_ATTRIBUTES;
$_data_log = $_data;
switch ($_action) {
case 'quick_delete':
@ -22,7 +23,7 @@ function quarantine($_action, $_data = null) {
return false;
}
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
WHERE `qhash` = :hash
WHERE `qhash` = :hash
AND user_acl.quarantine = 1
AND rcpt IN (SELECT username FROM mailbox)');
$stmt->execute(array(':hash' => $hash));
@ -65,7 +66,7 @@ function quarantine($_action, $_data = null) {
return false;
}
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
WHERE `qhash` = :hash
WHERE `qhash` = :hash
AND `user_acl`.`quarantine` = 1
AND `username` IN (SELECT `username` FROM `mailbox`)');
$stmt->execute(array(':hash' => $hash));
@ -331,6 +332,9 @@ function quarantine($_action, $_data = null) {
$max_age = 365;
}
$exclude_domains = (array)$_data['exclude_domains'];
// Server-wide default quarantine notification settings for new mailboxes
$def_notification = (in_array($_data['def_notification'], array('never', 'hourly', 'daily', 'weekly'))) ? $_data['def_notification'] : 'never';
$def_category = (in_array($_data['def_category'], array('add_header', 'reject', 'all'))) ? $_data['def_category'] : 'reject';
try {
$redis->Set('Q_RETENTION_SIZE', intval($retention_size));
$redis->Set('Q_MAX_SIZE', intval($max_size));
@ -343,6 +347,8 @@ function quarantine($_action, $_data = null) {
$redis->Set('Q_REDIRECT', $redirect);
$redis->Set('Q_SUBJ', $subject);
$redis->Set('Q_HTML', $html);
$redis->Set('Q_DEF_NOTIFICATION', $def_notification);
$redis->Set('Q_DEF_CATEGORY', $def_category);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
@ -358,6 +364,55 @@ function quarantine($_action, $_data = null) {
'msg' => 'saved_settings'
);
}
// Force the server-wide default onto all existing mailboxes (overwrites user overrides)
elseif ($_data['action'] == 'apply_existing') {
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
// Resolve the default to apply: prefer the values submitted with the form
// (the settings form is merged into this request), fall back to the stored default.
$def_notification = (in_array($_data['def_notification'], array('never', 'hourly', 'daily', 'weekly'))) ? $_data['def_notification'] : get_quarantine_default('notification');
$def_category = (in_array($_data['def_category'], array('add_header', 'reject', 'all'))) ? $_data['def_category'] : get_quarantine_default('category');
try {
// Keep the stored default in sync with what we are forcing onto mailboxes
$redis->Set('Q_DEF_NOTIFICATION', $def_notification);
$redis->Set('Q_DEF_CATEGORY', $def_category);
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`,
'$.quarantine_notification', :quarantine_notification,
'$.quarantine_category', :quarantine_category)");
$stmt->execute(array(
':quarantine_notification' => $def_notification,
':quarantine_category' => $def_category
));
$affected = $stmt->rowCount();
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('quarantine_defaults_applied', $affected)
);
}
// Release item
elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') {
if (!is_array($_data['id'])) {
@ -791,6 +846,8 @@ function quarantine($_action, $_data = null) {
if (empty($settings['html_tmpl'])) {
$settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl"));
}
$settings['def_notification'] = $redis->Get('Q_DEF_NOTIFICATION') ?: $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'];
$settings['def_category'] = $redis->Get('Q_DEF_CATEGORY') ?: $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category'];
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
@ -833,7 +890,7 @@ function quarantine($_action, $_data = null) {
)));
return false;
}
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
$stmt->execute(array(':hash' => $hash));
return $stmt->fetch(PDO::FETCH_ASSOC);
break;

View file

@ -315,6 +315,11 @@
"private_key": "Private key",
"quarantine": "Quarantine",
"quarantine_bcc": "Send a copy of all notifications (BCC) to this recipient:<br><small>Leave empty to disable. <b>Unsigned, unchecked mail. Should be delivered internally only.</b></small>",
"quarantine_defaults_apply": "Apply default to all existing mailboxes",
"quarantine_defaults_apply_confirm": "This will overwrite the quarantine notification interval and category of ALL existing mailboxes with the default selected above. User overrides will be lost. Continue?",
"quarantine_defaults_apply_info": "Applies the default to <b>all existing mailboxes</b> and overwrites individual user settings. New mailboxes always inherit the default above.",
"quarantine_defaults_category": "Default notification category for new mailboxes",
"quarantine_defaults_notification": "Default notification interval for new mailboxes",
"quarantine_exclude_domains": "Exclude domains and alias-domains",
"quarantine_max_age": "Maximum age in days<br><small>Value must be equal to or greater than 1 day.</small>",
"quarantine_max_score": "Discard notification if spam score of a mail is higher than this value:<br><small>Defaults to 9999.0</small>",
@ -1190,6 +1195,7 @@
"pushover_settings_edited": "Pushover settings successfully set, please verify credentials.",
"qlearn_spam": "Message ID %s was learned as spam and deleted",
"queue_command_success": "Queue command completed successfully",
"quarantine_defaults_applied": "Default quarantine notification settings applied to %s existing mailbox(es)",
"recipient_map_entry_deleted": "Recipient map ID %s has been deleted",
"recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved",
"recovery_email_sent": "Recovery email sent to %s",

View file

@ -62,6 +62,34 @@
</div>
</div>
<hr>
<div class="row mb-2">
<label class="col-sm-4 control-label text-sm-end" for="quarantine_def_notification">{{ lang.admin.quarantine_defaults_notification|raw }}</label>
<div class="col-sm-8 col-md-6 col-lg-4 col-xl-3">
<select data-width="100%" id="quarantine_def_notification" name="def_notification" class="selectpicker" title="{{ lang.tfa.select }}">
<option {% if q_data.def_notification == 'never' %}selected{% endif %} value="never">{{ lang.user.never }}</option>
<option {% if q_data.def_notification == 'hourly' %}selected{% endif %} value="hourly">{{ lang.user.hourly }}</option>
<option {% if q_data.def_notification == 'daily' %}selected{% endif %} value="daily">{{ lang.user.daily }}</option>
<option {% if q_data.def_notification == 'weekly' %}selected{% endif %} value="weekly">{{ lang.user.weekly }}</option>
</select>
</div>
</div>
<div class="row mb-2">
<label class="col-sm-4 control-label text-sm-end" for="quarantine_def_category">{{ lang.admin.quarantine_defaults_category|raw }}</label>
<div class="col-sm-8 col-md-6 col-lg-4 col-xl-3">
<select data-width="100%" id="quarantine_def_category" name="def_category" class="selectpicker" title="{{ lang.tfa.select }}">
<option {% if q_data.def_category == 'reject' %}selected{% endif %} value="reject">{{ lang.user.q_reject }}</option>
<option {% if q_data.def_category == 'add_header' %}selected{% endif %} value="add_header">{{ lang.user.q_add_header }}</option>
<option {% if q_data.def_category == 'all' %}selected{% endif %} value="all">{{ lang.user.q_all }}</option>
</select>
</div>
</div>
<div class="row mb-4">
<div class="col-sm-8 offset-sm-4">
<button type="button" class="btn btn-sm d-block d-sm-inline btn-warning" data-action="edit_selected" data-item="self" data-id="quarantine" data-api-url='edit/quarantine' data-api-attr='{"action":"apply_existing"}' onclick="if(!confirm('{{ lang.admin.quarantine_defaults_apply_confirm }}')){event.stopImmediatePropagation();event.preventDefault();return false;}"><i class="bi bi-arrow-repeat"></i> {{ lang.admin.quarantine_defaults_apply }}</button>
<p class="text-muted mt-2 mb-0">{{ lang.admin.quarantine_defaults_apply_info|raw }}</p>
</div>
</div>
<hr>
<div class="row mb-2">
<label class="col-sm-4 control-label text-sm-end" for="quarantine_release_format">{{ lang.admin.quarantine_release_format }}:</label>
<div class="col-sm-8 col-md-6 col-lg-4 col-xl-3">