/**
* COPYRIGHT © 2022 JOHN PEARCEY
* All rights reserved
*/
/**
* The last nonce we just got from the server
*/
var g_svr_nonce = [];
/**
* 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
* for the user. I'm sure that there are better ways with modern HTML5 but polling is easy and the application is not
* time critical. This is sufficient.
*/
var g_bu_list_state = [];
/**
* This var g_stack_pages contains the app navigation pages in order. The top most page being the
* current page. So, to go back, you would remove the top most page and navigate to the new top page.
*/
var g_stack_pages = [];
function clear_page_state(){
g_bu_list_state = [];
g_stack_pages = [];
}
function push_page( pagename ){
g_stack_pages.push( pagename );
}
/**
* Pops the current page off the stack and returns the penultimate one.
*/
function back_page( ){
g_stack_pages.pop( );
return g_stack_pages[g_stack_pages.length - 1];
}
function index_onload(){
update_bu_state( false );
set_main_content( true );
}
function update_bu_state( bCheckPage = true ){
setTimeout( () => {
update_bu_state();
}, 10000);
if(bCheckPage){
//console.log( "update_bu_state pages:", g_stack_pages );
if( g_stack_pages[g_stack_pages.length - 1] != 'backup-list' ) return;
}
//console.log( "update_bu_state polling" );
map_params = new Map();
map_params.set('action', 'backup-list-state' );
postData( "post_handler.php", function( success, data, m ){
if(data[1][0]=='login'){
console.log( 'Cannot get backup-list-state: Not logged in' );
return;
}
let new_state = data[1][1];
//console.log( 'backup-list-state' + new_state );
if( g_bu_list_state.length == 0 ){
//just store the new state
g_bu_list_state = new_state;
return;
}
//compare the two arrays, if different length, then refresh the UI.
if( g_bu_list_state.length != new_state.length ){
g_bu_list_state = new_state;
set_main_content( false );
return;
}
//check each row in turn
let bFound = false;
for( j=0; j<g_bu_list_state.length; j++){
curr_state_j = g_bu_list_state[j];
bFound = false;
//console.log("looking for ID = " + curr_state_j[0] );
for( i=0; i<new_state.length; i++){
new_state_i = new_state[i];
//console.log("compare with", new_state_i );
if( new_state_i[0] == curr_state_j[0]){
bFound = (new_state_i[1] == curr_state_j[1]);
break;
}
}
if(!bFound){
//at least one ID or value does not match
g_bu_list_state = new_state;
set_main_content( false );
break;
}
}
}, map_params );
}
function set_main_content( bPushPage ){
//console.log("3");
map_params = new Map();
map_params.set('action', 'backup-list' );
postData( "post_handler.php", function( success, data, m ){
if(data[0]!="OK"){
console.log( data );
return;
}
let elem = document.getElementById('page_content');
//display page from server
[ page_type, elem.innerHTML ] = data[1];
console.log('set_main_content page_type: ', page_type );
if(page_type=='login'){
//clear client side data
clear_page_state();
g_svr_nonce = data[1][2];
return;
}
if(bPushPage) push_page('backup-list');
}, map_params );
}
/**
* Button clicked and we require a new page from the server to be placed in the page_content area.
* instr(uction) is usually the page name except where a 'back' navigation is required.
*/
async function btn_clk_nav( elem, instr, id=0 ){
// instr can now also be a space separated list of instructions
//we split them out
instrArr = instr.split(" ");
instr = instrArr[0];
//noTest
map_params = new Map();
map_params.set('id', id );
let b_is_db_type = false;
switch(instr){
case 'btn_login':
if( !valid_btn_login() ) return;
await set_btn_login( map_params );
map_params.set('svr_nonce', g_svr_nonce );
map_params.set('final-page', 'backup-list' );
navigate_page( instr, map_params, false );
return;
case 'btn_run_bu':
bIsBuTest = (instrArr.length==2 && instrArr[1]=='noTest');
if( bIsBuTest ){
map_params.set('isTest', 'false' );
}else{
map_params.set('isTest', 'true' );
console.log("running a test backup");
}
exec_instruction( instr, map_params );
if(!bIsBuTest){
monitorBuOutput( true, id, -1 );
}
return;
case 'delete-bu':
if( !confirm('Are you sure you wish to delete backup item ' + id ) ) return;
map_params.set('final-page', 'backup-list' );
navigate_page( instr, map_params, false );
return;
case 'btn_edit_shed_bu':
if( !valid_btn_edit_shed_bu() ) return;
set_btn_edit_shed_bu( map_params );
map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
navigate_page( 'edit_shed_bu', map_params, false );
return;
case 'btn_new_bu_db':
b_is_db_type=true;
//drop through
case 'btn_new_bu_file':
if( !valid_btn_new_bu( b_is_db_type ) ) return;
set_btn_new_bu( b_is_db_type, map_params );
map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
navigate_page( 'new-item-bu', map_params, false );
return;
case 'btn_edit_bu_db':
b_is_db_type=true;
//drop through
case 'btn_edit_bu_file':
if( !valid_btn_new_bu( b_is_db_type ) ) return;
set_btn_new_bu( b_is_db_type, map_params );
map_params.set('final-page', back_page() ); //server to return previous page (usually backup-list) after processing
navigate_page( 'edit-item-bu', map_params, false );
return;
case 'btn_cancel_new_bu':
case 'btn_cancel':
navigate_page( back_page(), map_params, false );
return;
}
navigate_page( instr, map_params, true );
}
function navigate_page( goto_page, map_params, bPushPage ){
//console.log( 'navigate_page: ', goto_page );
map_params.set('action', goto_page );
//map_params.set('updateList', JSON.stringify( usrUpdates ) );
postData( "post_handler.php", function( success, data, m ){
if(data[0]!="OK"){
console.log( data[0], data[1] );
if(!bPushPage){
//we need to put back the page that we dropped off or maybe an error page
//but use goto_page for now
push_page( goto_page );
}
return;
}
let elem = document.getElementById('page_content');
[ page_type, elem.innerHTML ] = data[1];
console.log('navigate_page page_type: ', page_type);
if(page_type=='login'){
//clear client side data
clear_page_state();
g_svr_nonce = data[1][2];
return;
}
if(page_type=='authenticated'){
elem = document.getElementById('anc_login');
elem.innerHTML="Logout";
push_page( map_params.get('final-page') );
return;
}
if(bPushPage) push_page( goto_page );
}, map_params );
}
function setTextAreaFromArray( elem, arr ){
elem.value = arr.toString().replaceAll(',', "\r\n" );
}
function exec_instruction( instr, map_params ){
console.log( 'exec_instruction: ', instr );
let id = map_params.get('id');
map_params.set('action', instr );
//map_params.set('updateList', JSON.stringify( usrUpdates ) );
postData( "post_handler.php", function( success, data, m ){
if(data[0]!="OK"){
console.log( data );
return;
}
bu_result = data[1][0]; //was it ok or err?
switch(instr){
case 'btn_run_bu':
if(bu_result!='ok:'){
//latest msg contains similar format as used by the server rpc layer
console.log("odd message: " + bu_result );
console.log( data );
return;
}
bu_status = data[1][1]; //extra info
if(bu_status=='running'){
// is it a test run? - by rights, I should send back the 'test' flag
let elem = document.getElementById( "ta_testoutput" );
if(elem==null){
//not a test - set the tick
let elem = document.getElementById( "td_run_" + id );
elem.innerHTML = "<img id=\"td_run_\"" + id + " src=\"img/green-tick.png\">";
//ensure the state map is also adjusted
for( j=0; j<g_bu_list_state.length; j++){
if(g_bu_list_state[j][0]==id){
g_bu_list_state[j][1] = 1;
}
}
}else{
//test run
setTextAreaFromArray( elem, bu_result + " " + bu_status );
}
}else{
console.log("unknown message: " + bu_status );
console.log( data );
}
break;
default:
console.log( data[1] );
}
}, map_params );
}
function monitorBuOutput( bMonitor, buid, from_line_num ){
if(from_line_num==-1){
//first call, just set the timer and give some time for the test to kick off
setTimeout( () => {
monitorBuOutput(true, buid, 0);
}, 1000);
return ;
}
console.log("Fetch output from line: " + from_line_num );
let e_ta_testoutput = document.getElementById( "ta_testoutput" );
if(e_ta_testoutput==null){
// page gone (should terminate server open file?)
return;
}
let map_params = new Map();
map_params.set('action', 'bu-test-file-lines' );
map_params.set('buid', buid );
map_params.set('from-line-num', from_line_num );
postData( "post_handler.php", function( success, data, m ){
//console.log( data );
let cnt=0;
let rtm_msg = data[1][cnt++]; //'f:bu-test-file-lines'
let line_count = data[1][cnt++];
let line_data = '';
if(line_count>0){
line_data = data[1][cnt++];
}
e_ta_testoutput = document.getElementById( "ta_testoutput" );
if(e_ta_testoutput==null){
// page gone (should terminate server open file?)
return;
}
if(line_count>0){
//setTextAreaFromArray( e_ta_testoutput, line_data );
e_ta_testoutput.value += "\n" + line_data;
setTimeout( () => {
monitorBuOutput(true, buid, line_count + from_line_num );
}, 1000);
return ;
}else{
console.log("No more lines to fetch" );
}
}, map_params);
}
function valid_btn_login(){
let login_name = document.getElementById('login_name').value;
if(login_name=='') {
alert("Please enter your login name");
return false;
}
let login_pwd = document.getElementById('login_pwd').value;
if(login_pwd=='') {
alert("Please enter your password");
return false;
}
return true;
}
/**
* Collect data from login form
*/
async function set_btn_login( map_params ){
let raw_pwd = document.getElementById('login_pwd').value;
const pwd_hash = await getHash256(raw_pwd+g_svr_nonce);
map_params.set( 'username', document.getElementById('login_name').value );
map_params.set( 'password', pwd_hash );
}
async function getHash256(message) {
//https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // convert bytes to hex string
return hashHex;
}
/**
* Validate the form for a new backup entry
* b_is_db_type is true for DB entry and false for file entry.
*/
function valid_btn_new_bu( b_is_db_type ){
return true;
}
/**
* Collect the form data together.
* b_is_db_type is true for DB entry and false for file entry.
*/
function set_btn_new_bu( b_is_db_type, map_params ){
map_params.set( 'new_bu_is_dbType', b_is_db_type?1:0 );
map_params.set( 'new_bu_name', document.getElementById('new_bu_name').value );
map_params.set( 'new_bu_source', document.getElementById('new_bu_source').value );
map_params.set( 'new_bu_dest', document.getElementById('new_bu_dest').value );
if(!b_is_db_type){
map_params.set( 'new_bu_exf', document.getElementById('new_bu_exf').value );
}
}
/**
* Validate the form for the backup scheduling page
*/
function valid_btn_edit_shed_bu(){
sched_date = document.getElementById('sched_date').value;
if(sched_date=='') {
alert("Please choose a date");
return false;
}
sched_time = document.getElementById('sched_time').value;
if(sched_time=='') {
alert("Please choose a time");
return false;
}
let dt_chosen = new Date( sched_date + 'T' + sched_time + ':00Z');
let now = new Date();
if(dt_chosen<=now){
alert("Please choose a time in the future, not the past: ", dt_chosen, now );
return false;
}
console.log("dates: ", dt_chosen, now );
return true;
}
/**
* Collect the form data together for the backup scheduling page
*/
function set_btn_edit_shed_bu( map_params ){
map_params.set( 'sched_date', document.getElementById('sched_date').value );
map_params.set( 'sched_time', document.getElementById('sched_time').value );
switch(document.querySelector('input[name="sched_period"]:checked').id){
case 'sched_ip_daily':
map_params.set( 'sched_period', 'D' );
break;
case 'sched_ip_weekly':
map_params.set( 'sched_period', 'W' );
break;
case 'sched_ip_monthly':
map_params.set( 'sched_period', 'M' );
break;
case 'sched_ip_none':
default:
map_params.set( 'sched_period', 'N' );
}
}