mirror of
https://github.com/serghey-rodin/vesta.git
synced 2025-08-14 02:28:03 -07:00
React js integration with vesta control panel.
This commit is contained in:
parent
4c6dd0d719
commit
c12c21ec8b
338 changed files with 43501 additions and 0 deletions
68
web/js/react/README.md
Executable file
68
web/js/react/README.md
Executable 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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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
|
6
web/js/react/jsconfig.json
Normal file
6
web/js/react/jsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
14969
web/js/react/package-lock.json
generated
Normal file
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
51
web/js/react/package.json
Normal 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
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
26
web/js/react/public/index.html
Executable 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>
|
15
web/js/react/public/manifest.json
Executable file
15
web/js/react/public/manifest.json
Executable 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"
|
||||
}
|
79
web/js/react/src/ControlPanelService/Backup.js
Normal file
79
web/js/react/src/ControlPanelService/Backup.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
64
web/js/react/src/ControlPanelService/Cron.js
Normal file
64
web/js/react/src/ControlPanelService/Cron.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
106
web/js/react/src/ControlPanelService/Db.js
Normal file
106
web/js/react/src/ControlPanelService/Db.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
80
web/js/react/src/ControlPanelService/Dns.js
Normal file
80
web/js/react/src/ControlPanelService/Dns.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
23
web/js/react/src/ControlPanelService/Favorites.js
Normal file
23
web/js/react/src/ControlPanelService/Favorites.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
96
web/js/react/src/ControlPanelService/Firewalls.js
Normal file
96
web/js/react/src/ControlPanelService/Firewalls.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
145
web/js/react/src/ControlPanelService/GeneratorOptions.js
Normal file
145
web/js/react/src/ControlPanelService/GeneratorOptions.js
Normal 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' }
|
||||
];
|
68
web/js/react/src/ControlPanelService/Ip.js
Normal file
68
web/js/react/src/ControlPanelService/Ip.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
46
web/js/react/src/ControlPanelService/Languages.js
Normal file
46
web/js/react/src/ControlPanelService/Languages.js
Normal 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"
|
||||
];
|
8
web/js/react/src/ControlPanelService/Logs.js
Normal file
8
web/js/react/src/ControlPanelService/Logs.js
Normal 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);
|
||||
}
|
127
web/js/react/src/ControlPanelService/Mail.js
Normal file
127
web/js/react/src/ControlPanelService/Mail.js
Normal 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',
|
||||
}
|
||||
];
|
18
web/js/react/src/ControlPanelService/Notifications.js
Normal file
18
web/js/react/src/ControlPanelService/Notifications.js
Normal 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")
|
||||
}
|
||||
});
|
||||
}
|
67
web/js/react/src/ControlPanelService/Package.js
Normal file
67
web/js/react/src/ControlPanelService/Package.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
12
web/js/react/src/ControlPanelService/RRD.js
Normal file
12
web/js/react/src/ControlPanelService/RRD.js
Normal 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`;
|
||||
}
|
26
web/js/react/src/ControlPanelService/ResetPassword.js
Normal file
26
web/js/react/src/ControlPanelService/ResetPassword.js
Normal 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);
|
||||
};
|
12
web/js/react/src/ControlPanelService/Search.js
Normal file
12
web/js/react/src/ControlPanelService/Search.js
Normal 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);
|
||||
}
|
72
web/js/react/src/ControlPanelService/Select.js
Normal file
72
web/js/react/src/ControlPanelService/Select.js
Normal 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 }
|
||||
]
|
||||
}
|
||||
};
|
76
web/js/react/src/ControlPanelService/Server.js
Normal file
76
web/js/react/src/ControlPanelService/Server.js
Normal 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',
|
||||
];
|
8
web/js/react/src/ControlPanelService/Statistics.js
Normal file
8
web/js/react/src/ControlPanelService/Statistics.js
Normal 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);
|
||||
}
|
35
web/js/react/src/ControlPanelService/Updates.js
Normal file
35
web/js/react/src/ControlPanelService/Updates.js
Normal 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}`);
|
||||
};
|
8
web/js/react/src/ControlPanelService/UserNS.js
Normal file
8
web/js/react/src/ControlPanelService/UserNS.js
Normal 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);
|
||||
}
|
65
web/js/react/src/ControlPanelService/Users.js
Normal file
65
web/js/react/src/ControlPanelService/Users.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
67
web/js/react/src/ControlPanelService/Web.js
Normal file
67
web/js/react/src/ControlPanelService/Web.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
7
web/js/react/src/ControlPanelService/WebLogs.js
Normal file
7
web/js/react/src/ControlPanelService/WebLogs.js
Normal 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);
|
||||
}
|
119
web/js/react/src/FileManagerHelper.js
Normal file
119
web/js/react/src/FileManagerHelper.js
Normal 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);
|
||||
}
|
|
@ -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: ''
|
||||
};
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export const ADD_CPANEL_FOCUSED_ELEMENT = 'ADD_CPANEL_FOCUSED_ELEMENT';
|
||||
export const REMOVE_CPANEL_FOCUSED_ELEMENT = 'REMOVE_CPANEL_FOCUSED_ELEMENT';
|
|
@ -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: ''
|
||||
};
|
||||
};
|
|
@ -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';
|
164
web/js/react/src/actions/Session/sessionActions.js
Normal file
164
web/js/react/src/actions/Session/sessionActions.js
Normal 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: ''
|
||||
}
|
||||
}
|
||||
}
|
5
web/js/react/src/actions/Session/sessionTypes.js
Normal file
5
web/js/react/src/actions/Session/sessionTypes.js
Normal 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';
|
84
web/js/react/src/components/Backup/Backup.jsx
Normal file
84
web/js/react/src/components/Backup/Backup.jsx
Normal 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">↩</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;
|
7
web/js/react/src/components/Backup/Backup.scss
Normal file
7
web/js/react/src/components/Backup/Backup.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.backups-wrapper .list-item .c-1 div > span {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.backups-restore-details .list-item .star {
|
||||
display: none;
|
||||
}
|
135
web/js/react/src/components/Backup/Exclusion/Edit/index.jsx
Normal file
135
web/js/react/src/components/Backup/Exclusion/Edit/index.jsx
Normal 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;
|
|
@ -0,0 +1,3 @@
|
|||
.content .edit-backup-exclusions .toolbar .search-toolbar-name {
|
||||
width: auto;
|
||||
}
|
30
web/js/react/src/components/Backup/Exclusion/index.jsx
Normal file
30
web/js/react/src/components/Backup/Exclusion/index.jsx
Normal 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> {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;
|
33
web/js/react/src/components/Backup/Exclusion/style.scss
Normal file
33
web/js/react/src/components/Backup/Exclusion/style.scss
Normal 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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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} {months[month]} {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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,7 @@
|
|||
.form-group.select-group {
|
||||
label {
|
||||
span {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,11 @@
|
|||
.form-group.text-on-the-right {
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: 15px;
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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%;
|
||||
}
|
106
web/js/react/src/components/ControlPanel/Hotkeys/Hotkeys.jsx
Normal file
106
web/js/react/src/components/ControlPanel/Hotkeys/Hotkeys.jsx
Normal 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">←</span>
|
||||
<span className="description">{i18n['Move backward through top menu']}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="name">→</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">↑</span>
|
||||
<span className="description">{i18n['Move up through elements list']}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="name">↓</span>
|
||||
<span className="description">{i18n['Move down through elements list']}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Hotkeys;
|
|
@ -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;
|
||||
}
|
100
web/js/react/src/components/ControlPanel/ListItem/ListItem.jsx
Normal file
100
web/js/react/src/components/ControlPanel/ListItem/ListItem.jsx
Normal 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} {months[month - 1]} {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;
|
148
web/js/react/src/components/ControlPanel/ListItem/ListItem.scss
Normal file
148
web/js/react/src/components/ControlPanel/ListItem/ListItem.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,4 @@
|
|||
.login-layout {
|
||||
margin-top: 1rem;
|
||||
padding: 2rem 3rem;
|
||||
}
|
32
web/js/react/src/components/ControlPanel/Modal/Modal.jsx
Normal file
32
web/js/react/src/components/ControlPanel/Modal/Modal.jsx
Normal 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">×</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;
|
18
web/js/react/src/components/ControlPanel/Modal/Modal.scss
Normal file
18
web/js/react/src/components/ControlPanel/Modal/Modal.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
198
web/js/react/src/components/CronJob/Add/AddCronJob.jsx
Normal file
198
web/js/react/src/components/CronJob/Add/AddCronJob.jsx
Normal 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;
|
31
web/js/react/src/components/CronJob/Add/AddCronJob.scss
Normal file
31
web/js/react/src/components/CronJob/Add/AddCronJob.scss
Normal 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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
95
web/js/react/src/components/CronJob/CronJob.jsx
Normal file
95
web/js/react/src/components/CronJob/CronJob.jsx
Normal 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">↩</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;
|
13
web/js/react/src/components/CronJob/CronJob.scss
Normal file
13
web/js/react/src/components/CronJob/CronJob.scss
Normal 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;
|
||||
}
|
||||
}
|
200
web/js/react/src/components/CronJob/Edit/EditCronJob.jsx
Normal file
200
web/js/react/src/components/CronJob/Edit/EditCronJob.jsx
Normal 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;
|
39
web/js/react/src/components/CronJob/Edit/EditCronJob.scss
Normal file
39
web/js/react/src/components/CronJob/Edit/EditCronJob.scss
Normal 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;
|
||||
}
|
74
web/js/react/src/components/CronJob/Generator/Generator.jsx
Normal file
74
web/js/react/src/components/CronJob/Generator/Generator.jsx
Normal 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;
|
108
web/js/react/src/components/CronJob/Generator/Generator.scss
Normal file
108
web/js/react/src/components/CronJob/Generator/Generator.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
142
web/js/react/src/components/DNSRecord/Add/AddDNSRecord.jsx
Normal file
142
web/js/react/src/components/DNSRecord/Add/AddDNSRecord.jsx
Normal 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>
|
||||
);
|
||||
}
|
73
web/js/react/src/components/DNSRecord/DNSRecord.jsx
Normal file
73
web/js/react/src/components/DNSRecord/DNSRecord.jsx
Normal 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">↩</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>
|
||||
);
|
||||
}
|
0
web/js/react/src/components/DNSRecord/DNSRecord.scss
Normal file
0
web/js/react/src/components/DNSRecord/DNSRecord.scss
Normal file
176
web/js/react/src/components/DNSRecord/Edit/EditDNSRecord.jsx
Normal file
176
web/js/react/src/components/DNSRecord/Edit/EditDNSRecord.jsx
Normal 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>
|
||||
);
|
||||
}
|
220
web/js/react/src/components/Database/Add/AddDatabase.jsx
Normal file
220
web/js/react/src/components/Database/Add/AddDatabase.jsx
Normal 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;
|
45
web/js/react/src/components/Database/Add/AddDatabase.scss
Normal file
45
web/js/react/src/components/Database/Add/AddDatabase.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
89
web/js/react/src/components/Database/Database.jsx
Normal file
89
web/js/react/src/components/Database/Database.jsx
Normal 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">↩</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;
|
3
web/js/react/src/components/Database/Database.scss
Normal file
3
web/js/react/src/components/Database/Database.scss
Normal 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
Loading…
Add table
Add a link
Reference in a new issue