瀏覽代碼

[Web] Move Rspamd UI settings from debug to "Access" section
[Web] Move Rspamd settings map from debug to "Configuration" section
[Web] Some minor fixes to JS and PHP
[Web] Feature: Allow to set Rspamd settings from web UI (includes 2 presets)
[Web] Add missing primary keys

André 7 年之前
父節點
當前提交
02b1226312

+ 127 - 2
data/web/admin.php

@@ -137,6 +137,47 @@ $tfa_data = get_tfa();
           </div>
         </div>
     </div>
+
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <h3 class="panel-title">Rspamd UI</h3>
+      </div>
+      <div class="panel-body">
+        <div class="row">
+          <div class="col-sm-9">
+          <form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post">
+            <div class="form-group">
+              <div class="col-sm-offset-3 col-sm-9">
+                <label>
+                  <a href="/rspamd/" target="_blank"><span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> Rspamd UI</a>
+                </label>
+              </div>
+            </div>
+            <div class="form-group">
+              <label class="control-label col-sm-3" for="rspamd_ui_pass"><?=$lang['admin']['password'];?>:</label>
+              <div class="col-sm-9">
+              <input type="password" class="form-control" name="rspamd_ui_pass" id="rspamd_ui_pass" required>
+              </div>
+            </div>
+            <div class="form-group">
+              <label class="control-label col-sm-3" for="rspamd_ui_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
+              <div class="col-sm-9">
+              <input type="password" class="form-control" name="rspamd_ui_pass2" id="rspamd_ui_pass2" required>
+              </div>
+            </div>
+            <div class="form-group">
+              <div class="col-sm-offset-3 col-sm-9">
+                <button type="submit" class="btn btn-default" id="rspamd_ui" name="rspamd_ui" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+              </div>
+            </div>
+          </form>
+          </div>
+          <div class="col-sm-3">
+            <img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI" />
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 
   <div role="tabpanel" class="tab-pane" id="tab-config">
@@ -148,6 +189,7 @@ $tfa_data = get_tfa();
         <a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
         <a href="#relayhosts" class="list-group-item">Relayhosts</a>
         <a href="#quarantine" class="list-group-item"><?=$lang['admin']['quarantine'];?></a>
+        <a href="#rsettings" class="list-group-item">Rspamd settings map</a>
         <a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
         <a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
       </div>
@@ -429,13 +471,13 @@ $tfa_data = get_tfa();
             <div class="col-sm-6">
               <div class="form-group">
                 <label for="retention_size"><?=$lang['admin']['quarantine_retention_size'];?></label>
-                <input type="number" class="form-control" id="retention_size" name="retention_size" value="<?=$q_data['retention_size'];?>" required>
+                <input type="number" class="form-control" id="retention_size" name="retention_size" value="<?=$q_data['retention_size'];?>" placeholder="0" required>
               </div>
             </div>
             <div class="col-sm-6">
               <div class="form-group">
                 <label for="max_size"><?=$lang['admin']['quarantine_max_size'];?></label>
-                <input type="number" class="form-control" id="max_size" name="max_size" value="<?=$q_data['max_size'];?>" required>
+                <input type="number" class="form-control" id="max_size" name="max_size" value="<?=$q_data['max_size'];?>" placeholder="0" required>
               </div>
             </div>
           </div>
@@ -456,6 +498,89 @@ $tfa_data = get_tfa();
       </div>
     </div>
 
