Merge pull request #2067 from serghey-rodin/react-integration

React js integration with vesta control panel.
This commit is contained in:
Serghey Rodin 2021-10-14 14:49:30 +03:00 committed by GitHub
commit 67f2ad0c99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
338 changed files with 43501 additions and 0 deletions

68
web/js/react/README.md Executable file
View file

@ -0,0 +1,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"baseUrl": "."
},
"include": ["src"]
}

14969
web/js/react/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

51
web/js/react/package.json Normal file
View file

@ -0,0 +1,51 @@
{
"name": "react-control-panel",
"author": "Alexander Osinskii",
"email": "alexanderosinskii@gmail.com",
"version": "1.0.0",
"private": false,
"dependencies": {
"@fortawesome/fontawesome-free": "^5.10.1",
"@fortawesome/fontawesome-svg-core": "^1.2.21",
"@fortawesome/free-brands-svg-icons": "^5.10.1",
"@fortawesome/free-solid-svg-icons": "^5.10.1",
"@fortawesome/react-fontawesome": "^0.1.4",
"axios": "^0.21.4",
"bootstrap": "^4.3.1",
"classname": "0.0.0",
"dayjs": "^1.10.7",
"jquery": "^3.5.1",
"node-sass": "^4.14.1",
"popper.js": "^1.15.0",
"prop-types": "^15.7.2",
"qs": "^6.9.4",
"react": "^16.10.2",
"react-codemirror": "^1.0.0",
"react-dom": "^16.10.2",
"react-helmet": "^6.1.0",
"react-html-parser": "^2.0.2",
"react-redux": "^7.2.1",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.1",
"react-toastify": "^5.3.1",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"validate.js": "^0.13.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

BIN
web/js/react/public/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

26
web/js/react/public/index.html Executable file
View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/node_modules/bootstrap/dist/js/bootstrap.min.js">
<title>Vesta</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -0,0 +1,79 @@
import axios from "axios";
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const webApiUri = '/list/backup/backup.php';
const scheduleBackupUri = '/schedule/backup/';
const backupDetailsUri = '/list/backup/backup.php';
const backupExclusionsUri = '/api/list/backup/exclusions/index.php';
const backupExclusionsInfoUri = '/api/edit/backup/exclusions/index.php';
const backupRestoreSettingUri = '/api/schedule/restore/index.php';
const bulkRestoreUri = '/api/bulk/restore/index.php';
export const getBackupList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, backups) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
backups.forEach(backup => {
formData.append("backup[]", backup);
formData.append("delete_url", `/delete/backup/?backup=${backup}&token=${token}`);
});
return axios.post(BASE_URL + '/bulk/backup/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const scheduleBackup = () => {
return axios.get(BASE_URL + scheduleBackupUri);
}
export const getBackupDetails = backup => {
return axios.get(BASE_URL + `${backupDetailsUri}?backup=${backup}`);
}
export const restoreBackupSetting = params => {
return axios.get(BASE_URL + `${backupRestoreSettingUri}${params}`);
}
export const bulkRestore = (action, selection, backup) => {
const formData = new FormData();
formData.append("token", token);
formData.append("action", action);
formData.append("backup", backup);
selection.forEach(udir => {
formData.append("udir[]", udir);
});
return axios.post(BASE_URL + `${bulkRestoreUri}`, formData);
}
export const getBackupExclusions = () => {
return axios.get(BASE_URL + `${backupExclusionsUri}`);
}
export const getBackupExclusionsInfo = () => {
return axios.get(BASE_URL + `${backupExclusionsInfoUri}`);
}
export const updateBackupExclusions = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + backupExclusionsInfoUri, formDataObject, {
params: {
token
}
});
}

View file

@ -0,0 +1,64 @@
import axios from "axios";
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const webApiUri = '/list/cron/cron.php';
const cronAddApiUri = '/api/add/cron/index.php';
const jobInfoUri = '/api/edit/cron/index.php';
const updateCronJobUri = '/api/edit/cron/index.php';
export const getCronList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, domainNameSystems) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
domainNameSystems.forEach(domainNameSystem => {
formData.append("job[]", domainNameSystem);
formData.append("suspend_url", `/suspend/cron/?job=${domainNameSystem}&token=${token}`);
formData.append("delete_url", `/delete/cron/?job=${domainNameSystem}&token=${token}`);
});
return axios.post(BASE_URL + '/bulk/cron/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addCronJob = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + cronAddApiUri, formDataObject);
}
export const getCronJobInfo = job => {
return axios.get(BASE_URL + jobInfoUri, {
params: {
job,
token
}
});
}
export const updateCronJob = (data, job) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateCronJobUri, formDataObject, {
params: {
job,
token
}
});
}

View file

@ -0,0 +1,106 @@
import axios from "axios";
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const webApiUri = '/list/db/db.php';
const addDbApiUri = '/api/add/db/index.php';
const optionalDbInfoUri = '/api/add/db/index.php';
const dbInfoUri = '/api/edit/db/index.php';
const updateDatabaseUri = '/api/edit/db/index.php';
export const getDatabaseList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, domainNameSystems) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
domainNameSystems.forEach(domainNameSystem => {
formData.append("database[]", domainNameSystem);
});
return axios.post(BASE_URL + '/bulk/db/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const getDbOptionalInfo = () => {
return axios.get(BASE_URL + optionalDbInfoUri);
}
export const addDatabase = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addDbApiUri, formDataObject);
}
export const dbCharsets = [
'big5',
'dec8',
'cp850',
'hp8',
'koi8r',
'latin1',
'latin2',
'swe7',
'ascii',
'ujis',
'sjis',
'hebrew',
'tis620',
'euckr',
'koi8u',
'gb2312',
'greek',
'cp1250',
'gbk',
'latin5',
'armscii8',
'utf8',
'ucs2',
'cp866',
'keybcs2',
'macce',
'macroman',
'cp852',
'latin7',
'cp1251',
'cp1256',
'cp1257',
'binary',
'geostd8',
'cp932',
'eucjpms'
];
export const getDatabaseInfo = database => {
return axios.get(BASE_URL + dbInfoUri, {
params: {
database,
token
}
});
}
export const updateDatabase = (data, database) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateDatabaseUri, formDataObject, {
params: {
database,
token
}
});
}

View file

@ -0,0 +1,80 @@
import axios from "axios";
const updateDNSUri = '/api/edit/dns/index.php';
const addDnsApiUri = '/api/add/dns/index.php';
const dNSInfoUri = '/api/edit/dns/index.php';
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const dnsApiUri = '/list/dns/dns.php';
export const getDnsList = () => {
return axios.get(BASE_URL + dnsApiUri);
}
export const getDNSRecordsList = domain => {
return axios.get(`${BASE_URL}${dnsApiUri}?domain=${domain}`);
}
export const getDNSRecordInfo = (domain, recordId) => {
return axios.get(`${BASE_URL}${updateDNSUri}?domain=${domain}&record_id=${recordId}`);
}
export const bulkAction = (action, domainNameSystems) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
domainNameSystems.forEach(domainNameSystem => {
formData.append("domain[]", domainNameSystem);
});
return axios.post(BASE_URL + '/bulk/dns/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addDomainNameSystem = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addDnsApiUri, formDataObject);
}
export const addDomainNameSystemRecord = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addDnsApiUri, formDataObject);
}
export const getDNSInfo = domain => {
return axios.get(BASE_URL + dNSInfoUri, {
params: {
domain,
token
}
});
}
export const updateDNS = (data, domain) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateDNSUri, formDataObject, {
params: {
domain,
token
}
});
}

View file

@ -0,0 +1,23 @@
import axios from "axios";
let addFavoriteUri = '/add/favorite/index.php';
let deleteFavoriteUri = '/delete/favorite/index.php';
let BASE_URL = window.location.origin;
export const addFavorite = (unitId, section) => {
return axios.get(BASE_URL + addFavoriteUri, {
params: {
'v_unit_id': unitId,
'v_section': section
}
});
}
export const deleteFavorite = (unitId, section) => {
return axios.get(BASE_URL + deleteFavoriteUri, {
params: {
'v_unit_id': unitId,
'v_section': section
}
});
}

View file

@ -0,0 +1,96 @@
import axios from 'axios';
const BASE_URL = window.location.origin;
const token = localStorage.getItem("token");
const usersUri = '/list/firewall/firewall.php';
const addFirewallUri = '/api/add/firewall/index.php';
const firewallInfoUri = '/api/edit/firewall/index.php';
const updateFirewallUri = '/api/edit/firewall/index.php';
const addBanIpsUri = '/api/add/firewall/banlist/index.php';
const banListUri = '/list/firewall/banlist/banlist.php';
export const getFirewallList = () => {
return axios.get(BASE_URL + usersUri);
}
export const getBanList = () => {
return axios.get(BASE_URL + banListUri);
}
export const bulkAction = (action, firewalls) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
firewalls.forEach(firewall => {
formData.append("rule[]", firewall);
});
return axios.post(BASE_URL + '/bulk/firewall/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const getBanIps = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.get(BASE_URL + addBanIpsUri, {
params: {
token
}
});
}
export const addBanIp = (data) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.get(BASE_URL + addBanIpsUri, {
params: {
token
}
});
}
export const addFirewall = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addFirewallUri, formDataObject);
}
export const getFirewallInfo = rule => {
return axios.get(BASE_URL + firewallInfoUri, {
params: {
rule,
token
}
});
}
export const updateFirewall = (data, rule) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateFirewallUri, formDataObject, {
params: {
rule,
token
}
});
}

View file

@ -0,0 +1,145 @@
const { i18n } = window.GLOBAL.App;
export const minutesRunCommandsOptions = [
{ name: i18n['every minute'] ?? 'every minute', value: '*' },
{ name: i18n['every two minutes'] ?? 'every two minutes', value: '*/2' },
{ name: `${i18n.every || 'every'} 5`, value: '*/5' },
{ name: `${i18n.every || 'every'} 10`, value: '*/10' },
{ name: `${i18n.every || 'every'} 15`, value: '*/15' },
{ name: `${i18n.every || 'every'} 30`, value: '*/30' }
];
export const hoursRunCommandsOptions = [
{ name: i18n['every hour'] ?? 'every hour', value: '*' },
{ name: i18n['every two hours'] ?? 'every two hours', value: '*/2' },
{ name: `${i18n.every || 'every'} 6`, value: '*/6' },
{ name: `${i18n.every || 'every'} 12`, value: '*/12' }
];
export const daysRunCommandsOptions = [
{ name: i18n['every day'] ?? 'every day', value: '*' },
{ name: i18n['every odd day'] ?? 'every odd day', value: '1-31/2' },
{ name: i18n['every even day'] ?? 'every even day', value: '*/2' },
{ name: `${i18n.every || 'every'} 3`, value: '*/3' },
{ name: `${i18n.every || 'every'} 5`, value: '*/5' },
{ name: `${i18n.every || 'every'} 10`, value: '*/10' },
{ name: `${i18n.every || 'every'} 15`, value: '*/15' }
];
export const hoursOptions = [
{ name: '00', value: '0' },
{ name: '01', value: '1' },
{ name: '02', value: '2' },
{ name: '03', value: '3' },
{ name: '04', value: '4' },
{ name: '05', value: '5' },
{ name: '06', value: '6' },
{ name: '07', value: '7' },
{ name: '08', value: '8' },
{ name: '09', value: '9' },
{ name: '10', value: '10' },
{ name: '11', value: '11' },
{ name: '12', value: '12' },
{ name: '13', value: '13' },
{ name: '14', value: '14' },
{ name: '15', value: '15' },
{ name: '16', value: '16' },
{ name: '17', value: '17' },
{ name: '18', value: '18' },
{ name: '19', value: '19' },
{ name: '20', value: '20' },
{ name: '21', value: '21' },
{ name: '22', value: '22' },
{ name: '23', value: '23' }
];
export const hourlyMinutesOptions = [
{ name: '00', value: '0' },
{ name: '15', value: '15' },
{ name: '30', value: '30' },
{ name: '45', value: '45' }
];
export const dailyMinutesOptions = [
{ name: '00', value: '0' },
{ name: '01', value: '1' },
{ name: '02', value: '2' },
{ name: '05', value: '5' },
{ name: '10', value: '10' },
{ name: '15', value: '15' },
{ name: '20', value: '20' },
{ name: '25', value: '25' },
{ name: '30', value: '30' },
{ name: '35', value: '35' },
{ name: '40', value: '40' },
{ name: '45', value: '45' },
{ name: '50', value: '50' },
{ name: '55', value: '55' }
];
export const weeklyRunCommandOptions = [
{ name: i18n['every day'] ?? 'every day', value: '*' },
{ name: i18n['weekdays (5 days)'] ?? 'weekdays (5 days)', value: '1,2,3,4,5' },
{ name: i18n['weekend (2 days)'] ?? 'weekend (2 days)', value: '0,6' },
{ name: i18n.Monday ?? 'Monday', value: '1' },
{ name: i18n.Tuesday ?? 'Tuesday', value: '2' },
{ name: i18n.Wednesday ?? 'Wednesday', value: '3' },
{ name: i18n.Thursday ?? 'Thursday', value: '4' },
{ name: i18n.Friday ?? 'Friday', value: '5' },
{ name: i18n.Saturday ?? 'Saturday', value: '6' },
{ name: i18n.Sunday ?? 'Sunday', value: '0' }
];
export const monthlyRunCommandOptions = [
{ name: i18n['every month'] ?? 'every month', value: '*' },
{ name: i18n['every odd month'] ?? 'every odd month', value: '1-11/2' },
{ name: i18n['every even month'] ?? 'every even month', value: '*/2' },
{ name: `${i18n.every || 'every'} 3`, value: '*/3' },
{ name: `${i18n.every || 'every'} 6`, value: '*/6' },
{ name: i18n.Jan ?? 'Jan', value: '1' },
{ name: i18n.Feb ?? 'Feb', value: '2' },
{ name: i18n.Mar ?? 'Mar', value: '3' },
{ name: i18n.Apr ?? 'Apr', value: '4' },
{ name: i18n.May ?? 'May', value: '5' },
{ name: i18n.Jun ?? 'Jun', value: '6' },
{ name: i18n.Jul ?? 'Jul', value: '7' },
{ name: i18n.Aug ?? 'Aug', value: '8' },
{ name: i18n.Sep ?? 'Sep', value: '9' },
{ name: i18n.Oct ?? 'Oct', value: '10' },
{ name: i18n.Nov ?? 'Nov', value: '11' },
{ name: i18n.Dec ?? 'Dec', value: '12' }
];
export const dateOptions = [
{ name: '1', value: '1' },
{ name: '2', value: '2' },
{ name: '3', value: '3' },
{ name: '4', value: '4' },
{ name: '5', value: '5' },
{ name: '6', value: '6' },
{ name: '7', value: '7' },
{ name: '8', value: '8' },
{ name: '9', value: '9' },
{ name: '10', value: '10' },
{ name: '11', value: '11' },
{ name: '12', value: '12' },
{ name: '13', value: '13' },
{ name: '14', value: '14' },
{ name: '15', value: '15' },
{ name: '16', value: '16' },
{ name: '17', value: '17' },
{ name: '18', value: '18' },
{ name: '19', value: '19' },
{ name: '20', value: '20' },
{ name: '21', value: '21' },
{ name: '22', value: '22' },
{ name: '23', value: '23' },
{ name: '24', value: '24' },
{ name: '25', value: '25' },
{ name: '26', value: '26' },
{ name: '27', value: '27' },
{ name: '28', value: '28' },
{ name: '29', value: '29' },
{ name: '30', value: '30' },
{ name: '31', value: '31' }
];

