فهرست منبع

Added GUI in system settings for backup disks

TC pushbot 5 4 سال پیش
والد
کامیت
c1939c98a9

+ 54 - 4
backup.go

@@ -2,10 +2,13 @@ package main
 
 import (
 	"encoding/json"
+	"errors"
 	"net/http"
 	"path/filepath"
+	"strings"
 
 	"imuslab.com/arozos/mod/disk/hybridBackup"
+	user "imuslab.com/arozos/mod/user"
 
 	prout "imuslab.com/arozos/mod/prouter"
 )
@@ -25,6 +28,16 @@ func backup_init() {
 	router.HandleFunc("/system/backup/restoreFile", backup_restoreSelected)
 	router.HandleFunc("/system/backup/snapshotSummary", backup_renderSnapshotSummary)
 	router.HandleFunc("/system/backup/listAll", backup_listAllBackupDisk)
+
+	//Register settings
+	registerSetting(settingModule{
+		Name:         "Backup Disks",
+		Desc:         "All backup disk in the system",
+		IconPath:     "img/system/backup.svg",
+		Group:        "Disk",
+		StartDir:     "SystemAO/disk/backup/backups.html",
+		RequireAdmin: true,
+	})
 }
 
 //List all backup disk info
@@ -199,8 +212,15 @@ func backup_restoreSelected(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	//Pick the correct HybridBackup Manager
+	targetHybridBackupManager, err := backup_pickHybridBackupManager(userinfo, fsh.UUID)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
 	//Handle restore of the file
-	err = userinfo.HomeDirectories.HyperBackupManager.HandleRestore(fsh.UUID, relpath, &userinfo.Username)
+	err = targetHybridBackupManager.HandleRestore(fsh.UUID, relpath, &userinfo.Username)
 	if err != nil {
 		sendErrorResponse(w, err.Error())
 		return
@@ -217,7 +237,7 @@ func backup_restoreSelected(w http.ResponseWriter, r *http.Request) {
 	}
 
 	//Get access path for this file
-	parentDiskId, err := userinfo.HomeDirectories.HyperBackupManager.GetParentDiskIDByRestoreDiskID(fsh.UUID)
+	parentDiskId, err := targetHybridBackupManager.GetParentDiskIDByRestoreDiskID(fsh.UUID)
 	if err != nil {
 		//Unable to get parent disk ID???
 
@@ -240,6 +260,31 @@ func backup_restoreSelected(w http.ResponseWriter, r *http.Request) {
 	sendJSONResponse(w, string(js))
 }
 
+//As one user might be belongs to multiple groups, check which storage pool is this disk ID owned by and return its corect backup maanger
+func backup_pickHybridBackupManager(userinfo *user.User, diskID string) (*hybridBackup.Manager, error) {
+	//Filter out the :/ if it exists in the disk ID
+	if strings.Contains(diskID, ":") {
+		diskID = strings.Split(diskID, ":")[0]
+	}
+
+	//Get all backup managers that this user ac can access
+	userpg := userinfo.GetUserPermissionGroup()
+
+	if userinfo.HomeDirectories.ContainDiskID(diskID) {
+		return userinfo.HomeDirectories.HyperBackupManager, nil
+	}
+
+	//Extract the backup Managers
+	for _, pg := range userpg {
+		if pg.StoragePool.ContainDiskID(diskID) {
+			return pg.StoragePool.HyperBackupManager, nil
+		}
+
+	}
+
+	return nil, errors.New("Disk ID not found in any storage pool this user can access")
+}
+
 //Generate and return a restorable report
 func backup_listRestorable(w http.ResponseWriter, r *http.Request) {
 	//Get user accessiable storage pools
@@ -263,10 +308,15 @@ func backup_listRestorable(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	userBackupManager := userinfo.HomeDirectories.HyperBackupManager
+	//Get all backup managers that this user ac can access
+	targetBackupManager, err := backup_pickHybridBackupManager(userinfo, vroot)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
 
 	//Get the user's storage pool and list restorable by the user's storage pool access
-	restorableReport, err := userBackupManager.ListRestorable(fsh.UUID)
+	restorableReport, err := targetBackupManager.ListRestorable(fsh.UUID)
 	if err != nil {
 		sendErrorResponse(w, err.Error())
 		return

+ 3 - 2
mod/disk/hybridBackup/hybridBackup.go

@@ -191,11 +191,12 @@ func (backupConfig *BackupTask) HandleBackupProcess() (string, error) {
 		if backupConfig.CycleCounter%3 == 0 {
 			//Perform deep backup, use walk function
 			deepBackup = true
+			log.Println("[HybridBackup] Basic backup executed: " + backupConfig.ParentUID + ":/ -> " + backupConfig.DiskUID + ":/")
+			backupConfig.LastCycleTime = time.Now().Unix()
 		} else {
 			deepBackup = false
-			backupConfig.LastCycleTime = time.Now().Unix()
 		}
-		log.Println("[HybridBackup] Basic backup executed: " + backupConfig.ParentUID + ":/ -> " + backupConfig.DiskUID + ":/")
+
 		//Add one to the cycle counter
 		backupConfig.CycleCounter++
 		executeBackup(backupConfig, deepBackup)

+ 11 - 0
mod/storage/storage.go

@@ -77,6 +77,17 @@ func NewStoragePool(fsHandlers []*fs.FileSystemHandler, owner string) (*StorageP
 	}, nil
 }
 
+//Check if this storage pool contain this particular disk ID
+func (s *StoragePool) ContainDiskID(diskID string) bool {
+	for _, fsh := range s.Storages {
+		if fsh.UUID == diskID {
+			return true
+		}
+	}
+
+	return false
+}
+
 //Use to compare two StoragePool permissions leve
 func (s *StoragePool) HasHigherOrEqualPermissionThan(a *StoragePool) bool {
 	if s.OtherPermission == "readonly" && a.OtherPermission == "readwrite" {

+ 2 - 2
storage.pool.go

@@ -188,7 +188,7 @@ func setFSHConfigByGroupAndId(group string, uuid string, options fs.FileSystemOp
 
 	//Write config back to file
 	js, _ := json.MarshalIndent(newConfig, "", " ")
-	return ioutil.WriteFile(targerFile, js, 777)
+	return ioutil.WriteFile(targerFile, js, 0775)
 }
 
 //Handle Storage Pool toggle on-off
@@ -492,7 +492,7 @@ func HandleStorageNewFsHandler(w http.ResponseWriter, r *http.Request) {
 	js, err := json.Marshal(oldConfigs)
 	resultingJson := pretty.Pretty(js)
 
-	err = ioutil.WriteFile(configFile, resultingJson, 777)
+	err = ioutil.WriteFile(configFile, resultingJson, 0775)
 	if err != nil {
 		//Write Error. This could sometime happens on Windows host for unknown reason
 		js, _ := json.Marshal(errorObject{

+ 131 - 0
web/SystemAO/disk/backup/backups.html

@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta name="mobile-web-app-capable" content="yes">
+	<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+	<meta charset="UTF-8">
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <script src="../../script/jquery.min.js"></script>
+	<script src="../../script/semantic/semantic.min.js"></script>
+    <style>
+        .hidden{
+            display:none;
+        }
+
+        .disabled{
+            opacity: 0.5;
+            pointer-events: none;
+        }
+    </style>
+</head>
+<body>
+    <div class="ui container">
+        <div class="ui basic segment">
+            <h3 class="ui header">
+                Backup Disk
+                <div class="sub header">Virtual Roots managed by Hybrid Backup Manager</div>
+            </h3>
+        </div>
+       
+        <table class="ui celled table">
+            <thead>
+              <tr><th>Backup Disk (UUID)</th>
+              <th>Source Disk (UUID)</th>
+              <th>Mode</th>
+              <th>Last Cycle Time</th>
+              <th>Cycle Counter</th>
+              <th>Action</th>
+            </tr></thead>
+            <tbody id="diskTable">
+          
+            </tbody>
+        </table>
+        <div class="ui divider"></div>
+        <button class="ui tiny right floated green button" onclick="initBackupDiskList();"><i class="refresh icon"></i> Refresh List</button>
+        <button class="ui tiny basic button" onclick="toggleModeInfo();"><i class="question icon"></i> Know more about backup modes</button>
+        <div class="ui message" id="modeInfo" style="display:none;">
+            <div class="header">
+                <i class="refresh icon"></i> Backup & Restore Modes
+            </div>
+            <p>There are 3 backup Modes under the ArozOS Hybrid Backup Manager. </p>
+            <ul class="list">
+                <li>Basic (Recommended)
+                    <ul class="list">
+                        <li>Backup newly created files within 5 minutes</li>
+                        <li>Deleted files will be kept 24 hours before it is deleted from backup storage</li>
+                        <li><i class="green checkmark icon"></i> Changes are backed up in near real time</li>
+                        <li><i class="green checkmark icon"></i> Suitable for recovering accidentally deleted files from other access methods (e.g. Samba delete)</li>
+                        <li><i class="red remove icon"></i> Only for disk with small number of files</li>
+                        <li><i class="red remove icon"></i> Frequent READ operations on disk (Recommended for SSD / SD cards only)</li>
+                    </ul>
+                </li>
+                <li>Nightly
+                    <ul class="list">
+                        <li>Backup newly created files every 24 hours</li>
+                        <li>Deleted files will be kept 24 - 48 hours before it is deleted from backup storage</li>
+                        <li><i class="green checkmark icon"></i> Allowing user to restore selected file vs (snapshot) restore the whole virtual root folder</li>
+                        <li><i class="green checkmark icon"></i> Suitable for recovering accidentally deleted files from other access methods (e.g. Samba delete)</li>
+                        <li><i class="red remove icon"></i> Changes might take 24 hours to back up</li>
+                    </ul>
+                </li>
+                <li>Version (Experimental)
+                    <ul class="list">
+                        <li>Snapshot based backup method</li>
+                        <li>All file changes are kept on disk</li>
+                        <li><i class="green checkmark icon"></i> Allowing user to restore to a specific snapshot</li>
+                        <li><i class="green checkmark icon"></i> Newly created files will be kept, files exists in snapshot will be restored / overwritten</li>
+                        <li><i class="red remove icon"></i> Require a lot of storage space</li>
+                        <li><i class="red remove icon"></i> Computational and IO access heavy, might not suitable for ARM SBCs.</li>
+                    </ul>
+                </li>
+            </ul>
+        </div>
+    </div>
+    <script>
+        
+        function toggleModeInfo(){
+            $("#modeInfo").slideToggle('fast');
+        }
+
+        initBackupDiskList();
+       function initBackupDiskList(){
+            $("#diskTable").html(``);
+            $.get(`../../system/backup/listAll`, function(data){
+                if (data.error !== undefined){
+                    alert(data.error);
+                }else{
+                    //Render the list
+                    data.forEach(disk => {
+                        $("#diskTable").append(`<tr>
+                            <td data-label=""><img class="ui avatar image" style="border-radius: 0px;" src="../../img/system/drive-backup.svg"> ${disk.DiskName} (${disk.DiskUID}:/)</td>
+                            <td data-label=""><img class="ui avatar image" style="border-radius: 0px;" src="../../img/system/drive-virtual.svg"> ${disk.ParentName} (${disk.ParentUID}:/)</td>
+                            <td data-label="">${disk.BackupMode}</td>
+                            <td data-label="">${ao_module_utils.timeConverter(disk.LastBackupCycleTime)}</td>
+                            <td data-label="">${disk.BackupCycleCount}</td>
+                            <td data-label=""><button class="ui teal tiny button" onclick="openRestore('${disk.ParentUID}');">Restore Settings</button></td>
+                        </tr> `);
+                    });
+                    if (data.length == 0){
+                        $('#diskTable').append(`<tr>
+                            <td data-label=""col="5"><i class="red remove icon"></i> No backup disk found on this system</td>
+                        </tr> `);
+                    }
+                }
+            });
+       }
+
+       function openRestore(diskUUID){
+            var rootname = [diskUUID + ":/"];
+            console.log(rootname);
+            var hashPassthrough = encodeURIComponent(JSON.stringify(rootname));
+            ao_module_newfw({
+                url: "SystemAO/disk/disk_restore.html#" + hashPassthrough,
+                width: 410,
+                height: 580,
+                appicon: "img/system/backup.svg",
+                title: "Backup & Restore",
+            });
+       }
+    </script>
+</body>
+</html>

+ 2 - 2
web/SystemAO/disk/disk_restore.html

@@ -174,7 +174,6 @@
             vrootID = JSON.parse(decodeURIComponent(window.location.hash.substr(1)));
 
             if (vrootID.length >= 1){
-                //At least 1 vroot is passed in
                 listRestorableFiles(vrootID[0]);
             }else{
                 $("#error").removeClass("hidden");
@@ -334,7 +333,8 @@
                 data: {vroot: rootID},
                 success: function(data){
                     if (data.error !== undefined){
-                        $("#error").find(".reason").texxt(data.error);
+                        $("#error").find(".reason").text(data.error);
+                        $("#error").removeClass("hidden");
                     }else{
 
                         //Filter out the restorable snapshot and files

+ 15 - 1
web/script/ao_module.js

@@ -700,7 +700,8 @@ ao_module_utils.getIconFromExt(ext); //Get icon tag from file extension
 
 ao_module_utils.getDropFileInfo(dropEvent); //Get the filepath and filename list from file explorer drag drop
 ao_module_utils.formatBytes(byte, decimals); //Format file byte size to human readable size
- **/
+ao_module_utils.timeConverter(unix_timestamp); //Get human readable timestamp 
+**/
 class ao_module_utils{
     
     //Two simple functions for converting any Javascript object into string that can be put into the attr value of an DOM object
@@ -822,6 +823,19 @@ class ao_module_utils{
         };
 
     }
+
+    static timeConverter(UNIX_timestamp){
+        var a = new Date(UNIX_timestamp * 1000);
+        var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+        var year = a.getFullYear();
+        var month = months[a.getMonth()];
+        var date = a.getDate();
+        var hour = a.getHours().toString().padStart(2, "0");
+        var min = a.getMinutes().toString().padStart(2, "0");
+        var sec = a.getSeconds().toString().padStart(2, "0");
+        var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
+        return time;
+    }
     
     static formatBytes(a,b=2){if(0===a)return"0 Bytes";const c=0>b?0:b,d=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,d)).toFixed(c))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]}
 }