Преглед изворни кода

Updated react UI and UX part.

Alexander пре 4 година
родитељ
комит
91e7d9ff4a
100 измењених фајлова са 1522 додато и 832 уклоњено
  1. 1 1
      web/js/react/src/ControlPanelService/Backup.js
  2. 16 3
      web/js/react/src/ControlPanelService/Dns.js
  3. 1 1
      web/js/react/src/ControlPanelService/Search.js
  4. 27 0
      web/js/react/src/actions/MenuCounters/menuCounterActions.js
  5. 1 0
      web/js/react/src/actions/MenuCounters/menuCounterTypes.js
  6. 39 6
      web/js/react/src/actions/Session/sessionActions.js
  7. 1 1
      web/js/react/src/components/Backup/Backup.jsx
  8. 2 1
      web/js/react/src/components/Backup/Exclusion/Edit/index.jsx
  9. 21 6
      web/js/react/src/components/ControlPanel/AddItemLayout/AddItemLayout.scss
  10. 1 5
      web/js/react/src/components/ControlPanel/AddItemLayout/Form/TextInputWithExtraButton/TextInputWithExtraButton.jsx
  11. 1 1
      web/js/react/src/components/ControlPanel/AddItemLayout/Form/TextInputWithTextOnTheRight/TextInputWithTextOnTheRight.jsx
  12. 9 8
      web/js/react/src/components/CronJob/Add/AddCronJob.jsx
  13. 2 2
      web/js/react/src/components/CronJob/CronJob.jsx
  14. 10 7
      web/js/react/src/components/CronJob/Edit/EditCronJob.jsx
  15. 11 8
      web/js/react/src/components/DNSRecord/Add/AddDNSRecord.jsx
  16. 13 1
      web/js/react/src/components/DNSRecord/DNSRecord.jsx
  17. 10 7
      web/js/react/src/components/DNSRecord/Edit/EditDNSRecord.jsx
  18. 17 17
      web/js/react/src/components/Database/Add/AddDatabase.jsx
  19. 32 9
      web/js/react/src/components/Database/Edit/EditDatabase.jsx
  20. 9 0
      web/js/react/src/components/Database/Edit/EditDatabase.scss
  21. 9 9
      web/js/react/src/components/DomainNameSystem/Add/AddDomainNameSystem.jsx
  22. 10 7
      web/js/react/src/components/DomainNameSystem/Edit/EditDomainNameSystem.jsx
  23. 9 8
      web/js/react/src/components/Firewall/Add/AddFirewall.jsx
  24. 2 1
      web/js/react/src/components/Firewall/Add/Banlist/index.jsx
  25. 10 7
      web/js/react/src/components/Firewall/Edit/EditFirewall.jsx
  26. 9 8
      web/js/react/src/components/InternetProtocol/Add/AddInternetProtocol.jsx
  27. 13 3
      web/js/react/src/components/InternetProtocol/Edit/EditInternetProtocol.jsx
  28. 2 2
      web/js/react/src/components/Lists/Row/Row.scss
  29. 10 0
      web/js/react/src/components/Log/Log.scss
  30. 12 6
      web/js/react/src/components/Mail/Add/AddMail.jsx
  31. 10 7
      web/js/react/src/components/Mail/Edit/EditMail.jsx
  32. 1 1
      web/js/react/src/components/Mail/Mail.jsx
  33. 5 1
      web/js/react/src/components/Mail/Mail.scss
  34. 13 7
      web/js/react/src/components/MailAccount/Add/AddMailAccount.jsx
  35. 15 0
      web/js/react/src/components/MailAccount/Add/AddMailAccount.scss
  36. 66 73
      web/js/react/src/components/MailAccount/Edit/EditMailAccount.jsx
  37. 3 1
      web/js/react/src/components/MailAccount/MailInfoBlock/MailInfoBlock.scss
  38. 2 1
      web/js/react/src/components/MainNav/MainNav.jsx
  39. 9 6
      web/js/react/src/components/MainNav/Panel/Notifications/Notifications.jsx
  40. 2 3
      web/js/react/src/components/MainNav/Panel/Panel.jsx
  41. 34 9
      web/js/react/src/components/MainNav/Panel/Panel.scss
  42. 2 1
      web/js/react/src/components/MainNav/Stat-menu/Menu.jsx
  43. 0 6
      web/js/react/src/components/MainNav/Toolbar/LeftButton/LeftButton.scss
  44. 15 5
      web/js/react/src/components/MainNav/Toolbar/SearchInput/SearchInput.jsx
  45. 9 1
      web/js/react/src/components/MainNav/Toolbar/Select/Select.jsx
  46. 27 5
      web/js/react/src/components/MainNav/Toolbar/Toolbar.scss
  47. 2 0
      web/js/react/src/components/Modal/Modal.scss
  48. 9 8
      web/js/react/src/components/Package/Add/AddPackage.jsx
  49. 13 4
      web/js/react/src/components/Package/Edit/EditPackage.jsx
  50. 1 2
      web/js/react/src/components/Package/Package.jsx
  51. 3 3
      web/js/react/src/components/Path/Path.jsx
  52. 51 7
      web/js/react/src/components/Searchitem/SearchItem.jsx
  53. 2 1
      web/js/react/src/components/Server/Edit/Bind9/Bind9.jsx
  54. 2 1
      web/js/react/src/components/Server/Edit/Dovecot/Dovecot.jsx
  55. 2 1
      web/js/react/src/components/Server/Edit/EditServer.jsx
  56. 2 1
      web/js/react/src/components/Server/Edit/Httpd/EditHttpd.jsx
  57. 2 1
      web/js/react/src/components/Server/Edit/Mysql/Mysql.jsx
  58. 2 1
      web/js/react/src/components/Server/Edit/Nginx/EditServerNginx.jsx
  59. 2 1
      web/js/react/src/components/Server/Edit/PHP/EditPhp.jsx
  60. 2 1
      web/js/react/src/components/Server/Edit/Postgresql/Postgresql.jsx
  61. 2 1
      web/js/react/src/components/Server/Edit/Service/Service.jsx
  62. 1 2
      web/js/react/src/components/TopPanel/TopPanel.jsx
  63. 17 10
      web/js/react/src/components/User/Add/AddUser.jsx
  64. 10 7
      web/js/react/src/components/User/Edit/EditUser.jsx
  65. 1 2
      web/js/react/src/components/User/User.jsx
  66. 1 0
      web/js/react/src/components/User/User.scss
  67. 11 8
      web/js/react/src/components/WebDomain/Add/AddWebDomain.jsx
  68. 61 60
      web/js/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.jsx
  69. 13 12
      web/js/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.jsx
  70. 48 0
      web/js/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.scss
  71. 10 7
      web/js/react/src/components/WebDomain/Edit/EditWeb.jsx
  72. 2 2
      web/js/react/src/components/WebDomain/WebDomain.jsx
  73. 1 1
      web/js/react/src/components/WebDomain/WebDomain.scss
  74. 0 4
      web/js/react/src/containers/App/App.js
  75. 4 0
      web/js/react/src/containers/App/App.scss
  76. 56 30
      web/js/react/src/containers/Backups/Backups.jsx
  77. 4 5
      web/js/react/src/containers/ControlPanelContent/ControlPanelContent.jsx
  78. 51 27
      web/js/react/src/containers/CronJobs/CronJobs.jsx
  79. 44 28
      web/js/react/src/containers/DNSRecords/DNSRecords.jsx
  80. 1 0
      web/js/react/src/containers/DNSRecords/DNSRecords.scss
  81. 44 27
      web/js/react/src/containers/Databases/Databases.jsx
  82. 44 28
      web/js/react/src/containers/DomainNameSystems/DomainNameSystems.jsx
  83. 8 7
      web/js/react/src/containers/FileManager/FileManager.js
  84. 40 23
      web/js/react/src/containers/Firewalls/Banlist/index.jsx
  85. 48 27
      web/js/react/src/containers/Firewalls/Firewalls.jsx
  86. 47 27
      web/js/react/src/containers/InternetProtocols/InternetProtocols.jsx
  87. 44 27
      web/js/react/src/containers/MailAccounts/MailAccounts.jsx
  88. 2 0
      web/js/react/src/containers/MailAccounts/MailAccounts.scss
  89. 43 27
      web/js/react/src/containers/Mails/Mails.jsx
  90. 43 25
      web/js/react/src/containers/Packages/Packages.jsx
  91. 8 2
      web/js/react/src/containers/RRDs/RRDs.scss
  92. 43 23
      web/js/react/src/containers/Search/Search.jsx
  93. 42 25
      web/js/react/src/containers/Servers/Servers.jsx
  94. 7 1
      web/js/react/src/containers/Statistics/Statistics.scss
  95. 41 26
      web/js/react/src/containers/Users/Users.jsx
  96. 41 29
      web/js/react/src/containers/Web/Web.jsx
  97. 1 2
      web/js/react/src/containers/WebLogs/WebLogs.jsx
  98. 19 0
      web/js/react/src/reducers/MenuCounters/menuCounterReducer.js
  99. 1 1
      web/js/react/src/reducers/Notification/notificationReducer.js
  100. 0 5
      web/js/react/src/reducers/Session/sessionReducer.js

+ 1 - 1
web/js/react/src/ControlPanelService/Backup.js

@@ -3,7 +3,7 @@ import { getAuthToken } from "src/utils/token";
 
 const BASE_URL = window.location.origin;
 const webApiUri = '/api/v1/list/backup/index.php';
-const scheduleBackupUri = '/schedule/backup/';
+const scheduleBackupUri = '/api/v1/schedule/restore/';
 const backupDetailsUri = '/api/v1/list/backup/index.php';
 const backupExclusionsUri = '/api/v1/list/backup/exclusions/index.php';
 const backupExclusionsInfoUri = '/api/v1/edit/backup/exclusions/index.php';

+ 16 - 3
web/js/react/src/ControlPanelService/Dns.js

@@ -19,13 +19,26 @@ export const getDNSRecordInfo = (domain, recordId) => {
   return axios.get(`${BASE_URL}${updateDNSUri}?domain=${domain}&record_id=${recordId}`);
 }
 
