Sfoglia il codice sorgente

Completed snapshot summary UI for showing what has changed in one snapshot

TC pushbot 5 4 anni fa
parent
commit
5ca5e8334e

+ 55 - 0
backup.go

@@ -23,6 +23,61 @@ func backup_init() {
 	//Register API endpoints
 	router.HandleFunc("/system/backup/listRestorable", backup_listRestorable)
 	router.HandleFunc("/system/backup/restoreFile", backup_restoreSelected)
+	router.HandleFunc("/system/backup/snapshotSummary", backup_renderSnapshotSummary)
+}
+
+//Generate a snapshot summary for vroot
+func backup_renderSnapshotSummary(w http.ResponseWriter, r *http.Request) {
+	//Get user accessiable storage pools
+	userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		sendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	//Get Backup disk ID from request
+	bdid, err := mv(r, "bdid", true)
+	if err != nil {
+		sendErrorResponse(w, "Invalid backup disk ID given")
+		return
+	}
+
+	//Get target snapshot name from request
+	snapshot, err := mv(r, "snapshot", true)
+	if err != nil {
+		sendErrorResponse(w, "Invalid snapshot name given")
+		return
+	}
+
+	//Get fsh from the id
+	fsh, err := GetFsHandlerByUUID(bdid)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Get task by the backup disk id
+	task, err := userinfo.HomeDirectories.HyperBackupManager.GetTaskByBackupDiskID(fsh.UUID)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
+	if task.Mode == "version" {
+		//Generate snapshot summary
+		summary, err := task.GenerateSnapshotSummary(snapshot)
+		if err != nil {
+			sendErrorResponse(w, err.Error())
+			return
+		}
+
+		js, _ := json.Marshal(summary)
+		sendJSONResponse(w, string(js))
+	} else {
+		sendErrorResponse(w, "Unable to genreate snapshot summary: Backup mode is not snapshot")
+		return
+	}
+
 }
 
 //Restore a given file

BIN
mod/disk/hybridBackup/doc.txt


+ 15 - 0
mod/disk/hybridBackup/hybridBackup.go

@@ -53,6 +53,13 @@ type BackupTask struct {
 	Mode              string           //Backup mode
 }
 
+//A snapshot summary
+type SnapshotSummary struct {
+	ChangedFiles   map[string]string
+	UnchangedFiles map[string]string
+	DeletedFiles   map[string]string
+}
+
 //A file in the backup drive that is restorable
 type RestorableFile struct {
 	Filename      string //Filename of this restorable object
@@ -361,3 +368,11 @@ func getFileHash(filename string) (string, error) {
 
 	return hex.EncodeToString(h.Sum(nil)), nil
 }
+
+func (m *Manager) GetTaskByBackupDiskID(backupDiskID string) (*BackupTask, error) {
+	targetTask := m.getTaskByBackupDiskID(backupDiskID)
+	if targetTask == nil {
+		return nil, errors.New("Task not found")
+	}
+	return targetTask, nil
+}

+ 50 - 0
mod/disk/hybridBackup/versionBackup.go

@@ -380,3 +380,53 @@ func listVersionRestorables(task *BackupTask) ([]*RestorableFile, error) {
 	return restorableFiles, nil
 
 }
+
+//This function generate and return a snapshot summary
+func (task *BackupTask) GenerateSnapshotSummary(snapshotName string) (*SnapshotSummary, error) {
+	//Check if the task is version
+	if task.Mode != "version" {
+		return nil, errors.New("Invalid backup mode. This function only support snapshot mode backup task.")
+	}
+
+	//Check if the snapshot folder exists
+	targetSnapshotFolder := filepath.Join(task.DiskPath, "/version/", snapshotName)
+	if !fileExists(targetSnapshotFolder) {
+		return nil, errors.New("Snapshot not exists")
+	}
+
+	if !fileExists(filepath.Join(targetSnapshotFolder, "snapshot.datalink")) {
+		return nil, errors.New("Snapshot datalink file not exists")
+	}
+
+	summary := SnapshotSummary{
+		ChangedFiles:   map[string]string{},
+		UnchangedFiles: map[string]string{},
+		DeletedFiles:   map[string]string{},
+	}
+
+	fastWalk(targetSnapshotFolder, func(filename string) error {
+		if filepath.Base(filename) == "snapshot.datalink" {
+			//Exceptional
+			return nil
+		}
+		relPath, err := filepath.Rel(targetSnapshotFolder, filename)
+		if err != nil {
+			return err
+		}
+
+		summary.ChangedFiles["/"+filepath.ToSlash(relPath)] = snapshotName
+		return nil
+	})
+
+	//Generate the summary
+	linkFileMap, err := readLinkFile(targetSnapshotFolder)
+	if err != nil {
+		return nil, err
+	}
+
+	//Move the file map into result
+	summary.UnchangedFiles = linkFileMap.UnchangedFile
+	summary.DeletedFiles = linkFileMap.DeletedFiles
+
+	return &summary, nil
+}

+ 21 - 0
web/SystemAO/disk/disk_restore.html

@@ -122,6 +122,7 @@
         <span class="restorePoint">N/A</span><br>
         <span class="deleteTime">N/A</span><br>
         <span class="deleteRemainingTime">N/A</span><br>
+        <button id="snapshotSummaryBtn" class="ui mini button" style="display:none;" onclick="openSnapshotInfo();">View Snapshot Summary</button>
         <button class="circular ui icon basic small button closebtn" onclick='$("#fileinfo").slideUp("fast");'>
             <i class="icon remove"></i>
         </button>
@@ -156,6 +157,7 @@
     <br><br>
     <script>
         var vrootID = "";
+        var viewingFileInfo = {};
 
         $(".ui.checkbox").checkbox();
         //Extract vroot id from the window hash
@@ -173,6 +175,21 @@
             $("#error").removeClass("hidden");
         }
 
