瀏覽代碼

[Web] Queue manager for Postfix
[Web] Add sogo_access mail attribute
[Web] Allow to wipe SOGo profiles

André 6 年之前
父節點
當前提交
e30dfd6751

+ 45 - 0
data/web/admin.php

@@ -11,6 +11,7 @@ $tfa_data = get_tfa();
     <li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li>
     <li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li>
     <li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li>
+    <li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab">Queue manager</a></li>
   </ul>
 
   <div class="tab-content" style="padding-top:20px">
@@ -855,8 +856,52 @@ $tfa_data = get_tfa();
         </form>
       </div>
     </div>
+  </div>
 
+  <div role="tabpanel" class="tab-pane" id="tab-mailq">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        Queue manager <span class="badge badge-info table-lines"></span>
+        <div class="btn-group pull-right">
+          <button class="btn btn-xs btn-default refresh_table" data-draw="draw_queue" data-table="queuetable"><?=$lang['admin']['refresh'];?></button>
+        </div>
+      </div>
+      <div class="panel-body">
+      <div class="table-responsive">
+        <table class="table table-striped table-condensed" id="queuetable"></table>
+      </div>
+      <div class="mass-actions-admin">
+        <div class="btn-group">
+          <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="mailqitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
+          <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
+          <ul class="dropdown-menu">
+            <li><a data-toggle="tooltip" title="postqueue -i" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"deliver"}' href="#">Deliver</a></li>
+            <li><a data-toggle="tooltip" title="postsuper -H" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"unhold"}' href="#">Unhold</a></li>
+            <li><a data-toggle="tooltip" title="postsuper -h" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"hold"}' href="#">Hold</a></li>
+            <li role="separator" class="divider"></li>
+            <li><a data-toggle="tooltip" title="postsuper -d" data-action="delete_selected" data-id="mailqitems" data-api-url='delete/mailq' href="#"><?=$lang['mailbox']['remove'];?></a></li>
+          </ul>
+          <a class="btn btn-sm btn-primary"
+            data-action="edit_selected"
+            data-item="mailqitems-all"
+            data-api-url='edit/mailq'
+            data-api-attr='{"action":"flush"}'
+            data-toggle="tooltip" title="postqueue -f"
+            href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['admin']['flush_queue'];?></a>
+          <a class="btn btn-sm btn-danger"
+            id="super_delete"
+            data-action="edit_selected"
+            data-item="mailqitems-all"
+            data-api-url='edit/mailq'
+            data-api-attr='{"action":"super_delete"}'
+            data-toggle="tooltip" title="postsuper -d ALL"
+            href="#"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?=$lang['admin']['delete_queue'];?></a>
+        </div>
+      </div>
+      </div>
+    </div>
   </div>
+
 </div> <!-- /container -->
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/admin.php';

+ 1 - 1
data/web/debug.php

@@ -35,7 +35,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
       <div class="tab-content" style="padding-top:20px">
         <div class="debug-log-info"><?=sprintf($lang['debug']['log_info'], getenv('LOG_LINES') + 1);?></div>
         <?php
-          $exec_fields = array('cmd' => 'df', 'dir' => '/var/vmail');
+          $exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
           $vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true));
         ?>
         <div role="tabpanel" class="tab-pane active" id="tab-containers">

+ 9 - 0
data/web/edit.php

@@ -478,6 +478,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
           <input type="hidden" value="default" name="sender_acl">
           <input type="hidden" value="0" name="active">
           <input type="hidden" value="0" name="force_pw_update">
+          <input type="hidden" value="0" name="sogo_access">
           <div class="form-group">
             <label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?>:</label>
             <div class="col-sm-10">
@@ -573,6 +574,14 @@ if (isset($_SESSION['mailcow_cc_role'])) {
               </div>
             </div>
           </div>
+          <div class="form-group">
+            <div class="col-sm-offset-2 col-sm-10">
+              <div class="checkbox">
+              <label><input type="checkbox" value="1" name="sogo_access" <?=($result['attributes']['sogo_access']=="1") ? "checked" : null;?>> <?=$lang['edit']['sogo_access'];?></label>
+              <small class="help-block"><?=$lang['edit']['sogo_access_info'];?></small>
+              </div>
+            </div>
+          </div>
           <div class="form-group">
             <div class="col-sm-offset-2 col-sm-10">
               <button class="btn btn-success" data-action="edit_selected" data-id="editmailbox" data-item="<?=htmlspecialchars($result['username']);?>" data-api-url='edit/mailbox' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>

+ 16 - 0
data/web/inc/ajax/queue_manager.php

@@ -0,0 +1,16 @@
+<?php
+session_start();
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+header('Content-Type: text/plain');
+if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') {
+  exit();
+}
+$docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq'));
+
+if (isset($docker_return['type']['danger'])) {
+  echo "Cannot load mail queue: " . $docker_return['msg'];
+}
+else {
+  echo $docker_return;
+}
+?>