View file

@ -0,0 +1,68 @@
import axios from "axios";
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const webApiUri = '/list/ip/ip.php';
const addIpApiUri = '/api/add/ip/index.php';
const additionalInfoUri = '/api/add/ip/index.php';
const ipInfoUri = '/api/edit/ip/index.php';
const updateIpUri = '/api/edit/ip/index.php';
export const getIpList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, internetProtocols) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
internetProtocols.forEach(internetProtocol => {
formData.append("ip[]", internetProtocol);
formData.append("delete_url", `/delete/ip/?ip=${internetProtocol}&token=${token}`);
});
return axios.post(BASE_URL + '/bulk/ip/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const getAdditionalInfo = () => {
return axios.get(BASE_URL + additionalInfoUri);
}
export const addInternetProtocol = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addIpApiUri, formDataObject);
}
export const getInternetProtocolInfo = ip => {
return axios.get(BASE_URL + ipInfoUri, {
params: {
ip,
token
}
});
}
export const updateInternetProtocol = (data, ip) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateIpUri, formDataObject, {
params: {
ip,
token
}
});
}

View file

@ -0,0 +1,46 @@
import axios from "axios";
const webApiUri = '/api/languages.php';
const BASE_URL = window.location.origin;
export const getLanguages = () => {
return axios.get(BASE_URL + webApiUri);
}
export const languagesMock = [
"ar",
"az",
"bg",
"bs",
"cn",
"cz",
"da",
"de",
"el",
"en",
"es",
"fa",
"fi",
"fr",
"hu",
"id",
"it",
"ja",
"ka",
"ko",
"nl",
"no",
"pl",
"pt-BR",
"pt",
"ro",
"ru",
"se",
"sr",
"th",
"tr",
"tw",
"ua",
"ur",
"vi"
];

View file

@ -0,0 +1,8 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const webApiUri = '/list/log/log.php';
export const getLogsList = () => {
return axios.get(BASE_URL + webApiUri);
}

View file

@ -0,0 +1,127 @@
import axios from "axios";
const token = localStorage.getItem("token");
const { i18n } = window.GLOBAL.App;
const BASE_URL = window.location.origin;
const webApiUri = '/list/mail/mail.php';
const addMailApiUri = '/api/add/mail/index.php';
const mailInfoUri = '/api/edit/mail/index.php';
const updateMailUri = '/api/edit/mail/index.php';
export const getMailList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const getMailAccountList = domain => {
return axios.get(`${BASE_URL}${webApiUri}?domain=${domain}`)
}
export const getMailAccountInfo = (domain, account) => {
return axios.get(`${BASE_URL}${mailInfoUri}?domain=${domain}&account=${account}`)
}
export const bulkAction = (action, domainNameSystems) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
domainNameSystems.forEach(domainNameSystem => {
formData.append("domain[]", domainNameSystem);
});
return axios.post(BASE_URL + '/bulk/mail/', formData);
};
export const bulkMailAccountAction = (action, domain, accounts = []) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
formData.append("domain", domain);
accounts.forEach(account => {
formData.append("account[]", account);
});
return axios.post(BASE_URL + '/bulk/mail/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addMail = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addMailApiUri, formDataObject);
}
export const addMailAccount = (data, domain) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(`${BASE_URL}${addMailApiUri}?domain=${domain}`, formDataObject);
}
export const editMailAccount = (data, domain, account) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(`${BASE_URL}${updateMailUri}?domain=${domain}&account=${account}`, formDataObject);
}
export const getMailInfo = domain => {
return axios.get(BASE_URL + mailInfoUri, {
params: {
domain,
token
}
});
}
export const updateMail = (data, domain) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateMailUri, formDataObject, {
params: {
domain,
token
}
});
}
export const mailInfoBlockSelectOptions = [
{
value: i18n['Use server hostname'],
type: 'hostname',
},
{
value: i18n['Use domain hostname'],
type: 'domain',
},
{
value: i18n['Use STARTTLS'],
type: 'starttls',
},
{
value: i18n['Use SSL / TLS'],
type: 'ssl',
},
{
value: i18n['No encryption'],
type: 'no_encryption',
}
];

View file

@ -0,0 +1,18 @@
import axios from "axios";
let BASE_URL = window.location.origin;
let getNotificationsUri = '/list/notifications/?ajax=1';
let deleteNotificationsUri = '/delete/notification';
export const getAppNotifications = () => {
return axios.get(BASE_URL + getNotificationsUri);
}
export const deleteNotification = id => {
return axios.get(BASE_URL + deleteNotificationsUri, {
params: {
'delete': 1,
'notification_id': id,
'token': localStorage.getItem("token")
}
});
}

View file

@ -0,0 +1,67 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const token = localStorage.getItem("token");
const webApiUri = '/list/package/package.php';
const additionalPackageInfoUri = '/api/add/package/index.php';
const addPackageUri = '/api/add/package/index.php';
const packageInfoUri = '/api/edit/package/index.php';
const updatePackageUri = '/api/edit/package/index.php';
export const getPackageList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, backups) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
backups.forEach(backup => {
formData.append("package[]", backup);
formData.append("delete_url", `/delete/package/?package=${backup}&token=${token}`);
});
return axios.post(BASE_URL + '/bulk/package/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addPackage = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addPackageUri, formDataObject);
}
export const getAdditionalPackageInfo = () => {
return axios.get(BASE_URL + additionalPackageInfoUri);
}
export const getPackageInfo = item => {
return axios.get(BASE_URL + packageInfoUri, {
params: {
package: item,
token
}
});
}
export const updatePackage = (data, item) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updatePackageUri, formDataObject, {
params: {
package: item
}
});
}

View file

@ -0,0 +1,12 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const webApiUri = '/list/rrd/rrd.php';
export const getRrdList = () => {
return axios.get(BASE_URL + webApiUri);
}
export function generateImagePath(period, type, rrd) {
return `/list/rrd/image.php?/rrd/${type}/${period}-${rrd}.png`;
}

View file

@ -0,0 +1,26 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const resetPasswordUri = '/api/reset/index.php';
export const resetPassword = (user = '', code = '', password = '', confirmPassword = '') => {
const formData = new FormData();
if (password) {
formData.append('password', password);
}
if (confirmPassword) {
formData.append('password_confirm', confirmPassword);
}
if (user) {
formData.append('user', user);
}
if (code) {
formData.append('code', code);
}
return axios.post(BASE_URL + resetPasswordUri, formData);
};

View file

@ -0,0 +1,12 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const webApiUri = '/search/search.php';
export const getSearchResultsList = term => {
return axios.get(BASE_URL + webApiUri + '?q=' + term);
}
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}

View file

@ -0,0 +1,72 @@
export const values = (select = {}) => {
return {
usersList: [
{ value: 'rebuild', name: select.rebuild },
{ value: 'rebuild web', name: select['rebuild web'] },
{ value: 'rebuild dns', name: select['rebuild dns'] },
{ value: 'rebuild mail', name: select['rebuild mail'] },
{ value: 'rebuild db', name: select['rebuild db'] },
{ value: 'rebuild cron', name: select['rebuild cron'] },
{ value: 'update counters', name: select['update counters'] },
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
webList: [
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
dnsList: [
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
mailList: [
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
dbList: [
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
cronList: [
{ value: 'add-cron-reports', name: select['turn on notifications'] },
{ value: 'delete-cron-reports', name: select['turn off notifications'] },
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
backupList: [
{ value: 'delete', name: select.delete }
],
packagesList: [
{ value: 'delete', name: select.delete }
],
internetProtocolsList: [
{ value: 'reread IP', name: select['reread IP'] },
{ value: 'delete', name: select.delete }
],
updatesList: [
{ value: 'update', name: select.update }
],
firewallList: [
{ value: 'suspend', name: select.suspend },
{ value: 'unsuspend', name: select.unsuspend },
{ value: 'delete', name: select.delete }
],
serverList: [
{ value: 'stop', name: select.stop },
{ value: 'start', name: select.start },
{ value: 'restart', name: select.restart }
],
backupDetailList: [
{ value: 'restore', name: select.restore }
],
banList: [
{ value: 'delete', name: select.delete }
]
}
};

View file

@ -0,0 +1,76 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const token = localStorage.getItem("token");
const webApiUri = '/list/server/server.php';
const serverAdditionalInfoUri = '/api/edit/server/index.php';
export const getServersList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, services) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
services.forEach(service => {
formData.append("service[]", service);
});
return axios.post(BASE_URL + '/api/bulk/service/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const getServerAdditionalInfo = () => {
return axios.get(BASE_URL + serverAdditionalInfoUri, {
params: {
token
}
});
}
export const updateService = (data, uri = '') => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + `/api/edit/server/${uri}/index.php`, formDataObject, {
params: {
token
}
});
}
export const getServiceInfo = service => {
return axios.get(`${BASE_URL}/api/edit/server/${service}/index.php`);
}
export const getServiceLogs = service => {
return axios.get(`${BASE_URL}/list/server/server.php?${service}`);
}
export const services = [
'apache2',
'clamd',
'cron',
'crond',
'exim',
'exim4',
'fail2ban',
'iptables',
'mariadb',
'mysqld',
'named',
'php-fpm',
'php5-fpm',
'proftpd',
'spamassassin',
'spamd',
'vsftpd',
];

View file

@ -0,0 +1,8 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const webApiUri = '/list/stats/stats.php';
export const getStatisticsList = user => {
return axios.get(BASE_URL + webApiUri + '?user=' + user);
}

View file

@ -0,0 +1,35 @@
import axios from "axios";
const deleteAutoUpdateUri = '/delete/cron/autoupdate/';
const addAutoUpdateUri = '/add/cron/autoupdate/';
const webApiUri = '/list/updates/updates.php';
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
export const getUpdatesList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, updates) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
updates.forEach(update => {
formData.append("pkg[]", update);
});
return axios.post(BASE_URL + '/bulk/vesta/', formData);
};
export const handleAction = uri => {
return axios.get(`${BASE_URL}${uri}?token=${token}`);
}
export const enableAutoUpdate = () => {
return axios.get(`${BASE_URL}${addAutoUpdateUri}?token=${token}`);
};
export const disableAutoUpdate = () => {
return axios.get(`${BASE_URL}${deleteAutoUpdateUri}?token=${token}`);
};

View file

@ -0,0 +1,8 @@
import axios from "axios";
const BASE_URL = window.location.origin;
const userNSApiUri = '/api/list-user-ns.php';
export const getUserNS = () => {
return axios.get(BASE_URL + userNSApiUri);
}

View file

@ -0,0 +1,65 @@
import axios from 'axios';
let token = localStorage.getItem('token');
const BASE_URL = window.location.origin;
const usersUri = '/list/user/user.php';
const addUsersUri = '/api/add/user/index.php';
const userInfoUri = '/api/edit/user/index.php';
const updateUserUri = '/api/edit/user/index.php';
export const getUsersList = () => {
return axios.get(BASE_URL + usersUri);
}
export const bulkAction = (action, selectedUsers) => {
const formData = new FormData();
formData.append("token", token);
formData.append("action", action);
selectedUsers.forEach(user => {
formData.append("user[]", user);
});
return axios.post(BASE_URL + '/bulk/user/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addUser = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
formDataObject.append("token", token);
formDataObject.append("ok", "Add");
return axios.post(BASE_URL + addUsersUri, formDataObject);
}
export const getUserInfo = username => {
return axios.get(BASE_URL + userInfoUri, {
params: {
user: username,
token
}
});
}
export const updateUser = (data, user) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateUserUri, formDataObject, {
params: {
user,
token
}
});
}

View file

@ -0,0 +1,67 @@
import axios from "axios";
const token = localStorage.getItem("token");
const BASE_URL = window.location.origin;
const addWebUri = '/api/add/web/index.php';
const webApiUri = '/list/web/web.php';
const webStatsUri = '/api/web-stats.php';
const domainInfoUri = '/api/edit/web/index.php';
const updateDomainUri = '/api/edit/web/index.php';
export const getWebList = () => {
return axios.get(BASE_URL + webApiUri);
}
export const bulkAction = (action, webDomains) => {
const formData = new FormData();
formData.append("action", action);
formData.append("token", token);
webDomains.forEach(webDomain => {
formData.append("domain[]", webDomain);
});
return axios.post(BASE_URL + '/bulk/web/', formData);
};
export const handleAction = uri => {
return axios.get(BASE_URL + uri);
}
export const addWeb = data => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + addWebUri, formDataObject);
}
export const getWebStats = () => {
return axios.get(BASE_URL + webStatsUri);
}
export const getDomainInfo = domain => {
return axios.get(BASE_URL + domainInfoUri, {
params: {
domain,
token
}
});
}
export const updateWebDomain = (data, domain) => {
let formDataObject = new FormData();
for (let key in data) {
formDataObject.append(key, data[key]);
}
return axios.post(BASE_URL + updateDomainUri, formDataObject, {
params: {
domain,
token
}
});
}

View file

@ -0,0 +1,7 @@
import axios from "axios";
const BASE_URL = window.location.origin;
export const getWebLogs = uri => {
return axios.get(BASE_URL + uri);
}

View file

