]> git.proxmox.com Git - pmg-gui.git/blobdiff - js/Utils.js
utils: align notification toast to bottom-right again
[pmg-gui.git] / js / Utils.js
index fed1b145c60fd11184a121e47a22a3098b017747..328a146b3483c9770ac6bfd5cd9641fad4c2c703 100644 (file)
@@ -1,4 +1,3 @@
-/*global Proxmox */
 Ext.ns('PMG');
 
 console.log("Starting PMG Manager");
@@ -9,6 +8,33 @@ Ext.define('PMG.Utils', {
 
     // this singleton contains miscellaneous utilities
 
+    // use in panels with object spread (...) operator, for example:
+    // ...PMG.Utils.onlineHelpTool('sysadmin_certificate_management'),
+    onlineHelpTool: function(blockid) {
+       let info = Proxmox.Utils.get_help_info(blockid);
+       if (info === undefined) {
+           info = Proxmox.Utils.get_help_info('pmg_documentation_index');
+           if (info === undefined) {
+               throw "get_help_info failed"; // should not happen
+           }
+       }
+
+       let docsURI = window.location.origin + info.link;
+       let title = info.title || gettext('Help');
+       if (info.subtitle) {
+           title += ' - ' + info.subtitle;
+       }
+       return {
+           tools: [
+               {
+                   type: 'help',
+                   tooltip: title,
+                   handler: () => window.open(docsURI),
+               },
+           ],
+       };
+    },
+
     senderText: gettext('Sender'),
     receiverText: gettext('Receiver'),
     scoreText: gettext('Score'),
@@ -16,6 +42,7 @@ Ext.define('PMG.Utils', {
     user_role_text: {
        root: gettext('Superuser'),
        admin: gettext('Administrator'),
+       helpdesk: gettext('Help Desk'),
        qmanager: gettext('Quarantine Manager'),
        audit: gettext('Auditor'),
     },
@@ -30,7 +57,7 @@ Ext.define('PMG.Utils', {
        when: gettext('When Objects'),
        action: gettext('Action Objects'),
        from: gettext('From'),
-       to: gettext('To')
+       to: gettext('To'),
     },
 
     oclass_icon: {
@@ -39,7 +66,7 @@ Ext.define('PMG.Utils', {
        when: '<span class="fa fa-fw fa-clock-o"></span> ',
        action: '<span class="fa fa-fw fa-flag"></span> ',
        from: '<span class="fa fa-fw fa-user-circle"></span> ',
-       to: '<span class="fa fa-fw fa-user-circle"></span> '
+       to: '<span class="fa fa-fw fa-user-circle"></span> ',
     },
 
     mail_status_map: {
@@ -50,44 +77,31 @@ Ext.define('PMG.Utils', {
        G: 'greylisted',
        A: 'accepted',
        B: 'blocked',
-       Q: 'quarantine'
+       Q: 'quarantine',
     },
 
-    icon_status_map: {
-       2: {
-           fa: 'check-circle',
-           color: 'green'
-       },
-       4: {
-           fa: 'clock-o',
-       },
-       5: {
-           fa: 'mail-reply',
-           color: 'gray'
-       },
-       N: {
-           fa: 'times-circle'
-       },
-       G: {
-           fa: 'list'
-       },
-       A: {
-           fa: 'check',
-           color: 'green'
-       },
-       B: {
-           fa: 'ban',
-           color: 'red'
-       },
-       Q: {
-           fa: 'cube'
-       }
+    icon_status_map_class: {
+       2: 'check-circle',
+       4: 'clock-o',
+       5: 'mail-reply',
+       N: 'times-circle',
+       G: 'list',
+       A: 'check',
+       B: 'ban',
+       Q: 'cube',
+    },
+
+    icon_status_map_color: {
+       2: 'green',
+       5: 'gray',
+       A: 'green',
+       B: 'red',
     },
 
     format_status_icon: function(status) {
-       var icon = PMG.Utils.icon_status_map[status] || {};
-       return '<i class="fa fa-' + (icon.fa || 'question-circle') + ' ' +
-              (icon.color || '') + '"></i> ';
+       var icon = PMG.Utils.icon_status_map_class[status] || 'question-circle';
+       var color = PMG.Utils.icon_status_map_color[status] || '';
+       return '<i class="fa fa-' + icon + ' ' + color + '"></i> ';
     },
 
     format_oclass: function(oclass) {
@@ -99,13 +113,13 @@ Ext.define('PMG.Utils', {
     rule_direction_text: {
        0: gettext('In'),
        1: gettext('Out'),
-       2: gettext('In & Out')
+       2: gettext('In & Out'),
     },
 
     rule_direction_icon: {
        0: '<span class="fa fa-fw fa-long-arrow-left"></span> ',
        1: '<span class="fa fa-fw fa-long-arrow-right"></span> ',
-       2: '<span class="fa fa-fw fa-exchange"></span> '
+       2: '<span class="fa fa-fw fa-exchange"></span> ',
     },
 
     format_rule_direction: function(dir) {
@@ -116,21 +130,31 @@ Ext.define('PMG.Utils', {
 
     format_otype: function(otype) {
        var editor = PMG.Utils.object_editors[otype];
+       var iconCls = 'fa fa-question-circle';
        if (editor) {
-           return editor.subject;
+           var icon = '<span class="fa-fw ' + (editor.iconCls || iconCls) + '"></span> ';
+           return icon + editor.subject;
        }
-       return 'unknown';
+
+       return '<span class="fa-fw ' + iconCls + '"></span> unknown';
     },
 
     format_ldap_protocol: function(p) {
        if (p === undefined) { return 'LDAP'; }
        if (p === 'ldap') { return 'LDAP'; }
        if (p === 'ldaps') { return 'LDAPS'; }
+       if (p === 'ldap+starttls') { return 'LDAP+STARTTLS'; }
        return 'unknown';
     },
 
+    convert_field_to_per_min: function(value, record) {
+       return value/(record.data.timespan/60);
+    },
+
     object_editors: {
        1000: {
+           onlineHelp: 'pmg_mailfilter_regex',
+           iconCls: 'fa fa-filter',
            xtype: 'proxmoxWindowEdit',
            subdir: 'regex',
            subject: gettext("Regular Expression"),
@@ -140,21 +164,35 @@ Ext.define('PMG.Utils', {
                    xtype: 'textfield',
                    name: 'regex',
                    labelWidth: 150,
-                   fieldLabel: gettext("Regular Expression")
-               }
-           ]
+                   reference: 'regex',
+                   fieldLabel: gettext("Regular Expression"),
+               },
+               {
+                   labelWidth: 150,
+                   fieldLabel: gettext('Test String'),
+                   xtype: 'pmgRegexTester',
+                   wholeMatch: true,
+                   regexFieldReference: 'regex',
+               },
+           ],
        },
        1005: {
+           onlineHelp: 'pmgconfig_ldap',
+           iconCls: 'fa fa-users',
            xtype: 'pmgLDAPGroupEditor',
            subdir: 'ldap',
-           subject: gettext("LDAP Group")
+           subject: gettext("LDAP Group"),
        },
        1006: {
+           onlineHelp: 'pmgconfig_ldap',
+           iconCls: 'fa fa-user',
            xtype: 'pmgLDAPUserEditor',
            subdir: 'ldapuser',
-           subject: gettext("LDAP User")
+           subject: gettext("LDAP User"),
        },
        1009: {
+           onlineHelp: 'pmg_mailfilter_regex',
+           iconCls: 'fa fa-filter',
            xtype: 'proxmoxWindowEdit',
            subdir: 'receiver_regex',
            subject: gettext("Regular Expression"),
@@ -165,38 +203,44 @@ Ext.define('PMG.Utils', {
                    xtype: 'textfield',
                    name: 'regex',
                    labelWidth: 150,
-                   fieldLabel: gettext("Regular Expression")
-               }
-           ]
+                   fieldLabel: gettext("Regular Expression"),
+               },
+           ],
        },
        1001: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-envelope-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'email',
-           subject: gettext("Email"),
+           subject: gettext("E-Mail"),
            width: 400,
            items: [
                {
                    xtype: 'textfield',
                    name: 'email',
-                   fieldLabel: gettext("Email")
-               }
-           ]
+                   fieldLabel: gettext("E-Mail"),
+               },
+           ],
        },
        1007: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-envelope-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'receiver',
-           subject: gettext("Email"),
+           subject: gettext("E-Mail"),
            receivertest: true,
            width: 400,
            items: [
                {
                    xtype: 'textfield',
                    name: 'email',
-                   fieldLabel: gettext("Email")
-               }
-           ]
+                   fieldLabel: gettext("E-Mail"),
+               },
+           ],
        },
        1002: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-globe',
            xtype: 'proxmoxWindowEdit',
            subdir: 'domain',
            subject: gettext("Domain"),
@@ -205,11 +249,13 @@ Ext.define('PMG.Utils', {
                {
                    xtype: 'textfield',
                    name: 'domain',
-                   fieldLabel: gettext("Domain")
-               }
-           ]
+                   fieldLabel: gettext("Domain"),
+               },
+           ],
        },
        1008: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-globe',
            xtype: 'proxmoxWindowEdit',
            subdir: 'receiver_domain',
            subject: gettext("Domain"),
@@ -219,11 +265,13 @@ Ext.define('PMG.Utils', {
                {
                    xtype: 'textfield',
                    name: 'domain',
-                   fieldLabel: gettext("Domain")
-               }
-           ]
+                   fieldLabel: gettext("Domain"),
+               },
+           ],
        },
        1003: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-globe',
            xtype: 'proxmoxWindowEdit',
            subdir: 'ip',
            subject: gettext("IP Address"),
@@ -232,11 +280,13 @@ Ext.define('PMG.Utils', {
                {
                    xtype: 'textfield',
                    name: 'ip',
-                   fieldLabel: gettext("IP Address")
-               }
-           ]
+                   fieldLabel: gettext("IP Address"),
+               },
+           ],
        },
        1004: {
+           onlineHelp: 'pmg_mailfilter_who',
+           iconCls: 'fa fa-globe',
            xtype: 'proxmoxWindowEdit',
            subdir: 'network',
            subject: gettext("IP Network"),
@@ -245,11 +295,13 @@ Ext.define('PMG.Utils', {
                {
                    xtype: 'textfield',
                    name: 'cidr',
-                   fieldLabel: gettext("IP Network")
-               }
-           ]
+                   fieldLabel: gettext("IP Network"),
+               },
+           ],
        },
        2000: {
+           onlineHelp: 'pmg_mailfilter_when',
+           iconCls: 'fa fa-clock-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'timeframe',
            subject: gettext("TimeFrame"),
@@ -258,17 +310,19 @@ Ext.define('PMG.Utils', {
                    xtype: 'timefield',
                    name: 'start',
                    format: 'H:i',
-                   fieldLabel: gettext("Start Time")
+                   fieldLabel: gettext("Start Time"),
                },
                {
                    xtype: 'timefield',
                    name: 'end',
                    format: 'H:i',
-                   fieldLabel: gettext("End Time")
-               }
-           ]
+                   fieldLabel: gettext("End Time"),
+               },
+           ],
        },
        3000: {
+           onlineHelp: 'pmg_mailfilter_what',
+           iconCls: 'fa fa-bullhorn',
            xtype: 'proxmoxWindowEdit',
            subdir: 'spamfilter',
            subject: gettext('Spam Filter'),
@@ -278,11 +332,27 @@ Ext.define('PMG.Utils', {
                    name: 'spamlevel',
                    allowBlank: false,
                    minValue: 0,
-                   fieldLabel: gettext('Level')
-               }
-           ]
+                   fieldLabel: gettext('Level'),
+               },
+           ],
+       },
+       3001: {
+           onlineHelp: 'pmg_mailfilter_what',
+           iconCls: 'fa fa-bug',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'virusfilter',
+           subject: gettext('Virus Filter'),
+           uneditable: true,
+           // there are no parameters to give, so we simply submit it
+           listeners: {
+               show: function(win) {
+                   win.submit();
+               },
+           },
        },
        3002: {
+           onlineHelp: 'pmg_mailfilter_regex',
+           iconCls: 'fa fa-code',
            xtype: 'proxmoxWindowEdit',
            subdir: 'matchfield',
            subject: gettext('Match Field'),
@@ -293,7 +363,7 @@ Ext.define('PMG.Utils', {
                    name: 'field',
                    labelWidth: 150,
                    allowBlank: false,
-                   fieldLabel: gettext('Field')
+                   fieldLabel: gettext('Field'),
                },
                {
                    xtype: 'textfield',
@@ -301,17 +371,19 @@ Ext.define('PMG.Utils', {
                    name: 'value',
                    labelWidth: 150,
                    allowBlank: false,
-                   fieldLabel: gettext('Value')
+                   fieldLabel: gettext('Value'),
                },
                {
                    labelWidth: 150,
                    fieldLabel: gettext('Test String'),
                    xtype: 'pmgRegexTester',
-                   regexFieldReference: 'value'
-               }
-           ]
+                   regexFieldReference: 'value',
+               },
+           ],
        },
        3003: {
+           onlineHelp: 'pmg_mailfilter_what',
+           iconCls: 'fa fa-file-image-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'contenttype',
            width: 400,
@@ -329,8 +401,8 @@ Ext.define('PMG.Utils', {
                        autoLoad: true,
                        proxy: {
                            type: 'proxmox',
-                           url: '/api2/json/config/mimetypes'
-                       }
+                           url: '/api2/json/config/mimetypes',
+                       },
                    },
                    fieldLabel: gettext('Content Type'),
                    anyMatch: true,
@@ -339,19 +411,21 @@ Ext.define('PMG.Utils', {
                        change: function(cb, value) {
                            var me = this;
                            me.up().down('displayfield').setValue(value);
-                       }
-                   }
+                       },
+                   },
                },
                {
                    xtype: 'displayfield',
                    fieldLabel: gettext('Value'),
                    labelWidth: 150,
                    allowBlank: false,
-                   reset: Ext.emptyFn
-               }
-           ]
+                   reset: Ext.emptyFn,
+               },
+           ],
        },
        3004: {
+           onlineHelp: 'pmg_mailfilter_regex',
+           iconCls: 'fa fa-file-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'filenamefilter',
            width: 400,
@@ -363,18 +437,20 @@ Ext.define('PMG.Utils', {
                    reference: 'filename',
                    fieldLabel: gettext('Filename'),
                    labelWidth: 150,
-                   allowBlank: false
+                   allowBlank: false,
                },
                {
                    labelWidth: 150,
                    fieldLabel: gettext('Test String'),
                    wholeMatch: true,
                    xtype: 'pmgRegexTester',
-                   regexFieldReference: 'filename'
-               }
-           ]
+                   regexFieldReference: 'filename',
+               },
+           ],
        },
        3005: {
+           onlineHelp: 'pmg_mailfilter_what',
+           iconCls: 'fa fa-file-archive-o',
            xtype: 'proxmoxWindowEdit',
            subdir: 'archivefilter',
            width: 400,
@@ -392,8 +468,8 @@ Ext.define('PMG.Utils', {
                        autoLoad: true,
                        proxy: {
                            type: 'proxmox',
-                           url: '/api2/json/config/mimetypes'
-                       }
+                           url: '/api2/json/config/mimetypes',
+                       },
                    },
                    fieldLabel: gettext('Content Type'),
                    anyMatch: true,
@@ -402,19 +478,135 @@ Ext.define('PMG.Utils', {
                        change: function(cb, value) {
                            var me = this;
                            me.up().down('displayfield').setValue(value);
-                       }
-                   }
+                       },
+                   },
                },
                {
                    xtype: 'displayfield',
                    fieldLabel: gettext('Value'),
                    labelWidth: 150,
                    allowBlank: false,
-                   reset: Ext.emptyFn
-               }
-           ]
+                   reset: Ext.emptyFn,
+               },
+           ],
+       },
+       3006: {
+           onlineHelp: 'pmg_mailfilter_regex',
+           iconCls: 'fa fa-file-archive-o',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'archivefilenamefilter',
+           width: 400,
+           subject: gettext('Match Archive Filename'),
+           items: [
+               {
+                   xtype: 'textfield',
+                   name: 'filename',
+                   reference: 'filename',
+                   fieldLabel: gettext('Filename'),
+                   labelWidth: 150,
+                   allowBlank: false,
+               },
+               {
+                   labelWidth: 150,
+                   fieldLabel: gettext('Test String'),
+                   wholeMatch: true,
+                   xtype: 'pmgRegexTester',
+                   regexFieldReference: 'filename',
+               },
+           ],
+       },
+       4002: {
+           onlineHelp: 'pmg_mailfilter_action',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'notification',
+           subject: gettext('Notification'),
+           width: 400,
+           items: [
+               {
+                   xtype: 'textfield',
+                   name: 'name',
+                   allowBlank: false,
+                   fieldLabel: gettext('Name'),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'info',
+                   fieldLabel: gettext("Comment"),
+               },
+               {
+                   xtype: 'textfield',
+                   name: 'to',
+                   allowBlank: false,
+                   value: '__ADMIN__',
+                   fieldLabel: gettext('Receiver'),
+               },
+               {
+                   xtype: 'textfield',
+                   name: 'subject',
+                   allowBlank: false,
+                   value: 'Notification: __SUBJECT__',
+                   fieldLabel: gettext('Subject'),
+               },
+               {
+                   xtype: 'textarea',
+                   name: 'body',
+                   allowBlank: false,
+                   grow: true,
+                   growMax: 250,
+                   value:
+                       "Proxmox Notifcation:\n\n" +
+                       "Sender:   __SENDER__\n" +
+                       "Receiver: __RECEIVERS__\n" +
+                       "Targets:  __TARGETS__\n\n" +
+                       "Subject:  __SUBJECT__\n\n" +
+                       "Matching Rule: __RULE__\n\n" +
+                       "__RULE_INFO__\n\n" +
+                       "__VIRUS_INFO__\n" +
+                       "__SPAM_INFO__\n",
+                   fieldLabel: gettext('Body'),
+               },
+               {
+                   xtype: 'proxmoxcheckbox',
+                   name: 'attach',
+                   fieldLabel: gettext("Attach orig. Mail"),
+               },
+           ],
+       },
+       4003: {
+           onlineHelp: 'pmg_mailfilter_action',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'field',
+           subject: gettext('Header Attribute'),
+           width: 400,
+           items: [
+               {
+                   xtype: 'textfield',
+                   name: 'name',
+                   allowBlank: false,
+                   fieldLabel: gettext('Name'),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'info',
+                   fieldLabel: gettext("Comment"),
+               },
+               {
+                   xtype: 'textfield',
+                   name: 'field',
+                   allowBlank: false,
+                   fieldLabel: gettext('Field'),
+               },
+               {
+                   xtype: 'textfield',
+                   reference: 'value',
+                   name: 'value',
+                   allowBlank: false,
+                   fieldLabel: gettext('Value'),
+               },
+           ],
        },
        4005: {
+           onlineHelp: 'pmg_mailfilter_action',
            xtype: 'proxmoxWindowEdit',
            subdir: 'bcc',
            subject: gettext('BCC'),
@@ -424,64 +616,131 @@ Ext.define('PMG.Utils', {
                    xtype: 'textfield',
                    name: 'name',
                    allowBlank: false,
-                   fieldLabel: gettext('Name')
+                   fieldLabel: gettext('Name'),
                },
                {
                    xtype: 'textareafield',
                    name: 'info',
-                   fieldLabel: gettext("Description")
+                   fieldLabel: gettext("Comment"),
                },
                {
                    xtype: 'textfield',
                    name: 'target',
                    allowBlank: false,
-                   fieldLabel: gettext("Target")
+                   fieldLabel: gettext("Target"),
                },
                {
                    xtype: 'proxmoxcheckbox',
                    checked: true,
                    name: 'original',
-                   fieldLabel: gettext("send orig. Mail")
-               }
-           ]
-
-       }
-    },
+                   fieldLabel: gettext("send orig. Mail"),
+               },
+           ],
 