+    <span class="anchor" id="rsettings"></span>
+    <div class="panel panel-default">
+      <div class="panel-heading">Rspamd settings map</div>
+      <div class="panel-body">
+      <legend>Active settings map</legend>
+      <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="settings_map" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
+      <hr>
+      <?php $rsettings = rsettings('get'); ?>
+        <form class="form" data-id="rsettings" role="form" method="post">
+          <div class="row">
+            <div class="col-sm-3">
+              <div class="list-group">
+                <?php
+                if (empty($rsettings)):
+                ?>
+                  <span class="list-group-item"><em><?=$lang['admin']['rsetting_none'];?></em></span>
+                <?php
+                else:
+                foreach ($rsettings as $rsetting):
+                  $rsetting_details = rsettings('details', $rsetting['id']);
+                ?>
+                  <a href="#<?=$rsetting_details['id'];?>" class="list-group-item list-group-item-<?=($rsetting_details['active_int'] == '1') ? 'success' : ''; ?>" data-dont-remember="1" data-toggle="tab"><?=$rsetting_details['desc'];?> (ID #<?=$rsetting['id'];?>)</a>
+                <?php
+                endforeach;
+                endif;
+                ?>
+                  <a href="#" class="list-group-item list-group-item-default"
+                    data-id="add_domain_admin"
+                    data-toggle="modal"
+                    data-dont-remember="1"
+                    data-target="#addRsettingModal"
+                    data-toggle="tab"><?=$lang['admin']['rsetting_add_rule'];?></a>
+              </div>
+            </div>
+            <div class="col-sm-9">
+              <div class="tab-content">
+                <?php
+                if (empty($rsettings)):
+                ?>
+                <div id="none" class="tab-pane active">
+                  <p class="help-block"><?=$lang['admin']['rsetting_none'];?></p>
+                </div>
+                <?php
+                else:
+                ?>
+                <div id="none" class="tab-pane active">
+                  <p class="help-block"><?=$lang['admin']['rsetting_no_selection'];?></p>
+                </div>
+                <?php
+                foreach ($rsettings as $rsetting):
+                  $rsetting_details = rsettings('details', $rsetting['id']);
+                ?>
+                <div id="<?=$rsetting_details['id'];?>" class="tab-pane">
+                  <form class="form" data-id="rsettings" role="form" method="post">
+                    <input type="hidden" name="active" value="0">
+                    <div class="form-group">
+                      <label for="desc"><?=$lang['admin']['rsetting_desc'];?>:</label>
+                      <input type="text" class="form-control" id="desc" name="desc" value="<?=$rsetting_details['desc'];?>">
+                    </div>
+                    <div class="form-group">
+                      <label for="content"><?=$lang['admin']['rsetting_content'];?>:</label>
+                      <textarea class="form-control" id="content" name="content" rows="10"><?=$rsetting_details['content'];?></textarea>
+                    </div>
+                    <div class="form-group">
+                      <label>
+                        <input type="checkbox" name="active" value="1" <?=($rsetting_details['active_int'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['active'];?>
+                      </label>
+                    </div>
+                    <button class="btn btn-default" id="edit_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url='edit/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+                    <button class="btn btn-danger" id="delete_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url="delete/rsetting" data-api-attr='{}' href="#"><?=$lang['admin']['remove'];?></button>
+                  </form>
+                </div>
+                <?php
+                endforeach;
+                endif;
+                ?>
+              </div>
+            </div>
+          </div>
+        </form>
+      </div>
+    </div>
+
     <span class="anchor" id="customize"></span>
     <div class="panel panel-default">
       <div class="panel-heading"><?=$lang['admin']['customize'];?></div>

+ 0 - 61
data/web/debug.php

@@ -24,13 +24,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
         <li role="presentation"><a href="#tab-api-logs" aria-controls="tab-api-logs" role="tab" data-toggle="tab">API</a></li>
       </ul>
     </li>
-    <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Rspamd
-      <span class="caret"></span></a>
-      <ul class="dropdown-menu">
-        <li role="presentation"><a href="#tab-rspamd-ui" aria-controls="tab-rspamd-ui" role="tab" data-toggle="tab">Rspamd UI</a></li>
-        <li role="presentation"><a href="#tab-rspamd-settings" aria-controls="tab-rspamd-settings" role="tab" data-toggle="tab">Rspamd settings map</a></li>
-      </ul>
-    </li>
   </ul>
 
 	<div class="row">
@@ -275,60 +268,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
           </div>
         </div>
 
-        <div role="tabpanel" class="tab-pane" id="tab-rspamd-ui">
-          <div class="panel panel-default">
-            <div class="panel-heading">
-              <h3 class="panel-title">Rspamd UI</h3>
-            </div>
-            <div class="panel-body">
-              <div class="row">
-                <div class="col-sm-9">
-                <form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post">
-                  <div class="form-group">
-                    <div class="col-sm-offset-3 col-sm-9">
-                      <label>
-                        <a href="/rspamd/" target="_blank"><span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> Rspamd UI</a>
-                      </label>
-                    </div>
-                  </div>
-                  <div class="form-group">
-                    <label class="control-label col-sm-3" for="rspamd_ui_pass"><?=$lang['admin']['password'];?>:</label>
-                    <div class="col-sm-9">
-                    <input type="password" class="form-control" name="rspamd_ui_pass" id="rspamd_ui_pass" required>
-                    </div>
-                  </div>
-                  <div class="form-group">
-                    <label class="control-label col-sm-3" for="rspamd_ui_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
-                    <div class="col-sm-9">
-                    <input type="password" class="form-control" name="rspamd_ui_pass2" id="rspamd_ui_pass2" required>
-                    </div>
-                  </div>
-                  <div class="form-group">
-                    <div class="col-sm-offset-3 col-sm-9">
-                      <button type="submit" class="btn btn-default" id="rspamd_ui" name="rspamd_ui" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
-                    </div>
-                  </div>
-                </form>
-                </div>
-                <div class="col-sm-3">
-                  <img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI" />
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <div role="tabpanel" class="tab-pane" id="tab-rspamd-settings">
-          <div class="panel panel-default">
-            <div class="panel-heading">
-              <h3 class="panel-title">Rspamd settings map</h3>
-            </div>
-            <div class="panel-body">
-            <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="settings_map" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
-            </div>
-          </div>
-        </div>
-
       </div> <!-- /tab-content -->
     </div> <!-- /col-md-12 -->
   </div> <!-- /row -->

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

@@ -152,6 +152,9 @@ $(document).ready(function() {
     'use strict';
     if ($('a[data-toggle="tab"]').length) {
       $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+        if ($(this).data('dont-remember') == 1) {
+          return true;
+        }
         var id = $(this).parents('[role="tablist"]').attr('id');
         var key = 'lastTag';
         if (id) {

+ 164 - 0
data/web/inc/functions.rsettings.inc.php

@@ -0,0 +1,164 @@
+<?php
+function rsettings($_action, $_data = null) {
+	global $pdo;
+	global $lang;
+  switch ($_action) {
+    case 'add':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      $content = $_data['content'];
+      $desc = $_data['desc'];
+      $active = $_data['active'];
+      if (empty($content)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Content cannot be empty'
+        );
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`)
+          VALUES (:content, :desc, :active)");
+        $stmt->execute(array(
+          ':content' => $content,
+          ':desc' => $desc,
+          ':active' => $active
+        ));
+      }
+      catch (PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+        return false;
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => 'Added settings map entry'
+      );
+    break;
+    case 'edit':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        $is_now = rsettings('details', $id);
+        if (!empty($is_now)) {
+          $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content'];
+          $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc'];
+          $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
+        }
+        else {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'Settings map invalid'
+          );
+          return false;
+        }
+        $content = trim($content);
+        try {
+          $stmt = $pdo->prepare("UPDATE `settingsmap` SET
+            `content` = :content,
+            `desc` = :desc,
+            `active` = :active
+              WHERE `id` = :id");
+          $stmt->execute(array(
+            ':content' => $content,
+            ':desc' => $desc,
+            ':active' => $active,
+            ':id' => $id
+          ));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+          return false;
+        }
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars(implode(', ', $ids)))
+      );
+    break;
+    case 'delete':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        try {
+          $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id");
+          $stmt->execute(array(':id' => $id));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+          return false;
+        }
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => 'Removed settings map ID'
+      );
+    break;
+    case 'get':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        return false;
+      }
+      $settingsmaps = array();
+      try {
+        $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`");
+        $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC);
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+      }
+      return $settingsmaps;
+    break;
+    case 'details':
+      if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
+        return false;
+      }
+      $settingsmapdata = array();
+      try {
+        $stmt = $pdo->prepare("SELECT `id`,
+          `desc`,
+          `content`,
+          `active` AS `active_int`,
+          CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+            FROM `settingsmap`
+              WHERE `id` = :id");
+        $stmt->execute(array(':id' => $_data));
+        $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC);
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+      }
+      return $settingsmapdata;
+    break;
+  }
+}

+ 42 - 3
data/web/inc/init_db.inc.php

@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "06052018_1839";
+    $db_version = "05062018_2039";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -130,10 +130,15 @@ function init_db_schema() {
       ),
       "sender_acl" => array(
         "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
           "logged_in_as" => "VARCHAR(255) NOT NULL",
           "send_as" => "VARCHAR(255) NOT NULL"
         ),
-        "keys" => array(),
+        "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          )
+        ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
       "domain" => array(
@@ -303,6 +308,8 @@ function init_db_schema() {
           "object" => "VARCHAR(255) NOT NULL DEFAULT ''",
           "option" => "VARCHAR(50) NOT NULL DEFAULT ''",
           "value" => "VARCHAR(100) NOT NULL DEFAULT ''",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
           "prefid" => "INT(11) NOT NULL AUTO_INCREMENT"
         ),
         "keys" => array(
@@ -315,6 +322,22 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "settingsmap" => array(
+        "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
+          "desc" => "VARCHAR(255) NOT NULL",
+          "content" => "LONGTEXT NOT NULL",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
+          "active" => "TINYINT(1) NOT NULL DEFAULT '0'"
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "quota2" => array(
         "cols" => array(
           "username" => "VARCHAR(255) NOT NULL",
@@ -330,12 +353,16 @@ function init_db_schema() {
       ),
       "domain_admins" => array(
         "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
           "username" => "VARCHAR(255) NOT NULL",
           "domain" => "VARCHAR(255) NOT NULL",
           "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
           "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
         ),
         "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          ),
           "key" => array(
             "username" => array("username")
           )
@@ -454,12 +481,16 @@ function init_db_schema() {
       ),
       "sogo_acl" => array(
         "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
           "c_folder_id" => "INT NOT NULL",
           "c_object" => "VARCHAR(255) NOT NULL",
           "c_uid" => "VARCHAR(255) NOT NULL",
           "c_role" => "VARCHAR(80) NOT NULL"
         ),
         "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          ),
           "key" => array(
             "sogo_acl_c_folder_id_idx" => array("c_folder_id"),
             "sogo_acl_c_uid_idx" => array("c_uid")
@@ -469,6 +500,7 @@ function init_db_schema() {
       ),
       "sogo_alarms_folder" => array(
         "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
           "c_path" => "VARCHAR(255) NOT NULL",
           "c_name" => "VARCHAR(255) NOT NULL",
           "c_uid" => "VARCHAR(255) NOT NULL",
@@ -476,7 +508,11 @@ function init_db_schema() {
           "c_alarm_number" => "INT(11) NOT NULL",
           "c_alarm_date" => "INT(11) NOT NULL"
         ),
-        "keys" => array(),
+        "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          )
+        ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
       "sogo_cache_folder" => array(
@@ -669,6 +705,9 @@ function init_db_schema() {
           $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); 
           $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
           if ($num_results == 0) {
+            if (strpos($type, 'AUTO_INCREMENT') !== false) {
+              $type = $type . ' PRIMARY KEY ';
+            }
             $pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type);
           }
           else {

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

@@ -88,6 +88,7 @@ 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.relayhost.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';

+ 13 - 0
data/web/js/admin.js

@@ -9,6 +9,16 @@ jQuery(function($){
     e.preventDefault();
     $('#import_dkim_arrow').toggleClass("animation"); 
   });
+  $("#rspamd_preset_1").on('click', function(e) {
+    e.preventDefault();
+    $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_1);
+    $("form[data-id=rsetting]").find("#content").val('priority = 10;\nauthenticated = yes;\napply "default" {\n  symbols_enabled = ["DKIM_SIGNED", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"];\n}');
+  });
+  $("#rspamd_preset_2").on('click', function(e) {
+    e.preventDefault();
+    $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_2);
+    $("form[data-id=rsetting]").find("#content").val('priority = 10;\nrcpt = "/postmaster@.*/";\nwant_spam = yes;');
+  });
   function draw_domain_admins() {
     ft_domainadmins = FooTable.init('#domainadminstable', {
       "columns": [
@@ -182,6 +192,9 @@ jQuery(function($){
 $(window).load(function(){
   initial_width = $("#sidebar-admin").width();
   $("#scrollbox").css("width", initial_width);
+  if (sessionStorage.scrollTop > 70) {
+    $('#scrollbox').addClass('scrollboxFixed');
+  }
   $(window).bind('scroll', function() {
     if ($(window).scrollTop() > 70) {
       $('#scrollbox').addClass('scrollboxFixed');

+ 14 - 2
data/web/lang/lang.de.php

@@ -373,6 +373,9 @@ $lang['tfa']['scan_qr_code'] = "Bitte scannen Sie jetzt den angezeigten QR-Code:
 $lang['tfa']['enter_qr_code'] = "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel";
 $lang['tfa']['confirm_totp_token'] = "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens";
 
+$lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd docs</a>
+  - Ein Name wird automatisch generiert. Beispielinhalte zur Einsicht stehen nachstehend bereit.';
+  
 $lang['admin']['no_new_rows'] = 'Keine weiteren Zeilen vorhanden';
 $lang['admin']['additional_rows'] = ' zusätzliche Zeilen geladen'; // parses to 'n additional rows were added'
 $lang['admin']['private_key'] = 'Private Key';
@@ -404,6 +407,15 @@ $lang['admin']['active'] = 'Aktiv';
 $lang['admin']['inactive'] = 'Inaktiv';
 $lang['admin']['action'] = 'Aktion';
 $lang['admin']['add_domain_admin'] = 'Domain-Administrator hinzufügen';
+$lang['admin']['add_settings_rule'] = 'Rspamd Setting hinzufügen';
+$lang['admin']['rsetting_desc'] = 'Kurze Beschreibung';
+$lang['admin']['rsetting_content'] = 'Regelinhalt';
+$lang['admin']['rsetting_none'] = 'Keine Regel hinterlegt';
+$lang['admin']['rsetting_no_selection'] = 'Bitte eine Regel auswählen';
+$lang['admin']['rsettings_preset_1'] = 'Alles außer DKIM and Ratelimits für authentifizierte Benutzer deaktivieren"';
+$lang['admin']['rsettings_preset_2'] = 'Spam an Postmaster-Addressen nicht blockieren';
+$lang['admin']['rsettings_insert_preset'] = 'Beispiel "%s" laden';
+$lang['admin']['rsetting_add_rule'] = 'Regel hinzufügen';
 $lang['admin']['admin_domains'] = 'Domain-Zuweisungen';
 $lang['admin']['domain_admins'] = 'Domain-Administratoren';
 $lang['admin']['username'] = 'Benutzername';
@@ -440,8 +452,8 @@ $lang['admin']['activate_api'] = "API aktivieren";
 $lang['admin']['regen_api_key'] = "API-Key regenerieren";
 
 $lang['admin']['quarantine'] = "Quarantäne";
-$lang['admin']['quarantine_retention_size'] = "Rückhaltungen pro Mailbox:";
-$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen):";
+$lang['admin']['quarantine_retention_size'] = "Rückhaltungen pro Mailbox<br />0 bedeutet <b>inaktiv</b>!";
+$lang['admin']['quarantine_max_size'] = "Maximale Größe in MiB (größere Elemente werden verworfen)<br />0 bedeutet <b>nicht</b> unlimitert!";
 $lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschließen:";
 
 $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt";

+ 14 - 2
data/web/lang/lang.en.php

@@ -376,6 +376,9 @@ $lang['tfa']['scan_qr_code'] = "Please scan the following code with your authent
 $lang['tfa']['enter_qr_code'] = "Your TOTP code if your device cannot scan QR codes";
 $lang['tfa']['confirm_totp_token'] = "Please confirm your changes by entering the generated token";
 
+$lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd docs</a>
+  - A setting name will be auto-generated, please see the example presets below.';
+
 $lang['admin']['no_new_rows'] = 'No further rows available';
 $lang['admin']['additional_rows'] = ' additional rows were added'; // parses to 'n additional rows were added'
 $lang['admin']['private_key'] = 'Private key';
@@ -410,6 +413,15 @@ $lang['admin']['active'] = 'Active';
 $lang['admin']['inactive'] = 'Inactive';
 $lang['admin']['action'] = 'Action';
 $lang['admin']['add_domain_admin'] = 'Add domain administrator';
+$lang['admin']['add_settings_rule'] = 'Add settings rule';
+$lang['admin']['rsetting_desc'] = 'Short description';
+$lang['admin']['rsetting_content'] = 'Rule content';
+$lang['admin']['rsetting_none'] = 'No rule available';
+$lang['admin']['rsetting_no_selection'] = 'Please select a rule';
+$lang['admin']['rsettings_preset_1'] = 'Disable all but DKIM and ratelimit for authenticated users"';
+$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']['admin_domains'] = 'Domain assignments';
 $lang['admin']['domain_admins'] = 'Domain administrators';
 $lang['admin']['username'] = 'Username';
@@ -461,8 +473,8 @@ $lang['admin']['activate_api'] = "Activate API";
 $lang['admin']['regen_api_key'] = "Regenerate API key";
 
 $lang['admin']['quarantine'] = "Quarantine";
-$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox:";
-$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded):";
+$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox<br />0 indicates <b>inactive</b>!";
+$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded)<br />0 does <b>not</b> indicate unlimited!";
 $lang['admin']['quarantine_exclude_domains'] = "Exclude domains and alias-domains:";
 
 $lang['admin']['ui_texts'] = "UI labels and texts";

+ 43 - 0
data/web/modals/admin.php

@@ -4,6 +4,49 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 	exit();
 }
 ?>
+<!-- add settings rule modal -->
+<div class="modal fade" id="addRsettingModal" tabindex="-1" role="dialog" aria-hidden="true">
+  <div class="modal-dialog modal-lg">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
+        <h3 class="modal-title"><?=$lang['admin']['add_settings_rule'];?></h3>
+      </div>
+      <div class="modal-body">
+        <form class="form-horizontal" data-cached-form="true" data-id="rsetting" role="form" method="post">
+          <div class="form-group">
+            <label class="control-label col-sm-2" for="desc"><?=$lang['admin']['rsetting_desc'];?>:</label>
+            <div class="col-sm-10">
+              <input type="text" class="form-control" name="desc" id="desc" required>
+            </div>
+          </div>
+          <div class="form-group">
+            <label class="control-label col-sm-2" for="content"><?=$lang['admin']['rsetting_content'];?>:</label>
+            <div class="col-sm-10">
+              <textarea class="form-control" id="content" name="content" rows="10"><?=$rsetting_details['content'];?></textarea>
+            </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="active" checked> <?=$lang['admin']['active'];?></label>
+              </div>
+            </div>
+          </div>
+          <div class="form-group">
+            <div class="col-sm-offset-2 col-sm-10">
+              <button class="btn btn-default" id="add_item" data-id="rsetting" data-api-url='add/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button>
+            </div>
+          </div>
+        </form>
+        <hr>
+        <p><?=$lang['admin']['rspamd-com_settings'];?></p>
+        <a href="#" class="small" id="rspamd_preset_1"><?=sprintf($lang['admin']['rsettings_insert_preset'], $lang['admin']['rsettings_preset_1']);?></a><br />
+        <a href="#" class="small" id="rspamd_preset_2"><?=sprintf($lang['admin']['rsettings_insert_preset'], $lang['admin']['rsettings_preset_2']);?></a>
+      </div>
+    </div>
+  </div>
+</div><!-- add settings rule modal -->
 <!-- add domain admin modal -->
 <div class="modal fade" id="addDomainAdminModal" tabindex="-1" role="dialog" aria-hidden="true">
   <div class="modal-dialog modal-lg">