Browse Source

[BS5] Replace FooTable with jquery Datatables

FreddleSpl0it 3 years ago
parent
commit
f13530d8a1

+ 0 - 4
data/web/js/site/mailbox.js

@@ -1179,10 +1179,6 @@ jQuery(function($){
     });
   };
 
-  $('body').on('click', 'span.footable-toggle', function () {
-    event.stopPropagation();
-  })
-
   // detect element visibility changes
   function onVisible(element, callback) {
     $(element).ready(function() {

+ 260 - 147
data/web/js/site/user.js

@@ -128,23 +128,15 @@ jQuery(function($){
   }
 
   function draw_tla_table() {
-    ft_tla_table = FooTable.init('#tla_table', {
-      "columns": [
-        {"name":"chkbox","title":"","style":{"min-width":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
-        {"name":"address","title":lang.alias},
-        {"name":"validity","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.alias_valid_until,"style":{"width":"170px"}},
-        {"sorted": true,"sortValue": function(value){res = new Date(value);return res.getTime();},"direction":"DESC","name":"created","formatter":function date_format(datetime) { var date = new Date(datetime.replace(/-/g, "/")); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.created_on,"style":{"width":"170px"}},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
-      ],
-      "empty": lang.empty,
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/time_limited_aliases',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw tla table');
-        },
-        success: function (data) {
+    $('#tla_table').DataTable({
+      processing: true,
+      serverSide: false,
+      language: lang_datatables,
+      ajax: {
+        type: "GET",
+        url: "/api/v1/get/time_limited_aliases",
+        dataSrc: function(data){
+          console.log(data);
           $.each(data, function (i, item) {
             if (acl_data.spam_alias === 1) {
               item.action = '<div class="btn-group footable-actions">' +
@@ -158,46 +150,52 @@ jQuery(function($){
               item.action = '<span>-</span>';
             }
           });
+
+          return data;
         }
-      }),
-      "paging": {
-        "enabled": true,
-        "limit": 5,
-        "size": pagination_size
       },
-      "state": {"enabled": true},
-      "sorting": {
-        "enabled": true
-      },
-      "toggleSelector": "table tbody span.footable-toggle"
+      columns: [          
+        {
+          // placeholder, so checkbox will not block child row toggle
+          title: '',
+          data: null,
+          searchable: false,
+          orderable: false,
+          defaultContent: ''
+        },
+        {
+          title: '',
+          data: 'chkbox'
+        },
+        {
+          title: lang.alias,
+          data: 'address'
+        },
+        {
+          title: lang.alias_valid_until,
+          data: 'validity',
+          render: function (data, type) {
+            var date = new Date(data ? data * 1000 : 0); 
+            return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
+          }
+        },
+        {
+          title: lang.action,
+          data: 'action'
+        }
+      ]
     });
   }
   function draw_sync_job_table() {
-    ft_syncjob_table = FooTable.init('#sync_job_table', {
-      "columns": [
-        {"name":"chkbox","title":"","style":{"min-width":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
-        {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
-        {"name":"server_w_port","title":"Server","breakpoints":"xs sm md","style":{"word-break":"break-all"}},
-        {"name":"enc1","title":lang.encryption,"breakpoints":"all"},
-        {"name":"user1","title":lang.username},
-        {"name":"exclude","title":lang.excludes,"breakpoints":"all"},
-        {"name":"mins_interval","title":lang.interval + " (min)","breakpoints":"all"},
-        {"name":"last_run","title":lang.last_run,"breakpoints":"xs sm md"},
-        {"name":"exit_status","filterable": false,"title":lang.syncjob_last_run_result},
-        {"name":"log","title":"Log"},
-        {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
-        {"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"260px","width":"260px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
-      ],
-      "empty": lang.empty,
-      "rows": $.ajax({
-        dataType: 'json',
+    $('#sync_job_table').DataTable({
+      processing: true,
+      serverSide: false,
+      language: lang_datatables,
+      ajax: {
+        type: "GET",
         url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw sync job table');
-        },
-        success: function (data) {
+        dataSrc: function(data){
+          console.log(data);
           $.each(data, function (i, item) {
             item.user1 = escapeHtml(item.user1);
             item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
@@ -239,39 +237,87 @@ jQuery(function($){
             }
             item.exit_status = item.success + ' ' + item.exit_status;
           });
+
+          return data;
         }
-      }),
-      "paging": {
-        "enabled": true,
-        "limit": 5,
-        "size": pagination_size
-      },
-      "state": {"enabled": true},
-      "sorting": {
-        "enabled": true
       },
-      "toggleSelector": "table tbody span.footable-toggle"
+      columns: [          
+        {
+          // placeholder, so checkbox will not block child row toggle
+          title: '',
+          data: null,
+          searchable: false,
+          orderable: false,
+          defaultContent: ''
+        },
+        {
+          title: '',
+          data: 'chkbox'
+        },
+        {
+          title: 'ID',
+          data: 'id'
+        },
+        {
+          title: 'Server',
+          data: 'server_w_port'
+        },
+        {
+          title: lang.encryption,
+          data: 'enc1'
+        },
+        {
+          title: lang.username,
+          data: 'user1'
+        },
+        {
+          title: lang.excludes,
+          data: 'exclude'
+        },
+        {
+          title: lang.interval + " (min)",
+          data: 'mins_interval'
+        },
+        {
+          title: lang.last_run,
+          data: 'last_run'
+        },
+        {
+          title: lang.syncjob_last_run_result,
+          data: 'exit_status'
+        },
+        {
+          title: 'Log',
+          data: 'log'
+        },
+        {
+          title: lang.active,
+          data: 'active',
+          render: function (data, type) {
+            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
+          }
+        },
+        {
+          title: lang.status,
+          data: 'is_running'
+        },
+        {
+          title: lang.action,
+          data: 'action'
+        }
+      ]
     });
   }
   function draw_app_passwd_table() {
-    ft_apppasswd_table = FooTable.init('#app_passwd_table', {
-      "columns": [
-        {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
-        {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
-        {"name":"name","title":lang.app_name},
-        {"name":"protocols","title":lang.allowed_protocols},
-        {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
-      ],
-      "empty": lang.empty,
-      "rows": $.ajax({
-        dataType: 'json',
+    $('#app_passwd_table').DataTable({
+      processing: true,
+      serverSide: false,
+      language: lang_datatables,
+      ajax: {
+        type: "GET",
         url: '/api/v1/get/app-passwd/all',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw app passwd table');
-        },
-        success: function (data) {
+        dataSrc: function(data){
+          console.log(data);
           $.each(data, function (i, item) {
             item.name = escapeHtml(item.name)
             item.protocols = []
@@ -294,37 +340,59 @@ jQuery(function($){
               item.chkbox = '<input type="checkbox" disabled />';
             }
           });
+
+          return data;
         }
-      }),
-      "paging": {
-        "enabled": true,
-        "limit": 5,
-        "size": pagination_size
       },
-      "state": {"enabled": true},
-      "sorting": {
-        "enabled": true
-      },
-      "toggleSelector": "table tbody span.footable-toggle"
+      columns: [          
+        {
+          // placeholder, so checkbox will not block child row toggle
+          title: '',
+          data: null,
+          searchable: false,
+          orderable: false,
+          defaultContent: ''
+        },
+        {
+          title: '',
+          data: 'chkbox'
+        },
+        {
+          title: 'ID',
+          data: 'id'
+        },
+        {
+          title: lang.app_name,
+          data: 'name'
+        },
+        {
+          title: lang.allowed_protocols,
+          data: 'protocols'
+        },
+        {
+          title: lang.active,
+          data: 'active',
+          render: function (data, type) {
+            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
+          }
+        },
+        {
+          title: lang.action,
+          data: 'action'
+        }
+      ]
     });
   }
   function draw_wl_policy_mailbox_table() {
-    ft_wl_policy_mailbox_table = FooTable.init('#wl_policy_mailbox_table', {
-      "columns": [
-        {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
-        {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
-        {"sorted": true,"name":"value","title":lang.spamfilter_table_rule},
-        {"name":"object","title":"Scope"}
-      ],
-      "empty": lang.empty,
-      "rows": $.ajax({
-        dataType: 'json',
+    $('#wl_policy_mailbox_table').DataTable({
+      processing: true,
+      serverSide: false,
+      language: lang_datatables,
+      ajax: {
+        type: "GET",
         url: '/api/v1/get/policy_wl_mailbox',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw mailbox policy wl table');
-        },
-        success: function (data) {
+        dataSrc: function(data){
+          console.log(data);
           $.each(data, function (i, item) {
             if (validateEmail(item.object)) {
               item.chkbox = '<input type="checkbox" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
@@ -336,36 +404,48 @@ jQuery(function($){
               item.chkbox = '<input type="checkbox" disabled />';
             }
           });
+
+          return data;
         }
-      }),
-      "state": {"enabled": true},
-      "paging": {
-        "enabled": true,
-        "limit": 5,
-        "size": pagination_size
       },
-      "sorting": {
-        "enabled": true
-      }
+      columns: [          
+        {
+          // placeholder, so checkbox will not block child row toggle
+          title: '',
+          data: null,
+          searchable: false,
+          orderable: false,
+          defaultContent: ''
+        },
+        {
+          title: '',
+          data: 'chkbox'
+        },
+        {
+          title: 'ID',
+          data: 'prefid'
+        },
+        {
+          title: lang.spamfilter_table_rule,
+          data: 'name'
+        },
+        {
+          title:'Scope',
+          data: 'object'
+        }
+      ]
     });
   }
   function draw_bl_policy_mailbox_table() {
-    ft_bl_policy_mailbox_table = FooTable.init('#bl_policy_mailbox_table', {
-      "columns": [
-        {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
-        {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false},
-        {"sorted": true,"name":"value","title":lang.spamfilter_table_rule},
-        {"name":"object","title":"Scope"}
-      ],
-      "empty": lang.empty,
-      "rows": $.ajax({
-        dataType: 'json',
+    $('#bl_policy_mailbox_table').DataTable({
+      processing: true,
+      serverSide: false,
+      language: lang_datatables,
+      ajax: {
+        type: "GET",
         url: '/api/v1/get/policy_bl_mailbox',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw mailbox policy bl table');
-        },
-        success: function (data) {
+        dataSrc: function(data){
+          console.log(data);
           $.each(data, function (i, item) {
             if (validateEmail(item.object)) {
               item.chkbox = '<input type="checkbox" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
@@ -377,31 +457,39 @@ jQuery(function($){
               item.chkbox = '<input type="checkbox" disabled />';
             }
           });
+
+          return data;
         }
-      }),
-      "paging": {
-        "enabled": true,
-        "limit": 5,
-        "size": pagination_size
       },
-      "state": {"enabled": true},
-      "sorting": {
-        "enabled": true
-      }
+      columns: [          
+        {
+          // placeholder, so checkbox will not block child row toggle
+          title: '',
+          data: null,
+          searchable: false,
+          orderable: false,
+          defaultContent: ''
+        },
+        {
+          title: '',
+          data: 'chkbox'
+        },
+        {
+          title: 'ID',
+          data: 'prefid'
+        },
+        {
+          title: lang.spamfilter_table_rule,
+          data: 'name'
+        },
+        {
+          title:'Scope',
+          data: 'object'
+        }
+      ]
     });
   }
 
-  $('body').on('click', 'span.footable-toggle', function () {
-    event.stopPropagation();
-  })
-
-  draw_sync_job_table();
-  draw_app_passwd_table();
-  draw_tla_table();
-  draw_wl_policy_mailbox_table();
-  draw_bl_policy_mailbox_table();
-  last_logins('get');
-
   // FIDO2 friendly name modal
   $('#fido2ChangeFn').on('show.bs.modal', function (e) {
     rename_link = $(e.relatedTarget)
@@ -433,4 +521,29 @@ jQuery(function($){
   $('#userFilterModal').on('hidden.bs.modal', function () {
     $('#user_sieve_filter').text(lang.loading);
   });
+
+  // detect element visibility changes
+  function onVisible(element, callback) {
+    $(element).ready(function() {
+      element_object = document.querySelector(element)
+      new IntersectionObserver((entries, observer) => {
+        entries.forEach(entry => {
+          if(entry.intersectionRatio > 0) {
+            callback(element_object);
+            observer.disconnect();
+          }
+        });
+      }).observe(element_object);
+    });
+  }
+
+  // Load only if the tab is visible
+  onVisible("[id^=SpamAliases]", () => draw_tla_table());
+  onVisible("[id^=Spamfilter]", () => { 
+    draw_wl_policy_mailbox_table();
+    draw_bl_policy_mailbox_table();
+  });
+  onVisible("[id^=Syncjobs]", () => draw_sync_job_table());
+  onVisible("[id^=AppPasswds]", () => draw_app_passwd_table());
+  last_logins('get');
 });

+ 22 - 17
data/web/templates/user/AppPasswds.twig

@@ -1,22 +1,27 @@
 <div role="tabpanel" class="tab-pane fade" id="AppPasswds" role="tabpanel" aria-labelledby="AppPasswds">
-  <p>{{ lang.user.app_hint|raw }}</p>
-  <div class="table-responsive">
-    <table class="table table-striped" id="app_passwd_table"></table>
-  </div>
-  <div class="mass-actions-user">
-    <div class="btn-group" data-acl="{{ acl.app_passwds }}">
-      <div class="btn-group">
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="apppasswd" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
-        <ul class="dropdown-menu">
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
-          <li><hr class="dropdown-divider"></li>
-          <li><a class="dropdown-item" data-action="delete_selected" data-id="apppasswd" data-api-url='delete/app-passwd' href="#">{{ lang.mailbox.remove }}</a></li>
-        </ul>
+  <div class="card">
+    <div class="card-header">{{ lang.user.app_passwds }}</div>
+    <div class="card-body">
+      <p>{{ lang.user.app_hint|raw }}</p>
+      <div class="table-responsive">
+        <table class="table table-striped" id="app_passwd_table"></table>
       </div>
-      <div class="btn-group">
-        <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addAppPasswdModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_app_passwd }}</a>
+      <div class="mass-actions-user">
+        <div class="btn-group" data-acl="{{ acl.app_passwds }}">
+          <div class="btn-group">
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="apppasswd" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
+            <ul class="dropdown-menu">
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
+              <li><hr class="dropdown-divider"></li>
+              <li><a class="dropdown-item" data-action="delete_selected" data-id="apppasswd" data-api-url='delete/app-passwd' href="#">{{ lang.mailbox.remove }}</a></li>
+            </ul>
+          </div>
+          <div class="btn-group">
+            <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addAppPasswdModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_app_passwd }}</a>
+          </div>
+        </div>
       </div>
     </div>
   </div>

File diff suppressed because it is too large
+ 0 - 6
data/web/templates/user/Pushover.twig


+ 36 - 33
data/web/templates/user/SpamAliases.twig

@@ -1,38 +1,41 @@
 <div role="tabpanel" class="tab-pane fade" id="SpamAliases" role="tabpanel" aria-labelledby="SpamAliases">
-  <div class="row">
-    <div class="col-md-12 col-sm-12 col-12">
-      <div class="table-responsive">
-        <table class="table table-striped" id="tla_table"></table>
+  <div class="card">
+    <div class="card-header">{{ lang.user.spam_aliases }}</div>
+    <div class="card-body">
+      <div class="row">
+        <div class="col-md-12 col-sm-12 col-12">
+          <table id="tla_table" class="table table-striped dt-responsive w-100"></table>
+        </div>
       </div>
-    </div>
-  </div>
-  <div class="mass-actions-user">
-    <div class="btn-group" data-acl="{{ acl.spam_alias }}">
-      <div class="btn-group">
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="tla" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
-        <ul class="dropdown-menu">
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
-          <li><hr class="dropdown-divider"></li>
-          <li><a class="dropdown-item" data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#">{{ lang.mailbox.remove }}</a></li>
-        </ul>
-      </div>
-      <div class="btn-group">
-        <a class="btn btn-sm d-block d-sm-inline btn-success dropdown-toggle" data-bs-toggle="dropdown" href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.alias_create_random }}, 1 {{ lang.user.year }}</a>
-        <ul class="dropdown-menu">
-          {% for domain in user_domains %}
-            <li>
-              <a class="dropdown-item" data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"domain":"{{ domain }}"}' href="#">
-                @ {{ domain }}
-              </a>
-            </li>
-          {% endfor %}
-        </ul>
+      <div class="mass-actions-user">
+        <div class="btn-group" data-acl="{{ acl.spam_alias }}">
+          <div class="btn-group">
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="tla" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
+            <ul class="dropdown-menu">
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
+              <li><hr class="dropdown-divider"></li>
+              <li><a class="dropdown-item" data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#">{{ lang.mailbox.remove }}</a></li>
+            </ul>
+          </div>
+          <div class="btn-group">
+            <a class="btn btn-sm d-block d-sm-inline btn-success dropdown-toggle" data-bs-toggle="dropdown" href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.alias_create_random }}, 1 {{ lang.user.year }}</a>
+            <ul class="dropdown-menu">
+              {% for domain in user_domains %}
+                <li>
+                  <a class="dropdown-item" data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"domain":"{{ domain }}"}' href="#">
+                    @ {{ domain }}
+                  </a>
+                </li>
+              {% endfor %}
+            </ul>
+          </div>
+        </div>
       </div>
     </div>
   </div>

+ 73 - 68
data/web/templates/user/Spamfilter.twig

@@ -1,75 +1,80 @@
 <div role="tabpanel" class="tab-pane fade" id="Spamfilter" role="tabpanel" aria-labelledby="Spamfilter">
-  <h4>{{ lang.user.spamfilter_behavior }}</h4>
-  <div class="row">
-    <div class="col-sm-12">
-      <form class="form-horizontal" role="form" data-id="spam_score" method="post">
-        <div class="row">
-          <div class="col-lg-8 col-sm-12">
-            <div id="spam_score" data-provide="slider" data-acl="{{ acl.spam_score }}"></div>
-            <input id="spam_score_value" name="spam_score" type="hidden" value="{{ user_spam_score }}">
-            <ul class="list-group list-group-flush">
-              <li class="list-group-item"><span class="label label-ham spam-ham-score"></span> {{ lang.user.spamfilter_green }}</li>
-              <li class="list-group-item"><span class="label label-spam spam-spam-score"></span> {{ lang.user.spamfilter_yellow }}</li>
-              <li class="list-group-item"><span class="label label-reject spam-reject-score"></span> {{ lang.user.spamfilter_red }}</li>
-            </ul>
-          </div>
-        </div>
-        <div class="btn-group" data-acl="{{ acl.spam_score }}">
-          <a type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected"
-             data-item="{{ mailcow_cc_username }}"
-             data-id="spam_score"
-             data-api-url='edit/spam-score'
-             data-api-attr='{}'><i class="bi bi-save"></i> {{ lang.user.save_changes }}</a>
-          <a type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" data-action="edit_selected"
-             data-item="{{ mailcow_cc_username }}"
-             data-id="spam_score_reset"
-             data-api-url='edit/spam-score'
-             data-api-attr='{"spam_score":"default"}'>{{ lang.user.spam_score_reset }}</a>
-        </div>
-      </form>
-    </div>
-  </div>
-  <hr>
-  <div class="row">
-    <div class="col-sm-6">
-      <h4>{{ lang.user.spamfilter_wl }}</h4>
-      <p>{{ lang.user.spamfilter_wl_desc|raw }}</p>
-      <form class="form-inline mb-4" data-id="add_wl_policy_mailbox">
-        <div class="input-group" data-acl="{{ acl.spam_policy }}">
-          <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
-          <span class="input-group-btn">
-            <button class="btn btn-secondary" data-action="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username": {{ mailcow_cc_username|json_encode|raw }},"object_list":"wl"}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.spamfilter_table_add }}</button>
-          </span>
-        </div>
-      </form>
-      <div class="table-responsive">
-        <table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table>
-      </div>
-      <div class="mass-actions-user">
-        <div class="btn-group" data-acl="{{ acl.spam_policy }}">
-          <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
-          <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-danger" data-action="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#">{{ lang.mailbox.remove }}</a>
+  <div class="card">
+    <div class="card-header">{{ lang.user.spamfilter }}</div>
+    <div class="card-body">
+      <h4>{{ lang.user.spamfilter_behavior }}</h4>
+      <div class="row">
+        <div class="col-sm-12">
+          <form class="form-horizontal" role="form" data-id="spam_score" method="post">
+            <div class="row">
+              <div class="col-lg-8 col-sm-12">
+                <div id="spam_score" data-provide="slider" data-acl="{{ acl.spam_score }}"></div>
+                <input id="spam_score_value" name="spam_score" type="hidden" value="{{ user_spam_score }}">
+                <ul class="list-group list-group-flush">
+                  <li class="list-group-item"><span class="label label-ham spam-ham-score"></span> {{ lang.user.spamfilter_green }}</li>
+                  <li class="list-group-item"><span class="label label-spam spam-spam-score"></span> {{ lang.user.spamfilter_yellow }}</li>
+                  <li class="list-group-item"><span class="label label-reject spam-reject-score"></span> {{ lang.user.spamfilter_red }}</li>
+                </ul>
+              </div>
+            </div>
+            <div class="btn-group" data-acl="{{ acl.spam_score }}">
+              <a type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected"
+                data-item="{{ mailcow_cc_username }}"
+                data-id="spam_score"
+                data-api-url='edit/spam-score'
+                data-api-attr='{}'><i class="bi bi-save"></i> {{ lang.user.save_changes }}</a>
+              <a type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" data-action="edit_selected"
+                data-item="{{ mailcow_cc_username }}"
+                data-id="spam_score_reset"
+                data-api-url='edit/spam-score'
+                data-api-attr='{"spam_score":"default"}'>{{ lang.user.spam_score_reset }}</a>
+            </div>
+          </form>
         </div>
       </div>
-    </div>
-    <div class="col-sm-6">
-      <h4>{{ lang.user.spamfilter_bl }}</h4>
-      <p>{{ lang.user.spamfilter_bl_desc|raw }}</p>
-      <form class="form-inline mb-4" data-id="add_bl_policy_mailbox">
-        <div class="input-group" data-acl="{{ acl.spam_policy }}">
-          <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
-          <span class="input-group-btn">
-            <button class="btn btn-secondary" data-action="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username": {{ mailcow_cc_username|json_encode|raw }},"object_list":"bl"}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.spamfilter_table_add }}</button>
-          </span>
+      <hr>
+      <div class="row">
+        <div class="col-sm-6">
+          <h4>{{ lang.user.spamfilter_wl }}</h4>
+          <p>{{ lang.user.spamfilter_wl_desc|raw }}</p>
+          <form class="form-inline mb-4" data-id="add_wl_policy_mailbox">
+            <div class="input-group" data-acl="{{ acl.spam_policy }}">
+              <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
+              <span class="input-group-btn">
+                <button class="btn btn-secondary" data-action="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username": {{ mailcow_cc_username|json_encode|raw }},"object_list":"wl"}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.spamfilter_table_add }}</button>
+              </span>
+            </div>
+          </form>
+          <div class="table-responsive">
+            <table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table>
+          </div>
+          <div class="mass-actions-user">
+            <div class="btn-group" data-acl="{{ acl.spam_policy }}">
+              <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
+              <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-danger" data-action="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#">{{ lang.mailbox.remove }}</a>
+            </div>
+          </div>
         </div>
-      </form>
-      <div class="table-responsive">
-        <table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table>
-      </div>
-      <div class="mass-actions-user">
-        <div class="btn-group" data-acl="{{ acl.spam_policy }}">
-          <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
-          <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-danger" data-action="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#">{{ lang.mailbox.remove }}</a>
+        <div class="col-sm-6">
+          <h4>{{ lang.user.spamfilter_bl }}</h4>
+          <p>{{ lang.user.spamfilter_bl_desc|raw }}</p>
+          <form class="form-inline mb-4" data-id="add_bl_policy_mailbox">
+            <div class="input-group" data-acl="{{ acl.spam_policy }}">
+              <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
+              <span class="input-group-btn">
+                <button class="btn btn-secondary" data-action="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username": {{ mailcow_cc_username|json_encode|raw }},"object_list":"bl"}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.user.spamfilter_table_add }}</button>
+              </span>
+            </div>
+          </form>
+          <div class="table-responsive">
+            <table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table>
+          </div>
+          <div class="mass-actions-user">
+            <div class="btn-group" data-acl="{{ acl.spam_policy }}">
+              <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
+              <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-danger" data-action="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#">{{ lang.mailbox.remove }}</a>
+            </div>
+          </div>
         </div>
       </div>
     </div>

+ 20 - 17
data/web/templates/user/Syncjobs.twig

@@ -1,21 +1,24 @@
 <div role="tabpanel" class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs">
-  <div class="table-responsive">
-    <table class="table table-striped" id="sync_job_table"></table>
-  </div>
-  <div class="mass-actions-user">
-    <div class="btn-group" data-acl="{{ acl.syncjobs }}">
-      <div class="btn-group">
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
-        <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
-        <ul class="dropdown-menu">
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
-          <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
-          <li><hr class="dropdown-divider"></li>
-          <li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li>
-        </ul>
-      </div>
-      <div class="btn-group">
-        <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
+  <div class="card">
+    <div class="card-header">{{ lang.user.sync_jobs }}</div>
+    <div class="card-body">
+      <table id="sync_job_table" class="table table-striped dt-responsive w-100"></table>
+      <div class="mass-actions-user">
+        <div class="btn-group" data-acl="{{ acl.syncjobs }}">
+          <div class="btn-group">
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
+            <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
+            <ul class="dropdown-menu">
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
+              <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
+              <li><hr class="dropdown-divider"></li>
+              <li><a class="dropdown-item" data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#">{{ lang.mailbox.remove }}</a></li>
+            </ul>
+          </div>
+          <div class="btn-group">
+            <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
+          </div>
+        </div>
       </div>
     </div>
   </div>

+ 1 - 0
data/web/templates/user_domainadmin_common.twig

@@ -7,4 +7,5 @@
   var pagination_size = '{{ pagination_size }}';
   var mailcow_cc_username = '{{ mailcow_cc_username }}';
   var user_spam_score = [{{ user_spam_score }}];
+  var lang_datatables = {{ lang_datatables|raw }};
 </script>

+ 1 - 0
data/web/user.php

@@ -88,6 +88,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
     'pushover_data' => $pushover_data,
     'lang_user' => json_encode($lang['user']),
     'number_of_app_passwords' => $number_of_app_passwords,
+    'lang_datatables' => json_encode($lang['datatables']),
   ];
 }
 

Some files were not shown because too many files changed in this diff