Newer
Older
backup-commander / html / js / main.js
  1.  
  2. /**
  3. * COPYRIGHT © 2022 JOHN PEARCEY
  4. * All rights reserved
  5. */
  6.  
  7. /**
  8. * The last nonce we just got from the server
  9. */
  10. var g_svr_nonce = [];
  11.  
  12. /**
  13. * Every 10secs, we get the state of the backup list from the server. This is so that we can keep the front end up-to-date
  14. * for the user. I'm sure that there are better ways with modern HTML5 but polling is easy and the application is not
  15. * time critical. This is sufficient.
  16. */
  17. var g_bu_list_state = [];
  18.  
  19. /**
  20. * This var g_stack_pages contains the app navigation pages in order. The top most page being the
  21. * current page. So, to go back, you would remove the top most page and navigate to the new top page.
  22. */
  23. var g_stack_pages = [];
  24. function clear_page_state(){
  25. g_bu_list_state = [];
  26. g_stack_pages = [];
  27. }
  28. function push_page( pagename ){
  29. g_stack_pages.push( pagename );
  30. }
  31.  
  32. /**
  33. * Pops the current page off the stack and returns the penultimate one.
  34. */
  35. function back_page( ){
  36. g_stack_pages.pop( );
  37. return g_stack_pages[g_stack_pages.length - 1];
  38. }
  39.  
  40. function index_onload(){
  41. update_bu_state( false );
  42. set_main_content( true );
  43. }
  44.  
  45. function update_bu_state( bCheckPage = true ){
  46. setTimeout( () => {
  47. update_bu_state();
  48. }, 10000);
  49.  
  50. if(bCheckPage){
  51. //console.log( "update_bu_state pages:", g_stack_pages );
  52. if( g_stack_pages[g_stack_pages.length - 1] != 'backup-list' ) return;
  53. }
  54.  
  55. //console.log( "update_bu_state polling" );
  56.  
  57. map_params = new Map();
  58. map_params.set('action', 'backup-list-state' );
  59. postData( "post_handler.php", function( success, data, m ){
  60. if(data[1][0]=='login'){
  61. console.log( 'Cannot get backup-list-state: Not logged in' );
  62. return;
  63. }
  64.  
  65. let new_state = data[1][1];
  66. //console.log( 'backup-list-state' + new_state );
  67. if( g_bu_list_state.length == 0 ){
  68. //just store the new state
  69. g_bu_list_state = new_state;
  70. return;
  71. }
  72. //compare the two arrays, if different length, then refresh the UI.
  73. if( g_bu_list_state.length != new_state.length ){
  74. g_bu_list_state = new_state;
  75. set_main_content( false );
  76. return;
  77. }
  78. //check each row in turn
  79. let bFound = false;
  80. for( j=0; j<g_bu_list_state.length; j++){
  81. curr_state_j = g_bu_list_state[j];
  82. bFound = false;
  83. //console.log("looking for ID = " + curr_state_j[0] );
  84. for( i=0; i<new_state.length; i++){
  85. new_state_i = new_state[i];
  86. //console.log("compare with", new_state_i );
  87. if( new_state_i[0] == curr_state_j[0]){
  88. bFound = (new_state_i[1] == curr_state_j[1]);
  89. break;
  90. }
  91. }
  92. if(!bFound){
  93. //at least one ID or value does not match
  94. g_bu_list_state = new_state;
  95. set_main_content( false );
  96. break;
  97. }
  98. }
  99. }, map_params );
  100.  
  101. }
  102.  
  103.  
  104.  
  105.  
  106. function set_main_content( bPushPage ){
  107.  
  108. //console.log("3");
  109.  
  110. map_params = new Map();
  111. map_params.set('action', 'backup-list' );
  112. postData( "post_handler.php", function( success, data, m ){
  113. if(data[0]!="OK"){
  114. console.log( data );
  115. return;
  116. }
  117.  
  118. let elem = document.getElementById('page_content');
  119. //display page from server
  120. [ page_type, elem.innerHTML ] = data[1];
  121. console.log('set_main_content page_type: ', page_type );
  122. if(page_type=='login'){
  123. //clear client side data
  124. clear_page_state();
  125. g_svr_nonce = data[1][2];
  126. return;
  127. }
  128. if(bPushPage) push_page('backup-list');
  129. }, map_params );
  130. }
  131.  
  132. /**
  133. * Button clicked and we require a new page from the server to be placed in the page_content area.
  134. * instr(uction) is usually the page name except where a 'back' navigation is required.
  135. */
  136. async function btn_clk_nav( elem, instr, id=0 ){
  137.  
  138. // instr can now also be a space separated list of instructions
  139. //we split them out
  140. instrArr = instr.split(" ");
  141. instr = instrArr[0];
  142. //noTest
  143.  
  144. map_params = new Map();
  145. map_params.set('id', id );
  146. let b_is_db_type = false;
  147. switch(instr){
  148.  
  149. case 'btn_login':
  150. if( !valid_btn_login() ) return;
  151. await set_btn_login( map_params );
  152. map_params.set('svr_nonce', g_svr_nonce );
  153. map_params.set('final-page', 'backup-list' );
  154. navigate_page( instr, map_params, false );
  155. return;
  156. case 'btn_run_bu':
  157. bIsBuTest = (instrArr.length==2 && instrArr[1]=='noTest');
  158. if( bIsBuTest ){
  159. map_params.set('isTest', 'false' );
  160. }else{
  161. map_params.set('isTest', 'true' );
  162. console.log("running a test backup");
  163. }
  164. exec_instruction( instr, map_params );
  165. if(!bIsBuTest){
  166. monitorBuOutput( true, id, -1 );
  167. }
  168. return;
  169. case 'delete-bu':
  170. if( !confirm('Are you sure you wish to delete backup item ' + id ) ) return;
  171. map_params.set('final-page', 'backup-list' );
  172. navigate_page( instr, map_params, false );
  173. return;
  174. case 'btn_edit_shed_bu':
  175. if( !valid_btn_edit_shed_bu() ) return;
  176. set_btn_edit_shed_bu( map_params );
  177. map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
  178. navigate_page( 'edit_shed_bu', map_params, false );
  179. return;
  180. case 'btn_new_bu_db':
  181. b_is_db_type=true;
  182. //drop through
  183. case 'btn_new_bu_file':
  184. if( !valid_btn_new_bu( b_is_db_type ) ) return;
  185. set_btn_new_bu( b_is_db_type, map_params );
  186. map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
  187. navigate_page( 'new-item-bu', map_params, false );
  188. return;
  189. case 'btn_edit_bu_db':
  190. b_is_db_type=true;
  191. //drop through
  192. case 'btn_edit_bu_file':
  193. if( !valid_btn_new_bu( b_is_db_type ) ) return;
  194. set_btn_new_bu( b_is_db_type, map_params );
  195. map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
  196. navigate_page( 'edit-item-bu', map_params, false );
  197. return;
  198.  
  199. case 'btn_cancel_new_bu':
  200. case 'btn_cancel':
  201. navigate_page( back_page(), map_params, false );
  202. return;
  203. }
  204. navigate_page( instr, map_params, true );
  205. }
  206.  
  207.  
  208. function navigate_page( goto_page, map_params, bPushPage ){
  209.  
  210. //console.log( 'navigate_page: ', goto_page );
  211. map_params.set('action', goto_page );
  212. //map_params.set('updateList', JSON.stringify( usrUpdates ) );
  213. postData( "post_handler.php", function( success, data, m ){
  214. if(data[0]!="OK"){
  215. console.log( data[0], data[1] );
  216. if(!bPushPage){
  217. //we need to put back the page that we dropped off or maybe an error page
  218. //but use goto_page for now
  219. push_page( goto_page );
  220. }
  221. return;
  222. }
  223. let elem = document.getElementById('page_content');
  224. [ page_type, elem.innerHTML ] = data[1];
  225. console.log('navigate_page page_type: ', page_type);
  226. if(page_type=='login'){
  227. //clear client side data
  228. clear_page_state();
  229. g_svr_nonce = data[1][2];
  230. return;
  231. }
  232.  
  233. if(page_type=='authenticated'){
  234. elem = document.getElementById('anc_login');
  235. elem.innerHTML="Logout";
  236. elem.setAttribute("href","post_handler.php?action=logout");
  237. push_page( map_params.get('final-page') );
  238. return;
  239. }
  240.  
  241. if(bPushPage) push_page( goto_page );
  242. }, map_params );
  243. }
  244.  
  245. function setTextAreaFromArray( elem, arr ){
  246. elem.value = arr.toString().replaceAll(',', "\r\n" );
  247. }
  248.  
  249. function exec_instruction( instr, map_params ){
  250.  
  251. console.log( 'exec_instruction: ', instr );
  252. let id = map_params.get('id');
  253. map_params.set('action', instr );
  254. //map_params.set('updateList', JSON.stringify( usrUpdates ) );
  255. postData( "post_handler.php", function( success, data, m ){
  256. if(data[0]!="OK"){
  257. console.log( data );
  258. return;
  259. }
  260. bu_result = data[1][0]; //was it ok or err?
  261. switch(instr){
  262. case 'btn_run_bu':
  263. if(bu_result!='ok:'){
  264. //latest msg contains similar format as used by the server rpc layer
  265. console.log("odd message: " + bu_result );
  266. console.log( data );
  267. return;
  268. }
  269. bu_status = data[1][1]; //extra info
  270. if(bu_status=='running'){
  271.  
  272. // is it a test run? - by rights, I should send back the 'test' flag
  273. let elem = document.getElementById( "ta_testoutput" );
  274. if(elem==null){
  275. //not a test - set the tick
  276. let elem = document.getElementById( "td_run_" + id );
  277. elem.innerHTML = "<img id=\"td_run_\"" + id + " src=\"img/green-tick.png\">";
  278. //ensure the state map is also adjusted
  279. for( j=0; j<g_bu_list_state.length; j++){
  280. if(g_bu_list_state[j][0]==id){
  281. g_bu_list_state[j][1] = 1;
  282. }
  283. }
  284.  
  285. }else{
  286. //test run
  287. setTextAreaFromArray( elem, bu_result + " " + bu_status );
  288. }
  289. }else{
  290. console.log("unknown message: " + bu_status );
  291. console.log( data );
  292. }
  293. break;
  294. default:
  295. console.log( data[1] );
  296. }
  297. }, map_params );
  298. }
  299.  
  300. function monitorBuOutput( bMonitor, buid, from_line_num ){
  301. if(from_line_num==-1){
  302. //first call, just set the timer and give some time for the test to kick off
  303. setTimeout( () => {
  304. monitorBuOutput(true, buid, 0);
  305. }, 1000);
  306. return ;
  307. }
  308. console.log("Fetch output from line: " + from_line_num );
  309. let e_ta_testoutput = document.getElementById( "ta_testoutput" );
  310. if(e_ta_testoutput==null){
  311. // page gone (should terminate server open file?)
  312. return;
  313. }
  314.  
  315. let map_params = new Map();
  316. map_params.set('action', 'bu-test-file-lines' );
  317. map_params.set('buid', buid );
  318. map_params.set('from-line-num', from_line_num );
  319. postData( "post_handler.php", function( success, data, m ){
  320. //console.log( data );
  321. let cnt=0;
  322. let rtm_msg = data[1][cnt++]; //'f:bu-test-file-lines'
  323. let line_count = data[1][cnt++];
  324. let line_data = '';
  325. if(line_count>0){
  326. line_data = data[1][cnt++];
  327. }
  328. e_ta_testoutput = document.getElementById( "ta_testoutput" );
  329. if(e_ta_testoutput==null){
  330. // page gone (should terminate server open file?)
  331. return;
  332. }
  333.  
  334. if(line_count>0){
  335. //setTextAreaFromArray( e_ta_testoutput, line_data );
  336. e_ta_testoutput.value += "\n" + line_data;
  337. setTimeout( () => {
  338. monitorBuOutput(true, buid, line_count + from_line_num );
  339. }, 1000);
  340. return ;
  341. }else{
  342. console.log("No more lines to fetch" );
  343. }
  344. }, map_params);
  345. }
  346.  
  347.  
  348. function valid_btn_login(){
  349. let login_name = document.getElementById('login_name').value;
  350. if(login_name=='') {
  351. alert("Please enter your login name");
  352. return false;
  353. }
  354. let login_pwd = document.getElementById('login_pwd').value;
  355. if(login_pwd=='') {
  356. alert("Please enter your password");
  357. return false;
  358. }
  359. return true;
  360. }
  361.  
  362. /**
  363. * Collect data from login form
  364. */
  365. async function set_btn_login( map_params ){
  366. let raw_pwd = document.getElementById('login_pwd').value;
  367.  
  368. console.log("pwd = "+raw_pwd);
  369. console.log("nonce = " + g_svr_nonce);
  370.  
  371. const pwd_hash = await getHash256(raw_pwd+g_svr_nonce);
  372. map_params.set( 'username', document.getElementById('login_name').value );
  373. map_params.set( 'password', pwd_hash );
  374. }
  375.  
  376. async function getHash256(message) {
  377. //https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
  378. const encoder = new TextEncoder();
  379. const data = encoder.encode(message);
  380. const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
  381. const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
  382. const hashHex = hashArray
  383. .map((b) => b.toString(16).padStart(2, "0"))
  384. .join(""); // convert bytes to hex string
  385. return hashHex;
  386. }
  387.  
  388. /**
  389. * Validate the form for a new backup entry
  390. * b_is_db_type is true for DB entry and false for file entry.
  391. */
  392. function valid_btn_new_bu( b_is_db_type ){
  393. return true;
  394. }
  395.  
  396. /**
  397. * Collect the form data together.
  398. * b_is_db_type is true for DB entry and false for file entry.
  399. */
  400. function set_btn_new_bu( b_is_db_type, map_params ){
  401. map_params.set( 'new_bu_is_dbType', b_is_db_type?1:0 );
  402. map_params.set( 'new_bu_name', document.getElementById('new_bu_name').value );
  403. map_params.set( 'new_bu_source', document.getElementById('new_bu_source').value );
  404. map_params.set( 'new_bu_dest', document.getElementById('new_bu_dest').value );
  405. if(b_is_db_type){
  406. console.log("Setting DB params username=" + document.getElementById('db_usr_name').value );
  407. map_params.set( 'db_usr_name', document.getElementById('db_usr_name').value );
  408. map_params.set( 'db_usr_pwd', document.getElementById('db_usr_pwd').value );
  409. }else{
  410. console.log("Setting File params");
  411. map_params.set( 'new_bu_exf', document.getElementById('new_bu_exf').value );
  412. }
  413. }
  414.  
  415. /**
  416. * Validate the form for the backup scheduling page
  417. */
  418. function valid_btn_edit_shed_bu(){
  419. sched_date = document.getElementById('sched_date').value;
  420. if(sched_date=='') {
  421. alert("Please choose a date");
  422. return false;
  423. }
  424. sched_time = document.getElementById('sched_time').value;
  425. if(sched_time=='') {
  426. alert("Please choose a time");
  427. return false;
  428. }
  429. let dt_chosen = new Date( sched_date + 'T' + sched_time + ':00Z');
  430. let now = new Date();
  431. if(dt_chosen<=now){
  432. alert("Please choose a time in the future, not the past: ", dt_chosen, now );
  433. return false;
  434. }
  435. console.log("dates: ", dt_chosen, now );
  436. return true;
  437. }
  438. /**
  439. * Collect the form data together for the backup scheduling page
  440. */
  441. function set_btn_edit_shed_bu( map_params ){
  442. map_params.set( 'sched_date', document.getElementById('sched_date').value );
  443. map_params.set( 'sched_time', document.getElementById('sched_time').value );
  444. switch(document.querySelector('input[name="sched_period"]:checked').id){
  445. case 'sched_ip_daily':
  446. map_params.set( 'sched_period', 'D' );
  447. break;
  448.  
  449. case 'sched_ip_weekly':
  450. map_params.set( 'sched_period', 'W' );
  451. break;
  452.  
  453. case 'sched_ip_monthly':
  454. map_params.set( 'sched_period', 'M' );
  455. break;
  456. case 'sched_ip_none':
  457. default:
  458. map_params.set( 'sched_period', 'N' );
  459. }
  460. }
  461.