@ -0,0 +1,119 @@
import axios from "axios";
const server = window.location.origin + "/file_manager/fm_api.php?";
export function validateAction(url) {
return axios.get(url);
}
export function cacheData(currentUser, history) {
if (localStorage.getItem("lastUser") === null || currentUser !== localStorage.getItem("lastUser")) {
localStorage.setItem("lastUser", currentUser);
localStorage.setItem("activeWindow", "left");
localStorage.setItem("leftListPath", window.GLOBAL.ROOT_DIR);
localStorage.setItem("rightListPath", window.GLOBAL.ROOT_DIR);
}
if (localStorage.getItem("activeWindow") === null || localStorage.getItem("leftListPath") === null || localStorage.getItem("rightListPath") === null) {
let path = history.location.search.substring(6).split('/');
localStorage.setItem("activeWindow", "left");
localStorage.setItem("leftListPath", path);
localStorage.setItem("rightListPath", window.GLOBAL.ROOT_DIR);
}
}
export function changeDirectoryOnLoading(server, list) {
return axios.get(`${server}dir=${encodePath(localStorage.getItem(list))}&action=cd`);
}
export function changeDirectory(server, path) {
return axios.get(`${server}dir=${encodePath(path)}&action=cd`);
}
export function getData(path) {
return axios.get(`${server}dir=%2F${path}&action=cd`);
}
export function checkExistingFileName(selectedFiles, activeWindow, leftListData, rightListData) {
let selectedFileNames = [];
let existingFileNames = [];
let newFiles = [];
for (let i = 0; i < selectedFiles.length; i++) {
selectedFileNames.push(selectedFiles[i]);
}
if (activeWindow === "left") {
for (let i = 0; i < selectedFileNames.length; i++) {
if (leftListData.map((item) => { return item.name }).includes(selectedFileNames[i].name)) {
existingFileNames.push(selectedFileNames[i]);
} else {
newFiles.push(selectedFileNames[i]);
}
}
} else {
for (let i = 0; i < selectedFileNames.length; i++) {
if (rightListData.map((item) => { return item.name }).includes(selectedFileNames[i].name)) {
existingFileNames.push(selectedFileNames[i]);
} else {
newFiles.push(selectedFileNames[i]);
}
}
}
return { existingFileNames, newFiles };
}
export function encodePath(path) {
let splitPath = path.split('/');
let encodedPath = splitPath.join('%2F');
return encodedPath;
}
export function activeWindowPath() {
if (localStorage.getItem("activeWindow") === "left") {
let currentPath = localStorage.getItem("leftListPath");
return currentPath;
} else if (localStorage.getItem("activeWindow") === "right") {
let currentPath = localStorage.getItem("rightListPath");
return currentPath;
}
}
export function deleteItems(url, path, selection) {
if (!selection.length) {
return false;
}
const promisesArray = selection.map(item =>
validateAction(`${url}item=${path}%2F${item}&dir=${path}&action=delete_files`)
.then(() => { })
);
return Promise.all(promisesArray);
}
export function moveItems(url, path, targetPath, selection) {
if (!selection.length) {
return false;
}
const promisesArray = selection.map(item =>
validateAction(`${url}item=${path}%2F${item}&target_name=${targetPath}&action=move_file`)
.then(() => { })
);
return Promise.all(promisesArray);
}
export function copyItems(url, path, targetPath, selection) {
if (!selection.length) {
return false;
}
const promisesArray = selection.map(item =>
validateAction(`${url}item=${path}%2F${item}&filename=${item}&dir=${path}&dir_target=${targetPath}&action=copy_file`)
.then(() => { })
);
return Promise.all(promisesArray);
}

View file

@ -0,0 +1,15 @@
import { ADD_CPANEL_FOCUSED_ELEMENT, REMOVE_CPANEL_FOCUSED_ELEMENT } from './controlPanelContentTypes';
export const addControlPanelContentFocusedElement = value => {
return {
type: ADD_CPANEL_FOCUSED_ELEMENT,
value
};
};
export const removeControlPanelContentFocusedElement = () => {
return {
type: REMOVE_CPANEL_FOCUSED_ELEMENT,
value: ''
};
};

View file

@ -0,0 +1,2 @@
export const ADD_CPANEL_FOCUSED_ELEMENT = 'ADD_CPANEL_FOCUSED_ELEMENT';
export const REMOVE_CPANEL_FOCUSED_ELEMENT = 'REMOVE_CPANEL_FOCUSED_ELEMENT';

View file

@ -0,0 +1,29 @@
import { ADD_FOCUSED_ELEMENT, ADD_ACTIVE_ELEMENT, REMOVE_FOCUSED_ELEMENT, REMOVE_ACTIVE_ELEMENT } from './mainNavigationTypes';
export const addFocusedElement = value => {
return {
type: ADD_FOCUSED_ELEMENT,
value
};
};
export const removeFocusedElement = () => {
return {
type: REMOVE_FOCUSED_ELEMENT,
value: ''
};
};
export const addActiveElement = value => {
return {
type: ADD_ACTIVE_ELEMENT,
value
};
};
export const removeActiveElement = () => {
return {
type: REMOVE_ACTIVE_ELEMENT,
value: ''
};
};

View file

@ -0,0 +1,4 @@
export const ADD_FOCUSED_ELEMENT = 'ADD_FOCUSED_ELEMENT';
export const REMOVE_FOCUSED_ELEMENT = 'REMOVE_FOCUSED_ELEMENT';
export const ADD_ACTIVE_ELEMENT = 'ADD_ACTIVE_ELEMENT';
export const REMOVE_ACTIVE_ELEMENT = 'REMOVE_ACTIVE_ELEMENT';

View file

@ -0,0 +1,164 @@
import { LOGIN, LOGOUT, LOGGED_OUT_AS, RESET_PASSWORD } from './sessionTypes';
import { checkAuth, signIn, signInAs, signOut } from 'src/services/session';
import { resetAuthToken, setAuthToken } from 'src/utils/token';
import { resetPassword } from 'src/ControlPanelService/ResetPassword';
const LOGOUT_RESPONSE = 'logged_out';
const LOGOUT_AS_RESPONSE = 'logged_out_as';
export const login = (user, password) => dispatch => {
return new Promise((resolve, reject) => {
signIn(user, password).then((response) => {
const { error, session, token, panel, data, user } = response.data;
if (token) setAuthToken(token);
dispatch({
type: LOGIN,
value: {
token: data ? token : '',
panel,
session,
userName: user,
user: data,
error
},
});
resolve(response.data);
}, (error) => {
reject(error);
});
});
}
export const reset = ({ user = '', code = '', password = '', password_confirm = '' }) => dispatch => {
return new Promise((resolve, reject) => {
resetPassword(user, code, password, password_confirm).then((response) => {
const { error, session, token, panel, user } = response.data;
if (token) setAuthToken(token);
dispatch({
type: RESET_PASSWORD,
value: {
token,
panel,
session,
userName: user,
user: {},
error
},
});
resolve(response.data);
}, (error) => {
reject(error);
});
});
}
export const loginAs = username => dispatch => {
return new Promise((resolve, reject) => {
signInAs(username).then((response) => {
const { error, token, session, panel, data, user } = response.data;
if (token) setAuthToken(token);
dispatch({
type: LOGIN,
value: {
token,
panel,
session,
userName: user,
user: data,
error,
},
});
resolve(response.data);
}, (error) => {
console.error(error);
reject();
});
});
}
export const logout = () => (dispatch, getState) => {
return new Promise((resolve, reject) => {
signOut().then((response) => {
const { logout_response, panel, session, user, data, token } = response.data;
if (logout_response === LOGOUT_RESPONSE) {
resetAuthToken();
dispatch({
type: LOGOUT,
value: {
userName: user,
user: {},
token,
panel,
session,
error: ''
},
});
resolve();
} else if (logout_response === LOGOUT_AS_RESPONSE) {
const { token } = getState().session;
dispatch({
type: LOGGED_OUT_AS,
value: {
userName: user,
user: data,
session,
panel,
token,
error: ''
},
});
resolve();
} else {
resolve(`Error while signing out: ${logout_response}`);
}
}, (error) => {
console.error(error);
reject();
});
});
}
export const setToken = (token) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
checkAuth(token)
.then(res => {
const { user, data, session, panel, error } = res.data;
dispatch({
type: LOGIN,
value: {
userName: user,
user: data,
session,
panel,
token,
error
}
});
resolve();
})
.catch(err => {
reject();
console.error(err);
});
});
}
export const removeToken = () => {
return {
type: LOGOUT,
value: {
token: '',
error: ''
}
}
}

View file

@ -0,0 +1,5 @@
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export const LOGIN_AS = 'LOGIN_AS';
export const LOGGED_OUT_AS = 'LOGGED_OUT_AS';
export const RESET_PASSWORD = 'RESET_PASSWORD';

View file