+ 1 - 1
data/web/inc/functions.inc.php

@@ -1225,7 +1225,7 @@ function rspamd_ui($action, $data = null) {
         );
         return false;
       }
-      $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json'));
+      $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json'));
       if ($docker_return_array = json_decode($docker_return, true)) {
         if ($docker_return_array['type'] == 'success') {
           $_SESSION['return'][] =  array(

+ 78 - 10
data/web/inc/functions.mailbox.inc.php

@@ -739,8 +739,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             array(
               'force_pw_update' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'])),
               'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])),
-              'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])))
-            );
+              'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])),
+              'sogo_access' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']))
+            )
+          );
           if (!is_valid_domain_name($domain)) {
             $_SESSION['return'][] = array(
               'type' => 'danger',
@@ -1881,6 +1883,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             if (!empty($is_now)) {
               $active     = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
               (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
+              (int)$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
               $name       = (!empty($_data['name'])) ? $_data['name'] : $is_now['name'];
               $domain     = $is_now['domain'];
               $quota_m    = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576);
@@ -2082,13 +2085,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
                 `active` = :active,
                 `name`= :name,
                 `quota` = :quota_b,
-                `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update)
+                `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
+                `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access)
                   WHERE `username` = :username");
             $stmt->execute(array(
               ':active' => $active,
               ':name' => $name,
               ':quota_b' => $quota_b,
               ':force_pw_update' => $force_pw_update,
+              ':sogo_access' => $sogo_access,
               ':username' => $username
             ));
             $_SESSION['return'][] = array(
@@ -2384,20 +2389,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             $_data = $_SESSION['mailcow_cc_username'];
           }
           $exec_fields = array(
-            'cmd' => 'sieve_list',
+            'cmd' => 'sieve',
+            'task' => 'list',
             'username' => $_data
           );
-          $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
-          $filters = array_filter(explode(PHP_EOL, $filters));
+          $filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
+          $filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters));
           foreach ($filters as $filter) {
             if (preg_match('/.+ ACTIVE/i', $filter)) {
               $exec_fields = array(
-                'cmd' => 'sieve_print',
+                'cmd' => 'sieve',
+                'task' => 'print',
                 'script_name' => substr($filter, 0, -7),
                 'username' => $_data
               );
-              $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
-              return preg_replace('/^.+\n/', '', $filters);
+              $script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
+              // Remove first line
+              return preg_replace('/^.+\n/', '', $script);
             }
           }
           return false;
@@ -3081,6 +3089,66 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             );
           }
         break;
+        case 'sogo_profile':
+          if (!is_array($_data['username'])) {
+            $usernames = array();
+            $usernames[] = $_data['username'];
+          }
+          else {
+            $usernames = $_data['username'];
+          }
+          if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) {
+            $_SESSION['return'][] = array(
+              'type' => 'danger',
+              'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+              'msg' => 'access_denied'
+            );
+            return false;
+          }
+          foreach ($usernames as $username) {
+            if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
+              $_SESSION['return'][] = array(
+                'type' => 'danger',
+                'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+                'msg' => 'access_denied'
+              );
+              continue;
+            }
+            $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
+            $stmt->execute(array(
+              ':username' => $username
+            ));
+            $_SESSION['return'][] = array(
+              'type' => 'success',
+              'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+              'msg' => array('sogo_profile_reset', htmlspecialchars($username))
+            );
+          }
+        break;
         case 'domain':
           if (!is_array($_data['domain'])) {
             $domains = array();
@@ -3119,7 +3187,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               );
               continue;
             }
-            $exec_fields = array('cmd' => 'maildir_cleanup', 'maildir' => $domain);
+            $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain);
             $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
             if ($maildir_gc['type'] != 'success') {
               $_SESSION['return'][] = array(

+ 72 - 0
data/web/inc/functions.mailq.inc.php

@@ -0,0 +1,72 @@
+<?php
+function mailq($_action, $_data = null) {
+  if ($_SESSION['mailcow_cc_role'] != "admin") {
+    $_SESSION['return'][] = array(
+      'type' => 'danger',
+      'log' => array(__FUNCTION__, $_action, $_data),
+      'msg' => 'access_denied'
+    );
+    return false;
+  }
+  function process_mailq_output($returned_output, $_action, $_data) {
+    if ($returned_output !== NULL) {
+      if (isset($returned_output['type']) && $returned_output['type'] == 'danger') {
+        $_SESSION['return'][] = array(
+          'type' => 'danger',
+          'log' => array('mailq', $_action, $_data),
+          'msg' => 'Error: ' . $returned_output['msg']
+        );
+      }
+      if (isset($returned_output['type']) && $returned_output['type'] == 'success') {
+        $_SESSION['return'][] = array(
+          'type' => 'success',
+          'log' => array('mailq', $_action, $_data),
+          'msg' => 'queue_command_success'
+        );
+      }
+    }
+    else {
+      $_SESSION['return'][] = array(
+        'type' => 'danger',
+        'log' => array('mailq', $_action, $_data),
+        'msg' => 'unknown'
+      );
+    }
+  }
+	global $lang;
+  switch ($_action) {
+    case 'delete':
+      if (!is_array($_data['qid'])) {
+        $qids = array();
+        $qids[] = $_data['qid'];
+      }
+      else {
+        $qids = $_data['qid'];
+      }
+      $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'delete', 'items' => $qids));
+      process_mailq_output(json_decode($docker_return, true), $_action, $_data);
+    break;
+    case 'edit':
+      if (in_array($_data['action'], array('hold', 'unhold', 'deliver'))) {
+        if (!is_array($_data['qid'])) {
+          $qids = array();
+          $qids[] = $_data['qid'];
+        }
+        else {
+          $qids = $_data['qid'];
+        }
+        if (!empty($qids)) {
+          $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'], 'items' => $qids));
+          process_mailq_output(json_decode($docker_return, true), $_action, $_data);
+        }
+      }
+      if (in_array($_data['action'], array('flush', 'super_delete'))) {
+        $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action']));
+        process_mailq_output(json_decode($docker_return, true), $_action, $_data);
+      }
+    break;
+    case 'get':
+      // todo: move get from json_api here
+    break;
+  }
+}

+ 4 - 2
data/web/inc/init_db.inc.php

@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "07102018_1502";
+    $db_version = "22102018_1502";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -291,6 +291,7 @@ function init_db_schema() {
           "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
           ),
         "keys" => array(
@@ -963,7 +964,8 @@ DELIMITER ;';
 
     // Migrate tls_enforce_* options and add force_pw_update attribute
     $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;");
-    $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.force_pw_update', 0) WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;");
+    $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;");
+    $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.sogo_access') IS NULL;");
     foreach($tls_options as $tls_user => $tls_options) {
       $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in),
         `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out)

+ 1 - 0
data/web/inc/prerequisites.inc.php

@@ -142,6 +142,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php';

+ 3 - 0
data/web/inc/vars.inc.php

@@ -139,3 +139,6 @@ $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false;
 
 // Force password change on next login (only allows login to mailcow UI)
 $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
+
+// Force password change on next login (only allows login to mailcow UI)
+$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;

+ 79 - 23
data/web/js/admin.js

@@ -5,18 +5,6 @@ jQuery(function($){
   var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
-  $("#import_dkim_legend").on('click', function(e) {
-    e.preventDefault();
-    $('#import_dkim_arrow').toggleClass("animation"); 
-  });
-  $("#duplicate_dkim_legend").on('click', function(e) {
-    e.preventDefault();
-    $('#duplicate_dkim_arrow').toggleClass("animation"); 
-  });
-  $("#api_legend").on('click', function(e) {
-    e.preventDefault();
-    $('#api_arrow').toggleClass("animation"); 
-  });
   $("#rspamd_preset_1").on('click', function(e) {
     e.preventDefault();
     $("form[data-id=rsetting]").find("#adminRspamdSettingsDesc").val(lang.rsettings_preset_1);
@@ -35,15 +23,40 @@ jQuery(function($){
      });
      $('#dkim_add_domains').val(domains);
   });
-  $("#mass_exclude").change(function(){ 
-    $("#mass_include").selectpicker('deselectAll');
-  });
-  $("#mass_include").change(function(){ 
-    $("#mass_exclude").selectpicker('deselectAll');
-  });
-  $("#mass_disarm").click(function() {
-    $("#mass_send").attr("disabled", !this.checked);
+  $("#import_dkim_legend").on('click', function(e) { e.preventDefault(); $('#import_dkim_arrow').toggleClass("animation"); });
+  $("#duplicate_dkim_legend").on('click', function(e) { e.preventDefault(); $('#duplicate_dkim_arrow').toggleClass("animation"); });
+  $("#api_legend").on('click', function(e) { e.preventDefault(); $('#api_arrow').toggleClass("animation"); });
+  $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
+  $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
+  $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
+  $("#super_delete").click(function() { return confirm(lang.queue_ays); });
+  $(".refresh_table").on('click', function(e) {
+    e.preventDefault();
+    var table_name = $(this).data('table');
+    $('#' + table_name).find("tr.footable-empty").remove();
+    draw_table = $(this).data('draw');
+    eval(draw_table + '()');
   });
+  if (localStorage.getItem("current_page") === null) {
+    var current_page = {};
+  } else {
+    var current_page = JSON.parse(localStorage.getItem('current_page'));
+  }
+  function table_admin_ready(ft, name) {
+    heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+    var ft_paging = ft.use(FooTable.Paging)
+    $(heading).children('.table-lines').text(function(){
+      return ft_paging.totalRows;
+    })
+    if (current_page[name]) {
+      ft_paging.goto(parseInt(current_page[name]))
+    }
+  }
+  function paging_admin_after(ft, name) {
+    var ft_paging = ft.use(FooTable.Paging)
+    current_page[name] = ft_paging.current;
+    localStorage.setItem('current_page', JSON.stringify(current_page));
+  }
   function draw_domain_admins() {
     ft_domainadmins = FooTable.init('#domainadminstable', {
       "columns": [
@@ -150,6 +163,43 @@ jQuery(function($){
       "sorting": {"enabled": true}
     });
   }
+  function draw_queue() {
+    ft_queuetable = FooTable.init('#queuetable', {
+      "columns": [
+        {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
+        {"name":"queue_id","type":"text","title":"QID","style":{"width":"50px"}},
+        {"name":"queue_name","type":"text","title":"Queue","style":{"width":"120px"}},
+        {"name":"arrival_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.arrival_time,"style":{"width":"170px"}},
+        {"name":"message_size","style":{"whiteSpace":"nowrap"},"title":lang.message_size,"formatter": function(value){
+          return humanFileSize(value);
+        }},
+        {"name":"sender","title":lang.sender, "type": "text","breakpoints":"xs sm"},
+        {"name":"recipients","title":lang.recipients, "type": "text","breakpoints":"xs sm"},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/mailq',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw forwarding hosts table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'queuetable');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          table_admin_ready(ft, 'queuetable');
+        },
+        "after.ft.paging": function(e, ft){
+          paging_admin_after(ft, 'queuetable');
+        }
+      }
+    });
+  }
 
   function process_table_data(data, table) {
     if (table == 'relayhoststable') {
@@ -161,6 +211,11 @@ jQuery(function($){
           '</div>';
         item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
       });
+    } else if (table == 'queuetable') {
+      $.each(data, function (i, item) {
+        item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
+        item.recipients = JSON.stringify(item.recipients);
+      });
     } else if (table == 'forwardinghoststable') {
       $.each(data, function (i, item) {
         item.action = '<div class="btn-group">' +
@@ -206,6 +261,7 @@ jQuery(function($){
   draw_admins();
   draw_fwd_hosts();
   draw_relayhosts();
+  draw_queue();
   // Relayhost
   $('#testRelayhostModal').on('show.bs.modal', function (e) {
     $('#test_relayhost_result').text("-");
@@ -225,9 +281,9 @@ jQuery(function($){
         dataType: 'text',
         data: $('#test_relayhost_form').serialize(),
         complete: function (data) {
-            $('#test_relayhost_result').html(data.responseText);
-            $('#test_relayhost').prop("disabled",false);
-            $('#test_relayhost').text(prev);
+          $('#test_relayhost_result').html(data.responseText);
+          $('#test_relayhost').prop("disabled",false);
+          $('#test_relayhost').text(prev);
         }
     });
   })

+ 4 - 41
data/web/js/mailbox.js

@@ -58,7 +58,6 @@ $(document).ready(function() {
       $(this.$domain).closest("select").selectpicker();
     }
   });
-
   // Auto-fill domain quota when adding new domain
   auto_fill_quota = function(domain) {
 		$.get("/api/v1/get/domain/" + domain, function(data){
@@ -80,7 +79,6 @@ $(document).ready(function() {
     auto_fill_quota($('#addSelectDomain').val());
 	});
   auto_fill_quota($('#addSelectDomain').val());
-
   $(".generate_password").click(function( event ) {
     event.preventDefault();
     $('[data-hibp]').trigger('input');
@@ -90,7 +88,6 @@ $(document).ready(function() {
     $(this).closest("form").find("input[name='password']").val(random_passwd);
     $(this).closest("form").find("input[name='password2']").val(random_passwd);
   });
-
   $(".goto_checkbox").click(function( event ) {
    $("form[data-id='add_alias'] .goto_checkbox").not(this).prop('checked', false);
     if ($("form[data-id='add_alias'] .goto_checkbox:checked").length > 0) {
@@ -108,7 +105,6 @@ $(document).ready(function() {
       $("#textarea_alias_goto").removeAttr('disabled');
     }
   });
-
   // Log modal
   $('#syncjobLogModal').on('show.bs.modal', function(e) {
     var syncjob_id = $(e.relatedTarget).data('syncjob-id');
@@ -124,7 +120,6 @@ $(document).ready(function() {
       }
     });
   });
-
   // Log modal
   $('#dnsInfoModal').on('show.bs.modal', function(e) {
     var domain = $(e.relatedTarget).data('domain');
@@ -141,13 +136,11 @@ $(document).ready(function() {
       }
     });
   });
-
   // Sieve data modal
   $('#sieveDataModal').on('show.bs.modal', function(e) {
     var sieveScript = $(e.relatedTarget).data('sieve-script');
     $(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>');
   });
-
   // Set line numbers for textarea
   $("#script_data").numberedtextarea({allowTabChar: true});
   // Disable submit button on script change
@@ -155,7 +148,6 @@ $(document).ready(function() {
     $('#add_filter_btns > #add_item').attr({"disabled": true});
     $('#validation_msg').html('-');
 	});
-
   // Validate script data
   $("#validate_sieve").click(function( event ) {
     event.preventDefault();
@@ -185,21 +177,8 @@ $(document).ready(function() {
 });
 jQuery(function($){
   // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
-  var entityMap = {
-  '&': '&amp;',
-  '<': '&lt;',
-  '>': '&gt;',
-  '"': '&quot;',
-  "'": '&#39;',
-  '/': '&#x2F;',
-  '`': '&#x60;',
-  '=': '&#x3D;'
-  };
-  function escapeHtml(string) {
-    return String(string).replace(/[&<>"'`=\/]/g, function (s) {
-      return entityMap[s];
-    });
-  }
+  var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
+  function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
   if (localStorage.getItem("current_page") === null) {
     var current_page = {};
   } else {
@@ -210,23 +189,7 @@ jQuery(function($){
     var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
     return re.test(email);
   }
-  // Calculation human readable file sizes
-  function humanFileSize(bytes) {
-    if(Math.abs(bytes) < 1024) {
-        return bytes + ' B';
-    }
-    var units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
-    var u = -1;
-    do {
-        bytes /= 1024;
-        ++u;
-    } while(Math.abs(bytes) >= 1024 && u < units.length - 1);
-    return bytes.toFixed(1)+' '+units[u];
-  }
-  function unix_time_format(tm) {
-    var date = new Date(tm ? tm * 1000 : 0);
-    return date.toLocaleString();
-  }
+  function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
   $(".refresh_table").on('click', function(e) {
     e.preventDefault();
     var table_name = $(this).data('table');
@@ -241,7 +204,7 @@ jQuery(function($){
         .removeAttr("href")
         .attr("title", "Dual login cannot be used twice")
         .tooltip();
-    }
+      }
     heading = ft.$el.parents('.tab-pane').find('.panel-heading')
     var ft_paging = ft.use(FooTable.Paging)
     $(heading).children('.table-lines').text(function(){

+ 27 - 0
data/web/json_api.php

@@ -218,6 +218,24 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
             }
           break;
 
+          case "mailq":
+            $mailq_lines = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'list'));
+            $lines = 0;
+            // Hard limit to 1000 items
+            foreach (preg_split("/((\r?\n)|(\r\n?))/", $mailq_lines) as $mailq_item) if ($lines++ < 1000) {
+              if (empty($mailq_item) || $mailq_item == '1') {
+                continue;
+              }
+              $line[] = json_decode($mailq_item, true);
+            }
+            if (!isset($line) || empty($line)) {
+              echo '{}';
+            }
+            else {
+              echo json_encode($line);
+            }
+          break;
+
           case "rl-domain":
             switch ($object) {
               case "all":
@@ -974,6 +992,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "filter":
             process_delete_return(mailbox('delete', 'filter', array('id' => $items)));
           break;
+          case "mailq":
+            process_delete_return(mailq('delete', array('qid' => $items)));
+          break;
           case "qitem":
             process_delete_return(quarantine('delete', array('id' => $items)));
           break;
@@ -1017,6 +1038,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "eas_cache":
             process_delete_return(mailbox('delete', 'eas_cache', array('username' => $items)));
           break;
+          case "sogo_profile":
+            process_delete_return(mailbox('delete', 'sogo_profile', array('username' => $items)));
+          break;
           case "domain-admin":
             process_delete_return(domain_admin('delete', array('username' => $items)));
           break;
@@ -1088,6 +1112,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "quarantine":
             process_edit_return(quarantine('edit', $attr));
           break;
+          case "mailq":
+            process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
+          break;
           case "time_limited_alias":
             process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr)));
           break;

+ 17 - 0
data/web/lang/lang.de.php

@@ -31,6 +31,7 @@ $lang['danger']['yotp_verification_failed'] = "Yubico OTP Verifizierung fehlgesc
 $lang['danger']['ip_list_empty'] = "Liste erlaubter IPs darf nicht leer sein";
 $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI Passwort muss mindestens 6 Zeichen lang sein";
 $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt";
+$lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgeführt";
 $lang['danger']['unknown'] = "Ein unbekannter Fehler trat auf";
 $lang['danger']['malformed_username'] = "Benutzername hat falsches Format";
 $lang['info']['awaiting_tfa_confirmation'] = "Warte auf TFA Verifizierung";
@@ -134,6 +135,7 @@ $lang['success']['domain_admin_removed'] = 'Domain-Administrator %s wurde entfer
 $lang['success']['admin_removed'] = 'Administrator %s wurde entfernt';
 $lang['success']['mailbox_removed'] = 'Mailbox %s wurde entfernt';
 $lang['success']['eas_reset'] = "ActiveSync Gerät des Benutzers %s wurden zurückgesetzt";
+$lang['success']['sogo_profile_reset'] = "ActiveSync Gerät des Benutzers %s wurden zurückgesetzt";
 $lang['success']['resource_removed'] = 'Ressource %s wurde entfernt';
 $lang['warning']['cannot_delete_self'] = 'Kann derzeit eingeloggten Benutzer nicht entfernen';
 $lang['warning']['no_active_admin'] = 'Kann letzten aktiven Administrator nicht deaktivieren';
@@ -221,10 +223,15 @@ $lang['user']['tag_in_none'] = 'Nichts tun';
 $lang['user']['tag_help_explain'] = 'Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt ("INBOX/Facebook").<br>
 In Betreff: Der Name des Tags wird dem Betreff angefügt, etwa "[Facebook] Meine Neuigkeiten".';
 $lang['user']['tag_help_example'] = 'Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org';
+
 $lang['user']['eas_reset'] = 'ActiveSync Geräte-Cache zurücksetzen';
 $lang['user']['eas_reset_now'] = 'Jetzt zurücksetzen';
 $lang['user']['eas_reset_help'] = 'In vielen Fällen kann ein ActiveSync Profil durch das Zurücksetzen des Caches repariert werden.<br><b>Vorsicht:</b> Alle Elemente werden erneut heruntergeladen!';
 
+$lang['user']['sogo_profile_reset'] = 'SOGo Profil zurücksetzen';
+$lang['user']['sogo_profile_reset_now'] = 'Profil jetzt zurücksetzen';
+$lang['user']['sogo_profile_reset_help'] = 'Das Profil wird zuzüglich aller Daten <b>unwiederbringlich gelöscht</b>.';
+
 $lang['user']['encryption'] = 'Verschlüsselung';
 $lang['user']['username'] = 'Benutzername';
 $lang['user']['last_run'] = 'Letzte Ausführung';
@@ -331,6 +338,8 @@ $lang['edit']['target_address'] = 'Ziel-Adresse(n) <small>(getrennt durch Komma)
 $lang['edit']['active'] = 'Aktiv';
 $lang['edit']['force_pw_update'] = 'Erzwinge Passwortänderung bei nächstem Login';
 $lang['edit']['force_pw_update_info'] = 'Dem Benutzer wird lediglich der Zugang zur mailcow UI ermöglicht.';
+$lang['edit']['sogo_access'] = 'SOGo Zugriffsrecht';
+$lang['edit']['sogo_access_info'] = 'Zugriff auf SOGo erlauben oder verbieten. Diese Einstellung hat weder Einfluss auf den Zugang sonstiger Dienste noch entfernt sie ein vorhandenes SOGo Benutzerprofil.';
 $lang['edit']['target_domain'] = 'Ziel-Domain:';
 $lang['edit']['password'] = 'Passwort:';
 $lang['edit']['password_repeat'] = 'Passwort (Wiederholung):';
@@ -367,6 +376,7 @@ $lang['acl']['spam_policy'] = 'Blacklist/Whitelist';
 $lang['acl']['delimiter_action'] = 'Delimiter Aktionen (tags)';
 $lang['acl']['syncjobs'] = 'Sync Jobs';
 $lang['acl']['eas_reset'] = 'EAS-Cache zurücksetzen';
+$lang['acl']['sogo_profile_reset'] = 'SOGo Profil zurücksetzen';
 $lang['acl']['quarantine'] = 'Quarantäne';
 $lang['acl']['login_as'] = 'Einloggen als Mailbox-Benutzer';
 $lang['acl']['bcc_maps'] = 'BCC Maps';
@@ -520,6 +530,13 @@ $lang['admin']['rsettings_preset_2'] = 'Spam an Postmaster-Addressen nicht block
 $lang['admin']['rsettings_insert_preset'] = 'Beispiel "%s" laden';
 $lang['admin']['rsetting_add_rule'] = 'Regel hinzufügen';
 $lang['admin']['admin_domains'] = 'Domain-Zuweisungen';
+$lang['admin']['queue_ays'] = 'Soll die derzeitige Queue wirklich komplett bereinigt werden?';
+$lang['admin']['arrival_time'] = 'Ankunftszeit (Serverzeit)';
+$lang['admin']['message_size'] = 'Nachrichtengröße';
+$lang['admin']['sender'] = 'Sender';
+$lang['admin']['recipients'] = 'Empfänger';
+$lang['admin']['flush_queue'] = 'Flush Queue';
+$lang['admin']['delete_queue'] = 'Alle löschen';
 $lang['admin']['domain_admins'] = 'Domain-Administratoren';
 $lang['admin']['username'] = 'Benutzername';
 $lang['admin']['edit'] = 'Bearbeiten';

+ 17 - 0
data/web/lang/lang.en.php

@@ -31,6 +31,7 @@ $lang['danger']['yotp_verification_failed'] = "Yubico OTP verification failed: %
 $lang['danger']['ip_list_empty'] = "List of allowed IPs cannot be empty";
 $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI password should be at least 6 chars long";
 $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI password successfully set";
+$lang['success']['queue_command_success'] = "Queue command completed successfully";
 $lang['danger']['unknown'] = "An unknown error occured";
 $lang['danger']['malformed_username'] = "Malformed username";
 $lang['info']['awaiting_tfa_confirmation'] = "Awaiting TFA confirmation";
@@ -137,6 +138,7 @@ $lang['success']['domain_admin_removed'] = "Domain administrator %s has been rem
 $lang['success']['admin_removed'] = "Administrator %s has been removed";
 $lang['success']['mailbox_removed'] = "Mailbox %s has been removed";
 $lang['success']['eas_reset'] = "ActiveSync devices for user %s were reset";
+$lang['success']['sogo_profile_reset'] = "SOGo profile for user %s was reset";
 $lang['success']['resource_removed'] = "Resource %s has been removed";
 $lang['warning']['cannot_delete_self'] = "Cannot delete logged in user";
 $lang['warning']['no_active_admin'] = "Cannot deactivate last active admin";
@@ -223,10 +225,15 @@ $lang['user']['tag_in_none'] = 'Do nothing';
 $lang['user']['tag_help_explain'] = 'In subfolder: a new subfolder named after the tag will be created below INBOX ("INBOX/Facebook").<br>
 In subject: the tags name will be prepended to the mails subject, example: "[Facebook] My News".';
 $lang['user']['tag_help_example'] = 'Example for a tagged email address: me<b>+Facebook</b>@example.org';
+
 $lang['user']['eas_reset'] = 'Reset ActiveSync device cache';
 $lang['user']['eas_reset_now'] = 'Reset now';
 $lang['user']['eas_reset_help'] = 'In many cases a device cache reset will help to recover a broken ActiveSync profile.<br><b>Attention:</b> All elements will be redownloaded!';
 
+$lang['user']['sogo_profile_reset'] = 'Reset SOGo profile';
+$lang['user']['sogo_profile_reset_now'] = 'Reset profile now';
+$lang['user']['sogo_profile_reset_help'] = 'This will destroy a users SOGo profile and <b>delete all data irretrievable</b>.';
+
 $lang['user']['encryption'] = 'Encryption';
 $lang['user']['username'] = 'Username';
 $lang['user']['last_run'] = 'Last run';
@@ -342,6 +349,8 @@ $lang['edit']['target_address'] = 'Goto address/es <small>(comma-separated)</sma
 $lang['edit']['active'] = 'Active';
 $lang['edit']['force_pw_update'] = 'Force password update at next login';
 $lang['edit']['force_pw_update_info'] = 'This user will only be able to login to mailcow UI.';
+$lang['edit']['sogo_access'] = 'Grant access to SOGo';
+$lang['edit']['sogo_access_info'] = 'Grant or permit access to SOGo. This setting does neither affect access to all other services nor does it delete or change a users existing SOGo profile.';
 $lang['edit']['target_domain'] = 'Target domain';
 $lang['edit']['password'] = 'Password';
 $lang['edit']['password_repeat'] = 'Confirmation password (repeat)';
@@ -378,6 +387,7 @@ $lang['acl']['spam_policy'] = 'Blacklist/Whitelist';
 $lang['acl']['delimiter_action'] = 'Delimiter action';
 $lang['acl']['syncjobs'] = 'Sync jobs';
 $lang['acl']['eas_reset'] = 'Reset EAS devices';
+$lang['acl']['sogo_profile_reset'] = 'Reset SOGo profile';
 $lang['acl']['quarantine'] = 'Quarantine';
 $lang['acl']['login_as'] = 'Login as mailbox user';
 $lang['acl']['bcc_maps'] = 'BCC maps';
@@ -533,8 +543,15 @@ $lang['admin']['rsettings_preset_1'] = 'Disable all but DKIM and rate limit for
 $lang['admin']['rsettings_preset_2'] = 'Postmasters want spam';
 $lang['admin']['rsettings_insert_preset'] = 'Insert example preset "%s"';
 $lang['admin']['rsetting_add_rule'] = 'Add rule';
+$lang['admin']['queue_ays'] = 'Please confirm you want to delete all items from the current queue.';
+$lang['admin']['arrival_time'] = 'Arrival time (server time)';
+$lang['admin']['message_size'] = 'Message size';
+$lang['admin']['sender'] = 'Sender';
+$lang['admin']['recipients'] = 'Recipients';
 $lang['admin']['admin_domains'] = 'Domain assignments';
 $lang['admin']['domain_admins'] = 'Domain administrators';
+$lang['admin']['flush_queue'] = 'Flush queue';
+$lang['admin']['delete_queue'] = 'Delete all';
 $lang['admin']['username'] = 'Username';
 $lang['admin']['edit'] = 'Edit';
 $lang['admin']['remove'] = 'Remove';

+ 11 - 1
data/web/user.php

@@ -224,6 +224,8 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
     </div>
   </div>
 
+  <hr>
+
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
     <div class="col-md-9 col-xs-7">
@@ -232,6 +234,14 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
     </div>
   </div>
 
+  <div class="row">
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['sogo_profile_reset'];?>:</div>
+    <div class="col-md-9 col-xs-7">
+    <button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['sogo_profile_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['sogo_profile_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="sogo_profile" data-api-url='delete/sogo_profile' href="#"><?=$lang['user']['sogo_profile_reset_now'];?></button>
+    <p class="help-block"><?=$lang['user']['sogo_profile_reset_help'];?></p>
+    </div>
+  </div>
+
 </div>
 </div>
 
@@ -392,7 +402,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
           <li><a 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 data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
           <li role="separator" class="divider"></li>
-          <li><a data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li>
+          <li><a data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li>
         </ul>
         <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
       </div>