Backups.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import React, { useState, useEffect } from 'react';
  2. import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
  3. import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
  4. import { bulkAction, getBackupList, handleAction, scheduleBackup } from '../../ControlPanelService/Backup';
  5. import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
  6. import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
  7. import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
  8. import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
  9. import Select from '../../components/MainNav/Toolbar/Select/Select';
  10. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
  11. import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
  12. import Modal from '../../components/ControlPanel/Modal/Modal';
  13. import Spinner from '../../components/Spinner/Spinner';
  14. import { useSelector, useDispatch } from 'react-redux';
  15. import Backup from '../../components/Backup/Backup';
  16. import { Helmet } from 'react-helmet';
  17. import { Link } from 'react-router-dom';
  18. import './Backups.scss';
  19. import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
  20. const Backups = props => {
  21. const { i18n } = useSelector(state => state.session);
  22. const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
  23. const { focusedElement } = useSelector(state => state.mainNavigation);
  24. const dispatch = useDispatch();
  25. const [loading, setLoading] = useState(false);
  26. const [modal, setModal] = useState({
  27. text: '',
  28. visible: false,
  29. actionUrl: ''
  30. });
  31. const [state, setState] = useState({
  32. backups: [],
  33. backupFav: [],
  34. toggledAll: false,
  35. selection: [],
  36. totalAmount: ''
  37. });
  38. useEffect(() => {
  39. dispatch(addActiveElement('/list/backup/'));
  40. dispatch(removeFocusedElement());
  41. dispatch(removeControlPanelContentFocusedElement());
  42. fetchData().then(() => setLoading(false));
  43. return () => {
  44. dispatch(removeControlPanelContentFocusedElement());
  45. }
  46. }, []);
  47. useEffect(() => {
  48. window.addEventListener("keydown", handleContentSelection);
  49. window.addEventListener("keydown", handleFocusedElementShortcuts);
  50. return () => {
  51. window.removeEventListener("keydown", handleContentSelection);
  52. window.removeEventListener("keydown", handleFocusedElementShortcuts);
  53. };
  54. }, [controlPanelFocusedElement, focusedElement, state.backups]);
  55. const handleContentSelection = event => {
  56. if (event.keyCode === 38 || event.keyCode === 40) {
  57. if (focusedElement) {
  58. dispatch(MainNavigation.removeFocusedElement());
  59. }
  60. }
  61. if (event.keyCode === 38) {
  62. event.preventDefault();
  63. handleArrowUp();
  64. } else if (event.keyCode === 40) {
  65. event.preventDefault();
  66. handleArrowDown();
  67. }
  68. }
  69. const initFocusedElement = backups => {
  70. backups[0]['FOCUSED'] = backups[0]['NAME'];
  71. setState({ ...state, backups });
  72. dispatch(addControlPanelContentFocusedElement(backups[0]['NAME']));
  73. }
  74. const handleArrowDown = () => {
  75. let backups = [...state.backups];
  76. if (focusedElement) {
  77. MainNavigation.removeFocusedElement();
  78. }
  79. if (controlPanelFocusedElement === '') {
  80. initFocusedElement(backups);
  81. return;
  82. }
  83. let focusedElementPosition = backups.findIndex(backup => backup.NAME === controlPanelFocusedElement);
  84. if (focusedElementPosition !== backups.length - 1) {
  85. let nextFocusedElement = backups[focusedElementPosition + 1];
  86. backups[focusedElementPosition]['FOCUSED'] = '';
  87. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  88. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  89. setState({ ...state, backups });
  90. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  91. }
  92. }
  93. const handleArrowUp = () => {
  94. let backups = [...state.backups];
  95. if (focusedElement) {
  96. MainNavigation.removeFocusedElement();
  97. }
  98. if (controlPanelFocusedElement === '') {
  99. initFocusedElement(backups);
  100. return;
  101. }
  102. let focusedElementPosition = backups.findIndex(backup => backup.NAME === controlPanelFocusedElement);
  103. if (focusedElementPosition !== 0) {
  104. let nextFocusedElement = backups[focusedElementPosition - 1];
  105. backups[focusedElementPosition]['FOCUSED'] = '';
  106. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  107. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  108. setState({ ...state, backups });
  109. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  110. }
  111. }
  112. const handleFocusedElementShortcuts = event => {
  113. let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
  114. if (controlPanelFocusedElement && !isSearchInputFocused) {
  115. switch (event.keyCode) {
  116. case 8: return handleDelete();
  117. case 13: return configureRestoreSettings();
  118. case 68: return download();
  119. default: break;
  120. }
  121. }
  122. }
  123. const configureRestoreSettings = () => {
  124. props.history.push(`/list/backup?backup=${controlPanelFocusedElement}`);
  125. }
  126. const download = () => {
  127. window.open(`/api/v1/download/backup?backup=${controlPanelFocusedElement}`);
  128. }
  129. const handleDelete = () => {
  130. const { backups } = state;
  131. let currentBackupData = backups.filter(backup => backup.NAME === controlPanelFocusedElement)[0];
  132. displayModal(currentBackupData.delete_conf, `/api/v1/delete/cron/?job=${controlPanelFocusedElement}`);
  133. }
  134. const fetchData = () => {
  135. setLoading(true);
  136. return new Promise((resolve, reject) => {
  137. getBackupList()
  138. .then(result => {
  139. setState({
  140. ...state,
  141. backups: reformatData(result.data.data),
  142. backupFav: result.data.backup_fav,
  143. totalAmount: result.data.totalAmount,
  144. selection: [],
  145. toggledAll: false
  146. });
  147. resolve();
  148. })
  149. .catch(err => console.error(err));
  150. });
  151. }
  152. const reformatData = data => {
  153. let backups = [];
  154. for (let i in data) {
  155. data[i]['NAME'] = i;
  156. data[i]['isChecked'] = false;
  157. data[i]['FOCUSED'] = controlPanelFocusedElement === i;
  158. backups.push(data[i]);
  159. }
  160. return backups;
  161. }
  162. const backups = () => {
  163. const { backups } = state;
  164. const result = [];
  165. const backupFav = { ...state.backupFav };
  166. backups.forEach(backup => {
  167. backup.FOCUSED = controlPanelFocusedElement === backup.NAME;
  168. if (backupFav[backup.NAME]) {
  169. backup.STARRED = backupFav[backup.NAME];
  170. } else {
  171. backup.STARRED = 0;
  172. }
  173. result.push(backup);
  174. });
  175. return result.map((item, index) => {
  176. return <Backup data={item} key={index} toggleFav={toggleFav} checkItem={checkItem} handleModal={displayModal} />;
  177. });
  178. }
  179. const checkItem = name => {
  180. const { selection, backups } = state;
  181. let duplicate = [...selection];
  182. let backupDuplicate = [...backups];
  183. let checkedItem = duplicate.indexOf(name);
  184. let incomingItem = backupDuplicate.findIndex(backup => backup.NAME === name);
  185. backupDuplicate[incomingItem].isChecked = !backupDuplicate[incomingItem].isChecked;
  186. if (checkedItem !== -1) {
  187. duplicate.splice(checkedItem, 1);
  188. } else {
  189. duplicate.push(name);
  190. }
  191. setState({ ...state, backups: backupDuplicate, selection: duplicate });
  192. }
  193. const toggleFav = (value, type) => {
  194. const { backupFav } = state;
  195. let backupFavDuplicate = backupFav;
  196. if (type === 'add') {
  197. backupFavDuplicate[value] = 1;
  198. addFavorite(value, 'backup')
  199. .then(() => {
  200. setState({ ...state, backupFav: backupFavDuplicate });
  201. })
  202. .catch(err => {
  203. console.error(err);
  204. });
  205. } else {
  206. backupFavDuplicate[value] = undefined;
  207. deleteFavorite(value, 'backup')
  208. .then(() => {
  209. setState({ ...state, backupFav: backupFavDuplicate });
  210. })
  211. .catch(err => {
  212. console.error(err);
  213. });
  214. }
  215. }
  216. const toggleAll = toggled => {
  217. const backupsDuplicate = [...state.backups];
  218. if (toggled) {
  219. let backupNames = [];
  220. let backups = backupsDuplicate.map(backup => {
  221. backupNames.push(backup.NAME);
  222. backup.isChecked = true;
  223. return backup;
  224. });
  225. setState({ ...state, backups, selection: backupNames, toggledAll: toggled });
  226. } else {
  227. let backups = backupsDuplicate.map(backup => {
  228. backup.isChecked = false;
  229. return backup;
  230. });
  231. setState({ ...state, backups, selection: [], toggledAll: toggled });
  232. }
  233. }
  234. const bulk = action => {
  235. const { selection } = state;
  236. if (selection.length && action) {
  237. setLoading(true);
  238. bulkAction(action, selection)
  239. .then(result => {
  240. if (result.status === 200) {
  241. fetchData().then(() => {
  242. refreshMenuCounters();
  243. toggleAll(false);
  244. });
  245. }
  246. })
  247. .catch(err => console.error(err));
  248. }
  249. }
  250. const displayModal = (text, url) => {
  251. setLoading(false);
  252. setModal({
  253. ...modal,
  254. visible: true,
  255. text: text,
  256. actionUrl: url
  257. });
  258. }
  259. const modalConfirmHandler = () => {
  260. if (!modal.actionUrl) {
  261. return modalCancelHandler();
  262. }
  263. modalCancelHandler();
  264. setLoading(true);
  265. handleAction(modal.actionUrl)
  266. .then(res => {
  267. if (res.data.error) {
  268. setLoading(false);
  269. return displayModal(res.data.error, '');
  270. }
  271. fetchData().then(() => refreshMenuCounters())
  272. })
  273. .catch(err => { setLoading(false); console.error(err); });
  274. }
  275. const refreshMenuCounters = () => {
  276. dispatch(refreshCounters()).then(() => setLoading(false));
  277. }
  278. const modalCancelHandler = () => {
  279. setModal({
  280. ...modal,
  281. visible: false,
  282. text: '',
  283. actionUrl: ''
  284. });
  285. }
  286. const scheduleBackupButton = () => {
  287. setLoading(true);
  288. scheduleBackup()
  289. .then(result => {
  290. if (result.data.error) {
  291. displayModal(result.data.error, '');
  292. } else {
  293. displayModal(result.data.ok, '');
  294. }
  295. })
  296. .catch(err => console.error(err));
  297. }
  298. return (
  299. <div className="backups">
  300. <Helmet>
  301. <title>{`Vesta - ${i18n.BACKUP}`}</title>
  302. </Helmet>
  303. <Toolbar mobile={false} >
  304. <div className="l-menu">
  305. <button onClick={scheduleBackupButton}>
  306. <FontAwesomeIcon icon="plus" />
  307. <span className="add">{i18n["Create Backup"]}</span>
  308. </button>
  309. </div>
  310. <div className="r-menu">
  311. <div className="input-group input-group-sm">
  312. <Link to='/list/backup/exclusions' className="button-extra" type="submit">{i18n['backup exclusions']}</Link>
  313. <Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
  314. <Select list='backupList' bulkAction={bulk} />
  315. <SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />
  316. </div>
  317. </div>
  318. </Toolbar>
  319. <div className="backups-wrapper">
  320. {loading
  321. ? <Spinner />
  322. : (<>
  323. {backups()}
  324. <div className="total">{state.totalAmount}</div>
  325. </>)}
  326. </div>
  327. <Modal
  328. onSave={modalConfirmHandler}
  329. onCancel={modalCancelHandler}
  330. show={modal.visible}
  331. text={modal.text} />
  332. </div>
  333. );
  334. }
  335. export default Backups;