@ -0,0 +1,84 @@
import React, { } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ListItem from '../ControlPanel/ListItem/ListItem';
import Container from '../ControlPanel/Container/Container';
import { faFileDownload } from '@fortawesome/free-solid-svg-icons'
import './Backup.scss';
import { Link } from 'react-router-dom';
const Backup = props => {
const { data } = props;
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const toggleFav = (starred) => {
if (starred) {
props.toggleFav(props.data.NAME, 'add');
} else {
props.toggleFav(props.data.NAME, 'delete');
}
}
const checkItem = () => {
props.checkItem(props.data.NAME);
}
const handleDelete = () => {
props.handleModal(data.delete_conf, `/delete/backup/?backup=${data.NAME}&token=${token}`);
}
return (
<ListItem
id={data.NAME}
date={data.DATE}
toggleFav={toggleFav}
checkItem={checkItem}
focused={data.FOCUSED}
starred={data.STARRED}
checked={data.isChecked}>
<Container className="r-col w-85">
<div className="name">{data.NAME}</div>
<div className="stats">
<Container className="c-1">
<div>{i18n['Backup Size']}: <span><span className="stat">{data.SIZE}</span>{i18n.mb}</span></div>
</Container>
<Container className="c-2">
<div>{i18n.Type}: <span className="stat">{data.TYPE}</span></div>
</Container>
<Container className="c-3">
<div>{i18n['Run Time']}: <span className="stat">{data.RUNTIME} minute</span></div>
</Container>
</div>
</Container>
<div className="actions">
{data.UPDATED === 'no' && <div><a href={`/update/vesta/?pkg=${data.NAME}`}>{i18n.update} <FontAwesomeIcon icon="wrench" /></a></div>}
<div>
<a className="link-download" href={`/download/backup/?backup=${data.NAME}&token=${token}`}>
{i18n.download}
{data.FOCUSED ? <span className="shortcut-button">D</span> : <FontAwesomeIcon icon={faFileDownload} />}
</a>
</div>
<div>
<Link className="link-download" to={`/list/backup?backup=${data.NAME}`}>
{i18n['configure restore settings']}
{data.FOCUSED ? <span className="shortcut-button html-unicode">&#8617;</span> : <FontAwesomeIcon icon="list" />}
</Link>
</div>
<div>
<button className="link-delete" onClick={() => handleDelete()}>
{i18n.Delete}
{data.FOCUSED ? <span className="shortcut-button del">Del</span> : <FontAwesomeIcon icon="times" />}
</button>
</div>
</div>
</ListItem>
);
}
export default Backup;

View file

@ -0,0 +1,7 @@
.backups-wrapper .list-item .c-1 div > span {
padding-left: 15px;
}
.backups-restore-details .list-item .star {
display: none;
}

View file

@ -0,0 +1,135 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "src/actions/MainNavigation/mainNavigationActions";
import { updateBackupExclusions, getBackupExclusionsInfo } from 'src/ControlPanelService/Backup';
import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
import AddItemLayout from 'src/components/ControlPanel/AddItemLayout/AddItemLayout';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Toolbar from 'src/components/MainNav/Toolbar/Toolbar';
import Spinner from 'src/components/Spinner/Spinner';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { Helmet } from 'react-helmet';
import './style.scss';
const EditBackupExclusions = () => {
const token = localStorage.getItem("token");
const { i18n } = window.GLOBAL.App;
const history = useHistory();
const dispatch = useDispatch();
const [state, setState] = useState({
data: {},
loading: false,
errorMessage: '',
okMessage: ''
});
useEffect(() => {
dispatch(addActiveElement('/list/backup/'));
dispatch(removeFocusedElement());
setState({ ...state, loading: true });
getBackupExclusionsInfo()
.then(response => {
setState({
...state,
data: response.data,
errorMessage: response.data['error_msg'],
okMessage: response.data['ok_msg'],
loading: false
});
})
.catch(err => console.error(err));
}, []);
const submitFormHandler = event => {
event.preventDefault();
let updatedExclusions = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
updatedExclusions[name] = value;
}
updatedExclusions['token'] = token;
updatedExclusions['save'] = 'save';
if (Object.keys(updatedExclusions).length !== 0 && updatedExclusions.constructor === Object) {
setState({ ...state, loading: true });
updateBackupExclusions(updatedExclusions)
.then(result => {
if (result.status === 200) {
const { error_msg: errorMessage, ok_msg: okMessage } = result.data;
if (errorMessage) {
setState({ ...state, errorMessage, okMessage: '', loading: false });
} else if (okMessage) {
setState({ ...state, errorMessage: '', okMessage, loading: false });
} else {
setState({ ...state, loading: false });
}
}
})
.catch(err => console.error(err));
}
}
return (
<div className="edit-template edit-backup-exclusions">
<Helmet>
<title>{`Vesta - ${i18n.BACKUP}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Editing Backup Exclusions']}</div>
<div className="error">
<span className="error-message">
{state.data.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}
</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout>
{state.loading ? <Spinner /> :
<form onSubmit={event => submitFormHandler(event)} id="edit-backup-exclusions">
<TextArea
title={i18n['Web Domains']}
defaultValue={state.data.web}
name="v_web"
id="v_web" />
<TextArea
title={i18n['Mail Domains']}
defaultValue={state.data.mail}
name="v_mail"
id="v_mail" />
<TextArea
title={i18n['Databases']}
defaultValue={state.data.db}
name="v_db"
id="v_db" />
<TextArea
title={i18n['User Directories']}
defaultValue={state.data.userdir}
name="v_userdir"
id="v_userdir" />
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Save}</button>
<button type="button" className="back" onClick={() => history.push('/list/backup/exclusions')}>{i18n.Back}</button>
</div>
</form>
}
</AddItemLayout>
</div>
);
}
export default EditBackupExclusions;

View file

@ -0,0 +1,3 @@
.content .edit-backup-exclusions .toolbar .search-toolbar-name {
width: auto;
}

View file

@ -0,0 +1,30 @@
import React from 'react';
import Container from '../../ControlPanel/Container/Container';
import './style.scss';
const Exclusion = ({ data, focused }) => {
const renderExclusionItems = () => {
if (!Array.isArray(data.ITEMS)) {
for (let item in data.ITEMS) {
return <><b>{item}</b> &nbsp; {data.ITEMS[item]}<br /></>;
}
} else {
return window.GLOBAL.App.i18n['no exclusions'];
}
}
return (
<div className={focused ? 'statistic-item focused' : 'statistic-item'} id={data.NAME}>
<Container className="l-col w-15" />
<Container className="r-col w-85">
<div className="stats">
<div className="name">{data.NAME}</div>
<div className="exclusion-items">{renderExclusionItems()}</div>
</div>
</Container>
</div>
);
}
export default Exclusion;

View file

@ -0,0 +1,33 @@
.statistic-item {
display: flex;
flex: 1 1 auto;
padding: 25px 0;
border-bottom: 1px solid #eee;
.l-col {
padding-left: 0;
flex-basis: 14.3%;
}
.r-col {
flex-basis: 85.7%;
.stats {
align-items: center;
.name {
font-size: 24px;
color: #111;
}
.exclusion-items {
flex-basis: 83.4%;
}
> div {
flex: unset;
flex-basis: 16.6%;
}
}
}
}

View file

@ -0,0 +1,58 @@
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 './RestoreSetting.scss';
export default function RestoreSetting({ data, checkItemFunc = () => { }, restoreSetting = () => { } }) {
const { i18n } = window.GLOBAL.App;
const displayBackupDetailName = type => {
switch (type) {
case 'WEB': return `WEB ${i18n['domain']}`;
case 'MAIL': return `MAIL ${i18n['domain']}`;
case 'DNS': return `DNS ${i18n['domain']}`;
case 'CRON': return i18n['cron'];
case 'UDIR': return i18n['user dir'];
default: return i18n['domain'];
}
}
const checkItem = () => {
checkItemFunc(data.NAME);
}
return (
<ListItem
date={false}
id={data.NAME}
focused={data.FOCUSED}
checked={data.isChecked}
checkItem={checkItem}>
<Container className="r-col w-85">
<div className="stats">
<Container className="c-1">
<div style={{ textTransform: 'uppercase' }}>{displayBackupDetailName(data.type)}</div>
</Container>
<Container className="c-2">
<b>{data.name}</b>
</Container>
<Container className="c-3"></Container>
<Container className="c-4"></Container>
<Container className="c-5"></Container>
</div>
</Container>
<div className="actions">
<div>
<button className="link-restore" onClick={() => restoreSetting(data.restoreLinkParams)}>
{i18n['restore']}
{data.FOCUSED ? <span className="shortcut-button">R</span> : <FontAwesomeIcon icon="play" />}
</button>
</div>
</div>
</ListItem>
);
}

View file

@ -0,0 +1,51 @@
.backups-restore-settings {
.list-item {
.r-col {
.stats {
.c-1 {
margin: 12px 0 0;
}
}
}
.actions {
> 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;
}
}
.link-restore {
svg {
color: white;
}
&:hover {
background-color: #9fbf0c;
}
}
}
}
}

View file

@ -0,0 +1,303 @@
import React, { useState, useEffect } from 'react';
import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from 'src/actions/ControlPanelContent/controlPanelContentActions';
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 { getBackupDetails, restoreBackupSetting, bulkRestore } from 'src/ControlPanelService/Backup';
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 RestoreSetting from '../RestoreSetting/RestoreSetting';
import Modal from 'src/components/ControlPanel/Modal/Modal';
import { useSelector, useDispatch } from 'react-redux';
import Spinner from 'src/components/Spinner/Spinner';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import './BackupRestoreSettings.scss';
export default function BackupRestoreSettings(props) {
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
const { focusedElement } = useSelector(state => state.mainNavigation);
const dispatch = useDispatch();
const [backupDetailsData, setBackupDetailsData] = useState([]);
const [modal, setModal] = useState({
text: '',
visible: false,
});
const [state, setState] = useState({
loading: false,
backupDetails: [],
toggledAll: false,
selection: [],
totalAmount: ''
});
useEffect(() => {
dispatch(removeControlPanelContentFocusedElement());
fetchData();
return () => dispatch(removeControlPanelContentFocusedElement());
}, []);
useEffect(() => {
window.addEventListener("keydown", handleContentSelection);
window.addEventListener("keydown", handleFocusedElementShortcuts);
return () => {
window.removeEventListener("keydown", handleContentSelection);
window.removeEventListener("keydown", handleFocusedElementShortcuts);
};
}, [controlPanelFocusedElement, focusedElement, backupDetailsData]);
const handleContentSelection = event => {
if (event.keyCode === 65) {
handleRestore(`?backup=${props.backup}`);
return;
}
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 = backupDetails => {
backupDetails[0]['FOCUSED'] = backupDetails[0]['NAME'];
setBackupDetailsData(backupDetails);
dispatch(addControlPanelContentFocusedElement(backupDetails[0]['NAME']));
}
const handleArrowDown = () => {
let backupDetails = [...backupDetailsData];
if (focusedElement) {
MainNavigation.removeFocusedElement();
}
if (controlPanelFocusedElement === '') {
initFocusedElement(backupDetails);
return;
}
let nextFocusedElement = backupDetails[controlPanelFocusedElement + 1];
if (nextFocusedElement) {
backupDetails[controlPanelFocusedElement]['FOCUSED'] = '';
nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
setBackupDetailsData(backupDetails);
dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
}
}
const handleArrowUp = () => {
let backupDetails = [...backupDetailsData];
if (focusedElement) {
MainNavigation.removeFocusedElement();
}
if (controlPanelFocusedElement === '') {
initFocusedElement(backupDetails);
return;
}
let nextFocusedElement = backupDetails[controlPanelFocusedElement - 1];
if (nextFocusedElement) {
backupDetails[controlPanelFocusedElement]['FOCUSED'] = '';
nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
setBackupDetailsData(backupDetails);
dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
}
}
const handleFocusedElementShortcuts = event => {
let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
if (controlPanelFocusedElement > 0 || controlPanelFocusedElement !== '' && !isSearchInputFocused) {
switch (event.keyCode) {
case 82: return handleRestore();
default: break;
}
}
}
const handleRestore = params => {
const paramsUri = params ? params : backupDetailsData[controlPanelFocusedElement].restoreLinkParams;
restoreBackupSetting(paramsUri)
.then(response => displayModal(response.data.message))
.catch(err => console.error(err));
}
const fetchData = () => {
setState({ ...state, loading: true });
getBackupDetails(props.backup)
.then(result => {
reformatData(result.data.data[props.backup]);
setState({
...state,
totalAmount: result.data.totalAmount,
loading: false
});
})
.catch(err => console.error(err));
}
const reformatData = data => {
let reformattedData = ['WEB', 'DNS', 'MAIL', 'DB', 'UDIR', 'CRON'].reduce((acc, cat) => {
data[cat].split(',').map(item => {
acc.push({
type: cat,
name: item,
restoreLinkParams: `?backup=${props.backup}&type=${cat.toLowerCase()}&object=${item}&token=${token}`
});
});
return acc;
}, []);
setBackupDetailsData(reformattedData);
}
const listBackups = () => {
const backupDetails = [...backupDetailsData];
const result = [];
backupDetails.forEach((backupDetail, index) => {
backupDetail.NAME = index;
backupDetail.FOCUSED = controlPanelFocusedElement === index;
result.push(backupDetail);
});
return result.map((item, index) => {
return <RestoreSetting data={item} key={index} checkItemFunc={name => checkItem(name)} restoreSetting={handleRestore} />;
});
}
const checkItem = name => {
const { selection } = state;
let duplicate = [...selection];
let backupDetailsDuplicate = [...backupDetailsData];
let checkedItem = duplicate.indexOf(name);
let incomingItem = backupDetailsDuplicate.findIndex(backupDetail => backupDetail.NAME === name);
backupDetailsDuplicate[incomingItem].isChecked = !backupDetailsDuplicate[incomingItem].isChecked;
if (checkedItem !== -1) {
duplicate.splice(backupDetailsDuplicate[name]['name'], 1);
} else {
duplicate.push(backupDetailsDuplicate[name]['name']);
}
setState({ ...state, selection: duplicate });
setBackupDetailsData(backupDetailsDuplicate);
}
const toggleAll = toggled => {
const backupDetailsDuplicate = [...backupDetailsData];
if (toggled) {
let backupDetailNames = [];
let backupDetails = backupDetailsDuplicate.map(backupDetail => {
backupDetailNames.push(backupDetail.name);
backupDetail.isChecked = true;
return backupDetail;
});
setState({ ...state, selection: backupDetailNames, toggledAll: toggled });
setBackupDetailsData(backupDetails);
} else {
let backupDetails = backupDetailsDuplicate.map(backupDetail => {
backupDetail.isChecked = false;
return backupDetail;
});
setState({ ...state, selection: [], toggledAll: toggled });
setBackupDetailsData(backupDetails);
}
}
const bulk = action => {
const { selection } = state;
if (selection.length && action) {
setState({ ...state, loading: true });
bulkRestore(action, selection, props.backup)
.then(result => {
if (result.status === 200) {
displayModal(result.data.message);
toggleAll(false);
}
})
.catch(err => console.error(err));
}
}
const displayModal = text => {
setState({ ...state, loading: false });
setModal({ ...modal, visible: true, text });
}
const modalCancelHandler = () => {
setModal({ ...modal, visible: false, text: '' });
}
return (
<div className="mail-accounts backups-restore-settings">
<Helmet>
<title>{`Vesta - ${i18n.BACKUP}`}</title>
</Helmet>
<Toolbar mobile={false} >
<LeftButton name={i18n['Restore All']} list="backup-details" onClick={() => handleRestore(`?backup=${props.backup}`)} showLeftMenu={true} />
<div className="r-menu">
<div className="input-group input-group-sm">
<Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
<Select list='backupDetailList' bulkAction={bulk} />
<SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />
</div>
</div>
</Toolbar>
{state.loading
? <Spinner />
: (
<>
<div className="mail-accounts-wrapper">
<div className="subtitle">
<span>{`${i18n['Listing']} ${props.backup}`}</span>
</div>
{listBackups()}
</div>
<div className="footer-actions-wrapper">
<div className="total">{state.totalAmount}</div>
<div className="back">
<Link to="/list/backup/">{i18n['Back']}</Link>
</div>
</div>
</>
)
}
<Modal
onSave={modalCancelHandler}
showCancelButton={false}
show={modal.visible}
text={modal.text} />
</div>
);
}

View file

@ -0,0 +1,21 @@
.backups-restore-settings {
.toolbar {
.backup-details-icon {
svg {
transform: translateX(2px);
}
}
}
.mail-accounts-wrapper {
.list-item {
.l-col {
.star {
.checkbox + div {
display: none;
}
}
}
}
}
}

View file

@ -0,0 +1,35 @@
import React from 'react';
import Container from '../Container/Container';
import './AddItemLayout.scss';
const AddItemLayout = ({ date = '', time = '', status = '', children }) => {
const renderDate = () => {
if (date.length > 0) {
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 <div className="date">{day} &nbsp; {months[month]} &nbsp; {year}</div>;
}
}
return (
<div className="edit-item">
<Container className="l-col w-14">
{renderDate()}
<div className="time">
{time}
</div>
<div className="status uppercase">
{status}
</div>
</Container>
{children}
</div>
);
}
export default AddItemLayout;

View file

@ -0,0 +1,268 @@
$textColor: #555;
$addButtonBackground: #9FBF0C;
$addButtonBackgroundHover: #C0E60F;
$optionalButtonHover: #9FBF0C;
$optionalButtonActive: #c0e60f;
$deleteButtonColorHover: #FF3438;
$deleteButtonColorActive: #FF5F5F;
$backButtonBackground: #DFDEDD;
$backButtonBackgroundHover: #999;
$transition: all 200ms cubic-bezier(0.4, 0.1, 0.5, 0.85);
$errorColor: #BE5ABF;
$successColor: $addButtonBackground;
.content .edit-template {
padding-bottom: 2rem;
.toolbar {
justify-content: unset;
> div {
width: 14.3%;
display: flex;
align-items: center;
}
div.error,
div.success {
width: fit-content;
span {
font-weight: bold;
font-size: 14px;
svg {
font-size: 13px;
}
}
span.error-message {
color: $errorColor;
svg {
margin-right: 7px;
font-size: 13px;
color: $errorColor;
}
}
span.ok-message {
color: $successColor;
svg {
color: $successColor;
}
}
}
.search-toolbar-name {
padding: 10px 0;
margin-left: 0;
}
}
}
.hide {
display: none;
}
.show {
display: flex;
}
.edit-item,
.login-layout {
display: flex;
font-size: 15px;
margin-top: 3rem;
margin-bottom: 1rem;
.l-col {
margin-right: 1.75%;
}
form {
button,
a {
color:#2C9491;
font-weight: bold;
&:hover {
color: #ff6701;
}
&:active {
color: #F7D616;
}
}
button.optional {
&:hover {
color: $optionalButtonHover;
}
&:active {
color: $optionalButtonActive;
}
}
button.delete {
&:hover {
color: $deleteButtonColorHover;
}
&:active {
color: $deleteButtonColorActive;
}
}
.form-group {
input[type=text],
input[type=password],
input[type=email],
textarea,
select {
width: 50%;
}
}
width: 100%;
.form-group select,
.form-group textarea {
border-radius: 0;
&:hover {
border-color: #909090;
}
&:focus,
&:active {
outline: none;
border-color: #55C9C0;
box-shadow: unset;
}
}
textarea {
&:focus {
background: #D7F9FF;;
}
}
button {
background: unset;
border: none;
box-shadow: unset;
&:focus,
&:active {
outline: none;
}
}
input {
height: calc(1.5em + 0.75rem + 6px);
border-radius: 0;
&:hover {
border-color: #909090;
}
&:focus,
&:active {
box-shadow: unset;
border-color: #55C9C0;
background: #D7F9FF;
}
}
label {
color: $textColor;
font-weight: bold;
}
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;
}
}
.checkbox-wrapper {
display: flex;
align-items: center;
label {
margin: 0 12px;
}
}
input {
color: $textColor;
}
.buttons-wrapper {
margin-top: 2.5rem;
button {
font-weight: bold;
font-size: 13px;
margin-right: 10px;
padding: .35rem 2.25rem;
border-radius: 4px;
transition: $transition;
}
.add {
color: white;
background: $addButtonBackground;
&:hover {
color: #555;
background: $addButtonBackgroundHover;
}
}
.back {
color: #777;
background: $backButtonBackground;
&:hover {
color: white;
background: $backButtonBackgroundHover;
}
}
}
}
.l-col {
padding: 20px 0 0;
.date,
.time {
margin: 10px 0 0;
color: #777;
font-size: 12px;
letter-spacing: 1px;
}
.time {
margin: 6px 0 10px;
}
.status {
color: #9FBF0C;
font-size: 11px;
letter-spacing: 1px;
text-transform: uppercase;
font-weight: bold;
}
}
}

View file

@ -0,0 +1,37 @@
import React, { useEffect, useState } from 'react';
const Checkbox = ({ name, id, title, defaultChecked = false, onChange = () => { }, checked }) => {
const [checkedState, setCheckedState] = useState(false);
useEffect(() => {
if (!!checked) {
setCheckedState(checked);
}
if (!!defaultChecked) {
setCheckedState(defaultChecked);
}
}, [checked, defaultChecked]);
const changeCheckbox = event => {
setCheckedState(event.target.checked);
onChange(event.target.checked);
}
return (
<div className="form-group">
<div className="checkbox-wrapper">
<input
type="checkbox"
name={name}
id={id}
checked={checkedState}
onChange={changeCheckbox}
defaultChecked={defaultChecked} />
<label htmlFor={id}>{title}</label>
</div>
</div>
);
}
export default Checkbox;

View file

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react';
const NameServers = props => {
const { i18n } = window.GLOBAL.App;
const [state, setState] = useState({
nameServersAmount: [],
usersNS: []
});
useEffect(() => {
if (props.usersNS) {
let initNameServersAmount = props.usersNS.map((userNS, index) => index + 1);
setState({ ...state, usersNS: props.usersNS, nameServersAmount: initNameServersAmount });
}
}, [props.usersNS]);
const renderNameServerInputs = () => {
return state.nameServersAmount.map((nameServer, index) => {
if (nameServer < 9) {
return (
<div className="name-server-input-wrapper" key={index}>
<input
type="text"
className="form-control"
id={`v_ns${index + 1}`}
defaultValue={state.usersNS[index] || ''}
name={`v_ns${index + 1}`} />
<button
type="button"
className={index < 2 ? 'hide' : 'show delete'}
onClick={() => onDeleteNameServer(index)}>
{i18n.delete ?? 'Delete'}
</button>
</div>
);
}
});
}
const addNameServerClassName = () => {
return state.nameServersAmount.length === 8 ? 'hide' : 'show optional';
}
const addNameServer = () => {
let nameServersLength = state.nameServersAmount.length;
let nameServersDuplicate = [...state.nameServersAmount];
nameServersDuplicate.push(nameServersLength + 1);
setState({ ...state, nameServersAmount: nameServersDuplicate });
}
const onDeleteNameServer = index => {
let nameServersDuplicate = [...state.nameServersAmount];
nameServersDuplicate.splice(index - 1, 1);
setState({ ...state, nameServersAmount: nameServersDuplicate });
}
return (
<>
<div className="form-group name-servers">
<label htmlFor="v_ns1">{i18n['Name servers'] ?? 'Name servers'}</label>
{renderNameServerInputs()}
</div>
<button
type="button"
className={addNameServerClassName()}
onClick={() => addNameServer()}>
{i18n['Add one more Name Server'] ?? 'Add one more Name Server'}
</button>
</>
);
}
export default NameServers;

View file

@ -0,0 +1,70 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useEffect, useState } from 'react';
const Password = ({ defaultValue, onChange = () => { }, id, name, title, showGenerationButton = true, ...props }) => {
const { i18n } = window.GLOBAL.App;
const [state, setState] = useState({
hidePassword: false,
generatedPassword: ''
});
useEffect(() => {
if (defaultValue && !state.generatedPassword) {
setState({ ...state, generatedPassword: defaultValue });
}
}, [defaultValue]);
const hidePasswordHandler = () => {
setState({ ...state, hidePassword: !state.hidePassword });
}
const generatePassword = () => {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
let stringLength = 10;
let result = '';
for (var i = 0; i < stringLength; i++) {
let randomNumber = Math.floor(Math.random() * chars.length);
result += chars.substr(randomNumber, 1);
}
setState({ ...state, generatedPassword: result });
}
const passwordInputHandler = value => {
setState({ ...state, generatedPassword: value });
onChange(value);
}
return (
<div className="form-group">
<label htmlFor="password">
{title ? title : i18n.Password}
{
showGenerationButton && (
<>/ <button type="button" className="generate-password" onClick={() => generatePassword()}>
{i18n.Generate}
</button></>
)
}
</label>
<div className="password-wrapper">
<input
type={state.hidePassword ? 'password' : 'text'}
className="form-control"
id={`password_${id}`}
name={name}
value={state.generatedPassword}
onChange={event => passwordInputHandler(event.target.value)}
{...props} />
<button type="button" onClick={() => hidePasswordHandler()}>
{state.hidePassword ?
<span className="eye-slash"><FontAwesomeIcon icon="eye-slash" /></span> :
<span className="eye"><FontAwesomeIcon icon="eye" /></span>}
</button>
</div>
</div>
);
}
export default Password;

View file

@ -0,0 +1,36 @@
import React from 'react';
const SelectInput = ({ options = [], id, name, title, optionalTitle = '', selected = '', onChange = () => { }, disabled = false }) => {
const { i18n } = window.GLOBAL.App;
const renderOptions = () => {
return options.map((option, index) =>
<option key={index} selected={selected === option} value={option === i18n['Disable and Cancel Licence'] ? 'cancel' : option}>
{option}
</option>
);
}
return (
<>
{
options
? (
<div className="form-group select-group">
<label className="label-wrapper" htmlFor={id}>
{title}
<span>{optionalTitle}</span>
</label>
<select className="form-control" id={id} name={name} disabled={disabled} onChange={event => onChange(event.target.value)}>
{disabled && <input type="hidden" name={name} value={selected || options[0]} />}
{renderOptions()}
</select>
</div>
)
: null
}
</>
);
}
export default SelectInput;

View file

@ -0,0 +1,7 @@
.form-group.select-group {
label {
span {
text-transform: uppercase;
}
}
}

View file

@ -0,0 +1,22 @@
import React from 'react';
const TextArea = ({ id, name, defaultValue = '', title, optionalTitle = '', rows = '3', disabled = false }) => {
return (
<div className="form-group">
<label className="label-wrapper" htmlFor={id}>
{title}
<span>{optionalTitle}</span>
</label>
<textarea
className="form-control"
id={id}
rows={rows}
name={name}
disabled={disabled}
defaultValue={defaultValue}>
</textarea>
</div>
);
}
export default TextArea;

View file

@ -0,0 +1,36 @@
import React, { useEffect, useState } from 'react';
const TextInput = ({ id, name, title, optionalTitle = '', type = 'text', onChange = () => { }, value = '', disabled = false }) => {
const [inputValue, setInputValue] = useState('');
useEffect(() => {
if (value) {
setInputValue(value);
}
}, [value]);
const changeCheckbox = event => {
setInputValue(event.target.value);
onChange(event);
}
return (
<div className="form-group">
<label className="label-wrapper" htmlFor={id}>
{title}
<span>{optionalTitle}</span>
</label>
<input
type={type}
name={name}
id={id}
onChange={changeCheckbox}
// disabled={disabled}
readOnly={disabled}
value={inputValue}
className="form-control" />
</div>
);
}
export default TextInput;

View file

@ -0,0 +1,48 @@
import React, { useEffect, useState } from 'react';
const TextInputWithExtraButton = props => {
const [state, setState] = useState({
value: '',
previousValue: ''
});
useEffect(() => {
if (props.value !== 'unlimited') {
setState({ ...state, value: state.previousValue });
} else {
setState({ ...state, value: props.value });
}
}, [props.value]);
useEffect(() => {
setState({ ...state, value: props.value, previousValue: props.value });
}, []);
const changeValue = event => {
let inputValue = event.target.value;
setState({ ...state, value: inputValue, previousValue: inputValue });
}
return (
<div class="form-group">
<label className="label-wrapper" htmlFor={props.id}>
{props.title}
<span className="lowercase">{props.optionalTitle ? `(${props.optionalTitle})` : ''}</span>
</label>
<div className="input-wrapper">
<input
type="text"
name={props.name}
id={props.id}
className="form-control"
onChange={changeValue}
value={state.value}
readOnly={state.value === 'unlimited'} />
{props.children}
</div>
</div>
);
}
export default TextInputWithExtraButton;

View file

@ -0,0 +1,36 @@
import React, { useEffect, useState } from 'react';
import './TextInputWithTextOnTheRight.scss';
const TextInputWithTextOnTheRight = ({ id, title, name, defaultValue = '', optionalTitle = '', disabled = false }) => {
const [inputValue, setInputValue] = useState('');
useEffect(() => {
if (defaultValue) {
setInputValue(defaultValue);
}
}, [defaultValue]);
return (
<div className="form-group text-on-the-right">
<label className="label-wrapper" htmlFor={id}>
{title}
<span>{optionalTitle || ''}</span>
</label>
<div className="input-wrapper">
<input
defaultValue={`admin_${defaultValue}`}
type="text"
className="form-control"
id={id}
value={inputValue}
onChange={event => setInputValue(event.target.value)}
disabled={disabled}
name={name} />
<span><i>{`admin_${inputValue}`}</i></span>
</div>
</div>
);
}
export default TextInputWithTextOnTheRight;

View file

@ -0,0 +1,11 @@
.form-group.text-on-the-right {
.input-wrapper {
display: flex;
align-items: center;
span {
margin-left: 15px;
color: #777;
}
}
}

View file

@ -0,0 +1,12 @@
import React from 'react';
import './Container.scss';
const Container = props => {
return(
<div className={props.className}>
{props.children}
</div>
);
}
export default Container;

View file

@ -0,0 +1,47 @@
.l-col {
padding-left: 15px;
}
.r-col {
font-size: 13px;
}
.w-14 {
width: 14.3%;
}
.w-20 {
width: 20%;
}
.w-25 {
width: 25%;
}
.w-30 {
width: 30%;
}
.w-35 {
width: 35%;
}
.w-40 {
width: 40%;
}
.w-45 {
width: 45%;
}
.w-50 {
width: 50%;
}
.w-60 {
width: 60%;
}
.w-85 {
width: 85%;
}

View file

@ -0,0 +1,106 @@
import React, { useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import './Hotkeys.scss';
const Hotkeys = props => {
const { i18n } = window.GLOBAL.App;
useEffect(() => {
window.addEventListener("keyup", toggleShortcutsLit);
return () => window.removeEventListener("keyup", toggleShortcutsLit);
}, [props.reference]);
const toggleShortcutsLit = event => {
let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
if (event.keyCode === 72 && !isSearchInputFocused) {
props.toggleHotkeys();
}
}
return (
<div className="hotkeys-list hide" ref={props.reference}>
<div className="head">
<div className="name">{i18n.Shortcuts}</div>
<div className="close" onClick={() => props.toggleHotkeys()}><FontAwesomeIcon icon="times" /></div>
</div>
<div className="body">
<ul>
<li>
<span className="name">a</span>
<span className="description">{i18n['Add New object']}</span>
</li>
<li>
<span className="name">Ctrl + Open</span>
<span className="description">{i18n['Save Form']}</span>
</li>
<li>
<span className="name">Ctrl + Backspace</span>
<span className="description">{i18n['Cancel saving form']}</span>
</li>
<li className="space-top">
<span className="name">1</span>
<span className="description">{i18n['Go to USER list']}</span>
</li>
<li>
<span className="name">2</span>
<span className="description">{i18n['Go to WEB list']}</span>
</li>
<li>
<span className="name">3</span>
<span className="description">{i18n['Go to DNS list']}</span>
</li>
<li>
<span className="name">4</span>
<span className="description">{i18n['Go to MAIL list']}</span>
</li>
<li>
<span className="name">5</span>
<span className="description">{i18n['Go to DB list']}</span>
</li>
<li>
<span className="name">6</span>
<span className="description">{i18n['Go to CRON list']}</span>
</li>
<li>
<span className="name">7</span>
<span className="description">{i18n['Go to BACKUP list']}</span>
</li>
</ul>
<ul>
<li>
<span className="name">f</span>
<span className="description">{i18n['Focus on search']}</span>
</li>
<li className="space-top">
<span className="name">h</span>
<span className="description">{i18n['Display/Close shortcuts']}</span>
</li>
<li className="space-top">
<span className="name">&#8592;</span>
<span className="description">{i18n['Move backward through top menu']}</span>
</li>
<li>
<span className="name">&#8594;</span>
<span className="description">{i18n['Move forward through top menu']}</span>
</li>
<li>
<span className="name">Enter</span>
<span className="description">{i18n['Enter focused element']}</span>
</li>
<li className="space-top">
<span className="name">&#8593;</span>
<span className="description">{i18n['Move up through elements list']}</span>
</li>
<li>
<span className="name">&#8595;</span>
<span className="description">{i18n['Move down through elements list']}</span>
</li>
</ul>
</div>
</div>
);
}
export default Hotkeys;

View file

@ -0,0 +1,79 @@
.hotkeys-list {
position: fixed;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
transform: translateX(-45%);
width: 53%;
background: rgba(50, 50, 50, 0.9);
font-size: 13px;
.head {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #333;
.name {
text-transform: uppercase;
padding: 5px 0 5px 10px;
font-size: 12px;
font-weight: bold;
color: #ffcc00;
letter-spacing: 2px;
padding: 15px;
}
.close {
padding: 12px;
svg {
color: #a1a1a1;
}
&:hover {
cursor: pointer;
background: black;
}
&:active {
background: #55c9c0;
}
}
}
.body {
display: flex;
ul {
padding: 25px 10px;
margin: 0;
width: 50%;
list-style: none;
margin-left: 3rem;
li {
padding: 5px;
span.name {
margin-right: 15px;
color: #48F4EF;
font-weight: bold;
}
span.description {
color: #929292;
}
}
li.space-top {
padding-top: 30px;
}
}
}
}
.hide {
display: none;
}

View file

@ -0,0 +1,100 @@
import React, { Component } from 'react';
import Container from '../Container/Container';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import './ListItem.scss';
class ListItem extends Component {
state = {
starred: false
}
UNSAFE_componentWillMount() {
this.setState({ starred: this.props.starred === 1 });
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
starred: nextProps.starred === 1
});
}
printDate = date => {
if (date) {
let newDate = new Date(date);
let day = newDate.getDate();
let month = newDate.getMonth() + 1;
let year = newDate.getFullYear();
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return <div className="date">{day} &nbsp; {months[month - 1]} &nbsp; {year}</div>;
}
}
toggleItem = () => {
this.props.checkItem();
}
starItem = () => {
this.setState({ starred: !this.state.starred }, () => {
this.props.toggleFav(this.state.starred);
});
}
className = () => {
const { starred } = this.state;
const { checked, outdated, suspended, stopped, focused, sysInfo } = this.props;
let className = 'list-item';
if (checked) {
className += ' toggled';
}
if (starred) {
className += ' starred';
}
if (outdated) {
className += ' outdated';
}
if (suspended) {
className += ' suspended';
}
if (stopped) {
className += ' stopped';
}
if (focused) {
className += ' focused';
}
if (sysInfo) {
className += ' sys-info';
}
return className;
}
render() {
return (
<div className={this.className()} id={this.props.id}>
<Container className="l-col w-14">
{this.printDate(this.props.date)}
<div className="text-status">
<div className="checkbox"><input type="checkbox" onChange={(e) => this.toggleItem(e)} checked={this.props.checked} /></div>
{this.props.leftNameText}
</div>
<div className="star">
<div className="checkbox"><input type="checkbox" onChange={(e) => this.toggleItem(e)} checked={this.props.checked} /></div>
<div onClick={this.starItem}><FontAwesomeIcon icon="star" /></div>
</div>
{this.props.suspended && <div className='suspended'>{window.GLOBAL.App.i18n.suspended}</div>}
</Container>
{this.props.children}
</div>
);
}
}
export default ListItem;

View file

@ -0,0 +1,148 @@
.list-item {
display: flex;
justify-content: flex-start;
align-items: flex-start;
padding-left: 15px;
font-size: 18px;
position: relative;
padding: 25px 0;
border-left: 2px solid white;
border-bottom: 1px solid #ddd;
color: rgb(104, 104, 104);
&:hover .l-col div.star div svg,
&:hover .actions {
opacity: 1;
}
.l-col {
.suspended {
letter-spacing: 3px;
}
.text-status {
font-size: 10px;
}
div.star {
display: flex;
align-items: center;
div.checkbox {
margin: 0 25% 4px 0;
}
}
}
.r-col {
.stats {
div > span {
width: 50%;
text-align: left;
}
.c-2 div > span {
width: 50%;
}
}
div.bandwidth span,
div.disk span {
width: auto;
}
}
.date {
font-size: 13px;
margin: 20px 0 10px;
}
}
.list-item.toggled {
background: #feef9a;
}
.list-item.starred {
border-left: 2px solid #ff6701;
.l-col div.star {
div > svg {
opacity: 1;
color: #ff6701;
}
}
}
.list-item.suspended {
background: #eaeaea;
color: #c0c0c0;
.r-col div, .r-col span {
color: #c0c0c0 !important;
}
}
.list-item.suspended.toggled {
background: #f2eab8;
color: #b2ac87;
.l-col,
.r-col .name,
.r-col .stats {
color: #b2ac87 !important;
}
}
.list-item.outdated {
background: #ffcaca;
border-left: 5px solid #ff6f6f;
}
.list-item.toggled.outdated {
background: #755d5d;
color: black;
.stat {
color: black;
}
}
.list-item.stopped {
background: #eaeaea;
}
.list-item.focused {
border-left: 2px solid #5edad0;
.actions {
opacity: 1;
div > a,
div > button {
padding-top: 6.5px;
padding-bottom: 6.5px;
.shortcut-button {
border-radius: 50%;
width: 25px;
height: 25px;
margin-left: 15px;
background: #69a298;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
}
.shortcut-button.html-unicode {
align-items: flex-end;
}
.shortcut-button.del {
text-transform: capitalize;
font-size: 10px;
}
}
}
}

View file

@ -0,0 +1,14 @@
import React from 'react';
import '../AddItemLayout/AddItemLayout.scss';
import './LoginLayout.scss';
const LoginLayout = ({ children }) => {
return (
<div className="login-layout">
{children}
</div>
);
}
export default LoginLayout;

View file

@ -0,0 +1,4 @@
.login-layout {
margin-top: 1rem;
padding: 2rem 3rem;
}

View file

@ -0,0 +1,32 @@
import React from 'react';
import './Modal.scss';
const { i18n } = window.GLOBAL.App;
const Modal = ({ show, text, onSave, onCancel, showSaveButton = true, showCancelButton = true }) => {
return (
<div>
<div className={`modal fade ${show ? 'show' : ''}`} id="c-panel-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" style={{ display: show ? 'block' : 'none' }}>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLabel">{i18n.Confirmation}</h5>
<button type="button" onClick={() => onCancel()} className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div className="modal-body">
{text}
</div>
<div className="modal-footer">
{showCancelButton ? <button onClick={() => onCancel()} type="button" className="btn btn-secondary" data-dismiss="modal">{i18n.Cancel}</button> : ''}
{showSaveButton ? <button onClick={() => onSave()} type="button" className="btn btn-primary">{i18n.OK}</button> : ''}
</div>
</div>
</div>
</div>
</div>
)
};
export default Modal;

View file

@ -0,0 +1,18 @@
div.modal {
z-index: 2;
}
div.content .modal .modal-content {
width: 75%;
.modal-header button.close {
color: white;
}
.modal-footer {
.btn-primary {
background: #9FBF0C;
border: 1px solid #9FBF0C;
}
}
}

View file

@ -0,0 +1,198 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { addCronJob } from '../../../ControlPanelService/Cron';
import Toolbar from '../../MainNav/Toolbar/Toolbar';
import Generator from '../Generator/Generator';
import { useHistory } from 'react-router-dom';
import Spinner from '../../Spinner/Spinner';
import { useDispatch } from 'react-redux';
import './AddCronJob.scss';
import { Helmet } from 'react-helmet';
const AddCronJob = props => {
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const history = useHistory();
const dispatch = useDispatch();
const [state, setState] = useState({
loading: false,
okMessage: '',
errorMessage: '',
generatedCronJob: {
h_min: '*',
h_hour: '*',
h_day: '*',
h_wday: '*',
h_month: '*'
}
});
useEffect(() => {
dispatch(addActiveElement('/list/cron/'));
dispatch(removeFocusedElement());
}, []);
const submitFormHandler = event => {
event.preventDefault();
let newCronJob = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
newCronJob[name] = value;
}
if (Object.keys(newCronJob).length !== 0 && newCronJob.constructor === Object) {
setState({ ...state, loading: true });
addCronJob(newCronJob)
.then(result => {
if (result.status === 200) {
const { error_msg, ok_msg } = result.data;
if (error_msg) {
setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
} else if (ok_msg) {
setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
} else {
setState({ ...state, loading: false })
}
}
})
.catch(err => console.error(err));
}
}
const saveGeneratedCronJob = generatedCronJob => {
setState({
...state,
generatedCronJob
});
}
const changeInput = input => {
let updatedGeneratedCronJob = {
...state.generatedCronJob,
[input.id]: input.value
};
setState({
...state,
generatedCronJob: updatedGeneratedCronJob
});
}
return (
<div className="edit-template add-cron">
<Helmet>
<title>{`Vesta - ${i18n.CRON}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Adding Cron Job']}</div>
<div className="error">
<span className="error-message">
{state.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
{state.errorMessage}</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
<span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout>
{state.loading ? <Spinner /> : (
<form onSubmit={event => submitFormHandler(event)}>
<input type="hidden" name="ok" value="add" />
<input type="hidden" name="token" value={token} />
<div className="form-group command">
<label htmlFor="command">{i18n.Command ?? 'Command'}</label>
<input
type="text"
className="form-control"
id="command"
name="v_cmd" />
</div>
<div className="cron-form-body">
<div className="body-col-1">
<div className="form-group">
<label htmlFor="h_min">{i18n.Minute ?? 'Minute'}</label>
<input
type="text"
className="form-control"
id="h_min"
onChange={event => changeInput(event.target)}
value={state.generatedCronJob.h_min}
name="v_min" />
</div>
<div className="form-group">
<label htmlFor="h_hour">{i18n.Hour ?? 'Hour'}</label>
<input
type="text"
className="form-control"
id="h_hour"
onChange={event => changeInput(event.target)}
value={state.generatedCronJob.h_hour}
name="v_hour" />
</div>
<div className="form-group">
<label htmlFor="h_day">{i18n.Day ?? 'Day'}</label>
<input
type="text"
className="form-control"
id="h_day"
onChange={event => changeInput(event.target)}
value={state.generatedCronJob.h_day}
name="v_day" />
</div>
<div className="form-group">
<label htmlFor="h_month">{i18n.Month ?? 'Month'}</label>
<input
type="text"
className="form-control"
id="h_month"
onChange={event => changeInput(event.target)}
value={state.generatedCronJob.h_month}
name="v_month" />
</div>
<div className="form-group">
<label htmlFor="h_wday">{i18n['Day of week'] ?? 'Days of week'}</label>
<input
type="text"
className="form-control"
id="h_wday"
onChange={event => changeInput(event.target)}
value={state.generatedCronJob.h_wday}
name="v_wday" />
</div>
</div>
<div className="body-col-2">
<Generator mode="add" generatedCronJob={saveGeneratedCronJob} />
</div>
</div>
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Add ?? 'Add'}</button>
<button type="button" className="back" onClick={() => history.push('/list/cron/')}>{i18n.Back ?? 'Back'}</button>
</div>
</form>
)}
</AddItemLayout>
</div>
);
}
export default AddCronJob;

View file

@ -0,0 +1,31 @@
.edit-template.add-cron {
form {
.form-group {
width: 100%;
input {
width: inherit;
}
}
.form-group.command {
width: 100%;
}
.cron-form-body {
display: flex;
.body-col-1 {
width: 30%;
}
.body-col-2 {
display: flex;
justify-content: flex-start;
align-items: flex-start;
padding: 1.7rem 1rem;
width: 70%;
}
}
}
}

View file

@ -0,0 +1,95 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ListItem from '../ControlPanel/ListItem/ListItem';
import Container from '../ControlPanel/Container/Container';
import './CronJob.scss';
import { Link } from 'react-router-dom';
const CronJob = props => {
const { data } = props;
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const toggleFav = (starred) => {
if (starred) {
props.toggleFav(props.data.NAME, 'add');
} else {
props.toggleFav(props.data.NAME, 'delete');
}
}
const checkItem = () => {
props.checkItem(props.data.NAME);
}
const handleSuspend = () => {
let suspendedStatus = data.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend' === 'yes' ? 'unsuspend' : 'suspend';
props.handleModal(data.suspend_conf, `/${suspendedStatus}/cron/?job=${data.NAME}&token=${token}`);
}
const handleDelete = () => {
props.handleModal(data.delete_conf, `/delete/cron/?job=${data.NAME}&token=${token}`);
}
return (
<ListItem
id={data.NAME}
date={data.DATE}
checkItem={checkItem}
toggleFav={toggleFav}
focused={data.FOCUSED}
starred={data.STARRED}
checked={data.isChecked}
suspended={data.SUSPENDED === 'yes'}>
<Container className="cron-jobs-list r-col w-85">
<div className="name">{data.CMD}</div>
<div className="stats">
<Container className="cron-col">
<div>{i18n.Min} <span>{data.MIN}</span></div>
</Container>
<Container className="cron-col">
<div>{i18n.Hour} <span>{data.HOUR}</span></div>
</Container>
<Container className="cron-col">
<div>{i18n.Day} <span>{data.DAY}</span></div>
</Container>
<Container className="cron-col">
<div>{i18n.Month} <span>{data.MONTH}</span></div>
</Container>
<Container className="cron-col">
<div>{i18n['Day of week']} <span>{data.WDAY}</span></div>
</Container>
</div>
</Container>
<div className="actions">
<div>
<Link className="link-edit" to={`/edit/cron/?job=${data.NAME}`}>
{i18n.edit}
{data.FOCUSED ? <span className="shortcut-button html-unicode">&#8617;</span> : <FontAwesomeIcon icon="pen" />}
</Link>
</div>
<div>
<button
className="link-gray"
onClick={() => handleSuspend()}>
{i18n[data.suspend_action]}
{data.FOCUSED ? <span className="shortcut-button">S</span> : <FontAwesomeIcon icon={data.SUSPENDED === 'yes' ? 'unlock' : 'lock'} />}
</button>
</div>
<div>
<button className="link-delete" onClick={() => handleDelete()}>
{i18n.Delete}
{data.FOCUSED ? <span className="shortcut-button del">Del</span> : <FontAwesomeIcon icon="times" />}
</button>
</div>
</div>
</ListItem>
);
}
export default CronJob;

View file

@ -0,0 +1,13 @@
.cron-jobs-list.r-col .name {
font-size: 25px;
}
.cron-col > div {
display: flex;
flex-direction: column;
span {
font-size: 18px;
font-weight: bolder;
}
}

View file

@ -0,0 +1,200 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
import { getCronJobInfo, updateCronJob } from '../../../ControlPanelService/Cron';
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 Generator from '../Generator/Generator';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import QS from 'qs';
import './EditCronJob.scss';
import { Helmet } from 'react-helmet';
const EditMail = props => {
const token = localStorage.getItem("token");
const { i18n } = window.GLOBAL.App;
const history = useHistory();
const dispatch = useDispatch();
const [state, setState] = useState({
data: {},
errorMessage: '',
okMessage: '',
loading: false,
generatedCronJob: {
h_min: '*',
h_hour: '*',
h_day: '*',
h_wday: '*',
h_month: '*'
}
});
useEffect(() => {
let queryParams = QS.parse(history.location.search, { ignoreQueryPrefix: true });
const { job } = queryParams;
dispatch(addActiveElement('/list/cron/'));
dispatch(removeFocusedElement());
if (job) {
setState({ ...state, loading: true });
getCronJobInfo(job)
.then(response => {
setState({
...state,
generatedCronJob: {
...state.generatedCronJob,
h_min: response.data.min,
h_hour: response.data.hour,
h_day: response.data.day,
h_wday: response.data.wday,
h_month: response.data.month
},
data: response.data,
errorMessage: response.data['error_msg'],
okMessage: response.data['ok_msg'],
loading: false
});
})
.catch(err => console.error(err));
}
}, []);
const submitFormHandler = event => {
event.preventDefault();
let updatedJob = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
updatedJob[name] = value;
}
if (Object.keys(updatedJob).length !== 0 && updatedJob.constructor === Object) {
setState({ ...state, loading: true });
updateCronJob(updatedJob, state.data.job)
.then(result => {
if (result.status === 200) {
const { error_msg, ok_msg } = result.data;
if (error_msg) {
setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
} else if (ok_msg) {
setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
} else {
setState({ ...state, loading: false });
}
}
})
.catch(err => console.error(err));
}
}
const saveGeneratedCronJob = generatedCronJob => {
setState({
...state,
generatedCronJob
});
}
const changeInput = input => {
let updatedGeneratedCronJob = {
...state.generatedCronJob,
[input.id]: input.value
};
setState({
...state,
generatedCronJob: updatedGeneratedCronJob
});
}
return (
<div className="edit-template edit-cron">
<Helmet>
<title>{`Vesta - ${i18n.CRON}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Editing Cron Job']}</div>
<div className="error">
<span className="error-message">
{state.data.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}
</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout date={state.data.date} time={state.data.time} status={state.data.status}>
{state.loading ? <Spinner /> :
<form onSubmit={event => submitFormHandler(event)} id="edit-cron">
<input type="hidden" name="save" value="save" />
<input type="hidden" name="token" value={token} />
<TextInput id="command" name="v_cmd" title={i18n['Command']} value={state.data.cmd} />
<div className="cron-form-body">
<div className="body-col-1">
<TextInput
value={state.generatedCronJob.h_min}
onChange={changeInput}
title={i18n['Minute']}
name="v_min"
id="h_min" />
<TextInput
value={state.generatedCronJob.h_hour}
onChange={changeInput}
title={i18n['Hour']}
name="v_hour"
id="h_hour" />
<TextInput
value={state.generatedCronJob.h_day}
onChange={changeInput}
title={i18n['Day']}
name="v_day"
id="h_day" />
<TextInput
value={state.generatedCronJob.h_month}
onChange={changeInput}
title={i18n['Month']}
name="v_month"
id="h_month" />
<TextInput
value={state.generatedCronJob.h_wday}
title={i18n['Day of week']}
onChange={changeInput}
name="v_wday"
id="h_wday" />
</div>
<div className="body-col-2">
<Generator mode="edit" job={state.data.job} generatedCronJob={saveGeneratedCronJob} />
</div>
</div>
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Save}</button>
<button type="button" className="back" onClick={() => history.push('/list/cron/')}>{i18n.Back}</button>
</div>
</form>
}
</AddItemLayout>
</div>
);
}
export default EditMail;

View file

@ -0,0 +1,39 @@
.edit-cron {
form {
.form-group {
width: 100%;
input {
width: inherit;
}
}
.form-group.command {
width: 100%;
}
.cron-form-body {
display: flex;
.body-col-1 {
width: 30%;
input {
width: 100%;
}
}
.body-col-2 {
display: flex;
justify-content: flex-start;
align-items: flex-start;
padding: 1.7rem 1rem;
width: 70%;
}
}
}
}
#edit-cron > .form-group input {
width: 95% !important;
}

View file

@ -0,0 +1,74 @@
import React, { useEffect, useRef, useState } from 'react';
import RunCommandSelect from './RunCommandSelect/RunCommandSelect';
import SelectsWrapper from './OtherSelects/SelectsWrapper';
import { Link, useHistory } from 'react-router-dom';
import QS from 'qs';
import './Generator.scss';
const Generator = props => {
const formElement = useRef(null);
const { i18n } = window.GLOBAL.App;
const history = useHistory();
const [state, setState] = useState({
activeTab: '1'
});
useEffect(() => {
let parsedQuery = QS.parse(history.location.search, { ignoreQueryPrefix: true });
let activeTab = parsedQuery.activeTab || '1';
setState({ ...state, activeTab });
}, [history.location.search]);
const activeClassName = tab => {
return state.activeTab === tab ? 'active' : '';
}
const emulateFormSubmit = () => {
let generatedCronJob = {};
for (let i = 0; i <= 4; i++) {
let iterableFormElement = formElement.current[i];
generatedCronJob[iterableFormElement.name] = iterableFormElement.value;
}
props.generatedCronJob(generatedCronJob);
}
const formatLink = tab => {
const { job, mode } = props;
return `/${mode}/cron/?${!!job ? `job=${job}&` : ''}activeTab=${tab}`;
}
return (
<div className="cron-job-generator">
<div className="header">
<Link to={formatLink('1')} className={activeClassName('1')}>{i18n.Minutes}</Link>
<Link to={formatLink('2')} className={activeClassName('2')}>{i18n.Hourly}</Link>
<Link to={formatLink('3')} className={activeClassName('3')}>{i18n.Daily}</Link>
<Link to={formatLink('4')} className={activeClassName('4')}>{i18n.Weekly}</Link>
<Link to={formatLink('5')} className={activeClassName('5')}>{i18n.Monthly}</Link>
</div>
<div className="body">
<form ref={formElement}>
<RunCommandSelect activeTab={state.activeTab} />
<SelectsWrapper activeTab={state.activeTab} />
<div className="form-actions">
<button type="button" onClick={emulateFormSubmit}>{i18n.Generate}</button>
</div>
</form>
</div>
</div>
);
}
export default Generator;

View file

@ -0,0 +1,108 @@
.cron-job-generator {
border: 1px solid #d9d9d9;
padding: 1rem 1.5rem;
margin-left: 2rem;
width: 90%;
.header {
display: flex;
padding: .4rem 0;
a {
color: #222222;
text-transform: uppercase;
font-size: 11px;
font-weight: bolder;
margin-right: 2.3rem;
text-decoration: none;
&:hover {
color: #ff6701;
}
&:active {
color: #55C9C0;
}
}
a.active {
color: #ff6701;
}
}
.body {
padding: 2rem 0 .4rem;
form {
.form-group {
display: flex;
align-items: center;
label {
width: 26%;
font-size: 13px;
margin: 0;
}
input {
width: auto;
}
}
.form-group.minute select {
width: 70px;
}
.form-group select,
.form-group textarea {
border-radius: 0;
&:hover {
border-color: #909090;
}
&:focus,
&:active {
outline: none;
border-color: #55C9C0;
box-shadow: unset;
}
}
.form-group select {
padding: .4rem .25rem;
border-color: #d9d9d9;
color: #555;
}
.form-actions {
button {
background: #55C9C0;
color: white;
border-radius: 3px;
padding: .35rem 1.1rem;
font-size: 14px;
&:hover {
color: #555;
background: #5BD8CF;
}
&:active {
color: white;
background: #D1D70D;
}
}
}
.form-group.show {
display: block;
}
.form-group.hide {
display: none;
}
}
}
}

View file

@ -0,0 +1,45 @@
import React from 'react';
import { dailyMinutesOptions, hoursOptions, dateOptions } from '../../../../ControlPanelService/GeneratorOptions';
export default function FifthTabSelects() {
const { i18n } = window.GLOBAL.App;
const renderDate = () => {
return dateOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
const renderHours = () => {
return hoursOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
const renderMinutes = () => {
return dailyMinutesOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
return (
<div className='fifth-tab-selects'>
<input type="hidden" name="h_wday" value="*" />
<div className="form-group date">
<label htmlFor="run-command">{i18n.Date ?? 'Date'}:</label>
<select className="form-control" name="h_day">
{renderDate()}
</select>
</div>
<div className="form-group hour">
<label htmlFor="run-command">{i18n.Hour ?? 'Hour'}:</label>
<select className="form-control" name="h_hour">
{renderHours()}
</select>
</div>
<div className="form-group minute">
<label htmlFor="run-command">{i18n.Minute ?? 'Minute'}:</label>
<select className="form-control" name="h_min">
{renderMinutes()}
</select>
</div>
</div>
);
}

View file

@ -0,0 +1,35 @@
import React from 'react';
import { dailyMinutesOptions, hoursOptions } from '../../../../ControlPanelService/GeneratorOptions';
export default function FourthTabSelects() {
const { i18n } = window.GLOBAL.App;
const renderHours = () => {
return hoursOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
const renderMinutes = () => {
return dailyMinutesOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
return (
<div className='fourth-tab-selects'>
<input type="hidden" name="h_month" value="*" />
<input type="hidden" name="h_day" value="*" />
<div className="form-group hour">
<label htmlFor="run-command">{i18n.Hour ?? 'Hour'}:</label>
<select className="form-control" name="h_hour">
{renderHours()}
</select>
</div>
<div className="form-group minute">
<label htmlFor="run-command">{i18n.Minute ?? 'Minute'}:</label>
<select className="form-control" name="h_min">
{renderMinutes()}
</select>
</div>
</div>
);
}

View file

@ -0,0 +1,25 @@
import React from 'react';
import { hourlyMinutesOptions } from '../../../../ControlPanelService/GeneratorOptions';
export default function SecondTabSelects() {
const { i18n } = window.GLOBAL.App;
const renderOptions = () => {
return hourlyMinutesOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
return (
<div className='second-tab-selects'>
<input type="hidden" name="h_day" value="*" />
<input type="hidden" name="h_month" value="*" />
<input type="hidden" name="h_wday" value="*" />
<div className="form-group minute">
<label htmlFor="run-command">{i18n.Minute ?? 'Minute'}:</label>
<select className="form-control" name="h_min">
{renderOptions()}
</select>
</div>
</div>
);
}

View file

@ -0,0 +1,44 @@
import React, { useEffect, useState } from 'react';
import SecondTabSelects from './SecondTabSelects';
import ThirdTabSelects from './ThirdTabSelects';
import FourthTabSelects from './FourthTabSelects';
import FifthTabSelects from './FifthTabSelects';
import './SelectsWrapper.scss';
const OtherSelects = props => {
const [state, setState] = useState({
activeTab: ''
});
useEffect(() => {
setState({ ...state, activeTab: props.activeTab });
}, [props.activeTab]);
const renderSelects = () => {
switch (state.activeTab) {
case '1': return (
<div>
<input type="hidden" name="h_hour" value="*" />
<input type="hidden" name="h_day" value="*" />
<input type="hidden" name="h_month" value="*" />
<input type="hidden" name="h_wday" value="*" />
</div>
)
case '2': return <SecondTabSelects />;
case '3': return <ThirdTabSelects />;
case '4': return <FourthTabSelects />;
case '5': return <FifthTabSelects />;
default: break;
}
}
return (
<div className={`tab-${state.activeTab}`}>
{renderSelects()}
</div>
);
}
export default OtherSelects;

View file

@ -0,0 +1,60 @@
.cron-form-body {
.third-tab-selects,
.fourth-tab-selects {
display: flex;
div.hour {
select {
width: 70px;
margin-left: 4.8rem;
}
}
div.minute {
label {
margin: 0;
}
select {
margin: 0;
}
}
}
.fifth-tab-selects {
display: flex;
> div {
width: 10%;
select {
width: 70px;
}
}
> div.date {
margin: 0 0 1rem 0;
}
> div.minute {
width: 200px;
}
> div.hour {
width: 155px;
margin-left: 1rem;
select {
margin-left: .75rem;
}
}
> div.date {
width: 240px;
select {
margin-left: 5.85rem;
}
}
}
}

View file

@ -0,0 +1,35 @@
import React from 'react';
import { dailyMinutesOptions, hoursOptions } from '../../../../ControlPanelService/GeneratorOptions';
export default function ThirdTabSelects() {
const { i18n } = window.GLOBAL.App;
const renderHours = () => {
return hoursOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
const renderOptions = () => {
return dailyMinutesOptions.map((option, index) => <option key={index} value={option.value}>{option.name}</option>);
}
return (
<div className='third-tab-selects'>
<input type="hidden" name="h_month" value="*" />
<input type="hidden" name="h_wday" value="*" />
<div className="form-group hour">
<label htmlFor="run-command">{i18n.Hour ?? 'Hour'}:</label>
<select className="form-control" name="h_hour">
{renderHours()}
</select>
</div>
<div className="form-group minute">
<label htmlFor="run-command">{i18n.Minute ?? 'Minute'}:</label>
<select className="form-control" name="h_min">
{renderOptions()}
</select>
</div>
</div>
);
}

View file

@ -0,0 +1,47 @@
import React, { useEffect, useState } from 'react';
import { daysRunCommandsOptions, hoursRunCommandsOptions, minutesRunCommandsOptions, monthlyRunCommandOptions, weeklyRunCommandOptions } from '../../../../ControlPanelService/GeneratorOptions';
const RunCommandSelect = props => {
const { i18n } = window.GLOBAL.App;
const [state, setState] = useState({
activeTab: ''
});
useEffect(() => {
setState({ ...state, activeTab: props.activeTab });
}, [props]);
const renderOptions = () => {
switch (state.activeTab) {
case '1': return minutesRunCommandsOptions.map(option => <option value={option.value}>{option.name}</option>);
case '2': return hoursRunCommandsOptions.map(option => <option value={option.value}>{option.name}</option>);
case '3': return daysRunCommandsOptions.map(option => <option value={option.value}>{option.name}</option>);
case '4': return weeklyRunCommandOptions.map(option => <option value={option.value}>{option.name}</option>);
case '5': return monthlyRunCommandOptions.map(option => <option value={option.value}>{option.name}</option>);
default: break;
}
}
const selectName = () => {
switch (state.activeTab) {
case '1': return 'h_min';
case '2': return 'h_hour';
case '3': return 'h_day';
case '4': return 'h_wday';
case '5': return 'h_month';
default: break;
}
}
return (
<div class="form-group run-command">
<label htmlFor="run-command">{i18n['Run Command']}:</label>
<select className="form-control" name={selectName()}>
{renderOptions()}
</select>
</div >
);
}
export default RunCommandSelect;

View file

@ -0,0 +1,142 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
import TextInput from 'src/components/ControlPanel/AddItemLayout/Form/TextInput/TextInput';
import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { addMail } from '../../../ControlPanelService/Mail';
import { addDomainNameSystemRecord } from '../../../ControlPanelService/Dns';
import Toolbar from '../../MainNav/Toolbar/Toolbar';
import { useHistory } from 'react-router-dom';
import Spinner from '../../Spinner/Spinner';
import { useDispatch } from 'react-redux';
import './AddDNSRecord.scss'
import { Helmet } from 'react-helmet';
export default function AddDNSRecord(props) {
const { i18n } = window.GLOBAL.App;
const dispatch = useDispatch();
const token = localStorage.getItem("token");
const history = useHistory();
const [state, setState] = useState({
data: {},
selectOptions: [
'A',
'AAAA',
'NS',
'CNAME',
'MX',
'TXT',
'SRV',
'DNSKEY',
'KEY',
'IPSECKEY',
'PTR',
'SPF',
'TLSA',
'CAA'
],
loading: false,
okMessage: '',
errorMessage: '',
});
useEffect(() => {
dispatch(addActiveElement(`/list/dns/`));
dispatch(removeFocusedElement());
}, []);
const submitFormHandler = event => {
event.preventDefault();
let newDnsRecord = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
newDnsRecord[name] = value;
}
newDnsRecord['ok_rec'] = 'add';
newDnsRecord['token'] = token;
newDnsRecord['v_domain'] = props.domain;
if (Object.keys(newDnsRecord).length !== 0 && newDnsRecord.constructor === Object) {
setState({ loading: true });
addDomainNameSystemRecord(newDnsRecord)
.then(result => {
if (result.status === 200) {
const { error_msg, ok_msg } = result.data;
if (error_msg) {
setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
} else if (ok_msg) {
setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
}
}
})
.catch(err => console.error(err));
}
}
return (
<div className="edit-template add-dns-record">
<Helmet>
<title>{`Vesta - ${i18n.DNS}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Adding DNS Record']}</div>
<div className="error">
<span className="error-message">
{state.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
{state.errorMessage}</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
<span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout>
{state.loading ? <Spinner /> : (
<form onSubmit={event => submitFormHandler(event)}>
<TextInput
title={i18n['Domain']}
value={props.domain}
name="v_domain"
id="domain"
disabled />
<TextInput
title={i18n['Record']}
name="v_rec"
id="domain" />
<SelectInput
options={state.selectOptions}
title={i18n['Type']}
name="v_type"
id="type" />
<TextInput
title={i18n['IP or Value']}
name="v_val"
id="val" />
<TextInput
optionalTitle={`(${i18n['optional']})`}
title={i18n['Priority']}
name="v_priority"
id="priority" />
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Add}</button>
<button type="button" className="back" onClick={() => history.push(`/list/dns/?domain=${props.domain}`)}>{i18n.Back}</button>
</div>
</form>
)}
</AddItemLayout>
</div>
);
}

View file

@ -0,0 +1,73 @@
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';
export default function DnsRecord({ data, domain, handleModal, ...props }) {
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const toggleFav = (starred) => {
if (starred) {
props.toggleFav(data.NAME, 'add');
} else {
props.toggleFav(data.NAME, 'delete');
}
}
const checkItem = () => {
props.checkItem(data.NAME);
}
const handleDelete = () => {
handleModal(data.delete_conf, `/delete/dns/?domain=${domain}&record_id=${data.ID}&token=${token}`);
}
return (
<ListItem
id={data.NAME}
date={data.DATE}
toggleFav={toggleFav}
checkItem={checkItem}
starred={data.STARRED}
focused={data.FOCUSED}
checked={data.isChecked}
suspended={data.SUSPENDED === 'yes'}>
<Container className="r-col w-85">
<div className="name">{data.dnsRecord}</div>
<br />
<div className="stats">
<Container className="c-1">
<span className="stat">{`${data.RECORD.substring(0, 12)}${data.RECORD.length > 12 ? '...' : ''}`}</span>
</Container>
<Container className="c-2">
<span className="stat">{data.TYPE}</span>
</Container>
<Container className="c-3">
<span className="stat">{data.PRIORITY}</span>
</Container>
<Container className="c-4 long-value">
<span className="stat">{data.VALUE}</span>
</Container>
</div>
</Container>
<div className="actions">
<div>
<Link className="link-edit" to={`/edit/dns/?domain=${domain}&record_id=${data.ID}`}>
{i18n.edit}
{data.FOCUSED ? <span className="shortcut-button html-unicode">&#8617;</span> : <FontAwesomeIcon icon="pen" />}
</Link>
</div>
<div>
<button className="link-delete" onClick={() => handleDelete()}>
{i18n.Delete}
{data.FOCUSED ? <span className="shortcut-button del">Del</span> : <FontAwesomeIcon icon="times" />}
</button>
</div>
</div>
</ListItem>
);
}

View file

@ -0,0 +1,176 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
import SelectInput from 'src/components/ControlPanel/AddItemLayout/Form/SelectInput/SelectInput';
import TextInput from '../../ControlPanel/AddItemLayout/Form/TextInput/TextInput';
import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
import { updateDNS, getDNSRecordInfo } from 'src/ControlPanelService/Dns';
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 } from 'react-redux';
import QS from 'qs';
import { Helmet } from 'react-helmet';
export default function EditDNSRecord(props) {
const token = localStorage.getItem("token");
const { i18n } = window.GLOBAL.App;
const dispatch = useDispatch();
const history = useHistory();
const [state, setState] = useState({
data: {},
selectOptions: [
'A',
'AAAA',
'NS',
'CNAME',
'MX',
'TXT',
'SRV',
'DNSKEY',
'KEY',
'IPSECKEY',
'PTR',
'SPF',
'TLSA',
'CAA'
],
loading: false,
errorMessage: '',
okMessage: ''
});
useEffect(() => {
const { domain, record_id } = props;
dispatch(addActiveElement('/list/dns/'));
dispatch(removeFocusedElement());
if (domain) {
setState({ ...state, loading: true });
getDNSRecordInfo(domain, record_id)
.then(response => {
setState({
...state,
data: response.data,
errorMessage: response.data['error_msg'],
okMessage: response.data['ok_msg'],
loading: false
});
})
.catch(err => console.error(err));
}
}, []);
const submitFormHandler = event => {
event.preventDefault();
let updatedRecord = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
updatedRecord[name] = value;
}
updatedRecord['v_domain'] = state.data.domain;
updatedRecord['v_rec'] = state.data.record;
updatedRecord['v_type'] = state.data.type;
if (Object.keys(updatedRecord).length !== 0 && updatedRecord.constructor === Object) {
setState({ ...state, loading: true });
updateDNS(updatedRecord, state.data.domain)
.then(result => {
if (result.status === 200) {
const { error_msg, ok_msg } = result.data;
if (error_msg) {
setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
} else if (ok_msg) {
setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
} else {
setState({ ...state, loading: false });
}
}
})
.catch(err => console.error(err));
}
}
return (
<div className="edit-template edit-dns-rec">
<Helmet>
<title>{`Vesta - ${i18n.DNS}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Editing DNS Record']}</div>
<div className="error">
<span className="error-message">
{state.data.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} {state.errorMessage}
</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''} <span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout date={state.data.date} time={state.data.time} status={state.data.status}>
{state.loading ? <Spinner /> :
<form onSubmit={event => submitFormHandler(event)} id="edit-dns-rec">
<input type="hidden" name="save" value="save" />
<input type="hidden" name="token" value={token} />
<TextInput
title={i18n['Domain']}
value={props.domain}
name="v_domain"
id="domain"
disabled />
<TextInput
value={state.data.rec}
title={i18n['Record']}
name="v_rec"
id="domain"
disabled />
<SelectInput
options={state.selectOptions}
selected={state.data.type}
title={i18n['Type']}
name="v_type"
id="type"
disabled />
<TextInput
title={i18n['IP or Value']}
value={state.data.val}
name="v_val"
id="val" />
<TextInput
optionalTitle={`(${i18n['optional']})`}
value={state.data.priority}
title={i18n['Priority']}
name="v_priority"
id="priority" />
<TextInput
optionalTitle={`(${i18n['internal']})`}
title={i18n['Record Number']}
value={state.data.record_id}
name="v_priority"
id="priority" />
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Save}</button>
<button type="button" className="back" onClick={() => history.push(`/list/dns?domain=${props.domain}`)}>{i18n.Back}</button>
</div>
</form>
}
</AddItemLayout>
</div>
);
}

View file

@ -0,0 +1,220 @@
import React, { useEffect, useState } from 'react';
import { addActiveElement, removeFocusedElement } from "../../../actions/MainNavigation/mainNavigationActions";
import { dbCharsets, addDatabase, getDbOptionalInfo } from '../../../ControlPanelService/Db';
import AddItemLayout from '../../ControlPanel/AddItemLayout/AddItemLayout';
import Password from '../../ControlPanel/AddItemLayout/Form/Password/Password';
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 } from 'react-redux';
import './AddDatabase.scss'
import { Helmet } from 'react-helmet';
const AddDatabase = props => {
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const dispatch = useDispatch();
const history = useHistory();
const [state, setState] = useState({
loading: false,
okMessage: '',
errorMessage: '',
dbTypes: [],
dbHosts: [],
dbCharsets: [],
user: '',
maxCharLength: '',
databaseInputValue: '',
databaseUserInputValue: '',
prefixI18N: ''
});
useEffect(() => {
dispatch(addActiveElement('/list/db/'));
dispatch(removeFocusedElement());
setState({ ...state, loading: true });
getDbOptionalInfo()
.then(result => {
if (result.status === 200) {
setState({
...state,
dbCharsets,
user: result.data.user,
dbTypes: result.data.dbTypes,
prefixI18N: result.data.prefixI18N,
maxCharLength: result.data.maxCharLength,
dbHosts: result.data.dbHosts,
loading: false
});
}
})
.catch(err => console.err(err));
}, []);
const renderDatabaseTypesOptions = () => {
return state.dbTypes.map((dbType, index) => <option key={index} value={dbType}>{dbType}</option>)
}
const renderDatabaseHostsOptions = () => {
return state.dbHosts.map((dbHost, index) => <option key={index} value={dbHost}>{dbHost}</option>)
}
const renderDatabaseCharsetOptions = () => {
return state.dbCharsets.map((dbCharset, index) =>
<option
key={index}
value={dbCharset}
selected={dbCharset === 'utf8'}>
{dbCharset}
</option>
);
}
const databaseUserInputHandler = value => {
setState({ ...state, databaseUserInputValue: value });
}
const databaseInputHandler = value => {
setState({ ...state, databaseInputValue: value });
}
const submitFormHandler = event => {
event.preventDefault();
let newDatabase = {};
for (var [name, value] of (new FormData(event.target)).entries()) {
newDatabase[name] = value;
}
newDatabase['v_database'] = `${state.user}_${state.databaseInputValue}`;
newDatabase['v_dbuser'] = `${state.user}_${state.databaseUserInputValue}`;
if (Object.keys(newDatabase).length !== 0 && newDatabase.constructor === Object) {
setState({ ...state, loading: true });
addDatabase(newDatabase)
.then(result => {
if (result.status === 200) {
const { error_msg, ok_msg } = result.data;
if (error_msg) {
setState({ ...state, errorMessage: error_msg, okMessage: '', loading: false });
} else if (ok_msg) {
setState({ ...state, errorMessage: '', okMessage: ok_msg, loading: false });
} else {
setState({ ...state, loading: false })
}
}
})
.catch(err => console.error(err));
}
}
return (
<div className="edit-template add-db">
<Helmet>
<title>{`Vesta - ${i18n.DB}`}</title>
</Helmet>
<Toolbar mobile={false}>
<div></div>
<div className="search-toolbar-name">{i18n['Adding database']}</div>
<div className="error">
<span className="error-message">
{state.errorMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
{state.errorMessage}</span>
</div>
<div className="success">
<span className="ok-message">
{state.okMessage ? <FontAwesomeIcon icon="long-arrow-alt-right" /> : ''}
<span dangerouslySetInnerHTML={{ __html: state.okMessage }}></span>
</span>
</div>
</Toolbar>
<AddItemLayout>
{state.loading ? <Spinner /> : (
<form onSubmit={event => submitFormHandler(event)}>
<input type="hidden" name="ok" value="add" />
<input type="hidden" name="token" value={token} />
<span className="prefix" dangerouslySetInnerHTML={{ __html: state.prefixI18N }}></span>
<div className="form-group database">
<label htmlFor="database">{i18n.Database}</label>
<div className="input-wrapper">
<input
type="text"
className="form-control"
id="database"
onChange={event => databaseInputHandler(event.target.value)}
value={state.databaseInputValue}
name="v_database" />
<span className="italic">{`${state.user}_${state.databaseInputValue}`}</span>
</div>
</div>
<div className="form-group">
<div className="label-wrapper">
<label htmlFor="user">{i18n.User}</label>
<span className="italic">({state.maxCharLength})</span>
</div>
<div className="input-wrapper">
<input
type="text"
className="form-control"
id="user"
value={state.databaseUserInputValue}
onChange={event => databaseUserInputHandler(event.target.value)}
name="v_dbuser" />
<span className="italic">{`${state.user}_${state.databaseUserInputValue}`}</span>
</div>
</div>
<Password name={'v_password'} />
<div class="form-group">
<label htmlFor="dbTypes">{i18n.Type}</label>
<select class="form-control" id="dbTypes" name="v_type">
{renderDatabaseTypesOptions()}
</select>
</div>
<div class="form-group">
<label htmlFor="dbHosts">{i18n.Host}</label>
<select class="form-control" id="dbHosts" name="v_host">
{renderDatabaseHostsOptions()}
</select>
</div>
<div class="form-group">
<label htmlFor="dbCharset">{i18n.Charset}</label>
<select class="form-control" id="dbCharset" name="v_charset">
{renderDatabaseCharsetOptions()}
</select>
</div>
<div className="form-group">
<label htmlFor="sendLoginCredentialsToEmailAddress">{i18n['Send login credentials to email address']}</label>
<input
type="email"
className="form-control"
id="sendLoginCredentialsToEmailAddress"
name="v_db_email" />
</div>
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Add}</button>
<button type="button" className="back" onClick={() => history.push('/list/db/')}>{i18n.Back}</button>
</div>
</form>
)}
</AddItemLayout>
</div>
);
}
export default AddDatabase;

View file

@ -0,0 +1,45 @@
.edit-template.add-db {
form {
span.italic {
font-style: italic;
}
.form-group {
.label-wrapper {
display: flex;
span.italic {
margin-left: 1.5rem;
color: #555;
}
}
.input-wrapper {
display: flex;
align-items: center;
span.italic {
font-size: 15px;
font-weight: bold;
margin-left: 1.5rem;
color: #777;
}
}
.input-wrapper {
span.italic {
font-weight: normal;
}
}
}
.form-group.database {
margin-top: 1rem;
}
span.prefix {
color: #777;
font-style: italic;
}
}
}

View file

@ -0,0 +1,89 @@
import React from 'react';
import ListItem from '../ControlPanel/ListItem/ListItem';
import Container from '../ControlPanel/Container/Container';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'react-router-dom';
const Database = props => {
const { data } = props;
const { i18n } = window.GLOBAL.App;
const token = localStorage.getItem("token");
const toggleFav = (starred) => {
if (starred) {
props.toggleFav(data.NAME, 'add');
} else {
props.toggleFav(data.NAME, 'delete');
}
}
const checkItem = () => {
props.checkItem(data.NAME);
}
const handleSuspend = () => {
let suspendedStatus = data.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend' === 'yes' ? 'unsuspend' : 'suspend';
props.handleModal(data.suspend_conf, `/${suspendedStatus}/db/?database=${data.NAME}&token=${token}`);
}
const handleDelete = () => {
props.handleModal(data.delete_conf, `/delete/db/?database=${data.NAME}&token=${token}`);
}
return (
<ListItem
id={data.NAME}
date={data.DATE}
toggleFav={toggleFav}
checkItem={checkItem}
starred={data.STARRED}
focused={data.FOCUSED}
checked={data.isChecked}
suspended={data.SUSPENDED === 'yes'}>
<Container className="r-col w-85">
<div className="name">{data.DATABASE}</div>
<br />
<div className="stats">
<Container className="c-1">
<div className="disk">{i18n.Disk}: <span><span className="stat">{data.U_DISK}</span>{i18n.mb}</span></div>
</Container>
<Container className="c-2">
<div>{i18n.User}: <span className="stat">{data.DBUSER}</span></div>
<div>{i18n.Charset}: <span className="stat">{data.CHARSET}</span></div>
</Container>
<Container className="c-3">
<div>{i18n.Host}: <span className="stat">{data.HOST}</span></div>
<div>{i18n.Type}: <span className="stat">{data.TYPE}</span></div>
</Container>
</div>
</Container>
<div className="actions">
<div>
<Link className="link-edit" to={`/edit/db/?database=${data.NAME}`}>
{i18n.edit}
{data.FOCUSED ? <span className="shortcut-button html-unicode">&#8617;</span> : <FontAwesomeIcon icon="pen" />}
</Link>
</div>
<div>
<button
className="link-gray"
onClick={() => handleSuspend()}>
{i18n[data.suspend_action]}
{data.FOCUSED ? <span className="shortcut-button">S</span> : <FontAwesomeIcon icon={data.SUSPENDED === 'yes' ? 'unlock' : 'lock'} />}
</button>
</div>
<div>
<button className="link-delete" onClick={() => handleDelete()}>
{i18n.Delete}
{data.FOCUSED ? <span className="shortcut-button del">Del</span> : <FontAwesomeIcon icon="times" />}
</button>
</div>
</div>
</ListItem>
);
}
export default Database;

View file

@ -0,0 +1,3 @@
.r-col .c-2.w-45 {
margin: 15px 1rem 0 5rem;
}

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