Browse Source

Patched bug in SFTP path translator

Toby Chui 2 years ago
parent
commit
6952984456

+ 18 - 24
mod/filesystem/abstractions/sftpfs/sftpfs.go

@@ -98,33 +98,27 @@ func NewSFTPFileSystemAbstraction(uuid string, hierarchy string, serverUrl strin
 	}, nil
 }
 func (s SFTPFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Chmod(filename, mode)
 }
 func (s SFTPFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Chown(filename, uid, gid)
 }
 func (s SFTPFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Chtimes(filename, atime, mtime)
 }
 func (s SFTPFileSystemAbstraction) Create(filename string) (arozfs.File, error) {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	//TODO: ADD FILE TYPE CONVERSION
 	return nil, arozfs.ErrNullOperation
 }
 func (s SFTPFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Mkdir(filename)
 }
 func (s SFTPFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.MkdirAll(filename)
 }
@@ -132,10 +126,9 @@ func (s SFTPFileSystemAbstraction) Name() string {
 	return ""
 }
 func (s SFTPFileSystemAbstraction) Open(filename string) (arozfs.File, error) {
-	joinedFilename := filepath.Join(s.mountFolder, filename)
-	joinedFilename = arozfs.GenericPathFilter(joinedFilename)
+	filename = arozfs.GenericPathFilter(filename)
 
-	f, err := s.client.Open(joinedFilename)
+	f, err := s.client.Open(filename)
 	if err != nil {
 		return nil, err
 	}
@@ -159,10 +152,9 @@ func (s SFTPFileSystemAbstraction) Open(filename string) (arozfs.File, error) {
 	return wf, nil
 }
 func (s SFTPFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
-	joinedFilename := filepath.Join(s.mountFolder, filename)
-	joinedFilename = arozfs.GenericPathFilter(joinedFilename)
+	filename = arozfs.GenericPathFilter(filename)
 
-	f, err := s.client.OpenFile(joinedFilename, flag)
+	f, err := s.client.OpenFile(filename, flag)
 	if err != nil {
 		return nil, err
 	}
@@ -186,12 +178,10 @@ func (s SFTPFileSystemAbstraction) OpenFile(filename string, flag int, perm os.F
 	return wf, nil
 }
 func (s SFTPFileSystemAbstraction) Remove(filename string) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Remove(filename)
 }
 func (s SFTPFileSystemAbstraction) RemoveAll(filename string) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	if s.IsDir(filename) {
 		return s.client.RemoveDirectory(filename)
@@ -200,14 +190,11 @@ func (s SFTPFileSystemAbstraction) RemoveAll(filename string) error {
 
 }
 func (s SFTPFileSystemAbstraction) Rename(oldname, newname string) error {
-	oldname = filepath.Join(s.mountFolder, oldname)
 	oldname = arozfs.GenericPathFilter(oldname)
-	newname = filepath.Join(s.mountFolder, newname)
 	newname = arozfs.GenericPathFilter(newname)
 	return s.client.Rename(oldname, newname)
 }
 func (s SFTPFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	return s.client.Stat(filename)
 }
@@ -229,15 +216,28 @@ func (s SFTPFileSystemAbstraction) Close() error {
 */
 
 func (s SFTPFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
-	return arozfs.GenericVirtualPathToRealPathTranslator(s.uuid, s.hierarchy, subpath, username)
+	rpath, err := arozfs.GenericVirtualPathToRealPathTranslator(s.uuid, s.hierarchy, subpath, username)
+	if err != nil {
+		return "", err
+	}
+	if !(len(rpath) >= len(s.mountFolder) && rpath[:len(s.mountFolder)] == s.mountFolder) {
+		//Prepend the mount folder (aka root folder) to the translated output from generic path translator
+		rpath = arozfs.ToSlash(filepath.Join(s.mountFolder, rpath))
+	}
+	return rpath, nil
 }
 
 func (s SFTPFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
+	if len(fullpath) >= len(s.mountFolder) && fullpath[:len(s.mountFolder)] == s.mountFolder {
+		//Trim out the mount folder path from the full path before passing into the generic path translator
+		fullpath = fullpath[len(s.mountFolder):]
+	}
 	return arozfs.GenericRealPathToVirtualPathTranslator(s.uuid, s.hierarchy, fullpath, username)
 }
 
 func (s SFTPFileSystemAbstraction) FileExists(realpath string) bool {
 	_, err := s.Stat(realpath)
+	fmt.Println(realpath, err)
 	return err == nil
 }
 
@@ -251,7 +251,6 @@ func (s SFTPFileSystemAbstraction) IsDir(realpath string) bool {
 }
 
 func (s SFTPFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
-	realpathWildcard = filepath.Join(s.mountFolder, realpathWildcard)
 	realpathWildcard = arozfs.GenericPathFilter(realpathWildcard)
 	return s.client.Glob(realpathWildcard)
 }
@@ -285,7 +284,6 @@ func (s SFTPFileSystemAbstraction) WriteFile(filename string, content []byte, mo
 	return err
 }
 func (s SFTPFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	f, err := s.client.OpenFile(filename, os.O_RDONLY)
 	if err != nil {
@@ -295,7 +293,6 @@ func (s SFTPFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
 	return ioutil.ReadAll(f)
 }
 func (s SFTPFileSystemAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	result := []fs.DirEntry{}
 	infos, err := s.client.ReadDir(filename)
@@ -311,7 +308,6 @@ func (s SFTPFileSystemAbstraction) ReadDir(filename string) ([]fs.DirEntry, erro
 	return result, nil
 }
 func (s SFTPFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	f, err := s.client.OpenFile(filename, os.O_CREATE|os.O_WRONLY)
 	if err != nil {
@@ -321,7 +317,6 @@ func (s SFTPFileSystemAbstraction) WriteStream(filename string, stream io.Reader
 	return err
 }
 func (s SFTPFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
-	filename = filepath.Join(s.mountFolder, filename)
 	filename = arozfs.GenericPathFilter(filename)
 	f, err := s.client.OpenFile(filename, os.O_RDONLY)
 	if err != nil {
@@ -331,7 +326,6 @@ func (s SFTPFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, e
 }
 
 func (s SFTPFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
-	root = filepath.Join(s.mountFolder, root)
 	root = arozfs.GenericPathFilter(root)
 	walker := s.client.Walk(root)
 	for walker.Step() {

+ 2 - 2
mod/filesystem/arozfs/arozfs.go

@@ -78,7 +78,7 @@ func NewRedirectionError(targetVpath string) error {
 
 //Check if a file system is network drive
 func IsNetworkDrive(fstype string) bool {
-	if fstype == "webdav" || fstype == "ftp" || fstype == "smb" || fstype == "nfs" {
+	if fstype == "webdav" || fstype == "ftp" || fstype == "smb" || fstype == "sftp" {
 		return true
 	}
 
@@ -87,7 +87,7 @@ func IsNetworkDrive(fstype string) bool {
 
 //Get a list of supported file system types for mounting via arozos
 func GetSupportedFileSystemTypes() []string {
-	return []string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs", "webdav", "ftp", "smb", "nfs"}
+	return []string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs", "webdav", "ftp", "smb", "sftp"}
 }
 
 /*

+ 326 - 318
web/SystemAO/storage/fshedit.html

@@ -1,319 +1,327 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
-    <script type="text/javascript" src="../../script/jquery.min.js"></script>
-    <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
-    <script type="text/javascript" src="../../script/ao_module.js"></script>
-    <title>Storage Pool Editor</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <style>
-        body{
-            background-color:white;
-        }
-        .themebackground{
-            background-color:#242424 !important;
-            color:white !important;
-            background-image: url("img/slate.png") !important;
-            background-repeat: no-repeat !important;
-            background-attachment: fixed !important;
-            height:100px;
-            border: 0px solid transparent !important;
-            padding:24px !important;
-        }
-        .fshList{
-            max-height: 300px;
-            overflow-y: auto;
-        }
-
-        .controls{
-            position: absolute;
-            top: 12px;
-            right: 12px;
-        }
-
-        a{
-            cursor: pointer;
-        }
-        .false{
-            color: #eb0909;
-        }
-
-        .true{
-            color: #05b074;
-        }
-
-        .backuponly{
-            display:none;
-        }
-
-        .backgroundIcon{
-            position: fixed;
-            bottom: 0px;
-            right: 0px;
-            opacity: 0.1;
-            margin-right: -3em;
-            margin-bottom: -5em;
-            z-index: -99;
-            pointer-events: none;
-            user-select: none;
-        }
-
-    </style>
-</head>
-<body>
-    <div class="backgroundIcon">
-        <img class="ui medium image" src="../../img/system/drive-virtual.svg">
-    </div>
-    <!-- 
-    <div class="ui fluid attached segment themebackground" >
-        <h4 class="ui inverted header">
-            <i class="folder icon"></i>
-            <div class="content">
-                <span id="pagetitle">Edit File System Handler</span>
-            <div class="sub header" id="pageSubTitle">Edit the selected File System Handler (FSH)</div>
-            </div>
-        </h4>
-    </div>
-    -->
-    <br>
-    <div class="ui container">
-        <h3 class="ui header">
-            <img src="../../img/system/drive-virtual.svg">
-            <div class="content">
-                <span>Bridge File System Handler </span><span id="poolid"></span>
-                <div class="sub header">Share Storage across Storage Pools</div>
-            </div>
-        </h3>
-        <form id="mainForm" class="ui form" onsubmit="handleFormSubmit(event);">
-            <div class="field" style="display: none;">
-                <label>Group</label>
-                <input type="text" name="group" id="groupfield" readonly="true">
-                </div>
-            <div class="field">
-                <label>Name</label>
-                <input type="text" name="name" placeholder="e.g. My Drive">
-            </div>
-            <div class="field">
-                <label>UUID</label>
-                <input type="text" name="uuid" placeholder="e.g. mydrive">
-            </div>
-            <div class="field">
-                <label>Path</label>
-                <input type="text" name="path" placeholder="e.g. /media/mydrive">
-            </div>
-            <div class="field">
-                <label>Access Permission</label>
-                <div id="accessfield" class="ui selection dropdown">
-                <input type="hidden" name="access" value="readwrite">
-                <i class="dropdown icon"></i>
-                <div class="default text">Access Permission</div>
-                <div class="menu">
-                    <div class="item" data-value="readonly">Read Only</div>
-                    <div class="item" data-value="readwrite">Read Write</div>
-                </div>
-                </div>
-            </div>
-            <div class="field">
-                <label>Storage Hierarchy</label>
-                <div id="hierarchyfield" class="ui selection dropdown" onchange="handleHierarchyChange(this);">
-                <input type="hidden" name="hierarchy" value="public">
-                <i class="dropdown icon"></i>
-                <div class="default text">Storage Hierarchy</div>
-                <div class="menu">
-                    <div class="item" data-value="user">Isolated User Folders</div>
-                    <div class="item" data-value="public">Public Access Folders</div>
-                </div>
-                </div>
-            </div>
-            <div class="ui divider"></div>
-            <p>Physical Disks Settings</p>
-            <div class="field">
-                <label>Filesystem Type</label>
-                <div id="fstype" class="ui selection dropdown">
-                <input type="hidden" name="filesystem" value="ntfs" onchange="handleFileSystemTypeChange(this.value);">
-                <i class="dropdown icon"></i>
-                <div class="default text">Filesystem Type</div>
-                <div class="menu">
-                    <div class="item" data-value="ext4">EXT4</div>
-                    <!-- <div class="item" data-value="ext3">EXT3</div> -->
-                    <div class="item" data-value="ntfs">NTFS</div>
-                    <div class="item" data-value="vfat">VFAT</div>
-                    <div class="item" data-value="fat">FAT</div>
-                    <div class="item" data-value="webdav">WebDAV</div>
-                    <div class="item" data-value="smb">SMB</div>
-                    <div class="item" data-value="ftp">FTP</div>
-                </div>
-                </div>
-            </div>
-            <div class="localfs">
-                <div class="field">
-                    <label>Mount Device</label>
-                    <input type="text" name="mountdev" placeholder="e.g. /dev/sda1">
-                </div>
-                <div class="field">
-                    <label>Mount Point</label>
-                    <input type="text" name="mountpt" placeholder="e.g. /media/myfolder">
-                </div>
-                <div class="field">
-                    <div class="ui checkbox">
-                    <input type="checkbox" id="automount" tabindex="0" class="hidden">
-                    <label>Automount</label>
-                    </div>
-                </div>
-                <br>
-            </div>
-            <div class="networkfs" style="display:none;">
-                <div class="ui divider"></div>
-                <p>Security and Authentication</p>
-                <div class="field">
-                    <label>Username</label>
-                    <input type="text" name="username" placeholder="">
-                </div>
-                <div class="field">
-                    <label>Password</label>
-                    <input type="password" name="password" placeholder="">
-                </div>
-                <small>Leave Username / Password field empty for using the old config</small>
-                <br><br>
-            </div>
-            <button class="ui right floated button" onclick='handleCancel();'>Cancel</button>
-            <button class="ui green right floated button" type="submit">Confirm</button>
-            <br><br><br><br>
-        </form>
-    </div>
-    <script>
-        //Get target fsh uuid and group from hash
-        var targetFSH = "";
-        var opr = "set";
-        $(".ui.dropdown").dropdown();
-        $(".ui.checkbox").checkbox();
-
-        if (window.location.hash.length > 0){
-            //Get a list of vroot from system
-            $("#backupIdList").html(``);
-            $.get("../../system/storage/pool/list", function(data){
-                data.forEach(usergroup => {
-                    if (usergroup.Storages != null){
-                        usergroup.Storages.forEach(storage => {
-                            $("#backupIdList").append(`<div class="item" data-value="${storage.UUID}">${storage.Name} (${storage.UUID}:/)</div>`);
-                        });
-                    }
-                });
-                $("#backupIdList").parent().dropdown();
-                renderFSHCurrentSettings();
-            });  
-        }
-
-        function handleFormSubmit(e){
-            e.preventDefault();
-            //Get the form value
-            let payload = new FormData(e.target);
-            let fshObject = {};
-            [...payload.entries()].forEach(function(field){
-                fshObject[field[0]] = field[1];
-            });
-
-            //Inject other payloads
-            fshObject.automount = $("#automount")[0].checked;
-            $.ajax({
-                url: "../../system/storage/pool/edit",
-                method: "POST",
-                data: {
-                    opr: opr,
-                    group: $("#groupfield").val(),
-                    config: JSON.stringify(fshObject),
-                },
-                success: function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        //Done
-                        window.location.href = "updateComplete.html";
-                    }
-                }
-
-            });
-        }
-
-        function renderFSHCurrentSettings(){
-            //Request server side to provide info on this FSH
-            var input = JSON.parse(decodeURIComponent(window.location.hash.substr(1)));
-            $("#groupfield").val(input.group);
-
-            if (input.uuid == undefined){
-                 //New fsh
-                $("#pagetitle").text("New File System Handler");
-                $("#pageSubTitle").text("Mount a new file system into this host as storage");
-                opr = "new";
-            }else{
-                $.ajax({
-                    url: "../../system/storage/pool/edit",
-                    method: "GET",
-                    data: {opr: "get", uuid: input.uuid, group: input.group},
-                    success: function(data){
-                        renderOptionsToForm(data);
-                    }
-                });
-
-                $("#mainForm").attr("action", "../../system/storage/pool/edit?opr=set&uuid=" + input.uuid + "&group=" + input.group);
-            }
-        }
-
-        function handleHierarchyChange(object){
-            var newHierarcy = $(object).find("input").val();
-            //Force access mode to readonly if backup mode
-            if (newHierarcy == "backup"){
-                $("#accessfield").dropdown("set selected", "readonly")
-                $("#accessfield").addClass("disabled");
-            }else{
-                $("#accessfield").removeClass("disabled");
-            }
-        }
-
-        function handleFileSystemTypeChange(fstype){
-            if (fstype == "webdav" || fstype == "ftp" || fstype == "smb"){
-                $(".localfs").hide();
-                $(".networkfs").show();
-            }else{
-                $(".localfs").show();
-                $(".networkfs").hide();
-            }
-        }
-
-        function renderOptionsToForm(option){
-            console.log(option);
-            $("input[name=name]").val(option.name);
-            $("input[name=uuid]").val(option.uuid);
-            $("input[name=path]").val(option.path);
-            $("#accessfield").dropdown("set selected", option.access);
-            $("#hierarchyfield").dropdown("set selected", option.hierarchy);
-            if (isNetworkFs(option.filesystem)){
-                $(".localfs").hide();
-                $(".networkfs").show();
-            }
-            $("#fstype").dropdown("set selected",option.filesystem);
-            handleFileSystemTypeChange(option.filesystem);
-            $("input[name=mountdev]").val(option.mountdev);
-            $("input[name=mountpt]").val(option.mountpt);
-            if (option.automount == true){
-                //$("input[name=automount]")[0].checked = true;
-                $("#automount").parent().checkbox("set checked");
-            }
-        }
-
-        function isNetworkFs(name){
-            if (name == "webdav" || name == "ftp"){
-                return true;
-            }
-            return false;
-        }
-
-        function handleCancel(){
-            ao_module_parentCallback(true);
-            ao_module_close();
-        }
-    </script>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <script type="text/javascript" src="../../script/jquery.min.js"></script>
+    <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
+    <script type="text/javascript" src="../../script/ao_module.js"></script>
+    <title>Storage Pool Editor</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <style>
+        body{
+            background-color:white;
+        }
+        .themebackground{
+            background-color:#242424 !important;
+            color:white !important;
+            background-image: url("img/slate.png") !important;
+            background-repeat: no-repeat !important;
+            background-attachment: fixed !important;
+            height:100px;
+            border: 0px solid transparent !important;
+            padding:24px !important;
+        }
+        .fshList{
+            max-height: 300px;
+            overflow-y: auto;
+        }
+
+        .controls{
+            position: absolute;
+            top: 12px;
+            right: 12px;
+        }
+
+        a{
+            cursor: pointer;
+        }
+        .false{
+            color: #eb0909;
+        }
+
+        .true{
+            color: #05b074;
+        }
+
+        .backuponly{
+            display:none;
+        }
+
+        .backgroundIcon{
+            position: fixed;
+            bottom: 0px;
+            right: 0px;
+            opacity: 0.1;
+            margin-right: -3em;
+            margin-bottom: -5em;
+            z-index: -99;
+            pointer-events: none;
+            user-select: none;
+        }
+
+    </style>
+</head>
+<body>
+    <div class="backgroundIcon">
+        <img class="ui medium image" src="../../img/system/drive-virtual.svg">
+    </div>
+    <!-- 
+    <div class="ui fluid attached segment themebackground" >
+        <h4 class="ui inverted header">
+            <i class="folder icon"></i>
+            <div class="content">
+                <span id="pagetitle">Edit File System Handler</span>
+            <div class="sub header" id="pageSubTitle">Edit the selected File System Handler (FSH)</div>
+            </div>
+        </h4>
+    </div>
+    -->
+    <br>
+    <div class="ui container">
+        <h3 class="ui header">
+            <img src="../../img/system/drive-virtual.svg">
+            <div class="content">
+                <span>Bridge File System Handler </span><span id="poolid"></span>
+                <div class="sub header">Share Storage across Storage Pools</div>
+            </div>
+        </h3>
+        <form id="mainForm" class="ui form" onsubmit="handleFormSubmit(event);">
+            <div class="field" style="display: none;">
+                <label>Group</label>
+                <input type="text" name="group" id="groupfield" readonly="true">
+                </div>
+            <div class="field">
+                <label>Name</label>
+                <input type="text" name="name" placeholder="e.g. My Drive">
+            </div>
+            <div class="field">
+                <label>UUID</label>
+                <input type="text" name="uuid" placeholder="e.g. mydrive">
+            </div>
+            <div class="field">
+                <label>Path</label>
+                <input type="text" name="path" placeholder="e.g. /media/mydrive">
+            </div>
+            <div class="field">
+                <label>Access Permission</label>
+                <div id="accessfield" class="ui selection dropdown">
+                <input type="hidden" name="access" value="readwrite">
+                <i class="dropdown icon"></i>
+                <div class="default text">Access Permission</div>
+                <div class="menu">
+                    <div class="item" data-value="readonly">Read Only</div>
+                    <div class="item" data-value="readwrite">Read Write</div>
+                </div>
+                </div>
+            </div>
+            <div class="field">
+                <label>Storage Hierarchy</label>
+                <div id="hierarchyfield" class="ui selection dropdown" onchange="handleHierarchyChange(this);">
+                <input type="hidden" name="hierarchy" value="public">
+                <i class="dropdown icon"></i>
+                <div class="default text">Storage Hierarchy</div>
+                <div class="menu">
+                    <div class="item" data-value="user">Isolated User Folders</div>
+                    <div class="item" data-value="public">Public Access Folders</div>
+                </div>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <p>Physical Disks Settings</p>
+            <div class="field">
+                <label>Filesystem Type</label>
+                <div id="fstype" class="ui selection dropdown">
+                <input type="hidden" name="filesystem" value="ntfs" onchange="handleFileSystemTypeChange(this.value);">
+                <i class="dropdown icon"></i>
+                <div class="default text">Filesystem Type</div>
+                <div class="menu">
+                    <div class="item" data-value="ext4">EXT4</div>
+                    <!-- <div class="item" data-value="ext3">EXT3</div> -->
+                    <div class="item" data-value="ntfs">NTFS</div>
+                    <div class="item" data-value="vfat">VFAT</div>
+                    <div class="item" data-value="fat">FAT</div>
+                    <div class="item" data-value="webdav">WebDAV</div>
+                    <div class="item" data-value="smb">SMB</div>
+                    <div class="item" data-value="ftp">FTP</div>
+                    <div class="item" data-value="sftp">SFTP</div>
+                </div>
+                </div>
+            </div>
+            <div class="localfs">
+                <div class="field">
+                    <label>Mount Device</label>
+                    <input type="text" name="mountdev" placeholder="e.g. /dev/sda1">
+                </div>
+                <div class="field">
+                    <label>Mount Point</label>
+                    <input type="text" name="mountpt" placeholder="e.g. /media/myfolder">
+                </div>
+                <div class="field">
+                    <div class="ui checkbox">
+                    <input type="checkbox" id="automount" tabindex="0" class="hidden">
+                    <label>Automount</label>
+                    </div>
+                </div>
+                <br>
+            </div>
+            <div class="networkfs" style="display:none;">
+                <div class="ui divider"></div>
+                <p>Security and Authentication</p>
+                <div class="field">
+                    <label>Username</label>
+                    <input type="text" name="username" placeholder="">
+                </div>
+                <div class="field">
+                    <label>Password</label>
+                    <input type="password" name="password" placeholder="">
+                </div>
+                <small>Leave Username / Password field empty for using the old config</small>
+                <br><br>
+            </div>
+            <button class="ui right floated button" onclick='handleCancel();'>Cancel</button>
+            <button class="ui green right floated button" type="submit">Confirm</button>
+            <br><br><br><br>
+        </form>
+    </div>
+    <script>
+        //Get target fsh uuid and group from hash
+        var targetFSH = "";
+        var opr = "set";
+        $(".ui.dropdown").dropdown();
+        $(".ui.checkbox").checkbox();
+
+        $(document).ready(function(){
+            initEditor();
+        });
+
+        function initEditor(){
+            if (window.location.hash.length > 0){
+                //Get a list of vroot from system
+                $("#backupIdList").html(``);
+                $.get("../../system/storage/pool/list", function(data){
+                    data.forEach(usergroup => {
+                        if (usergroup.Storages != null){
+                            usergroup.Storages.forEach(storage => {
+                                $("#backupIdList").append(`<div class="item" data-value="${storage.UUID}">${storage.Name} (${storage.UUID}:/)</div>`);
+                            });
+                        }
+                    });
+                    $("#backupIdList").parent().dropdown();
+                    renderFSHCurrentSettings();
+                });  
+            }
+        }
+
+        function handleFormSubmit(e){
+            e.preventDefault();
+            //Get the form value
+            let payload = new FormData(e.target);
+            let fshObject = {};
+            [...payload.entries()].forEach(function(field){
+                fshObject[field[0]] = field[1];
+            });
+
+            //Inject other payloads
+            fshObject.automount = $("#automount")[0].checked;
+            $.ajax({
+                url: "../../system/storage/pool/edit",
+                method: "POST",
+                data: {
+                    opr: opr,
+                    group: $("#groupfield").val(),
+                    config: JSON.stringify(fshObject),
+                },
+                success: function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        //Done
+                        window.location.href = "updateComplete.html";
+                    }
+                }
+
+            });
+        }
+
+        function renderFSHCurrentSettings(){
+            //Request server side to provide info on this FSH
+            var input = JSON.parse(decodeURIComponent(window.location.hash.substr(1)));
+            $("#groupfield").val(input.group);
+
+            if (input.uuid == undefined){
+                 //New fsh
+                $("#pagetitle").text("New File System Handler");
+                $("#pageSubTitle").text("Mount a new file system into this host as storage");
+                opr = "new";
+            }else{
+                $.ajax({
+                    url: "../../system/storage/pool/edit",
+                    method: "GET",
+                    data: {opr: "get", uuid: input.uuid, group: input.group},
+                    success: function(data){
+                        renderOptionsToForm(data);
+                    }
+                });
+
+                $("#mainForm").attr("action", "../../system/storage/pool/edit?opr=set&uuid=" + input.uuid + "&group=" + input.group);
+            }
+        }
+
+        function handleHierarchyChange(object){
+            var newHierarcy = $(object).find("input").val();
+            //Force access mode to readonly if backup mode
+            if (newHierarcy == "backup"){
+                $("#accessfield").dropdown("set selected", "readonly")
+                $("#accessfield").addClass("disabled");
+            }else{
+                $("#accessfield").removeClass("disabled");
+            }
+        }
+
+        function handleFileSystemTypeChange(fstype){
+            if (isNetworkFs(fstype)){
+                $(".localfs").hide();
+                $(".networkfs").show();
+            }else{
+                $(".localfs").show();
+                $(".networkfs").hide();
+            }
+        }
+
+        function isNetworkFs(name){
+            name = name.trim();
+            if (name == "webdav" || name == "smb" || name == "ftp" || name == "sftp"){
+                return true;
+            }
+            return false;
+        }
+
+        function renderOptionsToForm(option){
+            console.log(option);
+            $("input[name=name]").val(option.name);
+            $("input[name=uuid]").val(option.uuid);
+            $("input[name=path]").val(option.path);
+            $("#accessfield").dropdown("set selected", option.access);
+            $("#hierarchyfield").dropdown("set selected", option.hierarchy);
+            if (isNetworkFs(option.filesystem)){
+                $(".localfs").hide();
+                $(".networkfs").show();
+            }
+            $("#fstype").dropdown("set selected",option.filesystem);
+            handleFileSystemTypeChange(option.filesystem);
+            $("input[name=mountdev]").val(option.mountdev);
+            $("input[name=mountpt]").val(option.mountpt);
+            if (option.automount == true){
+                //$("input[name=automount]")[0].checked = true;
+                $("#automount").parent().checkbox("set checked");
+            }
+        }
+
+        function handleCancel(){
+            ao_module_parentCallback(true);
+            ao_module_close();
+        }
+    </script>
+</body>
 </html>