Просмотр исходного кода

Merge pull request #2071 from serghey-rodin/feature/new-react-ui

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

+ 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);

+ 8 - 5
web/js/react/src/ControlPanelService/Firewalls.js

@@ -17,16 +17,17 @@ export const getBanList = () => {
   return axios.get(BASE_URL + banListUri);
 }
 
-export const bulkAction = (action, firewalls) => {
+export const bulkAction = (action, ips, banIps) => {
   const formData = new FormData();
   formData.append("action", action);
   formData.append("token", getAuthToken());
 
-  firewalls.forEach(firewall => {
-    formData.append("rule[]", firewall);
+  ips.forEach(ip => {
+    const banIp = banIps.find(banIp => banIp.NAME === ip);
+    formData.append("ipchain[]", `${ip}:${banIp['CHAIN']}`);
   });
 
-  return axios.post(BASE_URL + '/api/v1/bulk/firewall/', formData);
+  return axios.post(BASE_URL + '/api/v1/bulk/firewall/banlist/', formData);
 };
 
 export const handleAction = uri => {
@@ -54,11 +55,13 @@ export const getBanIps = data => {
 export const addBanIp = (data) => {
   let formDataObject = new FormData();
 
+  formDataObject.append('token', getAuthToken());
+
   for (let key in data) {
     formDataObject.append(key, data[key]);
   }
 
-  return axios.get(BASE_URL + addBanIpsUri, {
+  return axios.post(BASE_URL + addBanIpsUri, formDataObject, {
     params: {
       token: getAuthToken()
     }

+ 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);

+ 2 - 2
web/js/react/src/ControlPanelService/Updates.js

@@ -1,8 +1,8 @@
 import axios from "axios";
 import { getAuthToken } from "src/utils/token";
 
-const deleteAutoUpdateUri = '/delete/cron/autoupdate/';
-const addAutoUpdateUri = '/add/cron/autoupdate/';
+const deleteAutoUpdateUri = '/api/v1/delete/cron/autoupdate/';
+const addAutoUpdateUri = '/api/v1/add/cron/autoupdate/';
 const webApiUri = '/api/v1/list/updates/index.php';
 const BASE_URL = window.location.origin;
 

+ 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>

+ 22 - 7
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;
       }
     }
     
@@ -192,7 +207,7 @@ $errorColor: #BE5ABF;
     label.label-wrapper {
       display: flex;
       align-items: flex-end;
-      width: fit-content;
+      width: max-content;
 
       span {
         font-weight: normal;

+ 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>

+ 1 - 1
web/js/react/src/components/Firewall/Add/AddFirewall.scss

@@ -1,6 +1,6 @@
 .content .edit-template.add-firewall {
   .toolbar .search-toolbar-name {
-    width: fit-content;
+    width: max-content;
   }
 
   label.label-wrapper[for=ip] span {

+ 9 - 3
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);
@@ -42,15 +43,18 @@ const AddBanIP = () => {
     }
 
     if (Object.keys(newUser).length !== 0 && newUser.constructor === Object) {
+      setState({ ...state, loading: true });
       addBanIp(newUser)
         .then(result => {
           if (result.status === 200) {
             const { error_msg, ok_msg } = result.data;
 
             if (error_msg) {
-              setState({ ...state, errorMessage: error_msg, okMessage: '' });
+              setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
             } else if (ok_msg) {
-              setState({ ...state, errorMessage: '', okMessage: ok_msg });
+              setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
+            } else {
+              setState({ ...state, loading: false });
             }
           }
         })
@@ -74,12 +78,14 @@ 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>
         {state.loading ? <Spinner /> :
           <form onSubmit={event => submitFormHandler(event)} id="add-user">
+            <input type="hidden" name="ok" value="add" />
+
             <div class="form-group">
               <label htmlFor="chain">{i18n.Banlist}</label>
               <select class="form-control" id="chain" name="v_chain">

+ 3 - 4
web/js/react/src/components/Firewall/Ban/index.jsx

@@ -6,14 +6,13 @@ import { useSelector } from 'react-redux';
 
 const Ban = ({ data, ...props }) => {
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
 
   const checkItem = () => {
     props.checkItem(data.NAME);
   }
 
   const handleDelete = () => {
-    props.handleModal(data.delete_conf, `/api/v1/delete/firewall/banlist/?ip=${data.NAME}&chain=${data.CHAIN}`);
+    props.handleModal(data.delete_confirmation, `/api/v1/delete/firewall/banlist/?ip=${data.NAME}&chain=${data.CHAIN}`);
   }
 
   return (
@@ -32,10 +31,10 @@ const Ban = ({ data, ...props }) => {
             <div></div>
           </Container>
           <Container className="c-2 w-30">
-            <div>{data.CHAIN}</div>
+            <div><b>{data.CHAIN}</b></div>
           </Container>
           <Container className="c-2 w-30">
-            <div>{data.NAME}</div>
+            <div><b>{data.NAME}</b></div>
           </Container>
         </div>
       </Container>

+ 25 - 21
web/js/react/src/components/Firewall/Edit/EditFirewall.jsx

@@ -13,6 +13,7 @@ import QS from 'qs';
 
 import './EditFirewall.scss';
 import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
 
 const EditFirewall = props => {
   const token = localStorage.getItem("token");
@@ -34,22 +35,26 @@ const EditFirewall = props => {
     dispatch(removeFocusedElement());
 
     if (rule) {
-      setState({ ...state, loading: true });
-
-      getFirewallInfo(rule)
-        .then(response => {
-          setState({
-            ...state,
-            data: response.data,
-            errorMessage: response.data['error_msg'],
-            okMessage: response.data['ok_msg'],
-            loading: false
-          });
-        })
-        .catch(err => console.error(err));
+      fetchData(rule);
     }
   }, []);
 
+  const fetchData = rule => {
+    setState({ ...state, loading: true });
+
+    getFirewallInfo(rule)
+      .then(response => {
+        setState({
+          ...state,
+          data: response.data,
+          errorMessage: response.data['error_msg'],
+          okMessage: response.data['ok_msg'],
+          loading: false
+        });
+      })
+      .catch(err => console.error(err));
+  }
+
   const submitFormHandler = event => {
     event.preventDefault();
     let updatedDomain = {};
@@ -61,20 +66,19 @@ const EditFirewall = props => {
     if (Object.keys(updatedDomain).length !== 0 && updatedDomain.constructor === Object) {
       setState({ ...state, loading: true });
 
-      updateFirewall(updatedDomain, state.data.domain)
+      updateFirewall(updatedDomain, state.data.rule)
         .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 });
+              setState({ ...state, okMessage, errorMessage: '', loading: false });
             }
           }
         })
+        .then(() => fetchData(state.data.rule))
         .catch(err => console.error(err));
     }
   }
@@ -94,7 +98,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>

+ 3 - 4
web/js/react/src/components/Firewall/Firewall.jsx

@@ -7,19 +7,18 @@ import './Firewall.scss';
 import { useSelector } from 'react-redux';
 
 const Firewall = ({ data, ...props }) => {
-  const token = localStorage.getItem("token");
   const { i18n } = useSelector(state => state.session);
 
   const toggleFav = (starred) => {
     if (starred) {
-      props.toggleFav(props.data.NAME, 'add');
+      props.toggleFav(data.NAME, 'add');
     } else {
-      props.toggleFav(props.data.NAME, 'delete');
+      props.toggleFav(data.NAME, 'delete');
     }
   }
 
   const checkItem = () => {
-    props.checkItem(props.data.NAME);
+    props.checkItem(data.NAME);
   }
 
   const handleSuspend = () => {

+ 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 13%;
+  }
+
   .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>

+ 16 - 1
web/js/react/src/components/MailAccount/Add/AddMailAccount.scss

@@ -1,6 +1,6 @@
 .content .edit-template.add-mail-account {
   .search-toolbar-name {
-    width: fit-content;
+    width: max-content;
   }
 
   form {
@@ -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 13% 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>

+ 1 - 1
web/js/react/src/components/Package/Add/AddPackage.scss

@@ -3,7 +3,7 @@
     label.label-wrapper {
       display: flex;
       align-items: flex-end;
-      width: fit-content;
+      width: max-content;
 
       span {
         font-weight: normal;

+ 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>

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

@@ -17,7 +17,7 @@ const RRD = props => {
     let year = newDate.getFullYear();
     let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 
-    return <div className="date">{day} &nbsp; {months[month - 1]} &nbsp; {year}</div>;
+    return <div className="date">{day} &nbsp; {months[month]} &nbsp; {year}</div>;
   }
 
   return (

+ 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>

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

@@ -1,7 +1,7 @@
 .content .edit-template.edit-bind9 {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
   }
 

+ 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>

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

@@ -1,7 +1,7 @@
 .content .edit-template.edit-bind9 {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
   }
 

+ 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>

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

@@ -121,7 +121,7 @@ $textColor: #555;
           padding: 7px 15px;
           text-transform: capitalize;
           text-decoration: none;
-          width: fit-content;
+          width: max-content;
           margin-right: 10px;
 
           &:hover {

+ 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 - 2
web/js/react/src/components/Server/Edit/Httpd/EditHttpd.scss

@@ -4,7 +4,7 @@ $secondaryLight: #f8b014;
 .content .edit-template.edit-httpd {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
 
       a {
         color: $secondary;
@@ -17,7 +17,7 @@ $secondaryLight: #f8b014;
     }
 
     .link {
-      width: fit-content;
+      width: max-content;
       margin-left: 15px;
 
       a {

+ 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>

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

@@ -1,7 +1,7 @@
 .content .edit-template.edit-mysql {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
   }
 

+ 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 - 2
web/js/react/src/components/Server/Edit/Nginx/EditServerNginx.scss

@@ -3,11 +3,11 @@ $secondaryLight: #f8b014;
 .content .edit-template.edit-nginx {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
 
     .link {
-      width: fit-content;
+      width: max-content;
       margin-left: 15px;
 
       a {

+ 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 - 2
web/js/react/src/components/Server/Edit/PHP/EditPhp.scss

@@ -5,13 +5,13 @@ $textColor: #555;
 .content .edit-template.edit-php {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
       color: $textColor;
     }
     
     .search-toolbar-name,
     .link {
-      width: fit-content;
+      width: max-content;
 
       a {
         text-decoration: none;

+ 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>

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

@@ -1,7 +1,7 @@
 .content .edit-template.edit-pgsql {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
   }
 

+ 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 - 1
web/js/react/src/components/Server/Edit/Service/Service.scss

@@ -1,7 +1,7 @@
 .content .edit-template.edit-service {
   .toolbar {
     .search-toolbar-name {
-      width: fit-content;
+      width: max-content;
     }
   }
 

+ 1 - 7
web/js/react/src/components/Server/Server.jsx

@@ -10,12 +10,6 @@ const Server = props => {
   const { data } = props;
   const { i18n } = useSelector(state => state.session);
 
-  const printTime = seconds => {
-    let hours = seconds / 60;
-    let days = Math.floor(hours / 24);
-    return days;
-  }
-
   const checkItem = () => {
     props.checkItem(data.NAME);
   }
@@ -41,7 +35,7 @@ const Server = props => {
             <div><span>{i18n.Memory}: <span className="stat">{data.MEM} {i18n.mb}</span></span></div>
           </Container>
           <Container className="c-3">
-            <div><span>{i18n.Uptime}: <span className="stat">{printTime(data.RTIME)} {i18n.days}</span></span></div>
+            <div><span>{i18n.Uptime}: <span className="stat">{data.RTIME}</span></span></div>
           </Container>
           <Container className="c-1" />
         </div>

+ 1 - 8
web/js/react/src/components/Server/ServerSys.jsx

@@ -9,13 +9,6 @@ import { useSelector } from 'react-redux';
 const Server = props => {
   const { data } = props;
   const { i18n } = useSelector(state => state.session);
-  const token = localStorage.getItem("token");
-
-  const printTime = seconds => {
-    let hours = seconds / 60;
-    let days = Math.floor(hours / 24);
-    return days;
-  }
 
   const checkItem = () => {
     props.checkItem(props.data.HOSTNAME);
@@ -39,7 +32,7 @@ const Server = props => {
             <div>{i18n['Load Average']}: <span><span className="stat">{data.LOADAVERAGE}</span></span></div>
           </Container>
           <Container className="c-3">
-            <div><span>{i18n.Uptime}: <span className="stat">{printTime(data.UPTIME)} {i18n.days}</span></span></div>
+            <div><span>{i18n.Uptime}: <span className="stat">{data.UPTIME}</span></span></div>
           </Container>
         </div>
       </Container>

+ 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}>

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

@@ -19,7 +19,7 @@ $textColor: #555;
         }
 
         &:nth-child(2) {
-          width: fit-content;
+          width: max-content;
         }
       }
     }

+ 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;

+ 53 - 29
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,12 @@ 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());
           }
         })
         .catch(err => console.error(err));
@@ -291,7 +294,7 @@ const Backups = props => {
   }
 
   const displayModal = (text, url) => {
-    setState({ ...state, loading: false });
+    setLoading(false);
     setModal({
       ...modal,
       visible: true,
@@ -301,13 +304,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 +335,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 +369,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} />} />

+ 48 - 26
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,19 @@ 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());
           }
         })
         .catch(err => console.error(err));
@@ -340,15 +349,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 +403,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}

+ 41 - 27
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,12 @@ 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());
           }
         })
         .catch(err => console.error(err));
@@ -302,12 +303,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 +348,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%;

+ 41 - 26
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,12 @@ 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());
           }
         })
         .catch(err => console.error(err));
@@ -345,13 +348,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 +396,7 @@ const Databases = props => {
         </div>
       </Toolbar>
       <div className="mails-wrapper">
-        {state.loading
+        {loading
           ? <Spinner />
           : (<>
             {databases()}

Некоторые файлы не были показаны из-за большого количества измененных файлов