+        function openSnapshotInfo(){
+            var snapshotInfo = {
+                SnapshotDisk: viewingFileInfo.BackupDiskUID,
+                SnapshotName: viewingFileInfo.Filename
+            };
+            var hashPassthrough = encodeURIComponent(JSON.stringify(snapshotInfo));
+            ao_module_newfw({
+                url: "SystemAO/disk/disk_snapshot.html#" + hashPassthrough,
+                width: 900,
+                height: 520,
+                appicon: "img/system/backup.svg",
+                title: "Snapshot Summary",
+            });
+        }
+
         function showFileInfo(object){
             var filedata = $(object).attr("filedata");
             filedata = JSON.parse(decodeURIComponent(filedata));
@@ -184,6 +201,7 @@
                 $("#fileinfo").find(".filename").text(filedata.Filename + ` (${ao_module_utils.formatBytes(filedata.Filesize, 2)})`);
             }
             
+            viewingFileInfo=filedata;
             
             //Hide the filename in relpath
             var shortenRelpath = filedata.RelpathOnDisk.split("/");
@@ -198,9 +216,12 @@
             if (filedata.IsSnapshot){
                 $("#fileinfo").find(".deleteRemainingTime").hide();
                 $("#fileinfo").find(".deleteTime").hide();
+                $("#snapshotSummaryBtn").show();
+
             }else{
                 $("#fileinfo").find(".deleteRemainingTime").show();
                 $("#fileinfo").find(".deleteTime").show();
+                $("#snapshotSummaryBtn").hide();
             }
 
             $("#fileinfo").slideDown('fast');

+ 118 - 0
web/SystemAO/disk/disk_snapshot.html

@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Snapshot Summary</title>
+	<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>
+    <script type="text/javascript" src="../../script/ao_module.js"></script>
+    <style>
+        .unchaged{
+            display:none;
+        }
+    </style>
+</head>
+<body>
+    <br>
+    <div class="ui container">
+        <h3 class="ui header">
+            Snapshot <span id="snapshotName"></span>
+            <div class="sub header">Snapshot Summary</div>
+        </h3>
+        <div class="ui divider"></div>
+            <div id="content">
+                <p><i class="question icon"></i> Snapshot summary is empty.</p>
+            </div>
+            <div id="summary" style="display:none;">
+                <div class="ui checkbox">
+                    <input type="checkbox" onchange="toggleUnchageFileVisibility(this.checked);">
+                    <label>Show Unchanged Files</label>
+                </div>
+                <table class="ui striped celled table">
+                    <thead>
+                        <tr>
+                        <th>#</th>
+                        <th>Relative Path</th>
+                        <th>Link To Snapshot</th>
+                        </tr>
+                    </thead>
+                    <tbody id="filelist">
+
+                    </tbody>
+                </table>
+            </div>
+        <br>
+           <button class="ui right floated button" onclick="ao_module_close();">Close</button>
+        <br>
+    </div>
+        
+    <br><br>
+    <script>
+        
+        //Get window hash data and start the request
+        if (window.location.hash.length > 1){
+            var snapshotInfo = window.location.hash.substr(1);
+            snapshotInfo = JSON.parse(decodeURIComponent(snapshotInfo));
+            $("#snapshotName").text(snapshotInfo.SnapshotName);
+            getSnapshotSummary(snapshotInfo.SnapshotDisk, snapshotInfo.SnapshotName);
+        }else{
+            //Invalid usage
+        }
+
+        function getSnapshotSummary(diskID, name){
+            $.ajax({
+                url: "../../system/backup/snapshotSummary",
+                data: {bdid: diskID, snapshot: name},
+                success: function(data){
+                    if (data.error !== undefined){
+                        $("#content").html(` <div class="ui red inverted segment">  
+                                <h5><i class="remove icon"></i> ${data.error}</h5>
+                            </div>`);
+                    }else{
+                        console.log(data);
+                        $("#summary").show();
+                        $("#content").hide();
+                        $("#filelist").html("");
+                        for (let relpath in data.ChangedFiles){
+                            $("#filelist").append(`<tr class="positive changed">
+                                <td><i class="add icon"></i></td>
+                                <td>${relpath}</td>
+                                <td>${data.ChangedFiles[relpath]}</td>
+                            </tr>`);
+                        }
+
+                        for (let relpath in data.DeletedFiles){
+                            $("#filelist").append(`<tr class="negative">
+                                <td><i class="minus icon"></i></td>
+                                <td>${relpath}</td>
+                                <td>${data.DeletedFiles[relpath]}</td>
+                            </tr>`);
+                        }
+
+                        for (let relpath in data.UnchangedFiles){
+                            $("#filelist").append(`<tr class="unchaged">
+                                <td></td>
+                                <td>${relpath}</td>
+                                <td>${data.UnchangedFiles[relpath]}</td>
+                            </tr>`);
+                        }
+                    }   
+                }
+            });
+        }
+
+        $(".ui.checkbox").checkbox();
+        function toggleUnchageFileVisibility(showUnchanged){
+            if (showUnchanged){
+                $(".unchaged").show();
+            }else{
+                $(".unchaged").hide();
+            }
+            
+        }
+    </script>
+</body>
+</html>