-export const bulkAction = (action, domainNameSystems) => {
+export const bulkDomainAction = (action, domains) => {
   const formData = new FormData();
   formData.append("action", action);
   formData.append("token", getAuthToken());
 
-  domainNameSystems.forEach(domainNameSystem => {
-    formData.append("domain[]", domainNameSystem);
+  domains.forEach(record => {
+    formData.append("domain[]", record);
+  });
+
+  return axios.post(BASE_URL + '/api/v1/bulk/dns/', formData);
+};
+
+export const bulkAction = (action, records, domain) => {
+  const formData = new FormData();
+  formData.append("action", action);
+  formData.append("token", getAuthToken());
+  formData.append("domain", domain);
+
+  records.forEach(record => {
+    formData.append("record[]", record);
   });
 
   return axios.post(BASE_URL + '/api/v1/bulk/dns/', formData);

+ 1 - 1
web/js/react/src/ControlPanelService/Search.js

@@ -2,7 +2,7 @@ import axios from "axios";
 import { getAuthToken } from "src/utils/token";
 
 const BASE_URL = window.location.origin;
-const webApiUri = '/search/search.php';
+const webApiUri = '/api/v1/search/';
 
 export const getSearchResultsList = term => {
   return axios.get(BASE_URL + webApiUri + '?q=' + term);

+ 27 - 0
web/js/react/src/actions/MenuCounters/menuCounterActions.js

@@ -0,0 +1,27 @@
+import { REFRESH_COUNTERS } from './menuCounterTypes';
+import { checkAuth } from 'src/services/session';
+import { setAuthToken } from 'src/utils/token';
+
+export const refreshCounters = () => (dispatch, getState) => {
+  return new Promise((resolve, reject) => {
+    checkAuth()
+      .then(res => {
+        const { data, token } = res.data;
+
+        if (token) setAuthToken(token);
+
+        dispatch({
+          type: REFRESH_COUNTERS,
+          value: {
+            user: data
+          }
+        });
+
+        resolve(token);
+      })
+      .catch(err => {
+        reject();
+        console.error(err);
+      });
+  });
+}

+ 1 - 0
web/js/react/src/actions/MenuCounters/menuCounterTypes.js

@@ -0,0 +1 @@
+export const REFRESH_COUNTERS = 'REFRESH_COUNTERS';

+ 39 - 6
web/js/react/src/actions/Session/sessionActions.js

@@ -2,6 +2,7 @@ import { LOGIN, LOGOUT, LOGGED_OUT_AS, CHECK_AUTH, RESET_PASSWORD } from './sess
 import { checkAuth, signIn, signInAs, signOut } from 'src/services/session';
 import { resetPassword } from 'src/ControlPanelService/ResetPassword';
 import { resetAuthToken, setAuthToken } from 'src/utils/token';
+import { REFRESH_COUNTERS } from '../MenuCounters/menuCounterTypes';
 
 const LOGOUT_RESPONSE = 'logged_out';
 const LOGOUT_AS_RESPONSE = 'logged_out_as';
@@ -21,10 +22,15 @@ export const login = (user, password) => dispatch => {
           session,
           i18n: i18n || {},
           userName: user,
-          user: data,
           error
         },
       });
+      dispatch({
+        type: REFRESH_COUNTERS,
+        value: {
+          user: data,
+        }
+      });
       resolve(token);
     }, (error) => {
       reject(error);
@@ -44,10 +50,15 @@ export const reset = ({ user = '', code = '', password = '', password_confirm =
           panel,
           session,
           userName: user,
-          user: {},
           error
         },
       });
+      dispatch({
+        type: REFRESH_COUNTERS,
+        value: {
+          user: {},
+        }
+      });
       resolve(token);
     }, (error) => {
       reject(error);
@@ -65,7 +76,6 @@ export const loginAs = username => dispatch => {
         type: LOGIN,
         value: {
           userName: user,
-          user: data,
           i18n,
           session,
           panel,
@@ -73,6 +83,12 @@ export const loginAs = username => dispatch => {
           error
         }
       });
+      dispatch({
+        type: REFRESH_COUNTERS,
+        value: {
+          user: data,
+        }
+      });
 
       resolve(token);
     }, (error) => {
@@ -94,7 +110,6 @@ export const logout = () => (dispatch, getState) => {
           type: LOGOUT,
           value: {
             userName: '',
-            user: {},
             token: '',
             panel: {},
             session: {},
@@ -102,6 +117,12 @@ export const logout = () => (dispatch, getState) => {
             error,
           },
         });
+        dispatch({
+          type: REFRESH_COUNTERS,
+          value: {
+            user: {},
+          }
+        });
 
         resolve();
       } else if (logout_response === LOGOUT_AS_RESPONSE) {
@@ -109,7 +130,6 @@ export const logout = () => (dispatch, getState) => {
           type: LOGGED_OUT_AS,
           value: {
             userName,
-            user,
             session,
             panel,
             token: '',
@@ -117,6 +137,12 @@ export const logout = () => (dispatch, getState) => {
             error,
           },
         });
+        dispatch({
+          type: REFRESH_COUNTERS,
+          value: {
+            user,
+          }
+        });
 
         resolve();
       } else {
@@ -135,11 +161,12 @@ export const checkAuthHandler = () => (dispatch, getState) => {
       .then(res => {
         const { user, data, session, panel, error, i18n, token } = res.data;
 
+        if (token) setAuthToken(token);
+
         dispatch({
           type: CHECK_AUTH,
           value: {
             userName: user,
-            user: data,
             i18n,
             session,
             panel,
@@ -147,6 +174,12 @@ export const checkAuthHandler = () => (dispatch, getState) => {
             error
           }
         });
+        dispatch({
+          type: REFRESH_COUNTERS,
+          value: {
+            user: data,
+          }
+        });
 
         resolve(token);
       })

+ 1 - 1
web/js/react/src/components/Backup/Backup.jsx

@@ -57,7 +57,7 @@ const Backup = props => {
         {data.UPDATED === 'no' && <div><a href={`/update/vesta/?pkg=${data.NAME}`}>{i18n.update} <FontAwesomeIcon icon="wrench" /></a></div>}
 
         <div>
-          <a className="link-download" href={`/download/backup/?backup=${data.NAME}&token=${token}`}>
+          <a className="link-download" href={`/api/v1/download/backup/?backup=${data.NAME}&token=${token}`}>
             {i18n.download}
             {data.FOCUSED ? <span className="shortcut-button">D</span> : <FontAwesomeIcon icon={faFileDownload} />}
           </a>

+ 2 - 1
web/js/react/src/components/Backup/Exclusion/Edit/index.jsx

@@ -11,6 +11,7 @@ import { useDispatch, useSelector } from 'react-redux';
 import { Helmet } from 'react-helmet';
 
 import './style.scss';
+import HtmlParser from 'react-html-parser';
 
 const EditBackupExclusions = () => {
   const token = localStorage.getItem("token");
@@ -90,7 +91,7 @@ const EditBackupExclusions = () => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 21 - 6
web/js/react/src/components/ControlPanel/AddItemLayout/AddItemLayout.scss

@@ -32,7 +32,7 @@ $errorColor: #BE5ABF;
   
     div.error,
     div.success {
-      width: fit-content !important;
+      width: max-content !important;
   
       span {
         font-weight: bold;
@@ -40,6 +40,7 @@ $errorColor: #BE5ABF;
   
         svg {
           font-size: 13px;
+          margin-right: 10px;
         }
       }
   
@@ -151,10 +152,24 @@ $errorColor: #BE5ABF;
       }
     }
 
-    textarea {
-      &:focus {
-        background: #D7F9FF;
-      }
+    input:-webkit-autofill,
+    input:-webkit-autofill:hover,
+    input:-webkit-autofill:focus,
+    input:-webkit-autofill:active  {
+      background-color: $primaryLight;
+      border-color: $primaryActive;
+      filter: none;
+      box-shadow: none;
+    }
+
+    input:autofill,
+    input:autofill:hover,
+    input:autofill:focus,
+    input:autofill:active  {
+      background-color: $primaryLight;
+      border-color: $primaryActive;
+      filter: none;
+      box-shadow: none;
     }
 
     button {
@@ -180,7 +195,7 @@ $errorColor: #BE5ABF;
       &:active {
         box-shadow: unset;
         border-color: $primaryActive;
-        background: #D7F9FF;
+        background: #d7dcef9e;
       }
     }
     

+ 1 - 5
web/js/react/src/components/ControlPanel/AddItemLayout/Form/TextInputWithExtraButton/TextInputWithExtraButton.jsx

@@ -7,11 +7,7 @@ const TextInputWithExtraButton = props => {
   });
 
   useEffect(() => {
-    if (props.value !== 'unlimited') {
-      setState({ ...state, value: state.previousValue });
-    } else {
-      setState({ ...state, value: props.value });
-    }
+    setState({ ...state, value: props.value });
   }, [props.value]);
 
   useEffect(() => {

+ 1 - 1
web/js/react/src/components/ControlPanel/AddItemLayout/Form/TextInputWithTextOnTheRight/TextInputWithTextOnTheRight.jsx

@@ -27,7 +27,7 @@ const TextInputWithTextOnTheRight = ({ id, title, name, defaultValue = '', optio
           onChange={event => setInputValue(event.target.value)}
           disabled={disabled}
           name={name} />
-        <span><i>{`admin_${inputValue}`}</i></span>
+        <span><i>{`${inputValue}`}</i></span>
       </div>
     </div>
   );

+ 9 - 8
web/js/react/src/components/CronJob/Add/AddCronJob.jsx

@@ -12,6 +12,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddCronJob.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddCronJob = props => {
   const { i18n } = useSelector(state => state.session);
@@ -46,18 +48,17 @@ const AddCronJob = props => {
 
     if (Object.keys(newCronJob).length !== 0 && newCronJob.constructor === Object) {
       setState({ ...state, loading: true });
-
       addCronJob(newCronJob)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false })
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -100,7 +101,7 @@ const AddCronJob = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 2
web/js/react/src/components/CronJob/CronJob.jsx

@@ -25,11 +25,11 @@ const CronJob = props => {
 
   const handleSuspend = () => {
     let suspendedStatus = data.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend' === 'yes' ? 'unsuspend' : 'suspend';
-    props.handleModal(data.suspend_conf, `/${suspendedStatus}/cron/index.php?job=${data.NAME}`);
+    props.handleModal(data.suspend_conf, `/api/v1/${suspendedStatus}/cron/index.php?job=${data.NAME}`);
   }
 
   const handleDelete = () => {
-    props.handleModal(data.delete_conf, `/delete/cron/index.php?job=${data.NAME}`);
+    props.handleModal(data.delete_conf, `/api/v1/delete/cron/index.php?job=${data.NAME}`);
   }
 
   return (

+ 10 - 7
web/js/react/src/components/CronJob/Edit/EditCronJob.jsx

@@ -13,6 +13,9 @@ import QS from 'qs';
 
 import './EditCronJob.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditMail = props => {
   const token = localStorage.getItem("token");
@@ -79,14 +82,14 @@ const EditMail = props => {
       updateCronJob(updatedJob, state.data.job)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -128,7 +131,7 @@ const EditMail = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 11 - 8
web/js/react/src/components/DNSRecord/Add/AddDNSRecord.jsx

@@ -5,7 +5,6 @@ import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectIn
 import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
 import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { addMail } from '../../../ControlPanelService/Mail';
 import { addDomainNameSystemRecord } from '../../../ControlPanelService/Dns';
 import Toolbar from '../../MainNav/Toolbar/Toolbar';
 import { useHistory } from 'react-router-dom';
@@ -14,6 +13,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddDNSRecord.scss'
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 export default function AddDNSRecord(props) {
   const { i18n } = useSelector(state => state.session);
@@ -61,16 +62,18 @@ export default function AddDNSRecord(props) {
     newDnsRecord['v_domain'] = props.domain;
 
     if (Object.keys(newDnsRecord).length !== 0 && newDnsRecord.constructor === Object) {
-      setState({ loading: true });
+      setState({ ...state, loading: true });
       addDomainNameSystemRecord(newDnsRecord)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -94,7 +97,7 @@ export default function AddDNSRecord(props) {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 13 - 1
web/js/react/src/components/DNSRecord/DNSRecord.jsx

@@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
 
 export default function DnsRecord({ data, domain, handleModal, ...props }) {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
 
   const toggleFav = (starred) => {
     if (starred) {
@@ -25,6 +24,10 @@ export default function DnsRecord({ data, domain, handleModal, ...props }) {
     handleModal(data.delete_conf, `/api/v1/delete/dns/?domain=${domain}&record_id=${data.ID}`);
   }
 
+  const handleSuspend = () => {
+    handleModal(data.suspend_conf, `/api/v1/${data.suspend_action}/dns/?domain=${domain}&record_id=${data.ID}`);
+  }
+
   return (
     <ListItem
       id={data.NAME}
@@ -62,6 +65,15 @@ export default function DnsRecord({ data, domain, handleModal, ...props }) {
           </Link>
         </div>
 
+        <div>
+          <button
+            className="link-gray"
+            onClick={handleSuspend}>
+            {data.suspend_action}
+            {data.FOCUSED ? <span className="shortcut-button">S</span> : <FontAwesomeIcon icon={data.SUSPENDED === 'yes' ? 'unlock' : 'lock'} />}
+          </button>
+        </div>
+
         <div>
           <button className="link-delete" onClick={() => handleDelete()}>
             {i18n.Delete}

+ 10 - 7
web/js/react/src/components/DNSRecord/Edit/EditDNSRecord.jsx

@@ -11,6 +11,9 @@ import { useHistory } from 'react-router-dom';
 import { useDispatch, useSelector } from 'react-redux';
 import QS from 'qs';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 export default function EditDNSRecord(props) {
   const token = localStorage.getItem("token");
@@ -81,14 +84,14 @@ export default function EditDNSRecord(props) {
       updateDNS(updatedRecord, props.domain, props.record_id)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -111,7 +114,7 @@ export default function EditDNSRecord(props) {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 17 - 17
web/js/react/src/components/Database/Add/AddDatabase.jsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { memo, useEffect, useState } from 'react';
 
 import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
 import { dbCharsets, addDatabase, getDbOptionalInfo } from '../../../ControlPanelService/Db';
@@ -9,11 +9,12 @@ import Toolbar from '../../MainNav/Toolbar/Toolbar';
 import { useHistory } from 'react-router-dom';
 import Spinner from '../../Spinner/Spinner';
 import { useDispatch, useSelector } from 'react-redux';
-
-import './AddDatabase.scss'
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
+import './AddDatabase.scss'
 
-const AddDatabase = props => {
+const AddDatabase = memo(props => {
   const { i18n } = useSelector(state => state.session);
   const token = localStorage.getItem("token");
   const dispatch = useDispatch();
@@ -91,23 +92,22 @@ const AddDatabase = props => {
       newDatabase[name] = value;
     }
 
-    newDatabase['v_database'] = `${state.user}_${state.databaseInputValue}`;
-    newDatabase['v_dbuser'] = `${state.user}_${state.databaseUserInputValue}`;
+    newDatabase['v_database'] = state.databaseInputValue;
+    newDatabase['v_dbuser'] = state.databaseUserInputValue;
 
     if (Object.keys(newDatabase).length !== 0 && newDatabase.constructor === Object) {
       setState({ ...state, loading: true });
-
       addDatabase(newDatabase)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage: '', loading: false });
             } else {
-              setState({ ...state, loading: false })
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -131,7 +131,7 @@ const AddDatabase = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>
@@ -141,7 +141,7 @@ const AddDatabase = props => {
             <input type="hidden" name="ok" value="add" />
             <input type="hidden" name="token" value={token} />
 
-            <span className="prefix" dangerouslySetInnerHTML={{ __html: state.prefixI18N }}></span>
+            <span className="prefix">{HtmlParser(state.prefixI18N)}</span>
 
             <div className="form-group database">
               <label htmlFor="database">{i18n.Database}</label>
@@ -215,6 +215,6 @@ const AddDatabase = props => {
       </AddItemLayout>
     </div>
   );
-}
+});
 
-export default AddDatabase;
+export default AddDatabase;

+ 32 - 9
web/js/react/src/components/Database/Edit/EditDatabase.jsx

@@ -14,15 +14,18 @@ import QS from 'qs';
 
 import './EditDatabase.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditDatabase = props => {
   const token = localStorage.getItem("token");
-  const { i18n } = useSelector(state => state.session);
+  const { i18n, userName } = useSelector(state => state.session);
   const history = useHistory();
   const dispatch = useDispatch();
   const [state, setState] = useState({
     data: {},
     loading: false,
+    databaseUserInputValue: '',
     errorMessage: '',
     okMessage: ''
   });
@@ -42,6 +45,7 @@ const EditDatabase = props => {
           setState({
             ...state,
             data: response.data,
+            databaseUserInputValue: response.data.dbuser.split('_').splice(1).join('_'),
             errorMessage: response.data['error_msg'],
             okMessage: response.data['ok_msg'],
             loading: false
@@ -60,6 +64,7 @@ const EditDatabase = props => {
     }
 
     updatedDatabase['v_database'] = state.data.database;
+    updatedDatabase['v_dbuser'] = `${userName}_${state.databaseUserInputValue}`;
 
     if (Object.keys(updatedDatabase).length !== 0 && updatedDatabase.constructor === Object) {
       setState({ ...state, loading: true });
@@ -67,14 +72,14 @@ const EditDatabase = props => {
       updateDatabase(updatedDatabase, state.data.database)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -82,6 +87,10 @@ const EditDatabase = props => {
     }
   }
 
+  const databaseUserInputHandler = value => {
+    setState({ ...state, databaseUserInputValue: value });
+  }
+
   return (
     <div className="edit-template edit-db">
       <Helmet>
@@ -97,7 +106,7 @@ const EditDatabase = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>
@@ -109,7 +118,21 @@ const EditDatabase = props => {
 
             <TextInputWithTextOnTheRight id="database" name="v_database" title={i18n['Database']} defaultValue={state.data.database} disabled />
 
-            <TextInputWithTextOnTheRight id="username" name="v_dbuser" title={i18n['User']} defaultValue={state.data.dbuser} />
+            <div className="form-group">
+              <div className="label-wrapper">
+                <label htmlFor="user">{i18n.User}</label>
+              </div>
+              <div className="input-wrapper">
+                <input
+                  type="text"
+                  className="form-control"
+                  id="user"
+                  value={state.databaseUserInputValue}
+                  onChange={event => databaseUserInputHandler(event.target.value)}
+                  name="v_dbuser" />
+                <span className="italic"><i>{`${userName}_${state.databaseUserInputValue}`}</i></span>
+              </div>
+            </div>
 
             <Password name="v_password" defaultValue={state.data.password} />
 

+ 9 - 0
web/js/react/src/components/Database/Edit/EditDatabase.scss

@@ -0,0 +1,9 @@
+.input-wrapper {
+  display: flex;
+  align-items: center;
+
+  span.italic {
+    margin-left: 15px;
+    color: #777;
+  }
+}

+ 9 - 9
web/js/react/src/components/DomainNameSystem/Add/AddDomainNameSystem.jsx

@@ -13,6 +13,8 @@ import './AddDomainNameSystem.scss';
 import AdvancedOptions from './AdvancedOptions/AdvancedOptions';
 import { addDomainNameSystem } from '../../../ControlPanelService/Dns';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddDomainNameSystem = props => {
   const { i18n } = useSelector(state => state.session);
@@ -33,7 +35,6 @@ const AddDomainNameSystem = props => {
     dispatch(removeFocusedElement());
 
     setState({ ...state, loading: true });
-
     getUserNS()
       .then(result => {
         if (result.data.length) {
@@ -53,18 +54,17 @@ const AddDomainNameSystem = props => {
 
     if (Object.keys(domainNameSystem).length !== 0 && domainNameSystem.constructor === Object) {
       setState({ ...state, loading: true });
-
       addDomainNameSystem(domainNameSystem)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -98,7 +98,7 @@ const AddDomainNameSystem = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 10 - 7
web/js/react/src/components/DomainNameSystem/Edit/EditDomainNameSystem.jsx

@@ -13,6 +13,9 @@ import QS from 'qs';
 
 import './EditDomainNameSystem.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditDomainNameSystem = props => {
   const token = localStorage.getItem("token");
@@ -66,14 +69,14 @@ const EditDomainNameSystem = props => {
       updateDNS(updatedDomain, state.data.domain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -96,7 +99,7 @@ const EditDomainNameSystem = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 9 - 8
web/js/react/src/components/Firewall/Add/AddFirewall.jsx

@@ -12,6 +12,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddFirewall.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddFirewall = props => {
   const token = localStorage.getItem("token");
@@ -48,18 +50,17 @@ const AddFirewall = props => {
 
     if (Object.keys(newFirewall).length !== 0 && newFirewall.constructor === Object) {
       setState({ ...state, loading: true });
-
       addFirewall(newFirewall)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false })
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -83,7 +84,7 @@ const AddFirewall = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Firewall/Add/Banlist/index.jsx

@@ -8,6 +8,7 @@ import Toolbar from '../../../MainNav/Toolbar/Toolbar';
 import { useHistory } from 'react-router-dom';
 import { useDispatch, useSelector } from 'react-redux';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const AddBanIP = () => {
   const { i18n } = useSelector(state => state.session);
@@ -74,7 +75,7 @@ const AddBanIP = () => {
         <div className="search-toolbar-name">{i18n['Adding IP Address to Banlist']}</div>
         <div className="error"><span className="error-message">{state.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}</span></div>
         <div className="success">
-          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span> </span>
+          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span> </span>
         </div>
       </Toolbar>
       <AddItemLayout>

+ 10 - 7
web/js/react/src/components/Firewall/Edit/EditFirewall.jsx

@@ -13,6 +13,9 @@ import QS from 'qs';
 
 import './EditFirewall.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditFirewall = props => {
   const token = localStorage.getItem("token");
@@ -64,14 +67,14 @@ const EditFirewall = props => {
       updateFirewall(updatedDomain, state.data.domain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -94,7 +97,7 @@ const EditFirewall = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 9 - 8
web/js/react/src/components/InternetProtocol/Add/AddInternetProtocol.jsx

@@ -14,6 +14,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddInternetProtocol.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddInternetProtocol = props => {
   const token = localStorage.getItem("token");
@@ -49,18 +51,17 @@ const AddInternetProtocol = props => {
 
     if (Object.keys(newIp).length !== 0 && newIp.constructor === Object) {
       setState({ ...state, loading: true });
-
       addInternetProtocol(newIp)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false })
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -96,7 +97,7 @@ const AddInternetProtocol = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 13 - 3
web/js/react/src/components/InternetProtocol/Edit/EditInternetProtocol.jsx

@@ -15,6 +15,9 @@ import QS from 'qs';
 
 import './EditInternetProtocol.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditInternetProtocol = () => {
   const token = localStorage.getItem("token");
@@ -72,8 +75,15 @@ const EditInternetProtocol = () => {
       updateInternetProtocol(updatedIP, state.data.ip)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-            setState({ ...state, errorMessage: error_msg || '', okMessage: ok_msg || '', loading: false });
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
+            }
           }
         })
         .catch(err => console.error(err));
@@ -99,7 +109,7 @@ const EditInternetProtocol = () => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 2
web/js/react/src/components/Lists/Row/Row.scss

@@ -169,8 +169,8 @@ li.inactive {
   background: rgb(220, 220, 220);
 }
 
-@media (max-width: 1200px){
+@media (max-width: 1320px){
   .fPermissions, .fOwner {
     display: none;
   }
-}
+}

+ 10 - 0
web/js/react/src/components/Log/Log.scss

@@ -1,4 +1,8 @@
 .logs-list {
+  .toolbar {
+    padding: 6px 12.5%;
+  }
+
   .statistic-item {
     .l-col {
       font-size: 13px;
@@ -25,4 +29,10 @@
       color: #5edad0;
     }
   }
+}
+
+@media (max-width: 1350px) {
+  .logs-list .toolbar {
+    padding: 6px 9.5%;
+  }
 }

+ 12 - 6
web/js/react/src/components/Mail/Add/AddMail.jsx

@@ -11,6 +11,9 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddMail.scss'
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddMail = props => {
   const { i18n } = useSelector(state => state.session);
@@ -40,15 +43,18 @@ const AddMail = props => {
     }
 
     if (Object.keys(newMailDomain).length !== 0 && newMailDomain.constructor === Object) {
+      setState({ ...state, loading: true });
       addMail(newMailDomain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '' });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -72,7 +78,7 @@ const AddMail = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 10 - 7
web/js/react/src/components/Mail/Edit/EditMail.jsx

@@ -13,6 +13,9 @@ import QS from 'qs';
 
 import './EditMail.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditMail = props => {
   const token = localStorage.getItem("token");
@@ -66,14 +69,14 @@ const EditMail = props => {
       updateMail(updatedDomain, state.data.domain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -96,7 +99,7 @@ const EditMail = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 1 - 1
web/js/react/src/components/Mail/Mail.jsx

@@ -62,7 +62,7 @@ const Mail = props => {
           </Container>
           <Container className="c-3">
             {printStat(i18n['AntiSpam Support'], data.ANTISPAM)}
-            <div>{i18n['Catchall email']}: <span className="stat">{data.CATCHALL}</span></div>
+            <div>{i18n['Catchall email']}: <span className="stat catchall-mail">{data.CATCHALL}</span></div>
           </Container>
         </div>
       </Container>

+ 5 - 1
web/js/react/src/components/Mail/Mail.scss

@@ -1,3 +1,7 @@
 .crossed {
   text-decoration: line-through;
-}
+}
+
+.catchall-mail {
+  text-transform: none;
+}

+ 13 - 7
web/js/react/src/components/MailAccount/Add/AddMailAccount.jsx

@@ -17,6 +17,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddMailAccount.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 export default function AddMailAccount(props) {
   const { i18n } = useSelector(state => state.session);
@@ -26,6 +28,7 @@ export default function AddMailAccount(props) {
   const [state, setState] = useState({
     data: {},
     advancedOptions: false,
+    autoreplyChecked: false,
     quotaValue: '',
     loading: false,
     password: '',
@@ -54,15 +57,18 @@ export default function AddMailAccount(props) {
     newMailDomain['Password'] = newMailDomain['v_password'];
 
     if (Object.keys(newMailDomain).length !== 0 && newMailDomain.constructor === Object) {
+      setState({ ...state, loading: true });
       addMailAccount(newMailDomain, props.domain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '' });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg });
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -114,7 +120,7 @@ export default function AddMailAccount(props) {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 15 - 0
web/js/react/src/components/MailAccount/Add/AddMailAccount.scss

@@ -21,10 +21,25 @@
             }
           }
         }
+
+        .form-group:nth-child(3) {
+          > div {
+            display: flex;
+
+            input {
+              width: 100%;
+            }
+
+            input + button {
+              padding-left: 10px;
+            }
+          }
+        }
       }
 
       .c-2 {
         width: 45%;
+        height: 100%;
         border: 1px solid #d9d9d9;
         padding: 0px 5px 12px 20px;
       }

+ 66 - 73
web/js/react/src/components/MailAccount/Edit/EditMailAccount.jsx

@@ -15,6 +15,9 @@ import { useHistory } from 'react-router-dom';
 import Spinner from '../../Spinner/Spinner';
 import { useDispatch, useSelector } from 'react-redux';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 export default function EditMailAccount(props) {
   const [autoreplyChecked, setAutoreplyChecked] = useState(false);
@@ -56,13 +59,14 @@ export default function EditMailAccount(props) {
       editMailAccount(newMailDomain, props.domain, props.account)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              goBack();
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -88,12 +92,9 @@ export default function EditMailAccount(props) {
       .catch(err => console.error(err));
   }
 
-  const toggleQuotaValue = () => {
-    if (state.quotaValue !== 'unlimited') {
-      setState({ ...state, quotaValue: 'unlimited' });
-    } else {
-      setState({ ...state, quotaValue: '' });
-    }
+  const toggleQuota = () => {
+    const value = state.data.quota === 'unlimited' ? '1000' : 'unlimited';
+    setState({ ...state, data: { ...state.data, quota: value } });
   }
 
   const goBack = () => {
@@ -116,7 +117,7 @@ export default function EditMailAccount(props) {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>
@@ -135,13 +136,58 @@ export default function EditMailAccount(props) {
                   id="domain"
                   disabled />
 
-                <TextInput
-                  title={i18n['Account']}
-                  value={props.account}
-                  name="v_account"
-                  id="account" />
-
                 <Password name="v_password" onChange={password => setState({ ...state, password })} />
+
+                <TextInputWithExtraButton title={i18n['Quota']} optionalTitle={i18n['in megabytes']} id="quota" name="v_quota" value={state.data.quota}>
+                  <button type="button" onClick={toggleQuota}>
+                    <FontAwesomeIcon icon="infinity" />
+                  </button>
+                </TextInputWithExtraButton>
+
+                <TextArea
+                  optionalTitle={`${i18n['use local-part']}`}
+                  defaultValue={state.data.aliases}
+                  title={i18n['Aliases']}
+                  name="v_aliases"
+                  id="aliases" />
+
+                <TextArea
+                  optionalTitle={`${i18n['one or more email addresses']}`}
+                  defaultValue={state.data.fwd}
+                  title={i18n['Forward to']}
+                  name="v_fwd"
+                  id="fwd" />
+
+                <Checkbox
+                  title={i18n['Do not store forwarded mail']}
+                  defaultChecked={state.data.fwd_only === 'yes'}
+                  name="v_fwd_only"
+                  id="fwd_only" />
+
+                <Checkbox
+                  title={i18n['Autoreply']}
+                  checked={autoreplyChecked}
+                  onChange={checked => setAutoreplyChecked(checked)}
+                  name="v_autoreply"
+                  id="autoreply" />
+
+                {
+                  autoreplyChecked && (
+                    <div style={{ transform: 'translateX(3rem)' }}>
+                      <TextArea
+                        defaultValue={state.data.autoreply_message}
+                        title={i18n['Message']}
+                        name="v_autoreply_message"
+                        id="autoreply_message" />
+                    </div>
+                  )
+                }
+
+                <TextInput
+                  title={i18n['Send login credentials to email address']}
+                  value={state.data.send_email}
+                  name="v_credentials"
+                  id="credentials" />
               </div>
 
               <div className="c-2">
@@ -153,59 +199,6 @@ export default function EditMailAccount(props) {
               </div>
             </div>
 
-            <div className="r-2">
-              <TextInputWithExtraButton title={i18n['Quota']} optionalTitle={i18n['in megabytes']} id="quota" name="v_quota" value={state.data.quota}>
-                <button type="button" onClick={toggleQuotaValue}>
-                  <FontAwesomeIcon icon="infinity" />
-                </button>
-              </TextInputWithExtraButton>
-
-              <TextArea
-                optionalTitle={`${i18n['use local-part']}`}
-                defaultValue={state.data.aliases}
-                title={i18n['Aliases']}
-                name="v_aliases"
-                id="aliases" />
-
-              <TextArea
-                optionalTitle={`${i18n['one or more email addresses']}`}
-                defaultValue={state.data.fwd}
-                title={i18n['Forward to']}
-                name="v_fwd"
-                id="fwd" />
-
-              <Checkbox
-                title={i18n['Do not store forwarded mail']}
-                defaultChecked={state.data.fwd_only === 'yes'}
-                name="v_fwd_only"
-                id="fwd_only" />
-
-              <Checkbox
-                title={i18n['Autoreply']}
-                checked={autoreplyChecked}
-                onChange={checked => setAutoreplyChecked(checked)}
-                name="v_fwd_only"
-                id="fwd_only" />
-
-              {
-                autoreplyChecked && (
-                  <div style={{ transform: 'translateX(3rem)' }}>
-                    <TextArea
-                      defaultValue={state.data.autoreply_message}
-                      title={i18n['Message']}
-                      name="v_autoreply_message"
-                      id="autoreply_message" />
-                  </div>
-                )
-              }
-
-              <TextInput
-                title={i18n['Send login credentials to email address']}
-                value={state.data.send_email}
-                name="v_credentials"
-                id="credentials" />
-            </div>
-
             <div className="buttons-wrapper">
               <button type="submit" className="add">{i18n.Add}</button>
               <button type="button" className="back" onClick={goBack}>{i18n.Back}</button>

+ 3 - 1
web/js/react/src/components/MailAccount/MailInfoBlock/MailInfoBlock.scss

@@ -1,3 +1,5 @@
+$primary: #2c54ac;
+
 .mail-info-block {
   .select-group {
     select {
@@ -5,7 +7,7 @@
       margin-top: 5px;
       padding: 0;
       border: none;
-      color: #2c9491;
+      color: $primary;
       text-transform: uppercase;
       font-size: 11px;
       font-weight: bold;

+ 2 - 1
web/js/react/src/components/MainNav/MainNav.jsx

@@ -18,7 +18,8 @@ const MainNav = () => {
     showTopNav: false
   });
 
-  const { userName, user, session: { look } } = useSelector(state => state.session);
+  const { userName, session: { look } } = useSelector(state => state.session);
+  const { user } = useSelector(state => state.menuCounters);
   const { activeElement, focusedElement, adminMenuTabs, userMenuTabs } = useSelector(state => state.mainNavigation);
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const dispatch = useDispatch();

+ 9 - 6
web/js/react/src/components/MainNav/Panel/Notifications/Notifications.jsx

@@ -5,6 +5,7 @@ import Bell from './Bell';
 import BellUnread from './BellUnread';
 import { useDispatch, useSelector } from 'react-redux';
 import './Notifications.scss';
+import HtmlParser from 'react-html-parser';
 
 const Notifications = () => {
   const { i18n } = useSelector(state => state.session);
@@ -13,8 +14,10 @@ const Notifications = () => {
   const [loading, setLoading] = useState(false);
 
   useEffect(() => {
-    fetchData();
-  }, []);
+    if (!notifications) {
+      fetchData();
+    }
+  }, [notifications]);
 
   const fetchData = () => {
     setLoading(true);
@@ -32,7 +35,7 @@ const Notifications = () => {
       .catch(err => {
         console.error(err);
         setLoading(false);
-      })
+      });
   }
 
   const removeNotification = id => {
@@ -44,7 +47,7 @@ const Notifications = () => {
   }
 
   const renderOptions = () => {
-    if (notifications.length) {
+    if (notifications && notifications.length) {
       return notifications.map(item => {
         return (
           <>
@@ -52,7 +55,7 @@ const Notifications = () => {
               <span className="title"><b>{item.TOPIC}</b></span>
               <span className="delete-notification" onClick={() => removeNotification(item.ID)}></span>
             </div>
-            <div dangerouslySetInnerHTML={{ __html: item.NOTICE }}></div>
+            <div>{HtmlParser(item.NOTICE)}</div>
             <div className="dropdown-divider"></div>
           </>
         );
@@ -71,7 +74,7 @@ const Notifications = () => {
       <button type="button" className="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
         <div className="bell">
           {
-            notifications.length
+            notifications && notifications.length
               ? <BellUnread />
               : <Bell />
           }

+ 2 - 3
web/js/react/src/components/MainNav/Panel/Panel.jsx

@@ -56,7 +56,6 @@ const Panel = props => {
 
     dispatch(logout())
       .then(() => {
-        history.push('/login/');
         setLoading(false);
       },
         error => {
@@ -69,7 +68,7 @@ const Panel = props => {
     <div className="panel-wrapper">
       {loading && <Spinner />}
 
-      <div className={`top-panel ${user ? 'long-profile' : ''}`}>
+      <div className={`top-panel ${look ? 'long-profile' : ''}`}>
         <div className="container left-menu">
           <div className="logo">
             <Link to="/list/user/" onClick={() => dispatch(addActiveElement('/list/user/'))}>
@@ -103,7 +102,7 @@ const Panel = props => {
               <Link to="/list/firewall/" onClick={event => handleState("/list/firewall/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Firewall}</Link>
             </div>}
           </>)}
-          {session.session.FILEMANAGER_KEY && <div className={className("/list/directory/", "fm")}>
+          {session.session.FILEMANAGER_KEY && <div className={className("/list/directory/")}>
             <Link to="/list/directory/">{i18n['File Manager']}</Link>
           </div>}
           {session.session.SOFTACULOUS === "yes" && <div className={className("/softaculous/")}><a href="/softaculous/">{i18n.Apps ?? 'Apps'}</a>

+ 34 - 9
web/js/react/src/components/MainNav/Panel/Panel.scss

@@ -52,7 +52,7 @@ $textColor: #555;
         display: flex;
         justify-content: center;
         align-items: center;
-        padding: 0 5px;
+        padding: 0 10px !important;
         width: 100%;
         height: 100%;
         text-decoration: none;
@@ -93,14 +93,15 @@ $textColor: #555;
   }
 
   .left-menu {
-    width: 80%;
+    width: 75%;
     padding-left: 0;
+    margin-left: 0;
+    justify-content: space-between;
 
     div {
-      width: 7rem;
+      flex: 1 1 auto;
       height: 100%;
       transform: translateX(-5px);
-      padding-right: 5px;
 
       &:hover {
         background: $secondaryLight;
@@ -108,8 +109,19 @@ $textColor: #555;
     }
 
     div.logo {
-      img {
-        width: 90%;
+      width: 7rem;
+      padding-left: 0;
+
+      a {
+        div {
+          padding: 0;
+          width: 6rem;
+          flex: none;
+
+          img {
+            width: 90%;
+          }
+        }
       }
 
       &:hover {
@@ -129,7 +141,7 @@ $textColor: #555;
   }
 
   .profile-menu {
-    width: 20%;
+    width: auto;
 
     div {
       width: 4rem;
@@ -189,11 +201,24 @@ $textColor: #555;
 
 .top-panel.long-profile {
   .left-menu {
-    width: 75%;
+    justify-content: start;
+
+    > div {
+      width: max-content;
+      flex: unset;
+      padding: 0 1rem;
+    }
+
+    .logo {
+      width: 7rem;
+      padding: 0;
+      margin-right: 1rem;
+    }
   }
 
   .profile-menu {
-    width: 25%;
+    justify-content: space-between;
+    align-items: center;
 
     > div + div {
       width: max-content;

+ 2 - 1
web/js/react/src/components/MainNav/Stat-menu/Menu.jsx

@@ -27,7 +27,8 @@ const style = ({ menuHeight, mobile }) => {
 
 const Menu = props => {
   const { activeElement, focusedElement } = useSelector(state => state.mainNavigation);
-  const { user, i18n, session: { look } } = useSelector(state => state.session);
+  const { i18n, session: { look } } = useSelector(state => state.session);
+  const { user } = useSelector(state => state.menuCounters);
   const dispatch = useDispatch();
 
   useEffect(() => {

+ 0 - 6
web/js/react/src/components/MainNav/Toolbar/LeftButton/LeftButton.scss

@@ -81,12 +81,6 @@ $textColor: #555;
 .servers-list,
 .exclusions-list {
   .l-menu {
-    &.server-icon {
-      a, span.add {
-        background: $primary;
-      }
-    }
-
     &.backup-details-icon {
       transform: translateX(2px);
     }

+ 15 - 5
web/js/react/src/components/MainNav/Toolbar/SearchInput/SearchInput.jsx

@@ -20,6 +20,14 @@ const SearchInput = props => {
     return () => window.removeEventListener("keyup", focusInput);
   }, []);
 
+  const onSubmit = e => {
+    e.preventDefault();
+
+    if (searchTerm) {
+      handleClick();
+    }
+  }
+
   const focusInput = event => {
     if (event.keyCode === 70) {
       return inputElement.current.focus();
@@ -27,11 +35,13 @@ const SearchInput = props => {
   }
 
   return (
-    <div className="search-input-form">
-      <input type="text" className="form-control" onChange={e => setSearchTerm(e.target.value)} ref={inputElement} />
-      <button className="btn btn-outline-secondary" type="submit" onClick={() => handleClick()}><FontAwesomeIcon icon="search" /></button>
-    </div>
+    <form onSubmit={onSubmit}>
+      <div className="search-input-form">
+        <input type="text" className="form-control" onChange={e => setSearchTerm(e.target.value)} ref={inputElement} />
+        <button className="btn btn-outline-secondary" type="submit" onClick={() => handleClick()}><FontAwesomeIcon icon="search" /></button>
+      </div>
+    </form>
   );
 }
 
-export default SearchInput;
+export default SearchInput;

+ 9 - 1
web/js/react/src/components/MainNav/Toolbar/Select/Select.jsx

@@ -4,7 +4,7 @@ import { values } from '../../../../ControlPanelService/Select';
 import { useSelector } from 'react-redux';
 import './Select.scss';
 
-const Select = props => {
+const Select = ({ cronReports, ...props }) => {
   const { i18n } = useSelector(state => state.session);
   const listValues = values(i18n);
 
@@ -44,6 +44,14 @@ const Select = props => {
     const { list } = props;
     let activeList = state[list];
 
+    if (list === 'cronList') {
+      if (cronReports) {
+        activeList = activeList.filter((item, index) => index !== 0);
+      } else {
+        activeList = activeList.filter((item, index) => index !== 1);
+      }
+    }
+
     if (list === 'statisticsList') {
       return props.users.map((item, index) => { return <option key={index} value={item}>{item}</option> });
     } else {

+ 27 - 5
web/js/react/src/components/MainNav/Toolbar/Toolbar.scss

@@ -1,3 +1,16 @@
+$primary: #2c54ac;
+$whiteBackground: #ececec;
+$primary: #2c54ac;
+$primaryLight: #d7dcef;
+$primaryActive: #1e5cb2;
+$secondary: #fcac04;
+$secondaryLight: #f8b014;
+$danger: #b00e5b;
+$secondaryActive: #fdb51c;
+$hoverButtonText: #2c54ac;
+$activeButtonText: #fff;
+$textColor: #555;
+
 .toolbar {
   display: flex;
   flex-direction: row;
@@ -8,16 +21,17 @@
   left: 0;
   width: 100%;
   z-index: 1;
-  padding: 0 13%;
+  padding: 3px 10% 1px;
+  border-top: 1px solid #e0e0e0;
   border-bottom: 1px solid #e0e0e0;
-  padding-top: 5px;
 
   .r-menu {
     display: flex;
     justify-content: center;
     align-items: center;
 
-    a.button-extra, button.button-extra {
+    a.button-extra,
+    button.button-extra {
       background: none;
       border: none;
       padding: 0 0.75rem;
@@ -27,9 +41,17 @@
       display: flex;
       justify-content: center;
       align-items: center;
+      margin-right: 10px;
+      border-radius: 3px;
 
       &:hover {
-        color: rgb(78, 76, 76);
+        background: $primaryLight;
+        color: $hoverButtonText;
+      }
+
+      &:active {
+        background: $primaryActive;
+        color: $activeButtonText;
       }
     }
     
@@ -131,7 +153,7 @@
 
 @media (max-width: 1350px) {
   .toolbar {
-    padding: 5px 10% 0;
+    padding: 3px 10% 1px;
   }
 }
 

+ 2 - 0
web/js/react/src/components/Modal/Modal.scss

@@ -36,6 +36,8 @@ $black: #000;
     margin-top: 100px;
 
     .modal-body {
+      overflow-wrap: anywhere;
+      font-size: 17px;
       margin-bottom: 40px;
 
       input {

+ 9 - 8
web/js/react/src/components/Package/Add/AddPackage.jsx

@@ -15,6 +15,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddPackage.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddPackage = props => {
   const token = localStorage.getItem("token");
@@ -89,18 +91,17 @@ const AddPackage = props => {
 
     if (Object.keys(newPackage).length !== 0 && newPackage.constructor === Object) {
       setState({ ...state, loading: true });
-
       addPackage(newPackage)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false })
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -141,7 +142,7 @@ const AddPackage = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 13 - 4
web/js/react/src/components/Package/Edit/EditPackage.jsx

@@ -16,6 +16,9 @@ import QS from 'qs';
 
 import './EditPackage.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditPackage = props => {
   const token = localStorage.getItem("token");
@@ -70,9 +73,15 @@ const EditPackage = props => {
       updatePackage(updatedPackage, state.data.package)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-            setState({ ...state, errorMessage: error_msg || '', okMessage: ok_msg || '', loading: false });
-            history.push('/list/package/');
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
+            }
           }
         })
         .catch(err => console.error(err));
@@ -113,7 +122,7 @@ const EditPackage = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 1 - 2
web/js/react/src/components/Package/Package.jsx

@@ -9,7 +9,6 @@ import { useSelector } from 'react-redux';
 const Package = props => {
   const { data } = props;
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
 
   const printNameServers = servers => {
     let serversArray = servers.split(',');
@@ -32,7 +31,7 @@ const Package = props => {
   }
 
   const handleDelete = () => {
-    props.handleModal(data.delete_conf, `/api/v1/delete/package?package=${data.NAME}`);
+    props.handleModal(data.delete_conf, `/api/v1/delete/package/index.php?package=${data.NAME}`);
   }
 
   return (

+ 3 - 3
web/js/react/src/components/Path/Path.jsx

@@ -5,13 +5,13 @@ import Dropdown from './Dropdown/Dropdown';
 import './Path.scss';
 
 const Path = ({ path, isActive, className, openDirectory, changeSorting, sorting, order }) => {
-  const session = useSelector(state => state.session);
+  const { user } = useSelector(state => state.menuCounters);
 
   const clickablePath = () => {
     let splitPath = path.split('/');
     splitPath.splice(0, 3);
 
-    if (path !== session.user.HOME) {
+    if (path !== user.HOME) {
       return (
         splitPath.map((item, index) => <span className="clickable" key={index} onClick={() => openDirectoryHandler(index)}>&nbsp;/&nbsp;{item}</span>)
       );
@@ -36,7 +36,7 @@ const Path = ({ path, isActive, className, openDirectory, changeSorting, sorting
     <div className={className}>
       <div className="clickable-wrapper">
         <span className="clickable-path">
-          <span className="clickable" onClick={() => openDirectory(session.user.HOME)}>{session.user.HOME}</span>
+          <span className="clickable" onClick={() => openDirectory(user.HOME)}>{user.HOME}</span>
           {clickablePath()}
         </span>
       </div>

+ 51 - 7
web/js/react/src/components/Searchitem/SearchItem.jsx

@@ -1,13 +1,54 @@
 import React from 'react';
+import { loginAs, logout } from 'src/actions/Session/sessionActions';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import ListItem from '../ControlPanel/ListItem/ListItem';
 import Container from '../ControlPanel/Container/Container';
-import { useSelector } from 'react-redux';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import { useDispatch, useSelector } from 'react-redux';
+import { Link, useHistory } from 'react-router-dom';
 import './SearchItem.scss';
-import { Link } from 'react-router-dom';
 
 const SearchItem = ({ data, handleModal }) => {
-  const { i18n } = useSelector(state => state.session);
+  const { i18n, userName } = useSelector(state => state.session);
+  const dispatch = useDispatch();
+  const history = useHistory()
+
+  const signInAs = user => {
+    dispatch(loginAs(user)).then(() => history.push('/'));
+  }
+
+  const signOut = () => {
+    dispatch(logout()).then(() => history.push('/'));
+  }
+
+  const handleDelete = () => {
+    handleModal(data.delete_confirmation, `/api/v1/${data.TYPE === 'user' ? `/api/v1/delete/user/index.php?user=${data.USER}` : data.delete_link}`);
+  }
+
+  const handleSuspend = () => {
+    handleModal(data.spnd_confirmation, `/api/v1/${data.TYPE === 'user' ? `${data.spnd_action}/user/index.php?user=${data.USER}` : data.spnd_link}`);
+  }
+
+  const printLoginActionButton = () => {
+    if (data.TYPE !== 'user') return;
+
+    if (userName === data.USER) {
+      return (
+        <div>
+          <button onClick={signOut}>{i18n['Log out']}
+            {data.FOCUSED ? <span className="shortcut-button">L</span> : <FontAwesomeIcon icon="user-lock" />}
+          </button>
+        </div>
+      );
+    } else {
+      return (
+        <div>
+          <button onClick={() => signInAs(data.USER)}>{i18n['login as']} {data.USER}
+            {data.FOCUSED ? <span className="shortcut-button">L</span> : <FontAwesomeIcon icon="user-lock" />}
+          </button>
+        </div>
+      );
+    }
+  }
 
   return (
     <ListItem date={data.DATE} suspended={data.SUSPENDED === 'yes'}>
@@ -15,7 +56,7 @@ const SearchItem = ({ data, handleModal }) => {
         <div className="name">{data.RESULT}</div>
         <div className="stats">
           <Container className="c-1">
-            <div className="object">{i18n[data.object]}</div>
+            <div className="object">{data.TYPE === 'user' ? i18n['USER'] : i18n[data.object]}</div>
           </Container>
           <Container className="c-2">
             <div className="owner">{i18n.Owner}: <span>{data.USER}</span></div>
@@ -26,17 +67,20 @@ const SearchItem = ({ data, handleModal }) => {
         </div>
       </Container>
       <div className="actions">
+        {printLoginActionButton()}
         <div><Link className="link-edit" to={data.edit_link}>{i18n.edit} <FontAwesomeIcon icon="pen" /></Link></div>
         <div>
           <button
             className="link-gray"
-            onClick={() => handleModal(data.spnd_confirmation, '/api/v1' + data.spnd_link)}>
+            onClick={handleSuspend}>
             {data.spnd_action}
             <FontAwesomeIcon icon={data.SUSPENDED === 'yes' ? 'unlock' : 'lock'} />
           </button>
         </div>
         <div>
-          <button className="link-delete" onClick={() => this.props.handleModal(data.delete_confirmation, data, data.delete_link)}>
+          <button
+            className="link-delete"
+            onClick={handleDelete}>
             {i18n.Delete}
             <FontAwesomeIcon icon="times" />
           </button>

+ 2 - 1
web/js/react/src/components/Server/Edit/Bind9/Bind9.jsx

@@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './Bind9.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const Bind9 = () => {
   const token = localStorage.getItem("token");
@@ -94,7 +95,7 @@ const Bind9 = () => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Dovecot/Dovecot.jsx

@@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './Dovecot.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const Dovecot = () => {
   const token = localStorage.getItem("token");
@@ -94,7 +95,7 @@ const Dovecot = () => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/EditServer.jsx

@@ -19,6 +19,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './EditServer.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const EditServer = props => {
   const token = localStorage.getItem("token");
@@ -106,7 +107,7 @@ const EditServer = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Httpd/EditHttpd.jsx

@@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './EditHttpd.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const EditHttpd = props => {
   const token = localStorage.getItem("token");
@@ -89,7 +90,7 @@ const EditHttpd = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Mysql/Mysql.jsx

@@ -13,6 +13,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './Mysql.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const Mysql = ({ serviceName = '' }) => {
   const token = localStorage.getItem("token");
@@ -107,7 +108,7 @@ const Mysql = ({ serviceName = '' }) => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Nginx/EditServerNginx.jsx

@@ -13,6 +13,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './EditServerNginx.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const EditServerNginx = props => {
   const token = localStorage.getItem("token");
@@ -100,7 +101,7 @@ const EditServerNginx = props => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/PHP/EditPhp.jsx

@@ -13,6 +13,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './EditPhp.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const EditPhp = ({ serviceName = '' }) => {
   const token = localStorage.getItem("token");
@@ -104,7 +105,7 @@ const EditPhp = ({ serviceName = '' }) => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Postgresql/Postgresql.jsx

@@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './Postgresql.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const Postgresql = () => {
   const token = localStorage.getItem("token");
@@ -94,7 +95,7 @@ const Postgresql = () => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 2 - 1
web/js/react/src/components/Server/Edit/Service/Service.jsx

@@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './Service.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const Service = ({ serviceName = '' }) => {
   const token = localStorage.getItem("token");
@@ -100,7 +101,7 @@ const Service = ({ serviceName = '' }) => {
         </div>
         <div className="success">
           <span className="ok-message">
-            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 1 - 2
web/js/react/src/components/TopPanel/TopPanel.jsx

@@ -10,10 +10,9 @@ import './TopPanel.scss';
 const TopPanel = ({ menuItems = [], extraMenuItems = [] }) => {
   const mainNavigation = useSelector(state => state.mainNavigation);
   const [loading, setLoading] = useState(false);
-  const { i18n } = useSelector(state => state.session);
+  const { i18n, userName } = useSelector(state => state.session);
   const dispatch = useDispatch();
   const history = useHistory();
-  const { userName } = useSelector(state => state.session);
 
   const className = cls => {
     let className = 'nav-link';

+ 17 - 10
web/js/react/src/components/User/Add/AddUser.jsx

@@ -13,6 +13,9 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddUser.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddUser = props => {
   const { i18n } = useSelector(state => state.session);
@@ -71,19 +74,23 @@ const AddUser = props => {
     }
 
     if (Object.keys(newUser).length !== 0 && newUser.constructor === Object) {
+      setState({ ...state, loading: true });
       addUser(newUser)
         .then(result => {
-          if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '' });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg });
-            }
+          const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+          if (errorMessage) {
+            setState({ ...state, errorMessage, okMessage, loading: false });
+          } else {
+            dispatch(refreshCounters()).then(() => {
+              setState({ ...state, okMessage, errorMessage: '', loading: false });
+            });
           }
         })
-        .catch(err => console.error(err));
+        .catch(err => {
+          setState({ ...state, loading: false });
+          console.error(err);
+        });
     }
   }
 
@@ -133,7 +140,7 @@ const AddUser = props => {
         <div className="search-toolbar-name">{i18n['Adding User']}</div>
         <div className="error"><span className="error-message">{state.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}</span></div>
         <div className="success">
-          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span> </span>
+          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span> </span>
         </div>
       </Toolbar>
       <AddItemLayout>

+ 10 - 7
web/js/react/src/components/User/Edit/EditUser.jsx

@@ -15,6 +15,9 @@ import QS from 'qs';
 
 import './EditUser.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditUser = props => {
   const token = localStorage.getItem("token");
@@ -68,14 +71,14 @@ const EditUser = props => {
       updateUser(updatedUser, state.username)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -103,7 +106,7 @@ const EditUser = props => {
         <div className="search-toolbar-name">{i18n['Editing User']}</div>
         <div className="error"><span className="error-message">{state.data.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}</span></div>
         <div className="success">
-          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span> </span>
+          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span> </span>
         </div>
       </Toolbar>
       <AddItemLayout date={state.data.date} time={state.data.time} status={state.data.status}>

+ 1 - 2
web/js/react/src/components/User/User.jsx

@@ -9,9 +9,8 @@ import { Link } from 'react-router-dom';
 import './User.scss';
 
 const User = ({ data, toggleFav, handleModal, checkItem }) => {
-  const { i18n } = useSelector(state => state.session);
+  const { i18n, userName } = useSelector(state => state.session);
   const session = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const dispatch = useDispatch();
 
   const printNameServers = servers => {

+ 1 - 0
web/js/react/src/components/User/User.scss

@@ -39,6 +39,7 @@ $textColor: #555;
 
 span.stat.email{
   display: block;
+  width: max-content;
   text-overflow: ellipsis;
   white-space: nowrap;
   overflow: hidden;

+ 11 - 8
web/js/react/src/components/WebDomain/Add/AddWebDomain.jsx

@@ -13,6 +13,8 @@ import { useDispatch, useSelector } from 'react-redux';
 
 import './AddWebDomain.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const AddWebDomain = props => {
   const { i18n } = useSelector(state => state.session);
@@ -37,7 +39,6 @@ const AddWebDomain = props => {
     dispatch(removeFocusedElement());
 
     setState({ ...state, loading: true });
-
     Promise.all([getWebStats(), getIpList()])
       .then(result => {
         const [webStats, internetProtocols] = result;
@@ -94,12 +95,14 @@ const AddWebDomain = props => {
       addWeb(newWebDomain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
-
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
+            } else {
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -123,7 +126,7 @@ const AddWebDomain = props => {
         <div className="success">
           <span className="ok-message">
             {state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
-            <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
+            <span>{HtmlParser(state.okMessage)}</span>
           </span>
         </div>
       </Toolbar>

+ 61 - 60
web/js/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.jsx

@@ -5,10 +5,10 @@ import Password from '../../../../components/ControlPanel/AddItemLayout/Form/Pas
 
 import './AdditionalFtpForEditing.scss';
 
-const AdditionalFtpForEditing = ({ domain, data = {}, onDeleteAdditionalFtp, prefixI18N, prePath, checked }) => {
-  const { userName, i18n } = useSelector(state => state.session);
+const AdditionalFtpForEditing = ({ domain, data = {}, onDeleteAdditionalFtp, prefixI18N, prePath, checked, ...props }) => {
+  const { i18n, userName } = useSelector(state => state.session);
   const [state, setState] = useState({
-    username: data.v_ftp_user,
+    username: data.v_ftp_user || '',
     path: ''
   });
 
@@ -29,72 +29,73 @@ const AdditionalFtpForEditing = ({ domain, data = {}, onDeleteAdditionalFtp, pre
         return <></>;
       }
 
-      return (<div className="additional-ftp">
-        <div className="title">
-          <input type="hidden" name={`v_ftp_user[${data.id}][delete]`} value="0" />
-          <input type="hidden" name={`v_ftp_user[${data.id}][is_new]`} value="1" />
+      return (
+        <div className="additional-ftp">
+          <div className="title">
+            <input type="hidden" name={`v_ftp_user[${data.id}][delete]`} value="0" />
+            <input type="hidden" name={`v_ftp_user[${data.id}][is_new]`} value="1" />
 
-          <span className="data.indexed-name">{i18n.FTP} #{data.id + 1}</span>
-          <span>
-            &nbsp;
-            <button
-              type="button"
-              onClick={() => onDeleteAdditionalFtp(data.id)}>
-              ({i18n.Delete ?? 'Delete'})
-            </button>
-          </span>
-        </div>
+            <span className="data.indexed-name">{i18n.FTP} #{data.id + 1}</span>
+            <span>
+              &nbsp;
+              <button
+                type="button"
+                onClick={() => onDeleteAdditionalFtp(data.id)}>
+                ({i18n.Delete ?? 'Delete'})
+              </button>
+            </span>
+          </div>
+
+          <div className="form-transform">
+            <div className="form-group username">
+              <label htmlFor={`ftp_user_${data.id}`}>{i18n.Username}</label>
+              <span className="prefix-note">{prefixI18N}</span>
+              <div className="input-wrapper">
+                <input
+                  defaultValue={state.username}
+                  onChange={event => setState({ ...state, username: event.target.value })}
+                  type="text"
+                  disabled={data.v_ftp_user}
+                  className="form-control"
+                  id={`ftp_user_${data.id}`}
+                  name={`v_ftp_user[${data.id}][v_ftp_user]`} />
+                <span>{data.v_ftp_user ? data.v_ftp_user : `${userName}_${state.username}`}</span>
+              </div>
+            </div>
+
+            <Password name={`v_ftp_user[${data.id}][v_ftp_password]`} id={data.id} />
+
+            <div className="form-group">
+              <input type="hidden" name="v_ftp_pre_path" value={prePath} />
+              <input type="hidden" name={`v_ftp_user[${data.id}][v_ftp_path_prev]`} value={data.v_ftp_path !== '/' ? '/' : ''} />
 
-        <div className="form-transform">
-          <div className="form-group username">
-            <label htmlFor={`ftp_user_${data.id}`}>{i18n.Username}</label>
-            <span className="prefix-note">{prefixI18N}</span>
-            <div className="input-wrapper">
+              <label htmlFor={`path${data.id}`}>{i18n.Path}</label>
               <input
-                defaultValue={state.username}
-                onChange={event => setState({ ...state, username: event.target.value })}
                 type="text"
-                disabled={data.v_ftp_user}
+                value={state.path}
+                onChange={event => setState({ ...state, path: event.target.value })}
                 className="form-control"
-                id={`ftp_user_${data.id}`}
-                name={`v_ftp_user[${data.id}][v_ftp_user]`} />
-              <span>{`${userName}_${state.username}`}</span>
+                id={`path${data.id}`}
+                name={`v_ftp_user[${data.id}][v_ftp_path]`} />
+              <span className="path-note">{prePath}</span>
             </div>
-          </div>
-
-          <Password name={`v_ftp_user[${data.id}][v_ftp_password]`} id={data.id} />
 
-          <div className="form-group">
-            <input type="hidden" name="v_ftp_pre_path" value={prePath} />
-            <input type="hidden" name={`v_ftp_user[${data.id}][v_ftp_path_prev]`} value={data.v_ftp_path !== '/' ? '/' : ''} />
+            {
+              data.is_new === 1 && (
+                <div className="form-group">
+                  <label htmlFor={`sendLoginCredentialsToEmailAddress_${data.id}`}>{i18n['Send login credentials to email address']}</label>
+                  <input
+                    type="email"
+                    className="form-control"
+                    id={`sendLoginCredentialsToEmailAddress_${data.id}`}
+                    defaultValue={data.v_ftp_email}
+                    name={`v_ftp_user[${data.id}][v_ftp_email]`} />
+                </div>
+              )
+            }
 
-            <label htmlFor={`path${data.id}`}>{i18n.Path}</label>
-            <input
-              type="text"
-              value={state.path}
-              onChange={event => setState({ ...state, path: event.target.value })}
-              className="form-control"
-              id={`path${data.id}`}
-              name={`v_ftp_user[${data.id}][v_ftp_path]`} />
-            <span className="path-note">{`${data.v_ftp_pre_path ? data.v_ftp_pre_path : ''}/${state.path}`}</span>
           </div>
-
-          {
-            data.is_new === 1 && (
-              <div className="form-group">
-                <label htmlFor={`sendLoginCredentialsToEmailAddress_${data.id}`}>{i18n['Send login credentials to email address']}</label>
-                <input
-                  type="email"
-                  className="form-control"
-                  id={`sendLoginCredentialsToEmailAddress_${data.id}`}
-                  defaultValue={data.v_ftp_email}
-                  name={`v_ftp_user[${data.id}][v_ftp_email]`} />
-              </div>
-            )
-          }
-
-        </div>
-      </div >);
+        </div >);
     }
 
     return <></>;

+ 13 - 12
web/js/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.jsx

@@ -1,34 +1,35 @@
 import React, { useEffect, useState } from 'react';
 import { useSelector } from 'react-redux';
-import AdditionalFtp from '../AdditionalFtp/AdditionalFtp';
 import AdditionalFtpForEditing from '../AdditionalFtpForEditing/AdditionalFtpForEditing';
+import './AdditionalFtpWrapper.scss';
 
-const AdditionalFtpWrapper = props => {
+const AdditionalFtpWrapper = ({ checked, ftps, unCheckAdditionalFtpBox, prefixI18N, ftpUserPrePath, domain, ...props }) => {
   const { i18n } = useSelector(state => state.session);
   const [state, setState] = useState({
     additionalFtp: []
   });
 
   useEffect(() => {
-    if (props.ftps) {
-      const data = props.ftps.map((item, index) => {
-        item['deleted'] = false;
+    if (ftps) {
+      const data = ftps.map((item, index) => {
+        item['deleted'] = !checked;
         item['id'] = index;
         return item;
       });
+
       setState({ ...state, additionalFtp: data });
     }
-  }, [props.ftps]);
+  }, [checked, ftps]);
 
   const renderAdditionalFtps = () => {
     return state.additionalFtp.map(ftp => {
       return <AdditionalFtpForEditing
         key={ftp.id}
-        prefixI18N={props.prefixI18N}
+        prefixI18N={prefixI18N}
         data={ftp}
-        checked={props.checked}
-        prePath={props.ftpUserPrePath}
-        domain={props.domain}
+        checked={checked}
+        prePath={ftpUserPrePath}
+        domain={domain}
         onDeleteAdditionalFtp={id => onDeleteFtp(id)} />;
     });
   }
@@ -45,7 +46,7 @@ const AdditionalFtpWrapper = props => {
     });
 
     if (!updatedAdditionalFtps.length) {
-      props.unCheckAdditionalFtpBox();
+      unCheckAdditionalFtpBox();
     }
 
     setState({ ...state, additionalFtp: updatedAdditionalFtps });
@@ -64,7 +65,7 @@ const AdditionalFtpWrapper = props => {
     <div>
       {renderAdditionalFtps()}
 
-      {props.checked && (
+      {checked && (
         <button type="button" onClick={() => addAdditionalFtp()}>
           {i18n['Add one more FTP Account'] ?? 'Add'}
         </button>)}

+ 48 - 0
web/js/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.scss

@@ -0,0 +1,48 @@
+.additional-ftp {
+  .title {
+    span:nth-child(1) {
+      color: #555;
+      font-size: 15px;
+      font-weight: bold;
+    }
+  }
+
+  .form-transform {
+    margin-top: 15px;
+    transform: translateX(3rem);
+
+    .form-group.username {
+      display: flex;
+      flex-direction: column;
+  
+      label {
+        margin: 0;
+      }
+  
+      span {
+        font-size: 10pt;
+        color: #777;
+        font-weight: bold;
+        margin-bottom: 10px;
+      }
+
+      .input-wrapper {
+        display: flex;
+        align-items: center;
+
+        span {
+          color: #777;
+          font-size: 15px;
+          font-style: italic;
+          font-weight: normal;
+          margin-left: 15px;
+        }
+      }
+    }
+  }
+
+  .path-note {
+    font-weight: bold;
+    color: #555;
+  }
+}

+ 10 - 7
web/js/react/src/components/WebDomain/Edit/EditWeb.jsx

@@ -18,6 +18,9 @@ import QS from 'qs';
 import './EditWeb.scss';
 import TextArea from '../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
 
 const EditWeb = props => {
   const token = localStorage.getItem("token");
@@ -83,14 +86,14 @@ const EditWeb = props => {
       updateWebDomain(updatedDomain, state.domain)
         .then(result => {
           if (result.status === 200) {
-            const { error_msg, ok_msg } = result.data;
+            const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
 
-            if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
-            } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            if (errorMessage) {
+              setState({ ...state, errorMessage, okMessage, loading: false });
             } else {
-              setState({ ...state, loading: false });
+              dispatch(refreshCounters()).then(() => {
+                setState({ ...state, okMessage, errorMessage: '', loading: false });
+              });
             }
           }
         })
@@ -134,7 +137,7 @@ const EditWeb = props => {
         <div className="search-toolbar-name">{i18n['Editing Domain']}</div>
         <div className="error"><span className="error-message">{state.data.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}</span></div>
         <div className="success">
-          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span> </span>
+          <span className="ok-message">{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span>{HtmlParser(state.okMessage)}</span> </span>
         </div>
       </Toolbar>
       <AddItemLayout date={state.data.date} time={state.data.time} status={state.data.status}>

+ 2 - 2
web/js/react/src/components/WebDomain/WebDomain.jsx

@@ -93,10 +93,10 @@ export default function WebDomain(props) {
           </Link>
         </div>
         <div>
-          <a className="link-gray" href={`/list/web-log?domain=${data.NAME}&type=access`}>
+          <Link className="link-gray" to={`/list/web-log?domain=${data.NAME}&type=access`}>
             {i18n['view logs']}
             {data.FOCUSED ? <span className="shortcut-button">L</span> : <FontAwesomeIcon icon="list" />}
-          </a>
+          </Link>
         </div>
         {
           data.STATS && (

+ 1 - 1
web/js/react/src/components/WebDomain/WebDomain.scss

@@ -22,7 +22,7 @@
     align-items: center;
 
     > div:nth-child(1) {
-      width: 34%;
+      margin-right: 2rem;
     }
 
     .dns-name-span {

+ 0 - 4
web/js/react/src/containers/App/App.js

@@ -71,10 +71,6 @@ const App = () => {
   useEffect(() => {
     if (!Object.entries(session.i18n).length) {
       dispatch(checkAuthHandler()).then(token => {
-        if (token) {
-          setAuthToken(token);
-        }
-
         setLoading(false);
       });
     }

+ 4 - 0
web/js/react/src/containers/App/App.scss

@@ -10,6 +10,10 @@ $hoverButtonText: #2c54ac;
 $activeButtonText: #fff;
 $textColor: #555;
 
+html {
+  overflow-y: scroll;
+}
+
 .App {
   font-size: 25px;
   font-family: Arial;

+ 56 - 30
web/js/react/src/containers/Backups/Backups.jsx

@@ -16,13 +16,14 @@ import Backup from '../../components/Backup/Backup';
 import { Helmet } from 'react-helmet';
 import { Link } from 'react-router-dom';
 import './Backups.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Backups = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -31,7 +32,6 @@ const Backups = props => {
   const [state, setState] = useState({
     backups: [],
     backupFav: [],
-    loading: true,
     toggledAll: false,
     selection: [],
     totalAmount: ''
@@ -41,7 +41,7 @@ const Backups = props => {
     dispatch(addActiveElement('/list/backup/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -146,7 +146,7 @@ const Backups = props => {
   }
 
   const download = () => {
-    props.history.push(`/download/backup?backup=${controlPanelFocusedElement}`);
+    window.open(`/api/v1/download/backup?backup=${controlPanelFocusedElement}`);
   }
 
   const handleDelete = () => {
@@ -157,19 +157,22 @@ const Backups = props => {
   }
 
   const fetchData = () => {
-    getBackupList()
-      .then(result => {
-        setState({
-          ...state,
-          backups: reformatData(result.data.data),
-          backupFav: result.data.backup_fav,
-          totalAmount: result.data.totalAmount,
-          selection: [],
-          toggledAll: false,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getBackupList()
+        .then(result => {
+          setState({
+            ...state,
+            backups: reformatData(result.data.data),
+            backupFav: result.data.backup_fav,
+            totalAmount: result.data.totalAmount,
+            selection: [],
+            toggledAll: false
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -278,12 +281,14 @@ const Backups = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ ...state, loading: true });
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -291,7 +296,7 @@ const Backups = props => {
   }
 
   const displayModal = (text, url) => {
-    setState({ ...state, loading: false });
+    setLoading(false);
     setModal({
       ...modal,
       visible: true,
@@ -301,13 +306,25 @@ const Backups = props => {
   }
 
   const modalConfirmHandler = () => {
-    if (!modal.actionUrl) return;
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
 
     modalCancelHandler();
-
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -320,10 +337,15 @@ const Backups = props => {
   }
 
   const scheduleBackupButton = () => {
-    setState({ ...state, loading: true });
-
+    setLoading(true);
     scheduleBackup()
-      .then(result => displayModal(result.data.error_msg, ''))
+      .then(result => {
+        if (result.data.error) {
+          displayModal(result.data.error, '');
+        } else {
+          displayModal(result.data.ok, '');
+        }
+      })
       .catch(err => console.error(err));
   }
 
@@ -349,9 +371,13 @@ const Backups = props => {
         </div>
       </Toolbar>
       <div className="backups-wrapper">
-        {state.loading ? <Spinner /> : backups()}
+        {loading
+          ? <Spinner />
+          : (<>
+            {backups()}
+            <div className="total">{state.totalAmount}</div>
+          </>)}
       </div>
-      <div className="total">{state.totalAmount}</div>
       <Modal
         onSave={modalConfirmHandler}
         onCancel={modalCancelHandler}

+ 4 - 5
web/js/react/src/containers/ControlPanelContent/ControlPanelContent.jsx

@@ -66,10 +66,10 @@ const ControlPanelContent = props => {
   const dispatch = useDispatch();
 
   useEffect(() => {
-    if (userName) {
-      setLoading(false);
+    if (!userName) {
+      return history.push('/login');
     } else {
-      history.push('/login');
+      setLoading(false);
     }
 
     if (look) {
@@ -102,7 +102,6 @@ const ControlPanelContent = props => {
       return;
     }
 
-    console.log(event);
     switch (event.keyCode) {
       case 49: return history.push('/list/user/');
       case 50: return history.push('/list/web/');
@@ -200,7 +199,7 @@ const ControlPanelContent = props => {
                   })
                 }
 
-                <Route path="/list/user" component={props => <Users changeSearchTerm={handleSearchTerm} loading={loading} {...props} />} />
+                <Route path="/list/user" component={props => <Users changeSearchTerm={handleSearchTerm} {...props} />} />
                 <Route path="/add/user" component={() => <AddUser />} />
                 <Route path="/edit/user" component={() => <EditUser />} />
                 <Route path="/list/web" component={props => <Web {...props} changeSearchTerm={handleSearchTerm} />} />

+ 51 - 27
web/js/react/src/containers/CronJobs/CronJobs.jsx

@@ -16,13 +16,14 @@ import Spinner from '../../components/Spinner/Spinner';
 import { useSelector, useDispatch } from 'react-redux';
 import './CronJobs.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const CronJobs = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -31,7 +32,6 @@ const CronJobs = props => {
   const [state, setState] = useState({
     cronJobs: [],
     cronFav: [],
-    loading: true,
     toggledAll: false,
     cronReports: '',
     sorting: i18n.Date,
@@ -44,7 +44,7 @@ const CronJobs = props => {
     dispatch(addActiveElement('/list/cron/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -164,20 +164,23 @@ const CronJobs = props => {
   }
 
   const fetchData = () => {
-    getCronList()
-      .then(result => {
-        setState({
-          ...state,
-          cronJobs: reformatData(result.data.data),
-          cronReports: result.data.cron_reports,
-          cronFav: result.data.cron_fav,
-          selection: [],
-          toggledAll: false,
-          totalAmount: result.data.totalAmount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getCronList()
+        .then(result => {
+          setState({
+            ...state,
+            cronJobs: reformatData(result.data.data),
+            cronReports: result.data.cron_reports,
+            cronFav: result.data.cron_fav,
+            selection: [],
+            toggledAll: false,
+            totalAmount: result.data.totalAmount
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -316,13 +319,21 @@ const CronJobs = props => {
 
   const bulk = action => {
     const { selection } = state;
+    const notifications = state.cronReports === 'yes' ? 'delete-cron-reports' : 'add-cron-reports';
+
+    if (action === notifications) {
+      return handleCronNotifications();
+    }
 
     if (selection.length && action) {
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -340,15 +351,24 @@ const CronJobs = props => {
 
   const modalConfirmHandler = () => {
     if (!modal.actionUrl) {
-      modalCancelHandler();
+      return modalCancelHandler();
     }
 
-    setState({ ...state, loading: true });
     modalCancelHandler();
-
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -385,16 +405,20 @@ const CronJobs = props => {
               {state.cronReports === 'yes' ? i18n['turn off notifications'] : i18n['turn on notifications']}
             </button>
             <Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
-            <Select list='cronList' bulkAction={bulk} />
+            <Select list='cronList' bulkAction={bulk} cronReports={state.cronReports === 'yes'} />
             <DropdownFilter changeSorting={changeSorting} sorting={state.sorting} order={state.order} list="cronList" />
             <SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />
           </div>
         </div>
       </Toolbar>
       <div className="cron-wrapper">
-        {state.loading ? <Spinner /> : cronJobs()}
+        {loading
+          ? <Spinner />
+          : (<>
+            {cronJobs()}
+            <div className="total">{state.totalAmount}</div>
+          </>)}
       </div>
-      <div className="total">{state.totalAmount}</div>
       <Modal
         showCancelButton={modal.actionUrl}
         onCancel={modalCancelHandler}

+ 44 - 28
web/js/react/src/containers/DNSRecords/DNSRecords.jsx

@@ -17,14 +17,15 @@ import QueryString from 'qs';
 
 import './DNSRecords.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 export default function DnsRecords(props) {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem('token');
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
   const history = useHistory();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -34,7 +35,6 @@ export default function DnsRecords(props) {
     dnsRecords: [],
     dnsRecordFav: [],
     domain: '',
-    loading: true,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -44,7 +44,7 @@ export default function DnsRecords(props) {
 
   useEffect(() => {
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -156,23 +156,23 @@ export default function DnsRecords(props) {
 
   const fetchData = () => {
     let parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
-
-    setState({ ...state, loading: true });
-
-    getDNSRecordsList(parsedQueryString.domain || '')
-      .then(result => {
-        setState({
-          ...state,
-          dnsRecords: reformatData(result.data.data),
-          dnsRecordFav: result.data.dnsRecordsFav,
-          totalAmount: result.data.totalAmount,
-          domain: parsedQueryString.domain,
-          toggledAll: false,
-          selection: [],
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getDNSRecordsList(parsedQueryString.domain || '')
+        .then(result => {
+          setState({
+            ...state,
+            dnsRecords: reformatData(result.data.data),
+            dnsRecordFav: result.data.dnsRecordsFav,
+            totalAmount: result.data.totalAmount,
+            domain: parsedQueryString.domain,
+            toggledAll: false,
+            selection: []
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -281,11 +281,14 @@ export default function DnsRecords(props) {
     const { selection } = state;
 
     if (selection.length && action) {
-      bulkAction(action, selection)
+      setLoading(true);
+      bulkAction(action, selection, state.domain)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -302,12 +305,25 @@ export default function DnsRecords(props) {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -334,7 +350,7 @@ export default function DnsRecords(props) {
           </div>
         </div>
       </Toolbar>
-      {state.loading
+      {loading
         ? <Spinner />
         : (
           <>

+ 1 - 0
web/js/react/src/containers/DNSRecords/DNSRecords.scss

@@ -70,6 +70,7 @@ $textColor: #555;
     display: flex;
     align-items: center;
     margin-top: 2rem;
+    padding-bottom: 1.5rem;
 
     .total {
       margin: 0 3.5rem 0 14.3%;

+ 44 - 27
web/js/react/src/containers/Databases/Databases.jsx

@@ -16,13 +16,14 @@ import { useSelector, useDispatch } from 'react-redux';
 import Spinner from '../../components/Spinner/Spinner';
 import './Databases.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Databases = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -31,7 +32,6 @@ const Databases = props => {
   const [state, setState] = useState({
     databases: [],
     dbFav: [],
-    loading: true,
     toggledAll: false,
     dbAdmin: '',
     dbAdminLink: '',
@@ -45,7 +45,7 @@ const Databases = props => {
     dispatch(addActiveElement('/list/db/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -165,21 +165,24 @@ const Databases = props => {
   }
 
   const fetchData = () => {
-    getDatabaseList()
-      .then(result => {
-        setState({
-          ...state,
-          databases: reformatData(result.data.data),
-          dbAdmin: result.data.db_admin,
-          dbAdminLink: result.data.db_admin_link,
-          dbFav: result.data.dbFav,
-          selection: [],
-          toggledAll: false,
-          totalAmount: result.data.totalAmount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getDatabaseList()
+        .then(result => {
+          setState({
+            ...state,
+            databases: reformatData(result.data.data),
+            dbAdmin: result.data.db_admin,
+            dbAdminLink: result.data.db_admin_link,
+            dbFav: result.data.dbFav,
+            selection: [],
+            toggledAll: false,
+            totalAmount: result.data.totalAmount
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -323,12 +326,14 @@ const Databases = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ ...state, loading: true });
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -345,13 +350,25 @@ const Databases = props => {
   }
 
   const modalConfirmHandler = () => {
-    setState({ ...state, loading: true });
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -381,7 +398,7 @@ const Databases = props => {
         </div>
       </Toolbar>
       <div className="mails-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (<>
             {databases()}

+ 44 - 28
web/js/react/src/containers/DomainNameSystems/DomainNameSystems.jsx

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
 import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
 import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
 import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
-import { bulkAction, getDnsList, handleAction } from '../../ControlPanelService/Dns';
+import { bulkDomainAction, getDnsList, handleAction } from '../../ControlPanelService/Dns';
 import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
 import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
 import DomainNameSystem from '../../components/DomainNameSystem/DomainNameSystem';
@@ -17,13 +17,14 @@ import './DomainNameSystems.scss';
 
 import { useDispatch, useSelector } from 'react-redux';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const DomainNameSystems = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -32,7 +33,6 @@ const DomainNameSystems = props => {
   const [state, setState] = useState({
     domainNameSystems: [],
     dnsFav: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -44,7 +44,7 @@ const DomainNameSystems = props => {
     dispatch(addActiveElement('/list/dns/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -174,21 +174,22 @@ const DomainNameSystems = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getDnsList()
-      .then(result => {
-        setState({
-          ...state,
-          domainNameSystems: reformatData(result.data.data),
-          dnsFav: result.data.dnsFav,
-          selection: [],
-          toggledAll: false,
-          totalAmount: result.data.totalAmount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getDnsList()
+        .then(result => {
+          setState({
+            ...state,
+            domainNameSystems: reformatData(result.data.data),
+            dnsFav: result.data.dnsFav,
+            selection: [],
+            toggledAll: false,
+            totalAmount: result.data.totalAmount
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -341,12 +342,14 @@ const DomainNameSystems = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ loading: true });
-      bulkAction(action, selection)
+      setLoading(true);
+      bulkDomainAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -363,12 +366,25 @@ const DomainNameSystems = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -397,7 +413,7 @@ const DomainNameSystems = props => {
         </div>
       </Toolbar>
       <div className="dns-wrapper">
-        {state.loading ? <Spinner /> : dns()}
+        {loading ? <Spinner /> : dns()}
       </div>
       <div className="total">{state.totalAmount}</div>
       <Modal

+ 8 - 7
web/js/react/src/containers/FileManager/FileManager.js

@@ -20,15 +20,15 @@ class FileManager extends Component {
     super(props);
     this.state = {
       leftList: {
-        path: this.props.session.user.HOME,
+        path: this.props.menuCounters.user.HOME,
         files: { listing: [] },
       },
       rightList: {
-        path: this.props.session.user.HOME,
+        path: this.props.menuCounters.user.HOME,
         files: { listing: [] },
       },
-      currentPath: this.props.session.user.HOME,
-      currentUser: this.props.session.user.HOME,
+      currentPath: this.props.menuCounters.user.HOME,
+      currentUser: this.props.menuCounters.user.HOME,
       activeWindow: "left",
       modalWindow: null,
       modalVisible: false,
@@ -44,7 +44,7 @@ class FileManager extends Component {
   }
 
   UNSAFE_componentWillMount = () => {
-    FM.cacheData(this.state.currentUser, this.props.history, this.props.session.user.HOME);
+    FM.cacheData(this.state.currentUser, this.props.history, this.props.menuCounters.user.HOME);
     let currentPath = FM.activeWindowPath();
     this.setState({ currentPath });
     this.changeDirectoryOnLoading();
@@ -435,7 +435,7 @@ class FileManager extends Component {
         addToPath={this.addToPath}
         cursor={this.state.cursor}
         passData={this.passData}
-        rootDir={this.props.session.user.HOME}
+        rootDir={this.props.menuCounters.user.HOME}
         ref={el => this[`${side}List`] = el}
         download={this.download}
         moveBack={this.moveBack}
@@ -480,7 +480,8 @@ class FileManager extends Component {
 
 function mapStateToProps(state) {
   return {
-    session: state.session
+    session: state.session,
+    menuCounters: state.menuCounters
   }
 }
 

+ 40 - 23
web/js/react/src/containers/Firewalls/Banlist/index.jsx

@@ -16,6 +16,7 @@ import { Helmet } from 'react-helmet';
 import { useHistory } from 'react-router';
 
 import './styles.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const BanLists = props => {
   const { i18n } = useSelector(state => state.session);
@@ -23,6 +24,7 @@ const BanLists = props => {
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -31,7 +33,6 @@ const BanLists = props => {
   const [state, setState] = useState({
     banIps: [],
     selection: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Action,
     order: "descending",
@@ -42,7 +43,7 @@ const BanLists = props => {
     dispatch(addActiveElement('/list/firewall/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -148,20 +149,21 @@ const BanLists = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getBanList()
-      .then(result => {
-        setState({
-          ...state,
-          banIps: reformatData(result.data.data),
-          totalAmount: result.data.total_amount,
-          toggledAll: false,
-          selection: [],
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getBanList()
+        .then(result => {
+          setState({
+            ...state,
+            banIps: reformatData(result.data.data),
+            totalAmount: result.data.total_amount,
+            toggledAll: false,
+            selection: []
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -234,8 +236,10 @@ const BanLists = props => {
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -247,12 +251,25 @@ const BanLists = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
     modalCancelHandler();
-    setState({ ...state, loading: true });
+    setLoading(true);
+    handleAction(modal.actionUrl)
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
 
-    handleAction(state.modalActionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -275,7 +292,7 @@ const BanLists = props => {
         </div>
       </Toolbar>
       <div className="banlist-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (<>
             {banIps()}

+ 48 - 27
web/js/react/src/containers/Firewalls/Firewalls.jsx

@@ -17,13 +17,14 @@ import { useDispatch, useSelector } from 'react-redux';
 import './Firewalls.scss';
 import { Helmet } from 'react-helmet';
 import { Link } from 'react-router-dom';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Firewalls = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -34,7 +35,6 @@ const Firewalls = props => {
     firewallFav: [],
     selection: [],
     firewallExtension: '',
-    loading: false,
     toggledAll: false,
     sorting: i18n.Action,
     order: "descending",
@@ -45,7 +45,7 @@ const Firewalls = props => {
     dispatch(addActiveElement('/list/firewall/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -165,22 +165,23 @@ const Firewalls = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getFirewallList()
-      .then(result => {
-        setState({
-          ...state,
-          firewalls: reformatData(result.data.data),
-          firewallFav: result.data.firewallFav,
-          selection: [],
-          firewallExtension: result.data.firewallExtension,
-          totalAmount: result.data.totalAmount,
-          toggledAll: false,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getFirewallList()
+        .then(result => {
+          setState({
+            ...state,
+            firewalls: reformatData(result.data.data),
+            firewallFav: result.data.firewallFav,
+            selection: [],
+            firewallExtension: result.data.firewallExtension,
+            totalAmount: result.data.totalAmount,
+            toggledAll: false
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -319,11 +320,14 @@ const Firewalls = props => {
     const { selection } = state;
 
     if (selection.length && action) {
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -335,12 +339,25 @@ const Firewalls = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
     modalCancelHandler();
-    setState({ ...state, loading: true });
+    setLoading(true);
+    handleAction(modal.actionUrl)
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
 
-    handleAction(state.modalActionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -365,9 +382,13 @@ const Firewalls = props => {
         </div>
       </Toolbar>
       <div className="firewalls-wrapper">
-        {state.loading ? <Spinner /> : firewalls()}
+        {loading
+          ? <Spinner />
+          : (<>
+            {firewalls()}
+            <div className="total">{state.totalAmount}</div>
+          </>)}
       </div>
-      <div className="total">{state.totalAmount}</div>
       <Modal
         onSave={modalConfirmHandler}
         onCancel={modalCancelHandler}

+ 47 - 27
web/js/react/src/containers/InternetProtocols/InternetProtocols.jsx

@@ -16,12 +16,14 @@ import Spinner from '../../components/Spinner/Spinner';
 import { useDispatch, useSelector } from 'react-redux';
 import './InternetProtocols.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const InternetProtocols = props => {
   const { i18n } = useSelector(state => state.session);
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -30,7 +32,6 @@ const InternetProtocols = props => {
   const [state, setState] = useState({
     internetProtocols: [],
     ipFav: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -42,7 +43,7 @@ const InternetProtocols = props => {
     dispatch(addActiveElement('/list/ip/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -153,21 +154,22 @@ const InternetProtocols = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getIpList()
-      .then(result => {
-        setState({
-          ...state,
-          internetProtocols: reformatData(result.data.data),
-          ipFav: result.data.ipFav,
-          selection: [],
-          totalAmount: result.data.totalAmount,
-          toggledAll: false,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getIpList()
+        .then(result => {
+          setState({
+            ...state,
+            internetProtocols: reformatData(result.data.data),
+            ipFav: result.data.ipFav,
+            selection: [],
+            totalAmount: result.data.totalAmount,
+            toggledAll: false
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -312,13 +314,14 @@ const InternetProtocols = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ ...state, loading: true });
-
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -330,12 +333,25 @@ const InternetProtocols = props => {
   }
 
   const modalConfirmHandler = () => {
-    modalCancelHandler();
-    setState({ ...state, loading: true });
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
 
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -359,9 +375,13 @@ const InternetProtocols = props => {
         </div>
       </Toolbar>
       <div className="ip-wrapper">
-        {state.loading ? <Spinner /> : internetProtocols()}
+        {loading
+          ? <Spinner />
+          : (<>
+            {internetProtocols()}
+            <div className="total">{state.totalAmount}</div>
+          </>)}
       </div>
-      <div className="total">{state.totalAmount}</div>
       <Modal
         onSave={modalConfirmHandler}
         onCancel={modalCancelHandler}

+ 44 - 27
web/js/react/src/containers/MailAccounts/MailAccounts.jsx

@@ -17,13 +17,15 @@ import { Link } from 'react-router-dom';
 
 import './MailAccounts.scss';
 import { Helmet } from 'react-helmet';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 export default function MailAccounts(props) {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -32,7 +34,6 @@ export default function MailAccounts(props) {
   const [state, setState] = useState({
     mailAccounts: [],
     mailAccountsFav: [],
-    loading: false,
     domain: props.domain,
     toggledAll: false,
     sorting: i18n.Date,
@@ -43,7 +44,7 @@ export default function MailAccounts(props) {
 
   useEffect(() => {
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -131,7 +132,6 @@ export default function MailAccounts(props) {
   }
 
   const handleFocusedElementShortcuts = event => {
-    event.preventDefault();
     let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
 
     if (controlPanelFocusedElement && !isSearchInputFocused) {
@@ -164,22 +164,23 @@ export default function MailAccounts(props) {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getMailAccountList(props.domain)
-      .then(result => {
-        setState({
-          ...state,
-          mailAccounts: reformatData(result.data.data),
-          webMail: result.data.webMail,
-          selection: [],
-          toggledAll: false,
-          mailAccountsFav: result.data.mailAccountsFav,
-          totalAmount: result.data.totalAmount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getMailAccountList(props.domain)
+        .then(result => {
+          setState({
+            ...state,
+            mailAccounts: reformatData(result.data.data),
+            webMail: result.data.webmail,
+            selection: [],
+            toggledAll: false,
+            mailAccountsFav: result.data.mailAccountsFav,
+            totalAmount: result.data.totalAmount
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -319,11 +320,14 @@ export default function MailAccounts(props) {
   const bulk = action => {
     const { selection } = state;
     if (selection.length && action) {
+      setLoading(true);
       bulkMailAccountAction(action, props.domain, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -340,12 +344,25 @@ export default function MailAccounts(props) {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -374,7 +391,7 @@ export default function MailAccounts(props) {
           </div>
         </div>
       </Toolbar>
-      {state.loading
+      {loading
         ? <Spinner />
         : (
           <>

+ 2 - 0
web/js/react/src/containers/MailAccounts/MailAccounts.scss

@@ -17,6 +17,7 @@ $primary: #2c54ac;
     div.list-item {
       .r-col {
         .stat.email {
+            width: max-content;
             text-transform: none;
         }
       }
@@ -27,6 +28,7 @@ $primary: #2c54ac;
     display: flex;
     align-items: center;
     margin-top: 2rem;
+    padding-bottom: 1.5rem;
 
     .total {
       margin: 0 3.5rem 0 14.3%;

+ 43 - 27
web/js/react/src/containers/Mails/Mails.jsx

@@ -17,13 +17,14 @@ import './Mails.scss';
 
 import { useSelector, useDispatch } from 'react-redux';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Mails = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -32,7 +33,6 @@ const Mails = props => {
   const [state, setState] = useState({
     mails: [],
     mailFav: [],
-    loading: false,
     toggledAll: false,
     webMail: '',
     sorting: i18n.Date,
@@ -45,7 +45,7 @@ const Mails = props => {
     dispatch(addActiveElement('/list/mail/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -175,22 +175,23 @@ const Mails = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getMailList()
-      .then(result => {
-        setState({
-          ...state,
-          mails: reformatData(result.data.data),
-          webMail: result.data.webMail,
-          mailFav: result.data.mailFav,
-          selection: [],
-          toggledAll: false,
-          totalAmount: result.data.totalAmount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getMailList()
+        .then(result => {
+          setState({
+            ...state,
+            mails: reformatData(result.data.data),
+            webMail: result.data.webMail,
+            mailFav: result.data.mailFav,
+            selection: [],
+            toggledAll: false,
+            totalAmount: result.data.totalAmount
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -332,12 +333,14 @@ const Mails = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ ...state, loading: true });
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -354,12 +357,25 @@ const Mails = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -389,7 +405,7 @@ const Mails = props => {
         </div>
       </Toolbar>
       <div className="mails-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (<>
             {mails()}

+ 43 - 25
web/js/react/src/containers/Packages/Packages.jsx

@@ -16,12 +16,14 @@ import Spinner from '../../components/Spinner/Spinner';
 import { useDispatch, useSelector } from 'react-redux';
 import './Packages.scss';
 import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Packages = props => {
   const { i18n } = useSelector(state => state.session);
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -30,7 +32,6 @@ const Packages = props => {
   const [state, setState] = useState({
     packages: [],
     packagesFav: [],
-    loading: true,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -42,7 +43,7 @@ const Packages = props => {
     dispatch(addActiveElement('/list/package/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -149,23 +150,26 @@ const Packages = props => {
     const { packages } = state;
     let currentPackageData = packages.filter(pack => pack.NAME === controlPanelFocusedElement)[0];
 
-    displayModal(currentPackageData.delete_conf, `/api/v1/delete/package/?package=${controlPanelFocusedElement}`);
+    displayModal(currentPackageData.delete_conf, `/api/v1/delete/package/index.php?package=${controlPanelFocusedElement}`);
   }
 
   const fetchData = () => {
-    getPackageList()
-      .then(result => {
-        setState({
-          ...state,
-          packages: reformatData(result.data.data),
-          packagesFav: result.data.packagesFav,
-          totalAmount: result.data.totalAmount,
-          selection: [],
-          toggledAll: false,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getPackageList()
+        .then(result => {
+          setState({
+            ...state,
+            packages: reformatData(result.data.data),
+            packagesFav: result.data.packagesFav,
+            totalAmount: result.data.totalAmount,
+            selection: [],
+            toggledAll: false
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = data => {
@@ -306,13 +310,14 @@ const Packages = props => {
     const { selection } = state;
 
     if (selection.length && action) {
-      setState({ ...state, loading: true });
-
+      setLoading(true);
       bulkAction(action, selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -324,12 +329,25 @@ const Packages = props => {
   }
 
   const modalConfirmHandler = () => {
-    modalCancelHandler();
-    setState({ ...state, loading: true });
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
 
+    modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -354,7 +372,7 @@ const Packages = props => {
       </Toolbar>
       <div className="packages-wrapper">
         {
-          state.loading
+          loading
             ? <Spinner />
             : (<>
               {packages()}

+ 8 - 2
web/js/react/src/containers/RRDs/RRDs.scss

@@ -18,7 +18,7 @@ div.content {
   color: $textColor;
 
   .toolbar {
-    padding-bottom: 10px;
+    padding: 6px 12.5%;
   }
 
   .rrd-item {
@@ -89,4 +89,10 @@ div.content {
   > div.active {
     color: $secondaryActive;
   }
-}
+}
+
+@media (max-width: 1350px) {
+  .rrd-list .toolbar {
+    padding: 6px 9.5%;
+  }
+}

+ 43 - 23
web/js/react/src/containers/Search/Search.jsx

@@ -7,18 +7,20 @@ import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
 import Modal from '../../components/ControlPanel/Modal/Modal';
 import Spinner from '../../components/Spinner/Spinner';
 import './Search.scss';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
 import { useHistory } from 'react-router-dom';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Search = props => {
   const { i18n } = useSelector(state => state.session);
   const history = useHistory();
+  const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [state, setState] = useState({
     searchResults: [],
     totalAmount: '',
     sorting: i18n.Date,
     order: "descending",
-    loading: false,
     total: 0
   });
   const [modal, setModal] = useState({
@@ -34,29 +36,31 @@ const Search = props => {
       let searchTerm = search.split('=')[1];
 
       if (searchTerm !== '') {
-        fetchData(searchTerm);
+        fetchData(searchTerm).then(() => setLoading(false));
       } else {
         return history.push({ pathname: '/list/user/', search: '' });
       }
     } else if (props.searchTerm !== '') {
-      fetchData(props.searchTerm);
+      fetchData(props.searchTerm).then(() => setLoading(false));
     } else {
       return history.push({ pathname: '/list/user/', search: '' });
     }
   }, []);
 
   const fetchData = searchTerm => {
-    setState({ ...state, loading: true });
-    getSearchResultsList(searchTerm)
-      .then(result => {
-        setState({
-          ...state,
-          searchResults: result.data.data,
-          totalAmount: result.data.total_amount,
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getSearchResultsList(searchTerm)
+        .then(result => {
+          setState({
+            ...state,
+            searchResults: result.data.data,
+            totalAmount: result.data.total
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const searchResults = () => {
@@ -99,7 +103,6 @@ const Search = props => {
     switch (sorting) {
       case Date: return 'DATE';
       case Name: return 'RESULT';
-      case Starred: return 'STARRED';
       default: break;
     }
   }
@@ -114,12 +117,25 @@ const Search = props => {
   }
 
   const modalConfirmHandler = () => {
-    handleAction(state.modalActionUrl)
-      .then(() => {
-        fetchData();
-        modalCancelHandler();
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
+    modalCancelHandler();
+    setLoading(true);
+    handleAction(modal.actionUrl)
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -141,9 +157,13 @@ const Search = props => {
         </div>
       </Toolbar>
       <div className="statistics-wrapper">
-        {state.loading ? <Spinner /> : searchResults()}
+        {loading
+          ? <Spinner />
+          : (<>
+            {searchResults()}
+            <div className="total">{state.totalAmount}</div>
+          </>)}
       </div>
-      <div className="total">{state.totalAmount}</div>
       <Modal
         onSave={modalConfirmHandler}
         onCancel={modalCancelHandler}

+ 42 - 25
web/js/react/src/containers/Servers/Servers.jsx

@@ -15,12 +15,14 @@ import Server from '../../components/Server/Server';
 import { Link } from 'react-router-dom';
 import { Helmet } from 'react-helmet';
 import './Servers.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Servers = props => {
   const { i18n } = useSelector(state => state.session);
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -29,7 +31,6 @@ const Servers = props => {
   const [state, setState] = useState({
     servers: [],
     selection: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Action,
     order: "descending",
@@ -39,7 +40,7 @@ const Servers = props => {
     dispatch(addActiveElement('/list/server/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -156,19 +157,20 @@ const Servers = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getServersList()
-      .then(result => {
-        setState({
-          ...state,
-          selection: [],
-          toggledAll: false,
-          servers: reformatData(result.data.data, result.data.sys),
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getServersList()
+        .then(result => {
+          setState({
+            ...state,
+            selection: [],
+            toggledAll: false,
+            servers: reformatData(result.data.data, result.data.sys)
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const reformatData = (servers, sysInfo) => {
@@ -207,18 +209,17 @@ const Servers = props => {
   const onHandleAction = uri => {
     dispatch(removeControlPanelContentFocusedElement());
     if (uri) {
-      setState({ ...state, loading: true });
-
+      setLoading(true);
       handleAction(uri)
         .then(res => {
           if (res.data.error) {
             displayModal(res.data.error);
           }
 
-          setState({ ...state, loading: false });
+          setLoading(false);
         })
         .catch(err => {
-          setState({ ...state, loading: false });
+          setLoading(false);
           console.error(err)
         });
     }
@@ -258,8 +259,10 @@ const Servers = props => {
             displayModal(res.data.error);
           }
 
-          fetchData();
-          toggleAll(false);
+          fetchData().then(() => {
+            refreshMenuCounters();
+            toggleAll(false);
+          });
         })
         .catch(err => console.error(err));
     }
@@ -288,11 +291,25 @@ const Servers = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
     modalCancelHandler();
-    setState({ ...state, loading: true });
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => fetchData())
-      .catch(err => console.error(err));
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
+      })
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -315,7 +332,7 @@ const Servers = props => {
           </div>
         </div>
       </Toolbar>
-      {state.loading ? <Spinner /> : (
+      {loading ? <Spinner /> : (
         <div className="servers-wrapper">
           {servers()}
         </div>

+ 7 - 1
web/js/react/src/containers/Statistics/Statistics.scss

@@ -2,7 +2,7 @@
   color: #686868;
 
   .toolbar {
-    padding-bottom: 5px;
+    padding: 6px 12.5%;
   }
 
   .l-col {
@@ -28,4 +28,10 @@
   .r-col .date {
     color: #5edad0;
   }
+}
+
+@media (max-width: 1350px) {
+  .statistics-list .toolbar {
+    padding: 6px 9.5%;
+  }
 }

+ 41 - 26
web/js/react/src/containers/Users/Users.jsx

@@ -16,12 +16,14 @@ import Spinner from '../../components/Spinner/Spinner';
 import User from '../../components/User/User';
 import { Helmet } from 'react-helmet';
 import './Users.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Users = props => {
   const { userName, i18n, session: { look } } = useSelector(state => state.session);
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -30,7 +32,6 @@ const Users = props => {
   const [state, setState] = useState({
     users: [],
     userFav: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -42,7 +43,7 @@ const Users = props => {
     dispatch(addActiveElement('/list/user/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -60,21 +61,22 @@ const Users = props => {
   }, [controlPanelFocusedElement, focusedElement, state.users]);
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getUsersList()
-      .then(result => {
-        setState({
-          ...state,
-          users: reformatData(result.data.data),
-          userFav: result.data.userFav,
-          totalAmount: result.data.totalAmount,
-          toggledAll: false,
-          selection: [],
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getUsersList()
+        .then(result => {
+          setState({
+            ...state,
+            users: reformatData(result.data.data),
+            userFav: result.data.userFav,
+            totalAmount: result.data.totalAmount,
+            toggledAll: false,
+            selection: []
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const handleFocusedElementShortcuts = event => {
@@ -322,13 +324,14 @@ const Users = props => {
 
   const bulk = action => {
     if (state.selection.length && action) {
-      setState({ ...state, loading: true });
-
+      setLoading(true);
       bulkAction(action, state.selection)
         .then(result => {
           if (result.status === 200) {
-            fetchData();
-            toggleAll(false);
+            fetchData().then(() => {
+              refreshMenuCounters();
+              toggleAll(false);
+            });
           }
         })
         .catch(err => console.error(err));
@@ -345,13 +348,25 @@ const Users = props => {
   }
 
   const modalConfirmHandler = () => {
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
     modalCancelHandler();
-    setState({ ...state, loading: true });
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -383,7 +398,7 @@ const Users = props => {
         </div>
       </Toolbar>
       <div className="users-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (<>
             {users()}

+ 41 - 29
web/js/react/src/containers/Web/Web.jsx

@@ -16,13 +16,14 @@ import Modal from '../../components/ControlPanel/Modal/Modal';
 import { useDispatch, useSelector } from 'react-redux';
 import { Helmet } from 'react-helmet';
 import './Web.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
 
 const Web = props => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
   const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
   const { focusedElement } = useSelector(state => state.mainNavigation);
   const dispatch = useDispatch();
+  const [loading, setLoading] = useState(false);
   const [modal, setModal] = useState({
     text: '',
     visible: false,
@@ -31,7 +32,6 @@ const Web = props => {
   const [state, setState] = useState({
     webDomains: [],
     webFav: [],
-    loading: false,
     toggledAll: false,
     sorting: i18n.Date,
     order: "descending",
@@ -43,7 +43,7 @@ const Web = props => {
     dispatch(addActiveElement('/list/web/'));
     dispatch(removeFocusedElement());
     dispatch(removeControlPanelContentFocusedElement());
-    fetchData();
+    fetchData().then(() => setLoading(false));
 
     return () => {
       dispatch(removeControlPanelContentFocusedElement());
@@ -168,21 +168,22 @@ const Web = props => {
   }
 
   const fetchData = () => {
-    setState({ ...state, loading: true });
-
-    getWebList()
-      .then(result => {
-        setState({
-          ...state,
-          webDomains: reformatData(result.data.data),
-          webFav: result.data.webFav,
-          totalAmount: result.data.totalAmount,
-          toggledAll: false,
-          selection: [],
-          loading: false
-        });
-      })
-      .catch(err => console.error(err));
+    setLoading(true);
+    return new Promise((resolve, reject) => {
+      getWebList()
+        .then(result => {
+          setState({
+            ...state,
+            webDomains: reformatData(result.data.data),
+            webFav: result.data.webFav,
+            totalAmount: result.data.totalAmount,
+            toggledAll: false,
+            selection: []
+          });
+          resolve();
+        })
+        .catch(err => console.error(err));
+    });
   }
 
   const changeSorting = (sorting, order) => {
@@ -318,14 +319,13 @@ const Web = props => {
 
   const bulk = action => {
     if (state.selection.length && action) {
-      setState({ ...state, loading: true });
-
+      setLoading(true);
       bulkAction(action, state.selection)
         .then(result => {
-          if (result.status === 200) {
-            fetchData();
+          fetchData().then(() => {
+            refreshMenuCounters();
             toggleAll(false);
-          }
+          });
         })
         .catch(err => console.error(err));
     }
@@ -334,20 +334,32 @@ const Web = props => {
   const displayModal = (text, url) => {
     setModal({
       ...modal,
-      visible: !modal.visible,
+      visible: true,
       text,
       actionUrl: url
     });
   }
 
   const modalConfirmHandler = () => {
-    setState({ ...state, loading: true });
+    if (!modal.actionUrl) {
+      return modalCancelHandler();
+    }
+
     modalCancelHandler();
+    setLoading(true);
     handleAction(modal.actionUrl)
-      .then(() => {
-        fetchData();
+      .then(res => {
+        if (res.data.error) {
+          setLoading(false);
+          return displayModal(res.data.error, '');
+        }
+        fetchData().then(() => refreshMenuCounters())
       })
-      .catch(err => console.error(err));
+      .catch(err => { setLoading(false); console.error(err); });
+  }
+
+  const refreshMenuCounters = () => {
+    dispatch(refreshCounters()).then(() => setLoading(false));
   }
 
   const modalCancelHandler = () => {
@@ -376,7 +388,7 @@ const Web = props => {
         </div>
       </Toolbar>
       <div className="web-domains-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (
             <>

+ 1 - 2
web/js/react/src/containers/WebLogs/WebLogs.jsx

@@ -12,11 +12,10 @@ import Spinner from 'src/components/Spinner/Spinner';
 import { Helmet } from 'react-helmet';
 
 export default function WebLogs() {
-  const { i18n } = useSelector(state => state.session);
+  const { i18n, userName } = useSelector(state => state.session);
   const history = useHistory();
   const dispatch = useDispatch();
   const mainNavigation = useSelector(state => state.mainNavigation);
-  const { userName } = useSelector(state => state.session);
   const [domain, setDomain] = useState();
   const [state, setState] = useState({
     data: "",

+ 19 - 0
web/js/react/src/reducers/MenuCounters/menuCounterReducer.js

@@ -0,0 +1,19 @@
+import { REFRESH_COUNTERS } from 'src/actions/MenuCounters/menuCounterTypes';
+
+const INITIAL_STATE = {
+  user: {},
+};
+
+const menuCounterReducer = (state = INITIAL_STATE, action) => {
+  switch (action.type) {
+    case REFRESH_COUNTERS:
+      return {
+        ...state,
+        user: action.value.user,
+      };
+
+    default: return state;
+  }
+};
+
+export default menuCounterReducer;

+ 1 - 1
web/js/react/src/reducers/Notification/notificationReducer.js

@@ -1,7 +1,7 @@
 import { ADD_NOTIFICATIONS, REMOVE_NOTIFICATIONS } from 'src/actions/Notification/notificationTypes';
 
 const INITIAL_STATE = {
-  notifications: []
+  notifications: null
 };
 
 const notificationReducer = (state = INITIAL_STATE, action) => {

+ 0 - 5
web/js/react/src/reducers/Session/sessionReducer.js

@@ -2,7 +2,6 @@ import { LOGGED_OUT_AS, LOGIN, LOGOUT, CHECK_AUTH } from '../../actions/Session/
 
 const INITIAL_STATE = {
   token: '',
-  user: {},
   error: '',
   session: {},
   i18n: {},
@@ -16,7 +15,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
       return {
         ...state,
         token: action.value.token,
-        user: action.value.user,
         session: action.value.session,
         userName: action.value.userName,
         i18n: action.value.i18n || {},
@@ -28,7 +26,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
       return {
         ...state,
         token: action.value.token,
-        user: action.value.user,
         session: action.value.session,
         userName: action.value.userName,
         i18n: action.value.i18n || {},
@@ -40,7 +37,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
       return {
         ...state,
         token: action.value.token,
-        user: action.value.user,
         session: action.value.session,
         userName: action.value.userName,
         i18n: action.value.i18n || {},
@@ -51,7 +47,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
     case CHECK_AUTH: return {
       ...state,
       token: action.value.token,
-      user: action.value.user,
       session: action.value.session,
       userName: action.value.userName,
       i18n: action.value.i18n || {},

Неке датотеке нису приказане због велике количине промена