-    openVNCViewer: function(consoletype, nodename) {
-       var url = Ext.urlEncode({
-           console: consoletype, // upgrade or shell
-           novnc: 1,
-           node: nodename
-       });
-       var nw = window.open("?" + url, '_blank',
-                            "innerWidth=745,innerheight=427");
-       nw.focus();
+       },
+       4007: {
+           onlineHelp: 'pmg_mailfilter_action',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'removeattachments',
+           subject: gettext('Remove Attachments'),
+           width: 500,
+           fieldDefaults: {
+               labelWidth: 150,
+           },
+           items: [
+               {
+                   xtype: 'textfield',
+                   name: 'name',
+                   allowBlank: false,
+                   fieldLabel: gettext('Name'),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'info',
+                   fieldLabel: gettext("Comment"),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'text',
+                   grow: true,
+                   growMax: 250,
+                   fieldLabel: gettext("Text Replacement"),
+               },
+               {
+                   xtype: 'proxmoxcheckbox',
+                   checked: true,
+                   name: 'all',
+                   fieldLabel: gettext("Remove all attachments"),
+               },
+               {
+                   xtype: 'proxmoxcheckbox',
+                   checked: false,
+                   name: 'quarantine',
+                   fieldLabel: gettext("Copy orignal mail to Attachment Quarantine"),
+               },
+           ],
+       },
+       4009: {
+           onlineHelp: 'pmg_mailfilter_action',
+           xtype: 'proxmoxWindowEdit',
+           subdir: 'disclaimer',
+           subject: gettext('Disclaimer'),
+           width: 400,
+           items: [
+               {
+                   xtype: 'textfield',
+                   name: 'name',
+                   allowBlank: false,
+                   fieldLabel: gettext('Name'),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'info',
+                   fieldLabel: gettext("Comment"),
+               },
+               {
+                   xtype: 'textareafield',
+                   name: 'disclaimer',
+                   grow: true,
+                   growMax: 250,
+                   fieldLabel: gettext("Disclaimer"),
+               },
+           ],
+       },
     },
 
     updateLoginData: function(data) {
        Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
        Proxmox.UserName = data.username;
-       Ext.util.Cookies.set('PMGAuthCookie', data.ticket, null, '/', null, true );
+       Ext.util.Cookies.set('PMGAuthCookie', data.ticket, null, '/', null, true);
     },
 
     quarantineActionExtracted: false,
 
     extractQuarantineAction: function() {
-
-       if (PMG.Utils.quarantineActionExtracted) { return; }
+       if (PMG.Utils.quarantineActionExtracted) {
+           return null;
+       }
 
        PMG.Utils.quarantineActionExtracted = true;
 
-       var qs = Ext.Object.fromQueryString(location.search);
+       let qs = Ext.Object.fromQueryString(location.search);
 
-       var cselect = qs.cselect;
-       var action = qs.action;
-       var ticket = qs.ticket;
+       let cselect = qs.cselect;
+       let action = qs.action;
+       let dateString = qs.date;
+
+       if (dateString) {
+           let date = new Date(dateString).getTime()/1000;
+
+           // set from date for QuarantineList
+           PMG.QuarantineList.from = date;
+       }
 
        delete qs.cselect;
        delete qs.action;
        delete qs.ticket;
+       delete qs.date;
 
        var newsearch = Ext.Object.toQueryString(qs);
 
@@ -490,12 +749,16 @@ Ext.define('PMG.Utils', {
        newurl += location.hash;
 
        if (window.history) {
-           window.history.pushState({ path:newurl }, '', newurl);
+           window.history.pushState({ path: newurl }, '', newurl);
        }
 
        if (action || cselect) {
-           return { action: action, cselect: cselect };
+           return {
+               action: action,
+               cselect: cselect,
+           };
        }
+       return null;
     },
 
     doQuarantineAction: function(action, id, callback) {
@@ -503,30 +766,72 @@ Ext.define('PMG.Utils', {
            url: '/quarantine/content/',
            params: {
                action: action,
-               id: id
+               id: id,
            },
            method: 'POST',
            failure: function(response, opts) {
                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
            },
            success: function(response, opts) {
-               var win = Ext.create('Ext.window.MessageBox',{
-                   closeAction: 'destroy'
-               }).show({
-                   title: gettext('Info'),
-                   message: "Action '" + action + ' ' +
-                   id + "' successful",
-                   buttons: Ext.Msg.OK,
-                   icon: Ext.MessageBox.INFO
+               let count = id.split(';').length;
+               let fmt = count > 1
+                   ? gettext("Action '{0}' for '{1}' items successful")
+                   : gettext("Action '{0}' successful")
+                   ;
+               let message = Ext.String.format(fmt, action, count);
+               let title = Ext.String.format("{0} successful", Ext.String.capitalize(action));
+
+               Ext.toast({
+                   html: message,
+                   title: title,
+                   minWidth: 200,
+                   hideDuration: 250,
+                   slideBackDuration: 250,
+                   slideBackAnimation: 'easeOut',
+                   iconCls: 'fa fa-check',
+                   shadow: true,
+                   align: 'br',
                });
 
                if (Ext.isFunction(callback)) {
                    callback();
                }
-           }
+           },
        });
     },
 
+    render_filetype: function(value) {
+       let iconCls = 'fa-file-o';
+       let text = Proxmox.Utils.unknownText;
+
+       if (!value) {
+           return `<i class='fa ${iconCls}'></i> ${text}`;
+       }
+
+       text = value.toString().toLowerCase();
+       const type = text.split('/')[0];
+
+       switch (type) {
+           case 'audio':
+           case 'image':
+           case 'video':
+           case 'text':
+               iconCls = `fa-file-${type}-o`;
+               break;
+           case 'application': {
+               const subtypes = ['excel', 'pdf', 'word', 'powerpoint'];
+               let found = subtypes.find(st => text.includes(st));
+               if (found !== undefined) {
+                   iconCls = `fa-file-${found}-o`;
+               }
+           } break;
+           default:
+               break;
+       }
+
+       return `<i class='fa ${iconCls}'></i> ${text}`;
+    },
+
     sender_renderer: function(value, metaData, rec) {
        var subject = Ext.htmlEncode(value);
        var from = Ext.htmlEncode(rec.data.from);
@@ -542,5 +847,67 @@ Ext.define('PMG.Utils', {
        var me = this;
 
        // do whatever you want here
-    }
+       Proxmox.Utils.override_task_descriptions({
+           applycustomscores: ['', gettext('Apply custom SpamAssassin scores')],
+           avupdate: ['', gettext('ClamAV update')],
+           backup: ['', gettext('Backup')],
+           clustercreate: ['', gettext('Create Cluster')],
+           clusterjoin: ['', gettext('Join Cluster')],
+           restore: ['', gettext('Restore')],
+           saupdate: ['', gettext('SpamAssassin update')],
+       });
+    },
+});
+
+Ext.define('PMG.Async', {
+    singleton: true,
+
+    // Returns a Promise which executes a quarantine action when awaited.
+    // Shows a Toast message box once completed, if batchNumber and batchTotal
+    // are set, they will be included into the title of that toast.
+    doQAction: function(action, ids, batchNumber, batchTotal) {
+       if (!Ext.isArray(ids)) {
+           ids = [ids];
+       }
+       return Proxmox.Async.api2({
+           url: '/quarantine/content/',
+           params: {
+               action: action,
+               id: ids.join(';'),
+           },
+           method: 'POST',
+       }).then(
+           response => {
+               let count = ids.length;
+               let fmt = count > 1
+                   ? gettext("Action '{0}' for '{1}' items successful")
+                   : gettext("Action '{0}' successful")
+                   ;
+               let message = Ext.String.format(fmt, action, count);
+               let titleFmt = batchNumber !== undefined && batchTotal > 1
+                   ? gettext("{0} ({1}/{2}) successful")
+                   : gettext("{0} successful")
+                   ;
+               let title = Ext.String.format(
+                   titleFmt,
+                   Ext.String.capitalize(action),
+                   batchNumber,
+                   batchTotal,
+               );
+
+               Ext.toast({
+                   html: message,
+                   title: title,
+                   minWidth: 200,
+                   hideDuration: 250,
+                   slideBackDuration: 250,
+                   slideBackAnimation: 'easeOut',
+                   iconCls: 'fa fa-check',
+                   shadow: true,
+                   align: 'br',
+               });
+           },
+           response => Proxmox.Utils.alertResponseFailure(response),
+       );
+    },
 });