diff --git a/README.md b/README.md index 468f046..0528c25 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ -backup-commander -=============== - -Simple web-based backup GUI \ No newline at end of file +Add this line in the sudoers file (use visudo) and it must go AFTER the %sudo line www-data ALL=(ALL) NOPASSWD: /usr/bin/php /var/www/html/bin/run-bu-by-id.php 3rd-party backup-commander dom_persist eclipse-gwt help_dlang help.txt html_backup_UI html_stanhope res_builder stanhope_d tmp Test by switching user to www-data: su -s /bin/bash www-data cd /var/www/html php bin/run-bu-by-id.php 19 0 where 19 is the backup id and 0/1 is not-test/test Comment out /inc/secure.php in post_handler.php if the login is not required TODO: create an install script: -- currently all manually done First version on Las-server Pre-requesite to push onto pearcey.net Generate a database schema with default login add the sudo entry for www-data (or other webserver user) kick off the schedule runner at the install stage create the schedule runner in php. simple polling of the DB get passwords from BD create a user credential page: new user+password change own password but only if the login is enabled (in post_handler.php) diff --git a/html/bin/bu-n-err-18.txt b/html/bin/bu-n-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/bu-n-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/bu-n-err-19.txt b/html/bin/bu-n-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/bu-n-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/bu-n-op-19.txt b/html/bin/bu-n-op-19.txt new file mode 100644 index 0000000..f566b18 --- /dev/null +++ b/html/bin/bu-n-op-19.txt @@ -0,0 +1,12 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/bu-n-err-19.txt +html/bin/bu-n-op-19.txt +html/bin/bu-x-op-19.txt +html/bin/data.db +html/js/ +html/js/main.js + +sent 33,631 bytes received 127 bytes 67,516.00 bytes/sec +total size is 226,992 speedup is 6.72 diff --git a/html/bin/bu-t-err-18.txt b/html/bin/bu-t-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/bu-t-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/bu-t-op-18.txt b/html/bin/bu-t-op-18.txt new file mode 100644 index 0000000..8a74739 --- /dev/null +++ b/html/bin/bu-t-op-18.txt @@ -0,0 +1,51 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: johntest +-- ------------------------------------------------------ +-- Server version 10.6.18-MariaDB-0ubuntu0.22.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `BULIST` +-- + +DROP TABLE IF EXISTS `BULIST`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `BULIST` ( + `BUID` int(11) NOT NULL AUTO_INCREMENT, + `BUName` varchar(9) DEFAULT NULL, + PRIMARY KEY (`BUID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `BULIST` +-- + +LOCK TABLES `BULIST` WRITE; +/*!40000 ALTER TABLE `BULIST` DISABLE KEYS */; +/*!40000 ALTER TABLE `BULIST` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-10-03 16:13:05 diff --git a/html/bin/bu-x-op-19.txt b/html/bin/bu-x-op-19.txt new file mode 100644 index 0000000..9fcf8b5 --- /dev/null +++ b/html/bin/bu-x-op-19.txt @@ -0,0 +1 @@ +html/bin/rsync-n-op-19.txt diff --git a/html/bin/data.db b/html/bin/data.db new file mode 100644 index 0000000..e829a56 --- /dev/null +++ b/html/bin/data.db Binary files differ diff --git a/html/bin/rsync-n-err-18.txt b/html/bin/rsync-n-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/rsync-n-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/rsync-n-err-19.txt b/html/bin/rsync-n-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/rsync-n-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/rsync-n-op-19.txt b/html/bin/rsync-n-op-19.txt new file mode 100644 index 0000000..20ee8de --- /dev/null +++ b/html/bin/rsync-n-op-19.txt @@ -0,0 +1,13 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/data.db +html/bin/rsync-n-err-19.txt +html/bin/rsync-t-err-19.txt +html/bin/rsync-t-op-19.txt +html/bin/rsync-x-op-19.txt +html/inc/ +html/inc/bu-common.php + +sent 27,025 bytes received 146 bytes 54,342.00 bytes/sec +total size is 217,616 speedup is 8.01 diff --git a/html/bin/rsync-t-err-18.txt b/html/bin/rsync-t-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/rsync-t-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/rsync-t-err-19.txt b/html/bin/rsync-t-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/rsync-t-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/rsync-t-op-18.txt b/html/bin/rsync-t-op-18.txt new file mode 100644 index 0000000..42f1c56 --- /dev/null +++ b/html/bin/rsync-t-op-18.txt @@ -0,0 +1,51 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: johntest +-- ------------------------------------------------------ +-- Server version 10.6.18-MariaDB-0ubuntu0.22.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `BULIST` +-- + +DROP TABLE IF EXISTS `BULIST`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `BULIST` ( + `BUID` int(11) NOT NULL AUTO_INCREMENT, + `BUName` varchar(9) DEFAULT NULL, + PRIMARY KEY (`BUID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `BULIST` +-- + +LOCK TABLES `BULIST` WRITE; +/*!40000 ALTER TABLE `BULIST` DISABLE KEYS */; +/*!40000 ALTER TABLE `BULIST` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-10-03 15:59:07 diff --git a/html/bin/rsync-t-op-19.txt b/html/bin/rsync-t-op-19.txt new file mode 100644 index 0000000..6f9197e --- /dev/null +++ b/html/bin/rsync-t-op-19.txt @@ -0,0 +1,12 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/data.db +html/bin/rsync-t-err-19.txt +html/bin/rsync-t-op-19.txt +html/bin/rsync-x-op-19.txt +html/inc/ +html/inc/bu-common.php + +sent 1,238 bytes received 43 bytes 2,562.00 bytes/sec +total size is 217,366 speedup is 169.68 (DRY RUN) diff --git a/html/bin/rsync-x-op-19.txt b/html/bin/rsync-x-op-19.txt new file mode 100644 index 0000000..9fcf8b5 --- /dev/null +++ b/html/bin/rsync-x-op-19.txt @@ -0,0 +1 @@ +html/bin/rsync-n-op-19.txt diff --git a/html/bin/run-bu-by-id.php b/html/bin/run-bu-by-id.php new file mode 100644 index 0000000..7401295 --- /dev/null +++ b/html/bin/run-bu-by-id.php @@ -0,0 +1,25 @@ +<?php + +include_once __DIR__.'/../inc/bu-common.php'; + +// NOTE: +// php.ini requires register_argc_argv = On + +// $argv[0] => the name/path of this script +$id = $argv[1]; +$isTest = $argv[2]; + +$str_err = run_backup_block( $id, $isTest=='1' ); + +/* + * Since this script is used from within the shell, we loose return values. Therefore, any error + * recieved from the previous call will be echoed to the console. It shall have the string + * "Error:" prepended. + * + * The shell exec will pickup the echoed output and test for the "Error" string. + * + * The output is silent if all went well. + */ +if($str_err!='') echo "Error: $str_err"; + +?> diff --git a/html/css/main.css b/html/css/main.css new file mode 100644 index 0000000..9c6b9e2 --- /dev/null +++ b/html/css/main.css @@ -0,0 +1,188 @@ +* { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + } + + body, html { + padding: 3px 3px 3px 3px; + background-color: #D8DBE2; + font-family: Ubuntu, Verdana, sans-serif; + font-size: 11pt; + text-align: center; + } + + div.main_page { + position: relative; + display: table; + + width: 1000px; + + margin-bottom: 3px; + margin-left: auto; + margin-right: auto; + padding: 0px 0px 0px 0px; + + border-width: 2px; + border-color: #212738; + border-style: solid; + + background-color: #FFFFFF; + + text-align: center; + } + + div.content_section_text { + padding: 4px 8px 4px 8px; + + color: #000000; + font-size: 100%; + } + + div.content_section_text pre { + margin: 8px 0px 8px 0px; + padding: 8px 8px 8px 8px; + + border-width: 1px; + border-style: dotted; + border-color: #000000; + + background-color: #F5F6F7; + + font-style: italic; + } + + div.content_section_text p { + margin-bottom: 6px; + } + + div.content_section_text ul, div.content_section_text li { + padding: 4px 8px 4px 16px; + } + + div.section_header { + padding: 3px 6px 3px 6px; + + background-color: #8E9CB2; + + color: #FFFFFF; + font-weight: bold; + font-size: 112%; + text-align: center; + margin-top: 10px; + } + + div.content_section_text a { + text-decoration: none; + font-weight: bold; + } + + + div.content_section_text a:link, + div.content_section_text a:visited, + div.content_section_text a:active { + background-color: #DCDFE6; + color: #0e3f2b; + } + + div.content_section_text a:hover { + background-color: #0e3f2b; + color: #DCDFE6; + } + +.tblcenter { + width: 100%; + margin-left: 10px; + margin-right: 10px; + text-align: left; +} + +.flex_header{ + display: flex; + justify-content: space-between; + align-items: stretch; + background-color: #0e3f2b; + border: solid lightgray 1px; + border-bottom: solid gray 1px; + min-height: 50px; + margin-bottom: 10px; +} + +.shb_Title img { + margin: 3px 3px 3px 5px; +} + +.flex_header .shb_Title { + display: flex; + flex-direction: row; + align-items: center; + color: #cfa949; + font-size: 18px; + font-weight: bold; + font-family: Helvetica, Arial, sans-serif; + margin-left: 0px; +} + +.flex_header .shb_TitleRHS { + display: flex; + flex-direction: row; + align-items: center; /*align-items is perpendicular to the flex-direction*/ + color: #c9bda2; +} + +.page_inner{ + margin-left: 10px; + margin-right: 10px; +} + +.btn_mse:hover { + cursor: pointer; +} + +.btn_exec{ + background-color: #04AA6D; + border: none; + color: white; + padding: 5px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 12px; + border-radius: 8px; +} +.btn_small { + font-size: 10px; + border-radius: 6px; + padding: 2px 5px 2px 5px; +} + +.td_label_bkup{ + text-align: right; + padding-right: 15px; +} + +table tr.high-row-style td { + padding-top: 15px; +} + +table tr td.td_icon { + width: 35px; +} + +.two-col-panel { + padding-top: 15px; + text-align: left; + display: grid; + grid-template-columns: 200px auto; +} + +.two-col-panel > div { + margin-top: 5px; +} + +.two-col-panel input { + width: 300px; +} + +.two-col-panel textarea { + width: 300px; +} diff --git a/html/img/edit-icon-24x24.png b/html/img/edit-icon-24x24.png new file mode 100644 index 0000000..7777001 --- /dev/null +++ b/html/img/edit-icon-24x24.png Binary files differ diff --git a/html/img/edit-icon-32x32.png b/html/img/edit-icon-32x32.png new file mode 100644 index 0000000..cc0151c --- /dev/null +++ b/html/img/edit-icon-32x32.png Binary files differ diff --git a/html/img/edit-icon-L.png b/html/img/edit-icon-L.png new file mode 100644 index 0000000..b2ed893 --- /dev/null +++ b/html/img/edit-icon-L.png Binary files differ diff --git a/html/img/green-tick.png b/html/img/green-tick.png new file mode 100644 index 0000000..b86cdd8 --- /dev/null +++ b/html/img/green-tick.png Binary files differ diff --git a/html/img/red-cross.png b/html/img/red-cross.png new file mode 100644 index 0000000..b965b00 --- /dev/null +++ b/html/img/red-cross.png Binary files differ diff --git a/html/img/trash-bin-red-24x24.jpg b/html/img/trash-bin-red-24x24.jpg new file mode 100644 index 0000000..0dd4304 --- /dev/null +++ b/html/img/trash-bin-red-24x24.jpg Binary files differ diff --git a/html/img/trash-bin-red-24x24.png b/html/img/trash-bin-red-24x24.png new file mode 100644 index 0000000..bdec232 --- /dev/null +++ b/html/img/trash-bin-red-24x24.png Binary files differ diff --git a/html/img/uk-green.png b/html/img/uk-green.png new file mode 100644 index 0000000..dab2252 --- /dev/null +++ b/html/img/uk-green.png Binary files differ diff --git a/html/inc/.htaccess b/html/inc/.htaccess new file mode 100644 index 0000000..15f487c --- /dev/null +++ b/html/inc/.htaccess @@ -0,0 +1,4 @@ +<Files myfile.txt> +Order Allow,Deny +Deny from all +</Files> diff --git a/html/inc/btn_run_bu.php b/html/inc/btn_run_bu.php new file mode 100644 index 0000000..170ef10 --- /dev/null +++ b/html/inc/btn_run_bu.php @@ -0,0 +1,52 @@ +<?php + +include_once 'bu-common.php'; + + +function btn_run_bu( $buid, $testrun='true' ){ + + //we cannot fork using php inside apache for some stupid reason. + //so we need to deligate a fork to an external process. + //BASH can do this with the background '&' command, so we use it. + + $output = array(); + $result_code = null; + $bIsTest = $testrun=='true'; + + if(!$bIsTest) $str_test = '0 &'; + else $str_test = '1'; + + // get realpath, cos this is what is specified in visudo: + //www-data ALL=(ALL) NOPASSWD: /usr/bin/php /var/www/html/bin/run-bu-by-id.php * + $path_to_script = realpath(__DIR__."/../bin/run-bu-by-id.php"); + $command = "sudo php $path_to_script $buid $str_test"; + + if( exec( $command, $output, $result_code )===FALSE){ + error_log( "exec to php-cli failed - command: $command"); + return array('error', "exec to php-cli failed - command: $command" ); + } + //even though the script ran, it may have echoed errors to the console. We test here. + if( count($output)>0 ){ + if( substr( $output[0], 0, 6) == 'Error:' ){ + error_log( $output[0] ); + return array('error', $output[0] ); + } + } + + if($testrun!='false'){ + //a test run waits for the command to finish, so the output can be collected and sent back now + for($i=0;$i<2;$i++){ + $fn = __DIR__."/../bin/".getFilename_out( $buid, $bIsTest, $i==0 ); + $output[] = file_get_contents($fn); + } + + return array_merge( array('done'), $output ); + } + + //error_log( "Backup kicked off for ID($buid): isTest=$isTest"); + + return array_merge( array('running'), $output ); +} + + +?> diff --git a/html/inc/bu-common.php b/html/inc/bu-common.php new file mode 100644 index 0000000..508fa44 --- /dev/null +++ b/html/inc/bu-common.php @@ -0,0 +1,234 @@ +<?php + +/** + * Retrieve a backup row from the database using it's ID. + * + * Returns the following data as an array: + * + * [ BUName, Dir_Src, Dir_Dest, Files_Ex, Bu_Type, BuRunning, BuError, LastRunDt ] + * + */ +function get_bu_item( $id ){ + + $db = new SQLite3( __DIR__.'/../bin/data.db', SQLITE3_OPEN_READONLY ); + + $html_rows=''; + $results = $db->query("SELECT * FROM BULIST where BUID=$id"); + $row = $results->fetchArray(); + + if($row){ + return array( $row['BUName'], $row['Dir_Src'], $row['Dir_Dest'], $row['Files_Ex'], $row['BuType'], $row['BuRunning'], $row['BuError'], $row['LastRunDt'] ); + } + +} + +/** + * Set (unset) the running datbase flag. Return true on success. The flag is + * unaltered if running in test mode. + */ +function set_bu_running( $id, bool $setit, bool $bu_test ){ + + if($bu_test) return true; + + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + + $setit_int = $setit?1:0; + return $db->exec("update BULIST set BuRunning=$setit_int where BUID=$id"); +} + +/** + * Set the error string in the database. + */ +function set_bu_error( $buid, $str_errs ){ + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + if($str_errs==''){ + return $db->exec("update BULIST set BuError = NULL where BUID=$buid"); + } + $p = $db->prepare("update BULIST set BuError=:str_errs where BUID=:buid"); + $p->bindValue(':str_errs', $str_errs ); + $p->bindValue(':buid', $buid ); + $p->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } +} + +/** + * + */ +function set_last_run_date( $buid, $str_dt ){ + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + $p = $db->prepare("update BULIST set LastRunDt=:str_dt where BUID=:buid"); + $p->bindValue(':str_dt', $str_dt ); + $p->bindValue(':buid', $buid ); + $p->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } +} + + +/** + * Run the backup process for the given backup ID. + * + * Returns an error string on failure. + */ +function run_backup_block( $buid, $bu_test=true ){ + + $row = get_bu_item( $buid ); + + if( !$bu_test && $row[5]==1){ + return "Backup for item($buid) is already running\n"; + } + + $bu_name = $row[0]; + $bu_src = $row[1]; + $bu_dest = $row[2]; + $exFiles = $row[3]; + $bu_type = $row[4]; + $bu_rnng = $row[5]; + + // NOTE: + // using pcntl_fork() was a waste of time because the parent always waited for the child to finish. + // so we rely on bash & + + if($bu_type==1 ){ + return run_backup_DB( $bu_test, $buid, $bu_src, $bu_dest ); + } + + return run_backup_F( $bu_test, $buid, $bu_src, $bu_dest, $exFiles ); +} + +function run_backup_F( $bu_test, $buid, $bu_src, $bu_dest, $exFiles ){ + + if(!set_bu_running( $buid, true, $bu_test )){ + return "Could not set the DB flag"; + } + + if($bu_test){ + $flags = '-anv'; // n is the dry run indicator + }else{ + $flags = '-av'; + set_bu_error( $buid, '' ); //clear any previous error + set_last_run_date( $buid, gmdate("Y-m-d H:i:s") ); + } + + $fn_o = getFilename_out( $buid, $bu_test, false ); + $fn_e = getFilename_out( $buid, $bu_test, true ); + + // create the output files with the ID in the first line + exec("echo 'ID: $buid' > bin/$fn_o"); + exec("echo 'ID: $buid' > bin/$fn_e"); + + $str_exPaths = ''; + if(trim( $exFiles )!=''){ + $fn_x = getFilename_tmp( $buid, 'x' ); + file_put_contents( "bin/$fn_x", $exFiles ); + $str_exPaths = "--exclude-from=bin/$fn_x"; + } + + // bash command + //rsync -a --exclude-from=to-exclude.txt $PATH_SRC $BKUP_DEST + //rsync -anv --exclude-from=to-exclude.txt $PATH_SRC $BKUP_DEST <-- for testing + $command = "rsync $flags $str_exPaths $bu_src $bu_dest 2>> bin/$fn_e 1>> bin/$fn_o"; + + $output = array(); + $result_code = null; + if( exec( $command, $output, $result_code )===FALSE){ + set_bu_running( $buid, false, $bu_test ); + return "rsync failed"; + } + set_bu_running( $buid, false, $bu_test ); + + // can we find any error strings in the error file? + $str_errs = file_get_contents( "bin/$fn_e" ); + $pos = strpos($str_errs, 'error' ); + if($pos===FALSE){ + $pos = strpos($str_errs, 'failed' ); + } + if($pos===FALSE) return; + + //place it in the database + set_bu_error( $buid, $str_errs ); + +} + + +/** + * Very similar to the file version. The common code is running the bach script. + */ +function run_backup_DB( $bu_test, $buid, $bu_src, $bu_dest ){ + + if(!set_bu_running( $buid, true, $bu_test )){ + return "Could not set the DB flag"; + } + + if(!is_dir($bu_dest) ){ + return "Destination ($bu_dest) is not a directory"; + } + + if($bu_test){ + $fn_o = getFilename_out( $buid, $bu_test, false ); + $bu_dest = "bin"; + }else{ + $fn_o = "$bu_src.sql"; + set_bu_error( $buid, '' ); //clear any previous error + set_last_run_date( $buid, gmdate("Y-m-d H:i:s") ); + } + + // create the error files with the ID in the first line + $fn_e = getFilename_out( $buid, $bu_test, true ); + exec("echo 'ID: $buid' > bin/$fn_e"); + + // bash command + //mysqldump johntest > /home/johnp/tmp/dbs/johntest.sql + + $command = "mysqldump $bu_src 2>> bin/$fn_e 1>> $bu_dest/$fn_o"; + + $output = array(); + $result_code = null; + if( exec( $command, $output, $result_code )===FALSE){ + set_bu_running( $buid, false, $bu_test ); + return "mysqldump failed"; + } + set_bu_running( $buid, false, $bu_test ); + + // can we find any error strings in the error file? + $str_errs = file_get_contents( "bin/$fn_e" ); + $pos = strpos($str_errs, 'error' ); + if($pos===FALSE){ + $pos = strpos($str_errs, 'failed' ); + } + if($pos===FALSE) return; + + //place it in the database + set_bu_error( $buid, $str_errs ); + +} + +function getFilename_out( $buid, bool $bu_test, bool $isErr=false ){ + return getFilename_tmp( $buid, $bu_test?'t':'n', $isErr? 'err': 'op' ); +} + +/** + * Create an output file for the backup (forked) process to use. These are essentually + * temporary files which may be deleted after the back process completes. + * + * buid: backup record id + * + * fn_type: + * t: for test files + * n: for non-test files + * x: for file exclusion list + * + * fn_stream: + * op: output stream + * err: error stream + */ +function getFilename_tmp( $buid, $fn_type='t', $fn_stream='op' ){ + return "bu-$fn_type-$fn_stream-$buid.txt"; +} + +?> diff --git a/html/inc/bu_list_content.php b/html/inc/bu_list_content.php new file mode 100644 index 0000000..b5f9a2e --- /dev/null +++ b/html/inc/bu_list_content.php @@ -0,0 +1,111 @@ +<?php + +/** + * Provides a simple json array of the BU state info for the UI to poll changes. + */ +function getbu_list_state(){ + + $db = new SQLite3('bin/data.db', SQLITE3_OPEN_READONLY ); + + $bu_state = array(); + $results = $db->query('SELECT BUID, BuRunning FROM BULIST'); + while ($row = $results->fetchArray()) { + array_push( $bu_state, array( $row['BUID'], $row['BuRunning'] ) ); + } + return $bu_state; +} + + +/** + * Returns the main backup list page. + */ +function getbu_list_content(){ + + $db = new SQLite3('bin/data.db', SQLITE3_OPEN_READONLY ); + + $html_rows=''; + $results = $db->query('SELECT * FROM BULIST'); + while ($row = $results->fetchArray()) { + //var_dump($row); + + $view_type = 'edit-file-bu'; + if($row['BuType']==1){ + $view_type = 'edit-db-bu'; + } + + $html_row = '<tr>'; + + $html_row .= '<td>'.$row['BUID'].'</td>'; + $html_row .= "<td class='td_icon' title='edit' ><img class=\"btn_mse\" onclick=\"btn_clk_nav(this, '$view_type', {$row['BUID']})\" src='img/edit-icon-24x24.png'></td>"; // edit icon + $html_row .= "<td class='td_icon' title='delete' ><img class=\"btn_mse\" onclick=\"btn_clk_nav(this, 'delete-bu', {$row['BUID']})\" src='img/trash-bin-red-24x24.png'></td>"; // trash icon + $html_row .= "<td class=\"btn_mse\" onclick=\"btn_clk_nav(this, '$view_type', {$row['BUID']})\" >{$row['BUName']}</td>"; + $but = $row['BuType']==1?'DB':'F'; + $html_row .= "<td>$but</td>"; + + if($row['BuError']!=''){ + $html_row .= "<td><span style='color:red;'>!! </span>{$row['LastRunDt']}</td>"; + }else{ + $html_row .= "<td>{$row['LastRunDt']}</td>"; + } + + if( $row['BU_REPEAT']=='N' ){ + $html_row .= "<td title='edit schedule' ><img src=\"img/red-cross.png\" class=\" btn_mse\" onclick=\"btn_clk_nav(this, 'sched', {$row['BUID']})\" ></td>"; + }else{ + $html_row .= "<td title='edit schedule' ><button class=\"btn_mse btn_exec btn_small\" onclick=\"btn_clk_nav(this, 'sched', {$row['BUID']})\" >Edit</button> {$row['BU_REPEAT']}</td>"; + } + + if($row['BuRunning']){ + $html_row .= '<td><img src="img/green-tick.png"></td>'; + }else{ + $html_row .= "<td id=\"td_run_{$row['BUID']}\" ><button class=\"btn_mse btn_exec btn_small\" onclick=\"btn_clk_nav(this, 'btn_run_bu noTest', {$row['BUID']})\" >Run Now</button></td>"; + } + + $html_row .= '</tr>'; + + $html_rows .= $html_row; + } + +$content = <<<'EOD' + + <div class="section_header" >Backup Config List</div> + + <div class="content_section_text" style="min-height: 350px;" > + <br> + <table class="tblcenter" > + + <tr> + <th>ID</th> + <th></th> + <th></th> + <th>Name</th> + <th>Type</th> + <th>Last Run (UTC)</th> + <th>Scheduled</th> + <th>Running</th> + </tr> + --INSERT-ROWS-- + <tr class="high-row-style"> + <td></td> + <td></td> + <td></td> + <td class="td_label_bkup" >Database backup</td> + <td> <button class="btn_mse btn_exec" onclick="btn_clk_nav(this, 'new-db-bu')">Create</button> </td> + </tr> + + <tr> + <td></td> + <td></td> + <td></td> + <td class="td_label_bkup" >Files backup</td> + <td> <button class="btn_mse btn_exec" onclick="btn_clk_nav(this, 'new-file-bu' )">Create</button> </td> + </tr> + + </table> + </div> +EOD; + + return str_replace('--INSERT-ROWS--', $html_rows, $content); + +} + +?> diff --git a/html/inc/common.php b/html/inc/common.php new file mode 100644 index 0000000..240ad08 --- /dev/null +++ b/html/inc/common.php @@ -0,0 +1,352 @@ +<?php +/** + * COPYRIGHT © 2022 JOHN PEARCEY + * All rights reserved +*/ + +const BASE64_DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz$_'; + +if (version_compare( PHP_VERSION, '8', '<') ){ + include_once __DIR__ . "/common-pre-v8.php"; +} + +function getStackTrace(){ + try{ + throw new Exception; + }catch( Exception $e ){ + return $e->getTraceAsString(); + } +} + +/** + * e.g. split + * ../db/stanhopetest/img/img_52548 + * into + * [ ../db/stanhopetest/img/ , img_52548 ] + * + */ +function split_file_path( $ffn ){ + $fn = basename( $ffn ); // img_52548 + //$fp = substr( $ffn, 0, strlen($ffn) - strlen($fn) ); + $fp = dirname( $ffn ) . '/'; + return [ $fp, $fn ]; +} + +/** + * Returns true if it is possible to create this directory on the file system. + * Includes the possibility of multiple directories. + */ +function is_creatable_dir( $dirs ){ + $dir = $dirs; + while(!file_exists($dir)){ + $dir = dirname( $dir ); + } + return is_writable($dir); +} + +/** + * Function to make directories and actually SET the permissions + * https://www.php.net/manual/en/function.chmod.php + * + * $perms is an octal number + * e.g. mkdirs( $dir, 02770 ); //770 and g+s + */ +function mkdirs( $dirs, $perms ){ + $dir = $dirs; + while(!file_exists($dir)){ + $dir = dirname( $dir ); + } + $rootDir = $dir; + if(!file_exists($dirs)) mkdir( $dirs, 0750, true ); //perms not always set + $dir = $dirs; + while( $dir!=$rootDir) { + chmod( $dir, $perms ); + $dir = dirname( $dir ); + } +} + +/** + * Return the contents of a url as a string using the curl functions. + */ +function getContentsFromUrl( $url ){ + + $ch = curl_init( $url ); + //$fp = fopen("example_homepage.txt", "w"); + $fp = tmpfile(); + + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_HEADER, 0); + + curl_exec($ch); + $c_err = curl_error($ch); + curl_close($ch); + if($c_err) { + error_log( $c_err ); + fclose($fp); + return; + } + + $f_path = stream_get_meta_data($fp)['uri']; + $rtn = file_get_contents( $f_path ); + fclose($fp); + + return $rtn; +} + +/** + * Set the given key-value pair. + * + * Returns the record ID. + */ +function params_set( $key, $val ) { + + $rec = R::findOne( 'params', 'pkey=?', [$key] ); + if($rec) { + $rec->pval = $val; + R::store( $rec ); + return $rec->id; + } + + $rec = R::dispense( 'params' ); + $rec->pkey = $key; + $rec->pval = $val; + return R::store( $rec ); +} + +function params_get_date( $key ){ + return strtotime( params_get($key) ); +} + +/** + * Returns the value for the given key. Returns null if no such key exists. + */ +function params_get( $key ){ + + $rec = R::findOne( 'params', 'pkey=?', [$key] ); + if(!$rec) return null; + return $rec->pval; +} + +/** + * A novel way of creating a unique ID each time a call is made. The new ID is + * returned. + * + * e.g. + * This can be used to give a client which might want to create temporary unique IDs. It is + * called once per client refresh and used to ensure uniqueness of certain CSS data points. + */ +function params_unique( ) { + + $rec = R::dispense( 'params' ); + $rec->pkey = 'unique'; + $id = R::store( $rec ); + R::exec( "delete from params where id<$id and pkey='unique'" ); + return $id; +} + +/** + * Return a string representation for the given dom node suitable for debugging. + */ +function getNodeString( $rmElem ){ + + if($rmElem==null) return "Element is null"; + $str=""; + if( $rmElem->nodeType==XML_ELEMENT_NODE){ + $str .= $rmElem->nodeName; + $a = $rmElem->getAttribute("shb_compid"); + if($a){ + $str .= " shb_compid=$a"; + } + }else{ + $str .= 'type = '.$rmElem->nodeType; + } + return $str; +} + +/** + * Simple logger to write to a file. Using php error_log distorts all crlf pairs. This + * is a pain when you're trying to preserve them in html. + */ +function mylog( $msg ){ + + global $logging_on; + + if(!$logging_on) return; + $p = __DIR__ . "/../../shb.log"; + $h = fopen( $p, "a"); + if($h===false) return; + fwrite( $h, "$msg\n"); + fclose($h); + +} + +function generateNonce( $digitCount = 12 ){ + // e.g. 422466a05f24b09c978fa1f6 + return bin2hex( random_bytes($digitCount) ); +} + +/** + * Generate a randon username starting with a letter and containing only letters and numbers. + */ +function generateRndUserName( $digitCount = 10 ){ + $l1 = generateRndStrUsing( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 ); + return $l1 . generateRndStrUsing( BASE64_DIGITS, $digitCount-1 ); +} + +/** + * Generate a randon password with letters and numbers and some funny stuff. + */ +function generateRndPwd( $digitCount = 10 ){ + return generateRndStrUsing( '0123456789_!"£$%^&*()_-|\?/<>#~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', $digitCount ); +} + +/** + * Generate a randon string of asc char. Default consisting of only [0-9][a-z][A-Z] + */ +function generateRndStrUsing( $using=BASE64_DIGITS, $digitCount = 8 ){ + + $str = ''; + $maxlen = strlen($using); + for($i=0; $i<$digitCount; $i++){ + $iv = random_int(0, $maxlen-1); + $str .= mb_substr( $using, $iv, 1); //use mb_substr because £ is not and asc char! + } + return $str; +} + + +/** + * Remove all html from the given string returning just the text content. + */ +function removeHtml( $str ){ + //txt = txt.replace(/<div>/gi, '\n'); + return preg_replace( '/<\/?[^>]+>/i', '', $str ); +} + +function getHtmlOkAsString( $data ){ + $data = json_encode( $data ); + return "[\"OK\", $data ]"; +} + +function sendHtmlOk( $data="" ){ + echo getHtmlOkAsString( $data ); +} + +/** + * An effort to rectify my previously ill-thought out return encoding. You do not + * need to encode the data before calling this method. Any arrays will be json encoded. + */ +function sendHtmlOk_WithData( $data ){ + $data = json_encode( $data ); + echo "[\"OK\", $data ]"; +} + +function getHtmlErrorAsString( $strErrDesc = "--blank--" ){ + $data = json_encode( $strErrDesc ); + return "[\"Error\", $data]"; +} + +function sendHtmlError( $strErrDesc = "--blank--" ){ + $rtn = getHtmlErrorAsString( $strErrDesc ); + mylog( $rtn ); + echo $rtn; +} + +/** + * + */ +function getAttForPath( $name, $prefPath, $path){ + return "$name = \"$prefPath/$path\""; +} + +global $g_pub_path; + +/** + * If the browser=true, the file returned will be correct for adding to an href or src attribute so that the + * browser will find the link. + */ +function getDirForType( $filetype='html', $browser=false, $userid=-1 ){ + + global $g_pub_path; + + if($g_pub_path==null){ + $g_pub_path = params_get('pub_path'); + if($g_pub_path==null){ + throw new Exception("Publishing now requires a valid key('pub_path') in the params table for the publishing output path"); + } + + if(str_ends_with( $g_pub_path, '/') ) $g_pub_path = substr($g_pub_path, 0, strlen($g_pub_path)-1 ); + + //ensure all the directories exist + createUserDir( 'img', $userid ); + createUserDir( 'css', $userid ); + createUserDir( 'html', $userid ); + createUserDir( 'js', $userid ); + + } + + return getDirForType_( $filetype, $browser, $userid ); + +} + +function createUserDir( $dName, $userid ){ + + if(!file_exists(getDirForType_( $dName, false, $userid ))) { + $dir = getDirForType_( $dName, false, $userid ); + error_log("common.php: createUserDir: $dir"); + mkdir( $dir, 0770, true); + } +} + +/** + * Part of the new path handling system is to sepatate out path names from real files +*/ +function mkdirIfNotExists( $d ){ + if(!file_exists( $d ) ) { + error_log("common.php: createUserDir: $d"); + mkdir( $d, 0770, true ); + } +} + +/** + * For the moment, call getDirForType and not this function. In time, the setup routine will make the directories + * and eliminate the need for the check during a user session. + * + * If the browser flag is set, we assuming a document root sub-dir. But in time, this will need to also cater for + * a site serving the g_pub_path as root (wrt the browser). + * + */ +function getDirForType_( $filetype, $browser, $userid ){ + + global $g_pub_path; + $strPath = $g_pub_path; + + + if($userid>0){ + $strPath .= "_dbg_$userid"; + } + + switch($filetype){ + + case 'img': + if($browser) return "/$strPath/img/"; + return fxdPth( "$strPath/img/" ); + + case 'js': + if($browser) return "/$strPath/js/"; + return fxdPth( "$strPath/js/" ); + + case 'css': + if($browser) return "/$strPath/css/"; + return fxdPth( "$strPath/css/" ); + + case 'html': + default: + if($browser) return "/$strPath/"; + return fxdPth( "$strPath/" ); + + } +} + + +//NO white-space diff --git a/html/inc/delete-file-bu.php b/html/inc/delete-file-bu.php new file mode 100644 index 0000000..b24d99c --- /dev/null +++ b/html/inc/delete-file-bu.php @@ -0,0 +1,17 @@ +<?php + + +function delete_bu_item( $id ){ + + $db = new SQLite3('bin/data.db' ); + $db_st = $db->prepare('delete from BULIST where BUID=:buid'); + $db_st->bindValue(':buid', $id ); + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + +?> diff --git a/html/inc/edit-sh-bu.php b/html/inc/edit-sh-bu.php new file mode 100644 index 0000000..920a1c7 --- /dev/null +++ b/html/inc/edit-sh-bu.php @@ -0,0 +1,111 @@ +<?php + +function edit_sh_bu_getContent( $db_id ){ + + $db = new SQLite3('bin/data.db', SQLITE3_OPEN_READONLY ); + + $db_st = $db->prepare('SELECT * FROM BULIST where BUID=:buid'); + $db_st->bindValue(':buid', $db_id); + $results = $db_st->execute(); + $row = $results->fetchArray(); + if ( $row===FALSE ) { + return "Item $db_id not found"; + } + + $bun = $row['BUName']; + $bus = $row['Dir_Src']; + $bu_dt = $row['BU_DATE']; + if($bu_dt==''){ + $bu_dt = date("Y-m-d"); + } + $bu_t = $row['BU_TIME']; + if($bu_t==''){ + $bu_t = '00:00'; + } + $bu_r = $row['BU_REPEAT']; + + switch($bu_r){ + case 'D': + $id_checked = 'id="sched_ip_daily"'; + break; + case 'W': + $id_checked = 'id="sched_ip_weekly"'; + break; + case 'M': + $id_checked = 'id="sched_ip_monthly"'; + break; + case 'N': + default: + $id_checked = 'id="sched_ip_none"'; + } + +$content = <<<'EOD' + + <div class="section_header" >Edit Backup Schedule</div> + + <div class="content_section_text" style="min-height: 350px;" > + + <div class="two-col-panel"> + + <div>ID</div> + <div>$db_id</div> + + <div>Backup Name</div> + <div>$bun</div> + + <div>Data Source</div> + <div>$bus</div> + + <div>Next Run Date/Time</div> + <div><input id="sched_date" type="date" value="$bu_dt" ></div> + + <div></div> + <div><input id="sched_time" type="time" value="$bu_t"></div> + + <div>Repeat</div> + <div> + <input style="width: 25px;" type="radio" name="sched_period" id="sched_ip_none" ><label for="sched_ip_none">None</label><br> + <input style="width: 25px;" type="radio" name="sched_period" id="sched_ip_daily"><label for="sched_ip_daily">Daily</label><br> + <input style="width: 25px;" type="radio" name="sched_period" id="sched_ip_weekly" ><label for="sched_ip_weekly">Weekly</label><br> + <input style="width: 25px;" type="radio" name="sched_period" id="sched_ip_monthly"><label for="sched_ip_monthly">Monthly</label> + </div> + + <div></div> + <div> + <button class="btn_exec" onclick="btn_clk_nav(this, 'btn_edit_shed_bu', $db_id)" >Save</button> + <button class="btn_exec" onclick="btn_clk_nav(this, 'btn_cancel')" >Cancel</button> + </div> + + </div> + + </div> +EOD; + + $content = str_replace($id_checked, $id_checked.' checked ', $content ); + $content = str_replace('$bus', $bus, $content); + $content = str_replace('$bun', $bun, $content); + $content = str_replace('$bu_dt', $bu_dt, $content); + $content = str_replace('$bu_t', $bu_t, $content); + return str_replace('$db_id', $db_id, $content); +} + +function save_bu_schedule( $postVars ){ + + //error_log( $postVars['id'] ); + + $db = new SQLite3('bin/data.db' ); + + $db_st = $db->prepare('UPDATE BULIST SET BU_DATE=:bud, BU_TIME=:but, BU_REPEAT=:bur where BUID=:buid'); + $db_st->bindValue(':buid', $postVars['id'], SQLITE3_INTEGER ); + $db_st->bindValue(':bud', $postVars['sched_date'] ); + $db_st->bindValue(':but', $postVars['sched_time'] ); + $db_st->bindValue(':bur', $postVars['sched_period'] ); + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + +?> diff --git a/html/inc/login.php b/html/inc/login.php new file mode 100644 index 0000000..9304f5c --- /dev/null +++ b/html/inc/login.php @@ -0,0 +1,45 @@ +<?php + + +function getLogin_content( ){ + +$content = <<<'EOD' + + <div class="section_header" >Login</div> + + <div class="content_section_text" style="min-height: 350px;" > + + <div class="two-col-panel"> + + <div>Login Name</div> + <div><input id="login_name" type="text" $login_name_value ></div> + + <div>Password</div> + <div><input id="login_pwd" type="password" ></div> + + + <div></div> + <div> + <button class="btn_exec" onclick="btn_clk_nav(this, 'btn_login')" >Login</button> + </div> + + </div> + + </div> +EOD; + + //$content = str_replace('$header_cont', $header_cont, $content); + return $content; +} + +function authenticate( $username, $hashed_pwd_client, $svr_nonce ){ + + //TODO: + // get the username and password from somewhere. + $hashed_pwd_correct = hash('sha256', 'biggles'.$svr_nonce ); + + return $hashed_pwd_correct == $hashed_pwd_client; +} + + +?> diff --git a/html/inc/new-file-bu.php b/html/inc/new-file-bu.php new file mode 100644 index 0000000..0c6951d --- /dev/null +++ b/html/inc/new-file-bu.php @@ -0,0 +1,172 @@ +<?php + +include_once 'bu-common.php'; + + +/** + * This page provides the UI for a backup item for both new items and editing items for both + * database style and files. So there are 4 combinations: + * + * $bDbStyle = true, $db_id = 0: Create new database backup item + * $bDbStyle = false, $db_id = 0: Create new files backup item + * $bDbStyle = true, $db_id != 0: Edit an existing database backup item + * $bDbStyle = false, $db_id != 0: Edit an existing files backup item + * + */ +function getbu_cf_content( $bDbStyle = false, $db_id = 0 ){ + + $id_line=''; + $header_cont=''; + $lab1_cont = ''; + $exfiles_cont = ''; + $btn_new_bu = ''; + $item_data = null; + + $bu_name_value = ''; + $bu_src_value = ''; + $bu_dest_value = ''; + $bu_ex_files = ''; + $lastRunTime = ''; + $bu_error_list = ''; + + if($db_id>0){ + $id_line = "<div>ID</div><div>$db_id</div>"; + $item_data = get_bu_item( $db_id ); // [ BUName, Dir_Src, Dir_Dest, Files_Ex, Bu_Type, BuRunning, BuError, LastRunDt ] + + $bu_name_value = "value='{$item_data[0]}'"; + $bu_src_value = "value='{$item_data[1]}'"; + $bu_dest_value = "value='{$item_data[2]}'"; + $bu_ex_files = $item_data[3]; + + $bu_error_list = str_replace( "\n", '<br>', $item_data[6]); + $bu_error_list = "<div>Errors</div><div>$bu_error_list</div>"; + + $lastRunTime = "<div>Last Run (UTC)</div><div>{$item_data[7]}</div>"; + + } + + if($bDbStyle){ + if($db_id>0){ + $header_cont = '<div class="section_header" >Edit Database Backup Item</div>'; + $btn_new_bu = "btn_edit_bu_db','$db_id"; + + }else{ + $header_cont = '<div class="section_header" >Create New Database Backup Item</div>'; + $btn_new_bu = 'btn_new_bu_db'; + + } + $lab1_cont = '<div>Database name</div>'; + + }else{ + if($db_id>0){ + $header_cont = '<div class="section_header" >Edit File Backup Item</div>'; + $btn_new_bu = "btn_edit_bu_file','$db_id"; + + }else{ + $header_cont = '<div class="section_header" >Create New File Backup Item</div>'; + $btn_new_bu = 'btn_new_bu_file'; + + } + + $lab1_cont = '<div>Source File/Dir</div>'; + $exfiles_cont = "<div>Exclude Files</div><div><textarea id=\"new_bu_exf\" rows=\"4\" >$bu_ex_files</textarea></div>"; + } + +$content = <<<'EOD' + + $header_cont + + <div class="content_section_text" style="min-height: 350px;" > + + <div class="two-col-panel"> + + $id_line + + <div>Backup Name</div> + <div><input id="new_bu_name" type="text" $bu_name_value > (optional)</div> + + $lab1_cont + <div><input id="new_bu_source" type="text" $bu_src_value ></div> + + <div>Destination Dir</div> + <div><input id="new_bu_dest" type="text" $bu_dest_value ></div> + + $exfiles_cont + + <div></div> + <div> + <button class="btn_exec" onclick="btn_clk_nav(this, 'btn_run_bu')" >Test</button> + <button class="btn_exec" onclick="btn_clk_nav(this, '$btn_new_bu')" >Save</button> + <button class="btn_exec" onclick="btn_clk_nav(this, 'btn_cancel_new_bu')" >Cancel</button> + </div> + + <div>Test output</div> + <div><textarea id="ta_testoutput" rows="8" readonly ></textarea></div> + + $lastRunTime + $bu_error_list + + </div> + + </div> +EOD; + + $content = str_replace('$bu_error_list', $bu_error_list, $content); + $content = str_replace('$lastRunTime', $lastRunTime, $content); + $content = str_replace('btn_run_bu\'', "btn_run_bu', $db_id", $content); + $content = str_replace('$bu_name_value', $bu_name_value, $content); + $content = str_replace('$bu_src_value', $bu_src_value, $content); + $content = str_replace('$bu_dest_value', $bu_dest_value, $content); + + $content = str_replace('$id_line', $id_line, $content); + $content = str_replace('$btn_new_bu', $btn_new_bu, $content); + $content = str_replace('$exfiles_cont', $exfiles_cont, $content); + $content = str_replace('$lab1_cont', $lab1_cont, $content); + $content = str_replace('$header_cont', $header_cont, $content); + return $content; +} + +/** + * Both inserts as well as edits a single backup item. + */ +function save_bu_item( $postVars, $bu_id=0 ){ + + //error_log( "save_bu_item id = $bu_id, new_bu_dest = {$postVars['new_bu_dest']}" ); + + $db = new SQLite3('bin/data.db' ); + $db_st = null; + if( $postVars['new_bu_is_dbType'] ){ + if($bu_id==0){ + $db_st = $db->prepare('INSERT INTO BULIST ( BUName, Dir_Src, Dir_Dest, BuType ) values( :bun, :bus, :bud, 1 )'); + }else{ + $db_st = $db->prepare('update BULIST set BUName=:bun, Dir_Src=:bus, Dir_Dest=:bud where BUID=:buid'); + } + }else{ + if($bu_id==0){ + $db_st = $db->prepare('INSERT INTO BULIST ( BUName, Dir_Src, Dir_Dest, Files_Ex, BuType ) values( :bun, :bus, :bud, :buex, 0 )'); + }else{ + $db_st = $db->prepare('update BULIST set BUName=:bun, Dir_Src=:bus, Dir_Dest=:bud, Files_Ex=:buex where BUID=:buid'); + } + } + + if($bu_id>0){ + $db_st->bindValue(':buid', $bu_id ); + } + + $db_st->bindValue(':bun', $postVars['new_bu_name'] ); + $db_st->bindValue(':bus', $postVars['new_bu_source'] ); + $db_st->bindValue(':bud', $postVars['new_bu_dest'] ); + if( !$postVars['new_bu_is_dbType'] ){ + $db_st->bindValue(':buex', $postVars['new_bu_exf'] ); + } + + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + + +?> diff --git a/html/inc/pagemap.php b/html/inc/pagemap.php new file mode 100644 index 0000000..11801de --- /dev/null +++ b/html/inc/pagemap.php @@ -0,0 +1,156 @@ +<?php +/** + * COPYRIGHT © 2024 JOHN PEARCEY + * All rights reserved +*/ + + +include_once __DIR__ . "/common.php"; + +/** + * The commands should all return true to indicate that they are valid calls from the client. + */ +function invokeCommand( $cmd, $postvars ){ + + if( $cmd=='backup-list-state' ){ + include_once __DIR__ . "/bu_list_content.php"; + sendHtmlOk_WithData( [ 'state', getbu_list_state() ] ); + return true; + } + + if( $cmd=='backup-list' ){ + include_once __DIR__ . "/bu_list_content.php"; + sendHtmlOk_WithData( [ 'page', getbu_list_content() ] ); + return true; + } + + if( $cmd=='edit-db-bu' ){ + include_once __DIR__ . "/new-file-bu.php"; + sendHtmlOk_WithData( [ 'page', getbu_cf_content( true, $postvars['id'] ) ] ); + return true; + } + + if( $cmd=='edit-file-bu' ){ + include_once __DIR__ . "/new-file-bu.php"; + sendHtmlOk_WithData( [ 'page', getbu_cf_content( false, $postvars['id'] ) ] ); + return true; + } + + if( $cmd=='new-db-bu' ){ + include_once __DIR__ . "/new-file-bu.php"; + sendHtmlOk_WithData( [ 'page', getbu_cf_content(true) ] ); + return true; + } + + if( $cmd=='new-file-bu' ){ + include_once __DIR__ . "/new-file-bu.php"; + sendHtmlOk_WithData( [ 'page', getbu_cf_content() ] ); + return true; + } + + if( $cmd=='sched' ){ + include_once __DIR__ . "/edit-sh-bu.php"; + sendHtmlOk_WithData( [ 'page', edit_sh_bu_getContent( $postvars['id'] ) ] ); + return true; + } + + if( $cmd=='btn_run_bu' ){ + + include_once __DIR__ . "/btn_run_bu.php"; + $res = btn_run_bu( $postvars['id'], $postvars['isTest'] ); + + if($res[0]=='running'){ + // script kicked off asynchronously + sendHtmlOk_WithData( $res ); + return true; + } + + if($res[0]=='done'){ + // script ran synchronously and finished + sendHtmlOk_WithData( $res ); + return true; + } + + if($res[0]=='error'){ + sendHtmlError( $res ); + return true; + } + + sendHtmlError( 'Unknown return value from btn_run_bu' ); + return true; + } + + if( $cmd=='edit_shed_bu' ){ + include_once __DIR__ . "/edit-sh-bu.php"; + $db_err = save_bu_schedule( $postvars ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + } + + + if( $cmd=='new-item-bu' ){ + + include_once __DIR__ . "/new-file-bu.php"; + $db_err = save_bu_item( $postvars ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + + if( $cmd=='edit-item-bu' ){ + + include_once __DIR__ . "/new-file-bu.php"; + $db_err = save_bu_item( $postvars, $postvars['id'] ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + + if( $cmd=='delete-bu' ){ + + include_once __DIR__ . "/delete-file-bu.php"; + $db_err = delete_bu_item( $postvars['id'] ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + +} + +?> diff --git a/html/inc/secure.php b/html/inc/secure.php new file mode 100644 index 0000000..0e1450a --- /dev/null +++ b/html/inc/secure.php @@ -0,0 +1,77 @@ +<?php + +/*if( isset($_POST['action']) ) { + error_log("Login action:".$_POST['action'] . "," . $_POST['username'] . "," . $_POST['password'] ); +}*/ + +session_start(); + +if( isset($_GET['action']) && $_GET['action']=='logout' ) { + unset( $_SESSION['username'] ); + include_once "index.php"; + exit; +} + +if( isset($_SESSION['username']) && strlen($_SESSION['username'])>=4 ){ + //the user is logged in so nothing further to do in this script + return; +} + +//is there a login attempt +if( isset($_POST['username']) && isset($_POST['password']) ){ + + //any request must have the 'action' set to btn_login + if( !isset($_POST['action']) || $_POST['action']!='btn_login' ) { + error_log("Login Error: Missing or incorrect value for 'action'"); + exit; + } + + if( !isset($_POST['svr_nonce']) || $_POST['svr_nonce']=='' ) { + error_log("Login Error: Missing or blank value for 'svr_nonce'"); + exit; + } + + //has the correct nonce been returned? + if( $_POST['svr_nonce']!=$_SESSION['svr_nonce'] ){ + $_SESSION['svr_nonce'] = ''; // invalidate nonce + error_log("Login Error: Incorrect value for 'svr_nonce'"); + exit; + } + + include_once "common.php"; + include_once "login.php"; + if(authenticate( $_POST['username'], $_POST['password'], $_SESSION['svr_nonce'] ) ){ + + error_log("Login Success: ".$_POST['username']); + $_SESSION['username'] = $_POST['username']; + + //return the backup list page + include_once __DIR__ . "/bu_list_content.php"; + sendHtmlOk_WithData( [ 'authenticated', getbu_list_content() ] ); + + }else{ + error_log("Login Failure: ".$_POST['username']); + // failed authentication + // --- Send back the login page --- + $_SESSION['svr_nonce'] = generateNonce(); + sendHtmlOk_WithData( [ 'login', getLogin_content(), $_SESSION['svr_nonce'] ] ); + } + + exit; +} + +// no session is set +// no attempt to login was made +// likely that the page was just refreshed +// --- Send back the login page --- + +include_once "common.php"; +$_SESSION['svr_nonce'] = generateNonce(); + +include_once "login.php"; +sendHtmlOk_WithData( [ 'login', getLogin_content(), $_SESSION['svr_nonce'] ] ); +exit; //end the script to stop further processing by calling files + +//$svr_username = $_SESSION['username']; + +?> diff --git a/html/index.php b/html/index.php new file mode 100644 index 0000000..3232869 --- /dev/null +++ b/html/index.php @@ -0,0 +1,42 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Pearcey: Excecutive Backup Panel</title> + + <link rel="stylesheet" href="css/main.css"/> + + <script src="./js/common.js"></script> + <script src="./js/main.js"></script> + + </head> + <body onload="index_onload()"> + + <div class="main_page"> + + <div id="shb_Header" class="flex_header" > + <div id="shb_Title" class="shb_Title" ><img src="img/uk-green.png" style="margin-right: 20px; height: 40px;" >Executive Backup Panel <span id="shb_pageName" style="margin-left:30px; font-size: 10px;"></span></div> + <a id="anc_login" class="shb_TitleRHS" href="post_handler.php?action=logout" style="margin-right: 10px;" >Login</a> + </div> + + <div class="page_inner"> + + <div id="page_content"></div> + + <div class="section_header">Panel Help</div> + + <div class="content_section_text"> + + <p>For help using this panel, please email john@pearcey.net</p> + + <p>This backup utility is open source and can be downloaded from <a href="http://pearcey.net:8080/">pearcey.net</a></p> + + <p>Please report bugs to john@pearcey.net</p> + </div> + + </div> + </div> + + </body> +</html> diff --git a/html/js/common.js b/html/js/common.js new file mode 100644 index 0000000..dc25eab --- /dev/null +++ b/html/js/common.js @@ -0,0 +1,101 @@ + +/** + * COPYRIGHT © 2022 JOHN PEARCEY + * All rights reserved +*/ + +/* + * pagename: + * The name of the server-side script to handle this post. + * fnCallback: + * The javascript callback function, called after the AJAX call completes. + * map_params: + * A map containing name value pairs required by the script. + * e.g. + * map_params = new Map(); + map_params.set('username', document.getElementById('username').value ); + map_params.set('pwdhash', document.getElementById('pwd').value ); + */ +function postData( pagename, fnCallback, map_params ){ + + //console.log( 'postData called' ); + + let formData = new FormData(); + map_params.forEach((value, key, map) => { + //if(key=='action') console.log("postData: ", pagename, value ); + formData.append( key, value ); + }); + + //https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-fetch-api-to-get-data + //https://jsonplaceholder.typicode.com/users + //https://javascript.info/fetch + fetch( pagename, { + method: 'POST', + body: formData, + + }) + /*.then((response) => { + return response.json(); */ + + .then(response => response.text()) + .then((dataStr) => { + + //console.log(dataStr); + + //there's a whole bunch of warnings and errors that might be sent by PHP. It is not possible + //to catch them in the serverside PHP code. So here we search for our json encoded return string which + //must be either Error or OK. + let pos = dataStr.indexOf("[\"OK\""); + let additional=null; + if(pos==-1){ + pos = dataStr.indexOf("[\"Error\""); + if(pos==-1){ + //the PHP is pretty fucked up if you get here! + console.warn( dataStr ); + let encodedStr = JSON.stringify( ["Illegal return from server. Did not find Error or OK.", dataStr ] ); + return JSON.parse( encodedStr ); + } + } + //console.log(dataStr); + return JSON.parse(dataStr); + + }).then((data) => { + fnCallback( true, data, map_params ); + + }).catch(function(error) { + console.log( error ); + fnCallback( false, error, map_params ); + }); + +} + +function checkReturn( success, data, bGetErr=false ){ + + if(!success){ + console.warn( "callback error: " + data ); + return false; + } + + if(data[0]=="Error"){ + if(bGetErr) return data; + alert( "Error: " + data[1] ); + for( i=2; i<data.length; i++){ + console.warn( data[i] ); + } + return false; + } + + if(data[0]!="OK"){ + console.warn( "Unkown server return string ", data ); + if(bGetErr) return data; + return false; + } + if(bGetErr) return null; + return true; +} + +function stripHtml( txt ){ + txt = txt.replace(/<div>/gi, '\n'); + txt = txt.replace(/<\/?[^>]+>/gi, ''); //strip HTML + return txt; +} diff --git a/html/js/main.js b/html/js/main.js new file mode 100644 index 0000000..78fb441 --- /dev/null +++ b/html/js/main.js @@ -0,0 +1,454 @@ + +/** + * 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': + if(instrArr.length==2 && instrArr[1]=='noTest'){ + map_params.set('isTest', 'false' ); + }else{ + map_params.set('isTest', 'true' ); + console.log("running a test backup"); + } + exec_instruction( instr, map_params ); + 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; + } + + switch(instr){ + case 'btn_run_bu': + + if(data[1][0]=='done'){ + // this comes from the test run, although I should really explicitly send back the 'test' flag + let elem = document.getElementById( "ta_testoutput" ); + if(elem==null){ + console.log( data ); + return; + } + setTextAreaFromArray( elem, data[1] ); + return; + } + + if(data[1][0]!='running'){ + console.log("odd message: " + data[1][0] ); + console.log( data ); + return; + } + + //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; + } + } + } + + }, 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' ); + } + + +} + diff --git a/html/phpinfo.php b/html/phpinfo.php new file mode 100644 index 0000000..cf60860 --- /dev/null +++ b/html/phpinfo.php @@ -0,0 +1,3 @@ +<?php +phpinfo(); +?> diff --git a/html/post_handler.php b/html/post_handler.php new file mode 100644 index 0000000..bce003d --- /dev/null +++ b/html/post_handler.php @@ -0,0 +1,25 @@ +<?php +/** + * COPYRIGHT © 2024 JOHN PEARCEY + * All rights reserved +*/ + +//error_log("POST: " . print_r($_POST, true) ); + +include_once __DIR__ . "/inc/secure.php"; +include_once __DIR__ . "/inc/common.php"; + +if( !isset($_POST['action']) ){ + error_log("Post Error: Missing value for 'action'"); + exit; +} + +//error_log("post_handler.php: " . $_POST['action'] ); + +include_once __DIR__ . "/inc/pagemap.php"; +if( invokeCommand( $_POST['action'], $_POST ) ) exit; + +error_log( "Error: Illegal value for action: ". $_POST['action'] ); +sendHtmlError( "Illegal call to " . __FILE__ ); + +?>