Databases.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
  5. import { bulkAction, getDatabaseList, handleAction } from '../../ControlPanelService/Db';
  6. import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
  7. import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
  8. import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
  9. import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
  10. import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
  11. import Select from '../../components/MainNav/Toolbar/Select/Select';
  12. import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
  13. import Modal from '../../components/ControlPanel/Modal/Modal';
  14. import Database from '../../components/Database/Database';
  15. import { useSelector, useDispatch } from 'react-redux';
  16. import Spinner from '../../components/Spinner/Spinner';
  17. import './Databases.scss';
  18. import { Helmet } from 'react-helmet';
  19. import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
  20. const Databases = 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. databases: [],
  33. dbFav: [],
  34. toggledAll: false,
  35. dbAdmin: '',
  36. dbAdminLink: '',
  37. sorting: i18n.Date,
  38. order: "descending",
  39. selection: [],
  40. totalAmount: ''
  41. });
  42. useEffect(() => {
  43. dispatch(addActiveElement('/list/db/'));
  44. dispatch(removeFocusedElement());
  45. dispatch(removeControlPanelContentFocusedElement());
  46. fetchData().then(() => setLoading(false));
  47. return () => {
  48. dispatch(removeControlPanelContentFocusedElement());
  49. }
  50. }, []);
  51. useEffect(() => {
  52. window.addEventListener("keydown", handleContentSelection);
  53. window.addEventListener("keydown", handleFocusedElementShortcuts);
  54. return () => {
  55. window.removeEventListener("keydown", handleContentSelection);
  56. window.removeEventListener("keydown", handleFocusedElementShortcuts);
  57. };
  58. }, [controlPanelFocusedElement, focusedElement, state.databases]);
  59. const handleContentSelection = event => {
  60. if (event.keyCode === 38 || event.keyCode === 40) {
  61. if (focusedElement) {
  62. dispatch(MainNavigation.removeFocusedElement());
  63. }
  64. }
  65. if (event.keyCode === 38) {
  66. event.preventDefault();
  67. handleArrowUp();
  68. } else if (event.keyCode === 40) {
  69. event.preventDefault();
  70. handleArrowDown();
  71. }
  72. }
  73. const initFocusedElement = databases => {
  74. databases[0]['FOCUSED'] = databases[0]['NAME'];
  75. setState({ ...state, databases });
  76. dispatch(addControlPanelContentFocusedElement(databases[0]['NAME']));
  77. }
  78. const handleArrowDown = () => {
  79. let databases = [...state.databases];
  80. if (focusedElement) {
  81. MainNavigation.removeFocusedElement();
  82. }
  83. if (controlPanelFocusedElement === '') {
  84. initFocusedElement(databases);
  85. return;
  86. }
  87. let focusedElementPosition = databases.findIndex(database => database.NAME === controlPanelFocusedElement);
  88. if (focusedElementPosition !== databases.length - 1) {
  89. let nextFocusedElement = databases[focusedElementPosition + 1];
  90. databases[focusedElementPosition]['FOCUSED'] = '';
  91. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  92. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  93. setState({ ...state, databases });
  94. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  95. }
  96. }
  97. const handleArrowUp = () => {
  98. let databases = [...state.databases];
  99. if (focusedElement) {
  100. MainNavigation.removeFocusedElement();
  101. }
  102. if (controlPanelFocusedElement === '') {
  103. initFocusedElement(databases);
  104. return;
  105. }
  106. let focusedElementPosition = databases.findIndex(database => database.NAME === controlPanelFocusedElement);
  107. if (focusedElementPosition !== 0) {
  108. let nextFocusedElement = databases[focusedElementPosition - 1];
  109. databases[focusedElementPosition]['FOCUSED'] = '';
  110. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  111. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  112. setState({ ...state, databases });
  113. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  114. }
  115. }
  116. const handleFocusedElementShortcuts = event => {
  117. let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
  118. if (controlPanelFocusedElement && !isSearchInputFocused) {
  119. switch (event.keyCode) {
  120. case 8: return handleDelete();
  121. case 13: return handleEdit();
  122. case 83: return handleSuspend();
  123. default: break;
  124. }
  125. }
  126. }
  127. const handleEdit = () => {
  128. props.history.push(`/edit/database?domain=${controlPanelFocusedElement}`);
  129. }
  130. const handleSuspend = () => {
  131. const { databases } = state;
  132. let currentDatabaseData = databases.filter(database => database.NAME === controlPanelFocusedElement)[0];
  133. let suspendedStatus = currentDatabaseData.SUSPENDED === 'yes' ? 'unsuspend' : 'suspend';
  134. displayModal(currentDatabaseData.suspend_conf, `/api/v1/${suspendedStatus}/database/index.php?domain=${controlPanelFocusedElement}`);
  135. }
  136. const handleDelete = () => {
  137. const { databases } = state;
  138. let currentDatabaseData = databases.filter(database => database.NAME === controlPanelFocusedElement)[0];
  139. displayModal(currentDatabaseData.delete_conf, `/api/v1/delete/database/index.php?domain=${controlPanelFocusedElement}`);
  140. }
  141. const fetchData = () => {
  142. setLoading(true);
  143. return new Promise((resolve, reject) => {
  144. getDatabaseList()
  145. .then(result => {
  146. setState({
  147. ...state,
  148. databases: reformatData(result.data.data),
  149. dbAdmin: result.data.db_admin,
  150. dbAdminLink: result.data.db_admin_link,
  151. dbFav: result.data.dbFav,
  152. selection: [],
  153. toggledAll: false,
  154. totalAmount: result.data.totalAmount
  155. });
  156. resolve();
  157. })
  158. .catch(err => console.error(err));
  159. });
  160. }
  161. const reformatData = data => {
  162. let databases = [];
  163. for (let i in data) {
  164. data[i]['NAME'] = i;
  165. data[i]['FOCUSED'] = controlPanelFocusedElement === i;
  166. databases.push(data[i]);
  167. }
  168. return databases;
  169. }
  170. const changeSorting = (sorting, order) => {
  171. setState({
  172. ...state,
  173. sorting,
  174. order
  175. });
  176. }
  177. const databases = () => {
  178. const { databases } = state;
  179. const result = [];
  180. const dbFav = { ...state.dbFav };
  181. databases.forEach(database => {
  182. database.FOCUSED = controlPanelFocusedElement === database.NAME;
  183. if (dbFav[database.NAME]) {
  184. database.STARRED = dbFav[database.NAME];
  185. } else {
  186. database.STARRED = 0;
  187. }
  188. result.push(database);
  189. });
  190. let sortedResult = sortArray(result);
  191. return sortedResult.map((item, index) => {
  192. return <Database data={item} key={index} toggleFav={toggleFav} checkItem={checkItem} handleModal={displayModal} />;
  193. });
  194. }
  195. const checkItem = name => {
  196. const { selection, databases } = state;
  197. let duplicate = [...selection];
  198. let dbDuplicate = databases;
  199. let checkedItem = duplicate.indexOf(name);
  200. let incomingItem = dbDuplicate.findIndex(db => db.NAME === name);
  201. dbDuplicate[incomingItem].isChecked = !dbDuplicate[incomingItem].isChecked;
  202. if (checkedItem !== -1) {
  203. duplicate.splice(checkedItem, 1);
  204. } else {
  205. duplicate.push(name);
  206. }
  207. setState({ ...state, databases: dbDuplicate, selection: duplicate });
  208. }
  209. const sortArray = array => {
  210. const { order, sorting } = state;
  211. let sortingColumn = sortBy(sorting);
  212. if (order === "descending") {
  213. return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
  214. } else {
  215. return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
  216. }
  217. }
  218. const sortBy = sorting => {
  219. const { Date, Database, Disk, User, Host, Starred } = i18n;
  220. switch (sorting) {
  221. case Date: return 'DATE';
  222. case Database: return 'DATABASE';
  223. case Disk: return 'U_DISK';
  224. case User: return 'DBUSER';
  225. case Host: return 'HOST';
  226. case Starred: return 'STARRED';
  227. default: break;
  228. }
  229. }
  230. const toggleFav = (value, type) => {
  231. const { dbFav } = state;
  232. let dbFavDuplicate = dbFav;
  233. if (type === 'add') {
  234. dbFavDuplicate[value] = 1;
  235. addFavorite(value, 'db')
  236. .then(() => {
  237. setState({ ...state, dbFav: dbFavDuplicate });
  238. })
  239. .catch(err => {
  240. console.error(err);
  241. });
  242. } else {
  243. dbFavDuplicate[value] = undefined;
  244. deleteFavorite(value, 'db')
  245. .then(() => {
  246. setState({ ...state, dbFav: dbFavDuplicate });
  247. })
  248. .catch(err => {
  249. console.error(err);
  250. });
  251. }
  252. }
  253. const toggleAll = toggled => {
  254. const databasesDuplicate = [...state.databases];
  255. if (toggled) {
  256. let dbNames = []
  257. let databases = databasesDuplicate.map(database => {
  258. dbNames.push(database.NAME);
  259. database.isChecked = true;
  260. return database;
  261. });
  262. setState({ ...state, databases, selection: dbNames, toggledAll: toggled });
  263. } else {
  264. let databases = databasesDuplicate.map(database => {
  265. database.isChecked = false;
  266. return database;
  267. });
  268. setState({ ...state, databases, selection: [], toggledAll: toggled });
  269. }
  270. }
  271. const bulk = action => {
  272. const { selection } = state;
  273. if (selection.length && action) {
  274. setLoading(true);
  275. bulkAction(action, selection)
  276. .then(result => {
  277. if (result.status === 200) {
  278. toggleAll(false);
  279. fetchData().then(() => refreshMenuCounters());
  280. }
  281. })
  282. .catch(err => console.error(err));
  283. }
  284. }
  285. const displayModal = (text, url) => {
  286. setModal({
  287. ...modal,
  288. visible: true,
  289. text: text,
  290. actionUrl: url
  291. });
  292. }
  293. const modalConfirmHandler = () => {
  294. if (!modal.actionUrl) {
  295. return modalCancelHandler();
  296. }
  297. modalCancelHandler();
  298. setLoading(true);
  299. handleAction(modal.actionUrl)
  300. .then(res => {
  301. if (res.data.error) {
  302. setLoading(false);
  303. return displayModal(res.data.error, '');
  304. }
  305. fetchData().then(() => refreshMenuCounters())
  306. })
  307. .catch(err => { setLoading(false); console.error(err); });
  308. }
  309. const refreshMenuCounters = () => {
  310. dispatch(refreshCounters()).then(() => setLoading(false));
  311. }
  312. const modalCancelHandler = () => {
  313. setModal({
  314. ...modal,
  315. visible: false,
  316. text: '',
  317. actionUrl: ''
  318. });
  319. }
  320. return (
  321. <div className="databases">
  322. <Helmet>
  323. <title>{`Vesta - ${i18n.DB}`}</title>
  324. </Helmet>
  325. <Toolbar mobile={false} >
  326. <LeftButton name="Add Database" href="/add/db" showLeftMenu={true} />
  327. <div className="r-menu">
  328. <div className="input-group input-group-sm">
  329. <a href={state.dbAdminLink} className="button-extra" type="submit" target="_blank" rel="noopener noreferrer">{state.dbAdmin}</a>
  330. <Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
  331. <Select list='dbList' bulkAction={bulk} />
  332. <DropdownFilter changeSorting={changeSorting} sorting={state.sorting} order={state.order} list="dbList" />
  333. <SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />
  334. </div>
  335. </div>
  336. </Toolbar>
  337. <div className="mails-wrapper">
  338. {loading
  339. ? <Spinner />
  340. : (<>
  341. {databases()}
  342. <div className="total">{state.totalAmount}</div>
  343. </>)}
  344. </div>
  345. <Modal
  346. onSave={modalConfirmHandler}
  347. onCancel={modalCancelHandler}
  348. show={modal.visible}
  349. text={modal.text} />
  350. </div>
  351. );
  352. }
  353. export default Databases;