+
+ {userName === 'admin' && (<>
+
+ handleState("/list/package/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Packages}
+
+
+ handleState("/list/ip/", event)} onKeyPress={event => event.preventDefault()}>{i18n.IP}
+
+
+ handleState("/list/rrd/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Graphs}
+
+ >)}
+
+ handleState("/list/stats/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Statistics}
+
+
+ handleState("/list/log/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Log}
+
+ {userName === 'admin' && (<>
+
+ handleState("/list/updates/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Updates}
+
+ {session.FIREWALL_SYSTEM &&
+ handleState("/list/firewall/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Firewall}
+
}
+ >)}
+ {session.FILEMANAGER_KEY &&
+ {i18n['File Manager']}
+
}
+ {session.SOFTACULOUS === "yes" &&
{i18n.Apps ?? 'Apps'}
+
}
+ {userName === 'admin' && (
+
+ handleState("/list/server/", event)} onKeyPress={event => event.preventDefault()}>{i18n.Server}
+
+ )}
+
+
+
+
+
+ );
+}
+
+export default MobileTopNav;
diff --git a/src/react/src/components/MainNav/Mobile/MobileTopNav.scss b/src/react/src/components/MainNav/Mobile/MobileTopNav.scss
new file mode 100644
index 000000000..a84541eb8
--- /dev/null
+++ b/src/react/src/components/MainNav/Mobile/MobileTopNav.scss
@@ -0,0 +1,156 @@
+@import 'src/utils/scss/variables';
+@import 'src/utils/scss/breakpoints';
+
+.mobile-top-nav-wrapper.hide {
+ opacity: 0;
+}
+
+.mobile-top-nav-wrapper.show {
+ z-index: 5;
+ opacity: 1;
+ position: fixed;
+ width: 100%;
+ height: 111px;
+ background: white;
+ display: flex;
+ flex-direction: column;
+ animation: showMobileNav forwards .3s;
+ margin-top: 82px;
+
+ .mobile-menu {
+ padding: 0 10%;
+ flex-wrap: wrap;
+ margin-top: 15px;
+ }
+
+ > div .top-link {
+ display: flex;
+ font-size: 14px;
+ padding: 4px 0;
+
+ &:hover {
+ background: $secondaryLight;
+
+ a, button {
+ color: $white;
+ }
+ }
+ }
+
+ div {
+ display: flex;
+
+ a, button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0 10px !important;
+ width: fit-content;
+ height: 100%;
+ text-decoration: none;
+ color: $black;
+ }
+
+ button {
+ background: none;
+ border: none;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ div.active {
+ background: white;
+
+ a, button {
+ color: $secondary;
+ font-weight: bold;
+
+ &:hover {
+ color: white;
+ }
+
+ &:active {
+ background: $secondaryActive;
+ color: white;
+ }
+ }
+
+ &:hover {
+ color: white;
+ background: $secondaryLight;
+ }
+ }
+
+ .toolbar {
+ margin: 0;
+ position: relative;
+ border: none;
+ }
+
+
+ .mobile-stat-menu {
+ box-shadow: 0 5px 3px 0 hsla(0,0%,78.4%,.5);
+
+ .menu-wrapper {
+ position: relative;
+ height: auto;
+ min-height: auto;
+ padding: 0;
+ margin-top: 9px;
+
+ .menu-stat {
+ margin: 0;
+
+ .stat {
+ text-align: left;
+ flex: 1 1 auto;
+ margin-bottom: 15px;
+ }
+ }
+ }
+ }
+}
+
+@media (max-width: 1066px) {
+ .mobile-stat-menu .menu-wrapper .menu-stat {
+ margin-top: 40px;
+
+ .stat {
+ .stats {
+ display: none;
+ }
+ }
+ }
+}
+
+@media (max-width: 900px) {
+ .mobile-toolbar .toolbar {
+ padding: 0 1%;
+ }
+}
+
+@media (max-width: 725px) {
+ .mobile-top-nav-wrapper.show {
+ .menu-stat {
+ display: flex;
+ flex-wrap: wrap;
+ }
+ }
+
+ .mobile-menu {
+ padding: 0 10%;
+ }
+}
+
+@keyframes showMobileNav {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
diff --git a/src/react/src/components/MainNav/Panel/Notifications/Bell.jsx b/src/react/src/components/MainNav/Panel/Notifications/Bell.jsx
new file mode 100644
index 000000000..2f3b773a6
--- /dev/null
+++ b/src/react/src/components/MainNav/Panel/Notifications/Bell.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+
+export default function Bell(props) {
+ return (
+ replace(files)} files={files} />
+ case 'Nothing selected': return ;
+ default:
+ break;
+ }
+ }
+
+ return (
+
+ );
+}
+
+export default Modal;
\ No newline at end of file
diff --git a/src/react/src/components/Modal/Modal.scss b/src/react/src/components/Modal/Modal.scss
new file mode 100644
index 000000000..14737b5d0
--- /dev/null
+++ b/src/react/src/components/Modal/Modal.scss
@@ -0,0 +1,207 @@
+@import 'src/utils/scss/variables';
+
+.modal {
+ display: block;
+ text-align: center;
+ position: fixed;
+ z-index: 1;
+ padding-top: 200px;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: $black;
+ background-color: rgba(0, 0, 0, 0.4);
+
+ .modal-content {
+ box-shadow: 0 2px 11px 0 rgba(0, 0, 0, 0.5);
+ background: #222e44;
+ border: 1px solid #111824;
+ margin: auto;
+ width: 25%;
+ height: auto;
+ color: white;
+ margin-top: 100px;
+
+ .modal-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ overflow-wrap: anywhere;
+ font-size: 17px;
+ margin-bottom: 40px;
+
+ small {
+ margin-top: 5px;
+ font-size: 13px;
+
+ &.error {
+ color: red;
+ }
+ }
+
+ input {
+ background: #333;
+ border: 1px solid #111;
+ color: white;
+ margin-top: 10px;
+ width: 85%;
+ }
+ }
+
+ .modal-header {
+ border-bottom: none;
+ padding-bottom: 0;
+ word-break: break-word;
+
+ h3 {
+ font-size: 20px;
+ color: $secondaryLight;
+ }
+
+ .replace {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ }
+
+ .quot {
+ color: $secondaryActive;
+ }
+ }
+
+ .modal-footer {
+ border-top: 1px solid #555;
+
+ button {
+ color: white;
+ border-color: none;
+ background: none;
+ text-transform: uppercase;
+ font-size: 11px;
+
+ &:focus {
+ outline: none;
+ box-shadow: none;
+ }
+ }
+
+ button.btn-danger {
+ border-color: transparent;
+
+ &:focus {
+ outline: none;
+ border: none;
+ }
+ }
+
+ button.btn-danger:hover {
+ background: $danger;
+ border-color: $danger;
+ }
+
+ button + button {
+ padding: 6px 30px;
+ background: $secondary;
+ border-color: $secondary;
+
+ &:hover {
+ background: $primaryLight;
+ border-color: $primaryLight;
+ color: $hoverButtonText;
+ }
+
+ &:hover {
+ background: $primaryActive;
+ border-color: $primaryActive;
+ color: $activeButtonText;
+ }
+ }
+ }
+
+ .header .quot {
+ color: $secondaryActive;
+ }
+
+ .close {
+ top: 0;
+ right: 0;
+ color: $black;
+ }
+ }
+
+ .nothing-selected {
+ h3 {
+ padding: 15px;
+ }
+
+ .modal-footer {
+ margin-top: 20px;
+
+ button {
+ background: #d9534f;
+ border-color: #d9534f;
+ color: white;
+ }
+ }
+ }
+
+ .delete .modal-header {
+ border-bottom: none;
+ padding-bottom: 30px;
+
+ .close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ }
+ }
+
+ .permissions {
+ height: auto;
+ width: 30%;
+ margin-top: 0;
+
+ .error,
+ &:focus {
+ border: 1px solid red;
+ }
+
+ input[type='text'] {
+ size: 40px;
+ width: 60px;
+ margin: auto auto 10px auto;
+ display: inline-block;
+ }
+
+ .header h3 {
+ margin-bottom: 40px;
+ font-size: 30px;
+ }
+
+ form {
+ margin-top: 20px;
+
+ div {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ margin-bottom: 30px;
+ margin-left: 40px;
+
+ label {
+ display: inherit;
+ cursor: pointer;
+ font-size: 18px;
+
+ input {
+ margin-top: 6px;
+ margin-right: 5px;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/react/src/components/Modal/Move.jsx b/src/react/src/components/Modal/Move.jsx
new file mode 100644
index 000000000..a879eee75
--- /dev/null
+++ b/src/react/src/components/Modal/Move.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+const Move = (props) => {
+ const { i18n } = useSelector(state => state.session);
+
+ return (
+
+
+ {props.items > 0 ?
+
{i18n['Move files']} ({props.items}) {i18n['into']}: :
+ {i18n['Move']} "{props.fName}" {i18n['into']}: }
+
+
+
+
+
+ {i18n['Cancel']}
+ {i18n['Move']}
+
+
+ );
+}
+
+export default Move;
\ No newline at end of file
diff --git a/src/react/src/components/Modal/NothingSelected.jsx b/src/react/src/components/Modal/NothingSelected.jsx
new file mode 100644
index 000000000..2a55cfae7
--- /dev/null
+++ b/src/react/src/components/Modal/NothingSelected.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+const NothingSelected = (props) => {
+ const { i18n } = useSelector(state => state.session);
+
+ return (
+
+
+ {props.notAvailable ?
{i18n['Directory download not available in current version']} : {i18n['No file selected']} }
+
+
+ {i18n['Close']}
+
+
+ );
+}
+
+export default NothingSelected;
\ No newline at end of file
diff --git a/src/react/src/components/Modal/Permissions.jsx b/src/react/src/components/Modal/Permissions.jsx
new file mode 100644
index 000000000..719877beb
--- /dev/null
+++ b/src/react/src/components/Modal/Permissions.jsx
@@ -0,0 +1,151 @@
+import React, { Component } from 'react';
+import classNames from 'classname';
+import { connect } from 'react-redux';
+
+const defaultPermissions = {
+ owner: {
+ read: 0,
+ write: 0,
+ execute: 0,
+ },
+ group: {
+ read: 0,
+ write: 0,
+ execute: 0,
+ },
+ others: {
+ read: 0,
+ write: 0,
+ execute: 0,
+ },
+}
+
+class Permissions extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ permissions: this.decode(this.props.permissions) || defaultPermissions,
+ inputInvalid: false,
+ }
+ }
+
+ inArray(number, array) {
+ return !!~array.indexOf(number);
+ }
+
+ decodeSingleNumber = (string) => {
+ const number = parseInt(string, 0);
+
+ return {
+ read: this.inArray(number, [4, 5, 6, 7]) ? 4 : 0,
+ write: this.inArray(number, [2, 3, 6, 7]) ? 2 : 0,
+ execute: this.inArray(number, [1, 3, 5, 7]) ? 1 : 0
+ };
+ }
+
+ isValid(numbers = '') {
+ if (numbers.length !== 3 || numbers === '000' || numbers.match(/[A-Za-z]/)) {
+ return false;
+ }
+
+ return numbers.split('').find((number) => parseInt(number, 0) < 0 || parseInt(number, 0) > 7) === undefined;
+ }
+
+ decode(numbers) {
+ if (!this.isValid(numbers)) {
+ return null;
+ }
+
+ const numbersArray = numbers.split('');
+ const result = numbersArray.map(this.decodeSingleNumber);
+ return { owner: result[0], group: result[1], others: result[2] };
+ }
+
+ encode() {
+ function sumPermissions(permissionObject) {
+ return Object.values(permissionObject).map((number) => parseInt(number, 0)).reduce((acc, n) => acc + n, 0);
+ }
+ return ['owner', 'group', 'others'].reduce((acc, role) => {
+ const roleObject = this.state.permissions[role];
+ return acc + sumPermissions(roleObject);
+ }, '');
+ }
+
+ onChangeForm = (event) => {
+ const checkbox = event.target;
+ const [role, permissionName] = checkbox.name.split('_');
+ this.setState({
+ permissions: {
+ ...this.state.permissions,
+ [role]: {
+ ...this.state.permissions[role],
+ [permissionName]: checkbox.checked ? checkbox.value : 0,
+ }
+ }
+ }, (state) => {
+ this.inputRef.value = this.encode();
+ this.props.changePermissions(this.inputRef.value);
+ });
+ }
+
+ handleInputChange = (event) => {
+ const value = event.target.value;
+ if (!this.isValid(value)) {
+ return this.setState({ inputInvalid: true });
+ }
+
+ this.setState({
+ permissions: this.decode(value),
+ inputInvalid: false,
+ });
+ this.props.changePermissions(this.inputRef.value);
+ }
+
+ render() {
+ const { i18n } = this.props.session;
+ const { inputInvalid } = this.state;
+ const { close, save, fName } = this.props;
+ const inputClasses = classNames({
+ 'form-control total': true,
+ 'error': inputInvalid,
+ });
+
+ return (
+
+
+
{i18n['Change Rights']} "{fName}"
+
+
+
this.inputRef = ref} onChange={this.handleInputChange} maxLength="3" />
+
+ {i18n['Cancel']}
+ {i18n['OK']}
+
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ session: state.session
+ }
+}
+
+export default connect(mapStateToProps)(Permissions);
diff --git a/src/react/src/components/Modal/Rename.jsx b/src/react/src/components/Modal/Rename.jsx
new file mode 100644
index 000000000..1f6a92b7e
--- /dev/null
+++ b/src/react/src/components/Modal/Rename.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+const Rename = (props) => {
+ const { i18n } = useSelector(state => state.session);
+
+ return (
+
+
+
{i18n['Rename']} "{props.fName}"
+
+
+
+
+
+ {i18n['Cancel']}
+ {i18n['Rename']}
+
+
+ );
+}
+
+export default Rename;
\ No newline at end of file
diff --git a/src/react/src/components/Modal/Replace.jsx b/src/react/src/components/Modal/Replace.jsx
new file mode 100644
index 000000000..f75de46ef
--- /dev/null
+++ b/src/react/src/components/Modal/Replace.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+const Replace = (props) => {
+ return (
+
+
+ {props.files.length > 1 ?
+
These files already exist
+ {props.files.map(item =>
+ "{item.name}"
+ )}
+ :
+
This file already exists
+ "{props.files[0].name}"
+
+ }
+
+
+ Cancel
+ props.replace(props.files)}>Overwrite
+
+
+ );
+}
+
+export default Replace;
\ No newline at end of file
diff --git a/src/react/src/components/Package/Add/AddPackage.jsx b/src/react/src/components/Package/Add/AddPackage.jsx
new file mode 100644
index 000000000..56c17b21c
--- /dev/null
+++ b/src/react/src/components/Package/Add/AddPackage.jsx
@@ -0,0 +1,263 @@
+import React, { useEffect, useState } from 'react';
+
+import TextInputWithExtraButton from '../../ControlPanel/AddItemLayout/Form/TextInputWithExtraButton/TextInputWithExtraButton';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import { getAdditionalPackageInfo, addPackage } from '../../../ControlPanelService/Package';
+import NameServers from '../../ControlPanel/AddItemLayout/Form/NameServers/NameServers';
+import SelectInput from '../../ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+import Spinner from '../../Spinner/Spinner';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const dispatch = useDispatch();
+ const history = useHistory();
+ const [state, setState] = useState({
+ loading: false,
+ okMessage: '',
+ errorMessage: '',
+ webTemplates: [],
+ webSystem: '',
+ backendTemplates: [],
+ backendSystem: '',
+ proxySystem: '',
+ proxyTemplates: [],
+ dnsTemplates: [],
+ dnsSystem: '',
+ sshTemplates: [],
+ usersNS: [],
+ webDomains: '1',
+ webAliases: '1',
+ dnsDomains: '1',
+ dnsRecords: '1',
+ mailDomains: '1',
+ mailAccounts: '1',
+ databases: '1',
+ cronJobs: '1',
+ quota: '1000',
+ bandwidth: '1000',
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/package/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getAdditionalPackageInfo()
+ .then(result => {
+ setState({
+ ...state,
+ webTemplates: result.data.web_templates,
+ webSystem: result.data.web_system,
+ backendTemplates: result.data.backend_templates,
+ backendSystem: result.data.web_backend,
+ dnsTemplates: result.data.dns_templates,
+ dnsSystem: result.data.dns_system,
+ proxySystem: result.data.proxy_system,
+ proxyTemplates: result.data.proxy_templates,
+ sshTemplates: result.data.ssh_access,
+ loading: false
+ });
+ })
+ .catch(err => console.err(err));
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let newPackage = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ newPackage[name] = value;
+ }
+
+ newPackage['token'] = token;
+ newPackage['ok'] = 'Add';
+
+ if (Object.keys(newPackage).length !== 0 && newPackage.constructor === Object) {
+ setState({ ...state, loading: true });
+ addPackage(newPackage)
+ .then(result => {
+ if (result.status === 200) {
+ 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));
+ }
+ }
+
+ const toggleUnlimited = inputName => {
+ let inputNameToUpdate = state[inputName];
+ let defaultValue;
+
+ if (inputName === 'quota' || inputName === 'bandwidth') {
+ defaultValue = '1000';
+ } else {
+ defaultValue = '1';
+ }
+
+ if (inputNameToUpdate !== 'unlimited') {
+ setState({ ...state, [inputName]: 'unlimited' });
+ } else {
+ setState({ ...state, [inputName]: defaultValue });
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.PACKAGE}`}
+
+
+
+ {i18n['Adding Package']}
+
+
+ {state.errorMessage ? : ''}
+ {state.errorMessage}
+
+
+
+ {state.okMessage ? : ''}
+ {HtmlParser(state.okMessage)}
+
+
+
+
+ {state.loading ? : (
+
+ )}
+
+
+ );
+}
+
+export default AddPackage;
\ No newline at end of file
diff --git a/src/react/src/components/Package/Add/AddPackage.scss b/src/react/src/components/Package/Add/AddPackage.scss
new file mode 100644
index 000000000..b91b760d2
--- /dev/null
+++ b/src/react/src/components/Package/Add/AddPackage.scss
@@ -0,0 +1,35 @@
+.edit-template.add-package {
+ form {
+ label.label-wrapper {
+ display: flex;
+ align-items: flex-end;
+ width: fit-content;
+
+ span {
+ font-weight: normal;
+ text-transform: uppercase;
+ margin-left: 5px;
+ font-size: 12px;
+ }
+
+ span.lowercase {
+ text-transform: lowercase;
+ }
+ }
+
+ .input-wrapper {
+ display: flex;
+ align-items: center;
+
+ svg {
+ cursor: pointer;
+ color: #bec4ca;
+ margin-left: 10px;
+
+ &:hover {
+ color: #abb1b6;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Package/Edit/EditPackage.jsx b/src/react/src/components/Package/Edit/EditPackage.jsx
new file mode 100644
index 000000000..967ee4f64
--- /dev/null
+++ b/src/react/src/components/Package/Edit/EditPackage.jsx
@@ -0,0 +1,317 @@
+import React, { useEffect, useState } from 'react';
+
+import TextInputWithExtraButton from '../../ControlPanel/AddItemLayout/Form/TextInputWithExtraButton/TextInputWithExtraButton';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import SelectInput from '../../ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import NameServers from '../../ControlPanel/AddItemLayout/Form/NameServers/NameServers';
+import { getPackageInfo, updatePackage } from '../../../ControlPanelService/Package';
+import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../components/Spinner/Spinner';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+import QS from 'qs';
+
+import './EditPackage.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
+
+const EditPackage = props => {
+ const token = localStorage.getItem("token");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false
+ });
+
+ useEffect(() => {
+ let queryParams = QS.parse(history.location.search, { ignoreQueryPrefix: true });
+
+ dispatch(addActiveElement('/list/package/'));
+ dispatch(removeFocusedElement());
+
+ if (queryParams.package) {
+ setState({ ...state, loading: true });
+ fetchData(queryParams.package);
+ }
+ }, []);
+
+ const fetchData = pkg => {
+ getPackageInfo(pkg)
+ .then(response => {
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedPackage = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedPackage[name] = value;
+ }
+
+ updatedPackage['token'] = token;
+ updatedPackage['save'] = 'save';
+ updatedPackage['v_package'] = state.data.package;
+
+ if (Object.keys(updatedPackage).length !== 0 && updatedPackage.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updatePackage(updatedPackage, state.data.package)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ if (error_msg) {
+ setErrorMessage(error_msg);
+ setOkMessage('');
+ } else {
+ dispatch(refreshCounters()).then(() => {
+ setErrorMessage('');
+ setOkMessage(ok_msg);
+ });
+ }
+ }
+ })
+ .then(() => fetchData(state.data.package))
+ .catch(err => console.error(err));
+ }
+ }
+
+ const toggleUnlimited = inputName => {
+ let inputNameToUpdate = state.data[inputName];
+ let defaultValue;
+
+ if (inputName === 'quota' || inputName === 'bandwidth') {
+ defaultValue = '1000';
+ } else {
+ defaultValue = '1';
+ }
+
+ setState({
+ ...state,
+ data: {
+ ...state.data,
+ [inputName]: inputNameToUpdate !== 'unlimited' ? 'unlimited' : defaultValue
+ }
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.PACKAGE}`}
+
+
+
+ {i18n['Editing Package']}
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+
+ }
+
+
+ );
+}
+
+export default EditPackage;
\ No newline at end of file
diff --git a/src/react/src/components/Package/Edit/EditPackage.scss b/src/react/src/components/Package/Edit/EditPackage.scss
new file mode 100644
index 000000000..330d8b738
--- /dev/null
+++ b/src/react/src/components/Package/Edit/EditPackage.scss
@@ -0,0 +1,14 @@
+.edit-package {
+ form {
+ .form-group {
+ .input-wrapper {
+ display: flex;
+ align-items: center;
+
+ button {
+ transform: translateX(5px);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Package/Package.jsx b/src/react/src/components/Package/Package.jsx
new file mode 100644
index 000000000..f5dd9ae6c
--- /dev/null
+++ b/src/react/src/components/Package/Package.jsx
@@ -0,0 +1,95 @@
+import React, { } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import './Package.scss';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const Package = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+
+ const printNameServers = servers => {
+ let serversArray = servers.split(',');
+
+ return serversArray.map(
+ (server, index) => {server}
+ );
+ }
+
+ const toggleFav = (starred) => {
+ if (starred) {
+ props.toggleFav(data.NAME, 'add');
+ } else {
+ props.toggleFav(data.NAME, 'delete');
+ }
+ }
+
+ const checkItem = () => {
+ props.checkItem(data.NAME);
+ }
+
+ const handleDelete = () => {
+ props.handleModal(data.delete_conf, `/api/v1/delete/package/index.php?package=${data.NAME}`);
+ }
+
+ return (
+
+
+
+ {data.NAME}
+ {data.FNAME} {data.LNAME}
+
+
+ {i18n['Web Template']}: {data.WEB_TEMPLATE}
+ {session.PROXY_SYSTEM && {i18n['Proxy Template']}: {data.PROXY_TEMPLATE}
}
+ {i18n['DNS Template']}: {data.DNS_TEMPLATE}
+ {i18n['SSH Access']}: {data.SHELL}
+ {i18n['Web Domains']}: {data.WEB_DOMAINS}
+ {i18n['Web Aliases']}: {data.WEB_ALIASES}
+
+
+ {i18n['DNS domains']}: {data.DNS_DOMAINS}
+ {i18n['DNS records']}: {data.DNS_RECORDS}
+ {i18n['Mail Domains']}: {data.MAIL_DOMAINS}
+ {i18n['Mail Accounts']}: {data.MAIL_ACCOUNTS}
+ {i18n.Databases}: {data.DATABASES}
+ {i18n['Cron Jobs']}: {data.CRON_JOBS}
+
+
+ {i18n.Backups}: {data.BACKUPS}
+ {i18n.Bandwidth}: {data.BANDWIDTH} {i18n.mb}
+ {i18n.Disk}: {data.DISK_QUOTA} {i18n.mb}
+ {i18n['Name Servers']}: {printNameServers(data.NS)}
+
+
+
+
+
+
+ {i18n.edit}
+ {data.FOCUSED ? ↩ : }
+
+
+
+
+ handleDelete()}>
+ {i18n.Delete}
+ {data.FOCUSED ? Del : }
+
+
+
+
+ );
+}
+
+export default Package;
\ No newline at end of file
diff --git a/src/react/src/components/Package/Package.scss b/src/react/src/components/Package/Package.scss
new file mode 100644
index 000000000..f22af3015
--- /dev/null
+++ b/src/react/src/components/Package/Package.scss
@@ -0,0 +1,21 @@
+.packages {
+ .r-col {
+ .stats {
+ .c-2 {
+ padding-left: 2rem;
+
+ div > span {
+ width: 50%;
+ }
+ }
+
+ .c-3 {
+ margin-left: 0;
+
+ div.ns {
+ margin-top: 5px;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Path/Dropdown/Dropdown.jsx b/src/react/src/components/Path/Dropdown/Dropdown.jsx
new file mode 100644
index 000000000..495d9f1f9
--- /dev/null
+++ b/src/react/src/components/Path/Dropdown/Dropdown.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import './Dropdown.scss';
+
+const Dropdown = (props) => {
+ const { i18n } = useSelector(state => state.session);
+
+ const changeSorting = (field, order, props) => {
+ if (!props.isActive) {
+ return;
+ } else {
+ props.changeSorting(field, order);
+ }
+ }
+
+ const sort = (sorting) => {
+ if (sorting === "Type") {
+ return i18n.type;
+ } else if (sorting === "Size") {
+ return i18n.size;
+ } else if (sorting === "Date") {
+ return i18n.date;
+ } else if (sorting === "Name") {
+ return i18n.name;
+ }
+ }
+
+ const button = (sorting, order) => {
+ if (order === "descending") {
+ return (
+
+ {sort(sorting)}
+ ↓
+
+ );
+ } else {
+ return (
+
+ {sort(sorting)}
+ ↑
+
+ );
+ }
+ }
+
+ return (
+
+ {button(props.sorting, props.order)}
+
+
+ );
+}
+
+export default Dropdown;
\ No newline at end of file
diff --git a/src/react/src/components/Path/Dropdown/Dropdown.scss b/src/react/src/components/Path/Dropdown/Dropdown.scss
new file mode 100644
index 000000000..44d338e28
--- /dev/null
+++ b/src/react/src/components/Path/Dropdown/Dropdown.scss
@@ -0,0 +1,51 @@
+.btn-group button {
+ border: 0;
+}
+
+.dropdown-menu {
+ padding: 0;
+
+ ul {
+ padding: 0;
+ margin: 0;
+ display: inline-block;
+ list-style: none;
+ height: 150px;
+ width: 100%;
+ color: #333;
+
+ li {
+ display: inline-flex;
+ width: 100%;
+ height: 37.5px;
+ border-top: 1px solid #dbdbdb;
+
+ .arrow-down {
+ float: right;
+ }
+
+ .dropdown-item {
+ &:hover {
+ background: #bdbdbd;
+ }
+ }
+
+ span {
+ padding: 0 5px 0 10px;
+ line-height: 38px;
+ }
+
+ span.active{
+ background: #ffc900;
+ }
+
+ span + span {
+ text-align: center;
+ }
+ }
+ }
+}
+
+.lists-container .dropdown-menu.show{
+ transform: translate3d(-57%, 39px, 0px)!important;
+}
\ No newline at end of file
diff --git a/src/react/src/components/Path/Path.jsx b/src/react/src/components/Path/Path.jsx
new file mode 100644
index 000000000..9bd9ef06e
--- /dev/null
+++ b/src/react/src/components/Path/Path.jsx
@@ -0,0 +1,54 @@
+import React, { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router';
+
+import Dropdown from './Dropdown/Dropdown';
+import './Path.scss';
+
+const Path = ({ path, isActive, className, openDirectory, changeSorting, sorting, order }) => {
+ const { user } = useSelector(state => state.menuCounters);
+ const history = useHistory();
+
+ useEffect(() => {
+ if (!user) return history.push('/login');
+ }, [user]);
+
+ const clickablePath = () => {
+ let splitPath = path.split('/');
+ splitPath.splice(0, 3);
+
+ if (path !== user.HOME) {
+ return (
+ splitPath.map((item, index) => openDirectoryHandler(index)}> / {item} )
+ );
+ }
+ }
+
+ const openDirectoryHandler = index => {
+ let pathArray = path.split('/');
+
+ if (!isActive) {
+ return;
+ } else {
+ if (index !== undefined) {
+ let newPathArray = pathArray.splice(0, index + 4);
+ let newPath = newPathArray.join('/');
+ openDirectory(newPath);
+ }
+ }
+ }
+
+ return (
+
+
+
+ openDirectory(user.HOME)}>{user.HOME}
+ {clickablePath()}
+
+
+
+
+ );
+}
+
+export default Path;
diff --git a/src/react/src/components/Path/Path.scss b/src/react/src/components/Path/Path.scss
new file mode 100644
index 000000000..efc3ae550
--- /dev/null
+++ b/src/react/src/components/Path/Path.scss
@@ -0,0 +1,155 @@
+@import 'src/utils/scss/variables';
+
+.path {
+ display: flex;
+ justify-content: space-between;
+ font-size: 15px;
+ padding: 1px 0 0 5px;
+ height: 40px;
+ background: #222e44;
+ color: #999;
+ box-shadow: 0 2px 10px -4px #222e44;
+
+ .btn-group {
+ width: 75px;
+ margin-left: 15px;
+ background: none;
+ box-shadow: none;
+
+ .btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show>.btn-secondary.dropdown-toggle {
+ background: #222e44;
+ border: #222e44;
+ }
+
+ button {
+ padding-left: 0;
+ padding-right: 5px;
+ color: #999;
+ background: #222e44;
+ border: #222e44;
+ transition: none;
+
+ span {
+ color: #999;
+ padding: 0 0 0 5px;
+ }
+
+ &:active, &:focus {
+ background: #222e44;
+ border: #222e44;
+ }
+ }
+
+ .btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
+ box-shadow: none;
+ outline: none;
+ background: #222e44;
+ border: #222e44;
+ }
+
+ .btn-secondary {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ }
+
+ .clickable {
+ padding-top: 8px;
+ cursor: pointer;
+ }
+}
+
+.active-path {
+ display: flex;
+ justify-content: space-between;
+ font-size: 15px;
+ padding: 1px 0 0 5px;
+ height: 40px;
+ background: #d7dcef;
+ color: $white;
+ box-shadow: 0 2px 6px -2px #d7dcef;
+
+ .clickable-wrapper {
+ display: flex;
+ flex-wrap: nowrap;
+ overflow: auto;
+ }
+
+ .btn-group {
+ width: 75px;
+ background: none;
+ box-shadow: none;
+
+ .btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show>.btn-secondary.dropdown-toggle {
+ border-color: #d7dcef;
+ background: #d7dcef;
+ }
+
+ button {
+ padding-left: 0;
+ padding-right: 5px;
+ color: white;
+ border-color: #d7dcef;
+ background: #d7dcef;
+ transition: none;
+
+ span {
+ color: #fff;
+ padding: 0 0 0 5px;
+ }
+
+ &:active, &:focus {
+ border-color: #d7dcef;
+ background: #d7dcef;
+ }
+ }
+
+ .btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
+ box-shadow: none;
+ outline: none;
+ border-color: #d7dcef;
+ background: #d7dcef;
+ }
+
+ .btn-secondary {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: $primary;
+
+ span {
+ color: $primary;
+ }
+
+ &:hover {
+ color: #1c3876;
+ border-color: #d7dcef;
+ background: #d7dcef;
+
+ span {
+ color: #1c3876;
+ }
+ }
+ }
+ }
+
+ .clickable {
+ color: $primary;
+ padding-top: 8px;
+ cursor: pointer;
+
+ &:hover {
+ color: #1c3876;
+ }
+
+ &:active {
+ color: $primary;
+ }
+ }
+}
+
+.clickable-path {
+ display: flex;
+ float: 0 0 auto;
+}
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Editor/Editor.jsx b/src/react/src/components/Preview/Editor/Editor.jsx
new file mode 100644
index 000000000..165397a60
--- /dev/null
+++ b/src/react/src/components/Preview/Editor/Editor.jsx
@@ -0,0 +1,133 @@
+import React, { useEffect, useState } from 'react';
+import CodeMirror from 'react-codemirror';
+import './Editor.scss';
+import 'codemirror/lib/codemirror.css';
+import 'codemirror/mode/javascript/javascript';
+import 'codemirror/mode/markdown/markdown';
+import 'codemirror/mode/php/php';
+import 'codemirror/mode/css/css';
+import 'codemirror/mode/htmlmixed/htmlmixed';
+import axios from 'axios';
+import Spinner from '../../Spinner/Spinner';
+import { useHistory } from 'react-router-dom';
+import { toast, ToastContainer } from 'react-toastify';
+import { useSelector } from 'react-redux';
+
+const Editor = ({ close, name }) => {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const [state, setState] = useState({
+ code: '',
+ loading: false
+ });
+
+ useEffect(() => {
+ document.addEventListener("keydown", hotKey);
+
+ let path = `${history.location.search.substring(6, history.location.search.lastIndexOf('/'))}/${name}`;
+ setState({ ...state, loading: true });
+
+ checkFileType(path)
+ .then(res => {
+ if (res.data.result) {
+ axios.get(`${window.location.origin}/api/v1/edit/file/?path=${encodePath(path)}`)
+ .then(result => {
+ if (result.data.error) {
+ return showToast(res.data.error);
+ }
+
+ setState({ ...state, code: result.data.content, loading: false });
+ })
+ .catch(err => console.error(err));
+ } else {
+ console.error('Something went wrong with file type!');
+ }
+ })
+ .catch(err => console.error(err));
+
+ return () => {
+ document.removeEventListener("keydown", hotKey);
+ }
+ }, []);
+
+ const checkFileType = path => {
+ return axios.get(`${window.location.origin}/file_manager/fm_api.php?dir=${path}&action=check_file_type`);
+ }
+
+ const encodePath = path => {
+ return path.split('/').join('%2F');
+ }
+
+ const hotKey = e => {
+ if (e.keyCode === 113) {
+ save();
+ }
+ }
+
+ const save = () => {
+ let formData = new FormData();
+ let path = history.location.search.substring(6, history.location.search.lastIndexOf('/'));
+
+ formData.append('save', 'Save');
+ formData.append('contents', state.code);
+
+ setState({ ...state, loading: true });
+ axios.post(`${window.location.origin}/api/v1/edit/file/?path=${path}%2F${name}`, formData)
+ .then(res => {
+ if (res.data.error) {
+ showToast(res.data.error);
+ } else {
+ showToast('Saved successfully!');
+ }
+ setState({ ...state, loading: false });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const showToast = text => {
+ toast.success(text, {
+ position: "top-center",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true
+ });
+ }
+
+ const updateCode = newCode => {
+ setState({ ...state, code: newCode });
+ }
+
+ const getModeFromFileName = () => {
+ const fileExtension = name.split('.').pop();
+
+ switch (fileExtension) {
+ case 'js': return 'javascript';
+ case 'jsx': return 'javascript';
+ case 'php': return 'php';
+ case 'css': return 'css';
+ case 'scss': return 'css';
+ case 'html': return 'htmlmixed';
+ default: return 'markdown';
+ }
+ }
+
+ let options = {
+ mode: getModeFromFileName(),
+ lineNumbers: true
+ };
+
+ return (
+
+
+
+ {i18n.Save}
+ {i18n.Close}
+
+ {state.loading ?
:
}
+
+ );
+}
+
+export default Editor;
diff --git a/src/react/src/components/Preview/Editor/Editor.scss b/src/react/src/components/Preview/Editor/Editor.scss
new file mode 100644
index 000000000..3bb218d12
--- /dev/null
+++ b/src/react/src/components/Preview/Editor/Editor.scss
@@ -0,0 +1,64 @@
+.editor {
+ width: 100%;
+
+ .panel-editor {
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+ height: 40px;
+ position: absolute;
+ padding: 3px;
+ margin: 0;
+ background: #000000bf;
+ z-index: 4;
+ transition: 0.5s all ease-in-out;
+ border-bottom: 1px solid black;
+ border-radius: 0;
+
+ button {
+ font-size: 17px;
+ height: 98%;
+ margin: 0 0 0 30px;
+ padding: 0 10px;
+
+ &:focus {
+ outline: none;
+ box-shadow: none;
+ }
+ }
+ }
+
+ .close {
+ z-index: 4;
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ font-size: 40px;
+ opacity: 1;
+ color: #000;
+
+ &:hover {
+ color: rgb(139, 139, 139);
+ }
+ }
+
+ .CodeMirror {
+ height: 100vh;
+ padding-top: 4vh;
+
+ .CodeMirror-selected {
+ background:#ACCEF7;
+ }
+
+ .CodeMirror-scroll {
+ font-size: 14px;
+ }
+ }
+
+ .save {
+ font-size: 20px;
+ left: 95%;
+ bottom: 10px;
+ position: absolute;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Photo/Photo.jsx b/src/react/src/components/Preview/Photo/Photo.jsx
new file mode 100644
index 000000000..e16568cd3
--- /dev/null
+++ b/src/react/src/components/Preview/Photo/Photo.jsx
@@ -0,0 +1,105 @@
+import React, { Component } from 'react';
+import classNames from 'classname';
+import * as FM from '../../../FileManagerHelper';
+import './Photo.scss';
+import Spinner from '../../Spinner/Spinner';
+
+class Photo extends Component {
+ state = {
+ activeSlide: 0,
+ photoGallery: [],
+ loading: false
+ }
+
+ imgClass = (item) => {
+ if (item.match(/.gif/i)) {
+ return "gif";
+ } else {
+ return "img";
+ }
+ }
+
+ encodePath = (path) => {
+ let splitPath = path.split('/');
+ splitPath.splice(splitPath.length - 1, 1);
+ splitPath.splice(0, 1);
+ return splitPath.join('%2F');
+ }
+
+ formatPath = (path) => {
+ let splitPath = path.split('/');
+ splitPath.splice(splitPath.length - 1, 1);
+ return splitPath.join('/');
+ }
+
+ carouselIndicators = () => {
+ const gallery = this.state.photoGallery;
+ return gallery.map((item, i) => {
+ const imageClasses = classNames({ 'control-photo': true, 'active': i === this.state.activeSlide });
+ const result = (
+
+
);
+ return result;
+ });
+ }
+
+ carouselPhotos = () => {
+ const gallery = this.state.photoGallery || [];
+ return gallery.map((item, i) => (
+
+
+
+
+
+ ));
+ }
+
+ setStateAsync = updater => new Promise(resolve => this.setState(updater, resolve));
+
+ setPhotoGallery = async () => {
+ await this.setStateAsync({ loading: true });
+ const result = await FM.getData(this.encodePath(this.props.path));
+ let photoGallery = [...this.state.photoGallery];
+ result.data.listing.filter(item => item.name.match(/.png|.jpg|.jpeg|.gif/g) && !item.name.match(/.zip|.tgz|.tar.gz|.gzip|.tbz|.tar.bz|.gz|.zip|.tar|.rar/g) ? photoGallery.push(item.name) : null)
+ await this.setStateAsync({ photoGallery, loading: false })
+ this.setActiveImage();
+ }
+
+ setActiveImage = () => {
+ let activeImage = this.props.activeImage;
+ let activeImageIndex = this.state.photoGallery.indexOf(activeImage);
+ this.setState({ activeSlide: activeImageIndex });
+ }
+
+ componentDidMount() {
+ this.setPhotoGallery();
+ }
+
+ render() {
+ return (
+
+ {this.state.loading ?
:
+
+ }
+
+ );
+ }
+}
+
+export default Photo;
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Photo/Photo.scss b/src/react/src/components/Preview/Photo/Photo.scss
new file mode 100644
index 000000000..c2c1ad5a0
--- /dev/null
+++ b/src/react/src/components/Preview/Photo/Photo.scss
@@ -0,0 +1,65 @@
+.carousel {
+ width: 100%;
+ position: relative;
+
+ a {
+ width: 5%;
+ }
+
+ .carousel-inner {
+ .carousel-item {
+ background: #000;
+ width: 100%;
+ height: 100vh;
+
+ .gif {
+ height: 50%;
+ width: 50%;
+ }
+
+ .img {
+ width: 90%;
+ }
+ }
+ }
+
+ .carousel-indicators {
+ .indicator {
+ margin: 0 15px;
+ width: 80px;
+ height: 80px;
+
+ .control-photo {
+ width: 100px;
+ height: 80px;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.active > img {
+ border: 3px solid #3cc3f0;
+ }
+
+ .active {
+ border: 3px solid #3cc3f0;
+ }
+ }
+ }
+}
+
+span.close {
+ z-index: 10;
+ font-size: 30px;
+ position: absolute;
+ top: 5px;
+ right: 10px;
+ opacity: 1;
+ color: white;
+ cursor: pointer;
+
+ &:hover {
+ color: white;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Preview.jsx b/src/react/src/components/Preview/Preview.jsx
new file mode 100644
index 000000000..9bb8e5458
--- /dev/null
+++ b/src/react/src/components/Preview/Preview.jsx
@@ -0,0 +1,60 @@
+import React, { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router';
+import Editor from './Editor/Editor';
+import Photo from './Photo/Photo';
+import Video from './Video/Video';
+
+const Preview = (props) => {
+ const {userName} = useSelector(state => state.session);
+ const history = useHistory();
+
+ useEffect(() => {
+ if (!userName) history.push('/login');
+
+ document.addEventListener("keydown", hotkeys);
+
+ return () => {
+ document.removeEventListener("keydown", hotkeys);
+ }
+ }, []);
+
+ const hotkeys = e => {
+ if (e.keyCode === 121) {
+ onClose();
+ }
+ }
+
+ const onClose = () => {
+ let lastOpenedDirectory = history.location.search.substring(6, history.location.search.lastIndexOf('/'));
+ history.push({
+ pathname: '/list/directory',
+ search: `?path=${lastOpenedDirectory}`
+ })
+ }
+
+ const content = () => {
+ let split = history.location.search.split('/');
+ let name = split[split.length - 1];
+
+ if (history.location.pathname !== '/list/directory/preview/') {
+ return;
+ }
+
+ if (name.match('.mp4')) {
+ return ;
+ } else if (name.match(/png|jpg|jpeg|gif/g)) {
+ return ;
+ } else {
+ return ;
+ }
+ }
+
+ return (
+
+ {content()}
+
+ );
+}
+
+export default Preview;
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Video/Video.jsx b/src/react/src/components/Preview/Video/Video.jsx
new file mode 100644
index 000000000..d253e7873
--- /dev/null
+++ b/src/react/src/components/Preview/Video/Video.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+// import video from '../../../2.mp4';
+import './Video.scss';
+
+const Video = (props) => {
+ return (
+
+ ×
+
+
+
+
+ );
+}
+
+export default Video;
\ No newline at end of file
diff --git a/src/react/src/components/Preview/Video/Video.scss b/src/react/src/components/Preview/Video/Video.scss
new file mode 100644
index 000000000..83594f2e3
--- /dev/null
+++ b/src/react/src/components/Preview/Video/Video.scss
@@ -0,0 +1,28 @@
+.video-preview {
+ padding-bottom: 17px;
+ width: 100%;
+ background: #000;
+
+ .close {
+ z-index: 1;
+ font-size: 30px;
+ position: absolute;
+ top: 5px;
+ right: 2px;
+ opacity: 1;
+ color: #fff;
+
+ &:hover {
+ color: #000;
+ }
+ }
+
+ .video {
+ width: 100%;
+ height: auto;
+
+ &:focus {
+ outline: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/ProgressBar/ProgressBar.jsx b/src/react/src/components/ProgressBar/ProgressBar.jsx
new file mode 100644
index 000000000..a8f7f72b2
--- /dev/null
+++ b/src/react/src/components/ProgressBar/ProgressBar.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import './ProgressBar.scss';
+
+const ProgressBar = (props) => {
+ return (
+
+ );
+}
+
+export default ProgressBar;
diff --git a/src/react/src/components/ProgressBar/ProgressBar.scss b/src/react/src/components/ProgressBar/ProgressBar.scss
new file mode 100644
index 000000000..13ca1dd86
--- /dev/null
+++ b/src/react/src/components/ProgressBar/ProgressBar.scss
@@ -0,0 +1,16 @@
+.progress.upload {
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ z-index: 9999;
+ height: 5px;
+ position: fixed;
+ width: 100%;
+ margin: 0;
+ display: inline-table;
+ background: transparent;
+
+ .progress-bar {
+ height: 5px;
+ }
+}
diff --git a/src/react/src/components/RRD/RRD.jsx b/src/react/src/components/RRD/RRD.jsx
new file mode 100644
index 000000000..60bb9a6a2
--- /dev/null
+++ b/src/react/src/components/RRD/RRD.jsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faFileDownload } from '@fortawesome/free-solid-svg-icons';
+import Container from '../ControlPanel/Container/Container';
+import { generateImagePath } from '../../ControlPanelService/RRD';
+import './RRD.scss';
+import { useSelector } from 'react-redux';
+
+const RRD = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const printDate = date => {
+ let newDate = new Date(date);
+ let day = newDate.getDate();
+ let month = newDate.getMonth();
+ let year = newDate.getFullYear();
+ let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+ return {day} {months[month]} {year}
;
+ }
+
+ return (
+
+
+ {printDate(data.DATE)}
+
+
+ {i18n[data.NAME]}
+
+
+
+
+ );
+}
+
+export default RRD;
\ No newline at end of file
diff --git a/src/react/src/components/RRD/RRD.scss b/src/react/src/components/RRD/RRD.scss
new file mode 100644
index 000000000..f4678701b
--- /dev/null
+++ b/src/react/src/components/RRD/RRD.scss
@@ -0,0 +1,43 @@
+.rrd-item {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-bottom: 1px solid #eee;
+ position: relative;
+
+ .l-col {
+ .date {
+ font-size: 13px;
+ margin-top: 3rem;
+ }
+
+ .time {
+ font-size: 13px;
+ margin-top: 2rem;
+ text-align: left;
+ }
+ }
+
+ .r-col {
+ .name {
+ font-size: 32px;
+ }
+
+ .rrd-image {
+ font-size: 20px;
+ text-transform: lowercase;
+ }
+ }
+}
+
+.rrd-item.focused {
+ border-left: 2px solid #5edad0;
+
+ .l-col {
+ padding-left: 11px;
+ }
+
+ div.actions {
+ opacity: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/RRD/Timer/Timer.jsx b/src/react/src/components/RRD/Timer/Timer.jsx
new file mode 100644
index 000000000..0172f69b4
--- /dev/null
+++ b/src/react/src/components/RRD/Timer/Timer.jsx
@@ -0,0 +1,39 @@
+import React, { useEffect, useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import './Timer.scss';
+
+const Timer = props => {
+ const [isActive, setIsActive] = useState(true);
+
+ useEffect(() => {
+ let interval = null;
+
+ if (isActive) {
+ interval = setInterval(props.countDown, 1000);
+ } else if (!isActive && props.time !== 0) {
+ clearInterval(interval);
+ }
+
+ return () => clearInterval(interval);
+ }, [isActive, props.time]);
+
+ const handlePause = () => {
+ setIsActive(!isActive);
+ }
+
+ return (
+
+
handlePause()}>
+ {!isActive ? : }
+
+
+ {/*
+
+ */}
+ {props.time}
+
+
+ )
+}
+
+export default Timer;
\ No newline at end of file
diff --git a/src/react/src/components/RRD/Timer/Timer.scss b/src/react/src/components/RRD/Timer/Timer.scss
new file mode 100644
index 000000000..6291834ec
--- /dev/null
+++ b/src/react/src/components/RRD/Timer/Timer.scss
@@ -0,0 +1,45 @@
+.timer-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ button {
+ border: none;
+ background: none;
+
+ &:active, &:focus {
+ outline: none;
+ }
+ }
+
+ .circle-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 30px;
+ height: 30px;
+
+ span.seconds {
+ &:hover, &:active {
+ background: unset;
+ }
+ }
+
+ svg {
+ height: 30px;
+ width: 30px;
+
+ circle {
+ stroke: black;
+ fill: white;
+ stroke-width: 3%;
+ stroke-dasharray: 72;
+ stroke-dashoffset: 9;
+ transform-box: fill-box;
+ transform-origin: center;
+ transform: rotate(270deg);
+ transition: all 1s linear;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Searchitem/SearchItem.jsx b/src/react/src/components/Searchitem/SearchItem.jsx
new file mode 100644
index 000000000..671a26264
--- /dev/null
+++ b/src/react/src/components/Searchitem/SearchItem.jsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import { loginAs, logout } from 'src/actions/Session/sessionActions';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import { useDispatch, useSelector } from 'react-redux';
+import { Link, useHistory } from 'react-router-dom';
+import './SearchItem.scss';
+
+const SearchItem = ({ data, handleModal }) => {
+ 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 (
+
+ {i18n['Log out']}
+ {data.FOCUSED ? L : }
+
+
+ );
+ } else {
+ return (
+
+ signInAs(data.USER)}>{i18n['login as']} {data.USER}
+ {data.FOCUSED ? L : }
+
+
+ );
+ }
+ }
+
+ return (
+
+
+ {data.RESULT}
+
+
+ {data.TYPE === 'user' ? i18n['USER'] : i18n[data.object]}
+
+
+ {i18n.Owner}: {data.USER}
+
+
+ {i18n.Status}: {data.status}
+
+
+
+
+ {printLoginActionButton()}
+
{i18n.edit}
+
+
+ {data.spnd_action}
+
+
+
+
+
+ {i18n.Delete}
+
+
+
+
+
+ );
+}
+
+export default SearchItem;
diff --git a/src/react/src/components/Searchitem/SearchItem.scss b/src/react/src/components/Searchitem/SearchItem.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/components/Server/Edit/Bind9/Bind9.jsx b/src/react/src/components/Server/Edit/Bind9/Bind9.jsx
new file mode 100644
index 000000000..adfe16c78
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Bind9/Bind9.jsx
@@ -0,0 +1,140 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('bind9')
+ .then(response => {
+ if (response.data.config.includes('Error')) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, data: response.data, loading: false });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedService, '/bind9')
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+
+ }
+
+
+ );
+}
+
+export default Bind9;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Bind9/Bind9.scss b/src/react/src/components/Server/Edit/Bind9/Bind9.scss
new file mode 100644
index 000000000..612d1a073
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Bind9/Bind9.scss
@@ -0,0 +1,15 @@
+.content .edit-template.edit-bind9 {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Dovecot/Dovecot.jsx b/src/react/src/components/Server/Edit/Dovecot/Dovecot.jsx
new file mode 100644
index 000000000..353c705e8
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Dovecot/Dovecot.jsx
@@ -0,0 +1,207 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('dovecot')
+ .then(response => {
+ if (!response.data.config) {
+ history.push('/list/server');
+ }
+
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedService, '/dovecot')
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-dovecot">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default Dovecot;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Dovecot/Dovecot.scss b/src/react/src/components/Server/Edit/Dovecot/Dovecot.scss
new file mode 100644
index 000000000..612d1a073
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Dovecot/Dovecot.scss
@@ -0,0 +1,15 @@
+.content .edit-template.edit-bind9 {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditBackupOption.jsx b/src/react/src/components/Server/Edit/EditBackupOption.jsx
new file mode 100644
index 000000000..948785603
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditBackupOption.jsx
@@ -0,0 +1,81 @@
+import React, { useState } from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { useSelector } from 'react-redux';
+
+const EditBackupOption = ({ data, visible }) => {
+ const { i18n } = useSelector(state => state.session);
+ const [remoteBackup, setRemoteBackup] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+
setRemoteBackup(!remoteBackup)}>
+ {i18n['Remote backup']}
+ {remoteBackup ? : }
+
+
+ {
+ remoteBackup && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ );
+}
+
+export default EditBackupOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditDatabaseOption.jsx b/src/react/src/components/Server/Edit/EditDatabaseOption.jsx
new file mode 100644
index 000000000..447770c68
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditDatabaseOption.jsx
@@ -0,0 +1,124 @@
+import React from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const EditDatabaseOption = ({ data, visible }) => {
+ const { DB_PMA_URL, DB_PGA_URL } = useSelector(state => state.userSession.session);
+ const { i18n } = useSelector(state => state.session);
+
+ const printPhpMyAdminHosts = () => {
+ if (data.mysql_hosts.length) {
+ return data.mysql_hosts.map((host, index) => (
+
+
+
+
+
+
+
+
+
+ ));
+ }
+ }
+
+ const printPgSqlHosts = () => {
+ return data.pgsql_hosts.map((host, index) => (
+
+
+
+
+
+
+
+ ));
+ }
+
+ return (
+
+ {i18n['configure']}}
+ title={i18n['MySQL Support'] + ' / '}
+ selected={data.mysql}
+ options={[i18n['no'], i18n['yes']]}
+ name="v_mysql"
+ id="mysql"
+ disabled />
+
+ {
+ data.mysql === 'yes' && (
+ <>
+
+
+ {printPhpMyAdminHosts()}
+ >
+ )
+ }
+
+ {i18n['configure']}}
+ title={i18n['PostgreSQL Support'] + ' / '}
+ selected={data.pgsql}
+ options={[i18n['no'], i18n['yes']]}
+ name="v_pgsql"
+ id="pgsql"
+ disabled />
+
+ {
+ data.pgsql === 'yes' && (
+ <>
+
+
+ {printPgSqlHosts()}
+ >
+ )
+ }
+
+ );
+}
+
+export default EditDatabaseOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditMailOption.jsx b/src/react/src/components/Server/Edit/EditMailOption.jsx
new file mode 100644
index 000000000..fb5a2b260
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditMailOption.jsx
@@ -0,0 +1,155 @@
+import React, { useEffect, useState } from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import Checkbox from 'src/components/ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const EditMailOption = ({ data, visible }) => {
+ const { MAIL_URL } = useSelector(state => state.userSession.session);
+ const { i18n } = useSelector(state => state.session);
+ const [mailCertificateSystem, setMailCertificateSystem] = useState(false);
+
+ useEffect(() => {
+ if (data.mail_certificate) {
+ setMailCertificateSystem(true);
+ }
+ }, []);
+
+ const getMailCertificateOptions = () => {
+ let result = ['', ...data.ssl_domains];
+ return result;
+ }
+
+ return (
+
+
{i18n['configure']}}
+ title={i18n['MAIL Server'] + ' / '}
+ name="v_mail_system"
+ value={data.mail_system}
+ id="mail_system"
+ disabled />
+
+ {
+ data.antivirus_system && (
+ {i18n['configure']}}
+ title={i18n['Antivirus'] + ' / '}
+ name="v_antivirus_system"
+ value={data.antivirus_system}
+ id="antivirus_system"
+ disabled />
+ )
+ }
+
+ {
+ data.antispam_system && (
+ {i18n['configure']}}
+ title={i18n['DNS Server'] + ' / '}
+ name="v_antispam_system"
+ value={data.antispam_system}
+ id="antispam_system"
+ disabled />
+ )
+ }
+
+
+
+
+
+ setMailCertificateSystem(checked)}
+ title={i18n['Use Web Domain SSL Certificate']}
+ name="v_mail_ssl_domain_checkbox"
+ id="mail-ssl-domain-checkbox"
+ checked={!!data.mail_certificate} />
+
+ {
+ mailCertificateSystem && (
+
+
+
+
+ {
+ data.sys_ssl_subject && (
+
+ {i18n['SUBJECT']}:
+ {data.sys_ssl_subject}
+
+ )
+ }
+
+ {
+ data.sys_ssl_aliases && (
+
+ {i18n['ALIASES']}:
+ {data.sys_ssl_aliases}
+
+ )
+ }
+
+ {
+ data.sys_ssl_not_before && (
+
+ {i18n['NOT_BEFORE']}:
+ {data.sys_ssl_not_before}
+
+ )
+ }
+
+ {
+ data.sys_ssl_not_after && (
+
+ {i18n['NOT_AFTER']}:
+ {data.sys_ssl_not_after}
+
+ )
+ }
+
+ {
+ data.sys_ssl_signature && (
+
+ {i18n['SIGNATURE']}:
+ {data.sys_ssl_signature}
+
+ )
+ }
+
+ {
+ data.sys_ssl_pub_key && (
+
+ {i18n['PUB_KEY']}:
+ {data.sys_ssl_pub_key}
+
+ )
+ }
+
+ {
+ data.sys_ssl_issuer && (
+
+ {i18n['ISSUER']}:
+ {data.sys_ssl_issuer}
+
+ )
+ }
+
+
+ )
+ }
+
+ );
+}
+
+export default EditMailOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditServer.jsx b/src/react/src/components/Server/Edit/EditServer.jsx
new file mode 100644
index 000000000..23ea1cac1
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditServer.jsx
@@ -0,0 +1,242 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import { getServerAdditionalInfo, updateService } from '../../../ControlPanelService/Server';
+import SelectInput from '../../ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../components/Spinner/Spinner';
+import EditServerWebOption from './EditServerWebOption';
+import EditServerDnsOption from './EditServerDnsOption';
+import EditVestaPluginsOption from './EditVestaPlugins';
+import EditVestaSslOption from './EditVestaSslOption';
+import EditDatabaseOption from './EditDatabaseOption';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import EditBackupOption from './EditBackupOption';
+import EditMailOption from './EditMailOption';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+
+import './EditServer.scss';
+import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
+import { refreshUserSession } from 'src/actions/Session/sessionActions';
+
+const EditServer = props => {
+ const token = localStorage.getItem("token");
+ const { i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ webOption: false,
+ dnsOption: false,
+ mailOption: false,
+ backupOption: false,
+ sslOption: false,
+ pluginsOption: false,
+ dbOption: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServerAdditionalInfo()
+ .then(response => {
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedServer = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedServer[name] = value;
+ }
+
+ if (updatedServer['v_backup_type']) {
+ updatedServer['v_backup_type'] = updatedServer['v_backup_type'].toLowerCase();
+ }
+
+ updatedServer['save'] = 'save';
+ updatedServer['token'] = token;
+
+ if (updatedServer['v_softaculous'] === 'no' && !session['SOFTACULOUS']) {
+ delete updatedServer['v_softaculous'];
+ }
+
+ if (Object.keys(updatedServer).length !== 0 && updatedServer.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedServer)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ if (error_msg) {
+ setErrorMessage(error_msg);
+ setOkMessage('');
+ } else {
+ setErrorMessage('');
+ setOkMessage(ok_msg);
+ }
+ }
+ })
+ .then(() => dispatch(refreshUserSession()).then(() => fetchData()))
+ .catch(err => console.error(err));
+ }
+ }
+
+ const toggleOption = option => {
+ setState({
+ ...state,
+ [option]: !state[option]
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {i18n['Configuring Server']}
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-server">
+
+
+ {
+ state.data.timezones && (
+
+
+ {i18n['Time Zone']}
+
+
+ {
+ Object.keys(state.data.timezones).map(key => {
+ const value = state.data.timezones[key];
+
+ return {value} ;
+ })
+ }
+
+
+ )
+ }
+
+
+
+
+ toggleOption('webOption')}>
+ {i18n['WEB']}
+ {state.webOption ? : }
+
+
+
+
+ toggleOption('dnsOption')}>
+ {i18n['DNS']}
+ {state.dnsOption ? : }
+
+
+
+
+ toggleOption('mailOption')}>
+ {i18n['MAIL']}
+ {state.mailOption ? : }
+
+
+
+
+ toggleOption('dbOption')}>
+ {i18n['DB']}
+ {state.dbOption ? : }
+
+
+
+
+ toggleOption('backupOption')}>
+ {i18n['BACKUP']}
+ {state.backupOption ? : }
+
+
+
+
+ toggleOption('sslOption')}>
+ {i18n['Vesta SSL']}
+ {state.sslOption ? : }
+
+
+
+
+ toggleOption('pluginsOption')}>
+ {i18n['Vesta Control Panel Plugins']}
+ {state.pluginsOption ? : }
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default EditServer;
diff --git a/src/react/src/components/Server/Edit/EditServer.scss b/src/react/src/components/Server/Edit/EditServer.scss
new file mode 100644
index 000000000..43a776ddf
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditServer.scss
@@ -0,0 +1,156 @@
+@import 'src/utils/scss/variables';
+
+.edit-server {
+ .modules {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+
+ > div {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin: 5px 0;
+ }
+
+ > button {
+ margin: 15px 0;
+
+ svg {
+ margin-left: 10px;
+ }
+ }
+
+ .form-group {
+ a {
+ text-decoration: none;
+ color: $primary;
+ font-weight: bold;
+ font-size: 14px;
+
+ &:hover {
+ color: $primaryLight;
+ }
+ }
+ }
+
+ .additional-info {
+ margin-top: 10px;
+ transform: translateX(3rem);
+
+ > div {
+ display: flex;
+ margin: 5px 0;
+
+ span {
+ font-size: 12px;
+ color: #555;
+ }
+
+ span:nth-child(1) {
+ width: 180px;
+ }
+ }
+ }
+
+ .server-web-option,
+ .server-dns-option,
+ .server-ssl-option,
+ .server-mail-option {
+ transform: translateX(3rem);
+ }
+
+ .server-mail-option {
+ transform: translateX(3rem);
+
+ .mail-cert-info {
+ transform: translateX(3rem);
+ }
+ }
+
+ .server-mail-option {
+ .hosts {
+ transform: translateX(3rem);
+ }
+ }
+
+ .server-ssl-option {
+ transform: translateX(3rem);
+
+ .domain-group {
+ transform: translateX(3rem);
+ }
+ }
+
+ .server-plugins-option {
+ transform: translateX(3rem);
+
+ .sftp-module,
+ .soft-module,
+ .fm-module {
+ transform: translateX(3rem);
+ }
+
+ .sftp-module,
+ .fm-module {
+ font-size: 15px;
+ color: $textColor;
+ }
+
+ .buy-license {
+ display: flex;
+ margin: 5px 0;
+
+ a {
+ color: #FFF;
+ background: $primary;
+ border: none;
+ border-radius: 3px;
+ font-size: 13px;
+ font-weight: bold;
+ padding: 7px 15px;
+ text-transform: capitalize;
+ text-decoration: none;
+ width: fit-content;
+ margin-right: 10px;
+
+ &:hover {
+ color: $hoverButtonText;
+ background: $primaryLight;
+ }
+
+ &:hover {
+ color: $activeButtonText;
+ background: $primaryActive;
+ }
+ }
+
+ + span {
+ display: block;
+ margin-top: 15px;
+ font-style: italic;
+ font-size: 12px;
+ }
+ }
+
+ .license-description {
+ display: flex;
+ align-items: center;
+ margin: 1rem 0;
+
+ > span {
+ color: $primary;
+ }
+
+ .form-group {
+ margin: 0 0 0 20px;
+ width: 60%;
+
+ label {
+ display: none;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditServerDnsOption.jsx b/src/react/src/components/Server/Edit/EditServerDnsOption.jsx
new file mode 100644
index 000000000..e45213264
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditServerDnsOption.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const EditServerDnsOption = ({ dnsSystem, selected, dnsCluster, visible }) => {
+ const { i18n } = useSelector(state => state.session);
+
+ const printHosts = () => {
+ return Object.keys(dnsCluster).map((cluster, index) => (
+
+ ));
+ }
+
+ return (
+
+
{i18n['configure']}}
+ title={i18n['DNS Server'] + ' / '}
+ name="v_dns_system"
+ value={dnsSystem}
+ id="dns_system"
+ disabled />
+
+
+
+ {
+ selected === 'yes' && (
+
+ {printHosts()}
+
+ )
+ }
+
+ );
+}
+
+export default EditServerDnsOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditServerWebOption.jsx b/src/react/src/components/Server/Edit/EditServerWebOption.jsx
new file mode 100644
index 000000000..81a255c5e
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditServerWebOption.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const EditServerWebOption = ({ proxySystem, webSystem, webBackend, webBackendPool, visible = false }) => {
+ const { i18n } = useSelector(state => state.session);
+
+ return (
+
+
+ {
+ proxySystem && (
+ {i18n['configure']}}
+ title={i18n['Proxy Server'] + ' / '}
+ name="v_proxy_system"
+ value={proxySystem}
+ id="proxy_system"
+ disabled />
+ )
+ }
+
+ {
+ webSystem && (
+ {i18n['configure']}}
+ title={i18n['Web Server'] + ' / '}
+ name="v_web_system"
+ value={webSystem}
+ id="web_system"
+ disabled />
+ )
+ }
+
+ {
+ webBackend && (
+
+ )
+ }
+
+ {
+ webBackendPool && (
+ {i18n['configure']}}
+ title={i18n['Backend Pool Mode']}
+ name="v_web_backend_pool"
+ value={webBackendPool}
+ id="web_backend_pool"
+ disabled
+ />
+ )
+ }
+
+
+ );
+}
+
+export default EditServerWebOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/EditVestaPlugins.jsx b/src/react/src/components/Server/Edit/EditVestaPlugins.jsx
new file mode 100644
index 000000000..6b77b5f13
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditVestaPlugins.jsx
@@ -0,0 +1,224 @@
+import React, { useState } from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { useSelector } from 'react-redux';
+
+const EditVestaPluginsOption = ({ data, visible }) => {
+ const { i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+ const [sftpValue, setSftpValue] = useState(data.lead || session['SFTPJAIL_KEY'] ? 'yes' : 'no');
+ const [fmValue, setFmValue] = useState(data.fm_lead || session['FILEMANAGER_KEY'] ? 'yes' : 'no');
+ const [softaculousValue, setSoftaculousValue] = useState(session['SOFTACULOUS'] === 'yes' ? 'yes' : 'no');
+
+ const renderSoftaculous = () => {
+ if (softaculousValue === 'yes') {
+ if (session['SOFTACULOUS'] === 'yes') {
+ return (
+
+ {i18n['* plugin installation will run in background']}
+
+ Softaculous is a great Auto Installer having 426 great scripts, 1115 PHP Classes
+ and we are still adding more. Softaculous is ideal for Web Hosting companies and
+ it could give a significant boost to your sales. These scripts cover most of the
+ uses a customer could ever have. We have covered a wide array of Categories so that
+ everyone could find the required script one would need to power their Web Site.
+
+
+
+
+
);
+ }
+ }
+ }
+
+ const renderSftp = () => {
+ if (sftpValue === 'yes') {
+ if (!data.sftp_license_key && session['SFTPJAIL_KEY']) {
+ return (
+
{i18n['Restrict users so that they cannot use SSH and access only their home directory.']}
+
+ {i18n['Licence Key']}:
+
+
+
)
+ } else {
+ return (
+ <>
+
{i18n['Restrict users so that they cannot use SSH and access only their home directory.']}
+
{i18n['This is a commercial module, you would need to purchace license key to enable it.']}
+ >
+
+ {i18n['Enter License Key']}:
+
+
+
+
+
+
2Checkout.com Inc. (Ohio, USA) is a payment facilitator for goods and services provided by vestacp.com.
+
)
+ }
+ }
+ }
+
+ const renderFm = () => {
+ if (fmValue === 'yes') {
+ if (!data.fm_license_key && session['FILEMANAGER_KEY']) {
+ return (
+
{i18n['Browse, copy, edit, view, and retrieve all of your web domain files using fully featured File Manager.']}
+
+ {i18n['Licence Key']}:
+
+
+
);
+ } else {
+ return (
+ <>
+
{i18n['Browse, copy, edit, view, and retrieve all of your web domain files using fully featured File Manager.']}
+
{i18n['This is a commercial module, you would need to purchace license key to enable it.']}
+ >
+
+ {i18n['Enter License Key']}:
+
+
+
+
+
+
2Checkout.com Inc. (Ohio, USA) is a payment facilitator for goods and services provided by vestacp.com.
+
)
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+ {i18n['FileSystem Disk Quota']}
+
+
+ {i18n['no']}
+ {i18n['yes']}
+
+
+
+
+
+
+
+ {i18n['Firewall']}
+
+
+ {i18n['no']}
+ {i18n['yes']}
+
+
+
+
+
+
+
+
+
+ {i18n['SFTP Chroot']}
+
+ setSftpValue(event.target.value)}>
+ {
+ session['SFTPJAIL_KEY']
+ ? {i18n['Disable and Cancel Licence']}
+ : {i18n['no']}
+ }
+
+ {i18n['yes']}
+
+
+
+ {renderSftp()}
+
+
+
+
+
+ {i18n['File Manager']}
+
+ setFmValue(event.target.value)}>
+ {
+ session['FILEMANAGER_KEY']
+ ? {i18n['Disable and Cancel Licence']}
+ : {i18n['no']}
+ }
+
+ {i18n['yes']}
+
+
+
+ {renderFm()}
+
+
+
+
+
+ {i18n['Softaculous'] ?? 'Softaculous'}
+
+ setSoftaculousValue(event.target.value)}>
+ {i18n['no']}
+ {i18n['yes']}
+
+
+
+ {renderSoftaculous()}
+
+ );
+}
+
+export default EditVestaPluginsOption;
diff --git a/src/react/src/components/Server/Edit/EditVestaSslOption.jsx b/src/react/src/components/Server/Edit/EditVestaSslOption.jsx
new file mode 100644
index 000000000..844b8aa85
--- /dev/null
+++ b/src/react/src/components/Server/Edit/EditVestaSslOption.jsx
@@ -0,0 +1,129 @@
+import React, { useEffect, useState } from 'react';
+
+import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import Checkbox from 'src/components/ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const EditVestaSslOption = ({ data, visible }) => {
+ const { i18n } = useSelector(state => state.session);
+ const [domainsVisible, setDomainsVisible] = useState(false);
+ const [sslDomains, setSslDomains] = useState([]);
+
+ useEffect(() => {
+ const { ssl_domains } = data;
+
+ if (ssl_domains) {
+ setSslDomains(['', ...ssl_domains]);
+ } else {
+ setSslDomains(['']);
+ }
+
+ }, []);
+
+ return (
+
+
setDomainsVisible(checked)}
+ title={i18n['Use Web Domain SSL Certificate']}
+ defaultChecked={data.vesta_certificate}
+ name="v_web_ssl_domain_checkbox"
+ id="web_ssl_domain_checkbox"
+ checked={domainsVisible}
+ />
+
+ {
+ domainsVisible && (
+
+
+
+ )
+ }
+
+
+
+
+
+
+ {
+ data.sys_ssl_subject && (
+
+ {i18n['SUBJECT']}:
+ {data.sys_ssl_subject}
+
+ )
+ }
+
+ {
+ data.sys_ssl_aliases && (
+
+ {i18n['ALIASES']}:
+ {data.sys_ssl_aliases}
+
+ )
+ }
+
+ {
+ data.sys_ssl_not_before && (
+
+ {i18n['NOT_BEFORE']}:
+ {data.sys_ssl_not_before}
+
+ )
+ }
+
+ {
+ data.sys_ssl_not_after && (
+
+ {i18n['NOT_AFTER']}:
+ {data.sys_ssl_not_after}
+
+ )
+ }
+
+ {
+ data.sys_ssl_signature && (
+
+ {i18n['SIGNATURE']}:
+ {data.sys_ssl_signature}
+
+ )
+ }
+
+ {
+ data.sys_ssl_pub_key && (
+
+ {i18n['PUB_KEY']}:
+ {data.sys_ssl_pub_key}
+
+ )
+ }
+
+ {
+ data.sys_ssl_issuer && (
+
+ {i18n['ISSUER']}:
+ {data.sys_ssl_issuer}
+
+ )
+ }
+
+
+ );
+}
+
+export default EditVestaSslOption;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Httpd/EditHttpd.jsx b/src/react/src/components/Server/Edit/Httpd/EditHttpd.jsx
new file mode 100644
index 000000000..c23aec7f4
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Httpd/EditHttpd.jsx
@@ -0,0 +1,137 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { Link, useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ errorMessage: '',
+ okMessage: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('httpd')
+ .then(response => {
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedService, '/httpd')
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ if (error_msg) {
+ setErrorMessage(error_msg);
+ setOkMessage('');
+ } else {
+ setErrorMessage('');
+ setOkMessage(ok_msg);
+ }
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+ {i18n['Configure']} php.ini
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-httpd">
+
+
+
+
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default EditHttpd;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Httpd/EditHttpd.scss b/src/react/src/components/Server/Edit/Httpd/EditHttpd.scss
new file mode 100644
index 000000000..bba176cc6
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Httpd/EditHttpd.scss
@@ -0,0 +1,48 @@
+$secondary: #fcac04;
+$secondaryLight: #f8b014;
+
+.content .edit-template.edit-httpd {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+
+ a {
+ color: $secondary;
+ text-decoration: none;
+
+ &:hover {
+ color: rgb(119, 119, 119);
+ }
+ }
+ }
+
+ .link {
+ width: fit-content;
+ margin-left: 15px;
+
+ a {
+ text-decoration: none;
+ font-size: 12px;
+ text-transform: uppercase;
+ color: rgb(119, 119, 119);
+ font-weight: 700;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+ }
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+
+ svg {
+ margin-left: 5px;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Mysql/Mysql.jsx b/src/react/src/components/Server/Edit/Mysql/Mysql.jsx
new file mode 100644
index 000000000..df9840b8b
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Mysql/Mysql.jsx
@@ -0,0 +1,226 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import TextInput from '../../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [restart, setRestart] = useState(true);
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ basicOptions: true,
+ advancedOptions: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ if (!serviceName) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('mysql')
+ .then(response => {
+ if (response.data.config.includes('Error')) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, data: response.data, loading: false });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updatedService['v_config'] = state.data.config;
+ updatedService['v_restart'] = restart ? 'yes' : 'no';
+
+ updateService(updatedService, `/${serviceName}`)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ const toggleOptions = () => {
+ setState({
+ ...state,
+ advancedOptions: !state.advancedOptions,
+ basicOptions: !state.basicOptions
+ });
+ }
+
+ const onUpdateConfig = ({ id, value }) => {
+ if (!value) return;
+
+ var regexp = new RegExp(`(${id})(.+)(${state.data[id]})`, 'gm');
+ const updatedConfig = state.data.config.replace(regexp, `$1$2${value}`);
+ setState({ ...state, data: { ...state.data, config: updatedConfig, [id]: value } });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-mysql">
+
+
+
+ {
+ !state.basicOptions && (
+ toggleOptions()}>
+ {i18n['Basic options']}
+ {state.basicOptions ? : }
+
+ )
+ }
+
+ {
+ state.basicOptions && (
+ <>
+ onUpdateConfig(event.target)}
+ value={state.data.max_connections} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.max_user_connections} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.wait_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.interactive_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.max_allowed_packet} />
+ >
+ )
+ }
+
+ {
+ !state.advancedOptions && (
+ toggleOptions()}>
+ {i18n['Advanced options']}
+ {state.advancedOptions ? : }
+
+ )
+ }
+
+
+
+
+ {
+ state.advancedOptions && (
+ <>
+ setState({ ...state, data: { ...state.data, config: e.target.value } })}
+ name="v_config"
+ id="v_config"
+ rows="25" />
+
+
+
+ setRestart(checked)}
+ name="v_restart"
+ id="restart" />
+ >
+ )
+ }
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default Mysql;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Mysql/Mysql.scss b/src/react/src/components/Server/Edit/Mysql/Mysql.scss
new file mode 100644
index 000000000..b53f1479a
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Mysql/Mysql.scss
@@ -0,0 +1,19 @@
+.content .edit-template.edit-mysql {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+
+ svg {
+ margin-left: 5px;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Nginx/EditServerNginx.jsx b/src/react/src/components/Server/Edit/Nginx/EditServerNginx.jsx
new file mode 100644
index 000000000..caf955981
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Nginx/EditServerNginx.jsx
@@ -0,0 +1,263 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import TextInput from '../../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { Link, useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [restart, setRestart] = useState(true);
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ basicOptions: true,
+ advancedOptions: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('nginx')
+ .then(response => {
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err)
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updatedService['v_config'] = state.data.config;
+ updatedService['v_restart'] = restart ? 'yes' : 'no';
+
+ updateService(updatedService, '/nginx')
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ if (error_msg) {
+ setErrorMessage(error_msg);
+ setOkMessage('');
+ } else {
+ setErrorMessage('');
+ setOkMessage(ok_msg);
+ }
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ const toggleOptions = () => {
+ setState({
+ ...state,
+ advancedOptions: !state.advancedOptions,
+ basicOptions: !state.basicOptions
+ });
+ }
+
+ const onUpdateConfig = ({ id, value }) => {
+ if (!value) return;
+
+ var regexp = new RegExp(`(${id})(.+)(${state.data[id]})`, 'gm');
+ const updatedConfig = state.data.config.replace(regexp, `$1$2${value}`);
+ setState({ ...state, data: { ...state.data, config: updatedConfig, [id]: value } });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+ {i18n['Configure']} php.ini
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-mail">
+
+
+
+ {
+ !state.basicOptions && (
+ toggleOptions()}>
+ {i18n['Basic options']}
+ {state.basicOptions ? : }
+
+ )
+ }
+
+ {
+ state.basicOptions && (
+ <>
+ onUpdateConfig(event.target)}
+ value={state.data.worker_processes} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.worker_connections} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.client_max_body_size} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.send_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.proxy_connect_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.proxy_send_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.proxy_read_timeout} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.gzip} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.gzip_comp_level} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.charset} />
+ >
+ )
+ }
+
+ {
+ !state.advancedOptions && (
+ toggleOptions()}>
+ {i18n['Advanced options']}
+ {state.advancedOptions ? : }
+
+ )
+ }
+
+
+
+
+ {
+ state.advancedOptions && (
+ <>
+ setState({ ...state, data: { ...state.data, config: e.target.value } })}
+ name="v_config"
+ id="v_config"
+ rows="25" />
+
+
+
+ setRestart(checked)}
+ name="v_restart"
+ id="restart" />
+ >
+ )
+ }
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default EditServerNginx;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Nginx/EditServerNginx.scss b/src/react/src/components/Server/Edit/Nginx/EditServerNginx.scss
new file mode 100644
index 000000000..56f469d02
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Nginx/EditServerNginx.scss
@@ -0,0 +1,38 @@
+$secondaryLight: #f8b014;
+
+.content .edit-template.edit-nginx {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+
+ .link {
+ width: fit-content;
+ margin-left: 15px;
+
+ a {
+ text-decoration: none;
+ font-size: 12px;
+ text-transform: uppercase;
+ color: rgb(119, 119, 119);
+ font-weight: 700;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+ }
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+
+ svg {
+ margin-left: 5px;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/PHP/EditPhp.jsx b/src/react/src/components/Server/Edit/PHP/EditPhp.jsx
new file mode 100644
index 000000000..7aacae2ce
--- /dev/null
+++ b/src/react/src/components/Server/Edit/PHP/EditPhp.jsx
@@ -0,0 +1,241 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import TextInput from '../../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { Link, useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [restart, setRestart] = useState(true);
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ basicOptions: true,
+ advancedOptions: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ if (!serviceName) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo(serviceName)
+ .then(response => {
+ setState({
+ ...state,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updatedService['v_config'] = state.data.config;
+ updatedService['v_restart'] = restart ? 'yes' : 'no';
+
+ updateService(updatedService, `/${serviceName}`)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ const toggleOptions = () => {
+ setState({
+ ...state,
+ advancedOptions: !state.advancedOptions,
+ basicOptions: !state.basicOptions
+ });
+ }
+
+ const onUpdateConfig = ({ id, value }) => {
+ if (!value) return;
+
+ var regexp = new RegExp(`(${id})(.+)(${state.data[id]})`, 'gm');
+ const updatedConfig = state.data.config.replace(regexp, `$1$2${value}`);
+ setState({ ...state, data: { ...state.data, config: updatedConfig, [id]: value } });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {i18n['Configuring Server']} / {state.data.web_system}
+ {i18n['Configure']} php.ini
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-mail">
+
+
+
+ {
+ !state.basicOptions && (
+ toggleOptions()}>
+ {i18n['Basic options']}
+ {state.basicOptions ? : }
+
+ )
+ }
+
+ {
+ state.basicOptions && (
+ <>
+ onUpdateConfig(event.target)}
+ value={parseInt(state.data.max_execution_time)} />
+
+ onUpdateConfig(event.target)}
+ value={parseInt(state.data.max_input_time)} />
+
+ onUpdateConfig(event.target)}
+ value={parseInt(state.data.memory_limit)} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.error_reporting} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.display_errors} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.post_max_size} />
+
+ onUpdateConfig(event.target)}
+ value={state.data.upload_max_filesize} />
+ >
+ )
+ }
+
+ {
+ !state.advancedOptions && (
+ toggleOptions()}>
+ {i18n['Advanced options']}
+ {state.advancedOptions ? : }
+
+ )
+ }
+
+
+
+
+ {
+ state.advancedOptions && (
+ <>
+ setState({ ...state, data: { ...state.data, config: e.target.value } })}
+ defaultValue={state.data.config}
+ title={state.data.config_path}
+ name="v_config"
+ id="v_config"
+ rows="25" />
+
+
+
+ setRestart(checked)}
+ name="v_restart"
+ id="restart" />
+ >
+ )
+ }
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default EditPhp;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/PHP/EditPhp.scss b/src/react/src/components/Server/Edit/PHP/EditPhp.scss
new file mode 100644
index 000000000..5ff39c738
--- /dev/null
+++ b/src/react/src/components/Server/Edit/PHP/EditPhp.scss
@@ -0,0 +1,53 @@
+$secondary: #fcac04;
+$secondaryLight: #f8b014;
+$textColor: #555;
+
+.content .edit-template.edit-php {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ color: $textColor;
+ }
+
+ .search-toolbar-name,
+ .link {
+ width: fit-content;
+
+ a {
+ text-decoration: none;
+ font-size: 12px;
+ text-transform: uppercase;
+ color: $textColor;
+ font-weight: 700;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+ }
+ }
+
+ .link {
+ margin-left: 15px;
+
+ a {
+ color: $secondary;
+
+ &:hover {
+ color: $textColor;
+ }
+ }
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+
+ svg {
+ margin-left: 5px;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Postgresql/Postgresql.jsx b/src/react/src/components/Server/Edit/Postgresql/Postgresql.jsx
new file mode 100644
index 000000000..730a7ea8b
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Postgresql/Postgresql.jsx
@@ -0,0 +1,140 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ fetchData();
+ }, []);
+
+ const fetchData = () => {
+ getServiceInfo('postgresql')
+ .then(response => {
+ if (!response.data.config) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, data: response.data, loading: false });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedService, '/postgresql')
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData())
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-pgsql">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default Postgresql;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Postgresql/Postgresql.scss b/src/react/src/components/Server/Edit/Postgresql/Postgresql.scss
new file mode 100644
index 000000000..f27f28899
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Postgresql/Postgresql.scss
@@ -0,0 +1,15 @@
+.content .edit-template.edit-pgsql {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Service/Service.jsx b/src/react/src/components/Server/Edit/Service/Service.jsx
new file mode 100644
index 000000000..0382922e1
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Service/Service.jsx
@@ -0,0 +1,134 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../../actions/MainNavigation/mainNavigationActions";
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import AddItemLayout from '../../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { getServiceInfo, updateService } from 'src/ControlPanelService/Server';
+import Spinner from '../../../../components/Spinner/Spinner';
+import Toolbar from '../../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+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");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+
+ if (!serviceName) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, loading: true });
+ fetchData(serviceName);
+ }, []);
+
+ const fetchData = serviceName => {
+ getServiceInfo(serviceName)
+ .then(response => {
+ const { config } = response.data;
+
+ if (!config || config.includes("file doesn't exist")) {
+ history.push('/list/server');
+ }
+
+ setState({ ...state, data: response.data, loading: false });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedService = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedService[name] = value;
+ }
+
+ if (Object.keys(updatedService).length !== 0 && updatedService.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateService(updatedService, serviceName)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ setErrorMessage(error_msg || '');
+ setOkMessage(ok_msg || '');
+ }
+ })
+ .then(() => fetchData(serviceName))
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+
+ {i18n['Configuring Server']} / {state.data.service_name}
+
+
+
+ {errorMessage ? : ''} {errorMessage}
+
+
+
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="edit-service">
+
+
+
+
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/server/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default Service;
\ No newline at end of file
diff --git a/src/react/src/components/Server/Edit/Service/Service.scss b/src/react/src/components/Server/Edit/Service/Service.scss
new file mode 100644
index 000000000..a2ac8de06
--- /dev/null
+++ b/src/react/src/components/Server/Edit/Service/Service.scss
@@ -0,0 +1,15 @@
+.content .edit-template.edit-service {
+ .toolbar {
+ .search-toolbar-name {
+ width: fit-content;
+ }
+ }
+
+ textarea {
+ width: 75%;
+ }
+
+ .checkbox-wrapper label {
+ text-transform: capitalize;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Server/Server.jsx b/src/react/src/components/Server/Server.jsx
new file mode 100644
index 000000000..7717e37b6
--- /dev/null
+++ b/src/react/src/components/Server/Server.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import './Server.scss';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const Server = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const checkItem = () => {
+ props.checkItem(data.NAME);
+ }
+
+ return (
+
+
+
+ {data.NAME}
+
+
+ {i18n[data.SYSTEM]}
+
+
+
+ {i18n.CPU}: {data.CPU}
+
+
+ {i18n.Memory}: {data.MEM} {i18n.mb}
+
+
+ {i18n.Uptime}: {data.RTIME}
+
+
+
+
+
+
+
+
+ {i18n.configure}
+ {data.FOCUSED ? ↩ : }
+
+
+
+
+ props.handleAction('/api/v1' + data.action_url)}>
+ {data.STATE === 'running' ? i18n.stop : i18n.start}
+ {data.FOCUSED ? S : }
+
+
+
+
+
props.handleAction(`/api/v1/restart/service/?srv=${data.NAME}`)}>
+ {i18n.restart}
+ {
+ data.FOCUSED
+ ? R
+ :
+
+
+
+ }
+
+
+
+
+
+ );
+}
+
+export default Server;
diff --git a/src/react/src/components/Server/Server.scss b/src/react/src/components/Server/Server.scss
new file mode 100644
index 000000000..ef58c837a
--- /dev/null
+++ b/src/react/src/components/Server/Server.scss
@@ -0,0 +1,44 @@
+.servers-list .list-item .star {
+ display: none;
+}
+
+.servers-list .r-col .stats .c-2 {
+ padding: 0;
+}
+
+.servers-wrapper .list-item .r-col .stats .c-3 {
+ padding: 0;
+
+ span.stat {
+ margin-left: 0;
+ }
+}
+
+.servers-wrapper .l-col {
+ display: flex;
+ align-items: center;
+ height: 55px;
+
+ .checkbox {
+ margin: 0px;
+ }
+}
+
+.servers-wrapper .r-col {
+ padding-left: 2rem;
+
+ .server-name {
+ color: black;
+ font-size: 28px;
+ }
+}
+
+.servers-wrapper .list-item .r-col {
+ .c-3 > div > span {
+ transform: translateX(-10px);
+ }
+
+ .stats div > span {
+ width: 100%;
+ }
+}
diff --git a/src/react/src/components/Server/ServerSys.jsx b/src/react/src/components/Server/ServerSys.jsx
new file mode 100644
index 000000000..38c573deb
--- /dev/null
+++ b/src/react/src/components/Server/ServerSys.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import './ServerSys.scss';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+
+const Server = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const checkItem = () => {
+ props.checkItem(props.data.HOSTNAME);
+ }
+
+ return (
+
+
+
+ {data.HOSTNAME}
+
+
+ {data.OS} {data.VERSION} {`(${data.ARCH})`}
+
+
+ {i18n['Load Average']}: {data.LOADAVERAGE}
+
+
+ {i18n.Uptime}: {data.UPTIME}
+
+
+
+
+
+
+
+ {i18n.configure}
+ {data.FOCUSED ? ↩ : }
+
+
+
+
+
props.handleAction(`/api/v1/restart/system/?hostname=${data.NAME}`)}>
+ {i18n.restart}
+ {
+ data.FOCUSED
+ ? R
+ :
+
+
+
+ }
+
+
+
+
+ );
+}
+
+export default Server;
\ No newline at end of file
diff --git a/src/react/src/components/Server/ServerSys.scss b/src/react/src/components/Server/ServerSys.scss
new file mode 100644
index 000000000..6e1959156
--- /dev/null
+++ b/src/react/src/components/Server/ServerSys.scss
@@ -0,0 +1,29 @@
+.servers-list {
+ .servers-wrapper .list-item.sys-info {
+ .r-col {
+ padding: 0;
+
+ .name {
+ font-size: 35px;
+ }
+
+ .c-2 span > span.stat {
+ margin-left: 10px;
+ }
+
+ .c-3 {
+ padding-right: 0;
+
+ > div > span {
+ width: 100%;
+ }
+ }
+ }
+ }
+
+ .actions a.link-gray.restart svg,
+ .actions button.link-gray.restart svg {
+ width: 1rem;
+ height: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/Spinner/Spinner.jsx b/src/react/src/components/Spinner/Spinner.jsx
new file mode 100644
index 000000000..0e5f2aa34
--- /dev/null
+++ b/src/react/src/components/Spinner/Spinner.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import './Spinner.scss';
+
+const Spinner = () => {
+ return (
+
+ );
+}
+
+export default Spinner;
\ No newline at end of file
diff --git a/src/react/src/components/Spinner/Spinner.scss b/src/react/src/components/Spinner/Spinner.scss
new file mode 100644
index 000000000..409a61c23
--- /dev/null
+++ b/src/react/src/components/Spinner/Spinner.scss
@@ -0,0 +1,7 @@
+.spinner-wrapper {
+ position: absolute;
+ bottom: 5px;
+ width: 150px;
+ left: 42%;
+ height: 10px;
+}
diff --git a/src/react/src/components/Statistic/Statistic.jsx b/src/react/src/components/Statistic/Statistic.jsx
new file mode 100644
index 000000000..f87387a7e
--- /dev/null
+++ b/src/react/src/components/Statistic/Statistic.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import Container from '../ControlPanel/Container/Container';
+import './Statistic.scss';
+
+const Statistic = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const printDate = date => {
+ let newDate = new Date(date);
+ let day = newDate.getDate();
+ let month = newDate.getMonth();
+ let year = newDate.getFullYear();
+ let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+ return {day} {months[month]} {year}
;
+ }
+
+ const printName = date => {
+ let newDate = new Date(date);
+ let month = newDate.getMonth();
+ let year = newDate.getFullYear();
+ let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+ return {months[month]} {year}
;
+ }
+
+ return (
+
+
+ {printDate(data.DATE)}
+
+
+ {printName(data.DATE)}
+
+
+
+ {i18n.Bandwidth}
+
{data.U_BANDWIDTH} {i18n.mb}
+
+
+
+ {i18n.Disk}:
{data.U_DISK} {i18n.mb}
+
+
+
+
+
{i18n.Web}: {data.U_DISK_WEB} {i18n.mb}
+
{i18n.Mail}: {data.U_DISK_MAIL} {i18n.mb}
+
+
+
{i18n.Databases}: {data.U_DATABASES} {i18n.mb}
+
{i18n['User Directories']}: {data.U_DISK_DIRS} {i18n.mb}
+
+
+
+
+ {i18n['Web Domains']}: {data.U_WEB_DOMAINS}
+ {i18n['SSL Domains']}: {data.U_WEB_SSL}
+ {i18n['Web Aliases']}: {data.U_WEB_ALIASES}
+ {i18n['DNS Domains']}: {data.U_DNS_DOMAINS}
+ {i18n['DNS records']}: {data.U_DNS_RECORDS}
+
+
+ {i18n['Mail Domains']}: {data.U_MAIL_DOMAINS}
+ {i18n['Mail Accounts']}: {data.U_MAIL_ACCOUNTS}
+ {i18n['Databases']}: {data.U_DATABASES}
+ {i18n['Cron Jobs']}: {data.U_CRON_JOBS}
+ {i18n['IP Addresses']}: {data.IP_OWNED}
+
+
+
+
+ );
+}
+
+export default Statistic;
\ No newline at end of file
diff --git a/src/react/src/components/Statistic/Statistic.scss b/src/react/src/components/Statistic/Statistic.scss
new file mode 100644
index 000000000..7e2d9c3b6
--- /dev/null
+++ b/src/react/src/components/Statistic/Statistic.scss
@@ -0,0 +1,54 @@
+.statistic-item {
+ display: flex;
+ justify-content: center;
+ padding: 25px 0;
+ border-bottom: 1px solid #eee;
+
+ .l-col {
+ .date {
+ font-size: 13px;
+ margin-top: 3rem;
+ }
+ }
+
+ .r-col {
+ .name {
+ font-size: 32px;
+ }
+
+ .c-1 {
+ .bandwidth, .disk {
+ width: 220px;
+ }
+
+ div.sub-disk-stats {
+ width: 250px;
+ }
+ }
+
+ .c-2 {
+ margin-right: 0;
+
+ div>span:nth-child(2) {
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ .c-3.w-35 {
+ span.stat {
+ width: 80px;
+ }
+
+
+ div > span {
+ width: 50%;
+ text-align: left;
+ }
+
+ div>span:nth-child(2) {
+ width: 100px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/TopPanel/TopPanel.jsx b/src/react/src/components/TopPanel/TopPanel.jsx
new file mode 100644
index 000000000..378d17b8b
--- /dev/null
+++ b/src/react/src/components/TopPanel/TopPanel.jsx
@@ -0,0 +1,101 @@
+import React, { useState } from 'react';
+import { addActiveElement } from 'src/actions/MainNavigation/mainNavigationActions';
+import { logout } from 'src/actions/Session/sessionActions';
+import { useDispatch, useSelector } from 'react-redux';
+import { Link, useHistory } from 'react-router-dom';
+import Spinner from '../Spinner/Spinner';
+import PerfectScrollbar from 'react-perfect-scrollbar';
+
+import './TopPanel.scss';
+
+const TopPanel = ({ menuItems = [], extraMenuItems = [] }) => {
+ const mainNavigation = useSelector(state => state.mainNavigation);
+ const [loading, setLoading] = useState(false);
+ const { i18n, userName } = useSelector(state => state.session);
+ const dispatch = useDispatch();
+ const history = useHistory();
+
+ const className = cls => {
+ let className = 'nav-link';
+
+ if (mainNavigation.activeElement === cls) {
+ return className += ' active';
+ }
+
+ return className;
+ }
+
+ const renderMenuItems = () => {
+ if (!menuItems.length) return;
+
+ return menuItems.map(({ route, name }) => (
+
+ handleState(event, route)}>{name}
+
+ ));
+ }
+
+ const renderExtraMenuItems = () => {
+ if (!extraMenuItems.length) return;
+
+ return extraMenuItems.map(({ link, text, type }, index) => (
+
+ {
+ type === 'download'
+ ?
{text}
+ :
{text}
+ }
+
+ ));
+ }
+
+ const handleState = (event, route) => {
+ event.preventDefault();
+ history.push(route);
+ dispatch(addActiveElement(route));
+ }
+
+ const signOut = () => {
+ setLoading(true);
+
+ dispatch(logout())
+ .then(() => {
+ history.push('/login/');
+ setLoading(false);
+ },
+ error => {
+ setLoading(false);
+ console.error(error);
+ });
+ }
+
+ return (
+
+ {loading &&
}
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderMenuItems()}
+ {renderExtraMenuItems()}
+
+
+
+
+
{userName}
+
{i18n['Log out']}
+
+
+
+ );
+}
+
+export default TopPanel;
diff --git a/src/react/src/components/TopPanel/TopPanel.scss b/src/react/src/components/TopPanel/TopPanel.scss
new file mode 100644
index 000000000..43944e77b
--- /dev/null
+++ b/src/react/src/components/TopPanel/TopPanel.scss
@@ -0,0 +1,95 @@
+@import 'src/utils/scss/variables';
+
+.panel-wrapper {
+ .logo-img {
+ img {
+ width: 82%;
+ }
+
+ &:hover {
+ background-color: transparent;
+ }
+ }
+
+ .nav-link {
+ padding: 0;
+ }
+
+ .left-menu {
+ justify-content: unset;
+
+ .logo {
+ width: fit-content;
+ flex: unset;
+ }
+
+ .scrollbar-container {
+ &:hover {
+ background: none;
+ }
+
+ .nav-link {
+ flex: unset;
+ padding: 0 5px;
+ }
+ }
+
+ a, button {
+ padding: 0 !important;
+ outline: none;
+ }
+
+ > div.nav-link {
+ padding: 0;
+ }
+ }
+
+ .profile-menu {
+ > div:nth-child(1) a {
+ &:hover {
+ color: $secondaryLight;
+ }
+
+ &:active {
+ color: $secondaryActive;
+ }
+ }
+
+ > div:nth-child(2) a {
+ &:hover {
+ color: $secondaryActive;
+ }
+
+ &:active {
+ color: $secondaryActive;
+ }
+ }
+
+ .user {
+ color: #a4abad;
+ font-weight: 700;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+
+ &:active {
+ color: $secondaryActive;
+ }
+ }
+
+ .log-out {
+ color: white;
+ cursor: pointer;
+ font-weight: 100;
+
+ &:hover {
+ color: $primaryLight;
+ }
+
+ &:active {
+ color: $secondaryActive;
+ }
+ }
+ }
+}
diff --git a/src/react/src/components/Update/Update.jsx b/src/react/src/components/Update/Update.jsx
new file mode 100644
index 000000000..be6219502
--- /dev/null
+++ b/src/react/src/components/Update/Update.jsx
@@ -0,0 +1,56 @@
+import React, { } from 'react';
+import { useSelector } from 'react-redux';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import './Update.scss';
+
+const Update = props => {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const isUpdated = status => {
+ if (status === 'no') {
+ return 'OUTDATED';
+ }
+
+ return 'UPDATED';
+ }
+
+ const isOutdated = status => {
+ return status === "no";
+ }
+
+ const checkItem = () => {
+ props.checkItem(props.data.NAME);
+ }
+
+ return (
+
+
+
+ {data.NAME}
+
+
+ {data.DESCR}
+
+
+ {i18n.Version}: {data.VERSION} {`(${data.ARCH})`}
+
+
+ {i18n.Release}: {data.RELEASE}
+
+
+
+
+
+ );
+}
+
+export default Update;
\ No newline at end of file
diff --git a/src/react/src/components/Update/Update.scss b/src/react/src/components/Update/Update.scss
new file mode 100644
index 000000000..2949a6c0a
--- /dev/null
+++ b/src/react/src/components/Update/Update.scss
@@ -0,0 +1,19 @@
+.statistics-list.updates {
+ .text-status {
+ display: flex;
+ margin-top: 20px;
+
+ > div.checkbox {
+ margin-bottom: 0;
+ margin-right: 10px;
+ }
+ }
+
+ .list-item .star {
+ display: none;
+ }
+
+ .r-col .c-1 {
+ margin-right: 10px;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/User/Add/AddUser.jsx b/src/react/src/components/User/Add/AddUser.jsx
new file mode 100644
index 000000000..fecf74ba8
--- /dev/null
+++ b/src/react/src/components/User/Add/AddUser.jsx
@@ -0,0 +1,223 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import Password from '../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { getLanguages } from '../../../ControlPanelService/Languages';
+import { getPackageList } from '../../../ControlPanelService/Package';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { addUser } from '../../../ControlPanelService/Users';
+import Spinner from '../../../components/Spinner/Spinner';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+
+import './AddUser.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
+
+const AddUser = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [state, setState] = useState({
+ vEmail: '',
+ vNotify: '',
+ languages: [],
+ packages: [],
+ errorMessage: '',
+ okMessage: '',
+ loading: false
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/user/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+
+ Promise.all([getAllPackages(), getAllLanguages()])
+ .then(result => {
+ const [packages, languages] = result;
+ let packageNames = getPackageNames(packages.data.data);
+
+ setState({ ...state, packages: packageNames, languages: languages.data, loading: false });
+ });
+ }, []);
+
+ const getAllPackages = () => {
+ return getPackageList().catch(err => console.error(err));
+ }
+
+ const getAllLanguages = () => {
+ return getLanguages().catch(err => console.error(err));
+ }
+
+ const getPackageNames = packages => {
+ let result = [];
+
+ for (let i in packages) {
+ result.push(i);
+ }
+
+ return result;
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let newUser = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ newUser[name] = value;
+ }
+
+ if (Object.keys(newUser).length !== 0 && newUser.constructor === Object) {
+ setState({ ...state, loading: true });
+ addUser(newUser)
+ .then(result => {
+ 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 => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+ }
+
+ const renderPackageOptions = () => {
+ return state.packages.map((pack, index) => (
+ {pack}
+ ));
+ }
+
+ const renderLanguageOptions = () => {
+ return state.languages.map((language, index) => (
+ {language}
+ ));
+ }
+
+ const onChangeEmail = value => {
+ setState({ ...state, vEmail: value });
+ }
+
+ const onBlurEmail = () => {
+ if (!state.vNotify) {
+ setState({ ...state, vNotify: state.vEmail });
+ }
+ }
+
+ const onChangeSecondEmail = value => {
+ setState({ ...state, vNotify: value });
+ }
+
+ const repeatEmailHandler = checked => {
+ if (checked) {
+ if (state.vEmail) {
+ setState({ ...state, vNotify: state.vEmail });
+ }
+ } else {
+ setState({ ...state, vNotify: '' });
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.USER}`}
+
+
+
+ {i18n['Adding User']}
+ {state.errorMessage ? : ''} {state.errorMessage}
+
+ {state.okMessage ? : ''} {HtmlParser(state.okMessage)}
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="add-user">
+
+ {i18n.Username}
+
+
+
+
+
+
+
+
+ {i18n.Package}
+
+ {renderPackageOptions()}
+
+
+
+
+ {i18n.Language}
+
+ {renderLanguageOptions()}
+
+
+
+
+ {i18n['First Name']}
+
+
+
+
+ {i18n['Last Name']}
+
+
+
+
+ {i18n['Send login credentials to email address']}
+ onChangeSecondEmail(e.target.value)}
+ name="v_notify" />
+
+
+
+ {i18n.Add}
+ history.push('/list/user/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default AddUser;
\ No newline at end of file
diff --git a/src/react/src/components/User/Add/AddUser.scss b/src/react/src/components/User/Add/AddUser.scss
new file mode 100644
index 000000000..2be826318
--- /dev/null
+++ b/src/react/src/components/User/Add/AddUser.scss
@@ -0,0 +1,59 @@
+@import 'src/utils/scss/variables';
+
+.password-wrapper {
+ display: flex;
+
+ button {
+ color: $primary;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+ }
+
+ span.eye {
+ margin-left: 9px;
+ }
+
+ span.eye-slash {
+ margin-left: 7px;
+ }
+}
+
+button.generate-password {
+ color:$primary;
+ font-weight: bold;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+
+ &:active {
+ color: $secondaryActive;
+ }
+}
+
+input {
+ color: $textColor;
+}
+
+label[for=email] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+
+ > div {
+ display: flex;
+ align-items: center;
+
+ input {
+ height: fit-content;
+ margin: 0 10px;
+ }
+
+ label {
+ margin: 0;
+ font-size: 12px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/User/Edit/EditUser.jsx b/src/react/src/components/User/Edit/EditUser.jsx
new file mode 100644
index 000000000..bff3b82e5
--- /dev/null
+++ b/src/react/src/components/User/Edit/EditUser.jsx
@@ -0,0 +1,167 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import Password from '../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+import SelectInput from '../../ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import NameServers from '../../ControlPanel/AddItemLayout/Form/NameServers/NameServers';
+import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import { updateUser, getUserInfo } from '../../../ControlPanelService/Users';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../components/Spinner/Spinner';
+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';
+import QS from 'qs';
+import './EditUser.scss';
+
+const EditUser = props => {
+ const token = localStorage.getItem("token");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ loading: false,
+ username: ''
+ });
+
+ useEffect(() => {
+ let queryParams = QS.parse(history.location.search, { ignoreQueryPrefix: true });
+ const { user } = queryParams;
+
+ dispatch(addActiveElement('/list/user/'));
+ dispatch(removeFocusedElement());
+
+ if (user) {
+ setState({ ...state, loading: true });
+ fetchData(user);
+ }
+ }, []);
+
+ const fetchData = user => {
+ getUserInfo(user)
+ .then(response => {
+ setState({
+ ...state,
+ username: user,
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedUser = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedUser[name] = value;
+ }
+
+ updatedUser['v_username'] = state.username;
+
+ if (Object.keys(updatedUser).length !== 0 && updatedUser.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateUser(updatedUser, state.username)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
+
+ if (errorMessage) {
+ setErrorMessage(errorMessage);
+ setOkMessage('');
+ } else {
+ setErrorMessage('');
+ setOkMessage(okMessage);
+ }
+ }
+ })
+ .then(() => fetchData(state.username))
+ .catch(err => console.error(err));
+ }
+ }
+
+ const convertObjectOfObjectsToArrayOfObjects = object => {
+ let result = [];
+
+ for (let i in object) {
+ result.push(i);
+ }
+
+ return result;
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.USER}`}
+
+
+
+ {i18n['Editing User']}
+ {errorMessage ? : ''} {errorMessage}
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="add-user">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.Save}
+ history.push('/list/user/')}>{i18n.Back}
+
+
+
+ }
+
+
+ );
+}
+
+export default EditUser;
\ No newline at end of file
diff --git a/src/react/src/components/User/Edit/EditUser.scss b/src/react/src/components/User/Edit/EditUser.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/components/User/User.jsx b/src/react/src/components/User/User.jsx
new file mode 100644
index 000000000..83b037eed
--- /dev/null
+++ b/src/react/src/components/User/User.jsx
@@ -0,0 +1,142 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import { useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+
+import './User.scss';
+
+const User = ({ data, toggleFav, handleModal, checkItem, logOut, logInAs }) => {
+ const { i18n, userName } = useSelector(state => state.session);
+
+ const printNameServers = servers => {
+ let serversArray = servers.split(',');
+
+ return serversArray.map(
+ (server, index) => {server}
+ );
+ }
+
+ const printLoginActionButton = user => {
+ let currentUser = userName;
+ if (currentUser === user) {
+ return (
+
+ {i18n['Log out']}
+ {data.FOCUSED ? L : }
+
+
+ );
+ } else {
+ return (
+
+ logInAs(user)}>{i18n['login as']} {user}
+ {data.FOCUSED ? L : }
+
+
+ );
+ }
+ }
+
+ const toggleFavorite = (starred) => {
+ if (starred) {
+ toggleFav(data.NAME, 'add');
+ } else {
+ toggleFav(data.NAME, 'delete');
+ }
+ }
+
+ const checkItemName = () => {
+ checkItem(data.NAME);
+ }
+
+ const handleSuspend = () => {
+ let suspendedStatus = data.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+ handleModal(data.spnd_conf, `/api/v1/${suspendedStatus}/user/index.php?user=${data.NAME}`);
+ }
+
+ const handleDelete = () => {
+ handleModal(data.delete_conf, `/api/v1/delete/user/index.php?user=${data.NAME}`);
+ }
+
+ return (
+
+
+
+ {data.NAME}
+ {data.FNAME} {data.LNAME}
+
+
+
+ {i18n.Bandwidth}
+
{data.U_BANDWIDTH} {data.U_BANDWIDTH_MEASURE}
+
+
+
+ {i18n.Disk}:
{data.U_DISK} {data.U_DISK_MEASURE}
+
+
+
+
+
{i18n.Web}: {data.U_DISK_WEB} {data.U_DISK_WEB_MEASURE}
+
{i18n.Mail}: {data.U_DISK_MAIL} {data.U_DISK_MAIL_MEASURE}
+
+
+
{i18n.Databases}: {data.U_DATABASES} {data.U_DATABASES_MEASURE}
+
{i18n['User Directories']}: {data.U_DISK_DIRS} {data.U_DISK_DIRS_MEASURE}
+
+
+
+
+ {i18n['Web Domains']}: {data.U_WEB_DOMAINS} / {data.WEB_DOMAINS}
+ {i18n['DNS Domains']}: {data.U_DNS_DOMAINS} / {data.DNS_DOMAINS}
+ {i18n['Mail Domains']}: {data.U_MAIL_DOMAINS} / {data.MAIL_DOMAINS}
+ {i18n.Databases}: {data.U_DATABASES} / {data.DATABASES}
+ {i18n['Cron Jobs']}: {data.U_CRON_JOBS} / {data.CRON_JOBS}
+ {i18n.Backups}: {data.U_BACKUPS} / {data.BACKUPS}
+
+
+ {i18n.Email}: {data.CONTACT}
+ {i18n.Package}: {data.PACKAGE}
+ {i18n['SSH Access']}: {data.SHELL}
+ {i18n['IP Addresses']}: {data.IP_OWNED}
+ {i18n['Name Servers']}: {printNameServers(data.NS)}
+
+
+
+
+ {printLoginActionButton(data.NAME)}
+
+ {i18n.edit}
+ {data.FOCUSED ? ↩ : }
+
+
+
+
+ {data.spnd_action}
+ {data.FOCUSED ? S : }
+
+
+
+
+ {i18n.Delete}
+ {data.FOCUSED ? Del : }
+
+
+
+
+ );
+}
+
+export default User;
\ No newline at end of file
diff --git a/src/react/src/components/User/User.scss b/src/react/src/components/User/User.scss
new file mode 100644
index 000000000..82e730256
--- /dev/null
+++ b/src/react/src/components/User/User.scss
@@ -0,0 +1,228 @@
+@import 'src/utils/scss/variables';
+
+.checkbox {
+ margin-bottom: 10px;
+}
+
+.date {
+ margin: 10px 0;
+}
+
+.l-col div.star {
+ text-align: center;
+
+ div > svg {
+ cursor: pointer;
+ opacity: 0;
+ color: gray;
+ padding: 8px;
+ width: 35px;
+ height: 35px;
+ border-radius: 50%;
+
+ &:hover {
+ background: #b4b4b478;
+ color: $primary;
+ }
+ }
+}
+
+span.stat.email{
+ display: block;
+ width: fit-content;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.r-col {
+ .name {
+ font-size: 35px;
+ margin-bottom: 5px;
+ color: black;
+ }
+
+ > div:nth-child(2) {
+ text-transform: capitalize;
+ }
+
+ .stats {
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ .c-1, .c-2, .c-3 {
+ width: 32%;
+
+ > div {
+ display: flex;
+ margin: 5px 0;
+ }
+
+ .bandwidth,
+ .disk {
+ width: 200px;
+ justify-content: space-between;
+ margin-bottom: 25px;
+ border-bottom: 1px dotted gray;
+ position: relative;
+
+ .percent {
+ position: absolute;
+ left: 0px;
+ bottom: -1px;
+ height: 2px;
+ background: $primary;
+ width: 0%;
+ }
+ }
+
+ .sub-disk-stats {
+ width: 250px;
+
+ > div {
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ font-size: 11px;
+
+ span:nth-child(1) {
+ width: 40px;
+ }
+ }
+
+ > div:nth-child(2) {
+ width: 70%;
+ justify-content: flex-end;
+
+ span:nth-child(1) {
+ width: 80px;
+ }
+ }
+ }
+ }
+
+ .c-1 {
+ margin: 0;
+ margin-top: 15px;
+ }
+
+ .c-2 {
+ margin: 15px 20px;
+
+ div {
+ > span:nth-child(1) {
+ width: 120px;
+ }
+
+ > span:nth-child(2) {
+ width: 150px;
+ padding-left: 15px;
+ }
+ }
+ }
+
+ .c-3 {
+ div {
+ > span:nth-child(1) {
+ width: 120px;
+ }
+
+ > span:nth-child(2) {
+ width: 200px;
+ }
+ }
+
+ div.ns {
+ margin-top: 20px;
+ }
+ }
+
+ > div {
+ margin: 15px 0;
+ flex: 1 1 0;
+ }
+
+ .stat {
+ font-weight: bold;
+ color: #5f5f5f;
+ }
+ }
+}
+
+.actions {
+ position: absolute;
+ top: 0;
+ right: 0;
+ opacity: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 12px;
+ background: #dfdedd;
+
+ > div {
+ font-weight: bolder;
+ text-transform: uppercase;
+ height: 38px;
+
+ a, button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: #777;
+ padding: 10px 15px;
+ text-decoration: none;
+
+ &:hover {
+ background: rgb(145, 145, 145);
+ color: white;
+
+ svg {
+ color: white;
+ }
+ }
+ }
+
+ svg {
+ color: #777;
+ }
+ }
+
+ > div:nth-child(2) a:hover {
+ background: $primary;
+ }
+
+ > div:nth-child(4) a:hover {
+ background: $danger;
+ }
+
+ svg {
+ margin-left: 12px;
+ }
+
+ @media (max-width: 1066px) {
+ opacity: 1;
+ }
+
+ @media (max-width: 850px) {
+ > div {
+ width: fit-content;
+
+ a, button {
+ width: 100%;
+ }
+ }
+ }
+}
+
+.list-item.focused .r-col .name {
+ color: $secondaryLight;
+}
+
+@media (max-width: 1066px) {
+ div.star div > svg {
+ opacity: 1 !important;
+ }
+}
diff --git a/src/react/src/components/WebDomain/Add/AddWebDomain.jsx b/src/react/src/components/WebDomain/Add/AddWebDomain.jsx
new file mode 100644
index 000000000..d9b7fc12c
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AddWebDomain.jsx
@@ -0,0 +1,278 @@
+import React, { useEffect, useState } from 'react';
+
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { addWeb, getWebDomainInfo } from '../../../ControlPanelService/Web';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import AdvancedOptions from './AdvancedOptions/AdvancedOptions';
+import Checkbox from 'src/components/ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import { useHistory } from 'react-router-dom';
+import Spinner from '../../Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+
+import './AddWebDomain.scss';
+import GenerateSSL from 'src/containers/GenerateCSR';
+import 'src/components/Modal/Modal.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
+
+const AddWebDomain = props => {
+ const { i18n, userName } = useSelector(state => state.session);
+ const { panel } = useSelector(state => state.panel);
+ const { session } = useSelector(state => state.userSession);
+ const dispatch = useDispatch();
+ const token = localStorage.getItem("token");
+ const [modalVisible, setModalVisible] = useState(false);
+ const history = useHistory();
+ const [state, setState] = useState({
+ loading: false,
+ dnsSupport: true,
+ mailSupport: true,
+ proxySupport: true,
+ showAdvancedOptions: false,
+ okMessage: '',
+ ssl_crt: '',
+ ssl_key: '',
+ domain: '',
+ errorMessage: '',
+ webStats: [],
+ prefixI18N: '',
+ prePath: '',
+ aliases: '',
+ proxy_ext: '',
+ internetProtocols: []
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/web/'));
+ dispatch(removeFocusedElement());
+
+ setState({ ...state, loading: true });
+ getWebDomainInfo()
+ .then(res => {
+ setState({
+ ...state,
+ internetProtocols: getInternetProtocolNames(res.data.ips),
+ webStats: res.data.stats,
+ prefixI18N: res.data.prefix,
+ proxy_ext: res.data.proxy_ext,
+ prePath: res.data.ftp_pre_path,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ })
+ }, []);
+
+ const getInternetProtocolNames = internetProtocols => {
+ let result = [];
+
+ for (let i in internetProtocols) {
+ result.push(i);
+ }
+
+ return result;
+ }
+
+ const renderInternetProtocolsOptions = () => {
+ return state.internetProtocols.map(ip => {ip} );
+ }
+
+ const showAdvancedOption = () => {
+ setState({ ...state, showAdvancedOptions: !state.showAdvancedOptions });
+ }
+
+ const renderAdvancedOptions = () => {
+ if (state.showAdvancedOptions) {
+ return setModalVisible(bool)}
+ sslCertificate={state.ssl_crt}
+ sslKey={state.ssl_key}
+ domain={state.domain}
+ webStats={state.webStats}
+ prePath={state.prePath} />;
+ }
+ }
+
+ const onBlurChangeAliases = value => {
+ setState({ ...state, aliases: `www.${value}`});
+ }
+
+ const checkboxHandler = (input, checked) => {
+ setState({ ...state, [input]: checked });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let newWebDomain = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ newWebDomain[name] = value;
+ }
+
+ if (Object.keys(newWebDomain).length !== 0 && newWebDomain.constructor === Object) {
+ setState({ loading: true });
+ addWeb(newWebDomain)
+ .then(result => {
+ if (result.status === 200) {
+ 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));
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.WEB}`}
+
+
+
+ {i18n['Adding Domain']}
+
+
+ {state.errorMessage ? : ''}
+ {state.errorMessage}
+
+
+
+ {state.okMessage ? : ''}
+ {HtmlParser(state.okMessage)}
+
+
+
+
+ {state.loading ? : (
+ submitFormHandler(event)}>
+
+
+
+
+ {i18n.Domain}
+ onBlurChangeAliases(event.target.value)} />
+
+
+
+ {i18n['IP Address']}
+
+ {renderInternetProtocolsOptions()}
+
+
+
+
+ {i18n.Aliases}
+
+
+
+ {
+ panel[userName]['DNS_DOMAINS'] !== '0' && (
+ checkboxHandler('dnsSupport', checked)}
+ name="v_dns"
+ id="dns-support"
+ title={i18n['DNS Support'] ?? 'DNS Support'}
+ defaultChecked={state.dnsSupport} />
+ )
+ }
+
+ {
+ panel[userName]['MAIL_DOMAINS'] !== '0' && (
+ checkboxHandler('mailSupport', checked)}
+ name="v_mail"
+ id="mail-support"
+ title={i18n['Mail Support'] ?? 'Mail Support'}
+ defaultChecked={state.mailSupport} />
+ )
+ }
+
+ {
+ session.PROXY_SYSTEM && (
+ <>
+ checkboxHandler('proxySupport', checked)}
+ name="v_proxy"
+ id="proxy"
+ title={i18n['Proxy Support'] ?? 'Proxy Support'}
+ defaultChecked={state.proxySupport} />
+
+ {
+ state.proxySupport && (
+
+
)
+ }
+ >
+ )
+ }
+
+
+ showAdvancedOption()}>
+ {i18n['Advanced options']}
+ {state.showAdvancedOptions ? : }
+
+
+
+ {renderAdvancedOptions()}
+
+
+ {i18n.Add}
+ history.push('/list/web/')}>{i18n.Back}
+
+
+ )}
+
+
+
+
+
+
{i18n['Generating CSR']}
+ setModalVisible(false)} className="close" data-dismiss="modal" aria-label="Close">
+ ×
+
+
+
setModalVisible(false)}
+ prePopulateInputs={({ crt, key }) => {
+ setState({ ...state, ssl_crt: crt, ssl_key: key });
+ setModalVisible(false);
+ }} />
+
+
+
+
+ );
+}
+
+export default AddWebDomain;
diff --git a/src/react/src/components/WebDomain/Add/AddWebDomain.scss b/src/react/src/components/WebDomain/Add/AddWebDomain.scss
new file mode 100644
index 000000000..0fca5455b
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AddWebDomain.scss
@@ -0,0 +1,50 @@
+.add-web {
+ form {
+ .form-group {
+ .checkbox-wrapper {
+ display: flex;
+ align-items: center;
+
+ label {
+ margin: 0 10px;
+ };
+ }
+ }
+
+ .advanced-options-button {
+ button {
+ display: flex;
+ align-items: center;
+ padding: 0;
+
+ svg {
+ margin-left: 10px;
+ }
+ }
+ }
+ }
+
+ #c-panel-modal {
+ padding: 2rem;
+
+ form .form-group input[type="text"] {
+ width: 90% !important;
+ }
+
+ label {
+ color: white;
+ }
+
+ .form-group {
+ padding-left: 1.5rem;
+
+ textarea {
+ width: 90% !important;
+ }
+ }
+
+ .l-col {
+ display: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.jsx b/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.jsx
new file mode 100644
index 000000000..4cd4eb97a
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.jsx
@@ -0,0 +1,70 @@
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+import Password from '../../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+
+import './AdditionalFtp.scss';
+
+const AdditionalFtp = ({ domain, data = {}, onDeleteAdditionalFtp, prefixI18N }) => {
+ const { i18n, userName } = useSelector(state => state.session);
+ const [state, setState] = useState({
+ path: '',
+ username: ''
+ });
+
+ return (
+
+
+ {i18n.FTP} #{data.id}
+
+
+ onDeleteAdditionalFtp(data.id)}>({i18n.Delete ?? 'Delete'})
+
+
+
+
+
+
{i18n.Username}
+
{prefixI18N}
+
+ setState({ ...state, username: event.target.value })}
+ type="text"
+ className="form-control"
+ id={`username_${data.id}`}
+ name={`v_ftp_user[${data.id}][v_ftp_user]`} />
+ {`${userName}_${state.username}`}
+
+
+
+
+
+
+ {i18n.Path}
+ setState({ ...state, path: event.target.value })}
+ className="form-control"
+ id={`path${data.id}`}
+ name={`v_ftp_user[${data.id}][v_ftp_path]`} />
+ {`/web/${domain ? domain + '/' : ''}${state.path}`}
+
+
+
+ {i18n['Send login credentials to email address']}
+
+
+
+
+
+
+
+ );
+}
+
+export default AdditionalFtp;
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.scss b/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.scss
new file mode 100644
index 000000000..50d04b0e3
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdditionalFtp/AdditionalFtp.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;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.jsx b/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.jsx
new file mode 100644
index 000000000..41524c87b
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.jsx
@@ -0,0 +1,108 @@
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+
+import Password from '../../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+
+import './AdditionalFtpForEditing.scss';
+
+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 || '',
+ path: data.v_ftp_path || '',
+ });
+
+ const renderForm = () => {
+ if (data.deleted) {
+ if (data.is_new === 0) {
+ return (<>
+
+
+
+
+
+
+ >);
+ }
+ } else {
+ if (!checked) {
+ return <>>;
+ }
+
+ return (
+
+
+
+
+
+
+ {i18n.FTP} #{data.id + 1}
+
+
+ onDeleteAdditionalFtp(data.id)}>
+ ({i18n.Delete ?? 'Delete'})
+
+
+
+
+
+
+
{i18n.Username}
+
{prefixI18N}
+
+ 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]`} />
+ {data.v_ftp_user ? data.v_ftp_user : `${userName}_${state.username}`}
+
+
+
+
+
+
+
+
+
+ {i18n.Path}
+ setState({ ...state, path: event.target.value.indexOf('/') !== 0 ? `/${event.target.value}` : event.target.value })}
+ className="form-control"
+ id={`path${data.id}`}
+ name={`v_ftp_user[${data.id}][v_ftp_path]`} />
+ {prePath}{state.path}
+
+
+ {
+ data.is_new === 1 && (
+
+ {i18n['Send login credentials to email address']}
+
+
+ )
+ }
+
+
+
);
+ }
+
+ return <>>;
+ }
+
+ return renderForm();
+}
+
+export default AdditionalFtpForEditing;
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.scss b/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.scss
new file mode 100644
index 000000000..3b8f94a54
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdditionalFtpForEditing/AdditionalFtpForEditing.scss
@@ -0,0 +1,7 @@
+.edit-web {
+ .additional-ftp {
+ .title .indexed-name {
+ font-weight: bold;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.jsx b/src/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.jsx
new file mode 100644
index 000000000..d23057b51
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.jsx
@@ -0,0 +1,76 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import AdditionalFtpForEditing from '../AdditionalFtpForEditing/AdditionalFtpForEditing';
+import './AdditionalFtpWrapper.scss';
+
+const AdditionalFtpWrapper = ({ checked, ftps, unCheckAdditionalFtpBox, prefixI18N, ftpUserPrePath, domain, ...props }) => {
+ const { i18n } = useSelector(state => state.session);
+ const [state, setState] = useState({
+ additionalFtp: []
+ });
+
+ useEffect(() => {
+ if (ftps) {
+ const data = ftps.map((item, index) => {
+ item['deleted'] = !checked;
+ item['id'] = index;
+ return item;
+ });
+
+ setState({ ...state, additionalFtp: data });
+ }
+ }, [checked, ftps]);
+
+ const renderAdditionalFtps = () => {
+ return state.additionalFtp.map(ftp => {
+ return onDeleteFtp(id)} />;
+ });
+ }
+
+ const onDeleteFtp = index => {
+ let updatedAdditionalFtps = [];
+
+ state.additionalFtp.forEach(item => {
+ if (item.id === index) {
+ item.deleted = true;
+ }
+
+ updatedAdditionalFtps.push(item);
+ });
+
+ if (!updatedAdditionalFtps.length) {
+ unCheckAdditionalFtpBox();
+ }
+
+ setState({ ...state, additionalFtp: updatedAdditionalFtps });
+ }
+
+ const addAdditionalFtp = () => {
+ let additionalFtpArrayLength = state.additionalFtp.length;
+ let additionalFtpsDuplicate = [...state.additionalFtp];
+
+ additionalFtpsDuplicate.push({ id: additionalFtpArrayLength, deleted: false, is_new: 1 });
+
+ setState({ ...state, additionalFtp: additionalFtpsDuplicate });
+ }
+
+ return (
+
+ {renderAdditionalFtps()}
+
+ {checked && (
+ addAdditionalFtp()}>
+ {i18n['Add one more FTP Account'] ?? 'Add'}
+ )}
+
+ );
+}
+
+export default AdditionalFtpWrapper;
diff --git a/src/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.scss b/src/react/src/components/WebDomain/Add/AdditionalFtpWrapper/AdditionalFtpWrapper.scss
new file mode 100644
index 000000000..50d04b0e3
--- /dev/null
+++ b/src/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;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.jsx b/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.jsx
new file mode 100644
index 000000000..eb649b73f
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.jsx
@@ -0,0 +1,103 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import Password from '../../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+import AdditionalFtpWrapper from '../AdditionalFtpWrapper/AdditionalFtpWrapper';
+import Checkbox from 'src/components/ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import SslSupport from '../SslSupport/SslSupport';
+
+import './AdvancedOptions.scss';
+
+const AdvancedOptions = ({ prefixI18N, prePath, ...props }) => {
+ const { i18n } = useSelector(state => state.session);
+ const [state, setState] = useState({
+ sslSupport: false,
+ additionalFtp: false,
+ statisticsAuthCheckbox: false,
+ statisticsAuth: false,
+ });
+
+ useEffect(() => {
+ let updatedDomain = `www.${props.domain}`;
+ setState({ ...state, aliases: updatedDomain });
+ }, [props.domain]);
+
+ const renderSslSupport = () => {
+ if (state.sslSupport) {
+ return props.setModalVisible(bool)} />;
+ }
+ }
+
+ const renderWebStats = () => {
+ return props.webStats.map(stat => {stat} );
+ }
+
+ const onChangeWebStatsSelect = value => {
+ if (value !== 'none') {
+ setState({ ...state, statisticsAuth: true });
+ } else {
+ setState({ ...state, statisticsAuth: false, statisticsAuthCheckbox: false });
+ }
+ }
+
+ return (
+
+
+
+ setState({ ...state, sslSupport: !state.sslSupport })} />
+ {i18n['SSL Support']}
+
+
+
+ {renderSslSupport()}
+
+
+ {i18n['Web Statistics']}
+ onChangeWebStatsSelect(event.target.value)}>
+ {renderWebStats()}
+
+
+
+
+
+ setState({ ...state, statisticsAuthCheckbox: !state.statisticsAuthCheckbox })} />
+ {i18n['Statistics Authorization']}
+
+
+
+
+
+ {i18n.Username}
+
+
+
+
+
+
+
setState({ ...state, additionalFtp: checked })}
+ name="v_ftp"
+ id="add-ftp"
+ checked={state.additionalFtp}
+ title={i18n['Additional FTP Account']} />
+
+ setState({ ...state, additionalFtp: false })} />
+
+ );
+}
+
+export default AdvancedOptions;
diff --git a/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.scss b/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.scss
new file mode 100644
index 000000000..c721bb263
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/AdvancedOptions/AdvancedOptions.scss
@@ -0,0 +1,9 @@
+.statistics-authorization {
+ transform: translateX(3rem);
+}
+
+.web-stats-wrapper {
+ display: flex;
+ flex-direction: column;
+ transform: translateX(3rem);
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.jsx b/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.jsx
new file mode 100644
index 000000000..8d9aa1a76
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.jsx
@@ -0,0 +1,48 @@
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+
+import './SslSupport.scss';
+
+const SslSupport = props => {
+ const { i18n } = useSelector(state => state.session);
+ const [letsEncrypt, setLetsEncrypt] = useState(false);
+
+ return (
+
+
+
+ setLetsEncrypt(!letsEncrypt)} />
+ {i18n['Lets Encrypt Support']}
+
+
{letsEncrypt ? i18n['Your certificate will be automatically issued in 5 minutes'] : null}
+
+
+
+
+
/ props.setModalVisible(true)} className="generate-csr">{i18n['Generate CSR']} >} />
+
+
+ {i18n['SSL Key']}
+
+
+
+
+ {i18n['SSL Certificate Authority / Intermediate']}
+
+
+
+ );
+}
+
+export default SslSupport;
diff --git a/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.scss b/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.scss
new file mode 100644
index 000000000..7ea191a32
--- /dev/null
+++ b/src/react/src/components/WebDomain/Add/SslSupport/SslSupport.scss
@@ -0,0 +1,8 @@
+.ssl-support {
+ transform: translateX(3rem);
+
+ .lets-encrypt-span {
+ font-style: italic;
+ color: #89a40a;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Edit/EditWeb.jsx b/src/react/src/components/WebDomain/Edit/EditWeb.jsx
new file mode 100644
index 000000000..e38e5d181
--- /dev/null
+++ b/src/react/src/components/WebDomain/Edit/EditWeb.jsx
@@ -0,0 +1,358 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
+import { updateWebDomain, getDomainInfo } from '../../../ControlPanelService/Web';
+import Password from '../../../components/ControlPanel/AddItemLayout/Form/Password/Password';
+import SelectInput from '../../ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
+import AdditionalFtpWrapper from '../Add/AdditionalFtpWrapper/AdditionalFtpWrapper';
+import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import Checkbox from '../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Spinner from '../../../components/Spinner/Spinner';
+import Toolbar from '../../MainNav/Toolbar/Toolbar';
+import SslSupport from './SslSupport/SslSupport';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+import GenerateSSL from 'src/containers/GenerateCSR';
+import 'src/components/Modal/Modal.scss';
+import QS from 'qs';
+
+import './EditWeb.scss';
+import TextArea from '../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import HtmlParser from 'react-html-parser';
+
+const EditWeb = props => {
+ const token = localStorage.getItem("token");
+ const { i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [modalVisible, setModalVisible] = useState(false);
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ domain: '',
+ webStat: '',
+ sslSupport: false,
+ letsEncrypt: false,
+ additionalFtp: false,
+ proxySupport: false,
+ statAuth: false,
+ loading: false
+ });
+
+ useEffect(() => {
+ let queryParams = QS.parse(history.location.search, { ignoreQueryPrefix: true });
+ const { domain } = queryParams;
+
+ dispatch(addActiveElement('/list/web/'));
+ dispatch(removeFocusedElement());
+
+ if (domain) {
+ setState({ ...state, loading: true });
+ fetchData(domain);
+ }
+ }, []);
+
+ const fetchData = domain => {
+ getDomainInfo(domain)
+ .then(response => {
+ setState({
+ ...state,
+ domain,
+ webStat: response.data.v_stats ? response.data.v_stats : 'none',
+ sslSupport: response.data.ssl === 'yes',
+ letsEncrypt: response.data.letsencrypt === 'yes',
+ proxySupport: !!response.data.proxy,
+ data: response.data,
+ additionalFtp: !!response.data.ftp_user,
+ statAuth: response.data.stats_user,
+ loading: false
+ });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let updatedDomain = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ updatedDomain[name] = value;
+ }
+
+ updatedDomain['v_domain'] = state.domain;
+
+ if (updatedDomain['v_ssl'] === 'on') {
+ updatedDomain['v_ssl'] = 'yes';
+ } else {
+ delete updatedDomain['v_ssl'];
+ }
+
+ if (updatedDomain['v_letsencrypt'] === 'on') {
+ updatedDomain['v_letsencrypt'] = 'yes';
+ } else {
+ delete updatedDomain['v_letsencrypt'];
+ }
+
+ if (!updatedDomain['v_ssl_ca']) {
+ delete updatedDomain['v_ssl_ca'];
+ }
+
+ if (!updatedDomain['v_ssl_crt']) {
+ delete updatedDomain['v_ssl_crt'];
+ }
+
+ if (!updatedDomain['v_ssl_key']) {
+ delete updatedDomain['v_ssl_key'];
+ }
+
+ if (Object.keys(updatedDomain).length !== 0 && updatedDomain.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ updateWebDomain(updatedDomain, state.domain)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg, ok_msg } = result.data;
+
+ if (error_msg) {
+ setErrorMessage(error_msg);
+ setOkMessage('');
+ setState({ ...state, loading: false });
+ } else {
+ dispatch(refreshCounters()).then(() => {
+ setErrorMessage('');
+ setOkMessage(ok_msg);
+ fetchData(state.domain);
+ });
+ }
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const formatData = object => {
+ let result = [];
+
+ for (let i in object) {
+ result.push(i);
+ }
+
+ return result;
+ }
+
+ const onChangeSslSupport = checked => {
+ setState({ ...state, sslSupport: checked });
+ }
+
+ const onChangeProxySupport = checked => {
+ setState({ ...state, proxySupport: checked });
+ }
+
+ const onChangeWebStats = webStat => {
+ setState({ ...state, webStat });
+ }
+
+ const onChangeStatisticsAuth = statAuth => {
+ setState({ ...state, statAuth });
+ }
+
+ const onChangeAdditionalFtp = additionalFtp => {
+ setState({ ...state, additionalFtp });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.WEB}`}
+
+
+
+ {i18n['Editing Domain']}
+ {errorMessage ? : ''} {errorMessage}
+
+ {okMessage ? : ''} {HtmlParser(okMessage)}
+
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="add-web">
+
+
+
+
+
+
+
+
+
+
+
+ {
+ session.WEB_BACKEND && (
+
+ )
+ }
+
+ {
+ state.data.proxy_system && (
+ <>
+
+
+ {
+ state.proxySupport && (
+
+
+
+
)
+ }
+ >
+ )
+ }
+
+
+
+ {
+ state.sslSupport && (
+ setModalVisible(bool)}
+ sslCertificateAuthority={state.data.ssl_ca}
+ domain={state.domain}
+ sslHome={state.data.ssl_home}
+ letsEncrypt={state.letsEncrypt}
+ />
+ )
+ }
+
+
+
+ {
+ state.webStat !== 'none' && (
+
+
+
+ {
+ state.statAuth && (
+ <>
+
+
+
+ >
+ )
+ }
+
+ )
+ }
+
+
+
+ onChangeAdditionalFtp(false)} />
+
+
+ {i18n.Save}
+ history.push('/list/web/')}>{i18n.Back}
+
+
+
+ }
+
+
+
+
+
+
+
{i18n['Generating CSR']}
+ setModalVisible(false)} className="close" data-dismiss="modal" aria-label="Close">
+ ×
+
+
+
setModalVisible(false)}
+ prePopulateInputs={({ crt, key }) => {
+ setState({ ...state, data: { ...state.data, ssl_crt: crt, ssl_key: key } });
+ setModalVisible(false);
+ }} />
+
+
+
+
+ );
+}
+
+export default EditWeb;
diff --git a/src/react/src/components/WebDomain/Edit/EditWeb.scss b/src/react/src/components/WebDomain/Edit/EditWeb.scss
new file mode 100644
index 000000000..d00b42b74
--- /dev/null
+++ b/src/react/src/components/WebDomain/Edit/EditWeb.scss
@@ -0,0 +1,29 @@
+.edit-web {
+ .web-stat-additional {
+ transform: translateX(3rem);
+ }
+
+ #c-panel-modal {
+ padding: 2rem;
+
+ form .form-group input[type="text"] {
+ width: 90% !important;
+ }
+
+ label {
+ color: white;
+ }
+
+ .form-group {
+ padding-left: 1.5rem;
+
+ textarea {
+ width: 90% !important;
+ }
+ }
+
+ .l-col {
+ display: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.jsx b/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.jsx
new file mode 100644
index 000000000..1117be528
--- /dev/null
+++ b/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.jsx
@@ -0,0 +1,136 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import Checkbox from '../../../ControlPanel/AddItemLayout/Form/Checkbox/Checkbox';
+import TextArea from '../../../ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+
+import './SslSupport.scss';
+
+const SslSupport = props => {
+ const { i18n } = useSelector(state => state.session);
+ const [letsEncrypt, setLetsEncrypt] = useState(props.letsEncrypt);
+
+ useEffect(() => {
+ setLetsEncrypt(props.letsEncrypt);
+ }, []);
+
+ const onChangeLetsEncrypt = checked => {
+ setLetsEncrypt(checked);
+ }
+
+ return (
+
+ <>
+
+
+ {!props.letsEncrypt &&
{letsEncrypt ? i18n['Your certificate will be automatically issued in 5 minutes'] : null} }
+ >
+
+
+
+
/ props.setModalVisible(true)} className="generate-csr">{i18n['Generate CSR']} >} />
+
+
+
+
+
+
+ {
+ props.sslSubject.length > 0
+ && (
+
+ {i18n['SUBJECT']}:
+ {props.sslSubject}
+
+ )
+ }
+
+ {
+ props.sslAliases.length > 0
+ && (
+
+ {i18n['ALIASES']}:
+ {props.sslAliases}
+
+ )
+ }
+
+ {
+ props.sslNotBefore.length > 0
+ && (
+
+ {i18n['NOT_BEFORE']}:
+ {props.sslNotBefore}
+
+ )
+ }
+
+ {
+ props.sslNotAfter.length > 0
+ && (
+
+ {i18n['NOT_AFTER']}:
+ {props.sslNotAfter}
+
+ )
+ }
+
+ {
+ props.sslSignature.length > 0
+ && (
+
+ {i18n['SIGNATURE']}:
+ {props.sslSignature}
+
+ )
+ }
+
+ {
+ props.sslPubKey.length > 0
+ && (
+
+ {i18n['PUB_KEY']}:
+ {props.sslPubKey}
+
+ )
+ }
+
+ {
+ props.sslIssuer.length > 0
+ && (
+
+ {i18n['ISSUER']}:
+ {props.sslIssuer}
+
+ )
+ }
+
+
+
+
+
+ );
+}
+
+export default SslSupport;
diff --git a/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.scss b/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.scss
new file mode 100644
index 000000000..fb0a9c773
--- /dev/null
+++ b/src/react/src/components/WebDomain/Edit/SslSupport/SslSupport.scss
@@ -0,0 +1,32 @@
+@import 'src/utils/scss/variables';
+
+.edit-web {
+ .ssl-support {
+ .additional-info {
+ display: flex;
+ flex-direction: column;
+
+ span {
+ &:nth-child(1) {
+ width: 120px;
+ display: inline-block;
+ }
+
+ &:nth-child(2) {
+ width: fit-content;
+ }
+ }
+ }
+
+ a.generate-csr {
+ text-transform: initial;
+ text-decoration: none;
+ color: $primary;
+ font-weight: bold;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/components/WebDomain/WebDomain.jsx b/src/react/src/components/WebDomain/WebDomain.jsx
new file mode 100644
index 000000000..2c55a116f
--- /dev/null
+++ b/src/react/src/components/WebDomain/WebDomain.jsx
@@ -0,0 +1,146 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Container from '../ControlPanel/Container/Container';
+import ListItem from '../ControlPanel/ListItem/ListItem';
+import { Link } from 'react-router-dom';
+import './WebDomain.scss';
+import { useSelector } from 'react-redux';
+
+export default function WebDomain(props) {
+ const { data } = props;
+ const { i18n } = useSelector(state => state.session);
+
+ const printStat = (stat, text) => {
+ if (text === 'no' || text === '') {
+ return {stat}
;
+ }
+
+ return {stat}: {text}
;
+ }
+
+ const toggleFav = (starred) => {
+ if (starred) {
+ props.toggleFav(data.NAME, 'add');
+ } else {
+ props.toggleFav(data.NAME, 'delete');
+ }
+ }
+
+ const checkItem = () => {
+ props.checkItem(data.NAME);
+ }
+
+ const renderProxySupport = () => {
+ if (!data.PROXY_SYSTEM) return;
+
+ if (data.PROXY_SUPPORT === 'no') {
+ printStat(i18n['Proxy Support'], '');
+ } else {
+ printStat(i18n['Proxy Support'], data.PROXY_SUPPORT);
+ }
+ }
+
+ const renderBackedSupport = () => {
+ if (!data.WEB_BACKEND) return;
+
+ if (data.BACKEND_SUPPORT === 'no') {
+ printStat(i18n['Backend Support'] ?? 'Backend Support', '');
+ } else {
+ printStat(i18n['Backend Support'] ?? 'Backend Support', data.BACKEND_SUPPORT);
+ }
+ }
+
+ const handleSuspend = () => {
+ let suspendedStatus = data.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+ props.handleModal(data.spnd_confirmation, `/api/v1/${suspendedStatus}/web/index.php?domain=${data.NAME}`);
+ }
+
+ const handleDelete = () => {
+ props.handleModal(data.delete_confirmation, `/api/v1/delete/web/index.php?domain=${data.NAME}`);
+ }
+
+ return (
+
+
+
+
{data.NAME}
+
{data.ALIAS.replace(/,/g, ', ')}
+
+ {data.IP}
+
+
+
+ {i18n.Bandwidth}
+
{data.U_BANDWIDTH_SIZE} {data.U_BANDWIDTH_MEASURE}
+
+
+
+ {i18n.Disk}:
{data.U_DISK_SIZE} {data.U_DISK_MEASURE}
+
+
+
+
+ {i18n['Web Template']}: {data.TPL}
+ {data.SSL === 'no'
+ ? printStat(i18n['SSL Support'], '')
+ : printStat(i18n['SSL Support'], data.LETSENCRYPT === 'yes' ? i18n['Lets Encrypt'] : i18n[data.SSL])}
+ {printStat(i18n['Web Statistics'], data.WEB_STATS)}
+
+
+ {renderProxySupport()}
+ {data.PROXY_SYSTEM && printStat(i18n['Proxy Template'] ?? 'Proxy Template', data.PROXY)}
+ {renderBackedSupport()}
+ {data.WEB_BACKEND && printStat(i18n['Backend Template'] ?? 'Backend Template', data.BACKEND)}
+ {printStat(i18n['Additional FTP Account'], data.FTP)}
+
+
+
+
+
+
+ {i18n.edit}
+ {data.FOCUSED ? ↩ : }
+
+
+
+
+ {i18n['view logs']}
+ {data.FOCUSED ? L : }
+
+
+ {
+ data.STATS && (
+
+ )
+ }
+
+
+ {i18n[data.spnd_action]}
+ {data.FOCUSED ? S : }
+
+
+
+
+ {i18n.Delete}
+ {data.FOCUSED ? Del : }
+
+
+
+
+ );
+}
diff --git a/src/react/src/components/WebDomain/WebDomain.scss b/src/react/src/components/WebDomain/WebDomain.scss
new file mode 100644
index 000000000..42afea54e
--- /dev/null
+++ b/src/react/src/components/WebDomain/WebDomain.scss
@@ -0,0 +1,42 @@
+.web-stats,
+.add-ftp {
+ text-decoration: line-through;
+}
+
+.r-col {
+ .stats {
+ .c-2 {
+ margin-left: 20px;
+ }
+ }
+}
+
+.r-col {
+ .c-2 span.stat,
+ .c-3 span.stat {
+ margin-left: 1rem;
+ }
+
+ .name {
+ display: flex;
+
+ > div:nth-child(1) {
+ margin-right: 2rem;
+ }
+
+ > div + div {
+ line-height: 10px;
+ margin-top: .75rem;
+ }
+
+ .dns-name-span {
+ font-style: italic;
+ color: #858585;
+ font-size: 14px;
+ }
+ }
+}
+
+div.crossed {
+ text-decoration: line-through;
+}
\ No newline at end of file
diff --git a/src/react/src/containers/AddDNSWrapper/AddDNSWrapper.jsx b/src/react/src/containers/AddDNSWrapper/AddDNSWrapper.jsx
new file mode 100644
index 000000000..e87f00e3f
--- /dev/null
+++ b/src/react/src/containers/AddDNSWrapper/AddDNSWrapper.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import AddDomainNameSystem from 'src/components/DomainNameSystem/Add/AddDomainNameSystem';
+import AddDNSRecord from 'src/components/DNSRecord/Add/AddDNSRecord';
+import { useHistory } from 'react-router-dom';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function AddDNSWrapper() {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const [isDnsRecord, setIsDnsRecord] = useState(false);
+
+ useEffect(() => {
+ if (parsedQueryString.domain) {
+ setIsDnsRecord(true);
+ } else {
+ setIsDnsRecord(false);
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.DNS}`}
+
+ {
+ isDnsRecord
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/AddDNSWrapper/AddDNSWrapper.scss b/src/react/src/containers/AddDNSWrapper/AddDNSWrapper.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/AddMailWrapper/AddMailWrapper.jsx b/src/react/src/containers/AddMailWrapper/AddMailWrapper.jsx
new file mode 100644
index 000000000..ee496f2eb
--- /dev/null
+++ b/src/react/src/containers/AddMailWrapper/AddMailWrapper.jsx
@@ -0,0 +1,36 @@
+import React, { useEffect, useState } from 'react';
+import AddMailAccount from 'src/components/MailAccount/Add/AddMailAccount';
+import AddMail from 'src/components/Mail/Add/AddMail';
+import { useHistory } from 'react-router-dom';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function AddMailWrapper() {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const [domain, setDomain] = useState(false);
+
+ useEffect(() => {
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+
+ if (parsedQueryString.domain) {
+ setDomain(parsedQueryString.domain);
+ } else {
+ setDomain('');
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.MAIL}`}
+
+ {
+ domain
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/App/App.js b/src/react/src/containers/App/App.js
new file mode 100755
index 000000000..fc06cf21a
--- /dev/null
+++ b/src/react/src/containers/App/App.js
@@ -0,0 +1,129 @@
+import React, { useEffect, useState } from 'react';
+import FileManager from '../FileManager/FileManager';
+import { Route, Switch, useHistory, Redirect } from "react-router";
+import Preview from '../../components/Preview/Preview';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import * as Icon from '@fortawesome/free-solid-svg-icons';
+import 'bootstrap/dist/css/bootstrap.css';
+import 'bootstrap/dist/js/bootstrap.min';
+import './App.scss';
+import ControlPanelContent from '../ControlPanelContent/ControlPanelContent';
+import WebLogs from '../WebLogs/WebLogs';
+import LoginForm from 'src/components/Login/LoginForm';
+import { useDispatch, useSelector } from 'react-redux';
+import { checkAuthHandler } from 'src/actions/Session/sessionActions';
+import ServiceInfo from 'src/containers/ServiceInfo';
+import ForgotPassword from 'src/components/ForgotPassword';
+import Spinner from 'src/components/Spinner/Spinner';
+
+library.add(
+ Icon.faBook,
+ Icon.faDownload,
+ Icon.faFile,
+ Icon.faFileAlt,
+ Icon.faFolderOpen,
+ Icon.faImage,
+ Icon.faEllipsisH,
+ Icon.faFolder,
+ Icon.faItalic,
+ Icon.faUser,
+ Icon.faCopy,
+ Icon.faPaste,
+ Icon.faTrash,
+ Icon.faBoxOpen,
+ Icon.faArrowDown,
+ Icon.faArrowUp,
+ Icon.faBell,
+ Icon.faPlus,
+ Icon.faAngleRight,
+ Icon.faStar,
+ Icon.faUserLock,
+ Icon.faPen,
+ Icon.faLock,
+ Icon.faTimes,
+ Icon.faSearch,
+ Icon.faCog,
+ Icon.faList,
+ Icon.faWrench,
+ Icon.faFileDownload,
+ Icon.faPause,
+ Icon.faPlay,
+ Icon.faCogs,
+ Icon.faStop,
+ Icon.faUnlock,
+ Icon.faLongArrowAltUp,
+ Icon.faEye,
+ Icon.faEyeSlash,
+ Icon.faLongArrowAltRight,
+ Icon.faCaretDown,
+ Icon.faCaretUp,
+ Icon.faInfinity,
+ Icon.faPlay
+);
+
+const App = () => {
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const session = useSelector(state => state.session);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ if (!Object.entries(session.i18n).length) {
+ dispatch(checkAuthHandler())
+ .then(token => {
+ setLoading(false);
+ }, (error) => {
+ console.error(error);
+ return history.push('/login');
+ });
+ }
+ }, [dispatch, history, session]);
+
+ const AuthenticatedRoute = ({ authenticated, ...rest }) => {
+ return (
+
+ authenticated
+ ?
+ : } />
+ );
+ }
+
+ return (
+
+ {
+ loading
+ ?
+ : (
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ );
+}
+
+export default App;
diff --git a/src/react/src/containers/App/App.scss b/src/react/src/containers/App/App.scss
new file mode 100644
index 000000000..069ec0cad
--- /dev/null
+++ b/src/react/src/containers/App/App.scss
@@ -0,0 +1,261 @@
+@import 'src/utils/scss/variables';
+
+html {
+ overflow-y: scroll;
+}
+
+.App {
+ font-size: 25px;
+ font-family: Arial;
+}
+
+.window {
+ background: #ececec;
+ height: 100vh;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+
+ .lists-container {
+ display: flex;
+ flex-direction: row;
+ }
+}
+
+.active {
+ background: #fff;
+}
+
+.progress {
+ height: 10px;
+ transition: all ease-out 0.1s;
+}
+
+.Toastify__toast-body {
+ font-size: 20px;
+ word-break: break-word;
+}
+.Toastify__toast-container {
+ top: 15em;
+}
+
+.actions {
+ div {
+ a.link-download:hover, button.link-download:hover,
+ a.link-edit:hover, button.link-edit:hover {
+ background: $primary !important;
+ }
+
+ a.link-list:hover,
+ button.link-list:hover {
+ background: $primary !important;
+ }
+
+ a.link-delete:hover,
+ button.link-delete:hover {
+ background: $danger !important;
+ }
+
+ a.link-gray:hover,
+ button.link-gray:hover {
+ background: #afafac !important;
+ }
+
+ button {
+ text-transform: inherit;
+ font-weight: bolder;
+ background: #dfdedd;
+ border: none;
+ }
+ }
+}
+
+@media screen and (max-width: 700px) {
+ .actions {
+ background: unset !important;
+ flex-wrap: wrap;
+ justify-content: flex-end !important;
+
+ div {
+ height: 30px !important;
+
+ a,button {
+ background: #dfdedd !important;
+ padding: 0px 10px !important;
+ height: 100% !important;
+ font-size: 10px !important;
+ }
+ }
+ }
+}
+
+button:active, a:active, .period:active {
+ color: $primaryActive;
+}
+
+.list-item:first-child {
+ margin-top: 5px;
+}
+
+.list-item .text-status {
+ display: none;
+}
+
+button {
+ &:hover, &:active, &:focus {
+ outline: none;
+ }
+}
+
+.fixed-buttons.fm {
+ position: fixed;
+ right: 15px;
+ bottom: 15px;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+
+ > div {
+ width: 40px;
+ height: 40px;
+ padding: 5px;
+ margin: 5px;
+ border-radius: 50%;
+ background: #c3c3c3;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ color: $hoverButtonText;
+ background: $primaryLight;
+ }
+
+ &:active {
+ color: $activeButtonText;
+ background: $primaryActive;
+ }
+
+ button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: none;
+ border: none;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ transform: scale(1.32);
+
+ svg {
+ color: white;
+ height: 70%;
+ }
+ }
+ }
+
+ > div:first-child {
+ background: $primary;
+
+ &:hover {
+ color: $hoverButtonText;
+ background: $primaryLight;
+
+ button svg {
+ color: $hoverButtonText;
+ }
+ }
+
+ &:active {
+ color: $activeButtonText;
+ background: $primaryActive;
+
+ button svg {
+ color: $activeButtonText;
+ }
+ }
+
+ button {
+ padding: 0;
+
+ svg {
+ color: $activeButtonText;
+ }
+ }
+ }
+}
+
+@media (min-width: 1067px) {
+ .App div.content {
+ padding-top: 0 !important;
+ margin-top: -20px !important;
+ }
+}
+
+@media (max-width: 1066px) {
+ .App div.content {
+ padding-top: 0 !important;
+ margin-top: -20px !important;
+ }
+
+ .content > div {
+ .l-col {
+ margin-top: 2rem;
+ padding-left: 5px;
+ }
+
+ .r-col {
+ margin-top: 1rem;
+ .name {
+ margin-top: 2rem;
+ }
+
+ .stats {
+ flex-wrap: wrap;
+
+ > div {
+ margin: 1rem 0 !important;
+ padding: 0 !important;
+ width: 100% !important;
+ }
+ }
+ }
+ }
+
+ .content .servers-list .servers-wrapper .l-col {
+ display: flex;
+ align-items: center;
+ height: 55px;
+ margin-top: .75rem;
+
+ .checkbox {
+ margin: 0px;
+ }
+ }
+}
+
+@media (max-width: 900px) {
+ .App div.content {
+ padding-top: 77px !important;
+ margin-top: 0 !important;
+ }
+
+ .content > div {
+ .l-col {
+ padding-left: 5px;
+ }
+
+ .r-col {
+ padding-left: 2rem;
+ }
+ }
+}
+
+@media (max-width: 725px) {
+ .content {
+ .r-col {
+ padding-left: 3.5rem;
+ }
+ }
+}
diff --git a/src/react/src/containers/App/App.test.js b/src/react/src/containers/App/App.test.js
new file mode 100755
index 000000000..a754b201b
--- /dev/null
+++ b/src/react/src/containers/App/App.test.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ ReactDOM.render( , div);
+ ReactDOM.unmountComponentAtNode(div);
+});
diff --git a/src/react/src/containers/App/serviceWorker.js b/src/react/src/containers/App/serviceWorker.js
new file mode 100755
index 000000000..2283ff9ce
--- /dev/null
+++ b/src/react/src/containers/App/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read http://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit http://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ 'New content is available and will be used when all ' +
+ 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf('javascript') === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/src/react/src/containers/BackupWrapper/BackupWrapper.jsx b/src/react/src/containers/BackupWrapper/BackupWrapper.jsx
new file mode 100644
index 000000000..862d5b1ac
--- /dev/null
+++ b/src/react/src/containers/BackupWrapper/BackupWrapper.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import BackupRestoreSettings from '../../components/Backup/RestoreSettings/BackupRestoreSettings';
+import { useHistory } from 'react-router-dom';
+import Backups from '../Backups/Backups';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function BackupWrapper(props) {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const [isBackupSettings, setIsBackupSettings] = useState(false);
+
+ useEffect(() => {
+ if (parsedQueryString.backup) {
+ setIsBackupSettings(true);
+ } else {
+ setIsBackupSettings(false);
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.DNS}`}
+
+ {
+ isBackupSettings
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Backups/Backups.jsx b/src/react/src/containers/Backups/Backups.jsx
new file mode 100644
index 000000000..d955a1fc4
--- /dev/null
+++ b/src/react/src/containers/Backups/Backups.jsx
@@ -0,0 +1,388 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getBackupList, handleAction, scheduleBackup } from '../../ControlPanelService/Backup';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Spinner from '../../components/Spinner/Spinner';
+import { useSelector, useDispatch } from 'react-redux';
+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 { 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,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ backups: [],
+ backupFav: [],
+ toggledAll: false,
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/backup/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.backups]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = backups => {
+ backups[0]['FOCUSED'] = backups[0]['NAME'];
+ setState({ ...state, backups });
+ dispatch(addControlPanelContentFocusedElement(backups[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let backups = [...state.backups];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(backups);
+ return;
+ }
+
+ let focusedElementPosition = backups.findIndex(backup => backup.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== backups.length - 1) {
+ let nextFocusedElement = backups[focusedElementPosition + 1];
+ backups[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, backups });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let backups = [...state.backups];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(backups);
+ return;
+ }
+
+ let focusedElementPosition = backups.findIndex(backup => backup.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = backups[focusedElementPosition - 1];
+ backups[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, backups });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return configureRestoreSettings();
+ case 68: return download();
+ default: break;
+ }
+ }
+ }
+
+ const configureRestoreSettings = () => {
+ props.history.push(`/list/backup?backup=${controlPanelFocusedElement}`);
+ }
+
+ const download = () => {
+ window.open(`/api/v1/download/backup?backup=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { backups } = state;
+ let currentBackupData = backups.filter(backup => backup.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentBackupData.delete_conf, `/api/v1/delete/cron/?job=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ 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 => {
+ let backups = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['isChecked'] = false;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ backups.push(data[i]);
+ }
+
+ return backups;
+ }
+
+ const backups = () => {
+ const { backups } = state;
+ const result = [];
+ const backupFav = { ...state.backupFav };
+
+ backups.forEach(backup => {
+ backup.FOCUSED = controlPanelFocusedElement === backup.NAME;
+
+ if (backupFav[backup.NAME]) {
+ backup.STARRED = backupFav[backup.NAME];
+ } else {
+ backup.STARRED = 0;
+ }
+
+ result.push(backup);
+ });
+
+ return result.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, backups } = state;
+ let duplicate = [...selection];
+ let backupDuplicate = [...backups];
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = backupDuplicate.findIndex(backup => backup.NAME === name);
+ backupDuplicate[incomingItem].isChecked = !backupDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, backups: backupDuplicate, selection: duplicate });
+ }
+
+ const toggleFav = (value, type) => {
+ const { backupFav } = state;
+ let backupFavDuplicate = backupFav;
+
+ if (type === 'add') {
+ backupFavDuplicate[value] = 1;
+
+ addFavorite(value, 'backup')
+ .then(() => {
+ setState({ ...state, backupFav: backupFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ backupFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'backup')
+ .then(() => {
+ setState({ ...state, backupFav: backupFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const backupsDuplicate = [...state.backups];
+
+ if (toggled) {
+ let backupNames = [];
+
+ let backups = backupsDuplicate.map(backup => {
+ backupNames.push(backup.NAME);
+ backup.isChecked = true;
+ return backup;
+ });
+
+ setState({ ...state, backups, selection: backupNames, toggledAll: toggled });
+ } else {
+ let backups = backupsDuplicate.map(backup => {
+ backup.isChecked = false;
+ return backup;
+ });
+ setState({ ...state, backups, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setLoading(false);
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ const scheduleBackupButton = () => {
+ setLoading(true);
+ scheduleBackup()
+ .then(result => {
+ if (result.data.error) {
+ displayModal(result.data.error, '');
+ } else {
+ displayModal(result.data.ok, '');
+ }
+ })
+ .catch(err => console.error(err));
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.BACKUP}`}
+
+
+
+
+
+ {i18n["Create Backup"]}
+
+
+
+
+ {i18n['backup exclusions']}
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {backups()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Backups;
diff --git a/src/react/src/containers/Backups/Backups.scss b/src/react/src/containers/Backups/Backups.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/Backups/Exclusions/index.jsx b/src/react/src/containers/Backups/Exclusions/index.jsx
new file mode 100644
index 000000000..9a7e8168d
--- /dev/null
+++ b/src/react/src/containers/Backups/Exclusions/index.jsx
@@ -0,0 +1,161 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from 'src/actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from 'src/actions/MainNavigation/mainNavigationActions';
+import * as MainNavigation from 'src/actions/MainNavigation/mainNavigationActions';
+import SearchInput from 'src/components/MainNav/Toolbar/SearchInput/SearchInput';
+import LeftButton from 'src/components/MainNav/Toolbar/LeftButton/LeftButton';
+import Toolbar from 'src/components/MainNav/Toolbar/Toolbar';
+import Spinner from 'src/components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import { Helmet } from 'react-helmet';
+import Exclusion from 'src/components/Backup/Exclusion';
+import { getBackupExclusions } from 'src/ControlPanelService/Backup';
+
+const BackupExclusions = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [state, setState] = useState({
+ exclusions: [],
+ loading: false,
+ total: 0
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/backup/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData();
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.exclusions]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = exclusions => {
+ exclusions[0]['FOCUSED'] = exclusions[0]['NAME'];
+ setState({ ...state, exclusions });
+ dispatch(addControlPanelContentFocusedElement(exclusions[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let exclusions = [...state.exclusions];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(exclusions);
+ return;
+ }
+
+ let focusedElementPosition = exclusions.findIndex(exclusion => exclusion.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== exclusions.length - 1) {
+ let nextFocusedElement = exclusions[focusedElementPosition + 1];
+ exclusions[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, exclusions });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let exclusions = [...state.exclusions];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(exclusions);
+ return;
+ }
+
+ let focusedElementPosition = exclusions.findIndex(exclusion => exclusion.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = exclusions[focusedElementPosition - 1];
+ exclusions[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, exclusions });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const fetchData = () => {
+ setState({ ...state, loading: true });
+
+ getBackupExclusions()
+ .then(result => {
+ setState({
+ exclusions: reformatData(result.data.data),
+ loading: false
+ });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const reformatData = data => {
+ let exclusions = [];
+
+ for (let i in data) {
+ exclusions.push({ NAME: i, ITEMS: data[i] });
+ }
+
+ return exclusions;
+ }
+
+ const exclusions = () => {
+ return state.exclusions.map((item, index) => );
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.BACKUP}`}
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {state.loading ? : exclusions()}
+
+
+ );
+}
+
+export default BackupExclusions;
diff --git a/src/react/src/containers/ControlPanelContent/ControlPanelContent.jsx b/src/react/src/containers/ControlPanelContent/ControlPanelContent.jsx
new file mode 100644
index 000000000..5d737f3a3
--- /dev/null
+++ b/src/react/src/containers/ControlPanelContent/ControlPanelContent.jsx
@@ -0,0 +1,232 @@
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { addActiveElement, removeFocusedElement } from "../../actions/MainNavigation/mainNavigationActions";
+import EditInternetProtocol from '../../components/InternetProtocol/Edit/EditInternetProtocol';
+import AddInternetProtocol from '../../components/InternetProtocol/Add/AddInternetProtocol';
+import EditServerNginx from 'src/components/Server/Edit/Nginx/EditServerNginx';
+import Postgresql from 'src/components/Server/Edit/Postgresql/Postgresql';
+import EditBackupExclusions from 'src/components/Backup/Exclusion/Edit';
+import { Route, Switch, Redirect, useHistory } from "react-router-dom";
+import InternetProtocols from '../InternetProtocols/InternetProtocols';
+import AddWebDomain from '../../components/WebDomain/Add/AddWebDomain';
+import EditDatabase from '../../components/Database/Edit/EditDatabase';
+import EditFirewall from '../../components/Firewall/Edit/EditFirewall';
+import Hotkeys from '../../components/ControlPanel/Hotkeys/Hotkeys';
+import AddDatabase from '../../components/Database/Add/AddDatabase';
+import AddFirewall from '../../components/Firewall/Add/AddFirewall';
+import EditCronJob from '../../components/CronJob/Edit/EditCronJob';
+import EditPackage from '../../components/Package/Edit/EditPackage';
+import EditHttpd from 'src/components/Server/Edit/Httpd/EditHttpd';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import AddCronJob from '../../components/CronJob/Add/AddCronJob';
+import AddPackage from '../../components/Package/Add/AddPackage';
+import EditServer from '../../components/Server/Edit/EditServer';
+import Dovecot from 'src/components/Server/Edit/Dovecot/Dovecot';
+import EditMailWrapper from '../EditMailWrapper/EditMailWrapper';
+import Service from 'src/components/Server/Edit/Service/Service';
+import AddMailWrapper from '../AddMailWrapper/AddMailWrapper';
+import EditDNSWrapper from '../EditDNSWrapper/EditDNSWrapper';
+import EditWeb from '../../components/WebDomain/Edit/EditWeb';
+import EditPhp from 'src/components/Server/Edit/PHP/EditPhp';
+import Databases from '../../containers/Databases/Databases';
+import Firewalls from '../../containers/Firewalls/Firewalls';
+import EditUser from '../../components/User/Edit/EditUser';
+import Bind9 from 'src/components/Server/Edit/Bind9/Bind9';
+import Mysql from 'src/components/Server/Edit/Mysql/Mysql';
+import AddDNSWrapper from '../AddDNSWrapper/AddDNSWrapper';
+import BackupWrapper from '../BackupWrapper/BackupWrapper';
+import AddBanIP from 'src/components/Firewall/Add/Banlist';
+import CronJobs from '../../containers/CronJobs/CronJobs';
+import Packages from '../../containers/Packages/Packages';
+import { services } from 'src/ControlPanelService/Server';
+import AddUser from '../../components/User/Add/AddUser';
+import Updates from '../../containers/Updates/Updates';
+import Servers from '../../containers/Servers/Servers';
+import MainNav from '../../components/MainNav/MainNav';
+import BackupExclusions from '../Backups/Exclusions';
+import MailWrapper from '../MailWrapper/MailWrapper';
+import Spinner from 'src/components/Spinner/Spinner';
+import DNSWrapper from '../DNSWrapper/DNSWrapper';
+import Statistics from '../Statistics/Statistics';
+import Users from '../../containers/Users/Users';
+import RRDs from '../../containers/RRDs/RRDs';
+import BanList from '../Firewalls/Banlist';
+import Web from '../../containers/Web/Web';
+import Search from '../Search/Search';
+import Logs from '../Logs/Logs';
+
+import './ControlPanelContent.scss';
+
+const ControlPanelContent = props => {
+ const { userName } = useSelector(state => state.session);
+ const history = useHistory();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [hotkeysList, setHotkeysList] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ if (!userName) {
+ return history.push('/login');
+ } else {
+ setLoading(false);
+ }
+ }, [userName]);
+
+ useEffect(() => {
+ dispatch(removeFocusedElement());
+ window.addEventListener("keyup", switchPanelTab);
+ window.addEventListener("keyup", addNewObject);
+
+ return () => {
+ window.removeEventListener("keyup", switchPanelTab);
+ window.removeEventListener("keyup", addNewObject);
+ }
+ }, []);
+
+ const switchPanelTab = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (isSearchInputFocused) {
+ return;
+ }
+
+ switch (event.keyCode) {
+ case 49: return history.push('/list/user/');
+ case 50: return history.push('/list/web/');
+ case 51: return history.push('/list/dns/');
+ case 52: return history.push('/list/mail/');
+ case 53: return history.push('/list/db/');
+ case 54: return history.push('/list/cron/');
+ case 55: return history.push('/list/backup/');
+ default: break;
+ }
+ }
+
+ const addNewObject = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (isSearchInputFocused) {
+ return;
+ }
+
+ if (event.keyCode === 65) {
+ switch (history.location.pathname) {
+ case '/list/web/': return history.push('/add/web/');
+ case '/list/dns/': return history.push('/add/dns/');
+ case '/list/mail/': return history.push('/add/mail/');
+ case '/list/db/': return history.push('/add/db/');
+ case '/list/cron/': return history.push('/add/cron/');
+ case '/list/backup/exclusions': return history.push('/edit/backup/exclusions/');
+ case '/list/package/': return history.push('/add/package/');
+ case '/list/ip/': return history.push('/add/ip/');
+ case '/list/firewall/': return history.push('/add/firewall/');
+ default: break;
+ }
+ }
+ }
+
+ const handleSearchTerm = searchTerm => {
+ setSearchTerm(searchTerm);
+ history.push({
+ pathname: '/search/',
+ search: `?q=${searchTerm}`
+ });
+ }
+
+ const scrollToTop = () => {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+
+ return (
+
+
+
+ {
+ loading
+ ?
+ : (
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {
+ !!services.length && services.map((service, index) => {
+ if (service === 'iptables') {
+ return
+ } else {
+ return } />
+ }
+ })
+ }
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+ )}
+
+
+
+ hotkeysList.classList.toggle('hide')}>
+
+
+
+
+ scrollToTop()}>
+
+
+
+
+
setHotkeysList(inp)} toggleHotkeys={() => hotkeysList.classList.toggle('hide')} />
+
+ );
+}
+
+export default ControlPanelContent;
diff --git a/src/react/src/containers/ControlPanelContent/ControlPanelContent.scss b/src/react/src/containers/ControlPanelContent/ControlPanelContent.scss
new file mode 100644
index 000000000..4a4495feb
--- /dev/null
+++ b/src/react/src/containers/ControlPanelContent/ControlPanelContent.scss
@@ -0,0 +1,124 @@
+@import 'src/utils/scss/variables';
+
+.content {
+ padding: 0 13%;
+ padding-bottom: 1%;
+ margin-top: 5px;
+
+ > div > div.total {
+ margin-top: 25px;
+ margin-left: 14%;
+ font-size: 12px;
+ color: #929292;
+ }
+}
+
+@media (max-width: 1350px) {
+ .content {
+ padding: 0 10%;
+ }
+}
+
+@media (max-width: 1066px) {
+ .content {
+ padding: 5% 10%;
+ }
+}
+
+@media (max-width: 900px) {
+ .content {
+ margin-top: 40px;
+ }
+}
+
+@media (max-width: 800px) {
+ .content {
+ padding-top: 5%;
+ }
+}
+
+.fixed-buttons {
+ position: fixed;
+ right: 15px;
+ bottom: 15px;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+
+ > div {
+ width: 40px;
+ height: 40px;
+ padding: 5px;
+ margin: 5px;
+ border-radius: 50%;
+ background: #c3c3c3;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ color: $hoverButtonText;
+ background: $primaryLight;
+ }
+
+ &:active {
+ color: $activeButtonText;
+ background: $primaryActive;
+ }
+
+ button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: none;
+ border: none;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ transform: scale(1.32);
+
+ svg {
+ color: white;
+ height: 70%;
+ }
+ }
+ }
+
+ > div:first-child {
+ color: #c3c3c3;
+ background: none;
+
+ &:hover {
+ background: $primaryLight;
+
+ button svg {
+ color: $activeButtonText;
+ }
+ }
+
+ &:active {
+ color: $activeButtonText;
+ background: $primaryActive;
+ }
+
+ button {
+ padding: 0;
+
+ svg {
+ color: #c3c3c3;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 1066px) {
+ .fixed-buttons > div:first-child {
+ display: none;
+ }
+}
+
+@media screen and (max-width: 450px) {
+ .content {
+ padding: 5% 1%;
+ }
+}
diff --git a/src/react/src/containers/CronJobs/CronJobs.jsx b/src/react/src/containers/CronJobs/CronJobs.jsx
new file mode 100644
index 000000000..37cf952cc
--- /dev/null
+++ b/src/react/src/containers/CronJobs/CronJobs.jsx
@@ -0,0 +1,430 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkAction, getCronList, handleAction } from '../../ControlPanelService/Cron';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import CronJob from '../../components/CronJob/CronJob';
+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 { 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,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ cronJobs: [],
+ cronFav: [],
+ toggledAll: false,
+ cronReports: '',
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/cron/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.cronJobs]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = cronJobs => {
+ cronJobs[0]['FOCUSED'] = cronJobs[0]['NAME'];
+ setState({ ...state, cronJobs });
+ dispatch(addControlPanelContentFocusedElement(cronJobs[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let cronJobs = [...state.cronJobs];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(cronJobs);
+ return;
+ }
+
+ let focusedElementPosition = cronJobs.findIndex(cronJob => cronJob.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== cronJobs.length - 1) {
+ let nextFocusedElement = cronJobs[focusedElementPosition + 1];
+ cronJobs[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, cronJobs });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let cronJobs = [...state.cronJobs];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(cronJobs);
+ return;
+ }
+
+ let focusedElementPosition = cronJobs.findIndex(cronJob => cronJob.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = cronJobs[focusedElementPosition - 1];
+ cronJobs[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, cronJobs });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/cron?job=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { cronJobs } = state;
+ let currentCronJobData = cronJobs.filter(cronJob => cronJob.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentCronJobData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentCronJobData.suspend_conf, `/api/v1/${suspendedStatus}/cron/index.php?job=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { cronJobs } = state;
+ let currentCronJobData = cronJobs.filter(cronJob => cronJob.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentCronJobData.delete_conf, `/api/v1/delete/cron/index.php?job=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ 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 => {
+ let cronJobs = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['isChecked'] = false;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ cronJobs.push(data[i]);
+ }
+
+ return cronJobs;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const cronJobs = () => {
+ const { cronJobs } = state;
+ const result = [];
+ const cronFav = { ...state.cronFav };
+
+ cronJobs.forEach(cronJob => {
+ cronJob.FOCUSED = controlPanelFocusedElement === cronJob.NAME;
+
+ if (cronFav[cronJob.NAME]) {
+ cronJob.STARRED = cronFav[cronJob.NAME];
+ } else {
+ cronJob.STARRED = 0;
+ }
+
+ result.push(cronJob);
+ });
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, cronJobs } = state;
+ let duplicate = [...selection];
+ let cronDuplicate = cronJobs;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = cronDuplicate.findIndex(cronJob => cronJob.NAME === name);
+ cronDuplicate[incomingItem].isChecked = !cronDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, cronJobs: cronDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, Command, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case Command: return 'CMD';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { cronFav } = state;
+ let cronFavDuplicate = cronFav;
+
+ if (type === 'add') {
+ cronFavDuplicate[value] = 1;
+
+ addFavorite(value, 'cron')
+ .then(() => {
+ setState({ ...state, cronFav: cronFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ cronFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'cron')
+ .then(() => {
+ setState({ ...state, cronFav: cronFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const cronJobsDuplicate = [...state.cronJobs];
+
+ if (toggled) {
+ let cronJobNames = [];
+
+ let cronJobs = cronJobsDuplicate.map(cronJob => {
+ cronJobNames.push(cronJob.NAME);
+ cronJob.isChecked = true;
+ return cronJob;
+ });
+
+ setState({ ...state, cronJobs, selection: cronJobNames, toggledAll: toggled });
+ } else {
+ let cronJobs = cronJobsDuplicate.map(cronJob => {
+ cronJob.isChecked = false;
+ return cronJob;
+ });
+ setState({ ...state, cronJobs, selection: [], toggledAll: toggled });
+ }
+ }
+
+ 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) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: !state.modalVisible,
+ text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ const handleCronNotifications = () => {
+ const token = localStorage.getItem("token");
+ const url = `/api/v1/${state.cronReports === 'yes' ? 'delete' : 'add'}/cron/reports/?token=${token}`;
+
+ handleAction(url)
+ .then(res => {
+ displayModal(res.data.message, '');
+ fetchData().then(() => setLoading(false));
+ })
+ .catch(err => console.error(err));
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.CRON}`}
+
+
+
+
+
+
+ {state.cronReports === 'yes' ? i18n['turn off notifications'] : i18n['turn on notifications']}
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {cronJobs()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default CronJobs;
diff --git a/src/react/src/containers/CronJobs/CronJobs.scss b/src/react/src/containers/CronJobs/CronJobs.scss
new file mode 100644
index 000000000..8dda7f547
--- /dev/null
+++ b/src/react/src/containers/CronJobs/CronJobs.scss
@@ -0,0 +1,37 @@
+@import 'src/utils/scss/variables';
+
+.r-menu {
+ > div {
+ > button {
+ font-size: 13px;
+ background: white;
+ border-color: white;
+ color: $textColor;
+
+ &:hover {
+ background: white;
+ border-color: white;
+ color: $textColor;
+ }
+ }
+ }
+}
+
+.cron-wrapper {
+ .r-col .stats > div {
+ flex: none;
+ width: 17%;
+ }
+
+ .actions {
+ > div:nth-child(1) a:hover,
+ > div:nth-child(1) button:hover {
+ background: $primaryLight;
+ }
+
+ > div:nth-child(2) a:hover,
+ > div:nth-child(2) button:hover {
+ background: #ff3438;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/DNSRecords/DNSRecords.jsx b/src/react/src/containers/DNSRecords/DNSRecords.jsx
new file mode 100644
index 000000000..8baf1b888
--- /dev/null
+++ b/src/react/src/containers/DNSRecords/DNSRecords.jsx
@@ -0,0 +1,377 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { bulkAction, getDNSRecordsList, handleAction } from '../../ControlPanelService/Dns';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from 'src/components/ControlPanel/Modal/Modal';
+import DnsRecord from 'src/components/DNSRecord/DNSRecord';
+import { useSelector, useDispatch } from 'react-redux';
+import Spinner from '../../components/Spinner/Spinner';
+import { Link, useHistory } from 'react-router-dom';
+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 { 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,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ dnsRecords: [],
+ dnsRecordFav: [],
+ domain: '',
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.dnsRecords]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = dnsRecords => {
+ dnsRecords[0]['FOCUSED'] = dnsRecords[0]['NAME'];
+ setState({ ...state, dnsRecords });
+ dispatch(addControlPanelContentFocusedElement(dnsRecords[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let dnsRecords = [...state.dnsRecords];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(dnsRecords);
+ return;
+ }
+
+ let focusedElementPosition = dnsRecords.findIndex(dnsRecord => dnsRecord.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== dnsRecords.length - 1) {
+ let nextFocusedElement = dnsRecords[focusedElementPosition + 1];
+ dnsRecords[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, dnsRecords });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let dnsRecords = [...state.dnsRecords];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(dnsRecords);
+ return;
+ }
+
+ let focusedElementPosition = dnsRecords.findIndex(dnsRecord => dnsRecord.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = dnsRecords[focusedElementPosition - 1];
+ dnsRecords[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, dnsRecords });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ if (event.keyCode === 13) {
+ return handleEdit();
+ } else if (event.keyCode === 8) {
+ return handleDelete();
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/dns/?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { databases } = state;
+ let currentDatabaseData = databases.filter(database => database.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentDatabaseData.delete_conf, `/api/v1/delete/database/?domain=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ let parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ 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 => {
+ let dnsRecords = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ dnsRecords.push(data[i]);
+ }
+
+ return dnsRecords;
+ }
+
+ const dnsRecords = () => {
+ const { dnsRecords } = state;
+ const result = [];
+ const dnsRecordFav = { ...state.dnsRecordFav };
+
+ dnsRecords.forEach(dnsRecord => {
+ dnsRecord.FOCUSED = controlPanelFocusedElement === dnsRecord.NAME;
+
+ if (dnsRecordFav[dnsRecord.NAME]) {
+ dnsRecord.STARRED = dnsRecordFav[dnsRecord.NAME];
+ } else {
+ dnsRecord.STARRED = 0;
+ }
+
+ result.push(dnsRecord);
+ });
+
+ return result.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, dnsRecords } = state;
+ let duplicate = [...selection];
+ let dnsDuplicate = dnsRecords;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = dnsDuplicate.findIndex(dns => dns.NAME === name);
+ dnsDuplicate[incomingItem].isChecked = !dnsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, dnsRecords: dnsDuplicate, selection: duplicate });
+ }
+
+ const toggleFav = (value, type) => {
+ const { dnsRecordFav } = state;
+ let dnsRecFavDuplicate = dnsRecordFav;
+
+ if (type === 'add') {
+ dnsRecFavDuplicate[value] = 1;
+
+ addFavorite(value, 'dns_rec')
+ .then(() => {
+ setState({ ...state, dnsRecordFav: dnsRecFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ dnsRecFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'dns_rec')
+ .then(() => {
+ setState({ ...state, dnsRecordFav: dnsRecFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const dnsRecordsDuplicate = [...state.dnsRecords];
+
+ if (toggled) {
+ let dnsRecordNames = []
+
+ let dnsRecords = dnsRecordsDuplicate.map(dnsRecord => {
+ dnsRecordNames.push(dnsRecord.NAME);
+ dnsRecord.isChecked = true;
+ return dnsRecord;
+ });
+
+ setState({ ...state, dnsRecords, selection: dnsRecordNames, toggledAll: toggled });
+ } else {
+ let dnsRecords = dnsRecordsDuplicate.map(dnsRecord => {
+ dnsRecord.isChecked = false;
+ return dnsRecord;
+ });
+
+ setState({ ...state, dnsRecords, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection, state.domain)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.DNS}`}
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+ {loading
+ ?
+ : (
+ <>
+
+
+ {`${i18n['Listing']} ${state.domain}`}
+
+ {dnsRecords()}
+
+
+
{state.totalAmount}
+
+ {i18n['Back']}
+
+
+ >
+ )
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/DNSRecords/DNSRecords.scss b/src/react/src/containers/DNSRecords/DNSRecords.scss
new file mode 100644
index 000000000..f50a0545a
--- /dev/null
+++ b/src/react/src/containers/DNSRecords/DNSRecords.scss
@@ -0,0 +1,81 @@
+@import 'src/utils/scss/variables';
+
+.dns-records {
+ div.subtitle {
+ color: $primary;
+ font-size: 12px;
+ margin: 30px 0 18px 14.3%;
+ text-transform: uppercase;
+ font-weight: bold;
+ }
+
+ .dns-records-wrapper {
+ > div:nth-child(2) {
+ border-top: 1px solid #ddd;
+ }
+
+ div.list-item {
+ .r-col {
+ .stats {
+ > div {
+ flex: unset;
+ }
+
+ .c-1 {
+ width: 14.3%;
+ }
+
+ .c-2 {
+ margin-left: 10px;
+ width: 14.3%;
+ }
+
+ .c-3 {
+ margin-left: 0;
+ width: 14.3%;
+
+ span {
+ color: #888;
+ font-weight: normal;
+ }
+ }
+
+ .c-4 {
+ width: 550px;
+
+ .stat {
+ display: block;
+ white-space: normal;
+ overflow: hidden;
+ width: 100%;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .footer-actions-wrapper {
+ display: flex;
+ align-items: center;
+ margin-top: 2rem;
+ padding-bottom: 1.5rem;
+
+ .total {
+ margin: 0 3.5rem 0 14.3%;
+ }
+
+ .back {
+ a {
+ padding: 8px 38px;
+ color: #777;
+ background: #DFDEDD;
+ border: 1px solid #DFDEDD;
+ border-radius: 3px;
+ font-size: 13px;
+ font-weight: bold;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/DNSWrapper/DNSWrapper.jsx b/src/react/src/containers/DNSWrapper/DNSWrapper.jsx
new file mode 100644
index 000000000..73eaa5ada
--- /dev/null
+++ b/src/react/src/containers/DNSWrapper/DNSWrapper.jsx
@@ -0,0 +1,35 @@
+import QueryString from 'qs';
+import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import DnsRecords from '../DNSRecords/DNSRecords';
+import DomainNameSystems from '../DomainNameSystems/DomainNameSystems';
+
+export default function DNSWrapper(props) {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const [isDnsRecords, setIsDnsRecords] = useState(false);
+
+ useEffect(() => {
+ if (parsedQueryString.domain) {
+ setIsDnsRecords(true);
+ } else {
+ setIsDnsRecords(false);
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.DNS}`}
+
+ {
+ isDnsRecords
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Databases/Databases.jsx b/src/react/src/containers/Databases/Databases.jsx
new file mode 100644
index 000000000..098484137
--- /dev/null
+++ b/src/react/src/containers/Databases/Databases.jsx
@@ -0,0 +1,421 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkAction, getDatabaseList, handleAction } from '../../ControlPanelService/Db';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Database from '../../components/Database/Database';
+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';
+import { Link } from 'react-router-dom';
+
+const Databases = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ databases: [],
+ dbFav: [],
+ toggledAll: false,
+ dbAdmin: '',
+ dbAdminLink: '',
+ db_myadmin_link: '',
+ db_pgadmin_link: '',
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/db/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.databases]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = databases => {
+ databases[0]['FOCUSED'] = databases[0]['NAME'];
+ setState({ ...state, databases });
+ dispatch(addControlPanelContentFocusedElement(databases[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let databases = [...state.databases];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(databases);
+ return;
+ }
+
+ let focusedElementPosition = databases.findIndex(database => database.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== databases.length - 1) {
+ let nextFocusedElement = databases[focusedElementPosition + 1];
+ databases[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, databases });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let databases = [...state.databases];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(databases);
+ return;
+ }
+
+ let focusedElementPosition = databases.findIndex(database => database.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = databases[focusedElementPosition - 1];
+ databases[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, databases });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/database?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { databases } = state;
+ let currentDatabaseData = databases.filter(database => database.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentDatabaseData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentDatabaseData.suspend_conf, `/api/v1/${suspendedStatus}/database/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { databases } = state;
+ let currentDatabaseData = databases.filter(database => database.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentDatabaseData.delete_conf, `/api/v1/delete/database/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ 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,
+ db_myadmin_link: result.data.db_myadmin_link,
+ db_pgadmin_link: result.data.db_pgadmin_link,
+ dbFav: result.data.dbFav,
+ selection: [],
+ toggledAll: false,
+ totalAmount: result.data.totalAmount
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let databases = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ databases.push(data[i]);
+ }
+
+ return databases;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const databases = () => {
+ const { databases } = state;
+ const result = [];
+ const dbFav = { ...state.dbFav };
+
+ databases.forEach(database => {
+ database.FOCUSED = controlPanelFocusedElement === database.NAME;
+
+ if (dbFav[database.NAME]) {
+ database.STARRED = dbFav[database.NAME];
+ } else {
+ database.STARRED = 0;
+ }
+
+ result.push(database);
+ });
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, databases } = state;
+ let duplicate = [...selection];
+ let dbDuplicate = databases;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = dbDuplicate.findIndex(db => db.NAME === name);
+ dbDuplicate[incomingItem].isChecked = !dbDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, databases: dbDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, Database, Disk, User, Host, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case Database: return 'DATABASE';
+ case Disk: return 'U_DISK';
+ case User: return 'DBUSER';
+ case Host: return 'HOST';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { dbFav } = state;
+ let dbFavDuplicate = dbFav;
+
+ if (type === 'add') {
+ dbFavDuplicate[value] = 1;
+
+ addFavorite(value, 'db')
+ .then(() => {
+ setState({ ...state, dbFav: dbFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ dbFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'db')
+ .then(() => {
+ setState({ ...state, dbFav: dbFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const databasesDuplicate = [...state.databases];
+
+ if (toggled) {
+ let dbNames = []
+
+ let databases = databasesDuplicate.map(database => {
+ dbNames.push(database.NAME);
+ database.isChecked = true;
+ return database;
+ });
+
+ setState({ ...state, databases, selection: dbNames, toggledAll: toggled });
+ } else {
+ let databases = databasesDuplicate.map(database => {
+ database.isChecked = false;
+ return database;
+ });
+
+ setState({ ...state, databases, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.DB}`}
+
+
+
+
+
+ {state.db_myadmin_link && phpMyAdmin}
+ {state.db_pgadmin_link && phpPgAdmin}
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {databases()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Databases;
diff --git a/src/react/src/containers/Databases/Databases.scss b/src/react/src/containers/Databases/Databases.scss
new file mode 100644
index 000000000..708dd998d
--- /dev/null
+++ b/src/react/src/containers/Databases/Databases.scss
@@ -0,0 +1,16 @@
+.r-menu {
+ > div {
+ > button {
+ font-size: 13px;
+ background: white;
+ border-color: white;
+ color: gray;
+
+ &:hover {
+ background: white;
+ border-color: white;
+ color: gray;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/DomainNameSystems/DomainNameSystems.jsx b/src/react/src/containers/DomainNameSystems/DomainNameSystems.jsx
new file mode 100644
index 000000000..7bf7718c0
--- /dev/null
+++ b/src/react/src/containers/DomainNameSystems/DomainNameSystems.jsx
@@ -0,0 +1,426 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkDomainAction, getDnsList, handleAction } from '../../ControlPanelService/Dns';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import DomainNameSystem from '../../components/DomainNameSystem/DomainNameSystem';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Spinner from '../../components/Spinner/Spinner';
+import './DomainNameSystems.scss';
+
+import { useDispatch, useSelector } from 'react-redux';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+const DomainNameSystems = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { 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,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ domainNameSystems: [],
+ dnsFav: [],
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: '',
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/dns/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.domainNameSystems]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = domainNameSystems => {
+ domainNameSystems[0]['FOCUSED'] = domainNameSystems[0]['NAME'];
+ setState({ ...state, domainNameSystems });
+ dispatch(addControlPanelContentFocusedElement(domainNameSystems[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let domainNameSystems = [...state.domainNameSystems];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(domainNameSystems);
+ return;
+ }
+
+ let focusedElementPosition = domainNameSystems.findIndex(domainNameSystem => domainNameSystem.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== domainNameSystems.length - 1) {
+ let nextFocusedElement = domainNameSystems[focusedElementPosition + 1];
+ domainNameSystems[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, domainNameSystems });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let domainNameSystems = [...state.domainNameSystems];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(domainNameSystems);
+ return;
+ }
+
+ let focusedElementPosition = domainNameSystems.findIndex(domainNameSystem => domainNameSystem.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = domainNameSystems[focusedElementPosition - 1];
+ domainNameSystems[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, domainNameSystems });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 76: return handleLogs();
+ case 78: return handleAddRecord();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleAddRecord = () => {
+ props.history.push(`/add/dns/?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleLogs = () => {
+ props.history.push(`/list/dns?domain=${controlPanelFocusedElement}&type=access`);
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/dns?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { domainNameSystems } = state;
+ let currentDomainNameSystemData = domainNameSystems.filter(domainNameSystem => domainNameSystem.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentDomainNameSystemData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentDomainNameSystemData.suspend_conf, `/api/v1/${suspendedStatus}/dns/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { domainNameSystems } = state;
+ let currentDomainNameSystemData = domainNameSystems.filter(domainNameSystem => domainNameSystem.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentDomainNameSystemData.delete_conf, `/api/v1/delete/dns/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getDnsList()
+ .then(result => {
+ setState({
+ ...state,
+ domainNameSystems: reformatData(result.data.data),
+ dnsFav: result.data.dnsFav,
+ selection: [],
+ toggledAll: false,
+ totalAmount: result.data.totalAmount
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let domainNameSystems = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['RECORDS'] = Number(data[i]['RECORDS']);
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ domainNameSystems.push(data[i]);
+ }
+
+ return domainNameSystems;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const dns = () => {
+ const { domainNameSystems } = state;
+ const dnsFav = { ...state.dnsFav };
+ const result = [];
+
+ domainNameSystems.forEach(domainNameSystem => {
+ domainNameSystem.FOCUSED = controlPanelFocusedElement === domainNameSystem.NAME;
+
+ if (dnsFav[domainNameSystem.NAME]) {
+ domainNameSystem.STARRED = dnsFav[domainNameSystem.NAME];
+ } else {
+ domainNameSystem.STARRED = 0;
+ }
+
+ result.push(domainNameSystem);
+ });
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, domainNameSystems } = state;
+ let duplicate = [...selection];
+ let domainNameSystemsDuplicate = domainNameSystems;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = domainNameSystemsDuplicate.findIndex(domainNameSystem => domainNameSystem.NAME === name);
+ domainNameSystemsDuplicate[incomingItem].isChecked = !domainNameSystemsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, domainNameSystems: domainNameSystemsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => {
+ const first = a[sortingColumn];
+ const second = b[sortingColumn];
+ return (first < second) ? 1 : ((second < first) ? -1 : 0);
+ });
+ } else {
+ return array.sort((a, b) => {
+ const first = a[sortingColumn];
+ const second = b[sortingColumn];
+ return (first > second) ? 1 : ((second > first) ? -1 : 0)
+ });
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, Expire, Domain, IP, Records, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case Expire: return 'EXP';
+ case Domain: return 'NAME';
+ case IP: return 'IP';
+ case Records: return 'RECORDS';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { dnsFav } = state;
+ let dnsFavDuplicate = dnsFav;
+
+ if (type === 'add') {
+ dnsFavDuplicate[value] = 1;
+
+ addFavorite(value, 'dns')
+ .then(() => {
+ setState({ ...state, dnsFav: dnsFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ dnsFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'dns')
+ .then(() => {
+ setState({ ...state, dnsFav: dnsFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const domainNameSystemsDuplicate = [...state.domainNameSystems];
+
+ if (toggled) {
+ let domainNameSystemsNames = [];
+
+ let domainNameSystems = domainNameSystemsDuplicate.map(domainNameSystem => {
+ domainNameSystemsNames.push(domainNameSystem.NAME);
+ domainNameSystem.isChecked = true;
+ return domainNameSystem;
+ });
+
+ setState({ ...state, domainNameSystems, selection: domainNameSystemsNames, toggledAll: toggled });
+ } else {
+ let domainNameSystems = domainNameSystemsDuplicate.map(domainNameSystem => {
+ domainNameSystem.isChecked = false;
+ return domainNameSystem;
+ });
+
+ setState({ ...state, domainNameSystems, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkDomainAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.DNS}`}
+
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading ? : dns()}
+
+
{state.totalAmount}
+
+
+ );
+}
+
+export default DomainNameSystems;
\ No newline at end of file
diff --git a/src/react/src/containers/DomainNameSystems/DomainNameSystems.scss b/src/react/src/containers/DomainNameSystems/DomainNameSystems.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/EditDNSWrapper/EditDNSWrapper.jsx b/src/react/src/containers/EditDNSWrapper/EditDNSWrapper.jsx
new file mode 100644
index 000000000..9dfd91fab
--- /dev/null
+++ b/src/react/src/containers/EditDNSWrapper/EditDNSWrapper.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import EditDomainNameSystem from 'src/components/DomainNameSystem/Edit/EditDomainNameSystem';
+import EditDNSRecord from 'src/components/DNSRecord/Edit/EditDNSRecord';
+import { useHistory } from 'react-router-dom';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function EditDNSWrapper() {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const [isDnsRecord, setIsDnsRecord] = useState(false);
+
+ useEffect(() => {
+ if (parsedQueryString.domain && parsedQueryString.record_id) {
+ setIsDnsRecord(true);
+ } else {
+ setIsDnsRecord(false);
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.DNS}`}
+
+ {
+ isDnsRecord
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/EditMailWrapper/EditMailWrapper.jsx b/src/react/src/containers/EditMailWrapper/EditMailWrapper.jsx
new file mode 100644
index 000000000..f182df2ef
--- /dev/null
+++ b/src/react/src/containers/EditMailWrapper/EditMailWrapper.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import EditMailAccount from 'src/components/MailAccount/Edit/EditMailAccount';
+import EditMail from 'src/components/Mail/Edit/EditMail';
+import { useHistory } from 'react-router-dom';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function EditMailWrapper() {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const [isMailAccount, setIsMailAccount] = useState(false);
+
+ useEffect(() => {
+ if (parsedQueryString.domain && parsedQueryString.account) {
+ setIsMailAccount(true);
+ } else {
+ setIsMailAccount(false);
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.MAIL}`}
+
+ {
+ isMailAccount
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/FileManager/FileManager.js b/src/react/src/containers/FileManager/FileManager.js
new file mode 100644
index 000000000..50b9ddeab
--- /dev/null
+++ b/src/react/src/containers/FileManager/FileManager.js
@@ -0,0 +1,486 @@
+import React, { Component } from 'react';
+import DirectoryList from '../../components/Lists/DirectoryList/DirectoryList';
+import ProgressBar from '../../components/ProgressBar/ProgressBar';
+import { toast, ToastContainer } from 'react-toastify';
+import Hotkeys from '../../components/Hotkeys/Hotkeys';
+import Modal from '../../components/Modal/Modal';
+import 'react-toastify/dist/ReactToastify.css';
+import { withRouter } from 'react-router-dom';
+import Menu from '../../components/Menu/Menu';
+import * as FM from '../../FileManagerHelper';
+import axios from 'axios';
+import { Helmet } from 'react-helmet';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import 'src/containers/App/App.scss';
+import { connect } from 'react-redux';
+
+const server = window.location.origin + "/file_manager/fm_api.php?";
+class FileManager extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ leftList: {
+ path: '',
+ files: { listing: [] },
+ },
+ rightList: {
+ path: '',
+ files: { listing: [] },
+ },
+ currentPath: '',
+ currentUser: '',
+ activeWindow: "left",
+ modalWindow: null,
+ modalVisible: false,
+ cursor: 0,
+ itemName: "",
+ itemPermissions: "",
+ itemType: "",
+ itemsSelected: [],
+ modalInputValue: "",
+ uploadPercent: "0",
+ loading: false
+ }
+ }
+
+ UNSAFE_componentWillMount = () => {
+ if (!this.props.session.userName) return this.props.history.push('/login');
+
+ FM.cacheData(this.state.currentUser, this.props.history, this.props.menuCounters.user.HOME);
+ let currentPath = FM.activeWindowPath();
+ this.setState({
+ currentPath,
+ currentUser: this.props.menuCounters.user.HOME,
+ leftList: { ...this.state.leftList, path: this.props.menuCounters.user.HOME },
+ rightList: { ...this.state.rightList, path: this.props.menuCounters.user.HOME }
+ });
+ this.changeDirectoryOnLoading();
+ }
+
+ componentDidMount = () => {
+ window.addEventListener("keydown", this.switchActiveList);
+ window.addEventListener("keydown", this.toggleActiveListOnTab);
+ document.addEventListener("keydown", this.hotkeysListener);
+
+ if (localStorage.getItem('activeWindow')) {
+ this.setState({ activeWindow: localStorage.getItem('activeWindow') });
+ }
+ }
+
+ componentWillUnmount = () => {
+ window.removeEventListener("keydown", this.switchActiveList);
+ window.removeEventListener("keydown", this.toggleActiveListOnTab);
+ document.removeEventListener("keydown", this.hotkeysListener);
+ }
+
+ cachePaths = () => {
+ localStorage.setItem('activeWindow', this.state.activeWindow);
+ localStorage.setItem('leftListPath', this.state.leftList.path);
+ localStorage.setItem('rightListPath', this.state.rightList.path);
+ }
+
+ setStateAsync = updater => new Promise(resolve => this.setState(updater, resolve));
+
+ changeDirectoryOnLoading = async () => {
+ ['leftList', 'rightList'].map(async (side) => {
+ const result = await FM.changeDirectoryOnLoading(server, `${side}Path`);
+ let path = localStorage.getItem(`${side}Path`);
+ let listing = result.data.listing;
+ await this.setStateAsync({ [side]: { files: { listing }, path } });
+ });
+
+ await this.setStateAsync({ loading: false });
+ }
+
+ changeDirectory = () => {
+ const { leftList, rightList } = this.state;
+ Promise.all([FM.changeDirectory(server, leftList.path), FM.changeDirectory(server, rightList.path)])
+ .then(result => {
+ const [leftListResponse, rightListResponse] = result;
+ let leftListing = leftListResponse.data.listing;
+ let rightListing = rightListResponse.data.listing;
+
+ this.setState({ leftList: { ...leftList, files: { listing: leftListing } }, rightList: { ...rightList, files: { listing: rightListing } }, loading: false });
+
+ this.leftList.resetData();
+ this.rightList.resetData();
+ });
+ }
+
+ toggleActiveListOnTab = (e) => {
+ const { activeWindow, rightList, leftList, currentPath } = this.state;
+
+ if (this.state.modalVisible) {
+ return;
+ }
+
+ if (e.keyCode === 9) {
+ e.preventDefault();
+ if (activeWindow === "left") {
+ this.setState({ activeWindow: "right", currentPath: rightList.path });
+ this.rightList.passData();
+ } else {
+ this.setState({ activeWindow: "left", currentPath: leftList.path });
+ this.leftList.passData();
+ }
+ this.changeQuery(currentPath);
+ this.cachePaths();
+ }
+ }
+
+ passSelection = (itemsSelected) => {
+ this.setState({ itemsSelected });
+ }
+
+ toggleActiveList = (list) => {
+ this.setState({ activeWindow: list });
+ }
+
+ switchActiveList = (e) => {
+ if (this.state.modalVisible) {
+ return;
+ }
+
+ if (e.keyCode === 39) {
+ this.setState({ activeWindow: "right", currentPath: this.state.rightList.path });
+ this.changeQuery(this.state.currentPath);
+ this.rightList.passData();
+ this.cachePaths();
+ } else if (e.keyCode === 37) {
+ this.setState({ activeWindow: "left", currentPath: this.state.leftList.path });
+ this.changeQuery(this.state.currentPath);
+ this.leftList.passData();
+ this.cachePaths();
+ }
+ }
+
+ validateAction = async (url) => {
+ await this.setStateAsync({ loading: true });
+ let response = await FM.validateAction(url);
+ if (response.data.result) {
+ this.changeDirectory();
+ } else {
+ this.showError(response.data.message);
+ }
+ }
+
+ showError = (error) => {
+ toast.error(error, {
+ position: "top-center",
+ autoClose: 3000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true
+ });
+ this.setState({ loading: false });
+ }
+
+ download = () => {
+ const { cursor, currentPath, itemName } = this.state;
+
+ if (cursor !== 0) {
+ window.open('/api/v1/download/file/?path=' + currentPath + '/' + itemName);
+ }
+ }
+
+ checkExistingFileName = (selectedFiles) => {
+ const { activeWindow, leftList, rightList } = this.state;
+ const { existingFileNames, newFiles } = FM.checkExistingFileName(selectedFiles, activeWindow, leftList.files.listing, rightList.files.listing);
+
+ if (existingFileNames.length !== 0) {
+ this.modal("Replace", existingFileNames);
+ this.upload(newFiles);
+ } else {
+ this.upload(selectedFiles);
+ }
+ }
+
+ replaceFiles = (selectedFiles) => {
+ for (let i = 0; i < selectedFiles.length; i++) {
+ this.validateAction(`${server}item=${FM.encodePath(this.state.currentPath)}%2F${selectedFiles[i].name}&dir=${FM.encodePath(this.state.currentPath)}&action=delete_files`);
+ }
+
+ this.upload(selectedFiles);
+ }
+
+ upload = (selectedFiles) => {
+ const formData = new FormData();
+
+ if (selectedFiles.length === 0) {
+ return;
+ }
+
+ for (let i = 0; i < selectedFiles.length; i++) {
+ formData.append('files[]', selectedFiles[i], selectedFiles[i].name);
+ }
+
+ this.setState({ loading: true }, () => {
+ axios.post(`${window.location.origin}/api/v1/upload/?dir=${this.state.currentPath}`, formData, {
+ onUploadProgress: progressEvent => {
+ let uploadPercent = Math.round(progressEvent.loaded / progressEvent.total * 100);
+ this.setState({ uploadPercent });
+ }
+ }).then(() => {
+ this.setState({ uploadPercent: "0" });
+ this.changeDirectory();
+ })
+ });
+ }
+
+ onDelete = async () => {
+ const { itemsSelected, itemName, currentPath } = this.state;
+ if (itemsSelected.length > 0) {
+ await this.setStateAsync({ loading: true });
+ await FM.deleteItems(server, FM.encodePath(currentPath), itemsSelected);
+ await this.setStateAsync({ itemsSelected: [] });
+ this.changeDirectory();
+ } else {
+ this.validateAction(`${server}item=${FM.encodePath(currentPath)}%2F${itemName}&dir=${FM.encodePath(currentPath)}&action=delete_files`);
+ }
+ }
+
+ newFile = () => {
+ let name = this.inputElement.value;
+ this.validateAction(`${server}filename=${name}&dir=${FM.encodePath(this.state.currentPath)}&action=create_file`);
+ }
+
+ newDir = () => {
+ let name = this.inputElement.value;
+ this.validateAction(`${server}dirname=${name}&dir=${FM.encodePath(this.state.currentPath)}&action=create_dir`);
+ }
+
+ onRename = () => {
+ const { modalInputValue, itemType, itemName, currentPath } = this.state;
+ let name = modalInputValue;
+ if (itemType === "f") {
+ this.validateAction(`${server}item=${itemName}&target_name=${name}&dir=${FM.encodePath(currentPath)}&action=rename_file`);
+ } else if (itemType === "d") {
+ this.validateAction(`${server}item=${itemName}&target_name=${name}&dir=${FM.encodePath(currentPath)}%2F&action=rename_directory`);
+ }
+ }
+
+ onChangePermissions = () => {
+ let permissions = this.state.modalInputValue;
+ this.validateAction(`${server}dir=${FM.encodePath(this.state.currentPath)}%2F&item=${this.state.itemName}&permissions=${permissions}&action=chmod_item`);
+ this.setState({ itemPermissions: permissions });
+ }
+
+ archiveItem = () => {
+ let name = this.inputElement.value;
+
+ if (this.state.itemsSelected.length > 0) {
+ this.setState({ loading: true }, () => {
+ let items = [];
+ for (let i = 0; i < this.state.itemsSelected.length; i++) {
+ let path = `${this.state.currentPath}/`;
+ items.push(path += this.state.itemsSelected[i]);
+ }
+ this.validateAction(`${server}items=${items}&dst_item=${FM.encodePath(name)}&action=pack_item`);
+ this.setState({ itemsSelected: [] });
+ })
+ } else {
+ this.validateAction(`${server}items=${FM.encodePath(this.state.currentPath)}%2F${this.state.itemName}&dst_item=${FM.encodePath(name)}&action=pack_item`);
+ }
+ }
+
+ extractItem = () => {
+ let name = this.inputElement.value;
+ this.validateAction(`${server}item=${FM.encodePath(this.state.currentPath)}%2F${this.state.itemName}&filename=${this.state.itemName}&dir=${FM.encodePath(this.state.currentPath)}&dir_target=${name}&action=unpack_item`);
+ }
+
+ moveItem = async () => {
+ const { currentPath, itemsSelected, itemName } = this.state;
+ let targetDir = this.inputElement.value;
+
+ if (itemsSelected.length > 0) {
+ await this.setStateAsync({ loading: true });
+ await FM.moveItems(server, FM.encodePath(currentPath), targetDir, itemsSelected);
+ await this.setStateAsync({ itemsSelected: [] });
+ this.changeDirectory();
+ } else {
+ this.validateAction(`${server}item=${currentPath}%2F${itemName}&target_name=${targetDir}&action=move_file`);
+ }
+ }
+
+ copyItem = async () => {
+ const { currentPath, itemsSelected, itemName } = this.state;
+ let targetDir = this.inputElement.value;
+
+ if (itemsSelected.length > 0) {
+ await this.setStateAsync({ loading: true });
+ await FM.copyItems(server, FM.encodePath(currentPath), targetDir, itemsSelected);
+ await this.setStateAsync({ itemsSelected: [] });
+ this.changeDirectory();
+ } else {
+ this.validateAction(`${server}item=${currentPath}%2F${itemName}&filename=${itemName}&dir=${currentPath}&dir_target=${targetDir}&action=copy_file`);
+ }
+ }
+
+ changeQuery = (path) => {
+ this.props.history.push({
+ pathname: '/list/directory/',
+ search: `?path=${path}`
+ });
+ }
+
+ openDirectory = () => {
+ this.setState({ loading: true }, () => {
+ this.changeDirectory();
+ this.cachePaths();
+ });
+ }
+
+ openCertainDirectory = () => {
+ this.setState({ loading: true }, () => {
+ this.changeDirectory();
+ this.cachePaths();
+ });
+ }
+
+ moveBack = () => {
+ const { activeWindow } = this.state;
+
+ let list = { ...this.state[`${activeWindow}List`] };
+ list.path = list.path.substring(0, list.path.lastIndexOf('/'));
+ this.setState({ [`${activeWindow}List`]: list, currentPath: list.path });
+ this.props.history.push({ search: `?path=${list.path}` })
+ this.openDirectory();
+ }
+
+ addToPath = (name) => {
+ const { activeWindow } = this.state;
+
+ let activeList = { ...this.state[`${activeWindow}List`] };
+ let oldPath = activeList.path;
+ activeList.path = `${oldPath}/${name}`;
+ this.setState({ [`${activeWindow}List`]: activeList, currentPath: activeList.path });
+ }
+
+ changeInputValue = (modalInputValue) => {
+ this.setState({ modalInputValue });
+ }
+
+ changePathAfterToggle = (currentPath) => {
+ this.setState({ currentPath });
+ }
+
+ changePath = (currentPath) => {
+ if (this.state.activeWindow === "left") {
+ this.setState({ leftList: { files: { ...this.state.leftList.files }, path: currentPath }, currentPath });
+ } else {
+ this.setState({ rightList: { files: { ...this.state.rightList.files }, path: currentPath }, currentPath });
+ }
+ }
+
+ passData = (cursor, itemName, itemPermissions, itemType) => {
+ this.setState({ cursor, itemName, itemPermissions, itemType });
+ }
+
+ closeModal = () => {
+ this.setState({ modalVisible: false });
+ }
+
+ hotkeysListener = (e) => {
+ if (this.state.modalVisible) {
+ return;
+ }
+
+ if (e.keyCode === 72) {
+ this.hotkeys();
+ }
+ }
+
+ hotkeys = () => {
+ if (this.state.hotkeysPanel === "inactive") {
+ this.setState({ hotkeysPanel: "active" });
+ } else {
+ this.setState({ hotkeysPanel: "inactive" });
+ }
+ }
+
+ modal = (type, items, available) => {
+ const { modalVisible, itemName, itemPermissions, currentPath } = this.state;
+ switch (type) {
+ case 'Copy': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Move': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Extract': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Archive': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Permissions': return this.setState({ modalWindow: , modalVisible: true });
+ case 'Rename': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Add directory': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Add file': return this.setState({ modalWindow: this.inputElement = inp} />, modalVisible: true });
+ case 'Delete': return this.setState({ modalWindow: , modalVisible: true });
+ case 'Nothing selected': return this.setState({ modalWindow: , modalVisible: true });
+ case "Replace": return this.setState({ modalWindow: this.replaceFiles(files)} onClose={this.closeModal} />, modalVisible: true });
+ default:
+ break;
+ }
+ }
+
+ render() {
+ const { activeWindow, modalWindow, modalVisible, itemsSelected, itemName, loading, uploadPercent, itemType } = this.state;
+ return (
+
+
+ {this.props.session.i18n['File Manager']}
+
+ {uploadPercent !== "0" &&
}
+
+
+
+ {this.props.session.userName && ['left', 'right'].map((side) =>
+
this[`${side}List`] = el}
+ download={this.download}
+ moveBack={this.moveBack}
+ path={this.state[`${side}List`].path}
+ history={this.props.history}
+ loading={loading}
+ list={side} />
+ )}
+
+
+ this.hotkeysList.classList.toggle('hide')}>
+
+
+
+
+ this.hotkeysList = inp} toggleHotkeys={() => this.hotkeysList.classList.toggle('hide')} />
+
+ {modalVisible && modalWindow}
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ session: state.session,
+ menuCounters: state.menuCounters
+ }
+}
+
+export default connect(mapStateToProps)(withRouter(FileManager));
diff --git a/src/react/src/containers/Firewalls/Banlist/index.jsx b/src/react/src/containers/Firewalls/Banlist/index.jsx
new file mode 100644
index 000000000..54154acc2
--- /dev/null
+++ b/src/react/src/containers/Firewalls/Banlist/index.jsx
@@ -0,0 +1,308 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from 'src/actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from 'src/actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getBanList, handleAction } from 'src/ControlPanelService/Firewalls';
+import * as MainNavigation from 'src/actions/MainNavigation/mainNavigationActions';
+import SearchInput from 'src/components/MainNav/Toolbar/SearchInput/SearchInput';
+import LeftButton from 'src/components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from 'src/components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from 'src/components/MainNav/Toolbar/Select/Select';
+import Toolbar from 'src/components/MainNav/Toolbar/Toolbar';
+import Modal from 'src/components/ControlPanel/Modal/Modal';
+import { useDispatch, useSelector } from 'react-redux';
+import Spinner from 'src/components/Spinner/Spinner';
+import Ban from 'src/components/Firewall/Ban';
+import { Helmet } from 'react-helmet';
+import { useHistory } from 'react-router';
+
+import './styles.scss';
+
+const BanLists = props => {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ 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,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ banIps: [],
+ selection: [],
+ toggledAll: false,
+ sorting: i18n.Action,
+ order: "descending",
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/firewall/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.banIps]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = banIps => {
+ banIps[0]['FOCUSED'] = banIps[0]['NAME'];
+ setState({ ...state, banIps });
+ dispatch(addControlPanelContentFocusedElement(banIps[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let banIps = [...state.banIps];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement.NAME === '') {
+ initFocusedElement(banIps);
+ return;
+ }
+
+ let focusedElementPosition = banIps.findIndex(banIp => banIp.NAME === controlPanelFocusedElement.NAME);
+
+ if (focusedElementPosition !== banIps.length - 1) {
+ let nextFocusedElement = banIps[focusedElementPosition + 1];
+ banIps[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, banIps });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let banIps = [...state.banIps];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement.NAME === '') {
+ initFocusedElement(banIps);
+ return;
+ }
+
+ let focusedElementPosition = banIps.findIndex(banIp => banIp.NAME === controlPanelFocusedElement.NAME);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = banIps[focusedElementPosition - 1];
+ banIps[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, banIps });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ default: break;
+ }
+ }
+ }
+
+ const handleDelete = () => {
+ const { banIps } = state;
+ let currentBanIpData = banIps.filter(banIp => banIp.NAME === controlPanelFocusedElement.NAME)[0];
+
+ displayModal(currentBanIpData.delete_conf, controlPanelFocusedElement.delete_url);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getBanList()
+ .then(result => {
+ setState({
+ ...state,
+ banIps: reformatData(result.data.data),
+ totalAmount: result.data.total_amount,
+ toggledAll: false,
+ selection: []
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let banIps = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement.NAME === i;
+ banIps.push(data[i]);
+ }
+
+ return banIps;
+ }
+
+ const banIps = () => {
+ let banIps = [...state.banIps];
+
+ banIps.forEach(banIp => {
+ banIp.FOCUSED = controlPanelFocusedElement.NAME === banIp.NAME;
+ });
+
+ return banIps.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, banIps } = state;
+ let duplicate = [...selection];
+ let banIpsDuplicate = banIps;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = banIpsDuplicate.findIndex(banIp => banIp.NAME === name);
+ banIpsDuplicate[incomingItem].isChecked = !banIpsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, banIps: banIpsDuplicate, selection: duplicate });
+ }
+
+ const toggleAll = toggled => {
+ if (toggled) {
+ let banIpNames = [];
+
+ let banIps = state.banIps.map(banIp => {
+ banIpNames.push(banIp.NAME);
+ banIp.isChecked = true;
+ return banIp;
+ });
+
+ setState({ ...state, banIps, selection: banIpNames, toggledAll: toggled });
+ } else {
+ let banIps = state.banIps.map(banIp => {
+ banIp.isChecked = false;
+ return banIp;
+ });
+
+ setState({ ...state, banIps, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ bulkAction(action, selection, state.banIps)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => setLoading(false));
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, actionUrl) => {
+ setModal({ ...modal, visible: !modal.visible, text, actionUrl });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => setLoading(false));
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const modalCancelHandler = () => {
+ setModal({ ...modal, visible: !modal.visible, text: '', actionUrl: '' });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.FIREWALL}`}
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+ {loading
+ ?
+ : (<>
+
+ {banIps()}
+
+
{state.totalAmount}
+
history.push('/list/firewall/')}>{i18n.Back}
+
+
+ >)
+ }
+
+
+ );
+}
+
+export default BanLists;
diff --git a/src/react/src/containers/Firewalls/Banlist/styles.scss b/src/react/src/containers/Firewalls/Banlist/styles.scss
new file mode 100644
index 000000000..801d72443
--- /dev/null
+++ b/src/react/src/containers/Firewalls/Banlist/styles.scss
@@ -0,0 +1,48 @@
+.banlist-wrapper {
+ display: flex;
+ flex-direction: column;
+
+ > div {
+ margin-right: 15px;
+ }
+
+ .list-item {
+ .l-col {
+ div.star {
+ .checkbox + div {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .buttons-wrapper {
+ width: 100%;
+ display: flex;
+ margin-top: 4rem;
+ align-items: center;
+
+ .total {
+ margin-right: 15px;
+ margin-top: 0;
+ }
+
+ button.back {
+ font-weight: 700;
+ font-size: 13px;
+ margin-right: 10px;
+ padding: .35rem 2.25rem;
+ border-radius: 4px;
+ transition: all .2s cubic-bezier(.4,.1,.5,.85);
+ color: #777;
+ background: #dfdedd;
+ border: none;
+ box-shadow: unset;
+
+ &:hover {
+ color: #fff;
+ background: #999;
+ }
+ }
+ }
+}
diff --git a/src/react/src/containers/Firewalls/Firewalls.jsx b/src/react/src/containers/Firewalls/Firewalls.jsx
new file mode 100644
index 000000000..e3656c8e2
--- /dev/null
+++ b/src/react/src/containers/Firewalls/Firewalls.jsx
@@ -0,0 +1,399 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkFirewallAction, getFirewallList, handleAction } from '../../ControlPanelService/Firewalls';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Firewall from '../../components/Firewall/Firewall';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import './Firewalls.scss';
+import { Helmet } from 'react-helmet';
+import { Link } from 'react-router-dom';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+const Firewalls = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { 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,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ firewalls: [],
+ firewallFav: [],
+ selection: [],
+ firewallExtension: '',
+ toggledAll: false,
+ sorting: i18n.Action,
+ order: "descending",
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/firewall/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.firewalls]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = firewalls => {
+ firewalls[0]['FOCUSED'] = firewalls[0]['NAME'];
+ setState({ ...state, firewalls });
+ dispatch(addControlPanelContentFocusedElement(firewalls[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let firewalls = [...state.firewalls];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(firewalls);
+ return;
+ }
+
+ let focusedElementPosition = firewalls.findIndex(firewall => firewall.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== firewalls.length - 1) {
+ let nextFocusedElement = firewalls[focusedElementPosition + 1];
+ firewalls[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, firewalls });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let firewalls = [...state.firewalls];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(firewalls);
+ return;
+ }
+
+ let focusedElementPosition = firewalls.findIndex(firewall => firewall.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = firewalls[focusedElementPosition - 1];
+ firewalls[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, firewalls });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/firewall?rule=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { firewalls } = state;
+ let currentFirewallData = firewalls.filter(firewall => firewall.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentFirewallData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentFirewallData.suspend_conf, `/api/v1/${suspendedStatus}/firewall/index.php?rule=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { firewalls } = state;
+ let currentFirewallData = firewalls.filter(firewall => firewall.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentFirewallData.delete_conf, `/api/v1/delete/firewall/index.php?rule=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getFirewallList()
+ .then(result => {
+ setState({
+ ...state,
+ firewalls: reformatData(result.data.data),
+ firewallFav: result.data.firewallFav,
+ selection: [],
+ firewallExtension: result.data.firewallExtension,
+ totalAmount: result.data.totalAmount,
+ toggledAll: false
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let firewalls = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ firewalls.push(data[i]);
+ }
+
+ return firewalls;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const firewalls = () => {
+ const firewallFav = { ...state.firewallFav };
+ let firewalls = [...state.firewalls];
+
+ firewalls.forEach(firewall => {
+ firewall.FOCUSED = controlPanelFocusedElement === firewall.NAME;
+
+ if (firewallFav[firewall.NAME]) {
+ firewall.STARRED = firewallFav[firewall.NAME];
+ } else {
+ firewall.STARRED = 0;
+ }
+ });
+
+ let sortedResult = sortArray(firewalls);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, firewalls } = state;
+ let duplicate = [...selection];
+ let firewallsDuplicate = firewalls;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = firewallsDuplicate.findIndex(db => db.NAME === name);
+ firewallsDuplicate[incomingItem].isChecked = !firewallsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, firewalls: firewallsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Action, Protocol, Port, Comment, Starred } = i18n;
+
+ switch (sorting) {
+ case Action: return 'ACTION';
+ case Protocol: return 'PROTOCOL';
+ case Port: return 'PORT';
+ case i18n['IP address']: return 'IP';
+ case Comment: return 'COMMENT';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { firewallFav } = state;
+ let firewallFavDuplicate = firewallFav;
+
+ if (type === 'add') {
+ firewallFavDuplicate[value] = 1;
+
+ addFavorite(value, 'firewall')
+ .then(() => {
+ setState({ ...state, firewallFav: firewallFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ firewallFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'firewall')
+ .then(() => {
+ setState({ ...state, firewallFav: firewallFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ if (toggled) {
+ let firewallNames = [];
+
+ let firewalls = state.firewalls.map(firewall => {
+ firewallNames.push(firewall.NAME);
+ firewall.isChecked = true;
+ return firewall;
+ });
+
+ setState({ ...state, firewalls, selection: firewallNames, toggledAll: toggled });
+ } else {
+ let firewalls = state.firewalls.map(firewall => {
+ firewall.isChecked = false;
+ return firewall;
+ });
+
+ setState({ ...state, firewalls, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkFirewallAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, actionUrl) => {
+ setModal({ ...modal, visible: !modal.visible, text, actionUrl });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({ ...modal, visible: !modal.visible, text: '', actionUrl: '' });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.FIREWALL}`}
+
+
+
+
+
+ {i18n['list fail2ban']}
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {firewalls()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Firewalls;
diff --git a/src/react/src/containers/Firewalls/Firewalls.scss b/src/react/src/containers/Firewalls/Firewalls.scss
new file mode 100644
index 000000000..fc14093f8
--- /dev/null
+++ b/src/react/src/containers/Firewalls/Firewalls.scss
@@ -0,0 +1,4 @@
+.firewalls-wrapper .r-col .stats > div {
+ flex: none;
+ width: 17%;
+}
\ No newline at end of file
diff --git a/src/react/src/containers/GenerateCSR/index.jsx b/src/react/src/containers/GenerateCSR/index.jsx
new file mode 100644
index 000000000..c3b2f3612
--- /dev/null
+++ b/src/react/src/containers/GenerateCSR/index.jsx
@@ -0,0 +1,154 @@
+import React, { useEffect, useState } from 'react';
+import { addActiveElement, removeFocusedElement } from "src/actions/MainNavigation/mainNavigationActions";
+import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
+import AddItemLayout from 'src/components/ControlPanel/AddItemLayout/AddItemLayout';
+import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
+import { generateCSR, getCsrInitialData } from 'src/ControlPanelService/Web';
+import { useDispatch, useSelector } from 'react-redux';
+import Spinner from 'src/components/Spinner/Spinner';
+import { useHistory } from 'react-router-dom';
+import HtmlParser from 'react-html-parser';
+
+const GenerateSSL = props => {
+ const token = localStorage.getItem("token");
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [okMessage, setOkMessage] = useState('');
+ const [state, setState] = useState({
+ data: {},
+ generatedData: {},
+ loading: false,
+ domain: ''
+ });
+
+ useEffect(() => {
+ const { domain } = props;
+
+ dispatch(addActiveElement('/list/web/'));
+ dispatch(removeFocusedElement());
+
+ if (domain) {
+ fetchData(domain);
+ } else {
+ fetchData();
+ }
+ }, []);
+
+ const fetchData = (domain = '') => {
+ getCsrInitialData(domain)
+ .then(response => {
+ setState({
+ ...state,
+ domain,
+ generatedData: {},
+ data: response.data,
+ loading: false
+ });
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+
+ const submitFormHandler = event => {
+ event.preventDefault();
+ let newCsr = {};
+
+ for (var [name, value] of (new FormData(event.target)).entries()) {
+ newCsr[name] = value;
+ }
+
+ newCsr['generate'] = 'generate';
+
+ if (Object.keys(newCsr).length !== 0 && newCsr.constructor === Object) {
+ setState({ ...state, loading: true });
+
+ generateCSR(newCsr)
+ .then(result => {
+ if (result.status === 200) {
+ const { error_msg: errorMessage, ok_msg: okMessage, crt, key, csr } = result.data;
+
+ if (errorMessage) {
+ setErrorMessage(errorMessage);
+ setOkMessage('');
+ setState({ ...state, generatedData: {}, loading: false });
+ } else {
+ setErrorMessage('');
+ setOkMessage(okMessage);
+
+ setState({ ...state, generatedData: { crt, key, csr }, loading: false });
+ }
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ return (
+
+
+ {state.loading ? :
+ submitFormHandler(event)} id="add-user">
+
+
+ {
+ Object.entries(state.generatedData).length
+ ? (<>
+
+
+
+
+
+
+
+ props.prePopulateInputs(state.generatedData)}>{i18n.Add}
+ {i18n.Back}
+
+ >)
+ : (<>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.Generate}
+ {i18n.Back}
+
+ >)
+ }
+
+ {errorMessage}
+
+ {HtmlParser(okMessage)}
+
+
+ }
+
+
+ );
+}
+
+export default GenerateSSL;
diff --git a/src/react/src/containers/InternetProtocols/InternetProtocols.jsx b/src/react/src/containers/InternetProtocols/InternetProtocols.jsx
new file mode 100644
index 000000000..f38bf90ca
--- /dev/null
+++ b/src/react/src/containers/InternetProtocols/InternetProtocols.jsx
@@ -0,0 +1,392 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getIpList, handleAction } from '../../ControlPanelService/Ip';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import InternetProtocol from '../../components/InternetProtocol/InternetProtocol';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import './InternetProtocols.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+const InternetProtocols = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ internetProtocols: [],
+ ipFav: [],
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/ip/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.internetProtocols]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = internetProtocols => {
+ internetProtocols[0]['FOCUSED'] = internetProtocols[0]['NAME'];
+ setState({ ...state, internetProtocols });
+ dispatch(addControlPanelContentFocusedElement(internetProtocols[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let internetProtocols = [...state.internetProtocols];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(internetProtocols);
+ return;
+ }
+
+ let focusedElementPosition = internetProtocols.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== internetProtocols.length - 1) {
+ let nextFocusedElement = internetProtocols[focusedElementPosition + 1];
+ internetProtocols[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, internetProtocols });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let internetProtocols = [...state.internetProtocols];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(internetProtocols);
+ return;
+ }
+
+ let focusedElementPosition = internetProtocols.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = internetProtocols[focusedElementPosition - 1];
+ internetProtocols[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, internetProtocols });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/ip/?ip=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { internetProtocols } = state;
+ let currentInternetProtocolData = internetProtocols.filter(pack => pack.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentInternetProtocolData.delete_conf, `/api/v1/delete/ip/?ip=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getIpList()
+ .then(result => {
+ setState({
+ ...state,
+ internetProtocols: reformatData(result.data.data),
+ ipFav: result.data.ipFav,
+ selection: [],
+ totalAmount: result.data.totalAmount,
+ toggledAll: false
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let internetProtocols = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ internetProtocols.push(data[i]);
+ }
+
+ return internetProtocols;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const internetProtocols = () => {
+ const { internetProtocols } = state;
+ const ipFav = { ...state.ipFav };
+ const result = [];
+
+ internetProtocols.forEach(internetProtocol => {
+ internetProtocol.FOCUSED = controlPanelFocusedElement === internetProtocol.NAME;
+
+ if (ipFav[internetProtocol.NAME]) {
+ internetProtocol.STARRED = ipFav[internetProtocol.NAME];
+ } else {
+ internetProtocol.STARRED = 0;
+ }
+
+ result.push(internetProtocol);
+ });
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, internetProtocols } = state;
+ let duplicate = [...selection];
+ let internetProtocolsDuplicate = internetProtocols;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = internetProtocolsDuplicate.findIndex(ip => ip.NAME === name);
+ internetProtocolsDuplicate[incomingItem].isChecked = !internetProtocolsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, internetProtocols: internetProtocolsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, IP, Domains, Netmask, Interface, Owner, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case IP: return 'IP';
+ case Netmask: return 'NETMASK';
+ case Interface: return 'INTERFACE';
+ case Domains: return 'U_WEB_DOMAINS';
+ case Owner: return 'OWNER';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { ipFav } = state;
+ let ipFavDuplicate = ipFav;
+
+ if (type === 'add') {
+ ipFavDuplicate[value] = 1;
+
+ addFavorite(value, 'ip')
+ .then(() => {
+ setState({ ...state, ipFav: ipFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ ipFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'ip')
+ .then(() => {
+ setState({ ...state, ipFav: ipFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ let internetProtocolsDuplicate = [...state.internetProtocols];
+
+ if (toggled) {
+ let ipNames = [];
+
+ let internetProtocols = internetProtocolsDuplicate.map(internetProtocol => {
+ ipNames.push(internetProtocol.NAME);
+ internetProtocol.isChecked = true;
+ return internetProtocol;
+ });
+
+ setState({ ...state, internetProtocols, selection: ipNames, toggledAll: toggled });
+ } else {
+ let internetProtocols = internetProtocolsDuplicate.map(internetProtocol => {
+ internetProtocol.isChecked = false;
+ return internetProtocol;
+ });
+
+ setState({ ...state, internetProtocols, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, actionUrl) => {
+ setModal({ ...modal, visible: true, text, actionUrl });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({ ...modal, visible: false, text: '', actionUrl: '' });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.IP}`}
+
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {internetProtocols()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default InternetProtocols;
diff --git a/src/react/src/containers/InternetProtocols/InternetProtocols.scss b/src/react/src/containers/InternetProtocols/InternetProtocols.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/Logs/Logs.jsx b/src/react/src/containers/Logs/Logs.jsx
new file mode 100644
index 000000000..cbb87f951
--- /dev/null
+++ b/src/react/src/containers/Logs/Logs.jsx
@@ -0,0 +1,175 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import { getLogsList } from '../../ControlPanelService/Logs';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import Log from '../../components/Log/Log';
+import './Logs.scss';
+import { Helmet } from 'react-helmet';
+
+const Logs = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [state, setState] = useState({
+ logs: [],
+ totalAmount: '',
+ loading: false,
+ total: 0
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/log/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData();
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.logs]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = logs => {
+ logs[0]['FOCUSED'] = logs[0]['NAME'];
+ setState({ ...state, logs });
+ dispatch(addControlPanelContentFocusedElement(logs[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let logs = [...state.logs];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(logs);
+ return;
+ }
+
+ let focusedElementPosition = logs.findIndex(log => log.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== logs.length - 1) {
+ let nextFocusedElement = logs[focusedElementPosition + 1];
+ logs[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, logs });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let logs = [...state.logs];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(logs);
+ return;
+ }
+
+ let focusedElementPosition = logs.findIndex(log => log.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = logs[focusedElementPosition - 1];
+ logs[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, logs });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const fetchData = () => {
+ setState({ ...state, loading: true });
+
+ getLogsList()
+ .then(result => {
+ setState({
+ logs: reformatData(result.data.data),
+ totalAmount: result.data.totalAmount,
+ loading: false
+ });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const reformatData = data => {
+ let logs = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ logs.push(data[i]);
+ }
+
+ return logs;
+ }
+
+ const logs = () => {
+ let logs = [...state.logs];
+
+ logs.forEach(log => {
+ log.FOCUSED = controlPanelFocusedElement === log.NAME;
+ });
+
+ return logs.map((item, index) => {
+ return ;
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.LOG}`}
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {state.loading ? : logs()}
+
+
{state.totalAmount}
+
+ );
+}
+
+export default Logs;
\ No newline at end of file
diff --git a/src/react/src/containers/Logs/Logs.scss b/src/react/src/containers/Logs/Logs.scss
new file mode 100644
index 000000000..dea57fb29
--- /dev/null
+++ b/src/react/src/containers/Logs/Logs.scss
@@ -0,0 +1,7 @@
+.logs-list .toolbar .search-input-form input {
+ margin-bottom: 5px;
+}
+
+.logs-list .l-col {
+ width: 14%;
+}
\ No newline at end of file
diff --git a/src/react/src/containers/MailAccounts/MailAccounts.jsx b/src/react/src/containers/MailAccounts/MailAccounts.jsx
new file mode 100644
index 000000000..8eb220349
--- /dev/null
+++ b/src/react/src/containers/MailAccounts/MailAccounts.jsx
@@ -0,0 +1,417 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { bulkMailAccountAction, getMailAccountList, handleAction } from '../../ControlPanelService/Mail';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import MailAccount from 'src/components/MailAccount/MailAccount';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import { useSelector, useDispatch } from 'react-redux';
+import Spinner from '../../components/Spinner/Spinner';
+import { Link } from 'react-router-dom';
+
+import './MailAccounts.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+export default function MailAccounts(props) {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ mailAccounts: [],
+ mailAccountsFav: [],
+ domain: props.domain,
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.mailAccounts]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = mailAccounts => {
+ mailAccounts[0]['FOCUSED'] = mailAccounts[0]['NAME'];
+ setState({ ...state, mailAccounts });
+ dispatch(addControlPanelContentFocusedElement(mailAccounts[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let mailAccounts = [...state.mailAccounts];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(mailAccounts);
+ return;
+ }
+
+ let focusedElementPosition = mailAccounts.findIndex(mailAccount => mailAccount.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== mailAccounts.length - 1) {
+ let nextFocusedElement = mailAccounts[focusedElementPosition + 1];
+ mailAccounts[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, mailAccounts });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let mailAccounts = [...state.mailAccounts];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(mailAccounts);
+ return;
+ }
+
+ let focusedElementPosition = mailAccounts.findIndex(mailAccount => mailAccount.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = mailAccounts[focusedElementPosition - 1];
+ mailAccounts[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, mailAccounts });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/mail?domain=${props.domain}&account=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { mailAccounts } = state;
+ let currentMailData = mailAccounts.filter(mail => mail.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentMailData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentMailData.suspend_conf, `/api/v1/${suspendedStatus}/mail/index.php?domain=${props.domain}&account=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { mailAccounts } = state;
+ let currentMailData = mailAccounts.filter(mail => mail.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentMailData.delete_conf, `/api/v1/delete/mail/index.php?domain=${props.domain}&account=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getMailAccountList(props.domain)
+ .then(result => {
+ setState({
+ ...state,
+ mailAccounts: reformatData(result.data.data),
+ webMail: result.data.webmail,
+ selection: [],
+ toggledAll: false,
+ mailAccountsFav: result.data.mailAccountsFav,
+ totalAmount: result.data.totalAmount
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let mailAccounts = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ mailAccounts.push(data[i]);
+ }
+
+ return mailAccounts;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const mailAccounts = () => {
+ const { mailAccounts } = state;
+ const mailAccountsFav = { ...state.mailAccountsFav };
+ const result = [];
+
+ mailAccounts.forEach(mailAccount => {
+ mailAccount.FOCUSED = controlPanelFocusedElement === mailAccount.NAME;
+
+ if (mailAccountsFav[mailAccount.NAME]) {
+ mailAccount.STARRED = mailAccountsFav[mailAccount.NAME];
+ } else {
+ mailAccount.STARRED = 0;
+ }
+
+ result.push(mailAccount);
+ });
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, mailAccounts } = state;
+ let duplicate = [...selection];
+ let mailAccountsDuplicate = mailAccounts;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = mailAccountsDuplicate.findIndex(mailAccount => mailAccount.NAME === name);
+ mailAccountsDuplicate[incomingItem].isChecked = !mailAccountsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, mailAccounts: mailAccountsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date: date, Accounts, Disk, Starred } = i18n;
+
+ switch (sorting) {
+ case date: return 'DATE';
+ case Accounts: return 'ACCOUNTS';
+ case Disk: return 'U_DISK';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { mailAccountsFav } = state;
+ let mailAccountsFavDuplicate = mailAccountsFav;
+
+ if (type === 'add') {
+ mailAccountsFavDuplicate[value] = 1;
+
+ addFavorite(value, 'mail_acc')
+ .then(() => {
+ setState({ ...state, mailAccountsFav: mailAccountsFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ mailAccountsFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'mail_acc')
+ .then(() => {
+ setState({ ...state, mailAccountsFav: mailAccountsFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const mailAccountsDuplicate = [...state.mailAccounts];
+
+ if (toggled) {
+ let mailAccountNames = [];
+
+ let mailAccounts = mailAccountsDuplicate.map(mailAccount => {
+ mailAccountNames.push(mailAccount.NAME);
+ mailAccount.isChecked = true;
+ return mailAccount;
+ });
+
+ setState({ ...state, mailAccounts, selection: mailAccountNames, toggledAll: toggled });
+ } else {
+ let mailAccounts = mailAccountsDuplicate.map(mailAccount => {
+ mailAccount.isChecked = false;
+ return mailAccount;
+ });
+
+ setState({ ...state, mailAccounts, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+ if (selection.length && action) {
+ setLoading(true);
+ bulkMailAccountAction(action, props.domain, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.MAIL}`}
+
+
+
+
+
+ {loading
+ ?
+ : (
+ <>
+
+
+ {`${i18n['Listing']} ${state.domain}`}
+
+ {mailAccounts()}
+
+
+
{state.totalAmount}
+
+ {i18n['Back']}
+
+
+ >
+ )
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/MailAccounts/MailAccounts.scss b/src/react/src/containers/MailAccounts/MailAccounts.scss
new file mode 100644
index 000000000..7ed611d06
--- /dev/null
+++ b/src/react/src/containers/MailAccounts/MailAccounts.scss
@@ -0,0 +1,49 @@
+@import 'src/utils/scss/variables';
+
+.mail-accounts {
+ div.subtitle {
+ color: $primary;
+ font-size: 12px;
+ margin: 30px 0 18px 14.3%;
+ text-transform: uppercase;
+ font-weight: bold;
+ }
+
+ .mail-accounts-wrapper {
+ > div:nth-child(2) {
+ border-top: 1px solid #ddd;
+ }
+
+ div.list-item {
+ .r-col {
+ .stat.email {
+ width: fit-content;
+ text-transform: none;
+ }
+ }
+ }
+ }
+
+ .footer-actions-wrapper {
+ display: flex;
+ align-items: center;
+ margin-top: 2rem;
+ padding-bottom: 1.5rem;
+
+ .total {
+ margin: 0 3.5rem 0 14.3%;
+ }
+
+ .back {
+ a {
+ padding: 8px 38px;
+ color: #777;
+ background: #DFDEDD;
+ border: 1px solid #DFDEDD;
+ border-radius: 3px;
+ font-size: 13px;
+ font-weight: bold;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/MailWrapper/MailWrapper.jsx b/src/react/src/containers/MailWrapper/MailWrapper.jsx
new file mode 100644
index 000000000..07dea0374
--- /dev/null
+++ b/src/react/src/containers/MailWrapper/MailWrapper.jsx
@@ -0,0 +1,36 @@
+import React, { useEffect, useState } from 'react';
+import MailAccounts from '../MailAccounts/MailAccounts';
+import { useHistory } from 'react-router-dom';
+import Mails from '../Mails/Mails';
+import QueryString from 'qs';
+import { Helmet } from 'react-helmet';
+import { useSelector } from 'react-redux';
+
+export default function MailWrapper(props) {
+ const { i18n } = useSelector(state => state.session);
+ const [mailDomain, setMailDomain] = useState('');
+ const history = useHistory();
+
+ useEffect(() => {
+ const parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+
+ if (parsedQueryString.domain) {
+ setMailDomain(parsedQueryString.domain);
+ } else {
+ setMailDomain('');
+ }
+ }, [history.location]);
+
+ return (
+ <>
+
+ {`Vesta - ${i18n.MAIL}`}
+
+ {
+ mailDomain
+ ?
+ :
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Mails/Mails.jsx b/src/react/src/containers/Mails/Mails.jsx
new file mode 100644
index 000000000..f7e788c60
--- /dev/null
+++ b/src/react/src/containers/Mails/Mails.jsx
@@ -0,0 +1,429 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkAction, getMailList, handleAction } from '../../ControlPanelService/Mail';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Spinner from '../../components/Spinner/Spinner';
+import Mail from '../../components/Mail/Mail';
+import './Mails.scss';
+
+import { useSelector, useDispatch } from 'react-redux';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import { Link } from 'react-router-dom';
+
+const Mails = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ mails: [],
+ mailFav: [],
+ toggledAll: false,
+ webmail: '',
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/mail/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.mails]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = mails => {
+ mails[0]['FOCUSED'] = mails[0]['NAME'];
+ setState({ ...state, mails });
+ dispatch(addControlPanelContentFocusedElement(mails[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let mails = [...state.mails];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(mails);
+ return;
+ }
+
+ let focusedElementPosition = mails.findIndex(mail => mail.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== mails.length - 1) {
+ let nextFocusedElement = mails[focusedElementPosition + 1];
+ mails[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, mails });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let mails = [...state.mails];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(mails);
+ return;
+ }
+
+ let focusedElementPosition = mails.findIndex(mail => mail.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = mails[focusedElementPosition - 1];
+ mails[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, mails });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ case 76: return handleLogs();
+ case 78: return handleAddRecord();
+ case 83: return handleSuspend();
+ default: break;
+ }
+ }
+ }
+
+ const handleAddRecord = () => {
+ props.history.push(`/add/mail/?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleLogs = () => {
+ props.history.push(`/list/mail?domain=${controlPanelFocusedElement}&type=access`);
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/mail?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { mails } = state;
+ let currentMailData = mails.filter(mail => mail.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentMailData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentMailData.suspend_conf, `/api/v1/${suspendedStatus}/mail/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { mails } = state;
+ let currentMailData = mails.filter(mail => mail.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentMailData.delete_conf, `/api/v1/delete/mail/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getMailList()
+ .then(result => {
+ setState({
+ ...state,
+ mails: reformatData(result.data.data),
+ webmail: result.data.webmail,
+ mailFav: result.data.mailFav,
+ selection: [],
+ toggledAll: false,
+ totalAmount: result.data.totalAmount
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let mails = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ mails.push(data[i]);
+ }
+
+ return mails;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const mails = () => {
+ const { mails } = state;
+ const mailFav = { ...state.mailFav };
+ const result = [];
+
+ mails.forEach(mail => {
+ mail.FOCUSED = controlPanelFocusedElement === mail.NAME;
+
+ if (mailFav[mail.NAME]) {
+ mail.STARRED = mailFav[mail.NAME];
+ } else {
+ mail.STARRED = 0;
+ }
+
+ result.push(mail);
+ });
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, mails } = state;
+ let duplicate = [...selection];
+ let mailsDuplicate = mails;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = mailsDuplicate.findIndex(mail => mail.NAME === name);
+ mailsDuplicate[incomingItem].isChecked = !mailsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, mails: mailsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, Domains, Accounts, Disk, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case Domains: return 'domain_account';
+ case Accounts: return 'ACCOUNTS';
+ case Disk: return 'U_DISK';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { mailFav } = state;
+ let mailFavDuplicate = mailFav;
+
+ if (type === 'add') {
+ mailFavDuplicate[value] = 1;
+
+ addFavorite(value, 'mail')
+ .then(() => {
+ setState({ ...state, mailFav: mailFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ mailFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'mail')
+ .then(() => {
+ setState({ ...state, mailFav: mailFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const mailsDuplicate = [...state.mails];
+
+ if (toggled) {
+ let mailNames = [];
+
+ let mails = mailsDuplicate.map(mail => {
+ mailNames.push(mail.NAME);
+ mail.isChecked = true;
+ return mail;
+ });
+
+ setState({ ...state, mails, selection: mailNames, toggledAll: toggled });
+ } else {
+ let mails = mailsDuplicate.map(mail => {
+ mail.isChecked = false;
+ return mail;
+ });
+
+ setState({ ...state, mails, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.MAIL}`}
+
+
+
+
+
+ {state.webmail &&
+ {i18n['open webmail']}
+ }
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {mails()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Mails;
diff --git a/src/react/src/containers/Mails/Mails.scss b/src/react/src/containers/Mails/Mails.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/Packages/Packages.jsx b/src/react/src/containers/Packages/Packages.jsx
new file mode 100644
index 000000000..9ee512446
--- /dev/null
+++ b/src/react/src/containers/Packages/Packages.jsx
@@ -0,0 +1,390 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getPackageList, handleAction } from '../../ControlPanelService/Package';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Package from '../../components/Package/Package';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import './Packages.scss';
+import { Helmet } from 'react-helmet';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+const Packages = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ packages: [],
+ packagesFav: [],
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/package/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.packages]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = packages => {
+ packages[0]['FOCUSED'] = packages[0]['NAME'];
+ setState({ ...state, packages });
+ dispatch(addControlPanelContentFocusedElement(packages[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let packages = [...state.packages];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(packages);
+ return;
+ }
+
+ let focusedElementPosition = packages.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== packages.length - 1) {
+ let nextFocusedElement = packages[focusedElementPosition + 1];
+ packages[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, packages });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let packages = [...state.packages];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(packages);
+ return;
+ }
+
+ let focusedElementPosition = packages.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = packages[focusedElementPosition - 1];
+ packages[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, packages });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ default: break;
+ }
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/package/?package=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { packages } = state;
+ let currentPackageData = packages.filter(pack => pack.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentPackageData.delete_conf, `/api/v1/delete/package/index.php?package=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getPackageList()
+ .then(result => {
+ setState({
+ ...state,
+ packages: reformatData(result.data.data),
+ packagesFav: result.data.packagesFav,
+ totalAmount: result.data.totalAmount,
+ selection: [],
+ toggledAll: false
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = data => {
+ let packages = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ packages.push(data[i]);
+ }
+
+ return packages;
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const packages = () => {
+ const { packages } = state;
+ const packagesFav = { ...state.packagesFav };
+ const result = [];
+
+ packages.forEach(pack => {
+ pack.FOCUSED = controlPanelFocusedElement === pack.NAME;
+
+ if (packagesFav[pack.NAME]) {
+ pack.STARRED = packagesFav[pack.NAME];
+ } else {
+ pack.STARRED = 0;
+ }
+
+ result.push(pack);
+ });
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ const { selection, packages } = state;
+ let duplicate = [...selection];
+ let packagesDuplicate = packages;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = packagesDuplicate.findIndex(pack => pack.NAME === name);
+ packagesDuplicate[incomingItem].isChecked = !packagesDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, packages: packagesDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortingColumn = sortBy(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case i18n['Package Name']: return 'NAME';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ const { packagesFav } = state;
+ let packagesFavDuplicate = packagesFav;
+
+ if (type === 'add') {
+ packagesFavDuplicate[value] = 1;
+
+ addFavorite(value, 'package')
+ .then(() => {
+ setState({ ...state, packagesFav: packagesFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ packagesFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'package')
+ .then(() => {
+ setState({ ...state, packagesFav: packagesFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const packagesDuplicate = [...state.packages];
+
+ if (toggled) {
+ let packageNames = [];
+
+ let packages = packagesDuplicate.map(pack => {
+ packageNames.push(pack.NAME);
+ pack.isChecked = true;
+ return pack;
+ });
+
+ setState({ ...state, packages, selection: packageNames, toggledAll: toggled });
+ } else {
+ let packages = packagesDuplicate.map(pack => {
+ pack.isChecked = false;
+ return pack;
+ });
+
+ setState({ ...state, packages, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setLoading(true);
+ bulkAction(action, selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, actionUrl) => {
+ setModal({ ...modal, visible: !modal.visible, text, actionUrl })
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({ ...modal, visible: false, text: '', actionUrl: '' })
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.PACKAGE}`}
+
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {
+ loading
+ ?
+ : (<>
+ {packages()}
+
{state.totalAmount}
+ >)
+ }
+
+
+
+ );
+}
+
+export default Packages;
diff --git a/src/react/src/containers/Packages/Packages.scss b/src/react/src/containers/Packages/Packages.scss
new file mode 100644
index 000000000..a0012abc2
--- /dev/null
+++ b/src/react/src/containers/Packages/Packages.scss
@@ -0,0 +1,11 @@
+.packages-wrapper {
+ .r-col .stats {
+ .c-1 .stat {
+ margin-left: 1rem;
+ }
+
+ .c-2 {
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/RRDs/RRDs.jsx b/src/react/src/containers/RRDs/RRDs.jsx
new file mode 100644
index 000000000..a7ab40bc1
--- /dev/null
+++ b/src/react/src/containers/RRDs/RRDs.jsx
@@ -0,0 +1,202 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import { getRrdList } from '../../ControlPanelService/RRD';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import Timer from '../../components/RRD/Timer/Timer';
+import RRD from '../../components/RRD/RRD';
+import './RRDs.scss';
+import { Helmet } from 'react-helmet';
+
+const RRDs = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [data, setData] = useState([]);
+ const [state, setState] = useState({
+ period: 'daily',
+ periodI18N: i18n.Daily,
+ time: 15,
+ loading: false,
+ total: 0
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/rrd/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData();
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ };
+ }, [controlPanelFocusedElement, focusedElement, data]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = data => {
+ data[0]['FOCUSED'] = data[0]['NAME'];
+ setData(data);
+ dispatch(addControlPanelContentFocusedElement(data[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(data);
+ return;
+ }
+
+ let focusedElementPosition = data.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== data.length - 1) {
+ let nextFocusedElement = data[focusedElementPosition + 1];
+ data[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setData(data);
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(data);
+ return;
+ }
+
+ let focusedElementPosition = data.findIndex(pack => pack.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = data[focusedElementPosition - 1];
+ data[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setData(data);
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const countDown = () => {
+ if (state.time === 0) {
+ fetchData();
+ } else {
+ setState({ ...state, time: state.time - 1 });
+ }
+ }
+
+ const fetchData = () => {
+ dispatch(removeControlPanelContentFocusedElement());
+
+ setState({ ...state, loading: true });
+
+ getRrdList()
+ .then(result => {
+ setData(reformatData(result.data.data));
+ setState({ ...state, time: 15, loading: false });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const reformatData = data => {
+ let rrds = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = data[i]['TITLE'];
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ rrds.push(data[i]);
+ }
+
+ return rrds;
+ }
+
+ const rrds = () => {
+ let dataDuplicate = [...data];
+
+ dataDuplicate.forEach(rrd => {
+ rrd.FOCUSED = controlPanelFocusedElement === rrd.NAME;
+ });
+
+ return dataDuplicate.map((item, index) => {
+ return ;
+ });
+ }
+
+ const printPeriods = () => {
+ const periods = [i18n.Daily, i18n.Weekly, i18n.Monthly, i18n.Yearly];
+
+ return periods.map(period => ( changePeriod(period)}>{period}
));
+ }
+
+ const periodClass = period => {
+ if (state.periodI18N === period) {
+ return "period active";
+ } else {
+ return "period";
+ }
+ }
+
+ const changePeriod = period => {
+ switch (period) {
+ case i18n.Daily: setState({ ...state, period: 'daily', periodI18N: i18n.Daily, time: 15 }); break;
+ case i18n.Weekly: setState({ ...state, period: 'weekly', periodI18N: i18n.Weekly, time: 15 }); break;
+ case i18n.Monthly: setState({ ...state, period: 'monthly', periodI18N: i18n.Monthly, time: 15 }); break;
+ case i18n.Yearly: setState({ ...state, period: 'yearly', periodI18N: i18n.Yearly, time: 15 }); break;
+ default: break;
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.RRD}`}
+
+
+
+ {printPeriods()}
+
+
+ props.changeSearchTerm(term)} />
+
+
+ {state.loading ? : rrds()}
+
+
+ );
+}
+
+export default RRDs;
\ No newline at end of file
diff --git a/src/react/src/containers/RRDs/RRDs.scss b/src/react/src/containers/RRDs/RRDs.scss
new file mode 100644
index 000000000..9a133ceb3
--- /dev/null
+++ b/src/react/src/containers/RRDs/RRDs.scss
@@ -0,0 +1,88 @@
+@import 'src/utils/scss/variables';
+
+div.content {
+ margin-top: 0;
+}
+
+.rrd-list {
+ color: $textColor;
+
+ .toolbar {
+ padding: 6px 13%;
+ }
+
+ .rrd-item {
+ padding: 25px 0;
+ align-items: flex-start;
+
+ .actions {
+ opacity: 0;
+ }
+
+ .l-col {
+ display: flex;
+ flex-direction: column;
+ margin: 15px 40px 0 0;
+
+ .date {
+ margin: 0;
+ }
+ }
+
+ &:hover {
+ .actions {
+ opacity: 1;
+ }
+ }
+ }
+}
+
+.periods-wrapper {
+ display: flex;
+ align-items: center;
+
+ > div:nth-child(1) {
+ margin-left: 8px;
+ }
+
+ > div {
+ cursor: pointer;
+ margin: 0 15px;
+ font-size: 14px;
+
+ &:hover {
+ color: $secondaryLight;
+ }
+
+ &:active {
+ color: $primaryActive;
+ }
+ }
+
+ .timer-wrapper {
+ > button svg {
+
+ &:hover {
+ color: $secondaryLight;
+ }
+
+ &:active {
+ color: $primaryActive;
+ }
+ }
+
+ > div.circle-wrapper {
+ cursor: default;
+ }
+ }
+
+ > div.active {
+ color: $secondaryActive;
+ }
+}
+
+@media (max-width: 1350px) {
+ .rrd-list .toolbar {
+ padding: 6px 9.5%;
+ }
+}
diff --git a/src/react/src/containers/Search/Search.jsx b/src/react/src/containers/Search/Search.jsx
new file mode 100644
index 000000000..aaa9b37a9
--- /dev/null
+++ b/src/react/src/containers/Search/Search.jsx
@@ -0,0 +1,176 @@
+import React, { Component, useEffect, useState } from 'react';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { getSearchResultsList, handleAction } from '../../ControlPanelService/Search';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import SearchItem from '../../components/Searchitem/SearchItem';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import Spinner from '../../components/Spinner/Spinner';
+import './Search.scss';
+import { useDispatch, useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+
+const Search = props => {
+ const { i18n } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [state, setState] = useState({
+ searchResults: [],
+ totalAmount: '',
+ sorting: i18n.Date,
+ order: "descending",
+ total: 0
+ });
+ const [modal, setModal] = useState({
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+
+ useEffect(() => {
+ const { search } = history.location;
+
+ if (search) {
+ let searchTerm = search.split('=')[1];
+
+ if (searchTerm !== '') {
+ fetchData(searchTerm).then(() => setLoading(false));
+ } else {
+ return history.push({ pathname: '/list/user/', search: '' });
+ }
+ } else if (props.searchTerm !== '') {
+ fetchData(props.searchTerm).then(() => setLoading(false));
+ } else {
+ return history.push({ pathname: '/list/user/', search: '' });
+ }
+ }, []);
+
+ const fetchData = searchTerm => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getSearchResultsList(searchTerm)
+ .then(result => {
+ setState({
+ ...state,
+ searchResults: result.data.data,
+ totalAmount: result.data.total
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const searchResults = () => {
+ const { searchResults } = state;
+ const result = [];
+
+ for (let i in searchResults) {
+ result.push(searchResults[i]);
+ }
+
+ let sortedResult = sortArray(result);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const sortArray = array => {
+ const { order, sorting } = state;
+ let sortBy = sortByHandler(sorting);
+
+ if (order === "descending") {
+ return array.sort((a, b) => (a[sortBy] < b[sortBy]) ? 1 : ((b[sortBy] < a[sortBy]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortBy] > b[sortBy]) ? 1 : ((b[sortBy] > a[sortBy]) ? -1 : 0));
+ }
+ }
+
+ const sortByHandler = sorting => {
+ const { Date, Name, Starred } = i18n;
+
+ switch (sorting) {
+ case Date: return 'DATE';
+ case Name: return 'RESULT';
+ default: break;
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: !modal.visible,
+ text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {i18n['Search Results']}
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+ {loading
+ ?
+ : (<>
+ {searchResults()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Search;
diff --git a/src/react/src/containers/Search/Search.scss b/src/react/src/containers/Search/Search.scss
new file mode 100644
index 000000000..ace8cf0a0
--- /dev/null
+++ b/src/react/src/containers/Search/Search.scss
@@ -0,0 +1,68 @@
+$secondary: #fcac04;
+
+.content {
+ .toolbar {
+ .search-toolbar-name {
+ font-size: 12px;
+ text-transform: uppercase;
+ color: $secondary;
+ font-weight: bold;
+ margin-left: 14.3%;
+ padding-left: 7px;
+ align-self: center;
+ }
+
+ .search-toolbar-right {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .btn-group {
+ button {
+ border: none;
+ background: none;
+ box-shadow: none;
+
+ &:focus, &:active, &:hover {
+ background: none;
+ box-shadow: none;
+ outline: none;
+ border: none;
+ color: #686868;
+ }
+ }
+ }
+
+ .search-input-form {
+ padding-top: 2px;
+ }
+ }
+ }
+
+ .statistics-wrapper {
+ > .list-item:first-child {
+ margin-top: 0;
+ }
+
+ .list-item {
+ .l-col {
+ > .star {
+ display: none;
+ }
+ }
+
+ .r-col {
+ div.object {
+ text-transform: uppercase;
+ }
+
+ .c-2 .owner span,
+ .c-3 .status span {
+ text-align: center;
+ font-weight: bold;
+ text-transform: lowercase;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Servers/Servers.jsx b/src/react/src/containers/Servers/Servers.jsx
new file mode 100644
index 000000000..45b1a62d7
--- /dev/null
+++ b/src/react/src/containers/Servers/Servers.jsx
@@ -0,0 +1,345 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getServersList, handleAction } from '../../ControlPanelService/Server';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import ServerSys from '../../components/Server/ServerSys';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import Server from '../../components/Server/Server';
+import { Link } from 'react-router-dom';
+import { Helmet } from 'react-helmet';
+import './Servers.scss';
+
+const Servers = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: '',
+ });
+ const [state, setState] = useState({
+ servers: [],
+ selection: [],
+ toggledAll: false,
+ sorting: i18n.Action,
+ order: "descending",
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/server/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.servers]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = servers => {
+ servers[0]['FOCUSED'] = servers[0]['NAME'];
+ setState({ ...state, servers });
+ dispatch(addControlPanelContentFocusedElement(servers[0]));
+ }
+
+ const handleArrowDown = () => {
+ let servers = [...state.servers];
+
+ if (focusedElement) {
+ removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement.NAME === '' || controlPanelFocusedElement === '') {
+ initFocusedElement(servers);
+ return;
+ }
+
+ let focusedElementPosition = servers.findIndex(server => server.NAME === controlPanelFocusedElement.NAME);
+
+ if (focusedElementPosition !== servers.length - 1) {
+ let nextFocusedElement = servers[focusedElementPosition + 1];
+ servers[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, servers });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let servers = [...state.servers];
+
+ if (focusedElement) {
+ removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement.NAME === '' || controlPanelFocusedElement === '') {
+ initFocusedElement(servers);
+ return;
+ }
+
+ let focusedElementPosition = servers.findIndex(server => server.NAME === controlPanelFocusedElement.NAME);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = servers[focusedElementPosition - 1];
+ servers[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, servers });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement.NAME && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 13: return handleConfigure();
+ case 82: return handleRestart();
+ case 83: return handleStop();
+ default: break;
+ }
+ }
+ }
+
+ const handleConfigure = () => {
+ if (controlPanelFocusedElement.NAME !== state.servers[0].NAME) {
+ props.history.push(`/edit/server/${controlPanelFocusedElement.NAME}`);
+ } else {
+ props.history.push('/edit/server/');
+ }
+ }
+
+ const handleStop = () => {
+ onHandleAction('/api/v1' + controlPanelFocusedElement.action_url);
+ }
+
+ const handleRestart = () => {
+ onHandleAction(`/api/v1/restart/service/?srv=${controlPanelFocusedElement.NAME}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getServersList()
+ .then(result => {
+ setState({
+ ...state,
+ selection: [],
+ toggledAll: false,
+ servers: reformatData(result.data.data, result.data.sys)
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const reformatData = (servers, sysInfo) => {
+ let result = [];
+
+ for (let i in servers) {
+ servers[i]['NAME'] = i;
+ servers[i]['FOCUSED'] = controlPanelFocusedElement.NAME === i;
+
+ result.push(servers[i]);
+ }
+
+ result.splice(0, 0, Object.values(sysInfo)[0]);
+ result[0]['NAME'] = result[0]['HOSTNAME'];
+
+ return result;
+ }
+
+ const servers = () => {
+ const result = [];
+
+ state.servers.forEach(server => {
+ server.FOCUSED = controlPanelFocusedElement.NAME === server.NAME;
+ result.push(server);
+ });
+
+ return result.map((item, index) => {
+ if (item.HOSTNAME) {
+ return
+ } else {
+ return
+ }
+ });
+ }
+
+ const onHandleAction = uri => {
+ dispatch(removeControlPanelContentFocusedElement());
+ if (uri) {
+ setLoading(true);
+ handleAction(uri)
+ .then(res => {
+ if (res.data.error) {
+ displayModal(res.data.error);
+ }
+
+ fetchData().then(() => setLoading(false));
+ })
+ .catch(err => {
+ setLoading(false);
+ console.error(err)
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ let serversDuplicate = [...state.servers];
+
+ if (toggled) {
+ let serverNames = [];
+
+ let servers = serversDuplicate.map(server => {
+ serverNames.push(server.NAME);
+ server.isChecked = true;
+ return server;
+ });
+
+ setState({ ...state, servers, selection: serverNames, toggledAll: toggled });
+ } else {
+ let servers = serversDuplicate.map(server => {
+ server.isChecked = false;
+ return server;
+ });
+
+ setState({ ...state, servers, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action) {
+ setState({ ...state, loading: true });
+ bulkAction(action, selection)
+ .then(res => {
+ if (res.data.error) {
+ displayModal(res.data.error);
+ }
+
+ toggleAll(false);
+ fetchData().then(() => setLoading(false));
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const checkItem = name => {
+ const { selection } = state;
+ let duplicate = [...selection];
+ let serversDuplicate = [...state.servers];
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = serversDuplicate.findIndex(server => server.NAME === name);
+ serversDuplicate[incomingItem].isChecked = !serversDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, servers: serversDuplicate, selection: duplicate });
+ }
+
+ const displayModal = text => {
+ setModal({ ...modal, visible: true, text });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+
+ fetchData().then(() => setLoading(false));
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const modalCancelHandler = () => {
+ setModal({ ...modal, visible: false, text: '' });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+
+
+ {i18n['show: CPU / MEM / NET / DISK']}
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+ {loading ?
: (
+
+ {servers()}
+
+ )}
+
+
+
+ );
+}
+
+export default Servers;
diff --git a/src/react/src/containers/Servers/Servers.scss b/src/react/src/containers/Servers/Servers.scss
new file mode 100644
index 000000000..001e5af4b
--- /dev/null
+++ b/src/react/src/containers/Servers/Servers.scss
@@ -0,0 +1,23 @@
+.servers-list .l-col {
+ .text-status {
+ display: block;
+ }
+
+ div.star {
+ display: none;
+ }
+
+ .servers-wrapper {
+ .list-item:first-child {
+ margin-top: 0;
+ }
+
+ .l-col {
+ width: 16.3%;
+
+ .server-name {
+ font-size: 28px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/ServiceInfo/index.jsx b/src/react/src/containers/ServiceInfo/index.jsx
new file mode 100644
index 000000000..f6b9fe2df
--- /dev/null
+++ b/src/react/src/containers/ServiceInfo/index.jsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react';
+
+import { addActiveElement } from 'src/actions/MainNavigation/mainNavigationActions';
+import TopPanel from 'src/components/TopPanel/TopPanel';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { getServiceLogs } from 'src/ControlPanelService/Server';
+import Spinner from 'src/components/Spinner/Spinner';
+import { Helmet } from 'react-helmet';
+import ReactHtmlParser from 'react-html-parser';
+
+import './styles.scss';
+import QueryString from 'qs';
+
+const ServiceInfo = () => {
+ const { i18n, userName } = useSelector(state => state.session);
+ const dispatch = useDispatch();
+ const history = useHistory();
+ const [state, setState] = useState({
+ data: "",
+ loading: false
+ });
+
+ useEffect(() => {
+ if (!userName) {
+ history.push('/login/');
+ }
+ }, [userName]);
+
+ useEffect(() => {
+ let queryParams = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+
+ if (!queryParams.srv) {
+ fetchData('cpu');
+ dispatch(addActiveElement('/list/server/service/?srv=cpu'));
+ return;
+ }
+
+ if (!menuItems.find(item => item.service === queryParams.srv)) {
+ dispatch(addActiveElement('/list/server/service/?srv=cpu'));
+ history.push('/list/server/service/?srv=cpu');
+ return;
+ }
+
+ fetchData(queryParams.srv);
+ dispatch(addActiveElement(`/list/server/service/?srv=${queryParams.srv}`));
+ }, [history.location.search]);
+
+ const fetchData = serviceName => {
+ setState({ ...state, loading: true });
+
+ getServiceLogs(serviceName)
+ .then(result => {
+ setState({ ...state, data: result.data.service_log, loading: false });
+ })
+ .catch(error => {
+ console.error(error);
+ setState({ ...state, loading: false });
+ });
+ }
+
+ const menuItems = [
+ {
+ route: '/list/server/service/?srv=cpu',
+ service: 'cpu',
+ name: i18n['CPU']
+ },
+ {
+ route: '/list/server/service/?srv=mem',
+ service: 'mem',
+ name: i18n['MEMORY']
+ },
+ {
+ route: '/list/server/service/?srv=disk',
+ service: 'disk',
+ name: i18n['DISK']
+ },
+ {
+ route: '/list/server/service/?srv=net',
+ service: 'net',
+ name: i18n['NETWORK']
+ },
+ {
+ route: '/list/server/service/?srv=web',
+ service: 'web',
+ name: i18n['WEB']
+ },
+ {
+ route: '/list/server/service/?srv=dns',
+ service: 'dns',
+ name: i18n['DNS']
+ },
+ {
+ route: '/list/server/service/?srv=mail',
+ service: 'mail',
+ name: i18n['MAIL']
+ },
+ {
+ route: '/list/server/service/?srv=db',
+ service: 'db',
+ name: i18n['DB']
+ }
+ ];
+
+ return (
+
+
+ {`Vesta - ${i18n.SERVER}`}
+
+
+
+ {
+ state.loading
+ ?
+ : (
+ {state.data && ReactHtmlParser(state.data)}
+ )
+ }
+
+
+ );
+}
+
+export default ServiceInfo;
diff --git a/src/react/src/containers/ServiceInfo/styles.scss b/src/react/src/containers/ServiceInfo/styles.scss
new file mode 100644
index 000000000..ea27fc579
--- /dev/null
+++ b/src/react/src/containers/ServiceInfo/styles.scss
@@ -0,0 +1,36 @@
+.App .service-info {
+ @media screen and (max-width: 1066px) {
+ .top-panel {
+ display: flex;
+ }
+ }
+
+ @media screen and (min-width: 1200px) {
+ .top-panel {
+ padding: 0 13%;
+ }
+ }
+
+ .content {
+ font-size: 14px;
+ color: #555;
+ padding-top: 4rem;
+
+ @media screen and (min-width: 1067px) {
+ padding-top: 5rem !important;
+ margin-top: 0px !important;
+ }
+
+ @media screen and (max-width: 1066px) {
+ padding-top: 5rem !important;
+ margin-top: 0px !important;
+ }
+
+ table {
+ td,th {
+ padding: 5px 10px;
+ border: 1px solid black;
+ }
+ }
+ }
+}
diff --git a/src/react/src/containers/Statistics/Statistics.jsx b/src/react/src/containers/Statistics/Statistics.jsx
new file mode 100644
index 000000000..9ed4f8e97
--- /dev/null
+++ b/src/react/src/containers/Statistics/Statistics.jsx
@@ -0,0 +1,190 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { getStatisticsList } from '../../ControlPanelService/Statistics';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Statistic from '../../components/Statistic/Statistic';
+import Spinner from '../../components/Spinner/Spinner';
+import { useDispatch, useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+import './Statistics.scss';
+import { Helmet } from 'react-helmet';
+
+const Statistics = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [state, setState] = useState({
+ statistics: [],
+ users: [],
+ totalAmount: '',
+ loading: false,
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/stats/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData();
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.statistics]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = statistics => {
+ statistics[0]['FOCUSED'] = statistics[0]['NAME'];
+ setState({ ...state, statistics });
+ dispatch(addControlPanelContentFocusedElement(statistics[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let statistics = [...state.statistics];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(statistics);
+ return;
+ }
+
+ let focusedElementPosition = statistics.findIndex(statistic => statistic.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== statistics.length - 1) {
+ let nextFocusedElement = statistics[focusedElementPosition + 1];
+ statistics[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, statistics });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let statistics = [...state.statistics];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(statistics);
+ return;
+ }
+
+ let focusedElementPosition = statistics.findIndex(statistic => statistic.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = statistics[focusedElementPosition - 1];
+ statistics[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, statistics });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const fetchData = () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ setState({ ...state, loading: true });
+
+ let search = window.location.search;
+ let user = search ? search.split('=')[1] : '';
+
+ getStatisticsList(user)
+ .then(result => {
+ setState({
+ ...state,
+ statistics: reformatData(result.data.data),
+ users: result.data.users || [],
+ totalAmount: result.data.totalAmount,
+ loading: false
+ });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const reformatData = data => {
+ let statistics = [];
+
+ for (let i in data) {
+ data[i]['DATE'] = i;
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ statistics.push(data[i]);
+ }
+
+ return statistics;
+ }
+
+ const statistics = () => {
+ let statistics = [...state.statistics];
+
+ statistics.forEach(statistic => {
+ statistic.FOCUSED = controlPanelFocusedElement === statistic.NAME;
+ });
+
+ return statistics.map((item, index) => {
+ return ;
+ });
+ }
+
+ const bulkAction = value => {
+ let user = value !== '' ? `?user=${value}` : '';
+ props.history.push({ search: user });
+ fetchData();
+ };
+
+ return (
+
+
+ {`Vesta - ${i18n.STATS}`}
+
+
+
+
+ {i18n['Overall Statistics']}
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {state.loading ? : statistics()}
+
+
{state.totalAmount}
+
+ );
+}
+
+export default Statistics;
\ No newline at end of file
diff --git a/src/react/src/containers/Statistics/Statistics.scss b/src/react/src/containers/Statistics/Statistics.scss
new file mode 100644
index 000000000..519ffefda
--- /dev/null
+++ b/src/react/src/containers/Statistics/Statistics.scss
@@ -0,0 +1,37 @@
+.statistics-list {
+ color: #686868;
+
+ .toolbar {
+ padding: 6px 13%;
+ }
+
+ .l-col {
+ width: 14.3%;
+
+ div.date {
+ margin-top: 25px;
+ }
+ }
+}
+
+.total {
+ margin-top: 25px;
+ margin-left: 15%;
+ font-size: 12px;
+ color: #929292;
+}
+
+.statistic-item.focused {
+ border-left: 2px solid #5edad0;
+ transform: translateX(-2px);
+
+ .r-col .date {
+ color: #5edad0;
+ }
+}
+
+@media (max-width: 1350px) {
+ .statistics-list .toolbar {
+ padding: 6px 9.5%;
+ }
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Updates/Updates.jsx b/src/react/src/containers/Updates/Updates.jsx
new file mode 100644
index 000000000..bccfef508
--- /dev/null
+++ b/src/react/src/containers/Updates/Updates.jsx
@@ -0,0 +1,314 @@
+import React, { useEffect, useState } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import { bulkAction, getUpdatesList, enableAutoUpdate, disableAutoUpdate } from '../../ControlPanelService/Updates';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Modal from 'src/components/ControlPanel/Modal/Modal';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import { useDispatch, useSelector } from 'react-redux';
+import Spinner from '../../components/Spinner/Spinner';
+import Update from '../../components/Update/Update';
+import './Updates.scss';
+import { Helmet } from 'react-helmet';
+
+const Updates = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const dispatch = useDispatch();
+ const [state, setState] = useState({
+ updates: [],
+ selection: [],
+ autoUpdate: '',
+ token: '',
+ loading: false,
+ toggledAll: false
+ });
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/updates/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData();
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.updates]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = updates => {
+ updates[0]['FOCUSED'] = updates[0]['NAME'];
+ setState({ ...state, updates });
+ dispatch(addControlPanelContentFocusedElement(updates[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let updates = [...state.updates];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(updates);
+ return;
+ }
+
+ let focusedElementPosition = updates.findIndex(update => update.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== updates.length - 1) {
+ let nextFocusedElement = updates[focusedElementPosition + 1];
+ updates[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, updates });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let updates = [...state.updates];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(updates);
+ return;
+ }
+
+ let focusedElementPosition = updates.findIndex(update => update.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = updates[focusedElementPosition - 1];
+ updates[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, updates });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const fetchData = () => {
+ setState({ ...state, loading: true });
+
+ getUpdatesList()
+ .then(result => {
+ setState({
+ ...state,
+ selection: [],
+ updates: reformatData(result.data.data),
+ autoUpdate: result.data.autoUpdate,
+ loading: false
+ });
+ })
+ .catch(err => console.error(err));
+ }
+
+ const reformatData = data => {
+ let updates = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ updates.push(data[i]);
+ }
+
+ return updates;
+ }
+
+ const updates = () => {
+ let updates = [...state.updates];
+
+ updates.forEach(update => {
+ update.FOCUSED = controlPanelFocusedElement === update.NAME;
+ });
+
+ return updates.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ let duplicate = [...state.selection];
+ let updatesDuplicate = [...state.updates];
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = updatesDuplicate.findIndex(update => update.NAME === name);
+ updatesDuplicate[incomingItem].isChecked = !updatesDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, updates: updatesDuplicate, selection: duplicate });
+ }
+
+ const toggleAll = toggled => {
+ const updatesDuplicate = [...state.updates];
+
+ if (toggled) {
+ let updateNames = [];
+
+ let updates = updatesDuplicate.map(update => {
+ updateNames.push(update.NAME);
+ update.isChecked = true;
+ return update;
+ });
+
+ setState({ ...state, updates, selection: updateNames, toggledAll: toggled });
+ } else {
+ let updates = updatesDuplicate.map(update => {
+ update.isChecked = false;
+ return update;
+ });
+
+ setState({ ...state, updates, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ const { selection } = state;
+
+ if (selection.length && action !== 'apply to selected') {
+ bulkAction(action, selection)
+ .then(res => {
+ toggleAll(false);
+ if (res.status === 200) {
+ if (res.data.error) {
+ setState({ ...state, loading: false });
+ return displayModal(res.data.error, '');
+ }
+
+ displayModal(res.data.message, '');
+ fetchData();
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const handleAutoUpdate = () => {
+ if (state.autoUpdate === 'Enabled') {
+ disableAutoUpdate()
+ .then(res => {
+ if (res.data.error) {
+ setState({ ...state, loading: false });
+ return displayModal(res.data.error, '');
+ }
+
+ displayModal(res.data.message, '');
+ fetchData();
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ } else {
+ enableAutoUpdate()
+ .then(res => {
+ if (res.data.error) {
+ setState({ ...state, loading: false });
+ return displayModal(res.data.error, '');
+ }
+
+ displayModal(res.data.message, '');
+ fetchData();
+ })
+ .catch(err => {
+ setState({ ...state, loading: false });
+ console.error(err);
+ });
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setState({ ...state, loading: true });
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ const printAutoUpdateButtonName = () => {
+ if (state.autoUpdate === 'Enabled') {
+ return i18n['disable autoupdate'];
+ } else {
+ return i18n['enable autoupdate'];
+ }
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.UPDATES}`}
+
+
+
+
+
+ {printAutoUpdateButtonName()}
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+ {state.loading ?
: updates()}
+
+
+ );
+}
+
+export default Updates;
\ No newline at end of file
diff --git a/src/react/src/containers/Updates/Updates.scss b/src/react/src/containers/Updates/Updates.scss
new file mode 100644
index 000000000..46280d399
--- /dev/null
+++ b/src/react/src/containers/Updates/Updates.scss
@@ -0,0 +1,3 @@
+.statistics-list.updates .toolbar {
+ height: 44px;
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Users/Users.jsx b/src/react/src/containers/Users/Users.jsx
new file mode 100644
index 000000000..8acacf2b4
--- /dev/null
+++ b/src/react/src/containers/Users/Users.jsx
@@ -0,0 +1,446 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from "../../actions/MainNavigation/mainNavigationActions";
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkAction, getUsersList, handleAction } from '../../ControlPanelService/Users';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import { useDispatch, useSelector } from 'react-redux';
+import Spinner from '../../components/Spinner/Spinner';
+import User from '../../components/User/User';
+import { Helmet } from 'react-helmet';
+import './Users.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import { useHistory } from 'react-router';
+import { loginAs, logout } from 'src/actions/Session/sessionActions';
+
+const Users = props => {
+ const { userName, i18n } = useSelector(state => state.session);
+ const { session } = useSelector(state => state.userSession);
+ 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,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ users: [],
+ userFav: [],
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ dispatch(addActiveElement('/list/user/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+ window.addEventListener("keyup", addNewObject);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ window.removeEventListener("keyup", addNewObject);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.users]);
+
+ const addNewObject = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (isSearchInputFocused) {
+ return;
+ }
+
+ if (event.keyCode === 65) {
+ switch (history.location.pathname) {
+ case '/list/user/': return session.look ? history.push('/add/web/') : history.push('/add/user/');
+ default: break;
+ }
+ }
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getUsersList()
+ .then(result => {
+ setState({
+ ...state,
+ users: reformatData(result.data.data),
+ userFav: result.data.userFav,
+ totalAmount: result.data.totalAmount,
+ toggledAll: false,
+ selection: []
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 76: return handleLogin();
+ case 83: return handleSuspend();
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ default: break;
+ }
+ }
+ }
+
+ const handleLogin = () => {
+ if (userName === controlPanelFocusedElement) {
+ props.history.push('/logout');
+ } else {
+ props.history.push(`/login/?loginas=${controlPanelFocusedElement}`);
+ }
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/user?user=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { users } = state;
+ let currentUserData = users.filter(user => user.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentUserData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentUserData.spnd_conf, `/api/v1/${suspendedStatus}/user/index.php?user=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { users } = state;
+ let currentUserData = users.filter(user => user.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentUserData.delete_conf, `/api/v1/delete/user/index.php?user=${controlPanelFocusedElement}`);
+ }
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = users => {
+ users[0]['FOCUSED'] = users[0]['NAME'];
+ setState({ ...state, users });
+ dispatch(addControlPanelContentFocusedElement(users[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let users = [...state.users];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(users);
+ return;
+ }
+
+ let focusedElementPosition = users.findIndex(user => user.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== users.length - 1) {
+ let nextFocusedElement = users[focusedElementPosition + 1];
+ users[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, users });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let users = [...state.users];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(users);
+ return;
+ }
+
+ let focusedElementPosition = users.findIndex(user => user.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = users[focusedElementPosition - 1];
+ users[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, users });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const reformatData = data => {
+ let users = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['isChecked'] = false;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ users.push(data[i]);
+ }
+
+ return users;
+ }
+
+ const users = () => {
+ const userFav = { ...state.userFav };
+ let users = [...state.users];
+
+ users.forEach(user => {
+ user.FOCUSED = controlPanelFocusedElement === user.NAME;
+
+ if (userFav[user.NAME]) {
+ user.STARRED = userFav[user.NAME];
+ } else {
+ user.STARRED = 0;
+ }
+ });
+
+ let sortedResult = sortArray(users);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const logOutHandler = () => {
+ setLoading(true);
+ dispatch(logout()).then(() => setLoading(false));
+ }
+
+ const logInAsHandler = username => {
+ setLoading(true);
+ dispatch(loginAs(username)).then(() => setLoading(false));
+ }
+
+ const checkItem = name => {
+ const { selection, users } = state;
+ let duplicate = [...selection];
+ let userDuplicate = [...users];
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = userDuplicate.findIndex(user => user.NAME === name);
+ userDuplicate[incomingItem].isChecked = !userDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, users: userDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ let sortingColumn = sortBy(state.sorting);
+
+ if (state.order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date: date, Username, Disk, Bandwidth, Starred } = i18n;
+
+ switch (sorting) {
+ case date: return 'DATE';
+ case Username: return 'NAME';
+ case Disk: return 'U_DISK';
+ case Bandwidth: return 'U_BANDWIDTH';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ let userFavDuplicate = state.userFav;
+
+ if (type === 'add') {
+ userFavDuplicate[value] = 1;
+
+ addFavorite(value, 'user')
+ .then(() => {
+ setState({ ...state, userFav: userFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ userFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'user')
+ .then(() => {
+ setState({ ...state, userFav: userFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const usersDuplicate = [...state.users];
+
+ if (toggled) {
+ let userNames = [];
+
+ let users = usersDuplicate.map(user => {
+ userNames.push(user.NAME);
+ user.isChecked = true;
+ return user;
+ });
+
+ setState({ ...state, users, selection: userNames, toggledAll: toggled });
+ } else {
+ let users = usersDuplicate.map(user => {
+ user.isChecked = false;
+ return user;
+ });
+
+ setState({ ...state, users, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ if (state.selection.length && action) {
+ setLoading(true);
+ bulkAction(action, state.selection)
+ .then(result => {
+ if (result.status === 200) {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ }
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text: text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.USER}`}
+
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (<>
+ {users()}
+
{state.totalAmount}
+ >)}
+
+
+
+ );
+}
+
+export default Users;
diff --git a/src/react/src/containers/Users/Users.scss b/src/react/src/containers/Users/Users.scss
new file mode 100644
index 000000000..486f396f3
--- /dev/null
+++ b/src/react/src/containers/Users/Users.scss
@@ -0,0 +1,4 @@
+.content .Toastify__toast-container {
+ bottom: 1em !important;
+ top: unset;
+}
\ No newline at end of file
diff --git a/src/react/src/containers/Web/Web.jsx b/src/react/src/containers/Web/Web.jsx
new file mode 100644
index 000000000..fb8c427a6
--- /dev/null
+++ b/src/react/src/containers/Web/Web.jsx
@@ -0,0 +1,416 @@
+import React, { useState, useEffect } from 'react';
+import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
+import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
+import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
+import { bulkAction, getWebList, handleAction } from '../../ControlPanelService/Web';
+import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
+import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
+import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
+import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
+import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
+import Select from '../../components/MainNav/Toolbar/Select/Select';
+import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
+import WebDomain from '../../components/WebDomain/WebDomain';
+import Spinner from '../../components/Spinner/Spinner';
+import Modal from '../../components/ControlPanel/Modal/Modal';
+import { useDispatch, useSelector } from 'react-redux';
+import { Helmet } from 'react-helmet';
+import './Web.scss';
+import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
+import { useHistory } from 'react-router-dom';
+
+const Web = props => {
+ const { i18n } = useSelector(state => state.session);
+ const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
+ const { focusedElement } = useSelector(state => state.mainNavigation);
+ const { panel } = useSelector(state => state.panel);
+ const { userName } = useSelector(state => state.session);
+ const dispatch = useDispatch();
+ const history = useHistory();
+ const [loading, setLoading] = useState(false);
+ const [modal, setModal] = useState({
+ text: '',
+ visible: false,
+ actionUrl: ''
+ });
+ const [state, setState] = useState({
+ webDomains: [],
+ webFav: [],
+ toggledAll: false,
+ sorting: i18n.Date,
+ order: "descending",
+ selection: [],
+ totalAmount: ''
+ });
+
+ useEffect(() => {
+ if (panel[userName]['WEB_DOMAINS'] === '0') {
+ return history.push('/');
+ }
+
+ dispatch(addActiveElement('/list/web/'));
+ dispatch(removeFocusedElement());
+ dispatch(removeControlPanelContentFocusedElement());
+ fetchData().then(() => setLoading(false));
+
+ return () => {
+ dispatch(removeControlPanelContentFocusedElement());
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleContentSelection);
+ window.addEventListener("keydown", handleFocusedElementShortcuts);
+
+ return () => {
+ window.removeEventListener("keydown", handleContentSelection);
+ window.removeEventListener("keydown", handleFocusedElementShortcuts);
+ };
+ }, [controlPanelFocusedElement, focusedElement, state.webDomains]);
+
+ const handleContentSelection = event => {
+ if (event.keyCode === 38 || event.keyCode === 40) {
+ if (focusedElement) {
+ dispatch(MainNavigation.removeFocusedElement());
+ }
+ }
+
+ if (event.keyCode === 38) {
+ event.preventDefault();
+ handleArrowUp();
+ } else if (event.keyCode === 40) {
+ event.preventDefault();
+ handleArrowDown();
+ }
+ }
+
+ const initFocusedElement = webDomains => {
+ webDomains[0]['FOCUSED'] = webDomains[0]['NAME'];
+ setState({ ...state, webDomains });
+ dispatch(addControlPanelContentFocusedElement(webDomains[0]['NAME']));
+ }
+
+ const handleArrowDown = () => {
+ let webDomains = [...state.webDomains];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(webDomains);
+ return;
+ }
+
+ let focusedElementPosition = webDomains.findIndex(webDomain => webDomain.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== webDomains.length - 1) {
+ let nextFocusedElement = webDomains[focusedElementPosition + 1];
+ webDomains[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, webDomains });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleArrowUp = () => {
+ let webDomains = [...state.webDomains];
+
+ if (focusedElement) {
+ MainNavigation.removeFocusedElement();
+ }
+
+ if (controlPanelFocusedElement === '') {
+ initFocusedElement(webDomains);
+ return;
+ }
+
+ let focusedElementPosition = webDomains.findIndex(webDomain => webDomain.NAME === controlPanelFocusedElement);
+
+ if (focusedElementPosition !== 0) {
+ let nextFocusedElement = webDomains[focusedElementPosition - 1];
+ webDomains[focusedElementPosition]['FOCUSED'] = '';
+ nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
+ document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
+ setState({ ...state, webDomains });
+ dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
+ }
+ }
+
+ const handleFocusedElementShortcuts = event => {
+ let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
+
+ if (controlPanelFocusedElement && !isSearchInputFocused) {
+ switch (event.keyCode) {
+ case 76: return handleLogs();
+ case 83: return handleSuspend();
+ case 8: return handleDelete();
+ case 13: return handleEdit();
+ default: break;
+ }
+ }
+ }
+
+ const handleLogs = () => {
+ props.history.push(`/list/web-log?domain=${controlPanelFocusedElement}&type=access`);
+ }
+
+ const handleEdit = () => {
+ props.history.push(`/edit/web?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleSuspend = () => {
+ const { webDomains } = state;
+ let currentWebDomainData = webDomains.filter(webDomain => webDomain.NAME === controlPanelFocusedElement)[0];
+ let suspendedStatus = currentWebDomainData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
+
+ displayModal(currentWebDomainData.spnd_confirmation, `/api/v1/${suspendedStatus}/web/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const handleDelete = () => {
+ const { webDomains } = state;
+ let currentWebDomainData = webDomains.filter(webDomain => webDomain.NAME === controlPanelFocusedElement)[0];
+
+ displayModal(currentWebDomainData.delete_confirmation, `/api/v1/web/index.php?domain=${controlPanelFocusedElement}`);
+ }
+
+ const fetchData = () => {
+ setLoading(true);
+ return new Promise((resolve, reject) => {
+ getWebList()
+ .then(result => {
+ setState({
+ ...state,
+ webDomains: reformatData(result.data.data),
+ webFav: result.data.webFav,
+ totalAmount: result.data.totalAmount,
+ toggledAll: false,
+ selection: []
+ });
+ resolve();
+ })
+ .catch(err => console.error(err));
+ });
+ }
+
+ const changeSorting = (sorting, order) => {
+ setState({
+ ...state,
+ sorting,
+ order
+ });
+ }
+
+ const reformatData = data => {
+ let webDomains = [];
+
+ for (let i in data) {
+ data[i]['NAME'] = i;
+ data[i]['FOCUSED'] = controlPanelFocusedElement === i;
+ webDomains.push(data[i]);
+ }
+
+ return webDomains;
+ }
+
+ const webDomains = () => {
+ const webFav = { ...state.webFav };
+ let webDomains = [...state.webDomains];
+
+ webDomains.forEach(webDomain => {
+ webDomain.FOCUSED = controlPanelFocusedElement === webDomain.NAME;
+
+ if (webFav[webDomain.NAME]) {
+ webDomain.STARRED = webFav[webDomain.NAME];
+ } else {
+ webDomain.STARRED = 0;
+ }
+ });
+
+ let sortedResult = sortArray(webDomains);
+
+ return sortedResult.map((item, index) => {
+ return ;
+ });
+ }
+
+ const checkItem = name => {
+ let duplicate = [...state.selection];
+ let webDomainsDuplicate = state.webDomains;
+ let checkedItem = duplicate.indexOf(name);
+
+ let incomingItem = webDomainsDuplicate.findIndex(webDomain => webDomain.NAME === name);
+ webDomainsDuplicate[incomingItem].isChecked = !webDomainsDuplicate[incomingItem].isChecked;
+
+ if (checkedItem !== -1) {
+ duplicate.splice(checkedItem, 1);
+ } else {
+ duplicate.push(name);
+ }
+
+ setState({ ...state, webDomains: webDomainsDuplicate, selection: duplicate });
+ }
+
+ const sortArray = array => {
+ let sortingColumn = sortBy(state.sorting);
+
+ if (state.order === "descending") {
+ return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
+ } else {
+ return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
+ }
+ }
+
+ const sortBy = sorting => {
+ const { Date: date, Domain, Disk, Bandwidth, Starred } = i18n;
+
+ switch (sorting) {
+ case date: return 'DATE';
+ case Domain: return 'ALIAS';
+ case i18n['IP Addresses']: return 'IP';
+ case Disk: return 'U_DISK';
+ case Bandwidth: return 'U_BANDWIDTH';
+ case Starred: return 'STARRED';
+ default: break;
+ }
+ }
+
+ const toggleFav = (value, type) => {
+ let webFavDuplicate = state.webFav;
+
+ if (type === 'add') {
+ webFavDuplicate[value] = 1;
+
+ addFavorite(value, 'web')
+ .then(() => {
+ setState({ ...state, webFav: webFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ } else {
+ webFavDuplicate[value] = undefined;
+
+ deleteFavorite(value, 'web')
+ .then(() => {
+ setState({ ...state, webFav: webFavDuplicate });
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ }
+ }
+
+ const toggleAll = toggled => {
+ const webDomainsDuplicate = [...state.webDomains];
+
+ if (toggled) {
+ let webDomainNames = [];
+
+ let webDomains = webDomainsDuplicate.map(webDomain => {
+ webDomainNames.push(webDomain.NAME);
+ webDomain.isChecked = true;
+ return webDomain;
+ });
+
+ setState({ ...state, webDomains, selection: webDomainNames, toggledAll: toggled });
+ } else {
+ let webDomains = webDomainsDuplicate.map(webDomain => {
+ webDomain.isChecked = false;
+ return webDomain;
+ });
+
+ setState({ ...state, webDomains, selection: [], toggledAll: toggled });
+ }
+ }
+
+ const bulk = action => {
+ if (state.selection.length && action) {
+ setLoading(true);
+ bulkAction(action, state.selection)
+ .then(result => {
+ toggleAll(false);
+ fetchData().then(() => refreshMenuCounters());
+ })
+ .catch(err => console.error(err));
+ }
+ }
+
+ const displayModal = (text, url) => {
+ setModal({
+ ...modal,
+ visible: true,
+ text,
+ actionUrl: url
+ });
+ }
+
+ const modalConfirmHandler = () => {
+ if (!modal.actionUrl) {
+ return modalCancelHandler();
+ }
+
+ modalCancelHandler();
+ setLoading(true);
+ handleAction(modal.actionUrl)
+ .then(res => {
+ if (res.data.error) {
+ setLoading(false);
+ return displayModal(res.data.error, '');
+ }
+ fetchData().then(() => refreshMenuCounters())
+ })
+ .catch(err => { setLoading(false); console.error(err); });
+ }
+
+ const refreshMenuCounters = () => {
+ dispatch(refreshCounters()).then(() => setLoading(false));
+ }
+
+ const modalCancelHandler = () => {
+ setModal({
+ ...modal,
+ visible: false,
+ text: '',
+ actionUrl: ''
+ });
+ }
+
+ return (
+
+
+ {`Vesta - ${i18n.WEB}`}
+
+
+
+
+
+
+
+
+ props.changeSearchTerm(term)} />
+
+
+
+
+ {loading
+ ?
+ : (
+ <>
+ {webDomains()}
+
{state.totalAmount}
+ >
+ )
+ }
+
+
+
+ );
+}
+
+export default Web;
\ No newline at end of file
diff --git a/src/react/src/containers/Web/Web.scss b/src/react/src/containers/Web/Web.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/react/src/containers/WebLogs/WebLogs.jsx b/src/react/src/containers/WebLogs/WebLogs.jsx
new file mode 100644
index 000000000..d97193159
--- /dev/null
+++ b/src/react/src/containers/WebLogs/WebLogs.jsx
@@ -0,0 +1,111 @@
+import React, { useEffect, useState } from 'react';
+
+import { addActiveElement } from 'src/actions/MainNavigation/mainNavigationActions';
+import TopPanel from 'src/components/TopPanel/TopPanel';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
+import QueryString from 'qs';
+
+import './WebLogs.scss';
+import { getWebLogs } from 'src/ControlPanelService/WebLogs';
+import Spinner from 'src/components/Spinner/Spinner';
+import { Helmet } from 'react-helmet';
+import HtmlParser from 'react-html-parser';
+
+export default function WebLogs() {
+ const { i18n, userName } = useSelector(state => state.session);
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const mainNavigation = useSelector(state => state.mainNavigation);
+ const [domain, setDomain] = useState();
+ const [state, setState] = useState({
+ data: "",
+ prefix: "",
+ loading: false
+ });
+
+ useEffect(() => {
+ if (!userName) {
+ history.push('/login/');
+ }
+ }, []);
+
+ useEffect(() => {
+ let parsedQueryString = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
+ const { domain, type } = parsedQueryString;
+
+ if (!parsedQueryString && !domain && !type) {
+ return history.goBack();
+ }
+
+ setDomain(domain);
+ let uri = `/list/web-log/?domain=${domain}&type=${type}`;
+ fetchData(uri);
+
+ dispatch(addActiveElement(`/list/web-log/?domain=${domain}&type=${type}`));
+ }, [mainNavigation.activeElement]);
+
+ const fetchData = uri => {
+ setState({
+ ...state,
+ loading: true
+ });
+
+ getWebLogs(uri)
+ .then(result => {
+ if (result.data) {
+ setState({ ...state, data: result.data.data, prefix: result.data.prefix, loading: false });
+ }
+ })
+ .catch(error => {
+ console.error(error);
+ setState({ ...state, loading: false });
+ });
+ }
+
+ const menuItems = [
+ {
+ route: `/list/web-log/?domain=${domain}&type=access`,
+ name: i18n['AccessLog']
+ },
+ {
+ route: `/list/web-log/?domain=${domain}&type=error`,
+ name: i18n['ErrorLog']
+ }
+ ];
+
+ const extraMenuItems = [
+ {
+ link: `/download/web-log/?domain=${domain ?? ''}&type=access`,
+ type: 'download',
+ text: i18n['Download AccessLog']
+ },
+ {
+ link: `/download/web-log/?domain=${domain ?? ''}&type=error`,
+ type: 'download',
+ text: i18n['Download ErrorLog'],
+ }
+ ];
+
+ return (
+
+
+ {`Vesta - ${i18n.WEB}`}
+
+
+
+
{state.prefix}
+
+ {
+ state.loading
+ ?
+ : (
+
+ {HtmlParser(state.data)}
+
+ )
+ }
+
+
+ );
+}
diff --git a/src/react/src/containers/WebLogs/WebLogs.scss b/src/react/src/containers/WebLogs/WebLogs.scss
new file mode 100644
index 000000000..f00f61bc0
--- /dev/null
+++ b/src/react/src/containers/WebLogs/WebLogs.scss
@@ -0,0 +1,30 @@
+.App .web-logs {
+ .top-panel {
+ .left-menu {
+ .logo {
+ justify-content: start;
+ margin: 0;
+
+ a {
+ padding: 0 !important;
+ }
+ }
+ }
+ }
+
+ .content {
+ font-size: 14px;
+ color: #555;
+ padding-top: 4rem;
+
+ @media screen and (min-width: 1067px) {
+ padding-top: 5rem !important;
+ margin-top: 0px !important;
+ }
+
+ @media screen and (max-width: 1066px) {
+ padding-top: 5rem !important;
+ margin-top: 0px !important;
+ }
+ }
+}
diff --git a/src/react/src/index.css b/src/react/src/index.css
new file mode 100755
index 000000000..cee5f348f
--- /dev/null
+++ b/src/react/src/index.css
@@ -0,0 +1,14 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
diff --git a/src/react/src/index.js b/src/react/src/index.js
new file mode 100755
index 000000000..97764173e
--- /dev/null
+++ b/src/react/src/index.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux'
+import configureStore from './store';
+import './index.css';
+import App from './containers/App/App';
+import * as serviceWorker from './containers/App/serviceWorker';
+import { BrowserRouter as Router } from 'react-router-dom';
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById('root')
+);
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: http://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/src/react/src/reducers/ControlPanelContent/controlPanelContentReducer.js b/src/react/src/reducers/ControlPanelContent/controlPanelContentReducer.js
new file mode 100644
index 000000000..f69808153
--- /dev/null
+++ b/src/react/src/reducers/ControlPanelContent/controlPanelContentReducer.js
@@ -0,0 +1,23 @@
+import { ADD_CPANEL_FOCUSED_ELEMENT, REMOVE_CPANEL_FOCUSED_ELEMENT } from '../../actions/ControlPanelContent/controlPanelContentTypes';
+
+const INITIAL_STATE = {
+ controlPanelFocusedElement: ''
+};
+
+const reducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case ADD_CPANEL_FOCUSED_ELEMENT:
+ return {
+ ...state, controlPanelFocusedElement: action.value,
+ };
+
+ case REMOVE_CPANEL_FOCUSED_ELEMENT:
+ return {
+ ...state, controlPanelFocusedElement: '',
+ };
+
+ default: return state;
+ }
+};
+
+export default reducer;
\ No newline at end of file
diff --git a/src/react/src/reducers/MainNavigation/mainNavigationReducer.js b/src/react/src/reducers/MainNavigation/mainNavigationReducer.js
new file mode 100644
index 000000000..3f6fa428b
--- /dev/null
+++ b/src/react/src/reducers/MainNavigation/mainNavigationReducer.js
@@ -0,0 +1,66 @@
+import { ADD_FOCUSED_ELEMENT, ADD_ACTIVE_ELEMENT, REMOVE_ACTIVE_ELEMENT, REMOVE_FOCUSED_ELEMENT } from '../../actions/MainNavigation/mainNavigationTypes';
+
+const INITIAL_STATE = {
+ focusedElement: '',
+ activeElement: '',
+ adminMenuTabs: [
+ '/list/user/',
+ '/list/web/',
+ '/list/dns/',
+ '/list/mail/',
+ '/list/db/',
+ '/list/cron/',
+ '/list/backup/',
+ '/list/package/',
+ '/list/ip/',
+ '/list/rrd/',
+ '/list/stats/',
+ '/list/log/',
+ '/list/updates/',
+ '/list/firewall/',
+ '/list/directory/',
+ '/softaculous/',
+ '/list/server/'
+ ],
+ userMenuTabs: [
+ '/list/user/',
+ '/list/web/',
+ '/list/dns/',
+ '/list/mail/',
+ '/list/db/',
+ '/list/cron/',
+ '/list/backup/',
+ '/list/stats/',
+ '/list/log/',
+ '/list/directory/',
+ '/softaculous/',
+ ]
+};
+
+const reducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case ADD_FOCUSED_ELEMENT:
+ return {
+ ...state, focusedElement: action.value,
+ };
+
+ case REMOVE_FOCUSED_ELEMENT:
+ return {
+ ...state, focusedElement: action.value,
+ };
+
+ case ADD_ACTIVE_ELEMENT:
+ return {
+ ...state, activeElement: action.value,
+ };
+
+ case REMOVE_ACTIVE_ELEMENT:
+ return {
+ ...state, activeElement: action.value,
+ };
+
+ default: return state;
+ }
+};
+
+export default reducer;
\ No newline at end of file
diff --git a/src/react/src/reducers/MenuCounters/menuCounterReducer.js b/src/react/src/reducers/MenuCounters/menuCounterReducer.js
new file mode 100644
index 000000000..56424d286
--- /dev/null
+++ b/src/react/src/reducers/MenuCounters/menuCounterReducer.js
@@ -0,0 +1,19 @@
+import { REFRESH_COUNTERS } from 'src/actions/MenuCounters/menuCounterTypes';
+
+const INITIAL_STATE = {
+ user: {},
+};
+
+const menuCounterReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case REFRESH_COUNTERS:
+ return {
+ ...state,
+ user: action.value.user,
+ };
+
+ default: return state;
+ }
+};
+
+export default menuCounterReducer;
diff --git a/src/react/src/reducers/Notification/notificationReducer.js b/src/react/src/reducers/Notification/notificationReducer.js
new file mode 100644
index 000000000..0e3a269eb
--- /dev/null
+++ b/src/react/src/reducers/Notification/notificationReducer.js
@@ -0,0 +1,25 @@
+import { ADD_NOTIFICATIONS, REMOVE_NOTIFICATIONS } from 'src/actions/Notification/notificationTypes';
+
+const INITIAL_STATE = {
+ notifications: []
+};
+
+const notificationReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case ADD_NOTIFICATIONS:
+ return {
+ ...state,
+ notifications: action.value,
+ };
+
+ case REMOVE_NOTIFICATIONS:
+ return {
+ ...state,
+ notifications: action.value,
+ };
+
+ default: return state;
+ }
+};
+
+export default notificationReducer;
diff --git a/src/react/src/reducers/Panel/panel.js b/src/react/src/reducers/Panel/panel.js
new file mode 100644
index 000000000..36ee5b39e
--- /dev/null
+++ b/src/react/src/reducers/Panel/panel.js
@@ -0,0 +1,19 @@
+import { REFRESH_PANEL } from '../../actions/Panel/panelTypes';
+
+const INITIAL_STATE = {
+ panel: {}
+};
+
+const panelReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case REFRESH_PANEL:
+ return {
+ ...state,
+ panel: action.value.panel
+ };
+
+ default: return state;
+ }
+};
+
+export default panelReducer;
diff --git a/src/react/src/reducers/Session/sessionReducer.js b/src/react/src/reducers/Session/sessionReducer.js
new file mode 100644
index 000000000..c05888534
--- /dev/null
+++ b/src/react/src/reducers/Session/sessionReducer.js
@@ -0,0 +1,51 @@
+import { LOGGED_OUT_AS, LOGIN, LOGOUT, CHECK_AUTH } from '../../actions/Session/sessionTypes';
+
+const INITIAL_STATE = {
+ token: '',
+ error: '',
+ i18n: {},
+ userName: ''
+};
+
+const sessionReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case LOGIN:
+ return {
+ ...state,
+ token: action.value.token,
+ userName: action.value.userName,
+ i18n: action.value.i18n || {},
+ error: action.value.error
+ };
+
+ case LOGOUT:
+ return {
+ ...state,
+ token: action.value.token,
+ userName: action.value.userName,
+ i18n: action.value.i18n || {},
+ error: action.value.error
+ };
+
+ case LOGGED_OUT_AS:
+ return {
+ ...state,
+ token: action.value.token,
+ userName: action.value.userName,
+ i18n: action.value.i18n || {},
+ error: action.value.error
+ };
+
+ case CHECK_AUTH: return {
+ ...state,
+ token: action.value.token,
+ userName: action.value.userName,
+ i18n: action.value.i18n || {},
+ error: action.value.error
+ };
+
+ default: return state;
+ }
+};
+
+export default sessionReducer;
diff --git a/src/react/src/reducers/UserSession/userSessionReducer.js b/src/react/src/reducers/UserSession/userSessionReducer.js
new file mode 100644
index 000000000..015d71eeb
--- /dev/null
+++ b/src/react/src/reducers/UserSession/userSessionReducer.js
@@ -0,0 +1,19 @@
+import { SET_USER_SESSION } from 'src/actions/UserSession/userSessionTypes';
+
+const INITIAL_STATE = {
+ session: {}
+};
+
+const userSessionReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case SET_USER_SESSION:
+ return {
+ ...state,
+ session: action.value,
+ };
+
+ default: return state;
+ }
+};
+
+export default userSessionReducer;
diff --git a/src/react/src/reducers/rootReducer.js b/src/react/src/reducers/rootReducer.js
new file mode 100644
index 000000000..c3fde2017
--- /dev/null
+++ b/src/react/src/reducers/rootReducer.js
@@ -0,0 +1,18 @@
+import { combineReducers } from 'redux';
+import mainNavigationReducer from './MainNavigation/mainNavigationReducer';
+import controlPanelContentReducer from './ControlPanelContent/controlPanelContentReducer';
+import notificationReducer from './Notification/notificationReducer';
+import menuCounterReducer from './MenuCounters/menuCounterReducer';
+import userSessionReducer from './UserSession/userSessionReducer';
+import sessionReducer from './Session/sessionReducer';
+import panelReducer from './Panel/panel';
+
+export default combineReducers({
+ mainNavigation: mainNavigationReducer,
+ controlPanelContent: controlPanelContentReducer,
+ notifications: notificationReducer,
+ menuCounters: menuCounterReducer,
+ userSession: userSessionReducer,
+ session: sessionReducer,
+ panel: panelReducer,
+});
\ No newline at end of file
diff --git a/src/react/src/services/session.js b/src/react/src/services/session.js
new file mode 100644
index 000000000..412cbf5f5
--- /dev/null
+++ b/src/react/src/services/session.js
@@ -0,0 +1,52 @@
+import axios from 'axios';
+import { getAuthToken } from 'src/utils/token';
+
+const BASE_URL = window.location.origin;
+
+export const signIn = (data) => {
+ let formDataObject = new FormData();
+
+ for (let key in data) {
+ formDataObject.append(key, data[key]);
+ }
+
+ formDataObject.append("token", getAuthToken());
+
+ return axios.post(`${BASE_URL}/api/v1/login/index.php`, formDataObject);
+};
+
+export const checkAuth = () => {
+ let uri = '/api/v1/login/index.php';
+ const token = getAuthToken();
+
+ if (token) uri += `?token=${token}`;
+
+ return axios.get(`${BASE_URL}${uri}`);
+};
+
+export const signInAs = (username) => {
+ return axios.get(`${BASE_URL}/api/v1/login/index.php`, {
+ params: {
+ loginas: username,
+ token: getAuthToken()
+ }
+ });
+};
+
+export const signOut = () => {
+ return axios.get(`${BASE_URL}/api/v1/logout/index.php`);
+};
+
+export const getMainData = () => {
+ let uri = `${BASE_URL}/api/v1/login/session.php`;
+
+ return axios.get(uri);
+};
+
+export const checkAuthToken = token => {
+ let uri = `${BASE_URL}/api/v1/login/session.php`;
+
+ if (token) uri += `?token=${token}`;
+
+ return axios.get(uri);
+};
diff --git a/src/react/src/store.js b/src/react/src/store.js
new file mode 100644
index 000000000..81f70f3d1
--- /dev/null
+++ b/src/react/src/store.js
@@ -0,0 +1,11 @@
+import { composeWithDevTools } from 'redux-devtools-extension';
+import { createStore, applyMiddleware } from 'redux';
+import rootReducer from './reducers/rootReducer';
+import thunk from 'redux-thunk';
+
+export default function configureStore() {
+ return createStore(
+ rootReducer,
+ composeWithDevTools(applyMiddleware(thunk))
+ );
+}
\ No newline at end of file
diff --git a/src/react/src/utils/scss/_breakpoints.scss b/src/react/src/utils/scss/_breakpoints.scss
new file mode 100644
index 000000000..8b6d4eddc
--- /dev/null
+++ b/src/react/src/utils/scss/_breakpoints.scss
@@ -0,0 +1,11 @@
+$phoneMin: 320px;
+$phoneMax: 480px;
+
+$tabletMin: 481px;
+$tabletMax: 768px;
+
+$smallScreenMin: 769px;
+$smallScreenMax: 1024px;
+
+$desktopMin: 1025px;
+$desktopMax: 1200px;
diff --git a/src/react/src/utils/scss/_variables.scss b/src/react/src/utils/scss/_variables.scss
new file mode 100644
index 000000000..8180b8582
--- /dev/null
+++ b/src/react/src/utils/scss/_variables.scss
@@ -0,0 +1,13 @@
+$whiteBackground: #ececec;
+$primary: #2c54ac;
+$primaryLight: #d7dcef;
+$primaryActive: #1e5cb2;
+$secondary: #fcac04;
+$secondaryLight: #f8b014;
+$secondaryActive: #fdb51c;
+$hoverButtonText: #2c54ac;
+$activeButtonText: #fff;
+$textColor: #555;
+$danger: #b00e5b;
+$black: #000;
+$white: #fff;
diff --git a/src/react/src/utils/token.js b/src/react/src/utils/token.js
new file mode 100644
index 000000000..5d5c71afb
--- /dev/null
+++ b/src/react/src/utils/token.js
@@ -0,0 +1,5 @@
+const itemName = 'token';
+
+export const setAuthToken = token => localStorage.setItem(itemName, token);
+export const getAuthToken = () => localStorage.getItem(itemName);
+export const resetAuthToken = () => localStorage.removeItem(itemName);
diff --git a/src/rpm/specs/vesta-ioncube.spec b/src/rpm/specs/vesta-ioncube.spec
index 868d84bd3..cd2095a7c 100644
--- a/src/rpm/specs/vesta-ioncube.spec
+++ b/src/rpm/specs/vesta-ioncube.spec
@@ -1,6 +1,6 @@
Name: vesta-ioncube
-Version: 0.9.8
-Release: 25
+Version: 1.0.0
+Release: 1
Summary: ionCube Loader
Group: System Environment/Base
License: "Freely redistributable without restriction"
@@ -16,6 +16,8 @@ Provides: vesta-ioncube
%description
This package contains ionCube loader for Vesta
+%global debug_package %{nil}
+
%prep
%setup -q -n %{name}-%{version}
diff --git a/src/rpm/specs/vesta-nginx.spec b/src/rpm/specs/vesta-nginx.spec
index 3d3ab4bd1..3cdd1cc68 100644
--- a/src/rpm/specs/vesta-nginx.spec
+++ b/src/rpm/specs/vesta-nginx.spec
@@ -1,6 +1,6 @@
Name: vesta-nginx
-Version: 0.9.8
-Release: 25
+Version: 1.0.0
+Release: 1
Summary: Vesta Control Panel
Group: System Environment/Base
License: BSD-like
diff --git a/src/rpm/specs/vesta-php.spec b/src/rpm/specs/vesta-php.spec
index ee07bbd05..b0ed45149 100644
--- a/src/rpm/specs/vesta-php.spec
+++ b/src/rpm/specs/vesta-php.spec
@@ -1,6 +1,6 @@
Name: vesta-php
-Version: 0.9.8
-Release: 25
+Version: 1.0.0
+Release: 1
Summary: Vesta Control Panel
Group: System Environment/Base
License: GPL
@@ -20,9 +20,9 @@ This package contains php-cgi for Vesta Control Panel web interface.
%setup -q -n %{name}-%{version}
%build
-./configure --prefix=/usr/local/vesta/php --with-zlib --enable-fpm --with-fpm-user=admin --with-fpm-group=admin --with-mysql --with-mysqli --with-curl --enable-mbstring
+./configure --prefix=/usr/local/vesta/php --with-zlib --enable-zip --enable-fpm --with-fpm-user=admin --with-fpm-group=admin --with-mysql --with-mysqli --with-curl --enable-mbstring
-make
+make ZEND_EXTRA_LIBS='-lresolv'
%install
make install INSTALL_ROOT=%{buildroot} INSTALLDIRS=vendor
diff --git a/src/rpm/specs/vesta-softaculous.spec b/src/rpm/specs/vesta-softaculous.spec
index 23b8cf16a..5d2f9ebb7 100644
--- a/src/rpm/specs/vesta-softaculous.spec
+++ b/src/rpm/specs/vesta-softaculous.spec
@@ -1,6 +1,6 @@
Name: vesta-softaculous
-Version: 0.9.8
-Release: 25
+Version: 1.0.0
+Release: 1
Summary: Vesta Control Panel
Group: System Environment/Base
License: Softaculous License
@@ -16,6 +16,8 @@ Provides: vesta-softaculous
%description
This package contains Softaculous apps for Vesta Control Panel web interface.
+%global debug_package %{nil}
+
%prep
%setup -q -n %{name}-%{version}
diff --git a/src/rpm/specs/vesta.spec b/src/rpm/specs/vesta.spec
index d0b59e6a2..c1f7163e2 100644
--- a/src/rpm/specs/vesta.spec
+++ b/src/rpm/specs/vesta.spec
@@ -1,6 +1,6 @@
Name: vesta
-Version: 0.9.8
-Release: 25
+Version: 1.0.0
+Release: 7
Summary: Vesta Control Panel
Group: System Environment/Base
License: GPL
@@ -16,6 +16,8 @@ Provides: vestacp vesta-api vesta
%description
This package contains the packages for Vesta Control Panel api.
+%global debug_package %{nil}
+
%prep
%setup -q -n %{name}-%{version}
@@ -68,6 +70,33 @@ fi
%config(noreplace) %{_vestadir}/web/css/uploadify.css
%changelog
+* Wed Oct 12 2022 Serghey Rodin - 1.0.0-7
+- Frontend bugfixes
+- Bugfixes
+- Security fixes
+
+* Fri Feb 25 2022 Serghey Rodin - 1.0.0-6
+- Frontend bugfixes
+
+* Mon Nov 22 2021 Serghey Rodin - 1.0.0-5
+- Bugfixes
+
+* Mon Nov 1 2021 Serghey Rodin - 1.0.0-4
+- Merge pull request #2128 to fix LE related issues in UI
+
+* Sun Oct 31 2021 Serghey Rodin - 1.0.0-3
+- Merge pull request #2108 and #2109
+
+* Sat Oct 30 2021 Serghey Rodin - 1.0.0-2
+- Merge pull request #2074 from mix5003/fix-fi
+
+* Sat Oct 30 2021 Serghey Rodin - 1.0.0-1
+- Modern Web UI based on React
+- Bugfixes
+
+* Sat Sep 29 2019 Serghey Rodin - 0.9.8-26
+- Let's Encrypt HTTP/2 support
+
* Thu Aug 15 2019 Serghey Rodin - 0.9.8-25
- Security bugfixes
- LEv2 idn fix
diff --git a/upd/add_notifications.sh b/upd/add_notifications.sh
index 06882d298..eb85a2403 100755
--- a/upd/add_notifications.sh
+++ b/upd/add_notifications.sh
@@ -5,4 +5,4 @@ rm -f /usr/local/vesta/data/users/admin/notifications.conf
/usr/local/vesta/bin/v-add-user-notification admin "File Manager" "Browse, copy, edit, view, and retrieve all your web domain files using a fully featured File Manager . Plugin is available for purchase ." 'filemanager'
/usr/local/vesta/bin/v-add-user-notification admin "Chroot SFTP" "If you want to have SFTP accounts that will be used only to transfer files (and not to SSH), you can purchase and enable SFTP Chroot "
/usr/local/vesta/bin/v-add-user-notification admin "Softaculous" "Softaculous is one of the best Auto Installers and it is finally available "
-/usr/local/vesta/bin/v-add-user-notification admin "Release 0.9.8-25" "This release is about stability and refinement. We added Let's Encrypt v2 support and added server certificate management tools. For more information please read release notes "
+/usr/local/vesta/bin/v-add-user-notification admin "Release 1.0.0-1" "This release brings a new UI that is much more pleasant to use. We've also fixed a dozen bugs and added support for the new Linux Distros. For more information please check release notes "
diff --git a/upd/limit_sudo.sh b/upd/limit_sudo.sh
index 4f4ac9249..225721514 100755
--- a/upd/limit_sudo.sh
+++ b/upd/limit_sudo.sh
@@ -4,3 +4,5 @@ if [ -e "/etc/sudoers.d/admin" ]; then
sed -i "s/admin.*ALL=(ALL).*/# sudo is limited to vesta scripts/" \
/etc/sudoers.d/admin
fi
+
+sed -i "s/%admin.*ALL=(ALL).*/# sudo is limited to vesta scripts/" /etc/sudoers
diff --git a/web/add/cron/autoupdate/index.php b/web/add/cron/autoupdate/index.php
index 90854d9bd..30e3dec0b 100644
--- a/web/add/cron/autoupdate/index.php
+++ b/web/add/cron/autoupdate/index.php
@@ -1,21 +1 @@
-
diff --git a/web/add/cron/index.php b/web/add/cron/index.php
index c9302ae65..30e3dec0b 100644
--- a/web/add/cron/index.php
+++ b/web/add/cron/index.php
@@ -1,69 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_min = escapeshellarg($_POST['v_min']);
- $v_hour = escapeshellarg($_POST['v_hour']);
- $v_day = escapeshellarg($_POST['v_day']);
- $v_month = escapeshellarg($_POST['v_month']);
- $v_wday = escapeshellarg($_POST['v_wday']);
- $v_cmd = escapeshellarg($_POST['v_cmd']);
-
- // Add cron job
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-cron-job ".$user." ".$v_min." ".$v_hour." ".$v_day." ".$v_month." ".$v_wday." ".$v_cmd, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('CRON_CREATED_OK');
- unset($v_min);
- unset($v_hour);
- unset($v_day);
- unset($v_month);
- unset($v_wday);
- unset($v_cmd);
- unset($output);
- }
-}
-
-// Render
-render_page($user, $TAB, 'add_cron');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/cron/reports/index.php b/web/add/cron/reports/index.php
index 197c57604..30e3dec0b 100644
--- a/web/add/cron/reports/index.php
+++ b/web/add/cron/reports/index.php
@@ -1,19 +1 @@
-
diff --git a/web/add/db/index.php b/web/add/db/index.php
index 68f66b4a4..30e3dec0b 100644
--- a/web/add/db/index.php
+++ b/web/add/db/index.php
@@ -1,129 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Validate email
- if ((!empty($_POST['v_db_email'])) && (empty($_SESSION['error_msg']))) {
- if (!filter_var($_POST['v_db_email'], FILTER_VALIDATE_EMAIL)) {
- $_SESSION['error_msg'] = __('Please enter valid email address.');
- }
- }
-
- // Check password length
- if (empty($_SESSION['error_msg'])) {
- $pw_len = strlen($_POST['v_password']);
- if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
- }
-
- // Protect input
- $v_database = escapeshellarg($_POST['v_database']);
- $v_dbuser = escapeshellarg($_POST['v_dbuser']);
- $v_type = $_POST['v_type'];
- $v_charset = $_POST['v_charset'];
- $v_host = $_POST['v_host'];
- $v_db_email = $_POST['v_db_email'];
-
- // Add database
- if (empty($_SESSION['error_msg'])) {
- $v_type = escapeshellarg($_POST['v_type']);
- $v_charset = escapeshellarg($_POST['v_charset']);
- $v_host = escapeshellarg($_POST['v_host']);
- $v_password = tempnam("/tmp","vst");
- $fp = fopen($v_password, "w");
- fwrite($fp, $_POST['v_password']."\n");
- fclose($fp);
- exec (VESTA_CMD."v-add-database ".$user." ".$v_database." ".$v_dbuser." ".$v_password." ".$v_type." ".$v_host." ".$v_charset, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- unlink($v_password);
- $v_password = escapeshellarg($_POST['v_password']);
- $v_type = $_POST['v_type'];
- $v_host = $_POST['v_host'];
- $v_charset = $_POST['v_charset'];
- }
-
- // Get database manager url
- if (empty($_SESSION['error_msg'])) {
- list($http_host, $port) = explode(':', $_SERVER["HTTP_HOST"] . ":");
- if ($_POST['v_host'] != 'localhost' ) $http_host = $_POST['v_host'];
- if ($_POST['v_type'] == 'mysql') $db_admin = "phpMyAdmin";
- if ($_POST['v_type'] == 'mysql') $db_admin_link = "http://".$http_host."/phpmyadmin/";
- if (($_POST['v_type'] == 'mysql') && (!empty($_SESSION['DB_PMA_URL']))) $db_admin_link = $_SESSION['DB_PMA_URL'];
- if ($_POST['v_type'] == 'pgsql') $db_admin = "phpPgAdmin";
- if ($_POST['v_type'] == 'pgsql') $db_admin_link = "http://".$http_host."/phppgadmin/";
- if (($_POST['v_type'] == 'pgsql') && (!empty($_SESSION['DB_PGA_URL']))) $db_admin_link = $_SESSION['DB_PGA_URL'];
- }
-
- // Email login credentials
- if ((!empty($v_db_email)) && (empty($_SESSION['error_msg']))) {
- $to = $v_db_email;
- $subject = __("Database Credentials");
- $hostname = exec('hostname');
- $from = __('MAIL_FROM',$hostname);
- $mailtext = __('DATABASE_READY',$user."_".$_POST['v_database'],$user."_".$_POST['v_dbuser'],$_POST['v_password'],$db_admin_link);
- send_email($to, $subject, $mailtext, $from);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('DATABASE_CREATED_OK',htmlentities($user)."_".htmlentities($_POST['v_database']),htmlentities($user)."_".htmlentities($_POST['v_database']));
- $_SESSION['ok_msg'] .= " / " . __('open %s',$db_admin) . " ";
- unset($v_database);
- unset($v_dbuser);
- unset($v_password);
- unset($v_type);
- unset($v_charset);
- }
-}
-
-// Get user email
-$v_db_email = $panel[$user]['CONTACT'];
-
-// List avaiable database types
-$db_types = explode(',', $_SESSION['DB_SYSTEM']);
-
-// List available database servers
-exec (VESTA_CMD."v-list-database-hosts json", $output, $return_var);
-$db_hosts_tmp1 = json_decode(implode('', $output), true);
-$db_hosts_tmp2 = array_map(function($host){return $host['HOST'];}, $db_hosts_tmp1);
-$db_hosts = array_values(array_unique($db_hosts_tmp2));
-unset($output);
-unset($db_hosts_tmp1);
-unset($db_hosts_tmp2);
-
-render_page($user, $TAB, 'add_db');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/dns/index.php b/web/add/dns/index.php
index 7c18faab3..30e3dec0b 100644
--- a/web/add/dns/index.php
+++ b/web/add/dns/index.php
@@ -1,180 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_domain = preg_replace("/^www./i", "", $_POST['v_domain']);
- $v_domain = escapeshellarg($v_domain);
- $v_domain = strtolower($v_domain);
- $v_ip = escapeshellarg($_POST['v_ip']);
- $v_ns1 = escapeshellarg($_POST['v_ns1']);
- $v_ns2 = escapeshellarg($_POST['v_ns2']);
- $v_ns3 = escapeshellarg($_POST['v_ns3']);
- $v_ns4 = escapeshellarg($_POST['v_ns4']);
- $v_ns5 = escapeshellarg($_POST['v_ns5']);
- $v_ns6 = escapeshellarg($_POST['v_ns6']);
- $v_ns7 = escapeshellarg($_POST['v_ns7']);
- $v_ns8 = escapeshellarg($_POST['v_ns8']);
-
- // Add dns domain
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-dns-domain ".$user." ".$v_domain." ".$v_ip." ".$v_ns1." ".$v_ns2." ".$v_ns3." ".$v_ns4." ".$v_ns5." ".$v_ns6." ".$v_ns7." ".$v_ns8." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
-
- // Set expiriation date
- if (empty($_SESSION['error_msg'])) {
- if ((!empty($_POST['v_exp'])) && ($_POST['v_exp'] != date('Y-m-d', strtotime('+1 year')))) {
- $v_exp = escapeshellarg($_POST['v_exp']);
- exec (VESTA_CMD."v-change-dns-domain-exp ".$user." ".$v_domain." ".$v_exp." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
-
- // Set ttl
- if (empty($_SESSION['error_msg'])) {
- if ((!empty($_POST['v_ttl'])) && ($_POST['v_ttl'] != '14400') && (empty($_SESSION['error_msg']))) {
- $v_ttl = escapeshellarg($_POST['v_ttl']);
- exec (VESTA_CMD."v-change-dns-domain-ttl ".$user." ".$v_domain." ".$v_ttl." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
-
- // Restart dns server
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-restart-dns", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('DNS_DOMAIN_CREATED_OK',htmlentities($_POST[v_domain]),htmlentities($_POST[v_domain]));
- unset($v_domain);
- }
-}
-
-
-// Check POST request for dns record
-if (!empty($_POST['ok_rec'])) {
-
- // Check token
- if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
- header('location: /login/');
- exit();
- }
-
- // Check empty fields
- if (empty($_POST['v_domain'])) $errors[] = 'domain';
- if (empty($_POST['v_rec'])) $errors[] = 'record';
- if (empty($_POST['v_type'])) $errors[] = 'type';
- if (empty($_POST['v_val'])) $errors[] = 'value';
- if (!empty($errors[0])) {
- foreach ($errors as $i => $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_domain = escapeshellarg($_POST['v_domain']);
- $v_rec = escapeshellarg($_POST['v_rec']);
- $v_type = escapeshellarg($_POST['v_type']);
- $v_val = escapeshellarg($_POST['v_val']);
- $v_priority = escapeshellarg($_POST['v_priority']);
-
- // Add dns record
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-dns-record ".$user." ".$v_domain." ".$v_rec." ".$v_type." ".$v_val." ".$v_priority, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- $v_type = $_POST['v_type'];
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('DNS_RECORD_CREATED_OK',htmlentities($_POST[v_rec]),htmlentities($_POST[v_domain]));
- unset($v_domain);
- unset($v_rec);
- unset($v_val);
- unset($v_priority);
- }
-}
-
-
-$v_ns1 = str_replace("'", "", $v_ns1);
-$v_ns2 = str_replace("'", "", $v_ns2);
-$v_ns3 = str_replace("'", "", $v_ns3);
-$v_ns4 = str_replace("'", "", $v_ns4);
-$v_ns5 = str_replace("'", "", $v_ns5);
-$v_ns6 = str_replace("'", "", $v_ns6);
-$v_ns7 = str_replace("'", "", $v_ns7);
-$v_ns8 = str_replace("'", "", $v_ns8);
-
-
-if (empty($_GET['domain'])) {
- // Display body for dns domain
-
- if (empty($v_ttl)) $v_ttl = 14400;
- if (empty($v_exp)) $v_exp = date('Y-m-d', strtotime('+1 year'));
- if (empty($v_ns1)) {
- exec (VESTA_CMD."v-list-user-ns ".$user." json", $output, $return_var);
- $nameservers = json_decode(implode('', $output), true);
- $v_ns1 = str_replace("'", "", $nameservers[0]);
- $v_ns2 = str_replace("'", "", $nameservers[1]);
- $v_ns3 = str_replace("'", "", $nameservers[2]);
- $v_ns4 = str_replace("'", "", $nameservers[3]);
- $v_ns5 = str_replace("'", "", $nameservers[4]);
- $v_ns6 = str_replace("'", "", $nameservers[5]);
- $v_ns7 = str_replace("'", "", $nameservers[6]);
- $v_ns8 = str_replace("'", "", $nameservers[7]);
- unset($output);
- }
-
- render_page($user, $TAB, 'add_dns');
-} else {
- // Display body for dns record
-
- $v_domain = $_GET['domain'];
- render_page($user, $TAB, 'add_dns_rec');
-}
-
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/favorite/index.php b/web/add/favorite/index.php
index e9f2e828d..30e3dec0b 100644
--- a/web/add/favorite/index.php
+++ b/web/add/favorite/index.php
@@ -1,23 +1 @@
-
-
-error_reporting(NULL);
-session_start();
-
-
-include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
-
-// Check token
-// if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
-// header('location: /login/');
-// exit();
-// }
-
- // Protect input
- $v_section = escapeshellarg($_REQUEST['v_section']);
- $v_unit_id = escapeshellarg($_REQUEST['v_unit_id']);
-
- $_SESSION['favourites'][strtoupper($_REQUEST['v_section'])][$_REQUEST['v_unit_id']] = 1;
-
- exec (VESTA_CMD."v-add-user-favourites ".$_SESSION['user']." ".$v_section." ".$v_unit_id, $output, $return_var);
-// check_return_code($return_var,$output);
-?>
\ No newline at end of file
+
diff --git a/web/add/firewall/banlist/index.php b/web/add/firewall/banlist/index.php
index a9f616a2e..30e3dec0b 100644
--- a/web/add/firewall/banlist/index.php
+++ b/web/add/firewall/banlist/index.php
@@ -1,61 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_chain = escapeshellarg($_POST['v_chain']);
- $v_ip = escapeshellarg($_POST['v_ip']);
-
- // Add firewall ban
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-firewall-ban ".$v_ip." ".$v_chain, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('BANLIST_CREATED_OK');
- unset($v_ip);
- }
-}
-
-// Render
-render_page($user, $TAB, 'add_firewall_banlist');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/firewall/index.php b/web/add/firewall/index.php
index 9aab3ff5a..30e3dec0b 100644
--- a/web/add/firewall/index.php
+++ b/web/add/firewall/index.php
@@ -1,71 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_action = escapeshellarg($_POST['v_action']);
- $v_protocol = escapeshellarg($_POST['v_protocol']);
- $v_port = str_replace(" ",",", $_POST['v_port']);
- $v_port = preg_replace('/\,+/', ',', $v_port);
- $v_port = trim($v_port, ",");
- $v_port = escapeshellarg($v_port);
- $v_ip = escapeshellarg($_POST['v_ip']);
- $v_comment = escapeshellarg($_POST['v_comment']);
-
- // Add firewall rule
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-firewall-rule ".$v_action." ".$v_ip." ".$v_port." ".$v_protocol." ".$v_comment, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('RULE_CREATED_OK');
- unset($v_port);
- unset($v_ip);
- unset($v_comment);
- }
-}
-
-// Render
-render_page($user, $TAB, 'add_firewall');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/ip/index.php b/web/add/ip/index.php
index 73f56b381..30e3dec0b 100644
--- a/web/add/ip/index.php
+++ b/web/add/ip/index.php
@@ -1,92 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_ip = escapeshellarg($_POST['v_ip']);
- $v_netmask = escapeshellarg($_POST['v_netmask']);
- $v_name = escapeshellarg($_POST['v_name']);
- $v_nat = escapeshellarg($_POST['v_nat']);
- $v_interface = escapeshellarg($_POST['v_interface']);
- $v_owner = escapeshellarg($_POST['v_owner']);
- $v_shared = $_POST['v_shared'];
-
- // Check shared checkmark
- if ($v_shared == 'on') {
- $ip_status = 'shared';
- } else {
- $ip_status = 'dedicated';
- $v_dedicated = 'yes';
-
- }
-
- // Add IP
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-sys-ip ".$v_ip." ".$v_netmask." ".$v_interface." ".$v_owner." ".$ip_status." ".$v_name." ".$v_nat, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- $v_owner = $_POST['v_owner'];
- $v_interface = $_POST['v_interface'];
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('IP_CREATED_OK',htmlentities($_POST['v_ip']),htmlentities($_POST['v_ip']));
- unset($v_ip);
- unset($v_netmask);
- unset($v_name);
- unset($v_nat);
- }
-}
-
-// List network interfaces
-exec (VESTA_CMD."v-list-sys-interfaces json", $output, $return_var);
-$interfaces = json_decode(implode('', $output), true);
-unset($output);
-
-// List users
-exec (VESTA_CMD."v-list-sys-users json", $output, $return_var);
-$users = json_decode(implode('', $output), true);
-unset($output);
-
-// Render
-render_page($user, $TAB, 'add_ip');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/mail/index.php b/web/add/mail/index.php
index b260b465d..30e3dec0b 100644
--- a/web/add/mail/index.php
+++ b/web/add/mail/index.php
@@ -1,214 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Check antispam option
- if (!empty($_POST['v_antispam'])) {
- $v_antispam = 'yes';
- } else {
- $v_antispam = 'no';
- }
-
- // Check antivirus option
- if (!empty($_POST['v_antivirus'])) {
- $v_antivirus = 'yes';
- } else {
- $v_antivirus = 'no';
- }
-
- // Check dkim option
- if (!empty($_POST['v_dkim'])) {
- $v_dkim = 'yes';
- } else {
- $v_dkim = 'no';
- }
-
- // Set domain name to lowercase and remove www prefix
- $v_domain = preg_replace("/^www./i", "", $_POST['v_domain']);
- $v_domain = escapeshellarg($v_domain);
- $v_domain = strtolower($v_domain);
-
- // Add mail domain
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-mail-domain ".$user." ".$v_domain." ".$v_antispam." ".$v_antivirus." ".$v_dkim, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('MAIL_DOMAIN_CREATED_OK',htmlentities($_POST['v_domain']),htmlentities($_POST['v_domain']));
- unset($v_domain);
- }
-}
-
-
-// Check POST request for mail account
-if (!empty($_POST['ok_acc'])) {
-
- // Check token
- if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
- header('location: /login/');
- exit();
- }
-
- // Check empty fields
- if (empty($_POST['v_domain'])) $errors[] = __('domain');
- if (empty($_POST['v_account'])) $errors[] = __('account');
- if (empty($_POST['v_password'])) $errors[] = __('password');
- if (!empty($errors[0])) {
- foreach ($errors as $i => $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Validate email
- if ((!empty($_POST['v_send_email'])) && (empty($_SESSION['error_msg']))) {
- if (!filter_var($_POST['v_send_email'], FILTER_VALIDATE_EMAIL)) {
- $_SESSION['error_msg'] = __('Please enter valid email address.');
- }
- }
-
- // Protect input
- $v_domain = escapeshellarg($_POST['v_domain']);
- $v_domain = strtolower($v_domain);
- $v_account = escapeshellarg($_POST['v_account']);
- $v_quota = escapeshellarg($_POST['v_quota']);
- $v_send_email = $_POST['v_send_email'];
- $v_credentials = $_POST['v_credentials'];
- $v_aliases = $_POST['v_aliases'];
- $v_fwd = $_POST['v_fwd'];
- if (empty($_POST['v_quota'])) $v_quota = 0;
- if ((!empty($_POST['v_quota'])) || (!empty($_POST['v_aliases'])) || (!empty($_POST['v_fwd'])) ) $v_adv = 'yes';
-
- // Add Mail Account
- if (empty($_SESSION['error_msg'])) {
- $v_password = tempnam("/tmp","vst");
- $fp = fopen($v_password, "w");
- fwrite($fp, $_POST['v_password']."\n");
- fclose($fp);
- exec (VESTA_CMD."v-add-mail-account ".$user." ".$v_domain." ".$v_account." ".$v_password." ".$v_quota, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- unlink($v_password);
- $v_password = escapeshellarg($_POST['v_password']);
- }
-
- // Add Aliases
- if ((!empty($_POST['v_aliases'])) && (empty($_SESSION['error_msg']))) {
- $valiases = preg_replace("/\n/", " ", $_POST['v_aliases']);
- $valiases = preg_replace("/,/", " ", $valiases);
- $valiases = preg_replace('/\s+/', ' ',$valiases);
- $valiases = trim($valiases);
- $aliases = explode(" ", $valiases);
- foreach ($aliases as $alias) {
- $alias = escapeshellarg($alias);
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-mail-account-alias ".$user." ".$v_domain." ".$v_account." ".$alias, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
- }
-
- // Add Forwarders
- if ((!empty($_POST['v_fwd'])) && (empty($_SESSION['error_msg']))) {
- $vfwd = preg_replace("/\n/", " ", $_POST['v_fwd']);
- $vfwd = preg_replace("/,/", " ", $vfwd);
- $vfwd = preg_replace('/\s+/', ' ',$vfwd);
- $vfwd = trim($vfwd);
- $fwd = explode(" ", $vfwd);
- foreach ($fwd as $forward) {
- $forward = escapeshellarg($forward);
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-mail-account-forward ".$user." ".$v_domain." ".$v_account." ".$forward, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
- }
-
- // Add fwd_only flag
- if ((!empty($_POST['v_fwd_only'])) && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-add-mail-account-fwd-only ".$user." ".$v_domain." ".$v_account, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Get webmail url
- if (empty($_SESSION['error_msg'])) {
- list($http_host, $port) = explode(':', $_SERVER["HTTP_HOST"].":");
- $webmail = "http://".$http_host."/webmail/";
- if (!empty($_SESSION['MAIL_URL'])) $webmail = $_SESSION['MAIL_URL'];
- }
-
- // Email login credentials
- if ((!empty($v_send_email)) && (empty($_SESSION['error_msg']))) {
- $to = $v_send_email;
- $subject = __("Email Credentials");
- $hostname = exec('hostname');
- $from = __('MAIL_FROM', $hostname);
- $mailtext = $v_credentials;
- send_email($to, $subject, $mailtext, $from);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('MAIL_ACCOUNT_CREATED_OK',htmlentities(strtolower($_POST['v_account'])),htmlentities($_POST[v_domain]),htmlentities(strtolower($_POST['v_account'])),htmlentities($_POST[v_domain]));
- $_SESSION['ok_msg'] .= " / " . __('open webmail') . " ";
- unset($v_account);
- unset($v_password);
- unset($v_password);
- unset($v_aliases);
- unset($v_fwd);
- unset($v_quota);
- }
-}
-
-// Render page
-if (empty($_GET['domain'])) {
- // Display body for mail domain
-
- render_page($user, $TAB, 'add_mail');
-} else {
- // Display body for mail account
-
- $v_domain = $_GET['domain'];
- render_page($user, $TAB, 'add_mail_acc');
-}
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/package/index.php b/web/add/package/index.php
index b0d8be71a..30e3dec0b 100644
--- a/web/add/package/index.php
+++ b/web/add/package/index.php
@@ -1,209 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Protect input
- $v_package = escapeshellarg($_POST['v_package']);
- $v_web_template = escapeshellarg($_POST['v_web_template']);
- $v_backend_template = escapeshellarg($_POST['v_backend_template']);
- $v_proxy_template = escapeshellarg($_POST['v_proxy_template']);
- $v_dns_template = escapeshellarg($_POST['v_dns_template']);
- $v_shell = escapeshellarg($_POST['v_shell']);
- $v_web_domains = escapeshellarg($_POST['v_web_domains']);
- $v_web_aliases = escapeshellarg($_POST['v_web_aliases']);
- $v_dns_domains = escapeshellarg($_POST['v_dns_domains']);
- $v_dns_records = escapeshellarg($_POST['v_dns_records']);
- $v_mail_domains = escapeshellarg($_POST['v_mail_domains']);
- $v_mail_accounts = escapeshellarg($_POST['v_mail_accounts']);
- $v_databases = escapeshellarg($_POST['v_databases']);
- $v_cron_jobs = escapeshellarg($_POST['v_cron_jobs']);
- $v_backups = escapeshellarg($_POST['v_backups']);
- $v_disk_quota = escapeshellarg($_POST['v_disk_quota']);
- $v_bandwidth = escapeshellarg($_POST['v_bandwidth']);
- $v_ns1 = trim($_POST['v_ns1'], '.');
- $v_ns2 = trim($_POST['v_ns2'], '.');
- $v_ns3 = trim($_POST['v_ns3'], '.');
- $v_ns4 = trim($_POST['v_ns4'], '.');
- $v_ns5 = trim($_POST['v_ns5'], '.');
- $v_ns6 = trim($_POST['v_ns6'], '.');
- $v_ns7 = trim($_POST['v_ns7'], '.');
- $v_ns8 = trim($_POST['v_ns8'], '.');
- $v_ns = $v_ns1.",".$v_ns2;
- if (!empty($v_ns3)) $v_ns .= ",".$v_ns3;
- if (!empty($v_ns4)) $v_ns .= ",".$v_ns4;
- if (!empty($v_ns5)) $v_ns .= ",".$v_ns5;
- if (!empty($v_ns6)) $v_ns .= ",".$v_ns6;
- if (!empty($v_ns7)) $v_ns .= ",".$v_ns7;
- if (!empty($v_ns8)) $v_ns .= ",".$v_ns8;
- $v_ns = escapeshellarg($v_ns);
- $v_time = escapeshellarg(date('H:i:s'));
- $v_date = escapeshellarg(date('Y-m-d'));
-
- // Create temporary dir
- if (empty($_SESSION['error_msg'])) {
- exec ('mktemp -d', $output, $return_var);
- $tmpdir = $output[0];
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Create package file
- if (empty($_SESSION['error_msg'])) {
- $pkg = "WEB_TEMPLATE=".$v_web_template."\n";
- if (!empty($_SESSION['WEB_BACKEND'])) {
- $pkg .= "BACKEND_TEMPLATE=".$v_backend_template."\n";
- }
- if (!empty($_SESSION['PROXY_SYSTEM'])) {
- $pkg .= "PROXY_TEMPLATE=".$v_proxy_template."\n";
- }
- $pkg .= "DNS_TEMPLATE=".$v_dns_template."\n";
- $pkg .= "WEB_DOMAINS=".$v_web_domains."\n";
- $pkg .= "WEB_ALIASES=".$v_web_aliases."\n";
- $pkg .= "DNS_DOMAINS=".$v_dns_domains."\n";
- $pkg .= "DNS_RECORDS=".$v_dns_records."\n";
- $pkg .= "MAIL_DOMAINS=".$v_mail_domains."\n";
- $pkg .= "MAIL_ACCOUNTS=".$v_mail_accounts."\n";
- $pkg .= "DATABASES=".$v_databases."\n";
- $pkg .= "CRON_JOBS=".$v_cron_jobs."\n";
- $pkg .= "DISK_QUOTA=".$v_disk_quota."\n";
- $pkg .= "BANDWIDTH=".$v_bandwidth."\n";
- $pkg .= "NS=".$v_ns."\n";
- $pkg .= "SHELL=".$v_shell."\n";
- $pkg .= "BACKUPS=".$v_backups."\n";
- $pkg .= "TIME=".$v_time."\n";
- $pkg .= "DATE=".$v_date."\n";
-
- $fp = fopen($tmpdir."/".$_POST['v_package'].".pkg", 'w');
- fwrite($fp, $pkg);
- fclose($fp);
- }
-
- // Add new package
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-user-package ".$tmpdir." ".$v_package, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Remove tmpdir
- exec ('rm -rf '.$tmpdir, $output, $return_var);
- unset($output);
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('PACKAGE_CREATED_OK',htmlentities($_POST['v_package']),htmlentities($_POST['v_package']));
- unset($v_package);
- }
-
-}
-
-
-// List web temmplates
-exec (VESTA_CMD."v-list-web-templates json", $output, $return_var);
-$web_templates = json_decode(implode('', $output), true);
-unset($output);
-
-// List web templates for backend
-if (!empty($_SESSION['WEB_BACKEND'])) {
- exec (VESTA_CMD."v-list-web-templates-backend json", $output, $return_var);
- $backend_templates = json_decode(implode('', $output), true);
- unset($output);
-}
-
-// List web templates for proxy
-if (!empty($_SESSION['PROXY_SYSTEM'])) {
- exec (VESTA_CMD."v-list-web-templates-proxy json", $output, $return_var);
- $proxy_templates = json_decode(implode('', $output), true);
- unset($output);
-}
-
-// List DNS templates
-exec (VESTA_CMD."v-list-dns-templates json", $output, $return_var);
-$dns_templates = json_decode(implode('', $output), true);
-unset($output);
-
-// List system shells
-exec (VESTA_CMD."v-list-sys-shells json", $output, $return_var);
-$shells = json_decode(implode('', $output), true);
-unset($output);
-
-// Set default values
-if (empty($v_web_template)) $v_web_template = 'default';
-if (empty($v_backend_template)) $v_backend_template = 'default';
-if (empty($v_proxy_template)) $v_proxy_template = 'default';
-if (empty($v_dns_template)) $v_dns_template = 'default';
-if (empty($v_shell)) $v_shell = 'nologin';
-if (empty($v_web_domains)) $v_web_domains = "'1'";
-if (empty($v_web_aliases)) $v_web_aliases = "'1'";
-if (empty($v_dns_domains)) $v_dns_domains = "'1'";
-if (empty($v_dns_records)) $v_dns_records = "'1'";
-if (empty($v_mail_domains)) $v_mail_domains = "'1'";
-if (empty($v_mail_accounts)) $v_mail_accounts = "'1'";
-if (empty($v_databases)) $v_databases = "'1'";
-if (empty($v_cron_jobs)) $v_cron_jobs = "'1'";
-if (empty($v_backups)) $v_backups = "'1'";
-if (empty($v_disk_quota)) $v_disk_quota = "'1000'";
-if (empty($v_bandwidth)) $v_bandwidth = "'1000'";
-if (empty($v_ns1)) $v_ns1 = 'ns1.example.ltd';
-if (empty($v_ns2)) $v_ns2 = 'ns2.example.ltd';
-
-// Render page
-render_page($user, $TAB, 'add_package');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/user/index.php b/web/add/user/index.php
index 2cc79287f..30e3dec0b 100644
--- a/web/add/user/index.php
+++ b/web/add/user/index.php
@@ -1,129 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Validate email
- if ((empty($_SESSION['error_msg'])) && (!filter_var($_POST['v_email'], FILTER_VALIDATE_EMAIL))) {
- $_SESSION['error_msg'] = __('Please enter valid email address.');
- }
-
- // Check password length
- if (empty($_SESSION['error_msg'])) {
- $pw_len = strlen($_POST['v_password']);
- if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
- }
-
- // Protect input
- $v_username = escapeshellarg($_POST['v_username']);
- $v_email = escapeshellarg($_POST['v_email']);
- $v_package = escapeshellarg($_POST['v_package']);
- $v_language = escapeshellarg($_POST['v_language']);
- $v_fname = escapeshellarg($_POST['v_fname']);
- $v_lname = escapeshellarg($_POST['v_lname']);
- $v_notify = $_POST['v_notify'];
-
-
- // Add user
- if (empty($_SESSION['error_msg'])) {
- $v_password = tempnam("/tmp","vst");
- $fp = fopen($v_password, "w");
- fwrite($fp, $_POST['v_password']."\n");
- fclose($fp);
- exec (VESTA_CMD."v-add-user ".$v_username." ".$v_password." ".$v_email." ".$v_package." ".$v_fname." ".$v_lname, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- unlink($v_password);
- $v_password = escapeshellarg($_POST['v_password']);
- }
-
- // Set language
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-change-user-language ".$v_username." ".$v_language, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Send email to the new user
- if ((empty($_SESSION['error_msg'])) && (!empty($v_notify))) {
- $to = $_POST['v_notify'];
- $subject = _translate($_POST['v_language'],"Welcome to Vesta Control Panel");
- $hostname = exec('hostname');
- unset($output);
- $from = _translate($_POST['v_language'],'MAIL_FROM',$hostname);
- if (!empty($_POST['v_fname'])) {
- $mailtext = _translate($_POST['v_language'],'GREETINGS_GORDON_FREEMAN',$_POST['v_fname'],$_POST['v_lname']);
- } else {
- $mailtext = _translate($_POST['v_language'],'GREETINGS');
- }
- $mailtext .= _translate($_POST['v_language'],'ACCOUNT_READY',$_SERVER['HTTP_HOST'],$_POST['v_username'],$_POST['v_password']);
- send_email($to, $subject, $mailtext, $from);
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('USER_CREATED_OK',htmlentities($_POST['v_username']),htmlentities($_POST['v_username']));
- $_SESSION['ok_msg'] .= " / " . __('login as') ." ".htmlentities($_POST['v_username']). " ";
- unset($v_username);
- unset($v_password);
- unset($v_email);
- unset($v_fname);
- unset($v_lname);
- unset($v_notify);
- }
-}
-
-
-// List hosting packages
-exec (VESTA_CMD."v-list-user-packages json", $output, $return_var);
-check_error($return_var);
-$data = json_decode(implode('', $output), true);
-unset($output);
-
-// List languages
-exec (VESTA_CMD."v-list-sys-languages json", $output, $return_var);
-$languages = json_decode(implode('', $output), true);
-unset($output);
-
-// Render page
-render_page($user, $TAB, 'add_user');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/add/web/index.php b/web/add/web/index.php
index d77b8f409..30e3dec0b 100644
--- a/web/add/web/index.php
+++ b/web/add/web/index.php
@@ -1,368 +1 @@
- $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Check stats password length
- if ((!empty($v_stats)) && (empty($_SESSION['error_msg']))) {
- if (!empty($_POST['v_stats_user'])) {
- $pw_len = strlen($_POST['v_stats_password']);
- if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
- }
- }
-
- // Set domain to lowercase and remove www prefix
- $v_domain = preg_replace("/^www\./i", "", $_POST['v_domain']);
- $v_domain = escapeshellarg($v_domain);
- $v_domain = strtolower($v_domain);
-
- // Define domain ip address
- $v_ip = escapeshellarg($_POST['v_ip']);
-
- // Using public IP instead of internal IP when creating DNS
- // Gets public IP from 'v-list-user-ips' command (that reads /vesta/data/ips/ip), precisely from 'NAT' field
- $v_public_ip = $v_ip;
- $v_clean_ip = $_POST['v_ip']; // clean_ip = IP without quotas
- exec (VESTA_CMD."v-list-user-ips ".$user." json", $output, $return_var);
- $ips = json_decode(implode('', $output), true);
- unset($output);
- if (isset($ips[$v_clean_ip]) && isset($ips[$v_clean_ip]['NAT']) && trim($ips[$v_clean_ip]['NAT'])!='') {
- $v_public_ip = trim($ips[$v_clean_ip]['NAT']);
- $v_public_ip = escapeshellarg($v_public_ip);
- }
-
- // Define domain aliases
- $v_aliases = $_POST['v_aliases'];
- $aliases = preg_replace("/\n/", ",", $v_aliases);
- $aliases = preg_replace("/\r/", ",", $aliases);
- $aliases = preg_replace("/\t/", ",", $aliases);
- $aliases = preg_replace("/ /", ",", $aliases);
- $aliases_arr = explode(",", $aliases);
- $aliases_arr = array_unique($aliases_arr);
- $aliases_arr = array_filter($aliases_arr);
- $aliases = implode(",",$aliases_arr);
- $aliases = escapeshellarg($aliases);
- if (empty($_POST['v_aliases'])) $aliases = 'none';
-
- // Define proxy extensions
- $v_proxy_ext = $_POST['v_proxy_ext'];
- $proxy_ext = preg_replace("/\n/", ",", $v_proxy_ext);
- $proxy_ext = preg_replace("/\r/", ",", $proxy_ext);
- $proxy_ext = preg_replace("/\t/", ",", $proxy_ext);
- $proxy_ext = preg_replace("/ /", ",", $proxy_ext);
- $proxy_ext_arr = explode(",", $proxy_ext);
- $proxy_ext_arr = array_unique($proxy_ext_arr);
- $proxy_ext_arr = array_filter($proxy_ext_arr);
- $proxy_ext = implode(",",$proxy_ext_arr);
- $proxy_ext = escapeshellarg($proxy_ext);
-
- // Define other options
- $v_elog = $_POST['v_elog'];
- $v_ssl = $_POST['v_ssl'];
- $v_ssl_crt = $_POST['v_ssl_crt'];
- $v_ssl_key = $_POST['v_ssl_key'];
- $v_ssl_ca = $_POST['v_ssl_ca'];
- $v_ssl_home = $data[$v_domain]['SSL_HOME'];
- $v_letsencrypt = $_POST['v_letsencrypt'];
- $v_stats = escapeshellarg($_POST['v_stats']);
- $v_stats_user = $data[$v_domain]['STATS_USER'];
- $v_stats_password = $data[$v_domain]['STATS_PASSWORD'];
- $v_ftp = $_POST['v_ftp'];
- $v_ftp_user = $_POST['v_ftp_user'];
- $v_ftp_password = $_POST['v_ftp_password'];
- $v_ftp_email = $_POST['v_ftp_email'];
- if (!empty($v_domain)) $v_ftp_user_prepath .= $v_domain;
-
- // Set advanced option checkmark
- if (!empty($_POST['v_proxy'])) $v_adv = 'yes';
- if (!empty($_POST['v_ftp'])) $v_adv = 'yes';
- if ($_POST['v_proxy_ext'] != $v_proxy_ext) $v_adv = 'yes';
- if ((!empty($_POST['v_aliases'])) && ($_POST['v_aliases'] != 'www.'.$_POST['v_domain'])) $v_adv = 'yes';
- if ((!empty($_POST['v_ssl'])) || (!empty($_POST['v_elog']))) $v_adv = 'yes';
- if ((!empty($_POST['v_ssl_crt'])) || (!empty($_POST['v_ssl_key']))) $v_adv = 'yes';
- if ((!empty($_POST['v_ssl_ca'])) || ($_POST['v_stats'] != 'none')) $v_adv = 'yes';
- if ((!empty($_POST['v_letsencrypt']))) $v_adv = 'yes';
-
- // Check advanced features
- if (empty($_POST['v_dns'])) $v_dns = 'off';
- if (empty($_POST['v_mail'])) $v_mail = 'off';
- if (empty($_POST['v_proxy'])) $v_proxy = 'off';
-
- // Add web domain
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-add-web-domain ".$user." ".$v_domain." ".$v_ip." no ".$aliases." ".$proxy_ext, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- $domain_added = empty($_SESSION['error_msg']);
- }
-
- // Add DNS domain
- if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-add-dns-domain ".$user." ".$v_domain." ".$v_public_ip." '' '' '' '' '' '' '' '' no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Add DNS for domain aliases
- if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
- foreach ($aliases_arr as $alias) {
- if ($alias != "www.".$_POST['v_domain']) {
- $alias = escapeshellarg($alias);
- exec (VESTA_CMD."v-add-dns-on-web-alias ".$user." ".$alias." ".$v_ip." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
- }
-
- // Add mail domain
- if (($_POST['v_mail'] == 'on') && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-add-mail-domain ".$user." ".$v_domain, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Delete proxy support
- if ((!empty($_SESSION['PROXY_SYSTEM'])) && ($_POST['v_proxy'] == 'off') && (empty($_SESSION['error_msg']))) {
- $ext = escapeshellarg($ext);
- exec (VESTA_CMD."v-delete-web-domain-proxy ".$user." ".$v_domain." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Add Lets Encrypt support
- if ((!empty($_POST['v_letsencrypt'])) && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-schedule-letsencrypt-domain ".$user." ".$v_domain, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- } else {
- // Add SSL certificates only if Lets Encrypt is off
- if ((!empty($_POST['v_ssl'])) && (empty($_SESSION['error_msg']))) {
- exec ('mktemp -d', $output, $return_var);
- $tmpdir = $output[0];
- unset($output);
-
- // Save certificate
- if (!empty($_POST['v_ssl_crt'])) {
- $fp = fopen($tmpdir."/".$_POST['v_domain'].".crt", 'w');
- fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_crt']));
- fwrite($fp, "\n");
- fclose($fp);
- }
-
- // Save private key
- if (!empty($_POST['v_ssl_key'])) {
- $fp = fopen($tmpdir."/".$_POST['v_domain'].".key", 'w');
- fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_key']));
- fwrite($fp, "\n");
- fclose($fp);
- }
-
- // Save CA bundle
- if (!empty($_POST['v_ssl_ca'])) {
- $fp = fopen($tmpdir."/".$_POST['v_domain'].".ca", 'w');
- fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_ca']));
- fwrite($fp, "\n");
- fclose($fp);
- }
-
- $v_ssl_home = escapeshellarg($_POST['v_ssl_home']);
- exec (VESTA_CMD."v-add-web-domain-ssl ".$user." ".$v_domain." ".$tmpdir." ".$v_ssl_home." no", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
- }
-
- // Add web stats
- if ((!empty($_POST['v_stats'])) && ($_POST['v_stats'] != 'none' ) && (empty($_SESSION['error_msg']))) {
- $v_stats = escapeshellarg($_POST['v_stats']);
- exec (VESTA_CMD."v-add-web-domain-stats ".$user." ".$v_domain." ".$v_stats, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Add web stats password
- if ((!empty($_POST['v_stats_user'])) && (empty($_SESSION['error_msg']))) {
- $v_stats_user = escapeshellarg($_POST['v_stats_user']);
- $v_stats_password = tempnam("/tmp","vst");
- $fp = fopen($v_stats_password, "w");
- fwrite($fp, $_POST['v_stats_password']."\n");
- fclose($fp);
- exec (VESTA_CMD."v-add-web-domain-stats-user ".$user." ".$v_domain." ".$v_stats_user." ".$v_stats_password, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- unlink($v_stats_password);
- $v_stats_password = escapeshellarg($_POST['v_stats_password']);
- }
-
- // Restart DNS server
- if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-restart-dns", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Restart web server
- if (empty($_SESSION['error_msg'])) {
- exec (VESTA_CMD."v-restart-web", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Restart proxy server
- if ((!empty($_SESSION['PROXY_SYSTEM'])) && ($_POST['v_proxy'] == 'on') && (empty($_SESSION['error_msg']))) {
- exec (VESTA_CMD."v-restart-proxy", $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- }
-
- // Add FTP
- if ((!empty($_POST['v_ftp'])) && (empty($_SESSION['error_msg']))) {
- $v_ftp_users_updated = array();
- foreach ($_POST['v_ftp_user'] as $i => $v_ftp_user_data) {
- if ($v_ftp_user_data['is_new'] == 1) {
- if ((!empty($v_ftp_user_data['v_ftp_email'])) && (!filter_var($v_ftp_user_data['v_ftp_email'], FILTER_VALIDATE_EMAIL))) $_SESSION['error_msg'] = __('Please enter valid email address.');
- if (empty($v_ftp_user_data['v_ftp_user'])) $errors[] = 'ftp user';
- if (empty($v_ftp_user_data['v_ftp_password'])) $errors[] = 'ftp user password';
- if (!empty($errors[0])) {
- foreach ($errors as $i => $error) {
- if ( $i == 0 ) {
- $error_msg = $error;
- } else {
- $error_msg = $error_msg.", ".$error;
- }
- }
- $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
- }
-
- // Validate email
- if ((!empty($v_ftp_user_data['v_ftp_email'])) && (!filter_var($v_ftp_user_data['v_ftp_email'], FILTER_VALIDATE_EMAIL))) {
- $_SESSION['error_msg'] = __('Please enter valid email address.');
- }
-
- // Check ftp password length
- if ((!empty($v_ftp_user_data['v_ftp']))) {
- if (!empty($v_ftp_user_data['v_ftp_user'])) {
- $pw_len = strlen($v_ftp_user_data['v_ftp_password']);
- if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
- }
- }
-
- $v_ftp_user_data['v_ftp_user'] = preg_replace("/^".$user."_/i", "", $v_ftp_user_data['v_ftp_user']);
- $v_ftp_username = $v_ftp_user_data['v_ftp_user'];
- $v_ftp_username_full = $user . '_' . $v_ftp_user_data['v_ftp_user'];
- $v_ftp_user = escapeshellarg($v_ftp_user_data['v_ftp_user']);
- if ($domain_added) {
- $v_ftp_path = escapeshellarg(trim($v_ftp_user_data['v_ftp_path']));
- $v_ftp_password = tempnam("/tmp","vst");
- $fp = fopen($v_ftp_password, "w");
- fwrite($fp, $v_ftp_user_data['v_ftp_password']."\n");
- fclose($fp);
- exec (VESTA_CMD."v-add-web-domain-ftp ".$user." ".$v_domain." ".$v_ftp_user." ".$v_ftp_password . " " . $v_ftp_path, $output, $return_var);
- check_return_code($return_var,$output);
- unset($output);
- unlink($v_ftp_password);
- if ((!empty($v_ftp_user_data['v_ftp_email'])) && (empty($_SESSION['error_msg']))) {
- $to = $v_ftp_user_data['v_ftp_email'];
- $subject = __("FTP login credentials");
- $from = __('MAIL_FROM',$_POST['v_domain']);
- $mailtext = __('FTP_ACCOUNT_READY',$_POST['v_domain'],$user,$v_ftp_user_data['v_ftp_user'],$v_ftp_user_data['v_ftp_password']);
- send_email($to, $subject, $mailtext, $from);
- unset($v_ftp_email);
- }
- } else {
- $return_var = -1;
- }
-
- if ($return_var == 0) {
- $v_ftp_password = "••••••••";
- $v_ftp_user_data['is_new'] = 0;
- } else {
- $v_ftp_user_data['is_new'] = 1;
- }
-
- $v_ftp_username = preg_replace("/^".$user."_/", "", $v_ftp_user_data['v_ftp_user']);
- $v_ftp_users_updated[] = array(
- 'is_new' => $v_ftp_user_data['is_new'],
- 'v_ftp_user' => $return_var == 0 ? $v_ftp_username_full : $v_ftp_username,
- 'v_ftp_password' => $v_ftp_password,
- 'v_ftp_path' => $v_ftp_user_data['v_ftp_path'],
- 'v_ftp_email' => $v_ftp_user_data['v_ftp_email'],
- 'v_ftp_pre_path' => $v_ftp_user_prepath
- );
- continue;
- }
- }
-
- if (!empty($_SESSION['error_msg']) && $domain_added) {
- $_SESSION['ok_msg'] = __('WEB_DOMAIN_CREATED_OK',htmlentities($_POST[v_domain]),htmlentities($_POST[v_domain]));
- $_SESSION['flash_error_msg'] = $_SESSION['error_msg'];
- $url = '/edit/web/?domain='.strtolower(preg_replace("/^www\./i", "", $_POST['v_domain']));
- header('Location: ' . $url);
- exit;
- }
- }
-
- // Flush field values on success
- if (empty($_SESSION['error_msg'])) {
- $_SESSION['ok_msg'] = __('WEB_DOMAIN_CREATED_OK',htmlentities($_POST['v_domain']),htmlentities($_POST['v_domain']));
- unset($v_domain);
- unset($v_aliases);
- unset($v_ssl);
- unset($v_ssl_crt);
- unset($v_ssl_key);
- unset($v_ssl_ca);
- unset($v_stats_user);
- unset($v_stats_password);
- unset($v_ftp);
- }
-}
-
-// Define user variables
-$v_ftp_user_prepath = $panel[$user]['HOME'] . "/web";
-$v_ftp_email = $panel[$user]['CONTACT'];
-
-// List IP addresses
-exec (VESTA_CMD."v-list-user-ips ".$user." json", $output, $return_var);
-$ips = json_decode(implode('', $output), true);
-unset($output);
-
-// List web stat engines
-exec (VESTA_CMD."v-list-web-stats json", $output, $return_var);
-$stats = json_decode(implode('', $output), true);
-unset($output);
-
-// Render page
-render_page($user, $TAB, 'add_web');
-
-// Flush session messages
-unset($_SESSION['error_msg']);
-unset($_SESSION['ok_msg']);
+
diff --git a/web/api/v1/add/cron/autoupdate/index.php b/web/api/v1/add/cron/autoupdate/index.php
new file mode 100644
index 000000000..843dc71a0
--- /dev/null
+++ b/web/api/v1/add/cron/autoupdate/index.php
@@ -0,0 +1,27 @@
+ $message,
+ 'error' => $error,
+);
+
+print json_encode($result);
diff --git a/web/api/v1/add/cron/index.php b/web/api/v1/add/cron/index.php
new file mode 100644
index 000000000..f9407df92
--- /dev/null
+++ b/web/api/v1/add/cron/index.php
@@ -0,0 +1,72 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_min = escapeshellarg($_POST['v_min']);
+ $v_hour = escapeshellarg($_POST['v_hour']);
+ $v_day = escapeshellarg($_POST['v_day']);
+ $v_month = escapeshellarg($_POST['v_month']);
+ $v_wday = escapeshellarg($_POST['v_wday']);
+ $v_cmd = escapeshellarg($_POST['v_cmd']);
+
+ // Add cron job
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-cron-job ".$user." ".$v_min." ".$v_hour." ".$v_day." ".$v_month." ".$v_wday." ".$v_cmd, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('CRON_CREATED_OK');
+ unset($v_min);
+ unset($v_hour);
+ unset($v_day);
+ unset($v_month);
+ unset($v_wday);
+ unset($v_cmd);
+ unset($output);
+ }
+}
+
+$result = array(
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/add/cron/reports/index.php b/web/api/v1/add/cron/reports/index.php
new file mode 100644
index 000000000..b4ada7cad
--- /dev/null
+++ b/web/api/v1/add/cron/reports/index.php
@@ -0,0 +1,25 @@
+ $message,
+ 'error' => $error
+);
+
+print json_encode($result);
diff --git a/web/api/v1/add/db/index.php b/web/api/v1/add/db/index.php
new file mode 100644
index 000000000..8d54acbe3
--- /dev/null
+++ b/web/api/v1/add/db/index.php
@@ -0,0 +1,143 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Validate email
+ if ((!empty($_POST['v_db_email'])) && (empty($_SESSION['error_msg']))) {
+ if (!filter_var($_POST['v_db_email'], FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['error_msg'] = __('Please enter valid email address.');
+ }
+ }
+
+ // Check password length
+ if (empty($_SESSION['error_msg'])) {
+ $pw_len = strlen($_POST['v_password']);
+ if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
+ }
+
+ // Protect input
+ $v_database = escapeshellarg($_POST['v_database']);
+ $v_dbuser = escapeshellarg($_POST['v_dbuser']);
+ $v_type = $_POST['v_type'];
+ $v_charset = $_POST['v_charset'];
+ $v_host = $_POST['v_host'];
+ $v_db_email = $_POST['v_db_email'];
+
+ // Add database
+ if (empty($_SESSION['error_msg'])) {
+ $v_type = escapeshellarg($_POST['v_type']);
+ $v_charset = escapeshellarg($_POST['v_charset']);
+ $v_host = escapeshellarg($_POST['v_host']);
+ $v_password = tempnam("/tmp","vst");
+ $fp = fopen($v_password, "w");
+ fwrite($fp, $_POST['v_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-database ".$user." ".$v_database." ".$v_dbuser." ".$v_password." ".$v_type." ".$v_host." ".$v_charset, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_password);
+ $v_password = escapeshellarg($_POST['v_password']);
+ $v_type = $_POST['v_type'];
+ $v_host = $_POST['v_host'];
+ $v_charset = $_POST['v_charset'];
+ }
+
+ // Get database manager url
+ if (empty($_SESSION['error_msg'])) {
+ list($http_host, $port) = explode(':', $_SERVER["HTTP_HOST"] . ":");
+ if ($_POST['v_host'] != 'localhost' ) $http_host = $_POST['v_host'];
+ if ($_POST['v_type'] == 'mysql') $db_admin = "phpMyAdmin";
+ if ($_POST['v_type'] == 'mysql') $db_admin_link = "http://".$http_host."/phpmyadmin/";
+ if (($_POST['v_type'] == 'mysql') && (!empty($_SESSION['DB_PMA_URL']))) $db_admin_link = $_SESSION['DB_PMA_URL'];
+ if ($_POST['v_type'] == 'pgsql') $db_admin = "phpPgAdmin";
+ if ($_POST['v_type'] == 'pgsql') $db_admin_link = "http://".$http_host."/phppgadmin/";
+ if (($_POST['v_type'] == 'pgsql') && (!empty($_SESSION['DB_PGA_URL']))) $db_admin_link = $_SESSION['DB_PGA_URL'];
+ }
+
+ // Email login credentials
+ if ((!empty($v_db_email)) && (empty($_SESSION['error_msg']))) {
+ $to = $v_db_email;
+ $subject = __("Database Credentials");
+ $hostname = exec('hostname');
+ $from = __('MAIL_FROM',$hostname);
+ $mailtext = __('DATABASE_READY',$user."_".$_POST['v_database'],$user."_".$_POST['v_dbuser'],$_POST['v_password'],$db_admin_link);
+ send_email($to, $subject, $mailtext, $from);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('DATABASE_CREATED_OK',htmlentities($user)."_".htmlentities($_POST['v_database']),htmlentities($user)."_".htmlentities($_POST['v_database']));
+ $_SESSION['ok_msg'] .= " / " . __('open %s',$db_admin) . " ";
+ unset($v_database);
+ unset($v_dbuser);
+ unset($v_password);
+ unset($v_type);
+ unset($v_charset);
+ }
+}
+
+// Get user email
+$v_db_email = $panel[$user]['CONTACT'];
+
+// List avaiable database types
+$db_types = explode(',', $_SESSION['DB_SYSTEM']);
+
+// List available database servers
+exec (VESTA_CMD."v-list-database-hosts json", $output, $return_var);
+$db_hosts_tmp1 = json_decode(implode('', $output), true);
+$db_hosts_tmp2 = array_map(function($host){return $host['HOST'];}, $db_hosts_tmp1);
+$db_hosts = array_values(array_unique($db_hosts_tmp2));
+unset($output);
+unset($db_hosts_tmp1);
+unset($db_hosts_tmp2);
+
+$prefixI18N = __('Prefix will be automaticaly added to database name and database user', "".$user."_ ");
+$maxCharLength = __('maximum characters length, including prefix', 16);
+
+$result = array(
+ 'dbHosts' => $db_hosts,
+ 'dbTypes' => $db_types,
+ 'user' => $user,
+ 'prefixI18N' => $prefixI18N,
+ 'maxCharLength' => $maxCharLength,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/add/dns/index.php b/web/api/v1/add/dns/index.php
new file mode 100644
index 000000000..bb39939bc
--- /dev/null
+++ b/web/api/v1/add/dns/index.php
@@ -0,0 +1,188 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_domain = preg_replace("/^www./i", "", $_POST['v_domain']);
+ $v_domain = escapeshellarg($v_domain);
+ $v_domain = strtolower($v_domain);
+ $v_ip = escapeshellarg($_POST['v_ip']);
+ $v_ns1 = escapeshellarg($_POST['v_ns1']);
+ $v_ns2 = escapeshellarg($_POST['v_ns2']);
+ $v_ns3 = escapeshellarg($_POST['v_ns3']);
+ $v_ns4 = escapeshellarg($_POST['v_ns4']);
+ $v_ns5 = escapeshellarg($_POST['v_ns5']);
+ $v_ns6 = escapeshellarg($_POST['v_ns6']);
+ $v_ns7 = escapeshellarg($_POST['v_ns7']);
+ $v_ns8 = escapeshellarg($_POST['v_ns8']);
+
+ // Add dns domain
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-dns-domain ".$user." ".$v_domain." ".$v_ip." ".$v_ns1." ".$v_ns2." ".$v_ns3." ".$v_ns4." ".$v_ns5." ".$v_ns6." ".$v_ns7." ".$v_ns8." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+
+ // Set expiriation date
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_exp'])) && ($_POST['v_exp'] != date('Y-m-d', strtotime('+1 year')))) {
+ $v_exp = escapeshellarg($_POST['v_exp']);
+ exec (VESTA_CMD."v-change-dns-domain-exp ".$user." ".$v_domain." ".$v_exp." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+
+ // Set ttl
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_ttl'])) && ($_POST['v_ttl'] != '14400') && (empty($_SESSION['error_msg']))) {
+ $v_ttl = escapeshellarg($_POST['v_ttl']);
+ exec (VESTA_CMD."v-change-dns-domain-ttl ".$user." ".$v_domain." ".$v_ttl." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+
+ // Restart dns server
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-restart-dns", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('DNS_DOMAIN_CREATED_OK',htmlentities($_POST[v_domain]),htmlentities($_POST[v_domain]));
+ unset($v_domain);
+ }
+}
+
+
+// Check POST request for dns record
+if (!empty($_POST['ok_rec'])) {
+
+ // Check token
+ if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+ header('location: /login/');
+ exit();
+ }
+
+ // Check empty fields
+ if (empty($_POST['v_domain'])) $errors[] = 'domain';
+ if (empty($_POST['v_rec'])) $errors[] = 'record';
+ if (empty($_POST['v_type'])) $errors[] = 'type';
+ if (empty($_POST['v_val'])) $errors[] = 'value';
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_domain = escapeshellarg($_POST['v_domain']);
+ $v_rec = escapeshellarg($_POST['v_rec']);
+ $v_type = escapeshellarg($_POST['v_type']);
+ $v_val = escapeshellarg($_POST['v_val']);
+ $v_priority = escapeshellarg($_POST['v_priority']);
+
+ // Add dns record
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-dns-record ".$user." ".$v_domain." ".$v_rec." ".$v_type." ".$v_val." ".$v_priority, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_type = $_POST['v_type'];
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('DNS_RECORD_CREATED_OK',htmlentities($_POST[v_rec]),htmlentities($_POST[v_domain]));
+ unset($v_domain);
+ unset($v_rec);
+ unset($v_val);
+ unset($v_priority);
+ }
+}
+
+
+$v_ns1 = str_replace("'", "", $v_ns1);
+$v_ns2 = str_replace("'", "", $v_ns2);
+$v_ns3 = str_replace("'", "", $v_ns3);
+$v_ns4 = str_replace("'", "", $v_ns4);
+$v_ns5 = str_replace("'", "", $v_ns5);
+$v_ns6 = str_replace("'", "", $v_ns6);
+$v_ns7 = str_replace("'", "", $v_ns7);
+$v_ns8 = str_replace("'", "", $v_ns8);
+
+
+if (empty($_GET['domain'])) {
+ // Display body for dns domain
+
+ if (empty($v_ttl)) $v_ttl = 14400;
+ if (empty($v_exp)) $v_exp = date('Y-m-d', strtotime('+1 year'));
+ if (empty($v_ns1)) {
+ exec (VESTA_CMD."v-list-user-ns ".$user." json", $output, $return_var);
+ $nameservers = json_decode(implode('', $output), true);
+ $v_ns1 = str_replace("'", "", $nameservers[0]);
+ $v_ns2 = str_replace("'", "", $nameservers[1]);
+ $v_ns3 = str_replace("'", "", $nameservers[2]);
+ $v_ns4 = str_replace("'", "", $nameservers[3]);
+ $v_ns5 = str_replace("'", "", $nameservers[4]);
+ $v_ns6 = str_replace("'", "", $nameservers[5]);
+ $v_ns7 = str_replace("'", "", $nameservers[6]);
+ $v_ns8 = str_replace("'", "", $nameservers[7]);
+ unset($output);
+ }
+
+ // render_page($user, $TAB, 'add_dns');
+} else {
+ // Display body for dns record
+
+ $v_domain = $_GET['domain'];
+ // render_page($user, $TAB, 'add_dns_rec');
+}
+
+$result = array(
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
+
diff --git a/web/api/v1/add/favorite/index.php b/web/api/v1/add/favorite/index.php
new file mode 100644
index 000000000..94d48dadd
--- /dev/null
+++ b/web/api/v1/add/favorite/index.php
@@ -0,0 +1,22 @@
+
+
+error_reporting(NULL);
+session_start();
+
+
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+// Check token
+ // if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+ // exit();
+ // }
+
+ // Protect input
+ $v_section = escapeshellarg($_REQUEST['v_section']);
+ $v_unit_id = escapeshellarg($_REQUEST['v_unit_id']);
+
+ $_SESSION['favourites'][strtoupper($_REQUEST['v_section'])][$_REQUEST['v_unit_id']] = 1;
+
+ exec (VESTA_CMD."v-add-user-favourites ".$_SESSION['user']." ".$v_section." ".$v_unit_id, $output, $return_var);
+// check_return_code($return_var,$output);
+?>
\ No newline at end of file
diff --git a/web/api/v1/add/firewall/banlist/index.php b/web/api/v1/add/firewall/banlist/index.php
new file mode 100644
index 000000000..e60b8066c
--- /dev/null
+++ b/web/api/v1/add/firewall/banlist/index.php
@@ -0,0 +1,68 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_chain = escapeshellarg($_POST['v_chain']);
+ $v_ip = escapeshellarg($_POST['v_ip']);
+
+ // Add firewall ban
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-firewall-ban ".$v_ip." ".$v_chain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('BANLIST_CREATED_OK');
+ unset($v_ip);
+ }
+}
+
+$result = array(
+ 'ip' => $v_ip,
+ 'chain' => $v_chain,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/add/firewall/index.php b/web/api/v1/add/firewall/index.php
new file mode 100644
index 000000000..0f6e69e27
--- /dev/null
+++ b/web/api/v1/add/firewall/index.php
@@ -0,0 +1,77 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_action = escapeshellarg($_POST['v_action']);
+ $v_protocol = escapeshellarg($_POST['v_protocol']);
+ $v_port = str_replace(" ",",", $_POST['v_port']);
+ $v_port = preg_replace('/\,+/', ',', $v_port);
+ $v_port = trim($v_port, ",");
+ $v_port = escapeshellarg($v_port);
+ $v_ip = escapeshellarg($_POST['v_ip']);
+ $v_comment = escapeshellarg($_POST['v_comment']);
+
+ // Add firewall rule
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-firewall-rule ".$v_action." ".$v_ip." ".$v_port." ".$v_protocol." ".$v_comment, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('RULE_CREATED_OK');
+ unset($v_port);
+ unset($v_ip);
+ unset($v_comment);
+ }
+}
+
+// Render
+// render_page($user, $TAB, 'add_firewall');
+$result = array(
+ 'ok_msg' => $_SESSION['ok_msg'],
+ 'error_msg' => $_SESSION['error_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/add/ip/index.php b/web/api/v1/add/ip/index.php
new file mode 100644
index 000000000..0f3cf412e
--- /dev/null
+++ b/web/api/v1/add/ip/index.php
@@ -0,0 +1,99 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_ip = escapeshellarg($_POST['v_ip']);
+ $v_netmask = escapeshellarg($_POST['v_netmask']);
+ $v_name = escapeshellarg($_POST['v_name']);
+ $v_nat = escapeshellarg($_POST['v_nat']);
+ $v_interface = escapeshellarg($_POST['v_interface']);
+ $v_owner = escapeshellarg($_POST['v_owner']);
+ $v_shared = $_POST['v_shared'];
+
+ // Check shared checkmark
+ if ($v_shared == 'on') {
+ $ip_status = 'shared';
+ } else {
+ $ip_status = 'dedicated';
+ $v_dedicated = 'yes';
+
+ }
+
+ // Add IP
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-sys-ip ".$v_ip." ".$v_netmask." ".$v_interface." ".$v_owner." ".$ip_status." ".$v_name." ".$v_nat, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_owner = $_POST['v_owner'];
+ $v_interface = $_POST['v_interface'];
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('IP_CREATED_OK',htmlentities($_POST['v_ip']),htmlentities($_POST['v_ip']));
+ unset($v_ip);
+ unset($v_netmask);
+ unset($v_name);
+ unset($v_nat);
+ }
+}
+
+// List network interfaces
+exec (VESTA_CMD."v-list-sys-interfaces json", $output, $return_var);
+$interfaces = json_decode(implode('', $output), true);
+unset($output);
+
+// List users
+exec (VESTA_CMD."v-list-sys-users json", $output, $return_var);
+$users = json_decode(implode('', $output), true);
+unset($output);
+
+$result = array(
+ 'interfaces' => $interfaces,
+ 'users' => $users,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/add/mail/index.php b/web/api/v1/add/mail/index.php
new file mode 100644
index 000000000..428cbb1bd
--- /dev/null
+++ b/web/api/v1/add/mail/index.php
@@ -0,0 +1,220 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Check antispam option
+ if (!empty($_POST['v_antispam'])) {
+ $v_antispam = 'yes';
+ } else {
+ $v_antispam = 'no';
+ }
+
+ // Check antivirus option
+ if (!empty($_POST['v_antivirus'])) {
+ $v_antivirus = 'yes';
+ } else {
+ $v_antivirus = 'no';
+ }
+
+ // Check dkim option
+ if (!empty($_POST['v_dkim'])) {
+ $v_dkim = 'yes';
+ } else {
+ $v_dkim = 'no';
+ }
+
+ // Set domain name to lowercase and remove www prefix
+ $v_domain = preg_replace("/^www./i", "", $_POST['v_domain']);
+ $v_domain = escapeshellarg($v_domain);
+ $v_domain = strtolower($v_domain);
+
+ // Add mail domain
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-mail-domain ".$user." ".$v_domain." ".$v_antispam." ".$v_antivirus." ".$v_dkim, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('MAIL_DOMAIN_CREATED_OK',htmlentities($_POST['v_domain']),htmlentities($_POST['v_domain']));
+ unset($v_domain);
+ }
+}
+
+
+// Check POST request for mail account
+if (!empty($_POST['ok_acc'])) {
+
+ // Check token
+ if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+ exit();
+ }
+
+ // Check empty fields
+ if (empty($_POST['v_domain'])) $errors[] = __('domain');
+ if (empty($_POST['v_account'])) $errors[] = __('account');
+ if (empty($_POST['v_password'])) $errors[] = __('password');
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Validate email
+ if ((!empty($_POST['v_send_email'])) && (empty($_SESSION['error_msg']))) {
+ if (!filter_var($_POST['v_send_email'], FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['error_msg'] = __('Please enter valid email address.');
+ }
+ }
+
+ // Protect input
+ $v_domain = escapeshellarg($_POST['v_domain']);
+ $v_domain = strtolower($v_domain);
+ $v_account = escapeshellarg($_POST['v_account']);
+ $v_quota = escapeshellarg($_POST['v_quota']);
+ $v_send_email = $_POST['v_send_email'];
+ $v_credentials = $_POST['v_credentials'];
+ $v_aliases = $_POST['v_aliases'];
+ $v_fwd = $_POST['v_fwd'];
+ if (empty($_POST['v_quota'])) $v_quota = 0;
+ if ((!empty($_POST['v_quota'])) || (!empty($_POST['v_aliases'])) || (!empty($_POST['v_fwd'])) ) $v_adv = 'yes';
+
+ // Add Mail Account
+ if (empty($_SESSION['error_msg'])) {
+ $v_password = tempnam("/tmp","vst");
+ $fp = fopen($v_password, "w");
+ fwrite($fp, $_POST['v_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-mail-account ".$user." ".$v_domain." ".$v_account." ".$v_password." ".$v_quota, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_password);
+ $v_password = escapeshellarg($_POST['v_password']);
+ }
+
+ // Add Aliases
+ if ((!empty($_POST['v_aliases'])) && (empty($_SESSION['error_msg']))) {
+ $valiases = preg_replace("/\n/", " ", $_POST['v_aliases']);
+ $valiases = preg_replace("/,/", " ", $valiases);
+ $valiases = preg_replace('/\s+/', ' ',$valiases);
+ $valiases = trim($valiases);
+ $aliases = explode(" ", $valiases);
+ foreach ($aliases as $alias) {
+ $alias = escapeshellarg($alias);
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-mail-account-alias ".$user." ".$v_domain." ".$v_account." ".$alias, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+ }
+
+ // Add Forwarders
+ if ((!empty($_POST['v_fwd'])) && (empty($_SESSION['error_msg']))) {
+ $vfwd = preg_replace("/\n/", " ", $_POST['v_fwd']);
+ $vfwd = preg_replace("/,/", " ", $vfwd);
+ $vfwd = preg_replace('/\s+/', ' ',$vfwd);
+ $vfwd = trim($vfwd);
+ $fwd = explode(" ", $vfwd);
+ foreach ($fwd as $forward) {
+ $forward = escapeshellarg($forward);
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-mail-account-forward ".$user." ".$v_domain." ".$v_account." ".$forward, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+ }
+
+ // Add fwd_only flag
+ if ((!empty($_POST['v_fwd_only'])) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-add-mail-account-fwd-only ".$user." ".$v_domain." ".$v_account, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Get webmail url
+ if (empty($_SESSION['error_msg'])) {
+ list($http_host, $port) = explode(':', $_SERVER["HTTP_HOST"].":");
+ $webmail = "http://".$http_host."/webmail/";
+ if (!empty($_SESSION['MAIL_URL'])) $webmail = $_SESSION['MAIL_URL'];
+ }
+
+
+ // Email login credentials
+ if ((!empty($v_send_email)) && (empty($_SESSION['error_msg']))) {
+ $to = $v_send_email;
+ $subject = __("Email Credentials");
+ $hostname = exec('hostname');
+ $from = __('MAIL_FROM', $hostname);
+ $mailtext = $v_credentials;
+ send_email($to, $subject, $mailtext, $from);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('MAIL_ACCOUNT_CREATED_OK',htmlentities(strtolower($_POST['v_account'])),htmlentities($_POST[v_domain]),htmlentities(strtolower($_POST['v_account'])),htmlentities($_POST[v_domain]));
+ $_SESSION['ok_msg'] .= " / " . __('open webmail') . " ";
+ unset($v_account);
+ unset($v_password);
+ unset($v_password);
+ unset($v_aliases);
+ unset($v_fwd);
+ unset($v_quota);
+ }
+}
+
+// Render page
+if (empty($_GET['domain'])) {
+ // Display body for mail domain
+
+ // render_page($user, $TAB, 'add_mail');
+} else {
+ // Display body for mail account
+
+ $v_domain = $_GET['domain'];
+ // render_page($user, $TAB, 'add_mail_acc');
+}
+
+$result = array(
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/add/package/index.php b/web/api/v1/add/package/index.php
new file mode 100644
index 000000000..055d0e9c1
--- /dev/null
+++ b/web/api/v1/add/package/index.php
@@ -0,0 +1,224 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_package = escapeshellarg($_POST['v_package']);
+ $v_web_template = escapeshellarg($_POST['v_web_template']);
+ $v_backend_template = escapeshellarg($_POST['v_backend_template']);
+ $v_proxy_template = escapeshellarg($_POST['v_proxy_template']);
+ $v_dns_template = escapeshellarg($_POST['v_dns_template']);
+ $v_shell = escapeshellarg($_POST['v_shell']);
+ $v_web_domains = escapeshellarg($_POST['v_web_domains']);
+ $v_web_aliases = escapeshellarg($_POST['v_web_aliases']);
+ $v_dns_domains = escapeshellarg($_POST['v_dns_domains']);
+ $v_dns_records = escapeshellarg($_POST['v_dns_records']);
+ $v_mail_domains = escapeshellarg($_POST['v_mail_domains']);
+ $v_mail_accounts = escapeshellarg($_POST['v_mail_accounts']);
+ $v_databases = escapeshellarg($_POST['v_databases']);
+ $v_cron_jobs = escapeshellarg($_POST['v_cron_jobs']);
+ $v_backups = escapeshellarg($_POST['v_backups']);
+ $v_disk_quota = escapeshellarg($_POST['v_disk_quota']);
+ $v_bandwidth = escapeshellarg($_POST['v_bandwidth']);
+ $v_ns1 = trim($_POST['v_ns1'], '.');
+ $v_ns2 = trim($_POST['v_ns2'], '.');
+ $v_ns3 = trim($_POST['v_ns3'], '.');
+ $v_ns4 = trim($_POST['v_ns4'], '.');
+ $v_ns5 = trim($_POST['v_ns5'], '.');
+ $v_ns6 = trim($_POST['v_ns6'], '.');
+ $v_ns7 = trim($_POST['v_ns7'], '.');
+ $v_ns8 = trim($_POST['v_ns8'], '.');
+ $v_ns = $v_ns1.",".$v_ns2;
+ if (!empty($v_ns3)) $v_ns .= ",".$v_ns3;
+ if (!empty($v_ns4)) $v_ns .= ",".$v_ns4;
+ if (!empty($v_ns5)) $v_ns .= ",".$v_ns5;
+ if (!empty($v_ns6)) $v_ns .= ",".$v_ns6;
+ if (!empty($v_ns7)) $v_ns .= ",".$v_ns7;
+ if (!empty($v_ns8)) $v_ns .= ",".$v_ns8;
+ $v_ns = escapeshellarg($v_ns);
+ $v_time = escapeshellarg(date('H:i:s'));
+ $v_date = escapeshellarg(date('Y-m-d'));
+
+ // Create temporary dir
+ if (empty($_SESSION['error_msg'])) {
+ exec ('mktemp -d', $output, $return_var);
+ $tmpdir = $output[0];
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Create package file
+ if (empty($_SESSION['error_msg'])) {
+ $pkg = "WEB_TEMPLATE=".$v_web_template."\n";
+ if (!empty($_SESSION['WEB_BACKEND'])) {
+ $pkg .= "BACKEND_TEMPLATE=".$v_backend_template."\n";
+ }
+ if (!empty($_SESSION['PROXY_SYSTEM'])) {
+ $pkg .= "PROXY_TEMPLATE=".$v_proxy_template."\n";
+ }
+ $pkg .= "DNS_TEMPLATE=".$v_dns_template."\n";
+ $pkg .= "WEB_DOMAINS=".$v_web_domains."\n";
+ $pkg .= "WEB_ALIASES=".$v_web_aliases."\n";
+ $pkg .= "DNS_DOMAINS=".$v_dns_domains."\n";
+ $pkg .= "DNS_RECORDS=".$v_dns_records."\n";
+ $pkg .= "MAIL_DOMAINS=".$v_mail_domains."\n";
+ $pkg .= "MAIL_ACCOUNTS=".$v_mail_accounts."\n";
+ $pkg .= "DATABASES=".$v_databases."\n";
+ $pkg .= "CRON_JOBS=".$v_cron_jobs."\n";
+ $pkg .= "DISK_QUOTA=".$v_disk_quota."\n";
+ $pkg .= "BANDWIDTH=".$v_bandwidth."\n";
+ $pkg .= "NS=".$v_ns."\n";
+ $pkg .= "SHELL=".$v_shell."\n";
+ $pkg .= "BACKUPS=".$v_backups."\n";
+ $pkg .= "TIME=".$v_time."\n";
+ $pkg .= "DATE=".$v_date."\n";
+
+ $fp = fopen($tmpdir."/".$_POST['v_package'].".pkg", 'w');
+ fwrite($fp, $pkg);
+ fclose($fp);
+ }
+
+ // Add new package
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-user-package ".$tmpdir." ".$v_package, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Remove tmpdir
+ exec ('rm -rf '.$tmpdir, $output, $return_var);
+ unset($output);
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('PACKAGE_CREATED_OK',htmlentities($_POST['v_package']),htmlentities($_POST['v_package']));
+ unset($v_package);
+ }
+
+}
+
+
+// List web temmplates
+exec (VESTA_CMD."v-list-web-templates json", $output, $return_var);
+$web_templates = json_decode(implode('', $output), true);
+unset($output);
+
+// List web templates for backend
+if (!empty($_SESSION['WEB_BACKEND'])) {
+ exec (VESTA_CMD."v-list-web-templates-backend json", $output, $return_var);
+ $backend_templates = json_decode(implode('', $output), true);
+ unset($output);
+}
+
+// List web templates for proxy
+if (!empty($_SESSION['PROXY_SYSTEM'])) {
+ exec (VESTA_CMD."v-list-web-templates-proxy json", $output, $return_var);
+ $proxy_templates = json_decode(implode('', $output), true);
+ unset($output);
+}
+
+// List DNS templates
+exec (VESTA_CMD."v-list-dns-templates json", $output, $return_var);
+$dns_templates = json_decode(implode('', $output), true);
+unset($output);
+
+// List system shells
+exec (VESTA_CMD."v-list-sys-shells json", $output, $return_var);
+$shells = json_decode(implode('', $output), true);
+unset($output);
+
+// Set default values
+if (empty($v_web_template)) $v_web_template = 'default';
+if (empty($v_backend_template)) $v_backend_template = 'default';
+if (empty($v_proxy_template)) $v_proxy_template = 'default';
+if (empty($v_dns_template)) $v_dns_template = 'default';
+if (empty($v_shell)) $v_shell = 'nologin';
+if (empty($v_web_domains)) $v_web_domains = "'1'";
+if (empty($v_web_aliases)) $v_web_aliases = "'1'";
+if (empty($v_dns_domains)) $v_dns_domains = "'1'";
+if (empty($v_dns_records)) $v_dns_records = "'1'";
+if (empty($v_mail_domains)) $v_mail_domains = "'1'";
+if (empty($v_mail_accounts)) $v_mail_accounts = "'1'";
+if (empty($v_databases)) $v_databases = "'1'";
+if (empty($v_cron_jobs)) $v_cron_jobs = "'1'";
+if (empty($v_backups)) $v_backups = "'1'";
+if (empty($v_disk_quota)) $v_disk_quota = "'1000'";
+if (empty($v_bandwidth)) $v_bandwidth = "'1000'";
+if (empty($v_ns1)) $v_ns1 = 'ns1.example.ltd';
+if (empty($v_ns2)) $v_ns2 = 'ns2.example.ltd';
+
+// Render page
+// render_page($user, $TAB, 'add_package');
+
+$result = array(
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'web_templates' => $web_templates,
+ 'web_backend' => $_SESSION['WEB_BACKEND'],
+ 'backend_templates' => $backend_templates,
+ 'proxy_system' => $_SESSION['PROXY_SYSTEM'],
+ 'proxy_templates' => $proxy_templates,
+ 'dns_system' => $_SESSION['DNS_SYSTEM'],
+ 'dns_templates' => $dns_templates,
+ 'ssh_access' => $shells,
+ 'ok_msg' => $_SESSION['ok_msg'],
+ 'error_msg' => $_SESSION['error_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/add/user/index.php b/web/api/v1/add/user/index.php
new file mode 100644
index 000000000..5057b774a
--- /dev/null
+++ b/web/api/v1/add/user/index.php
@@ -0,0 +1,131 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Validate email
+ if ((empty($_SESSION['error_msg'])) && (!filter_var($_POST['v_email'], FILTER_VALIDATE_EMAIL))) {
+ $_SESSION['error_msg'] = __('Please enter valid email address.');
+ }
+
+ // Check password length
+ if (empty($_SESSION['error_msg'])) {
+ $pw_len = strlen($_POST['v_password']);
+ if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
+ }
+
+ // Protect input
+ $v_username = escapeshellarg($_POST['v_username']);
+ $v_email = escapeshellarg($_POST['v_email']);
+ $v_package = escapeshellarg($_POST['v_package']);
+ $v_language = escapeshellarg($_POST['v_language']);
+ $v_fname = escapeshellarg($_POST['v_fname']);
+ $v_lname = escapeshellarg($_POST['v_lname']);
+ $v_notify = $_POST['v_notify'];
+
+
+ // Add user
+ if (empty($_SESSION['error_msg'])) {
+ $v_password = tempnam("/tmp","vst");
+ $fp = fopen($v_password, "w");
+ fwrite($fp, $_POST['v_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-user ".$v_username." ".$v_password." ".$v_email." ".$v_package." ".$v_fname." ".$v_lname, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_password);
+ $v_password = escapeshellarg($_POST['v_password']);
+ }
+
+ // Set language
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-change-user-language ".$v_username." ".$v_language, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Send email to the new user
+ if ((empty($_SESSION['error_msg'])) && (!empty($v_notify))) {
+ $to = $_POST['v_notify'];
+ $subject = _translate($_POST['v_language'],"Welcome to Vesta Control Panel");
+ $hostname = exec('hostname');
+ unset($output);
+ $from = _translate($_POST['v_language'],'MAIL_FROM',$hostname);
+ if (!empty($_POST['v_fname'])) {
+ $mailtext = _translate($_POST['v_language'],'GREETINGS_GORDON_FREEMAN',$_POST['v_fname'],$_POST['v_lname']);
+ } else {
+ $mailtext = _translate($_POST['v_language'],'GREETINGS');
+ }
+ $mailtext .= _translate($_POST['v_language'],'ACCOUNT_READY',$_SERVER['HTTP_HOST'],$_POST['v_username'],$_POST['v_password']);
+ send_email($to, $subject, $mailtext, $from);
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('USER_CREATED_OK',htmlentities($_POST['v_username']),htmlentities($_POST['v_username']));
+ $_SESSION['ok_msg'] .= " / " . __('login as') ." ".htmlentities($_POST['v_username']). " ";
+ unset($v_username);
+ unset($v_password);
+ unset($v_email);
+ unset($v_fname);
+ unset($v_lname);
+ unset($v_notify);
+ }
+}
+
+
+// List hosting packages
+exec (VESTA_CMD."v-list-user-packages json", $output, $return_var);
+check_error($return_var);
+$data = json_decode(implode('', $output), true);
+unset($output);
+
+// List languages
+exec (VESTA_CMD."v-list-sys-languages json", $output, $return_var);
+$languages = json_decode(implode('', $output), true);
+unset($output);
+
+$result = array(
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/add/web/index.php b/web/api/v1/add/web/index.php
new file mode 100644
index 000000000..8b76cafcf
--- /dev/null
+++ b/web/api/v1/add/web/index.php
@@ -0,0 +1,377 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Check stats password length
+ if ((!empty($v_stats)) && (empty($_SESSION['error_msg']))) {
+ if (!empty($_POST['v_stats_user'])) {
+ $pw_len = strlen($_POST['v_stats_password']);
+ if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
+ }
+ }
+
+ // Set domain to lowercase and remove www prefix
+ $v_domain = preg_replace("/^www\./i", "", $_POST['v_domain']);
+ $v_domain = escapeshellarg($v_domain);
+ $v_domain = strtolower($v_domain);
+
+ // Define domain ip address
+ $v_ip = escapeshellarg($_POST['v_ip']);
+
+ // Using public IP instead of internal IP when creating DNS
+ // Gets public IP from 'v-list-user-ips' command (that reads /vesta/data/ips/ip), precisely from 'NAT' field
+ $v_public_ip = $v_ip;
+ $v_clean_ip = $_POST['v_ip']; // clean_ip = IP without quotas
+ exec (VESTA_CMD."v-list-user-ips ".$user." json", $output, $return_var);
+ $ips = json_decode(implode('', $output), true);
+ unset($output);
+ if (isset($ips[$v_clean_ip]) && isset($ips[$v_clean_ip]['NAT']) && trim($ips[$v_clean_ip]['NAT'])!='') {
+ $v_public_ip = trim($ips[$v_clean_ip]['NAT']);
+ $v_public_ip = escapeshellarg($v_public_ip);
+ }
+
+ // Define domain aliases
+ $v_aliases = $_POST['v_aliases'];
+ $aliases = preg_replace("/\n/", ",", $v_aliases);
+ $aliases = preg_replace("/\r/", ",", $aliases);
+ $aliases = preg_replace("/\t/", ",", $aliases);
+ $aliases = preg_replace("/ /", ",", $aliases);
+ $aliases_arr = explode(",", $aliases);
+ $aliases_arr = array_unique($aliases_arr);
+ $aliases_arr = array_filter($aliases_arr);
+ $aliases = implode(",",$aliases_arr);
+ $aliases = escapeshellarg($aliases);
+ if (empty($_POST['v_aliases'])) $aliases = 'none';
+
+ // Define proxy extensions
+ $v_proxy_ext = $_POST['v_proxy_ext'];
+ $proxy_ext = preg_replace("/\n/", ",", $v_proxy_ext);
+ $proxy_ext = preg_replace("/\r/", ",", $proxy_ext);
+ $proxy_ext = preg_replace("/\t/", ",", $proxy_ext);
+ $proxy_ext = preg_replace("/ /", ",", $proxy_ext);
+ $proxy_ext_arr = explode(",", $proxy_ext);
+ $proxy_ext_arr = array_unique($proxy_ext_arr);
+ $proxy_ext_arr = array_filter($proxy_ext_arr);
+ $proxy_ext = implode(",",$proxy_ext_arr);
+ $proxy_ext = escapeshellarg($proxy_ext);
+
+ // Define other options
+ $v_elog = $_POST['v_elog'];
+ $v_ssl = $_POST['v_ssl'];
+ $v_ssl_crt = $_POST['v_ssl_crt'];
+ $v_ssl_key = $_POST['v_ssl_key'];
+ $v_ssl_ca = $_POST['v_ssl_ca'];
+ $v_ssl_home = $data[$v_domain]['SSL_HOME'];
+ $v_letsencrypt = $_POST['v_letsencrypt'];
+ $v_stats = escapeshellarg($_POST['v_stats']);
+ $v_stats_user = $data[$v_domain]['STATS_USER'];
+ $v_stats_password = $data[$v_domain]['STATS_PASSWORD'];
+ $v_ftp = $_POST['v_ftp'];
+ $v_ftp_user = $_POST['v_ftp_user'];
+ $v_ftp_password = $_POST['v_ftp_password'];
+ $v_ftp_email = $_POST['v_ftp_email'];
+ if (!empty($v_domain)) $v_ftp_user_prepath .= $v_domain;
+
+ // Set advanced option checkmark
+ if (!empty($_POST['v_proxy'])) $v_adv = 'yes';
+ if (!empty($_POST['v_ftp'])) $v_adv = 'yes';
+ if ($_POST['v_proxy_ext'] != $v_proxy_ext) $v_adv = 'yes';
+ if ((!empty($_POST['v_aliases'])) && ($_POST['v_aliases'] != 'www.'.$_POST['v_domain'])) $v_adv = 'yes';
+ if ((!empty($_POST['v_ssl'])) || (!empty($_POST['v_elog']))) $v_adv = 'yes';
+ if ((!empty($_POST['v_ssl_crt'])) || (!empty($_POST['v_ssl_key']))) $v_adv = 'yes';
+ if ((!empty($_POST['v_ssl_ca'])) || ($_POST['v_stats'] != 'none')) $v_adv = 'yes';
+ if ((!empty($_POST['v_letsencrypt']))) $v_adv = 'yes';
+
+ // Check advanced features
+ if (empty($_POST['v_dns'])) $v_dns = 'off';
+ if (empty($_POST['v_mail'])) $v_mail = 'off';
+ if (empty($_POST['v_proxy'])) $v_proxy = 'off';
+
+ // Add web domain
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-add-web-domain ".$user." ".$v_domain." ".$v_ip." no ".$aliases." ".$proxy_ext, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $domain_added = empty($_SESSION['error_msg']);
+ }
+
+ // Add DNS domain
+ if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-add-dns-domain ".$user." ".$v_domain." ".$v_public_ip." '' '' '' '' '' '' '' '' no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Add DNS for domain aliases
+ if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
+ foreach ($aliases_arr as $alias) {
+ if ($alias != "www.".$_POST['v_domain']) {
+ $alias = escapeshellarg($alias);
+ exec (VESTA_CMD."v-add-dns-on-web-alias ".$user." ".$alias." ".$v_ip." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+ }
+
+ // Add mail domain
+ if (($_POST['v_mail'] == 'on') && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-add-mail-domain ".$user." ".$v_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Delete proxy support
+ if ((!empty($_SESSION['PROXY_SYSTEM'])) && ($_POST['v_proxy'] == 'off') && (empty($_SESSION['error_msg']))) {
+ $ext = escapeshellarg($ext);
+ exec (VESTA_CMD."v-delete-web-domain-proxy ".$user." ".$v_domain." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Add Lets Encrypt support
+ if ((!empty($_POST['v_letsencrypt'])) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-schedule-letsencrypt-domain ".$user." ".$v_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ } else {
+ // Add SSL certificates only if Lets Encrypt is off
+ if ((!empty($_POST['v_ssl'])) && (empty($_SESSION['error_msg']))) {
+ exec ('mktemp -d', $output, $return_var);
+ $tmpdir = $output[0];
+ unset($output);
+
+ // Save certificate
+ if (!empty($_POST['v_ssl_crt'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".crt", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_crt']));
+ fwrite($fp, "\n");
+ fclose($fp);
+ }
+
+ // Save private key
+ if (!empty($_POST['v_ssl_key'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".key", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_key']));
+ fwrite($fp, "\n");
+ fclose($fp);
+ }
+
+ // Save CA bundle
+ if (!empty($_POST['v_ssl_ca'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".ca", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_ca']));
+ fwrite($fp, "\n");
+ fclose($fp);
+ }
+
+ $v_ssl_home = escapeshellarg($_POST['v_ssl_home']);
+ exec (VESTA_CMD."v-add-web-domain-ssl ".$user." ".$v_domain." ".$tmpdir." ".$v_ssl_home." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+ }
+
+ // Add web stats
+ if ((!empty($_POST['v_stats'])) && ($_POST['v_stats'] != 'none' ) && (empty($_SESSION['error_msg']))) {
+ $v_stats = escapeshellarg($_POST['v_stats']);
+ exec (VESTA_CMD."v-add-web-domain-stats ".$user." ".$v_domain." ".$v_stats, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Add web stats password
+ if ((!empty($_POST['v_stats_user'])) && (empty($_SESSION['error_msg']))) {
+ $v_stats_user = escapeshellarg($_POST['v_stats_user']);
+ $v_stats_password = tempnam("/tmp","vst");
+ $fp = fopen($v_stats_password, "w");
+ fwrite($fp, $_POST['v_stats_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-web-domain-stats-user ".$user." ".$v_domain." ".$v_stats_user." ".$v_stats_password, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_stats_password);
+ $v_stats_password = escapeshellarg($_POST['v_stats_password']);
+ }
+
+ // Restart DNS server
+ if (($_POST['v_dns'] == 'on') && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-restart-dns", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Restart web server
+ if (empty($_SESSION['error_msg'])) {
+ exec (VESTA_CMD."v-restart-web", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Restart proxy server
+ if ((!empty($_SESSION['PROXY_SYSTEM'])) && ($_POST['v_proxy'] == 'on') && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-restart-proxy", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Add FTP
+ if ((!empty($_POST['v_ftp'])) && (empty($_SESSION['error_msg']))) {
+ $v_ftp_users_updated = array();
+ foreach ($_POST['v_ftp_user'] as $i => $v_ftp_user_data) {
+ if ($v_ftp_user_data['is_new'] == 1) {
+ if ((!empty($v_ftp_user_data['v_ftp_email'])) && (!filter_var($v_ftp_user_data['v_ftp_email'], FILTER_VALIDATE_EMAIL))) $_SESSION['error_msg'] = __('Please enter valid email address.');
+ if (empty($v_ftp_user_data['v_ftp_user'])) $errors[] = 'ftp user';
+ if (empty($v_ftp_user_data['v_ftp_password'])) $errors[] = 'ftp user password';
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Validate email
+ if ((!empty($v_ftp_user_data['v_ftp_email'])) && (!filter_var($v_ftp_user_data['v_ftp_email'], FILTER_VALIDATE_EMAIL))) {
+ $_SESSION['error_msg'] = __('Please enter valid email address.');
+ }
+
+ // Check ftp password length
+ if ((!empty($v_ftp_user_data['v_ftp']))) {
+ if (!empty($v_ftp_user_data['v_ftp_user'])) {
+ $pw_len = strlen($v_ftp_user_data['v_ftp_password']);
+ if ($pw_len < 6 ) $_SESSION['error_msg'] = __('Password is too short.',$error_msg);
+ }
+ }
+
+ $v_ftp_user_data['v_ftp_user'] = preg_replace("/^".$user."_/i", "", $v_ftp_user_data['v_ftp_user']);
+ $v_ftp_username = $v_ftp_user_data['v_ftp_user'];
+ $v_ftp_username_full = $user . '_' . $v_ftp_user_data['v_ftp_user'];
+ $v_ftp_user = escapeshellarg($v_ftp_user_data['v_ftp_user']);
+ if ($domain_added) {
+ $v_ftp_path = escapeshellarg(trim($v_ftp_user_data['v_ftp_path']));
+ $v_ftp_password = tempnam("/tmp","vst");
+ $fp = fopen($v_ftp_password, "w");
+ fwrite($fp, $v_ftp_user_data['v_ftp_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-web-domain-ftp ".$user." ".$v_domain." ".$v_ftp_user." ".$v_ftp_password . " " . $v_ftp_path, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_ftp_password);
+ if ((!empty($v_ftp_user_data['v_ftp_email'])) && (empty($_SESSION['error_msg']))) {
+ $to = $v_ftp_user_data['v_ftp_email'];
+ $subject = __("FTP login credentials");
+ $from = __('MAIL_FROM',$_POST['v_domain']);
+ $mailtext = __('FTP_ACCOUNT_READY',$_POST['v_domain'],$user,$v_ftp_user_data['v_ftp_user'],$v_ftp_user_data['v_ftp_password']);
+ send_email($to, $subject, $mailtext, $from);
+ unset($v_ftp_email);
+ }
+ } else {
+ $return_var = -1;
+ }
+
+ if ($return_var == 0) {
+ $v_ftp_password = "••••••••";
+ $v_ftp_user_data['is_new'] = 0;
+ } else {
+ $v_ftp_user_data['is_new'] = 1;
+ }
+
+ $v_ftp_username = preg_replace("/^".$user."_/", "", $v_ftp_user_data['v_ftp_user']);
+ $v_ftp_users_updated[] = array(
+ 'is_new' => $v_ftp_user_data['is_new'],
+ 'v_ftp_user' => $return_var == 0 ? $v_ftp_username_full : $v_ftp_username,
+ 'v_ftp_password' => $v_ftp_password,
+ 'v_ftp_path' => $v_ftp_user_data['v_ftp_path'],
+ 'v_ftp_email' => $v_ftp_user_data['v_ftp_email'],
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+ continue;
+ }
+ }
+
+ if (!empty($_SESSION['error_msg']) && $domain_added) {
+ $_SESSION['ok_msg'] = __('WEB_DOMAIN_CREATED_OK',htmlentities($_POST[v_domain]),htmlentities($_POST[v_domain]));
+ $_SESSION['flash_error_msg'] = $_SESSION['error_msg'];
+ $url = '/edit/web/?domain='.strtolower(preg_replace("/^www\./i", "", $_POST['v_domain']));
+ header('Location: ' . $url);
+ exit;
+ }
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('WEB_DOMAIN_CREATED_OK',htmlentities($_POST['v_domain']),htmlentities($_POST['v_domain']));
+ unset($v_domain);
+ unset($v_aliases);
+ unset($v_ssl);
+ unset($v_ssl_crt);
+ unset($v_ssl_key);
+ unset($v_ssl_ca);
+ unset($v_stats_user);
+ unset($v_stats_password);
+ unset($v_ftp);
+ }
+}
+
+// Define user variables
+$v_ftp_user_prepath = $panel[$user]['HOME'] . "/web";
+$v_ftp_email = $panel[$user]['CONTACT'];
+
+// List IP addresses
+exec (VESTA_CMD."v-list-user-ips ".$user." json", $output, $return_var);
+$ips = json_decode(implode('', $output), true);
+unset($output);
+
+// List web stat engines
+exec (VESTA_CMD."v-list-web-stats json", $output, $return_var);
+$stats = json_decode(implode('', $output), true);
+unset($output);
+
+$result = array(
+ 'prefix' => __('Prefix will be automaticaly added to username',$user."_"),
+ 'ftp_pre_path' => '',
+ 'ftp_email' => $v_ftp_email,
+ 'stats' => $stats,
+ 'proxy_ext' => 'jpeg, jpg, png, gif, bmp, ico, svg, tif, tiff, css, js, htm, html, ttf, otf, webp, woff, txt, csv, rtf, doc, docx, xls, xlsx, ppt, pptx, odf, odp, ods, odt, pdf, psd, ai, eot, eps, ps, zip, tar, tgz, gz, rar, bz2, 7z, aac, m4a, mp3, mp4, ogg, wav, wma, 3gp, avi, flv, m4v, mkv, mov, mp4, mpeg, mpg, wmv, exe, iso, dmg, swf',
+ 'ips' => $ips,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/bulk/backup/exclusions/index.php b/web/api/v1/bulk/backup/exclusions/index.php
new file mode 100644
index 000000000..ccef8de5f
--- /dev/null
+++ b/web/api/v1/bulk/backup/exclusions/index.php
@@ -0,0 +1,31 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/backup/index.php b/web/api/v1/bulk/backup/index.php
new file mode 100644
index 000000000..aa7f1f6c5
--- /dev/null
+++ b/web/api/v1/bulk/backup/index.php
@@ -0,0 +1,35 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/cron/index.php b/web/api/v1/bulk/cron/index.php
new file mode 100644
index 000000000..5fd80765d
--- /dev/null
+++ b/web/api/v1/bulk/cron/index.php
@@ -0,0 +1,77 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/db/index.php b/web/api/v1/bulk/db/index.php
new file mode 100644
index 000000000..a0fa65fa0
--- /dev/null
+++ b/web/api/v1/bulk/db/index.php
@@ -0,0 +1,48 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/dns/index.php b/web/api/v1/bulk/dns/index.php
new file mode 100644
index 000000000..4340e9222
--- /dev/null
+++ b/web/api/v1/bulk/dns/index.php
@@ -0,0 +1,86 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/firewall/banlist/index.php b/web/api/v1/bulk/firewall/banlist/index.php
new file mode 100644
index 000000000..51e2f1ad5
--- /dev/null
+++ b/web/api/v1/bulk/firewall/banlist/index.php
@@ -0,0 +1,44 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/firewall/index.php b/web/api/v1/bulk/firewall/index.php
new file mode 100644
index 000000000..99acda51f
--- /dev/null
+++ b/web/api/v1/bulk/firewall/index.php
@@ -0,0 +1,49 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/ip/index.php b/web/api/v1/bulk/ip/index.php
new file mode 100644
index 000000000..3f252256b
--- /dev/null
+++ b/web/api/v1/bulk/ip/index.php
@@ -0,0 +1,43 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/mail/index.php b/web/api/v1/bulk/mail/index.php
new file mode 100644
index 000000000..590422b71
--- /dev/null
+++ b/web/api/v1/bulk/mail/index.php
@@ -0,0 +1,82 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/package/index.php b/web/api/v1/bulk/package/index.php
new file mode 100644
index 000000000..69f402d1f
--- /dev/null
+++ b/web/api/v1/bulk/package/index.php
@@ -0,0 +1,42 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/restore/index.php b/web/api/v1/bulk/restore/index.php
new file mode 100644
index 000000000..453f2e87d
--- /dev/null
+++ b/web/api/v1/bulk/restore/index.php
@@ -0,0 +1,56 @@
+', $output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['error_msg'] = __('Error: vesta did not return any output.');
+ }
+ if ($return_var == 4) {
+ $_SESSION['error_msg'] = __('RESTORE_EXISTS');
+ }
+ }
+}
+
+header('Content-Type: application/json');
+$result = array(
+ 'error' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/service/index.php b/web/api/v1/bulk/service/index.php
new file mode 100644
index 000000000..e100482d6
--- /dev/null
+++ b/web/api/v1/bulk/service/index.php
@@ -0,0 +1,49 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/user/index.php b/web/api/v1/bulk/user/index.php
new file mode 100644
index 000000000..213a84f88
--- /dev/null
+++ b/web/api/v1/bulk/user/index.php
@@ -0,0 +1,69 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/vesta/index.php b/web/api/v1/bulk/vesta/index.php
new file mode 100644
index 000000000..63b273384
--- /dev/null
+++ b/web/api/v1/bulk/vesta/index.php
@@ -0,0 +1,38 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/bulk/web/index.php b/web/api/v1/bulk/web/index.php
new file mode 100644
index 000000000..71e906364
--- /dev/null
+++ b/web/api/v1/bulk/web/index.php
@@ -0,0 +1,55 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/backup/exclusion/index.php b/web/api/v1/delete/backup/exclusion/index.php
new file mode 100644
index 000000000..dd2c3e879
--- /dev/null
+++ b/web/api/v1/delete/backup/exclusion/index.php
@@ -0,0 +1,28 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/backup/index.php b/web/api/v1/delete/backup/index.php
new file mode 100644
index 000000000..9d8bc9728
--- /dev/null
+++ b/web/api/v1/delete/backup/index.php
@@ -0,0 +1,34 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/cron/autoupdate/index.php b/web/api/v1/delete/cron/autoupdate/index.php
new file mode 100644
index 000000000..f26de9987
--- /dev/null
+++ b/web/api/v1/delete/cron/autoupdate/index.php
@@ -0,0 +1,28 @@
+ $message,
+ 'error' => $error
+);
+
+print json_encode($result);
diff --git a/web/api/v1/delete/cron/index.php b/web/api/v1/delete/cron/index.php
new file mode 100644
index 000000000..a0d700df2
--- /dev/null
+++ b/web/api/v1/delete/cron/index.php
@@ -0,0 +1,34 @@
+
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/cron/reports/index.php b/web/api/v1/delete/cron/reports/index.php
new file mode 100644
index 000000000..7ab5db428
--- /dev/null
+++ b/web/api/v1/delete/cron/reports/index.php
@@ -0,0 +1,26 @@
+ $message,
+ 'error' => $error,
+);
+
+print json_encode($result);
diff --git a/web/api/v1/delete/db/index.php b/web/api/v1/delete/db/index.php
new file mode 100644
index 000000000..ae5eeada7
--- /dev/null
+++ b/web/api/v1/delete/db/index.php
@@ -0,0 +1,33 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/dns/index.php b/web/api/v1/delete/dns/index.php
new file mode 100644
index 000000000..9078999ad
--- /dev/null
+++ b/web/api/v1/delete/dns/index.php
@@ -0,0 +1,45 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/favorite/index.php b/web/api/v1/delete/favorite/index.php
new file mode 100644
index 000000000..9f471b9bd
--- /dev/null
+++ b/web/api/v1/delete/favorite/index.php
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/web/api/v1/delete/firewall/banlist/index.php b/web/api/v1/delete/firewall/banlist/index.php
new file mode 100644
index 000000000..e58cea61a
--- /dev/null
+++ b/web/api/v1/delete/firewall/banlist/index.php
@@ -0,0 +1,36 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/firewall/index.php b/web/api/v1/delete/firewall/index.php
new file mode 100644
index 000000000..c90fa7461
--- /dev/null
+++ b/web/api/v1/delete/firewall/index.php
@@ -0,0 +1,35 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/ip/index.php b/web/api/v1/delete/ip/index.php
new file mode 100644
index 000000000..6c4ab74a3
--- /dev/null
+++ b/web/api/v1/delete/ip/index.php
@@ -0,0 +1,31 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/mail/index.php b/web/api/v1/delete/mail/index.php
new file mode 100644
index 000000000..2f6f2f82a
--- /dev/null
+++ b/web/api/v1/delete/mail/index.php
@@ -0,0 +1,45 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/notification/index.php b/web/api/v1/delete/notification/index.php
new file mode 100644
index 000000000..5aedaa5d0
--- /dev/null
+++ b/web/api/v1/delete/notification/index.php
@@ -0,0 +1,28 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/user/index.php b/web/api/v1/delete/user/index.php
new file mode 100644
index 000000000..a85982ef4
--- /dev/null
+++ b/web/api/v1/delete/user/index.php
@@ -0,0 +1,31 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/delete/web/index.php b/web/api/v1/delete/web/index.php
new file mode 100644
index 000000000..46a4aea83
--- /dev/null
+++ b/web/api/v1/delete/web/index.php
@@ -0,0 +1,34 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+ );
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/download/backup/index.php b/web/api/v1/download/backup/index.php
new file mode 100644
index 000000000..068467148
--- /dev/null
+++ b/web/api/v1/download/backup/index.php
@@ -0,0 +1,26 @@
+
diff --git a/web/api/v1/edit/backup/exclusions/index.php b/web/api/v1/edit/backup/exclusions/index.php
new file mode 100644
index 000000000..13bd32c97
--- /dev/null
+++ b/web/api/v1/edit/backup/exclusions/index.php
@@ -0,0 +1,143 @@
+ $value) {
+ if (!empty($value)){
+ $v_web .= $key . ":" . $value. "\n";
+ } else {
+ $v_web .= $key . "\n";
+ }
+}
+
+// Parse dns
+foreach ($data['DNS'] as $key => $value) {
+ if (!empty($value)){
+ $v_dns .= $key . ":" . $value. "\n";
+ } else {
+ $v_dns .= $key . "\n";
+ }
+}
+
+// Parse mail
+foreach ($data['MAIL'] as $key => $value) {
+ if (!empty($value)){
+ $v_mail .= $key . ":" . $value. "\n";
+ } else {
+ $v_mail .= $key . "\n";
+ }
+}
+
+// Parse databases
+foreach ($data['DB'] as $key => $value) {
+ if (!empty($value)){
+ $v_db .= $key . ":" . $value. "\n";
+ } else {
+ $v_db .= $key . "\n";
+ }
+}
+
+// Parse user directories
+foreach ($data['USER'] as $key => $value) {
+ if (!empty($value)){
+ $v_userdir .= $key . ":" . $value. "\n";
+ } else {
+ $v_userdir .= $key . "\n";
+ }
+}
+
+// Check POST request
+if (!empty($_POST['save'])) {
+
+ // Check token
+ if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+ exit();
+ }
+
+ $v_web = $_POST['v_web'];
+ $v_web_tmp = str_replace("\r\n", ",", $_POST['v_web']);
+ $v_web_tmp = rtrim($v_web_tmp, ",");
+ $v_web_tmp = "WEB=" . escapeshellarg($v_web_tmp);
+
+ $v_dns = $_POST['v_dns'];
+ $v_dns_tmp = str_replace("\r\n", ",", $_POST['v_dns']);
+ $v_dns_tmp = rtrim($v_dns_tmp, ",");
+ $v_dns_tmp = "DNS=" . escapeshellarg($v_dns_tmp);
+
+ $v_mail = $_POST['v_mail'];
+ $v_mail_tmp = str_replace("\r\n", ",", $_POST['v_mail']);
+ $v_mail_tmp = rtrim($v_mail_tmp, ",");
+ $v_mail_tmp = "MAIL=" . escapeshellarg($v_mail_tmp);
+
+ $v_db = $_POST['v_db'];
+ $v_db_tmp = str_replace("\r\n", ",", $_POST['v_db']);
+ $v_db_tmp = rtrim($v_db_tmp, ",");
+ $v_db_tmp = "DB=" . escapeshellarg($v_db_tmp);
+
+ $v_cron = $_POST['v_cron'];
+ $v_cron_tmp = str_replace("\r\n", ",", $_POST['v_cron']);
+ $v_cron_tmp = rtrim($v_cron_tmp, ",");
+ $v_cron_tmp = "CRON=" . escapeshellarg($v_cron_tmp);
+
+ $v_userdir = $_POST['v_userdir'];
+ $v_userdir_tmp = str_replace("\r\n", ",", $_POST['v_userdir']);
+ $v_userdir_tmp = rtrim($v_userdir_tmp, ",");
+ $v_userdir_tmp = "USER=" . escapeshellarg($v_userdir_tmp);
+
+ // Create temporary exeption list on a filesystem
+ exec ('mktemp', $mktemp_output, $return_var);
+ $tmp = $mktemp_output[0];
+ $fp = fopen($tmp, 'w');
+ fwrite($fp, $v_web_tmp . "\n" . $v_dns_tmp . "\n" . $v_mail_tmp . "\n" . $v_db_tmp . "\n" . $v_userdir_tmp . "\n");
+ fclose($fp);
+ unset($mktemp_output);
+
+ // Save changes
+ exec (VESTA_CMD."v-update-user-backup-exclusions ".$user." ".$tmp, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ // Set success message
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __("Changes has been saved.");
+ }
+}
+
+
+// Render page
+// render_page($user, $TAB, 'edit_backup_exclusions');
+
+$result = array(
+ 'web' => $v_web,
+ 'dns' => $v_dns,
+ 'mail' => $v_mail,
+ 'db' => $v_db,
+ 'userdir' => $v_userdir,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/cron/index.php b/web/api/v1/edit/cron/index.php
new file mode 100644
index 000000000..697c04922
--- /dev/null
+++ b/web/api/v1/edit/cron/index.php
@@ -0,0 +1,102 @@
+ $user,
+ 'job' => $_GET['job'],
+ 'min' => $data[$v_job]['MIN'],
+ 'hour' => $data[$v_job]['HOUR'],
+ 'day' => $data[$v_job]['DAY'],
+ 'month' => $data[$v_job]['MONTH'],
+ 'wday' => $data[$v_job]['WDAY'],
+ 'cmd' => $data[$v_job]['CMD'],
+ 'date' => $data[$v_job]['DATE'],
+ 'time' => $data[$v_job]['TIME'],
+ 'suspended' => $data[$v_job]['SUSPENDED'],
+ 'status' => $v_status,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/db/index.php b/web/api/v1/edit/db/index.php
new file mode 100644
index 000000000..7aaced147
--- /dev/null
+++ b/web/api/v1/edit/db/index.php
@@ -0,0 +1,109 @@
+ $user,
+ 'database' => $_GET['database'],
+ 'dbuser' => $data[$_GET['database']]['DBUSER'],
+ 'password' => $_POST['v_password'],
+ 'host' => $data[$_GET['database']]['HOST'],
+ 'type' => $data[$_GET['database']]['TYPE'],
+ 'charset' => $data[$_GET['database']]['CHARSET'],
+ 'date' => $data[$_GET['database']]['DATE'],
+ 'time' => $data[$_GET['database']]['TIME'],
+ 'suspended' => $data[$_GET['database']]['SUSPENDED'],
+ 'status' => $v_status,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/dns/index.php b/web/api/v1/edit/dns/index.php
new file mode 100644
index 000000000..74aae592c
--- /dev/null
+++ b/web/api/v1/edit/dns/index.php
@@ -0,0 +1,225 @@
+ $user,
+ 'domain' => $v_domain,
+ 'domain' => $_GET['domain'],
+ 'ip' => $data[$v_domain]['IP'],
+ 'record_id' => $v_record_id,
+ 'rec' => $v_rec,
+ 'type' => $v_type,
+ 'val' => $v_val,
+ 'priority' => $v_priority,
+ 'template' => $data[$v_domain]['TPL'],
+ 'ttl' => $data[$v_domain]['TTL'],
+ 'exp' => $data[$v_domain]['EXP'],
+ 'soa' => $data[$v_domain]['SOA'],
+ 'date' => $v_date,
+ 'time' => $v_time,
+ 'suspended' => $v_suspended,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg'],
+ 'status' => $v_status,
+ 'dns_system' => $_SESSION['DNS_SYSTEM'],
+ 'YYYY-MM-DD' => __('YYYY-MM-DD'),
+ 'templates' => $templates
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/file/index.php b/web/api/v1/edit/file/index.php
new file mode 100644
index 000000000..9f08e497d
--- /dev/null
+++ b/web/api/v1/edit/file/index.php
@@ -0,0 +1,51 @@
+ $error,
+ 'content' => $content
+);
+
+echo json_encode($result);
diff --git a/web/api/v1/edit/firewall/index.php b/web/api/v1/edit/firewall/index.php
new file mode 100644
index 000000000..19ac2bbed
--- /dev/null
+++ b/web/api/v1/edit/firewall/index.php
@@ -0,0 +1,101 @@
+ $_GET['rule'],
+ 'action' => $data[$v_rule]['ACTION'],
+ 'protocol' => $data[$v_rule]['PROTOCOL'],
+ 'port' => $data[$v_rule]['PORT'],
+ 'ip' => $data[$v_rule]['IP'],
+ 'comment' => $data[$v_rule]['COMMENT'],
+ 'date' => $data[$v_rule]['DATE'],
+ 'time' => $data[$v_rule]['TIME'],
+ 'suspended' => $data[$v_rule]['SUSPENDED'],
+ 'status' => $v_status,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/ip/index.php b/web/api/v1/edit/ip/index.php
new file mode 100644
index 000000000..006e89767
--- /dev/null
+++ b/web/api/v1/edit/ip/index.php
@@ -0,0 +1,124 @@
+ $user,
+ 'ip' => $_GET['ip'],
+ 'netmask' => $data[$v_ip]['NETMASK'],
+ 'interface' => $data[$v_ip]['INTERFACE'],
+ 'name' => $data[$v_ip]['NAME'],
+ 'nat' => $data[$v_ip]['NAT'],
+ 'ipstatus' => $data[$v_ip]['STATUS'],
+ 'dedicated' => $v_dedicated,
+ 'owner' => $data[$v_ip]['OWNER'],
+ 'date' => $data[$v_ip]['DATE'],
+ 'time' => $data[$v_ip]['TIME'],
+ 'suspended' => $data[$v_ip]['SUSPENDED'],
+ 'status' => 'active',
+ 'users' => $users,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/mail/index.php b/web/api/v1/edit/mail/index.php
new file mode 100644
index 000000000..a886b203f
--- /dev/null
+++ b/web/api/v1/edit/mail/index.php
@@ -0,0 +1,372 @@
+ $data[$v_domain]['ANTISPAM'],
+ 'antivirus' => $data[$v_domain]['ANTIVIRUS'],
+ 'dkim' => $data[$v_domain]['DKIM'],
+ 'catchall' => $data[$v_domain]['CATCHALL'],
+ 'status' => $v_status,
+ 'username' => $user,
+ 'domain' => $v_domain,
+ 'account' => $v_account,
+ 'password' => $v_password,
+ 'aliases' => $v_aliases,
+ 'valiases' => $valiases,
+ 'fwd' => $v_fwd,
+ 'vfwd' => $vfwd,
+ 'fwd_only' => $v_fwd_only,
+ 'quota' => $v_quota,
+ 'autoreply' => $v_autoreply,
+ 'quota' => $v_quota,
+ 'date' => $v_date,
+ 'time' => $v_time,
+ 'send_email' => $v_send_email,
+ 'credentials' => $v_credentials,
+ 'webmail' => $webmail,
+ 'hostname' => $hostname,
+ 'autoreply_message' => $v_autoreply_message,
+ 'v_suspended' => $v_suspended,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/package/index.php b/web/api/v1/edit/package/index.php
new file mode 100644
index 000000000..d890fa511
--- /dev/null
+++ b/web/api/v1/edit/package/index.php
@@ -0,0 +1,272 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Protect input
+ $v_package = escapeshellarg($_POST['v_package']);
+ $v_web_template = escapeshellarg($_POST['v_web_template']);
+ if (!empty($_SESSION['WEB_BACKEND'])) {
+ $v_backend_template = escapeshellarg($_POST['v_backend_template']);
+ }
+ if (!empty($_SESSION['PROXY_SYSTEM'])) {
+ $v_proxy_template = escapeshellarg($_POST['v_proxy_template']);
+ }
+ $v_dns_template = escapeshellarg($_POST['v_dns_template']);
+ $v_shell = escapeshellarg($_POST['v_shell']);
+ $v_web_domains = escapeshellarg($_POST['v_web_domains']);
+ $v_web_aliases = escapeshellarg($_POST['v_web_aliases']);
+ $v_dns_domains = escapeshellarg($_POST['v_dns_domains']);
+ $v_dns_records = escapeshellarg($_POST['v_dns_records']);
+ $v_mail_domains = escapeshellarg($_POST['v_mail_domains']);
+ $v_mail_accounts = escapeshellarg($_POST['v_mail_accounts']);
+ $v_databases = escapeshellarg($_POST['v_databases']);
+ $v_cron_jobs = escapeshellarg($_POST['v_cron_jobs']);
+ $v_backups = escapeshellarg($_POST['v_backups']);
+ $v_disk_quota = escapeshellarg($_POST['v_disk_quota']);
+ $v_bandwidth = escapeshellarg($_POST['v_bandwidth']);
+ $v_ns1 = trim($_POST['v_ns1'], '.');
+ $v_ns2 = trim($_POST['v_ns2'], '.');
+ $v_ns3 = trim($_POST['v_ns3'], '.');
+ $v_ns4 = trim($_POST['v_ns4'], '.');
+ $v_ns5 = trim($_POST['v_ns5'], '.');
+ $v_ns6 = trim($_POST['v_ns6'], '.');
+ $v_ns7 = trim($_POST['v_ns7'], '.');
+ $v_ns8 = trim($_POST['v_ns8'], '.');
+ $v_ns = $v_ns1.",".$v_ns2;
+ if (!empty($v_ns3)) $v_ns .= ",".$v_ns3;
+ if (!empty($v_ns4)) $v_ns .= ",".$v_ns4;
+ if (!empty($v_ns5)) $v_ns .= ",".$v_ns5;
+ if (!empty($v_ns6)) $v_ns .= ",".$v_ns6;
+ if (!empty($v_ns7)) $v_ns .= ",".$v_ns7;
+ if (!empty($v_ns8)) $v_ns .= ",".$v_ns8;
+ $v_ns = escapeshellarg($v_ns);
+ $v_time = escapeshellarg(date('H:i:s'));
+ $v_date = escapeshellarg(date('Y-m-d'));
+
+ // Create temprorary directory
+ exec ('mktemp -d', $output, $return_var);
+ $tmpdir = $output[0];
+ unset($output);
+
+ // Save package file on a fs
+ $pkg = "WEB_TEMPLATE=".$v_web_template."\n";
+ $pkg .= "BACKEND_TEMPLATE=".$v_backend_template."\n";
+ $pkg .= "PROXY_TEMPLATE=".$v_proxy_template."\n";
+ $pkg .= "DNS_TEMPLATE=".$v_dns_template."\n";
+ $pkg .= "WEB_DOMAINS=".$v_web_domains."\n";
+ $pkg .= "WEB_ALIASES=".$v_web_aliases."\n";
+ $pkg .= "DNS_DOMAINS=".$v_dns_domains."\n";
+ $pkg .= "DNS_RECORDS=".$v_dns_records."\n";
+ $pkg .= "MAIL_DOMAINS=".$v_mail_domains."\n";
+ $pkg .= "MAIL_ACCOUNTS=".$v_mail_accounts."\n";
+ $pkg .= "DATABASES=".$v_databases."\n";
+ $pkg .= "CRON_JOBS=".$v_cron_jobs."\n";
+ $pkg .= "DISK_QUOTA=".$v_disk_quota."\n";
+ $pkg .= "BANDWIDTH=".$v_bandwidth."\n";
+ $pkg .= "NS=".$v_ns."\n";
+ $pkg .= "SHELL=".$v_shell."\n";
+ $pkg .= "BACKUPS=".$v_backups."\n";
+ $pkg .= "TIME=".$v_time."\n";
+ $pkg .= "DATE=".$v_date."\n";
+ $fp = fopen($tmpdir."/".$_POST['v_package'].".pkg", 'w');
+ fwrite($fp, $pkg);
+ fclose($fp);
+
+ // Save changes
+ exec (VESTA_CMD."v-add-user-package ".$tmpdir." ".$v_package." yes", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ // Remove temporary dir
+ exec ('rm -rf '.$tmpdir, $output, $return_var);
+ unset($output);
+
+ // Propogate new package
+ exec (VESTA_CMD."v-update-user-package ".$v_package." json", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ // Set success message
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Changes has been saved.');
+ }
+}
+
+$result = array(
+ 'package' => $_GET['package'],
+ 'web_template' => $data[$v_package]['WEB_TEMPLATE'],
+ 'backend_template' => $data[$v_package]['BACKEND_TEMPLATE'],
+ 'proxy_template' => $data[$v_package]['PROXY_TEMPLATE'],
+ 'dns_template' => $data[$v_package]['DNS_TEMPLATE'],
+ 'web_domains' => $data[$v_package]['WEB_DOMAINS'],
+ 'web_aliases' => $data[$v_package]['WEB_ALIASES'],
+ 'dns_domains' => $data[$v_package]['DNS_DOMAINS'],
+ 'dns_records' => $data[$v_package]['DNS_RECORDS'],
+ 'mail_domains' => $data[$v_package]['MAIL_DOMAINS'],
+ 'mail_accounts' => $data[$v_package]['MAIL_ACCOUNTS'],
+ 'databases' => $data[$v_package]['DATABASES'],
+ 'cron_jobs' => $data[$v_package]['CRON_JOBS'],
+ 'disk_quota' => $data[$v_package]['DISK_QUOTA'],
+ 'bandwidth' => $data[$v_package]['BANDWIDTH'],
+ 'shell' => $data[$v_package]['SHELL'],
+ 'ns' => $data[$v_package]['NS'],
+ 'nameservers' => explode(",", $v_ns),
+ 'ns1' => $nameservers[0],
+ 'ns2' => $nameservers[1],
+ 'ns3' => $nameservers[2],
+ 'ns4' => $nameservers[3],
+ 'ns5' => $nameservers[4],
+ 'ns6' => $nameservers[5],
+ 'ns7' => $nameservers[6],
+ 'ns8' => $nameservers[7],
+ 'backups' => $data[$v_package]['BACKUPS'],
+ 'date' => $data[$v_package]['DATE'],
+ 'time' => $data[$v_package]['TIME'],
+ 'status' => $v_status,
+ 'web_templates' => $web_templates,
+ 'backend_templates' => $backend_templates,
+ 'proxy_templates' => $proxy_templates,
+ 'dns_templates' => $dns_templates,
+ 'shells' => $shells,
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'web_backend' => $_SESSION['WEB_BACKEND'],
+ 'proxy_system' => $_SESSION['PROXY_SYSTEM'],
+ 'dns_system' => $_SESSION['DNS_SYSTEM'],
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/apache2/index.php b/web/api/v1/edit/server/apache2/index.php
new file mode 100644
index 000000000..e23a31f57
--- /dev/null
+++ b/web/api/v1/edit/server/apache2/index.php
@@ -0,0 +1,66 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/bind9/index.php b/web/api/v1/edit/server/bind9/index.php
new file mode 100644
index 000000000..4fff011db
--- /dev/null
+++ b/web/api/v1/edit/server/bind9/index.php
@@ -0,0 +1,82 @@
+ $v_options_path,
+ 'config_path' => $v_config_path,
+ 'service_name' => $v_service_name,
+ 'options' => $v_options,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/clamd/index.php b/web/api/v1/edit/server/clamd/index.php
new file mode 100644
index 000000000..fef8c95b3
--- /dev/null
+++ b/web/api/v1/edit/server/clamd/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/cron/index.php b/web/api/v1/edit/server/cron/index.php
new file mode 100644
index 000000000..02f5a6c64
--- /dev/null
+++ b/web/api/v1/edit/server/cron/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/crond/index.php b/web/api/v1/edit/server/crond/index.php
new file mode 100644
index 000000000..9ef8f54dc
--- /dev/null
+++ b/web/api/v1/edit/server/crond/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/dovecot/index.php b/web/api/v1/edit/server/dovecot/index.php
new file mode 100644
index 000000000..198bd28a2
--- /dev/null
+++ b/web/api/v1/edit/server/dovecot/index.php
@@ -0,0 +1,206 @@
+ $v_config_path,
+ 'config_path1' => $v_config_path1,
+ 'config_path2' => $v_config_path2,
+ 'config_path3' => $v_config_path3,
+ 'config_path4' => $v_config_path4,
+ 'config_path5' => $v_config_path5,
+ 'config_path6' => $v_config_path6,
+ 'config_path7' => $v_config_path7,
+ 'config_path8' => $v_config_path8,
+ 'service_name' => $v_service_name,
+ 'config' => $config,
+ 'config1' => $config1,
+ 'config2' => $config2,
+ 'config3' => $config3,
+ 'config4' => $config4,
+ 'config5' => $config5,
+ 'config6' => $config6,
+ 'config7' => $config7,
+ 'config8' => $config8,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/exim/index.php b/web/api/v1/edit/server/exim/index.php
new file mode 100644
index 000000000..aa267feac
--- /dev/null
+++ b/web/api/v1/edit/server/exim/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/exim4/index.php b/web/api/v1/edit/server/exim4/index.php
new file mode 100644
index 000000000..84b2bd50e
--- /dev/null
+++ b/web/api/v1/edit/server/exim4/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/fail2ban/index.php b/web/api/v1/edit/server/fail2ban/index.php
new file mode 100644
index 000000000..fa7cf38c4
--- /dev/null
+++ b/web/api/v1/edit/server/fail2ban/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/httpd/index.php b/web/api/v1/edit/server/httpd/index.php
new file mode 100644
index 000000000..8af419d2d
--- /dev/null
+++ b/web/api/v1/edit/server/httpd/index.php
@@ -0,0 +1,64 @@
+ '/etc/httpd/conf/httpd.conf',
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/index.php b/web/api/v1/edit/server/index.php
new file mode 100644
index 000000000..77a443255
--- /dev/null
+++ b/web/api/v1/edit/server/index.php
@@ -0,0 +1,713 @@
+ $value) {
+ $v_dns_cluster = 'yes';
+}
+
+// List Database hosts
+exec (VESTA_CMD."v-list-database-hosts json", $output, $return_var);
+$db_hosts = json_decode(implode('', $output), true);
+unset($output);
+$v_mysql_hosts = array_values(array_filter($db_hosts, function($host){return $host['TYPE'] === 'mysql';}));
+$v_mysql = count($v_mysql_hosts) ? 'yes' : 'no';
+$v_pgsql_hosts = array_values(array_filter($db_hosts, function($host){return $host['TYPE'] === 'pgsql';}));
+$v_pgsql = count($v_pgsql_hosts) ? 'yes' : 'no';
+unset($db_hosts);
+
+// List backup settings
+$v_backup_remote_adv="yes";
+$v_backup_dir = "/backup";
+if (!empty($_SESSION['BACKUP'])) $v_backup_dir = $_SESSION['BACKUP'];
+$v_backup_gzip = '5';
+if (!empty($_SESSION['BACKUP_GZIP'])) $v_backup_gzip = $_SESSION['BACKUP_GZIP'];
+$backup_types = explode(",",$_SESSION['BACKUP_SYSTEM']);
+foreach ($backup_types as $backup_type) {
+ if ($backup_type == 'local') {
+ $v_backup = 'yes';
+ } else {
+ exec (VESTA_CMD."v-list-backup-host ".$backup_type. " json", $output, $return_var);
+ $v_remote_backup = json_decode(implode('', $output), true);
+ unset($output);
+ $v_backup_host = $v_remote_backup[$backup_type]['HOST'];
+ $v_backup_type = $v_remote_backup[$backup_type]['TYPE'];
+ $v_backup_username = $v_remote_backup[$backup_type]['USERNAME'];
+ $v_backup_password = "";
+ $v_backup_port = $v_remote_backup[$backup_type]['PORT'];
+ $v_backup_bpath = $v_remote_backup[$backup_type]['BPATH'];
+ }
+}
+
+// List ssl web domains
+exec (VESTA_CMD."v-search-ssl-certificates json", $output, $return_var);
+$v_ssl_domains = json_decode(implode('', $output), true);
+//$v_vesta_certificate
+unset($output);
+
+// List ssl certificate info
+exec (VESTA_CMD."v-list-sys-vesta-ssl json", $output, $return_var);
+$v_sys_ssl_str = json_decode(implode('', $output), true);
+unset($output);
+$v_sys_ssl_crt = $v_sys_ssl_str['VESTA']['CRT'];
+$v_sys_ssl_key = $v_sys_ssl_str['VESTA']['KEY'];
+$v_sys_ssl_ca = $v_sys_ssl_str['VESTA']['CA'];
+$v_sys_ssl_subject = $v_sys_ssl_str['VESTA']['SUBJECT'];
+$v_sys_ssl_aliases = $v_sys_ssl_str['VESTA']['ALIASES'];
+$v_sys_ssl_not_before = $v_sys_ssl_str['VESTA']['NOT_BEFORE'];
+$v_sys_ssl_not_after = $v_sys_ssl_str['VESTA']['NOT_AFTER'];
+$v_sys_ssl_signature = $v_sys_ssl_str['VESTA']['SIGNATURE'];
+$v_sys_ssl_pub_key = $v_sys_ssl_str['VESTA']['PUB_KEY'];
+$v_sys_ssl_issuer = $v_sys_ssl_str['VESTA']['ISSUER'];
+
+// List mail ssl certificate info
+if (!empty($_SESSION['VESTA_CERTIFICATE'])); {
+ exec (VESTA_CMD."v-list-sys-mail-ssl json", $output, $return_var);
+ $v_mail_ssl_str = json_decode(implode('', $output), true);
+ unset($output);
+ $v_mail_ssl_crt = $v_mail_ssl_str['MAIL']['CRT'];
+ $v_mail_ssl_key = $v_mail_ssl_str['MAIL']['KEY'];
+ $v_mail_ssl_ca = $v_mail_ssl_str['MAIL']['CA'];
+ $v_mail_ssl_subject = $v_mail_ssl_str['MAIL']['SUBJECT'];
+ $v_mail_ssl_aliases = $v_mail_ssl_str['MAIL']['ALIASES'];
+ $v_mail_ssl_not_before = $v_mail_ssl_str['MAIL']['NOT_BEFORE'];
+ $v_mail_ssl_not_after = $v_mail_ssl_str['MAIL']['NOT_AFTER'];
+ $v_mail_ssl_signature = $v_mail_ssl_str['MAIL']['SIGNATURE'];
+ $v_mail_ssl_pub_key = $v_mail_ssl_str['MAIL']['PUB_KEY'];
+ $v_mail_ssl_issuer = $v_mail_ssl_str['MAIL']['ISSUER'];
+}
+
+// Check POST request
+if (!empty($_POST['save'])) {
+
+ // Check token
+ if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+ exit();
+ }
+
+ // Change hostname
+ if ((!empty($_POST['v_hostname'])) && ($v_hostname != $_POST['v_hostname'])) {
+ exec (VESTA_CMD."v-change-sys-hostname ".escapeshellarg($_POST['v_hostname']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_hostname = $_POST['v_hostname'];
+ }
+
+ // Change timezone
+ if (empty($_SESSION['error_msg'])) {
+ if (!empty($_POST['v_timezone'])) {
+ $v_tz = $_POST['v_timezone'];
+ if ($v_tz == 'UTC' ) $v_tz = 'Etc/UTC';
+ if ($v_tz == 'HAST' ) $v_tz = 'Pacific/Honolulu';
+ if ($v_tz == 'HADT' ) $v_tz = 'US/Aleutian';
+ if ($v_tz == 'AKST' ) $v_tz = 'Etc/GMT+9';
+ if ($v_tz == 'AKDT' ) $v_tz = 'America/Anchorage';
+ if ($v_tz == 'PST' ) $v_tz = 'America/Dawson_Creek';
+ if ($v_tz == 'PDT' ) $v_tz = 'PST8PDT';
+ if ($v_tz == 'MDT' ) $v_tz = 'MST7MDT';
+ if ($v_tz == 'CST' ) $v_tz = 'Canada/Saskatchewan';
+ if ($v_tz == 'CDT' ) $v_tz = 'CST6CDT';
+ if ($v_tz == 'EDT' ) $v_tz = 'EST5EDT';
+ if ($v_tz == 'AST' ) $v_tz = 'America/Puerto_Rico';
+ if ($v_tz == 'ADT' ) $v_tz = 'America/Halifax';
+
+ if ($v_timezone != $v_tz) {
+ exec (VESTA_CMD."v-change-sys-timezone ".escapeshellarg($v_tz), $output, $return_var);
+ check_return_code($return_var,$output);
+ $v_timezone = $v_tz;
+ unset($output);
+ }
+ }
+ }
+
+ // Change default language
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_language'])) && ($_SESSION['LANGUAGE'] != $_POST['v_language'])) {
+ exec (VESTA_CMD."v-change-sys-language ".escapeshellarg($_POST['v_language']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $_SESSION['LANGUAGE'] = $_POST['v_language'];
+ }
+ }
+
+ // Set disk_quota support
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_quota'])) && ($_SESSION['DISK_QUOTA'] != $_POST['v_quota'])) {
+ if($_POST['v_quota'] == 'yes') {
+ exec (VESTA_CMD."v-add-sys-quota", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $_SESSION['DISK_QUOTA'] = 'yes';
+ } else {
+ exec (VESTA_CMD."v-delete-sys-quota", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $_SESSION['DISK_QUOTA'] = 'no';
+ }
+ }
+ }
+
+ // Set firewall support
+ if (empty($_SESSION['error_msg'])) {
+ if ($_SESSION['FIREWALL_SYSTEM'] == 'iptables') $v_firewall = 'yes';
+ if ($_SESSION['FIREWALL_SYSTEM'] != 'iptables') $v_firewall = 'no';
+ if ((!empty($_POST['v_firewall'])) && ($v_firewall != $_POST['v_firewall'])) {
+ if($_POST['v_firewall'] == 'yes') {
+ exec (VESTA_CMD."v-add-sys-firewall", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $_SESSION['FIREWALL_SYSTEM'] = 'iptables';
+ } else {
+ exec (VESTA_CMD."v-delete-sys-firewall", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $_SESSION['FIREWALL_SYSTEM'] = '';
+ }
+ }
+ }
+
+ // Update mysql pasword
+ if (empty($_SESSION['error_msg'])) {
+ if (!empty($_POST['v_mysql_password'])) {
+ exec (VESTA_CMD."v-change-database-host-password mysql localhost root ".escapeshellarg($_POST['v_mysql_password']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_db_adv = 'yes';
+ }
+ }
+
+
+ // Delete Mail Domain SSL certificate
+ if ((!isset($_POST['v_mail_ssl_domain_checkbox'])) && (!empty($_SESSION['MAIL_CERTIFICATE'])) && (empty($_SESSION['error_msg']))) {
+ unset($_SESSION['MAIL_CERTIFICATE']);
+ exec (VESTA_CMD."v-delete-sys-mail-ssl", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Updating Mail Domain SSL certificate
+ if ((isset($_POST['v_mail_ssl_domain_checkbox'])) && (isset($_POST['v_mail_ssl_domain'])) && (empty($_SESSION['error_msg']))) {
+ if ((!empty($_POST['v_mail_ssl_domain'])) && ($_POST['v_mail_ssl_domain'] != $_SESSION['MAIL_CERTIFICATE'])) {
+ $v_mail_ssl_str = explode(":", $_POST['v_mail_ssl_domain']);
+ $v_mail_ssl_user = escapeshellarg($v_mail_ssl_str[0]);
+ $v_mail_ssl_domain = escapeshellarg($v_mail_ssl_str[1]);
+ exec (VESTA_CMD."v-add-sys-mail-ssl ".$v_mail_ssl_user." ".$v_mail_ssl_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unset($v_mail_ssl_str);
+
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['MAIL_CERTIFICATE'] = $_POST['v_mail_ssl_domain'];
+
+ // List SSL certificate info
+ exec (VESTA_CMD."v-list-sys-mail-ssl json", $output, $return_var);
+ $v_mail_ssl_str = json_decode(implode('', $output), true);
+ unset($output);
+ $v_mail_ssl_crt = $v_mail_ssl_str['MAIL']['CRT'];
+ $v_mail_ssl_key = $v_mail_ssl_str['MAIL']['KEY'];
+ $v_mail_ssl_ca = $v_mail_ssl_str['MAIL']['CA'];
+ $v_mail_ssl_subject = $v_mail_ssl_str['MAIL']['SUBJECT'];
+ $v_mail_ssl_aliases = $v_mail_ssl_str['MAIL']['ALIASES'];
+ $v_mail_ssl_not_before = $v_mail_ssl_str['MAIL']['NOT_BEFORE'];
+ $v_mail_ssl_not_after = $v_mail_ssl_str['MAIL']['NOT_AFTER'];
+ $v_mail_ssl_signature = $v_mail_ssl_str['MAIL']['SIGNATURE'];
+ $v_mail_ssl_pub_key = $v_mail_ssl_str['MAIL']['PUB_KEY'];
+ $v_mail_ssl_issuer = $v_mail_ssl_str['MAIL']['ISSUER'];
+ }
+ }
+ }
+
+ // Update webmail url
+ if (empty($_SESSION['error_msg'])) {
+ if ($_POST['v_mail_url'] != $_SESSION['MAIL_URL']) {
+ exec (VESTA_CMD."v-change-sys-config-value MAIL_URL ".escapeshellarg($_POST['v_mail_url']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_mail_adv = 'yes';
+ }
+ }
+
+ // Update phpMyAdmin url
+ if (empty($_SESSION['error_msg'])) {
+ if ($_POST['v_mysql_url'] != $_SESSION['DB_PMA_URL']) {
+ exec (VESTA_CMD."v-change-sys-config-value DB_PMA_URL ".escapeshellarg($_POST['v_mysql_url']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_db_adv = 'yes';
+ }
+ }
+
+ // Update phpPgAdmin url
+ if (empty($_SESSION['error_msg'])) {
+ if ($_POST['v_pgsql_url'] != $_SESSION['DB_PGA_URL']) {
+ exec (VESTA_CMD."v-change-sys-config-value DB_PGA_URL ".escapeshellarg($_POST['v_pgsql_url']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_db_adv = 'yes';
+ }
+ }
+
+ // Disable local backup
+ if (empty($_SESSION['error_msg'])) {
+ if (($_POST['v_backup'] == 'no') && ($v_backup == 'yes' )) {
+ exec (VESTA_CMD."v-delete-backup-host local", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup = 'no';
+ $v_backup_adv = 'yes';
+ }
+ }
+
+ // Enable local backups
+ if (empty($_SESSION['error_msg'])) {
+ if (($_POST['v_backup'] == 'yes') && ($v_backup != 'yes' )) {
+ exec (VESTA_CMD."v-add-backup-host local", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup = 'yes';
+ $v_backup_adv = 'yes';
+ }
+ }
+
+ // Change backup gzip level
+ if (empty($_SESSION['error_msg'])) {
+ if ($_POST['v_backup_gzip'] != $v_backup_gzip ) {
+ exec (VESTA_CMD."v-change-sys-config-value BACKUP_GZIP ".escapeshellarg($_POST['v_backup_gzip']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_gzip = $_POST['v_backup_gzip'];
+ $v_backup_adv = 'yes';
+ }
+ }
+
+ // Change backup path
+ if (empty($_SESSION['error_msg'])) {
+ if ($_POST['v_backup_dir'] != $v_backup_dir ) {
+ exec (VESTA_CMD."v-change-sys-config-value BACKUP ".escapeshellarg($_POST['v_backup_dir']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_dir = $_POST['v_backup_dir'];
+ $v_backup_adv = 'yes';
+ }
+ }
+
+ // Add remote backup host
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_backup_host'])) && (empty($v_backup_host))) {
+ $v_backup_host = escapeshellarg($_POST['v_backup_host']);
+ $v_backup_type = escapeshellarg($_POST['v_backup_type']);
+ $v_backup_username = escapeshellarg($_POST['v_backup_username']);
+ $v_backup_password = escapeshellarg($_POST['v_backup_password']);
+ $v_backup_bpath = escapeshellarg($_POST['v_backup_bpath']);
+ exec (VESTA_CMD."v-add-backup-host ".$v_backup_type." ".$v_backup_host ." ".$v_backup_username." ".$v_backup_password." ".$v_backup_bpath, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_host = $_POST['v_backup_host'];
+ if (empty($_SESSION['error_msg'])) $v_backup_type = $_POST['v_backup_type'];
+ if (empty($_SESSION['error_msg'])) $v_backup_username = $_POST['v_backup_username'];
+ if (empty($_SESSION['error_msg'])) $v_backup_password = $_POST['v_backup_password'];
+ if (empty($_SESSION['error_msg'])) $v_backup_bpath = $_POST['v_backup_bpath'];
+ $v_backup_new = 'yes';
+ $v_backup_adv = 'yes';
+ $v_backup_remote_adv = 'yes';
+ }
+ }
+
+ // Change remote backup host type
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_backup_host'])) && ($_POST['v_backup_type'] != $v_backup_type)) {
+ exec (VESTA_CMD."v-delete-backup-host ". $v_backup_type, $output, $return_var);
+ unset($output);
+
+ $v_backup_host = escapeshellarg($_POST['v_backup_host']);
+ $v_backup_type = escapeshellarg($_POST['v_backup_type']);
+ $v_backup_username = escapeshellarg($_POST['v_backup_username']);
+ $v_backup_password = escapeshellarg($_POST['v_backup_password']);
+ $v_backup_bpath = escapeshellarg($_POST['v_backup_bpath']);
+ exec (VESTA_CMD."v-add-backup-host ".$v_backup_type." ".$v_backup_host." ".$v_backup_username." ".$v_backup_password." ".$v_backup_bpath, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_host = $_POST['v_backup_host'];
+ if (empty($_SESSION['error_msg'])) $v_backup_type = $_POST['v_backup_type'];
+ if (empty($_SESSION['error_msg'])) $v_backup_username = $_POST['v_backup_username'];
+ if (empty($_SESSION['error_msg'])) $v_backup_password = $_POST['v_backup_password'];
+ if (empty($_SESSION['error_msg'])) $v_backup_bpath = $_POST['v_backup_bpath'];
+ $v_backup_adv = 'yes';
+ $v_backup_remote_adv = 'yes';
+ }
+ }
+
+ // Change remote backup host
+ if (empty($_SESSION['error_msg'])) {
+ if ((!empty($_POST['v_backup_host'])) && ($_POST['v_backup_type'] == $v_backup_type) && (!isset($v_backup_new))) {
+ if (($_POST['v_backup_host'] != $v_backup_host) || ($_POST['v_backup_username'] != $v_backup_username) || ($_POST['v_backup_password'] != $v_backup_password) || ($_POST['v_backup_bpath'] != $v_backup_bpath)){
+ $v_backup_host = escapeshellarg($_POST['v_backup_host']);
+ $v_backup_type = escapeshellarg($_POST['v_backup_type']);
+ $v_backup_username = escapeshellarg($_POST['v_backup_username']);
+ $v_backup_password = escapeshellarg($_POST['v_backup_password']);
+ $v_backup_bpath = escapeshellarg($_POST['v_backup_bpath']);
+ exec (VESTA_CMD."v-add-backup-host ".$v_backup_type." ".$v_backup_host." ".$v_backup_username." ".$v_backup_password." ".$v_backup_bpath, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_host = $_POST['v_backup_host'];
+ if (empty($_SESSION['error_msg'])) $v_backup_type = $_POST['v_backup_type'];
+ if (empty($_SESSION['error_msg'])) $v_backup_username = $_POST['v_backup_username'];
+ if (empty($_SESSION['error_msg'])) $v_backup_password = $_POST['v_backup_password'];
+ if (empty($_SESSION['error_msg'])) $v_backup_bpath = $_POST['v_backup_bpath'];
+ $v_backup_adv = 'yes';
+ $v_backup_remote_adv = 'yes';
+ }
+ }
+ }
+
+ // Delete remote backup host
+ if (empty($_SESSION['error_msg'])) {
+ if ((empty($_POST['v_backup_host'])) && (!empty($v_backup_host))) {
+ exec (VESTA_CMD."v-delete-backup-host ". $v_backup_type, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) $v_backup_host = '';
+ if (empty($_SESSION['error_msg'])) $v_backup_type = '';
+ if (empty($_SESSION['error_msg'])) $v_backup_username = '';
+ if (empty($_SESSION['error_msg'])) $v_backup_password = '';
+ if (empty($_SESSION['error_msg'])) $v_backup_bpath = '';
+ $v_backup_adv = '';
+ $v_backup_remote_adv = '';
+ }
+ }
+
+
+
+ // Delete WEB Domain SSL certificate
+ if ((!isset($_POST['v_web_ssl_domain_checkbox'])) && (!empty($_SESSION['VESTA_CERTIFICATE'])) && (empty($_SESSION['error_msg']))) {
+ unset($_SESSION['VESTA_CERTIFICATE']);
+ exec (VESTA_CMD."v-delete-sys-vesta-ssl", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Updating WEB Domain SSL certificate
+ if ((isset($_POST['v_web_ssl_domain_checkbox'])) && (isset($_POST['v_web_ssl_domain'])) && (empty($_SESSION['error_msg']))) {
+
+ if ((!empty($_POST['v_web_ssl_domain'])) && ($_POST['v_web_ssl_domain'] != $_SESSION['VESTA_CERTIFICATE'])) {
+ $v_web_ssl_str = explode(":", $_POST['v_web_ssl_domain']);
+ $v_web_ssl_user = escapeshellarg($v_web_ssl_str[0]);
+ $v_web_ssl_domain = escapeshellarg($v_web_ssl_str[1]);
+ exec (VESTA_CMD."v-add-sys-vesta-ssl ".$v_web_ssl_user." ".$v_web_ssl_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['VESTA_CERTIFICATE'] = $_POST['v_web_ssl_domain'];
+
+ // List SSL certificate info
+ exec (VESTA_CMD."v-list-sys-vesta-ssl json", $output, $return_var);
+ $v_sys_ssl_str = json_decode(implode('', $output), true);
+ unset($output);
+ $v_sys_ssl_crt = $v_sys_ssl_str['VESTA']['CRT'];
+ $v_sys_ssl_key = $v_sys_ssl_str['VESTA']['KEY'];
+ $v_sys_ssl_ca = $v_sys_ssl_str['VESTA']['CA'];
+ $v_sys_ssl_subject = $v_sys_ssl_str['VESTA']['SUBJECT'];
+ $v_sys_ssl_aliases = $v_sys_ssl_str['VESTA']['ALIASES'];
+ $v_sys_ssl_not_before = $v_sys_ssl_str['VESTA']['NOT_BEFORE'];
+ $v_sys_ssl_not_after = $v_sys_ssl_str['VESTA']['NOT_AFTER'];
+ $v_sys_ssl_signature = $v_sys_ssl_str['VESTA']['SIGNATURE'];
+ $v_sys_ssl_pub_key = $v_sys_ssl_str['VESTA']['PUB_KEY'];
+ $v_sys_ssl_issuer = $v_sys_ssl_str['VESTA']['ISSUER'];
+ }
+ }
+ }
+
+
+ // Update SSL certificate
+ if ((!empty($_POST['v_sys_ssl_crt'])) && (empty($_POST['v_web_ssl_domain'])) && (empty($_SESSION['error_msg']))) {
+ if (($v_sys_ssl_crt != str_replace("\r\n", "\n", $_POST['v_sys_ssl_crt'])) || ($v_sys_ssl_key != str_replace("\r\n", "\n", $_POST['v_sys_ssl_key']))) {
+ exec ('mktemp -d', $mktemp_output, $return_var);
+ $tmpdir = $mktemp_output[0];
+
+ // Certificate
+ if (!empty($_POST['v_sys_ssl_crt'])) {
+ $fp = fopen($tmpdir."/certificate.crt", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_sys_ssl_crt']));
+ fwrite($fp, "\n");
+ fclose($fp);
+ }
+
+ // Key
+ if (!empty($_POST['v_sys_ssl_key'])) {
+ $fp = fopen($tmpdir."/certificate.key", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_sys_ssl_key']));
+ fwrite($fp, "\n");
+ fclose($fp);
+ }
+
+ exec (VESTA_CMD."v-change-sys-vesta-ssl ".$tmpdir, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ if (empty($_SESSION['error_msg'])) {
+ // List ssl certificate info
+ exec (VESTA_CMD."v-list-sys-vesta-ssl json", $output, $return_var);
+ $v_sys_ssl_str = json_decode(implode('', $output), true);
+ unset($output);
+ $v_sys_ssl_crt = $v_sys_ssl_str['VESTA']['CRT'];
+ $v_sys_ssl_key = $v_sys_ssl_str['VESTA']['KEY'];
+ $v_sys_ssl_ca = $v_sys_ssl_str['VESTA']['CA'];
+ $v_sys_ssl_subject = $v_sys_ssl_str['VESTA']['SUBJECT'];
+ $v_sys_ssl_aliases = $v_sys_ssl_str['VESTA']['ALIASES'];
+ $v_sys_ssl_not_before = $v_sys_ssl_str['VESTA']['NOT_BEFORE'];
+ $v_sys_ssl_not_after = $v_sys_ssl_str['VESTA']['NOT_AFTER'];
+ $v_sys_ssl_signature = $v_sys_ssl_str['VESTA']['SIGNATURE'];
+ $v_sys_ssl_pub_key = $v_sys_ssl_str['VESTA']['PUB_KEY'];
+ $v_sys_ssl_issuer = $v_sys_ssl_str['VESTA']['ISSUER'];
+ }
+ }
+ }
+
+ // Flush field values on success
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Changes has been saved.');
+ }
+
+ // activating sftp licence
+ if (empty($_SESSION['error_msg'])) {
+ if($_SESSION['SFTPJAIL_KEY'] != $_POST['v_sftp_licence'] && $_POST['v_sftp'] == 'yes'){
+ $module = 'sftpjail';
+ $licence_key = escapeshellarg($_POST['v_sftp_licence']);
+ exec (VESTA_CMD."v-activate-vesta-license ".$module." ".$licence_key, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Licence Activated');
+ $_SESSION['SFTPJAIL_KEY'] = $_POST['v_sftp_licence'];
+ }
+ }
+ }
+
+ // cancel sftp licence
+ if (empty($_SESSION['error_msg'])) {
+ if($_POST['v_sftp'] == 'cancel' && $_SESSION['SFTPJAIL_KEY']){
+ $module = 'sftpjail';
+ $licence_key = escapeshellarg($_SESSION['SFTPJAIL_KEY']);
+ exec (VESTA_CMD."v-deactivate-vesta-license ".$module." ".$licence_key, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Licence Deactivated');
+ unset($_SESSION['SFTPJAIL_KEY']);
+ }
+ }
+ }
+
+ // activating filemanager licence
+ if (empty($_SESSION['error_msg'])) {
+ if($_SESSION['FILEMANAGER_KEY'] != $_POST['v_filemanager_licence'] && $_POST['v_filemanager'] == 'yes'){
+ $module = 'filemanager';
+ $licence_key = escapeshellarg($_POST['v_filemanager_licence']);
+ exec (VESTA_CMD."v-activate-vesta-license ".$module." ".$licence_key, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Licence Activated');
+ $_SESSION['FILEMANAGER_KEY'] = $_POST['v_filemanager_licence'];
+ }
+ }
+ }
+
+ // cancel filemanager licence
+ if (empty($_SESSION['error_msg'])) {
+ if($_POST['v_filemanager'] == 'cancel' && $_SESSION['FILEMANAGER_KEY']){
+ $module = 'filemanager';
+ $licence_key = escapeshellarg($_SESSION['FILEMANAGER_KEY']);
+ exec (VESTA_CMD."v-deactivate-vesta-license ".$module." ".$licence_key, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Licence Deactivated');
+ unset($_SESSION['FILEMANAGER_KEY']);
+ }
+ }
+ }
+
+ // activating softaculous
+ if (empty($_SESSION['error_msg'])) {
+ if($_SESSION['SOFTACULOUS'] != $_POST['v_softaculous'] && $_POST['v_softaculous'] == 'yes'){
+ exec (VESTA_CMD."v-add-vesta-softaculous WEB", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Softaculous Activated');
+ $_SESSION['SOFTACULOUS'] = 'yes';
+ }
+ }
+ }
+
+ // disable softaculous
+ if (empty($_SESSION['error_msg'])) {
+ if($_SESSION['SOFTACULOUS'] != $_POST['v_softaculous'] && $_POST['v_softaculous'] == 'no'){
+ exec (VESTA_CMD."v-delete-vesta-softaculous", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Softaculous Disabled');
+ $_SESSION['SOFTACULOUS'] = '';
+ }
+ }
+ }
+
+ // Change port
+ if ((!empty($_POST['port'])) && ($port != $_POST['port'])) {
+ exec (VESTA_CMD."v-change-vesta-port ".escapeshellarg($_POST['port']), $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $port = $_POST['port'];
+ }
+
+}
+
+// Check system configuration
+exec (VESTA_CMD . "v-list-sys-config json", $output, $return_var);
+$data = json_decode(implode('', $output), true);
+unset($output);
+
+$sys_arr = $data['config'];
+foreach ($sys_arr as $key => $value) {
+ $_SESSION[$key] = $value;
+}
+
+
+// Render page
+// render_page($user, $TAB, 'edit_server');
+
+$result = array(
+ 'hostname' => $v_hostname,
+ 'timezones' => $v_timezones,
+ 'timezone' => $v_timezone,
+ 'port' => $port,
+ 'languages' => $languages,
+ 'backup_adv' => $v_backup_adv,
+ 'backup_remote_adv' => $v_backup_remote_adv,
+ 'language' => $_SESSION['LANGUAGE'],
+ 'proxy_system' => $_SESSION['PROXY_SYSTEM'],
+ 'mail_system' => $_SESSION['MAIL_SYSTEM'],
+ 'antivirus_system' => $_SESSION['ANTIVIRUS_SYSTEM'],
+ 'antispam_system' => $_SESSION['ANTISPAM_SYSTEM'],
+ 'mail_url' => $_SESSION['MAIL_URL'],
+ 'pgsql_url' => $_SESSION['DB_PGA_URL'],
+ 'mail_certificate' => $_SESSION['MAIL_CERTIFICATE'],
+ 'dns_system' => $_SESSION['DNS_SYSTEM'],
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'softaculous' => $_SESSION['SOFTACULOUS'],
+ 'firewall_system' => $_SESSION['FIREWALL_SYSTEM'],
+ 'web_backend' => $_SESSION['WEB_BACKEND'],
+ 'version' => $_SESSION['VERSION'],
+ 'http_host' => $_SERVER['HTTP_HOST'],
+ 'fm_key' => $_SESSION['FILEMANAGER_KEY'],
+ 'fm_license_key' => $_GET['filemanager_licence_key'],
+ 'disk_quota' => $_SESSION['DISK_QUOTA'],
+ 'web_backend_pool' => $_SESSION['WEB_BACKEND_POOL'],
+ 'sftpjail_key' => $_SESSION['SFTPJAIL_KEY'],
+ 'lead' => $_GET['lead'] == 'sftp',
+ 'softaculous_lead' => $_GET['lead'] == 'softaculous',
+ 'fm_lead' => $_GET['lead'] == 'filemanager',
+ 'sftp_licence_key' => $_GET['sftp_licence_key'],
+ 'licence_key' => $_GET['sftp_licence_key'] != '' ? $_GET['sftp_licence_key'] : $_SESSION['SFTPJAIL_KEY'],
+ 'fm_licence_key_option' => $_GET['filemanager_licence_key'] != '' ? $_GET['filemanager_licence_key'] : $_SESSION['FILEMANAGER_KEY'],
+ 'vesta_certificate' => $_SESSION['VESTA_CERTIFICATE'],
+ 'yes_no_options' => [ __('no'), __('yes') ],
+ 'dns_cluster_options' => [ __('no'), __('yes') ],
+ 'postgre_sql_options' => [ __('no'), __('yes') ],
+ 'mysql_support_options' => [ __('no'), __('yes') ],
+ 'dns_cluster' => $dns_cluster,
+ 'v_dns_cluster' => $v_dns_cluster,
+ 'db_hosts' => $db_hosts,
+ 'mysql_hosts' => $v_mysql_hosts,
+ 'mysql' => $v_mysql,
+ 'pgsql_hosts' => $v_pgsql_hosts,
+ 'pgsql' => $v_pgsql,
+ 'protocols' => [ __('ftp'), __('sftp') ],
+ 'backup_dir' => $v_backup_dir,
+ 'backup_gzip' => $v_backup_gzip,
+ 'backup_types' => $backup_types,
+ 'backup' => $v_backup,
+ 'remote_backup' => $v_remote_backup,
+ 'backup_host' => $v_backup_host,
+ 'backup_type' => $v_backup_type,
+ 'backup_username' => $v_backup_username,
+ 'backup_password' => $v_backup_password,
+ 'backup_port' => $v_backup_port,
+ 'backup_bpath' => $v_backup_bpath,
+ 'ssl_domains' => $v_ssl_domains,
+ 'sys_ssl_crt' => $v_sys_ssl_str['VESTA']['CRT'],
+ 'sys_ssl_key' => $v_sys_ssl_str['VESTA']['KEY'],
+ 'sys_ssl_ca' => $v_sys_ssl_str['VESTA']['CA'],
+ 'sys_ssl_subject' => $v_sys_ssl_str['VESTA']['SUBJECT'],
+ 'sys_ssl_aliases' => $v_sys_ssl_str['VESTA']['ALIASES'],
+ 'sys_ssl_not_before' => $v_sys_ssl_str['VESTA']['NOT_BEFORE'],
+ 'sys_ssl_not_after' => $v_sys_ssl_str['VESTA']['NOT_AFTER'],
+ 'sys_ssl_signature' => $v_sys_ssl_str['VESTA']['SIGNATURE'],
+ 'sys_ssl_pub_key' => $v_sys_ssl_str['VESTA']['PUB_KEY'],
+ 'sys_ssl_issuer' => $v_sys_ssl_str['VESTA']['ISSUER'],
+ 'mail_ssl_crt' => $v_mail_ssl_str['MAIL']['CRT'],
+ 'mail_ssl_key' => $v_mail_ssl_str['MAIL']['KEY'],
+ 'mail_ssl_ca' => $v_mail_ssl_str['MAIL']['CA'],
+ 'mail_ssl_subject' => $v_mail_ssl_str['MAIL']['SUBJECT'],
+ 'mail_ssl_aliases' => $v_mail_ssl_str['MAIL']['ALIASES'],
+ 'mail_ssl_not_before' => $v_mail_ssl_str['MAIL']['NOT_BEFORE'],
+ 'mail_ssl_not_after' => $v_mail_ssl_str['MAIL']['NOT_AFTER'],
+ 'mail_ssl_signature' => $v_mail_ssl_str['MAIL']['SIGNATURE'],
+ 'mail_ssl_pub_key' => $v_mail_ssl_str['MAIL']['PUB_KEY'],
+ 'mail_ssl_issuer' => $v_mail_ssl_str['MAIL']['ISSUER'],
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/iptables/index.php b/web/api/v1/edit/server/iptables/index.php
new file mode 100644
index 000000000..ff3875f4d
--- /dev/null
+++ b/web/api/v1/edit/server/iptables/index.php
@@ -0,0 +1,16 @@
+ $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/mariadb/index.php b/web/api/v1/edit/server/mariadb/index.php
new file mode 100644
index 000000000..2766d14f1
--- /dev/null
+++ b/web/api/v1/edit/server/mariadb/index.php
@@ -0,0 +1,79 @@
+ $v_max_user_connections,
+ 'max_connections' => $v_max_connections,
+ 'wait_timeout' => $v_wait_timeout,
+ 'interactive_timeout' => $v_interactive_timeout,
+ 'max_allowed_packet' => $v_max_allowed_packet,
+ 'config_path' => $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/mysql/index.php b/web/api/v1/edit/server/mysql/index.php
new file mode 100644
index 000000000..bbf15b682
--- /dev/null
+++ b/web/api/v1/edit/server/mysql/index.php
@@ -0,0 +1,79 @@
+ $v_max_user_connections,
+ 'max_connections' => $v_max_connections,
+ 'wait_timeout' => $v_wait_timeout,
+ 'interactive_timeout' => $v_interactive_timeout,
+ 'max_allowed_packet' => $v_max_allowed_packet,
+ 'config_path' => $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/mysqld/index.php b/web/api/v1/edit/server/mysqld/index.php
new file mode 100644
index 000000000..c63cb81ac
--- /dev/null
+++ b/web/api/v1/edit/server/mysqld/index.php
@@ -0,0 +1,79 @@
+ $v_max_user_connections,
+ 'max_connections' => $v_max_connections,
+ 'wait_timeout' => $v_wait_timeout,
+ 'interactive_timeout' => $v_interactive_timeout,
+ 'max_allowed_packet' => $v_max_allowed_packet,
+ 'config_path' => $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/named/index.php b/web/api/v1/edit/server/named/index.php
new file mode 100644
index 000000000..aa7d2ed88
--- /dev/null
+++ b/web/api/v1/edit/server/named/index.php
@@ -0,0 +1,65 @@
+ $v_service_name,
+ 'config_path' => $v_config_path,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/nginx/index.php b/web/api/v1/edit/server/nginx/index.php
new file mode 100644
index 000000000..2989975e9
--- /dev/null
+++ b/web/api/v1/edit/server/nginx/index.php
@@ -0,0 +1,89 @@
+ $data['CONFIG']['worker_processes'],
+ 'worker_connections' => $data['CONFIG']['worker_connections'],
+ 'send_timeout' => $data['CONFIG']['send_timeout'],
+ 'proxy_connect_timeout' => $data['CONFIG']['proxy_connect_timeout'],
+ 'proxy_send_timeout' => $data['CONFIG']['proxy_send_timeout'],
+ 'proxy_read_timeout' => $data['CONFIG']['proxy_read_timeout'],
+ 'client_max_body_size' => $data['CONFIG']['client_max_body_size'],
+ 'gzip' => $data['CONFIG']['gzip'],
+ 'gzip_comp_level' => $data['CONFIG']['gzip_comp_level'],
+ 'charset' => $data['CONFIG']['charset'],
+ 'config_path' => $data['CONFIG']['config_path'],
+ 'service_name' => strtoupper('nginx'),
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/php-fpm/index.php b/web/api/v1/edit/server/php-fpm/index.php
new file mode 100644
index 000000000..066c13bfe
--- /dev/null
+++ b/web/api/v1/edit/server/php-fpm/index.php
@@ -0,0 +1,82 @@
+ $data['CONFIG']['memory_limit'],
+ 'max_execution_time' => $data['CONFIG']['max_execution_time'],
+ 'max_input_time' => $data['CONFIG']['max_input_time'],
+ 'upload_max_filesize' => $data['CONFIG']['upload_max_filesize'],
+ 'post_max_size' => $data['CONFIG']['post_max_size'],
+ 'display_errors' => $data['CONFIG']['display_errors'],
+ 'error_reporting' => $data['CONFIG']['error_reporting'],
+ 'config_path' => $data['CONFIG']['config_path'],
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/php/index.php b/web/api/v1/edit/server/php/index.php
new file mode 100644
index 000000000..4ad60e1bb
--- /dev/null
+++ b/web/api/v1/edit/server/php/index.php
@@ -0,0 +1,82 @@
+ $data['CONFIG']['memory_limit'],
+ 'max_execution_time' => $data['CONFIG']['max_execution_time'],
+ 'max_input_time' => $data['CONFIG']['max_input_time'],
+ 'upload_max_filesize' => $data['CONFIG']['upload_max_filesize'],
+ 'post_max_size' => $data['CONFIG']['post_max_size'],
+ 'display_errors' => $data['CONFIG']['display_errors'],
+ 'error_reporting' => $data['CONFIG']['error_reporting'],
+ 'config_path' => $data['CONFIG']['config_path'],
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/php5-fpm/index.php b/web/api/v1/edit/server/php5-fpm/index.php
new file mode 100644
index 000000000..066c13bfe
--- /dev/null
+++ b/web/api/v1/edit/server/php5-fpm/index.php
@@ -0,0 +1,82 @@
+ $data['CONFIG']['memory_limit'],
+ 'max_execution_time' => $data['CONFIG']['max_execution_time'],
+ 'max_input_time' => $data['CONFIG']['max_input_time'],
+ 'upload_max_filesize' => $data['CONFIG']['upload_max_filesize'],
+ 'post_max_size' => $data['CONFIG']['post_max_size'],
+ 'display_errors' => $data['CONFIG']['display_errors'],
+ 'error_reporting' => $data['CONFIG']['error_reporting'],
+ 'config_path' => $data['CONFIG']['config_path'],
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/postgresql/index.php b/web/api/v1/edit/server/postgresql/index.php
new file mode 100644
index 000000000..c030cfb66
--- /dev/null
+++ b/web/api/v1/edit/server/postgresql/index.php
@@ -0,0 +1,87 @@
+ $v_options_path,
+ 'config_path' => $v_config_path,
+ 'service_name' => $v_service_name,
+ 'options' => $v_options,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/server/proftpd/index.php b/web/api/v1/edit/server/proftpd/index.php
new file mode 100644
index 000000000..2f7c4d1e1
--- /dev/null
+++ b/web/api/v1/edit/server/proftpd/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/spamassassin/index.php b/web/api/v1/edit/server/spamassassin/index.php
new file mode 100644
index 000000000..ef12e5a73
--- /dev/null
+++ b/web/api/v1/edit/server/spamassassin/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/spamd/index.php b/web/api/v1/edit/server/spamd/index.php
new file mode 100644
index 000000000..accfd9a6f
--- /dev/null
+++ b/web/api/v1/edit/server/spamd/index.php
@@ -0,0 +1,65 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/server/test.php b/web/api/v1/edit/server/test.php
new file mode 100644
index 000000000..a76682b4a
--- /dev/null
+++ b/web/api/v1/edit/server/test.php
@@ -0,0 +1,18 @@
+ $v_config_path,
+ 'service_name' => $v_service_name,
+ 'config' => $v_config,
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
\ No newline at end of file
diff --git a/web/api/v1/edit/user/index.php b/web/api/v1/edit/user/index.php
new file mode 100644
index 000000000..d2bf2733f
--- /dev/null
+++ b/web/api/v1/edit/user/index.php
@@ -0,0 +1,218 @@
+ '',
+ 'email' => $data[$v_username]['CONTACT'],
+ 'package' => $data[$v_username]['PACKAGE'],
+ 'language' => $data[$v_username]['LANGUAGE'],
+ 'fname' => $data[$v_username]['FNAME'],
+ 'lname' => $data[$v_username]['LNAME'],
+ 'shell' => $data[$v_username]['SHELL'],
+ 'nameservers' => $nameservers,
+ 'ns1' => $nameservers[0],
+ 'ns2' => $nameservers[1],
+ 'ns3' => $nameservers[2],
+ 'ns4' => $nameservers[3],
+ 'ns5' => $nameservers[4],
+ 'ns6' => $nameservers[5],
+ 'ns7' => $nameservers[6],
+ 'ns8' => $nameservers[7],
+ 'suspended' => $data[$v_username]['SUSPENDED'],
+ 'status' => $v_status,
+ 'time' => $data[$v_username]['TIME'],
+ 'date' => $data[$v_username]['DATE'],
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg'],
+ 'packages' => $packages,
+ 'languages' => $languages,
+ 'shells' => $shells
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/edit/web/index.php b/web/api/v1/edit/web/index.php
new file mode 100644
index 000000000..858b411a0
--- /dev/null
+++ b/web/api/v1/edit/web/index.php
@@ -0,0 +1,804 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ } else {
+ exec ('mktemp -d', $mktemp_output, $return_var);
+ $tmpdir = $mktemp_output[0];
+
+ // Certificate
+ if (!empty($_POST['v_ssl_crt'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".crt", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_crt']));
+ fclose($fp);
+ }
+
+ // Key
+ if (!empty($_POST['v_ssl_key'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".key", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_key']));
+ fclose($fp);
+ }
+
+ // CA
+ if (!empty($_POST['v_ssl_ca'])) {
+ $fp = fopen($tmpdir."/".$_POST['v_domain'].".ca", 'w');
+ fwrite($fp, str_replace("\r\n", "\n", $_POST['v_ssl_ca']));
+ fclose($fp);
+ }
+ exec (VESTA_CMD."v-add-web-domain-ssl ".$user." ".$v_domain." ".$tmpdir." ".$v_ssl_home." no", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_ssl = 'yes';
+ $restart_web = 'yes';
+ $restart_proxy = 'yes';
+
+ exec (VESTA_CMD."v-list-web-domain-ssl ".$user." ".$v_domain." json", $output, $return_var);
+ $ssl_str = json_decode(implode('', $output), true);
+ unset($output);
+ $v_ssl_crt = $ssl_str[$_POST['v_domain']]['CRT'];
+ $v_ssl_key = $ssl_str[$_POST['v_domain']]['KEY'];
+ $v_ssl_ca = $ssl_str[$_POST['v_domain']]['CA'];
+ $v_ssl_subject = $ssl_str[$_POST['v_domain']]['SUBJECT'];
+ $v_ssl_aliases = $ssl_str[$_POST['v_domain']]['ALIASES'];
+ $v_ssl_not_before = $ssl_str[$_POST['v_domain']]['NOT_BEFORE'];
+ $v_ssl_not_after = $ssl_str[$_POST['v_domain']]['NOT_AFTER'];
+ $v_ssl_signature = $ssl_str[$_POST['v_domain']]['SIGNATURE'];
+ $v_ssl_pub_key = $ssl_str[$_POST['v_domain']]['PUB_KEY'];
+ $v_ssl_issuer = $ssl_str[$_POST['v_domain']]['ISSUER'];
+
+ // Cleanup certificate tempfiles
+ if (!empty($_POST['v_ssl_crt'])) unlink($tmpdir."/".$_POST['v_domain'].".crt");
+ if (!empty($_POST['v_ssl_key'])) unlink($tmpdir."/".$_POST['v_domain'].".key");
+ if (!empty($_POST['v_ssl_ca'])) unlink($tmpdir."/".$_POST['v_domain'].".ca");
+ rmdir($tmpdir);
+ }
+ }
+
+
+
+ // Delete web stats
+ if ((!empty($v_stats)) && ($_POST['v_stats'] == 'none') && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-delete-web-domain-stats ".$v_username." ".$v_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_stats = '';
+ }
+
+ // Change web stats engine
+ if ((!empty($v_stats)) && ($_POST['v_stats'] != $v_stats) && (empty($_SESSION['error_msg']))) {
+ $v_stats = escapeshellarg($_POST['v_stats']);
+ exec (VESTA_CMD."v-change-web-domain-stats ".$v_username." ".$v_domain." ".$v_stats, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Add web stats
+ if ((empty($v_stats)) && ($_POST['v_stats'] != 'none') && (empty($_SESSION['error_msg']))) {
+ $v_stats = escapeshellarg($_POST['v_stats']);
+ exec (VESTA_CMD."v-add-web-domain-stats ".$v_username." ".$v_domain." ".$v_stats, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Delete web stats authorization
+ if ((!empty($v_stats_user)) && (empty($_POST['v_stats_auth'])) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-delete-web-domain-stats-user ".$v_username." ".$v_domain, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ $v_stats_user = '';
+ $v_stats_password = '';
+ }
+
+ // Change web stats user or password
+ if ((empty($v_stats_user)) && (!empty($_POST['v_stats_auth'])) && (empty($_SESSION['error_msg']))) {
+ if (empty($_POST['v_stats_user'])) $errors[] = __('stats username');
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ } else {
+ $v_stats_user = escapeshellarg($_POST['v_stats_user']);
+ $v_stats_password = tempnam("/tmp","vst");
+ $fp = fopen($v_stats_password, "w");
+ fwrite($fp, $_POST['v_stats_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-web-domain-stats-user ".$v_username." ".$v_domain." ".$v_stats_user." ".$v_stats_password, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_stats_password);
+ $v_stats_password = escapeshellarg($_POST['v_stats_password']);
+ }
+ }
+
+ // Add web stats authorization
+ if ((!empty($v_stats_user)) && (!empty($_POST['v_stats_auth'])) && (empty($_SESSION['error_msg']))) {
+ if (empty($_POST['v_stats_user'])) $errors[] = __('stats user');
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+ if (($v_stats_user != $_POST['v_stats_user']) || (!empty($_POST['v_stats_password'])) && (empty($_SESSION['error_msg']))) {
+ $v_stats_user = escapeshellarg($_POST['v_stats_user']);
+ $v_stats_password = tempnam("/tmp","vst");
+ $fp = fopen($v_stats_password, "w");
+ fwrite($fp, $_POST['v_stats_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-web-domain-stats-user ".$v_username." ".$v_domain." ".$v_stats_user." ".$v_stats_password, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ unlink($v_stats_password);
+ $v_stats_password = escapeshellarg($_POST['v_stats_password']);
+ }
+ }
+
+ // Update ftp account
+ if (!empty($_POST['v_ftp_user'])) {
+ $v_ftp_users_updated = array();
+ foreach ($_POST['v_ftp_user'] as $i => $v_ftp_user_data) {
+ if (empty($v_ftp_user_data['v_ftp_user'])) {
+ continue;
+ }
+
+ $v_ftp_user_data['v_ftp_user'] = preg_replace("/^".$user."_/i", "", $v_ftp_user_data['v_ftp_user']);
+ if ($v_ftp_user_data['is_new'] == 1 && !empty($_POST['v_ftp'])) {
+ if ((!empty($v_ftp_user_data['v_ftp_email'])) && (!filter_var($v_ftp_user_data['v_ftp_email'], FILTER_VALIDATE_EMAIL))) $_SESSION['error_msg'] = __('Please enter valid email address.');
+ if (empty($v_ftp_user_data['v_ftp_user'])) $errors[] = 'ftp user';
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Add ftp account
+ $v_ftp_username = $v_ftp_user_data['v_ftp_user'];
+ $v_ftp_username_full = $user . '_' . $v_ftp_user_data['v_ftp_user'];
+ $v_ftp_user = escapeshellarg($v_ftp_username);
+ $v_ftp_path = escapeshellarg(trim($v_ftp_user_data['v_ftp_path']));
+ if (empty($_SESSION['error_msg'])) {
+ $v_ftp_password = tempnam("/tmp","vst");
+ $fp = fopen($v_ftp_password, "w");
+ fwrite($fp, $v_ftp_user_data['v_ftp_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-add-web-domain-ftp ".$v_username." ".$v_domain." ".$v_ftp_user." ".$v_ftp_password . " " . $v_ftp_path, $output, $return_var);
+ check_return_code($return_var,$output);
+ if ((!empty($v_ftp_user_data['v_ftp_email'])) && (empty($_SESSION['error_msg']))) {
+ $to = $v_ftp_user_data['v_ftp_email'];
+ $subject = __("FTP login credentials");
+ $hostname = exec('hostname');
+ $from = __('MAIL_FROM',$hostname);
+ $mailtext = __('FTP_ACCOUNT_READY',$_GET['domain'],$user,$v_ftp_username,$v_ftp_user_data['v_ftp_password']);
+ send_email($to, $subject, $mailtext, $from);
+ unset($v_ftp_email);
+ }
+ unset($output);
+ unlink($v_ftp_password);
+ $v_ftp_password = escapeshellarg($v_ftp_user_data['v_ftp_password']);
+ }
+
+ if ($return_var == 0) {
+ $v_ftp_password = "";
+ $v_ftp_user_data['is_new'] = 0;
+ }
+ else {
+ $v_ftp_user_data['is_new'] = 1;
+ }
+
+ $v_ftp_users_updated[] = array(
+ 'is_new' => empty($_SESSION['error_msg']) ? 0 : 1,
+ 'v_ftp_user' => $v_ftp_username_full,
+ 'v_ftp_password' => $v_ftp_password,
+ 'v_ftp_path' => $v_ftp_user_data['v_ftp_path'],
+ 'v_ftp_email' => $v_ftp_user_data['v_ftp_email'],
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+
+ continue;
+ }
+
+ // Delete FTP account
+ if ($v_ftp_user_data['delete'] == 1) {
+ $v_ftp_username = $user . '_' . $v_ftp_user_data['v_ftp_user'];
+ exec (VESTA_CMD."v-delete-web-domain-ftp ".$v_username." ".$v_domain." ".$v_ftp_username, $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+
+ continue;
+ }
+
+ if (!empty($_POST['v_ftp'])) {
+ if (empty($v_ftp_user_data['v_ftp_user'])) $errors[] = __('ftp user');
+ if (!empty($errors[0])) {
+ foreach ($errors as $i => $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ }
+
+ // Change FTP account path
+ $v_ftp_username_for_emailing = $v_ftp_user_data['v_ftp_user'];
+ $v_ftp_username = $user . '_' . $v_ftp_user_data['v_ftp_user']; //preg_replace("/^".$user."_/", "", $v_ftp_user_data['v_ftp_user']);
+ $v_ftp_username = escapeshellarg($v_ftp_username);
+ $v_ftp_path = escapeshellarg(trim($v_ftp_user_data['v_ftp_path']));
+ if(escapeshellarg(trim($v_ftp_user_data['v_ftp_path_prev'])) != $v_ftp_path) {
+ exec (VESTA_CMD."v-change-web-domain-ftp-path ".$v_username." ".$v_domain." ".$v_ftp_username." ".$v_ftp_path, $output, $return_var);
+ }
+
+ // Change FTP account password
+ if (!empty($v_ftp_user_data['v_ftp_password'])) {
+ $v_ftp_password = tempnam("/tmp","vst");
+ $fp = fopen($v_ftp_password, "w");
+ fwrite($fp, $v_ftp_user_data['v_ftp_password']."\n");
+ fclose($fp);
+ exec (VESTA_CMD."v-change-web-domain-ftp-password ".$v_username." ".$v_domain." ".$v_ftp_username." ".$v_ftp_password, $output, $return_var);
+ unlink($v_ftp_password);
+
+ $to = $v_ftp_user_data['v_ftp_email'];
+ $subject = __("FTP login credentials");
+ $hostname = exec('hostname');
+ $from = __('MAIL_FROM',$hostname);
+ $mailtext = __('FTP_ACCOUNT_READY',$_GET['domain'],$user,$v_ftp_username_for_emailing,$v_ftp_user_data['v_ftp_password']);
+ send_email($to, $subject, $mailtext, $from);
+ unset($v_ftp_email);
+ }
+ check_return_code($return_var, $output);
+ unset($output);
+
+ $v_ftp_users_updated[] = array(
+ 'is_new' => 0,
+ 'v_ftp_user' => $v_ftp_username,
+ 'v_ftp_password' => $v_ftp_user_data['v_ftp_password'],
+ 'v_ftp_path' => $v_ftp_user_data['v_ftp_path'],
+ 'v_ftp_email' => $v_ftp_user_data['v_ftp_email'],
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+ }
+ }
+ }
+
+ // Restart web server
+ if (!empty($restart_web) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-restart-web", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Restart proxy server
+ if ((!empty($_SESSION['PROXY_SYSTEM'])) && !empty($restart_proxy) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-restart-proxy", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Restart dns server
+ if (!empty($restart_dns) && (empty($_SESSION['error_msg']))) {
+ exec (VESTA_CMD."v-restart-dns", $output, $return_var);
+ check_return_code($return_var,$output);
+ unset($output);
+ }
+
+ // Set success message
+ if (empty($_SESSION['error_msg'])) {
+ $_SESSION['ok_msg'] = __('Changes has been saved.');
+ }
+
+}
+
+
+$v_ftp_users_raw = explode(':', $v_ftp_user);
+$v_ftp_users_paths_raw = explode(':', $data[$v_domain]['FTP_PATH']);
+$v_ftp_users = array();
+foreach ($v_ftp_users_raw as $v_ftp_user_index => $v_ftp_user_val) {
+ if (empty($v_ftp_user_val)) {
+ continue;
+ }
+ $v_ftp_users[] = array(
+ 'is_new' => 0,
+ 'v_ftp_user' => $v_ftp_user_val,
+ 'v_ftp_password' => $v_ftp_password,
+ 'v_ftp_path' => (isset($v_ftp_users_paths_raw[$v_ftp_user_index]) ? $v_ftp_users_paths_raw[$v_ftp_user_index] : ''),
+ 'v_ftp_email' => $v_ftp_email,
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+}
+
+if (empty($v_ftp_users)) {
+ $v_ftp_user = null;
+ $v_ftp_users[] = array(
+ 'is_new' => 1,
+ 'v_ftp_user' => '',
+ 'v_ftp_password' => '',
+ 'v_ftp_path' => (isset($v_ftp_users_paths_raw[$v_ftp_user_index]) ? $v_ftp_users_paths_raw[$v_ftp_user_index] : ''),
+ 'v_ftp_email' => '',
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+}
+
+// set default pre path for newly created users
+$v_ftp_pre_path_new_user = $v_ftp_user_prepath;
+if (isset($v_ftp_users_updated)) {
+ $v_ftp_users = $v_ftp_users_updated;
+ if (empty($v_ftp_users_updated)) {
+ $v_ftp_user = null;
+ $v_ftp_users[] = array(
+ 'is_new' => 1,
+ 'v_ftp_user' => '',
+ 'v_ftp_password' => '',
+ 'v_ftp_path' => (isset($v_ftp_users_paths_raw[$v_ftp_user_index]) ? $v_ftp_users_paths_raw[$v_ftp_user_index] : ''),
+ 'v_ftp_email' => '',
+ 'v_ftp_pre_path' => $v_ftp_user_prepath
+ );
+ }
+}
+
+$result = array(
+ 'username' => $v_username,
+ 'domain' => $v_domain,
+ 'ip' => $v_ip,
+ 'template' => $v_template,
+ 'aliases' => $v_aliases,
+ 'valiases' => $valiases,
+ 'tpl' => $v_tpl,
+ 'elog' => $$v_elog,
+ 'ssl' => $v_ssl,
+ 'cgi' => $v_cgi,
+ 'ssl_crt' => $v_ssl_crt,
+ 'ssl_key' => $v_ssl_key,
+ 'ssl_ca' => $v_ssl_ca,
+ 'ssl_subject' => $v_ssl_subject,
+ 'ssl_aliases' => $v_ssl_aliases,
+ 'ssl_not_before' => $v_ssl_not_before,
+ 'ssl_not_after' => $v_ssl_not_after,
+ 'ssl_signature' => $v_ssl_signature,
+ 'ssl_pub_key' => $v_ssl_pub_key,
+ 'ssl_issuer' => $v_ssl_issuer,
+ 'letsencrypt' => $v_letsencrypt,
+ 'ssl_home' => $v_ssl_home,
+ 'backend_template' => $v_backend_template,
+ 'proxy' => $v_proxy,
+ 'proxy_template' => $v_proxy_template,
+ 'proxy_ext' => !empty($v_proxy_ext) ? $v_proxy_ext : 'jpg, jpeg, gif, png, ico, svg, css, zip, tgz, gz, rar, bz2, exe, pdf, doc, xls, ppt, txt, odt, ods, odp, odf, tar, bmp, rtf, js, mp3, avi, mpeg, flv, html, htm',
+ 'v_stats' => $v_stats,
+ 'stats_user' => $v_stats_user,
+ 'ftp_user' => $v_ftp_user,
+ 'ftp_path' => $v_ftp_path,
+ 'ftp_password' => $v_ftp_password,
+ 'ftp_user_prepath' => $v_ftp_user_prepath,
+ 'ftp_email' => $v_ftp_email,
+ 'suspended' => $v_suspended,
+ 'status' => $v_status,
+ 'time' => $v_time,
+ 'date' => $v_date,
+ 'ips' => $ips,
+ 'prefixI18N' => __('Prefix will be automaticaly added to username',$user."_"),
+ 'ftp_users' => $v_ftp_users,
+ 'templates' => $templates,
+ 'backend_templates' => $backend_templates,
+ 'proxy_templates' => $proxy_templates,
+ 'stats' => $stats,
+ 'proxy_system' => $_SESSION['PROXY_SYSTEM'],
+ 'web_backend' => $_SESSION['WEB_BACKEND'],
+ 'web_system' => $_SESSION['WEB_SYSTEM'],
+ 'error_msg' => $_SESSION['error_msg'],
+ 'ok_msg' => $_SESSION['ok_msg']
+);
+
+echo json_encode($result);
+
+// Flush session messages
+unset($_SESSION['error_msg']);
+unset($_SESSION['ok_msg']);
diff --git a/web/api/v1/generate/ssl/index.php b/web/api/v1/generate/ssl/index.php
new file mode 100644
index 000000000..4ecdf4255
--- /dev/null
+++ b/web/api/v1/generate/ssl/index.php
@@ -0,0 +1,106 @@
+ $error) {
+ if ( $i == 0 ) {
+ $error_msg = $error;
+ } else {
+ $error_msg = $error_msg.", ".$error;
+ }
+ }
+ $_SESSION['error_msg'] = __('Field "%s" can not be blank.',$error_msg);
+ unset($_SESSION['error_msg']);
+ exit;
+ }
+
+ // Protect input
+ $v_domain = escapeshellarg($_POST['v_domain']);
+ $v_email = escapeshellarg($_POST['v_email']);
+ $v_country = escapeshellarg($_POST['v_country']);
+ $v_state = escapeshellarg($_POST['v_state']);
+ $v_locality = escapeshellarg($_POST['v_locality']);
+ $v_org = escapeshellarg($_POST['v_org']);
+
+ exec (VESTA_CMD."v-generate-ssl-cert ".$v_domain." ".$v_email." ".$v_country." ".$v_state." ".$v_locality." ".$v_org." IT '' json", $output, $return_var);
+
+ // Revert to raw values
+ $v_domain = $_POST['v_domain'];
+ $v_email = $_POST['v_email'];
+ $v_country = $_POST['v_country'];
+ $v_state = $_POST['v_state'];
+ $v_locality = $_POST['v_locality'];
+ $v_org = $_POST['v_org'];
+
+ // Check return code
+ if ($return_var != 0) {
+ $error = implode(' ', $output);
+ if (empty($error)) $error = __('Error code:',$return_var);
+ $_SESSION['error_msg'] = $error;
+ unset($_SESSION['error_msg']);
+ exit;
+ }
+
+ // OK message
+ $_SESSION['ok_msg'] = __('SSL_GENERATED_OK');
+
+ // Parse output
+ $data = json_decode(implode('', $output), true);
+ unset($output);
+ $v_crt = $data[$v_domain]['CRT'];
+ $v_key = $data[$v_domain]['KEY'];
+ $v_csr = $data[$v_domain]['CSR'];
+}
+
+$result = array(
+ 'domain' => $v_domain,
+ 'email' => $v_email,
+ 'country' => $v_country,
+ 'state' => $v_state,
+ 'locality' => $v_locality,
+ 'org' => $v_org,
+ 'org_unit' => $v_org_unit,
+ 'crt' => $v_crt,
+ 'key' => $v_key,
+ 'csr' => $v_csr,
+ 'ok_msg' => $_SESSION['ok_msg'],
+ 'error_msg' => $_SESSION['error_msg']
+);
+
+echo json_encode($result);
+unset($_SESSION['ok_msg']);
+unset($_SESSION['error_msg']);
diff --git a/web/api/v1/index.php b/web/api/v1/index.php
new file mode 100644
index 000000000..80f72b99e
--- /dev/null
+++ b/web/api/v1/index.php
@@ -0,0 +1,125 @@
+ 0 ) {
+ echo 'Error: authentication failed';
+ exit;
+ }
+ } else {
+ $key = '/usr/local/vesta/data/keys/' . basename($_POST['hash']);
+ if (file_exists($key) && is_file($key)) {
+ exec(VESTA_CMD ."v-check-api-key ".escapeshellarg($key)." ".$v_ip, $output, $return_var);
+ unset($output);
+
+ // Check API answer
+ if ( $return_var > 0 ) {
+ echo 'Error: authentication failed';
+ exit;
+ }
+ } else {
+ $return_var = 1;
+ }
+ }
+
+ if ( $return_var > 0 ) {
+ echo 'Error: authentication failed';
+ exit;
+ }
+
+ // Prepare arguments
+ if (isset($_POST['cmd'])) $cmd = escapeshellarg($_POST['cmd']);
+ if (isset($_POST['arg1'])) $arg1 = escapeshellarg($_POST['arg1']);
+ if (isset($_POST['arg2'])) $arg2 = escapeshellarg($_POST['arg2']);
+ if (isset($_POST['arg3'])) $arg3 = escapeshellarg($_POST['arg3']);
+ if (isset($_POST['arg4'])) $arg4 = escapeshellarg($_POST['arg4']);
+ if (isset($_POST['arg5'])) $arg5 = escapeshellarg($_POST['arg5']);
+ if (isset($_POST['arg6'])) $arg6 = escapeshellarg($_POST['arg6']);
+ if (isset($_POST['arg7'])) $arg7 = escapeshellarg($_POST['arg7']);
+ if (isset($_POST['arg8'])) $arg8 = escapeshellarg($_POST['arg8']);
+ if (isset($_POST['arg9'])) $arg9 = escapeshellarg($_POST['arg9']);
+
+ // Build query
+ $cmdquery = VESTA_CMD.$cmd." ";
+ if(!empty($arg1)){
+ $cmdquery = $cmdquery.$arg1." "; }
+ if(!empty($arg2)){
+ $cmdquery = $cmdquery.$arg2." "; }
+ if(!empty($arg3)){
+ $cmdquery = $cmdquery.$arg3." "; }
+ if(!empty($arg4)){
+ $cmdquery = $cmdquery.$arg4." "; }
+ if(!empty($arg5)){
+ $cmdquery = $cmdquery.$arg5." "; }
+ if(!empty($arg6)){
+ $cmdquery = $cmdquery.$arg6." "; }
+ if(!empty($arg7)){
+ $cmdquery = $cmdquery.$arg7." "; }
+ if(!empty($arg8)){
+ $cmdquery = $cmdquery.$arg8." "; }
+ if(!empty($arg9)){
+ $cmdquery = $cmdquery.$arg9; }
+
+ // Check command
+ if ($cmd == "'v-make-tmp-file'") {
+ // Used in DNS Cluster
+ $fp = fopen($_POST['arg2'], 'w');
+ fwrite($fp, $_POST['arg1']."\n");
+ fclose($fp);
+ $return_var = 0;
+ } else {
+ // Run normal cmd query
+ exec ($cmdquery, $output, $return_var);
+ }
+
+ if ((!empty($_POST['returncode'])) && ($_POST['returncode'] == 'yes')) {
+ echo $return_var;
+ } else {
+ if (($return_var == 0) && (empty($output))) {
+ echo "OK";
+ } else {
+ echo implode("\n",$output)."\n";
+ }
+ }
+}
diff --git a/web/api/v1/languages.php b/web/api/v1/languages.php
new file mode 100644
index 000000000..8332fc217
--- /dev/null
+++ b/web/api/v1/languages.php
@@ -0,0 +1,15 @@
+ $data);
+
+echo json_encode($result);
diff --git a/web/api/v1/list/backup/index.php b/web/api/v1/list/backup/index.php
new file mode 100644
index 000000000..54e515202
--- /dev/null
+++ b/web/api/v1/list/backup/index.php
@@ -0,0 +1,77 @@
+ $value) {
+ ++$i;
+ $web = __('no');
+ $dns = __('no');
+ $mail = __('no');
+ $db = __('no');
+ $cron = __('no');
+ $udir = __('no');
+
+ if (!empty($data[$key]['WEB'])) $web = __('yes');
+ if (!empty($data[$key]['DNS'])) $dns = __('yes');
+ if (!empty($data[$key]['MAIL'])) $mail = __('yes');
+ if (!empty($data[$key]['DB'])) $db = __('yes');
+ if (!empty($data[$key]['CRON'])) $cron = __('yes');
+ if (!empty($data[$key]['UDIR'])) $udir = __('yes');
+
+ $data[$key]['delete_conf'] = __('DELETE_BACKUP_CONFIRMATION', $key);
+
+ if (empty($_GET['backup'])){
+ if ( $i == 1) {
+ $total_amount = __('1 archive');
+ } else {
+ $total_amount = __('%s archives',$i);
+ }
+ } else {
+ $webAr = explode(',',$data[$backup]['WEB']);
+ $dnsAr = explode(',',$data[$backup]['DNS']);
+ $mailAr = explode(',',$data[$backup]['MAIL']);
+ $dbAr = explode(',',$data[$backup]['DB']);
+ $cronAr = explode(',',$data[$backup]['CRON']);
+ $udirAr = explode(',',$data[$backup]['UDIR']);
+
+ $totalLength = count($webAr) + count($dnsAr) + count($mailAr) + count($dbAr) + count($cronAr) + count($udirAr);
+
+ $total_amount = __('%s items', $totalLength);
+ }
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->backup_fav = $_SESSION['favourites']['BACKUP'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/cron/index.php b/web/api/v1/list/cron/index.php
new file mode 100644
index 000000000..8502aaff4
--- /dev/null
+++ b/web/api/v1/list/cron/index.php
@@ -0,0 +1,59 @@
+ $value) {
+ ++$i;
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = __('UNSUSPEND_CRON_CONFIRMATION', $key);
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend';
+ $data[$key]['suspend_conf'] = __('SUSPEND_CRON_CONFIRMATION', $key);
+ }
+
+ $data[$key]['delete_conf'] = __('DELETE_CRON_CONFIRMATION', $key);
+
+ if ( $i == 1) {
+ $total_amount = __('1 cron job');
+ } else {
+ $total_amount = __('%s cron jobs', $i);
+ }
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->cron_reports = $panel[$user]['CRON_REPORTS'];
+$object->cron_fav = $_SESSION['favourites']['CRON'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/db/index.php b/web/api/v1/list/db/index.php
new file mode 100644
index 000000000..2ad5c4b50
--- /dev/null
+++ b/web/api/v1/list/db/index.php
@@ -0,0 +1,83 @@
+ $value) {
+ ++$i;
+
+ $data[$key]['U_DISK_PERCENT'] = get_percentage($data[$key]['U_DISK'],$panel[$user]['DISK_QUOTA']);
+
+ if ( $i == 1) {
+ $total_amount = __('1 database');
+ } else {
+ $total_amount = __('%s databases',$i);
+ }
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = __('UNSUSPEND_DATABASE_CONFIRMATION', $key);
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend';
+ $data[$key]['suspend_conf'] = __('SUSPEND_DATABASE_CONFIRMATION', $key);
+ }
+
+ if ($data[$key]['TYPE'] == 'mysql'){
+ $mysql = 1;
+
+ $db_myadmin_link = "http://".$http_host."/phpmyadmin/";
+ if (!empty($_SESSION['DB_PMA_URL']))
+ $db_myadmin_link = $_SESSION['DB_PMA_URL'];
+ }
+ if ($data[$key]['TYPE'] == 'pgsql'){
+ $pgsql = 1;
+ $db_pgadmin_link = "http://".$http_host."/phppgadmin/";
+ if (!empty($_SESSION['DB_PGA_URL']))
+ $db_pgadmin_link = $_SESSION['DB_PGA_URL'];
+ }
+
+ if ($data[$key]['HOST'] != 'localhost' ) $http_host = $data[$key]['HOST'];
+ if ($data[$key]['TYPE'] == 'mysql') $db_admin = "phpMyAdmin";
+ if ($data[$key]['TYPE'] == 'mysql') $db_admin_link = "http://".$http_host."/phpmyadmin/";
+ if (($data[$key]['TYPE'] == 'mysql') && (!empty($_SESSION['DB_PMA_URL']))) $db_admin_link = $_SESSION['DB_PMA_URL'];
+ if ($data[$key]['TYPE'] == 'pgsql') $db_admin = "phpPgAdmin";
+ if ($data[$key]['TYPE'] == 'pgsql') $db_admin_link = "http://".$http_host."/phppgadmin/";
+ if (($data[$key]['TYPE'] == 'pgsql') && (!empty($_SESSION['DB_PGA_URL']))) $db_admin_link = $_SESSION['DB_PGA_URL'];
+
+ $data[$key]['delete_conf'] = __('DELETE_DATABASE_CONFIRMATION', $key);
+}
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->db_admin = $db_admin;
+$object->db_admin_link = $db_admin_link;
+$object->db_myadmin_link = $db_myadmin_link;
+$object->db_pgadmin_link = $db_pgadmin_link;
+$object->totalAmount = $total_amount;
+$object->databases = $databases;
+$object->dbFav = $_SESSION['favourites']['DB'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/directory/index.php b/web/api/v1/list/directory/index.php
new file mode 100644
index 000000000..12919b140
--- /dev/null
+++ b/web/api/v1/list/directory/index.php
@@ -0,0 +1,35 @@
+ 0 ) {
+ header("Location: /error/");
+ exit;
+ }
+ $panel = json_decode(implode('', $output), true);
+}
+
+$path_a = !empty($_REQUEST['dir_a']) ? htmlentities($_REQUEST['dir_a']) : '';
+$path_b = !empty($_REQUEST['dir_b']) ? htmlentities($_REQUEST['dir_b']) : '';
+$GLOBAL_JS = '';
+$GLOBAL_JS .= '';
+$GLOBAL_JS .= '';
+
+
+// Footer
+include($_SERVER['DOCUMENT_ROOT'].'/templates/file_manager/main.php');
\ No newline at end of file
diff --git a/web/api/v1/list/directory/preview/index.php b/web/api/v1/list/directory/preview/index.php
new file mode 100644
index 000000000..737e19db3
--- /dev/null
+++ b/web/api/v1/list/directory/preview/index.php
@@ -0,0 +1,35 @@
+ 0 ) {
+ header("Location: /error/");
+ exit;
+ }
+ $panel = json_decode(implode('', $output), true);
+}
+
+$path_a = !empty($_REQUEST['dir_a']) ? $_REQUEST['dir_a'] : '';
+$path_b = !empty($_REQUEST['dir_b']) ? $_REQUEST['dir_b'] : '';
+$GLOBAL_JS = '';
+$GLOBAL_JS .= '';
+$GLOBAL_JS .= '';
+
+
+// Footer
+include($_SERVER['DOCUMENT_ROOT'].'/templates/file_manager/main.php');
\ No newline at end of file
diff --git a/web/api/v1/list/dns/index.php b/web/api/v1/list/dns/index.php
new file mode 100644
index 000000000..766258aab
--- /dev/null
+++ b/web/api/v1/list/dns/index.php
@@ -0,0 +1,72 @@
+ $value) {
+ ++$i;
+
+ if ( $i == 1) {
+ $total_amount = __('1 domain');
+ } else {
+ $total_amount = __('%s domains', $i);
+ }
+
+ if (!empty($_GET['domain'])){
+ if ( $i == 1) {
+ $total_amount = __('1 record');
+ } else {
+ $total_amount = __('%s records',$i);
+ }
+
+ $data[$key]['delete_conf'] = __('DELETE_RECORD_CONFIRMATION', $data[$key]['RECORD']);
+ } else {
+ $data[$key]['delete_conf'] = __('DELETE_DOMAIN_CONFIRMATION', $key);
+ }
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = !empty($_GET['domain']) ? __('UNSUSPEND_RECORD_CONFIRMATION', $data[$key]['RECORD']) : __('UNSUSPEND_DOMAIN_CONFIRMATION', $key);
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend';
+ $data[$key]['suspend_conf'] = !empty($_GET['domain']) ? __('SUSPEND_RECORD_CONFIRMATION', $data[$key]['RECORD']) : __('SUSPEND_DOMAIN_CONFIRMATION', $key);
+ }
+
+ $data[$key]['RECORDS_I18N'] = __('list records', $data[$key]['RECORDS']);
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->dnsFav = $_SESSION['favourites']['DNS'];
+$object->dnsRecordsFav = $_SESSION['favourites']['DNS_REC'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/favorites/index.php b/web/api/v1/list/favorites/index.php
new file mode 100644
index 000000000..0ddc4c0b0
--- /dev/null
+++ b/web/api/v1/list/favorites/index.php
@@ -0,0 +1,24 @@
+ Favorites: ';
+
+ // Data
+ exec (VESTA_CMD."v-list-user-favourites ".$_SESSION['user']." json", $output, $return_var);
+
+
+// print_r(implode('', $output));
+// $json = '{ "Favourites": { "USER": "", "WEB": "bulletfarm.com", "DNS": "", "MAIL": "", "DB": "", "CRON": "", "BACKUP": "", "IP": "", "PACKAGE": "", "FIREWALL": ""}}';
+// $data = json_decode($json, true);
+
+
+ $data = json_decode(implode('', $output).'}', true);
+ $data = array_reverse($data,true);
+
+ print_r($data);
+// $data = array_reverse($data,true);
+
+// $data = json_decode(implode('', $output), true);
+
+?>
\ No newline at end of file
diff --git a/web/api/v1/list/firewall/banlist/index.php b/web/api/v1/list/firewall/banlist/index.php
new file mode 100644
index 000000000..54136bb21
--- /dev/null
+++ b/web/api/v1/list/firewall/banlist/index.php
@@ -0,0 +1,56 @@
+ $value) {
+ ++$i;
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = 'UNSUSPEND_RULE_CONFIRMATION' ;
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend' ;
+ $data[$key]['suspend_conf'] = 'SUSPEND_RULE_CONFIRMATION' ;
+ }
+
+ $data[$key]['delete_url'] = '/delete/firewall/banlist/?ip='.$data[$key]['ip'].'&chain='.$data[$key]['CHAIN'].'&token='.$_SESSION['token'];
+ $data[$key]['delete_confirmation'] = __('DELETE_IP_CONFIRMATION',$key);
+
+ if ( $i == 1) {
+ $total_amount = __('1 rule');
+ } else {
+ $total_amount = __('%s rules',$i);
+ }
+}
+
+if ($i == 0) {
+ $total_amount = __('There are no currently banned IP');
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$result = array(
+ 'data' => $data,
+ 'total_amount' => $total_amount
+);
+
+echo json_encode($result);
diff --git a/web/api/v1/list/firewall/index.php b/web/api/v1/list/firewall/index.php
new file mode 100644
index 000000000..35d6ecbdf
--- /dev/null
+++ b/web/api/v1/list/firewall/index.php
@@ -0,0 +1,58 @@
+ $value) {
+ ++$i;
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = __('UNSUSPEND_RULE_CONFIRMATION', $key);
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend';
+ $data[$key]['suspend_conf'] = __('SUSPEND_RULE_CONFIRMATION', $key);
+ }
+
+ $data[$key]['delete_conf'] = __('DELETE_RULE_CONFIRMATION', $key);
+
+ if ( $i == 1) {
+ $total_amount = __('1 rule');
+ } else {
+ $total_amount = __('%s rules', $i);
+ }
+}
+
+// Render page
+// render_page($user, $TAB, 'list_firewall');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->firewallExtension = !empty($_SESSION['FIREWALL_EXTENSION']);
+$object->firewallFav = $_SESSION['favourites']['FIREWALL'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/index.php b/web/api/v1/list/index.php
new file mode 100644
index 000000000..b0d34a3f8
--- /dev/null
+++ b/web/api/v1/list/index.php
@@ -0,0 +1,8 @@
+
diff --git a/web/api/v1/list/ip/index.php b/web/api/v1/list/ip/index.php
new file mode 100644
index 000000000..899e57e5b
--- /dev/null
+++ b/web/api/v1/list/ip/index.php
@@ -0,0 +1,47 @@
+ $value) {
+ ++$i;
+ if ( $i == 1) {
+ $total_amount = __('1 IP address');
+ } else {
+ $total_amount = __('%s IP addresses',$i);
+ }
+
+ $data[$key]['delete_conf'] = __('DELETE_IP_CONFIRMATION', $key);
+}
+
+// Render page
+// render_page($user, $TAB, 'list_ip');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->ipFav = $_SESSION['favourites']['IP'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/log/index.php b/web/api/v1/list/log/index.php
new file mode 100644
index 000000000..4eb96b0fd
--- /dev/null
+++ b/web/api/v1/list/log/index.php
@@ -0,0 +1,36 @@
+ $value) {
+ ++$i;
+
+ if ( $i == 1) {
+ $total_amount = __('one log record');
+ } else {
+ $total_amount = __('%s log records',$i);
+ }
+}
+
+// Render page
+// render_page($user, $TAB, 'list_log');
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/mail/index.php b/web/api/v1/list/mail/index.php
new file mode 100644
index 000000000..11dfbbaec
--- /dev/null
+++ b/web/api/v1/list/mail/index.php
@@ -0,0 +1,92 @@
+
+error_reporting(NULL);
+$TAB = 'MAIL';
+header("Content-Type: application/json");
+
+// Main include
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+// Data & Render page
+if (empty($_GET['domain'])){
+ exec (VESTA_CMD."v-list-mail-domains $user json", $output, $return_var);
+ $data = json_decode(implode('', $output), true);
+ $data = array_reverse($data, true);
+ unset($output);
+
+ $favorites = $_SESSION['favourites']['MAIL'];
+
+ // render_page($user, $TAB, 'list_mail');
+} else {
+ exec (VESTA_CMD."v-list-mail-accounts ".$user." ".escapeshellarg($_GET['domain'])." json", $output, $return_var);
+ $data = json_decode(implode('', $output), true);
+ $data = array_reverse($data, true);
+ unset($output);
+
+ $favorites = $_SESSION['favourites']['MAIL_ACC'];
+
+ // render_page($user, $TAB, 'list_mail_acc');
+}
+
+$uname_arr=posix_uname();
+$hostname=$uname_arr['nodename'];
+
+top_panel(empty($_SESSION['look']) ? $_SESSION['user'] : $_SESSION['look'], $TAB);
+
+foreach ($data as $key => $value) {
+ ++$i;
+
+ if (empty($_GET['domain'])){
+ $data[$key]['U_DISK_PERCENT'] = get_percentage($data[$key]['U_DISK'], $data[$key]['QUOTA']);
+ } else {
+ $data[$key]['U_DISK_PERCENT'] = get_percentage($data[$key]['U_DISK'], $panel[$user]['DISK_QUOTA']);
+ }
+
+ list($http_host, $port) = explode(':', $_SERVER["HTTP_HOST"].":");
+ $webmail = "/webmail/";
+ if (!empty($_SESSION['MAIL_URL'])) $webmail = $_SESSION['MAIL_URL'];
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $data[$key]['status'] = 'suspended';
+ $data[$key]['suspend_action'] = 'unsuspend' ;
+ $data[$key]['suspend_conf'] = __('UNSUSPEND_DOMAIN_CONFIRMATION', $key);
+ } else {
+ $data[$key]['status'] = 'active';
+ $data[$key]['suspend_action'] = 'suspend';
+ $data[$key]['suspend_conf'] = __('SUSPEND_DOMAIN_CONFIRMATION', $key);
+ }
+
+ if (empty($data[$key]['CATCHALL'])) {
+ $data[$key]['CATCHALL'] = '/dev/null';
+ }
+
+ if (empty($_GET['domain'])){
+ $total_amount = $i == 1 ? __('1 domain') : __('%s domains', $i);
+ } else {
+ $total_amount = $i == 1 ? __('1 mail account') : __('%s mail account', $i);
+ }
+
+ $data[$key]['list_accounts_button'] = __('list accounts', $data[$key]['ACCOUNTS']);
+ $data[$key]['delete_conf'] = __('DELETE_DOMAIN_CONFIRMATION', $key);
+}
+
+if (count($data) == 0) {
+ if (empty($_GET['domain'])){
+ $total_amount = __('%s domains', 0);
+ } else {
+ $total_amount = __('%s mail account', 0);
+ }
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->webmail = $webmail;
+$object->hostname = $hostname;
+$object->totalAmount = $total_amount;
+$object->mailFav = $favorites;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/notifications/index.php b/web/api/v1/list/notifications/index.php
new file mode 100644
index 000000000..1190c5ce6
--- /dev/null
+++ b/web/api/v1/list/notifications/index.php
@@ -0,0 +1,25 @@
+ $note){
+ $note['ID'] = $key;
+ $notifications[$key] = $note;
+}
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$result = array(
+ 'result' => empty($notifications) ? [] : $notifications
+);
+
+echo json_encode($result);
diff --git a/web/api/v1/list/package/index.php b/web/api/v1/list/package/index.php
new file mode 100644
index 000000000..2fe581232
--- /dev/null
+++ b/web/api/v1/list/package/index.php
@@ -0,0 +1,45 @@
+ $value) {
+ ++$i;
+ if ( $i == 1) {
+ $total_amount = __('1 package');
+ } else {
+ $total_amount = __('%s packages',$i);
+ }
+
+ $data[$key]['delete_conf'] = __('DELETE_PACKAGE_CONFIRMATION', $key);
+}
+
+// Render page
+// render_page($user, $TAB, 'list_packages');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->totalAmount = $total_amount;
+$object->packagesFav = $_SESSION['favourites']['PACKAGE'];
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/rrd/image.php b/web/api/v1/list/rrd/image.php
new file mode 100644
index 000000000..600b11134
--- /dev/null
+++ b/web/api/v1/list/rrd/image.php
@@ -0,0 +1,13 @@
+
diff --git a/web/api/v1/list/rrd/index.php b/web/api/v1/list/rrd/index.php
new file mode 100644
index 000000000..2ba6b4dc5
--- /dev/null
+++ b/web/api/v1/list/rrd/index.php
@@ -0,0 +1,32 @@
+data = $data;
+$object->user = $user;
+$object->panel = $panel;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/server/index.php b/web/api/v1/list/server/index.php
new file mode 100644
index 000000000..dd2cabace
--- /dev/null
+++ b/web/api/v1/list/server/index.php
@@ -0,0 +1,108 @@
+ $value) {
+ if ($data[$key]['STATE'] == 'running') {
+ $data[$key]['action_url'] = '/stop/service/?srv='.$key;
+ } else {
+ $data[$key]['action_url'] = '/start/service/?srv='.$key;
+ }
+
+ $data[$key]['SYSTEM'] = __($data[$key]['SYSTEM']);
+ $data[$key]['RTIME'] = humanize_time($data[$key]['RTIME']);
+
+ $cpu = $data[$key]['CPU'] / 10;
+ $data[$key]['CPU'] = number_format($cpu, 1);
+ if ($cpu == '0.0') $data[$key]['CPU'] = 0;
+}
+
+foreach ($sys as $key => $value) {
+ $sys[$key]['UPTIME'] = humanize_time($sys[$key]['UPTIME']);
+}
+
+// Render page
+// render_page($user, $TAB, 'list_services');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->sys = $sys;
+$object->service_log = $service_log;
+$object->panel = $panel;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/stats/index.php b/web/api/v1/list/stats/index.php
new file mode 100644
index 000000000..480757720
--- /dev/null
+++ b/web/api/v1/list/stats/index.php
@@ -0,0 +1,61 @@
+ $value) {
+ ++$i;
+
+ $data[$key]['U_BANDWIDTH_PERCENT'] = get_percentage($data[$key]['U_BANDWIDTH'],$data[$key]['BANDWIDTH']);
+ $data[$key]['U_DISK_PERCENT'] = get_percentage($data[$key]['U_DISK'],$data[$key]['DISK_QUOTA']);
+
+ if ( $i == 1) {
+ $total_amount = __('1 month');
+ } else {
+ $total_amount = __('%s months',$i);
+ }
+}
+
+// Render page
+// render_page($user, $TAB, 'list_stats');
+
+// Back uri
+$_SESSION['back'] = $_SERVER['REQUEST_URI'];
+
+$object = (object)[];
+$object->data = $data;
+$object->user = $user;
+$object->panel = $panel;
+$object->users = $users;
+$object->totalAmount = $total_amount;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/updates/index.php b/web/api/v1/list/updates/index.php
new file mode 100644
index 000000000..7a4c7bdc3
--- /dev/null
+++ b/web/api/v1/list/updates/index.php
@@ -0,0 +1,36 @@
+data = $data;
+$object->user = $user;
+$object->autoUpdate = $autoupdate;
+$object->panel = $panel;
+
+print json_encode($object);
\ No newline at end of file
diff --git a/web/api/v1/list/user/index.php b/web/api/v1/list/user/index.php
new file mode 100644
index 000000000..01a1fe6c0
--- /dev/null
+++ b/web/api/v1/list/user/index.php
@@ -0,0 +1,77 @@
+ $value) {
+ ++$i;
+ if ( $i == 1) {
+ $total_amount = __('1 account');
+ } else {
+ $total_amount = __('%s accounts', $i);
+ }
+
+ $data[$key]['U_BANDWIDTH_PERCENT'] = get_percentage($data[$key]['U_BANDWIDTH'],$data[$key]['BANDWIDTH']);
+ $data[$key]['U_DISK_PERCENT'] = get_percentage($data[$key]['U_DISK'],$data[$key]['DISK_QUOTA']);
+
+ $data[$key]['U_BANDWIDTH_MEASURE'] = humanize_usage_measure($data[$key]['U_BANDWIDTH']);
+ $data[$key]['U_BANDWIDTH'] = humanize_usage_size($data[$key]['U_BANDWIDTH']);
+
+ $data[$key]['U_DISK_MEASURE'] = humanize_usage_measure($data[$key]['U_DISK']);
+ $data[$key]['U_DISK'] = humanize_usage_size($data[$key]['U_DISK']);
+
+ $data[$key]['U_DISK_WEB_MEASURE'] = humanize_usage_measure($data[$key]['U_DISK_WEB']);
+ $data[$key]['U_DISK_WEB'] = humanize_usage_size($data[$key]['U_DISK_WEB']);
+
+ $data[$key]['U_DISK_DB_MEASURE'] = humanize_usage_measure($data[$key]['U_DISK_DB']);
+ $data[$key]['U_DISK_DB'] = humanize_usage_size($data[$key]['U_DISK_DB']);
+
+ $data[$key]['U_DISK_MAIL_MEASURE'] = humanize_usage_measure($data[$key]['U_DISK_MAIL']);
+ $data[$key]['U_DISK_MAIL'] = humanize_usage_size($data[$key]['U_DISK_MAIL']);
+
+ $data[$key]['U_DISK_DIRS_MEASURE'] = humanize_usage_measure($data[$key]['U_DISK_DIRS']);
+ $data[$key]['U_DISK_DIRS'] = humanize_usage_size($data[$key]['U_DISK_DIRS']);
+
+ if ($data[$key]['SUSPENDED'] == 'yes') {
+ $spnd_action = 'unsuspend' ;
+ $spnd_confirmation = 'UNSUSPEND_USER_CONFIRMATION';
+ $data[$key]['spnd_action'] = __($spnd_action);
+ $data[$key]['spnd_conf'] = __($spnd_confirmation, $key);
+ } else {
+ $spnd_action = 'suspend' ;
+ $spnd_confirmation = 'SUSPEND_USER_CONFIRMATION';
+ $data[$key]['spnd_action'] = __($spnd_action);
+ $data[$key]['spnd_conf'] = __($spnd_confirmation, $key);
+ }
+
+ $data[$key]['isChecked'] = false;
+ $data[$key]['delete_conf'] = __('DELETE_USER_CONFIRMATION', $key);
+}
+
+$result = array(
+ 'data' => $data,
+ 'user' => $user,
+ 'panel' => $panel,
+ 'token' => $_SESSION['token'],
+ 'totalAmount' => $total_amount,
+ 'userFav' => $_SESSION['favourites']['USER'],
+);
+
+echo json_encode($result);
diff --git a/web/api/v1/list/web-log/index.php b/web/api/v1/list/web-log/index.php
new file mode 100644
index 000000000..055717a69
--- /dev/null
+++ b/web/api/v1/list/web-log/index.php
@@ -0,0 +1,37 @@
+ $content,
+ 'prefix' => __('Last 70 lines of %s.%s.log', htmlentities($_GET['domain']), htmlentities($_GET['type']))
+ )
+);
+
+// if ($return_var == 0 ) {
+// foreach($output as $file) {
+// echo htmlentities($file) . "\n";
+// }
+// }
+// echo " \n