瀏覽代碼

Added experimental raid test case

Toby Chui 1 年之前
父節點
當前提交
2163d5d283
共有 70 個文件被更改,包括 7273 次插入6989 次删除
  1. 27 13
      disk.go
  2. 48 47
      documents/router homepage/index.html
  3. 2 3
      main.flags.go
  4. 37 7
      mod/disk/raid/mdadm.go
  5. 10 6
      mod/disk/raid/raid.go
  6. 34 0
      mod/disk/raid/raid_test.go
  7. 18 0
      mod/disk/raid/raidutils.go
  8. 65 0
      mod/disk/raid/status.go
  9. 230 229
      system/auth/regsetting.html
  10. 52 51
      system/errors/alreadyLoggedin.html
  11. 51 50
      system/errors/forbidden.html
  12. 50 49
      system/errors/invalidToken.html
  13. 51 50
      system/errors/notfound.html
  14. 52 51
      system/errors/unauthorized.html
  15. 8 7
      system/newitem/Hypertext Markup Language.html
  16. 16 15
      web/Browser/loading.html
  17. 83 82
      web/Code Studio/tools/mobipreview/index.html
  18. 40 39
      web/Code Studio/tools/mobipreview/nothing.html
  19. 9 8
      web/Code Studio/tools/mobipreview/notviewable.html
  20. 1 0
      web/FFmpeg Factory/index.html
  21. 1 0
      web/FFmpeg Factory/public/concatDemuxer.html
  22. 1 0
      web/FFmpeg Factory/public/transcode-mt.esm.html
  23. 1 0
      web/FFmpeg Factory/public/transcode-mt.html
  24. 1 0
      web/MDEditor/mde.html
  25. 79 78
      web/NotepadA/embedded.html
  26. 1 0
      web/SystemAO/boot/bootflags.html
  27. 59 58
      web/SystemAO/boot/interface_disabled.html
  28. 106 105
      web/SystemAO/boot/interface_selector.html
  29. 71 70
      web/SystemAO/boot/no_interfaceing.html
  30. 1 0
      web/SystemAO/boot/poweroff.html
  31. 1 0
      web/SystemAO/boot/shutdown.html
  32. 823 822
      web/SystemAO/disk/diskmg.html
  33. 159 158
      web/SystemAO/disk/quota/manage.html
  34. 126 125
      web/SystemAO/disk/quota/quota.html
  35. 60 0
      web/SystemAO/disk/raid/index.html
  36. 148 148
      web/SystemAO/disk/smart/smart.html
  37. 1 0
      web/SystemAO/disk/space/diskspace.html
  38. 1 0
      web/SystemAO/disk/space/finder.html
  39. 1 0
      web/SystemAO/file_system/defaultOpener.html
  40. 1 0
      web/SystemAO/file_system/file_explorer.html
  41. 1 0
      web/SystemAO/file_system/file_operation.html
  42. 255 254
      web/SystemAO/file_system/file_permission.html
  43. 1 0
      web/SystemAO/file_system/file_properties.html
  44. 1 0
      web/SystemAO/file_system/file_selector.html
  45. 1 0
      web/SystemAO/file_system/file_share.html
  46. 242 241
      web/SystemAO/file_system/file_versions.html
  47. 185 184
      web/SystemAO/file_system/sharelist.html
  48. 362 361
      web/SystemAO/file_system/trashbin.html
  49. 65 64
      web/SystemAO/file_system/zip_extractor.html
  50. 575 574
      web/SystemAO/iot/hub/index.html
  51. 268 267
      web/SystemAO/modules/addAndRemove.html
  52. 106 105
      web/SystemAO/modules/defaultOpener.html
  53. 121 120
      web/SystemAO/modules/moduleList.html
  54. 162 161
      web/SystemAO/modules/subservices.html
  55. 1 0
      web/SystemAO/system_setting/index.html
  56. 204 203
      web/SystemAO/users/account.html
  57. 288 287
      web/SystemAO/users/editUser.html
  58. 329 328
      web/SystemAO/users/editgroup.html
  59. 196 195
      web/SystemAO/users/group.html
  60. 253 252
      web/SystemAO/users/newgroup.html
  61. 180 179
      web/SystemAO/users/pubreg.html
  62. 384 383
      web/SystemAO/users/userList.html
  63. 25 24
      web/SystemAO/vendor/index.html
  64. 32 31
      web/SystemAO/vendor/public/autoLoginForAdmin.html
  65. 29 28
      web/SystemAO/vendor/public/ftpForAdmin.html
  66. 223 222
      web/SystemAO/vendor/public/termsAndConditions.html
  67. 214 213
      web/Timer/timer.html
  68. 1 1
      web/desktop.system
  69. 42 41
      web/index.html
  70. 1 0
      web/mobile.system

+ 27 - 13
disk.go

@@ -12,6 +12,7 @@ import (
 	"imuslab.com/arozos/mod/disk/diskcapacity"
 	"imuslab.com/arozos/mod/disk/diskmg"
 	diskspace "imuslab.com/arozos/mod/disk/diskspace"
+	"imuslab.com/arozos/mod/disk/raid"
 	smart "imuslab.com/arozos/mod/disk/smart"
 	sortfile "imuslab.com/arozos/mod/disk/sortfile"
 	prout "imuslab.com/arozos/mod/prouter"
@@ -98,8 +99,6 @@ func DiskServiceInit() {
 				systemWideLogger.PrintAndLog("Disk", "Failed to create SMART listener: "+err.Error(), err)
 			} else {
 				//Listener created. Register endpoints
-
-				//Register as a system setting
 				registerSetting(settingModule{
 					Name:         "Disk SMART",
 					Desc:         "HardDisk Health Checking",
@@ -109,21 +108,36 @@ func DiskServiceInit() {
 					RequireAdmin: true,
 				})
 
-				/*
-					registerSetting(settingModule{
-						Name:         "SMART Log",
-						Desc:         "HardDisk Health Log",
-						IconPath:     "SystemAO/disk/smart/img/small_icon.png",
-						Group:        "Disk",
-						StartDir:     "SystemAO/disk/smart/log.html",
-						RequireAdmin: true,
-					})
-				*/
-
 				authRouter.HandleFunc("/system/disk/smart/getSMART", smartListener.GetSMART)
 			}
 		}
 
+		/*
+			RAID Management
+
+			Handle physical disk RAID for more NAS OS like experience
+		*/
+		if *allow_hardware_management {
+			//Register endpoints and settings for this host
+			registerSetting(settingModule{
+				Name:         "RAID",
+				Desc:         "Providing basic mdadm features",
+				IconPath:     "SystemAO/disk/raid/img/small_icon.png",
+				Group:        "Disk",
+				StartDir:     "SystemAO/disk/raid/index.html",
+				RequireAdmin: true,
+			})
+
+			rm, err := raid.NewRaidManager(raid.Options{})
+			if err == nil {
+				raidManager = rm
+
+			} else {
+				//Unable to start RAID manager. Skip it.
+				systemWideLogger.PrintAndLog("RAID", "Unable to start RAID manager", err)
+			}
+		}
+
 		/*
 			Disk Manager Initialization
 			See disk/diskmg.go for more details

+ 48 - 47
documents/router homepage/index.html

@@ -1,48 +1,49 @@
-<html>
-<head>
-    <meta charset="UTF-8">
-    <title>ArozOS Cloud Router</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link rel="icon" type="image/x-icon" href="favicon.ico">
-    <link rel="icon" type="image/png" href="./img/logo.png" />
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
-    <script type="text/javascript" src="./jquery.min.js"></script>
-    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
-    <link rel="preconnect" href="https://fonts.googleapis.com">
-    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300&display=swap" rel="stylesheet"> 
-    <style>
-        body p,span,div,a,h1,h2,h3,h4,h5,input{
-            font-family: 'Noto Sans TC', sans-serif;
-        }
-        
-    </style>
-</head>
-<body>
-    <br><br>
-    <div class="ui container">
-        <div class="ui segment">
-            <div class="ui grid">
-                <div class="eight wide column">
-                    
-                    <div class="ui basic segment">
-                        <h2 class="ui header">
-                            Hello!
-                            <div class="sub header">Welcome to the ArozOS Cloud Router</div>
-                        </h2>
-                        <p>This is a cloud VPS instance that do routing and reverse proxy for nodes that behind multiple NATs. If you need such services, contact us at [email protected] or find us in our Telegram group.</p>
-                        <p>這是一個雲端 VPS,正在為多個 NAT 後面的 ArozOS 節點執行路由和反向代理。 如果您也需要此服務,請通過 [email protected] 聯繫我們或在我們的 Telegram 群組中尋找我們協助。</p>
-                        <p>这是一个云端 VPS,正在为多个 NAT 后面的 ArozOS 节点执行路由和反向代理。如果您也需要此服务,请通过 [email protected] 联系我们或在我们的 Telegram 群组中寻找我们协助。</p>
-                        <p>複数のNATの背後にあるノードのルーティングとリバースプロキシーを行うクラウドVPSです。このようなサービスが必要な場合は、[email protected] または Telegram グループにご連絡ください。</p>
-                        <div class="ui divider"></div>
-                        <p>Illustration © ArozOS Original Author, All Right Reserved</p>
-                    </div>
-                </div>
-                <div class="eight wide column">
-                    <img src="title.png" style="pointer-events: none; user-select: none;">
-                </div>
-              </div>
-        </div>
-    </div>
-<body>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>ArozOS Cloud Router</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="icon" type="image/x-icon" href="favicon.ico">
+    <link rel="icon" type="image/png" href="./img/logo.png" />
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
+    <script type="text/javascript" src="./jquery.min.js"></script>
+    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300&display=swap" rel="stylesheet"> 
+    <style>
+        body p,span,div,a,h1,h2,h3,h4,h5,input{
+            font-family: 'Noto Sans TC', sans-serif;
+        }
+        
+    </style>
+</head>
+<body>
+    <br><br>
+    <div class="ui container">
+        <div class="ui segment">
+            <div class="ui grid">
+                <div class="eight wide column">
+                    
+                    <div class="ui basic segment">
+                        <h2 class="ui header">
+                            Hello!
+                            <div class="sub header">Welcome to the ArozOS Cloud Router</div>
+                        </h2>
+                        <p>This is a cloud VPS instance that do routing and reverse proxy for nodes that behind multiple NATs. If you need such services, contact us at [email protected] or find us in our Telegram group.</p>
+                        <p>這是一個雲端 VPS,正在為多個 NAT 後面的 ArozOS 節點執行路由和反向代理。 如果您也需要此服務,請通過 [email protected] 聯繫我們或在我們的 Telegram 群組中尋找我們協助。</p>
+                        <p>这是一个云端 VPS,正在为多个 NAT 后面的 ArozOS 节点执行路由和反向代理。如果您也需要此服务,请通过 [email protected] 联系我们或在我们的 Telegram 群组中寻找我们协助。</p>
+                        <p>複数のNATの背後にあるノードのルーティングとリバースプロキシーを行うクラウドVPSです。このようなサービスが必要な場合は、[email protected] または Telegram グループにご連絡ください。</p>
+                        <div class="ui divider"></div>
+                        <p>Illustration © ArozOS Original Author, All Right Reserved</p>
+                    </div>
+                </div>
+                <div class="eight wide column">
+                    <img src="title.png" style="pointer-events: none; user-select: none;">
+                </div>
+              </div>
+        </div>
+    </div>
+<body>
 </html>

+ 2 - 3
main.flags.go

@@ -8,6 +8,7 @@ import (
 	apt "imuslab.com/arozos/mod/apt"
 	auth "imuslab.com/arozos/mod/auth"
 	db "imuslab.com/arozos/mod/database"
+	"imuslab.com/arozos/mod/disk/raid"
 	"imuslab.com/arozos/mod/info/logger"
 	permission "imuslab.com/arozos/mod/permission"
 	user "imuslab.com/arozos/mod/user"
@@ -24,6 +25,7 @@ var authAgent *auth.AuthAgent //System authentication agent
 var permissionHandler *permission.PermissionHandler
 var userHandler *user.UserHandler         //User Handler
 var packageManager *apt.AptPackageManager //Manager for package auto installation
+var raidManager *raid.Manager             //Software RAID Manager, only activate on Linux hosts
 var userWwwHandler *www.Handler           //User Webroot handler
 var subserviceBasePort = 12810            //Next subservice port
 
@@ -37,9 +39,6 @@ var deviceModel = "AR100"                              //Hardware Model of the s
 var deviceModelDesc = "General Purpose Cloud Platform" //Device Model Description
 var vendorResRoot = "./vendor-res/"                    //Root folder for vendor overwrite resources
 
-//var iconVendor = "vendor/vendor_icon.png"              //Vendor icon location
-//var iconSystem = "vendor/system_icon.png"              //System icon location
-
 // =========== RUNTTIME RELATED ================
 var max_upload_size int64 = 8192 << 20                         //Maxmium upload size, default 8GB
 var sudo_mode bool = (os.Geteuid() == 0 || os.Geteuid() == -1) //Check if the program is launched as sudo mode or -1 on windows

+ 37 - 7
mod/disk/raid/mdadm.go

@@ -90,13 +90,20 @@ func (m *Manager) GetDiskUUIDByPath(devicePath string) (string, error) {
 }
 
 // CreateRAIDDevice creates a RAID device using the mdadm command.
-func (m *Manager) CreateRAIDDevice(devName string, raidLevel int, raidDev int, spareDevice int, raidDeviceIds []string, spareDeviceIds []string) error {
-	if len(raidDeviceIds) < raidDev {
-		return fmt.Errorf("insufficient RAID devices specified")
-	}
-
-	if len(spareDeviceIds) < spareDevice {
-		return fmt.Errorf("insufficient spare devices specified")
+func (m *Manager) CreateRAIDDevice(devName string, raidLevel int, raidDeviceIds []string, spareDeviceIds []string) error {
+	//Calculate the size of the raid devices
+	raidDev := len(raidDeviceIds)
+	spareDevice := len(spareDeviceIds)
+
+	//Validate the number of disk is enough for the raid
+	if raidLevel == 0 && raidDev < 2 {
+		return fmt.Errorf("not enough disks for raid0")
+	} else if raidLevel == 1 && raidDev < 2 {
+		return fmt.Errorf("not enough disks for raid1")
+	} else if raidLevel == 5 && raidDev < 3 {
+		return fmt.Errorf("not enough disk for raid5")
+	} else if raidLevel == 6 && raidDev < 4 {
+		return fmt.Errorf("not enough disk for raid6")
 	}
 
 	// Concatenate RAID and spare device arrays
@@ -119,3 +126,26 @@ func (m *Manager) CreateRAIDDevice(devName string, raidLevel int, raidDev int, s
 
 	return nil
 }
+
+// DANGER: Wipe the whole disk given the disk path
+func (m *Manager) WipeDisk(diskPath string) error {
+	// Unmount the disk
+	umountCmd := exec.Command("sudo", "umount", diskPath)
+	if err := umountCmd.Run(); err != nil {
+		return fmt.Errorf("error unmounting disk %s: %v", diskPath, err)
+	}
+
+	// Wipe all filesystem signatures on each of the partitions
+	wipeCmd1 := exec.Command("sudo", "wipefs", "--all", "--force", diskPath+"?")
+	if err := wipeCmd1.Run(); err != nil {
+		return fmt.Errorf("error wiping filesystem signatures on %s?: %v", diskPath, err)
+	}
+
+	// Wipe all filesystem signatures on the entire disk
+	wipeCmd2 := exec.Command("sudo", "wipefs", "--all", "--force", diskPath)
+	if err := wipeCmd2.Run(); err != nil {
+		return fmt.Errorf("error wiping filesystem signatures on %s: %v", diskPath, err)
+	}
+
+	return nil
+}

+ 10 - 6
mod/disk/raid/raid.go

@@ -6,6 +6,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"runtime"
 
 	"imuslab.com/arozos/mod/apt"
 	"imuslab.com/arozos/mod/utils"
@@ -20,20 +21,23 @@ type Options struct {
 }
 
 type Manager struct {
-	Usable  bool //If Manager can be used. Return false if package missing or not found
 	Options *Options
 }
 
 // Create a new raid manager
-func NewRaidManager(options *Options) (*Manager, error) {
+func NewRaidManager(options Options) (*Manager, error) {
+	//Check if platform is supported
+	if runtime.GOOS != "linux" {
+		return nil, errors.New("ArozOS do not support RAID management on this platform")
+	}
+
 	//Check if mdadm exists
 	mdadmExists, err := apt.PackageExists("mdadm")
-	if err != nil {
-		mdadmExists = false
+	if err != nil || !mdadmExists {
+		return nil, errors.New("mdadm not installed on this host")
 	}
 	return &Manager{
-		Usable:  mdadmExists,
-		Options: options,
+		Options: &options,
 	}, nil
 }
 

+ 34 - 0
mod/disk/raid/raid_test.go

@@ -0,0 +1,34 @@
+package raid_test
+
+import (
+	"fmt"
+	"testing"
+
+	"imuslab.com/arozos/mod/disk/raid"
+)
+
+func TestCreateRAIDDevice(t *testing.T) {
+	//Create an empty Manager
+	manager, _ := raid.NewRaidManager(raid.Options{})
+
+	// Make sure the loop0 and loop1 devies are mounted with automount.sh
+	devName, _ := raid.GetNextAvailableMDDevice()
+	raidLevel := 5
+	raidDeviceIds := []string{"/dev/loop0", "/dev/loop1"}
+	spareDeviceIds := []string{}
+
+	//Format the drives
+	for _, partion := range raidDeviceIds {
+		fmt.Printf("Wiping partition: " + partion)
+		manager.WipeDisk(partion)
+	}
+
+	// Call the function being tested
+	err := manager.CreateRAIDDevice(devName, raidLevel, raidDeviceIds, spareDeviceIds)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	fmt.Println("RAID array created")
+
+}

+ 18 - 0
mod/disk/raid/raidutils.go

@@ -0,0 +1,18 @@
+package raid
+
+import (
+	"fmt"
+	"os"
+)
+
+// Get the next avaible RAID array name
+func GetNextAvailableMDDevice() (string, error) {
+	for i := 0; i < 100; i++ {
+		mdDevice := fmt.Sprintf("/dev/md%d", i)
+		if _, err := os.Stat(mdDevice); os.IsNotExist(err) {
+			return mdDevice, nil
+		}
+	}
+
+	return "", fmt.Errorf("no available /dev/mdX devices found")
+}

+ 65 - 0
mod/disk/raid/status.go

@@ -0,0 +1,65 @@
+package raid
+
+import (
+	"fmt"
+	"os/exec"
+)
+
+// RAIDStatus represents the status of a RAID array.
+type RAIDStatus int
+
+const (
+	RAIDStatusNormal    RAIDStatus = 0
+	RAIDStatusOneFailed RAIDStatus = 1
+	RAIDStatusUnusable  RAIDStatus = 2
+	RAIDStatusError     RAIDStatus = 4
+	RAIDStatusUnknown   RAIDStatus = -1
+)
+
+// GetRAIDStatus scans and checks a given RAID array and returns the array status.
+func GetRAIDStatus(arrayName string) (RAIDStatus, error) {
+	cmd := exec.Command("mdadm", "--detail", arrayName)
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		// Error occurred while getting information about the array
+		return RAIDStatusError, fmt.Errorf("error getting RAID array status: %v", err)
+	}
+
+	exitStatus := cmd.ProcessState.ExitCode()
+	switch exitStatus {
+	case 0:
+		// The array is functioning normally
+		return RAIDStatusNormal, nil
+	case 1:
+		// The array has at least one failed device
+		return RAIDStatusOneFailed, nil
+	case 2:
+		// The array has multiple failed devices such that it is unusable
+		return RAIDStatusUnusable, nil
+	case 4:
+		// There was an error while trying to get information about the device
+		return RAIDStatusError, fmt.Errorf("error getting information about the RAID array: %s", string(output))
+	default:
+		// Unknown exit status
+		return RAIDStatusUnknown, fmt.Errorf("unknown exit status: %d", exitStatus)
+	}
+}
+
+// toString returns the string representation of the RAIDStatus.
+func (status RAIDStatus) toString() string {
+	switch status {
+	case RAIDStatusNormal:
+		return "Normal"
+	case RAIDStatusOneFailed:
+		return "One Failed Device"
+	case RAIDStatusUnusable:
+		return "Unusable (Multiple Failed Devices)"
+	case RAIDStatusError:
+		return "Error"
+	case RAIDStatusUnknown:
+		return "Unknown"
+	default:
+		return "Invalid Status"
+	}
+}

+ 230 - 229
system/auth/regsetting.html

@@ -1,230 +1,231 @@
-<html>
-    <head>
-        <title>Register Settings</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <style>
-
-        </style>
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="registered icon"></i>
-                    <div class="content">
-                        Public Account Registry Settings
-                      <div class="sub header">Setup the public account registry on the current host system</div>
-                    </div>
-                  </div>
-            </div> 
-            <div class="ui red segment">
-                <h3><i class="exclamation icon"></i> Warning</h3>
-                <p>Allow public register flags (public_reg) has been enable on this host. Hence, you can setup an interface for public to register an user account on this host.
-                    Note that this setting interface will change the behavior of the system account creation process and might lead to security issues if this is not configured properly.<br>
-                    Please seek network security professionals for consultation before releasing access to public for registering. </p>
-            </div>
-            <div id="updateSuccessMsg" class="ui green segment" style="display:none">
-                <i class="checkmark icon"></i> Settings Updated
-            </div>
-            <div class="field">
-                <div class="ui toggle checkbox">
-                    <input id="apr" type="checkbox" tabindex="0" onchange="toggleRegistrySettings(this.checked);">
-                    <label>Allow Public Registry</label>
-                </div>
-            </div>
-            <div class="ui divider"></div>
-            <form id="registerSettings" class="ui form" style="display:none;" onsubmit="formsubmit(event);">
-                <div class="field">
-                    <div class="ui toggle checkbox">
-                        <input id="eivc" type="checkbox" tabindex="0" onchange="toggleInvideCode(this.checked);">
-                        <label>Enable Invitation Code</label>
-                    </div>
-                </div>
-                <div id="ivc" class="field" style="display:none;">
-                    <label>Invitation Code</label>
-                    <input type="text" name="invitecode" placeholder="Invitation Code">
-                    <small>Only user with invitation code can register</small>
-                </div>
-                <div class="field">
-                    <div  class="ui selection dropdown">
-                        <input id="usergroup" type="hidden" name="group" onchange="updateSelectedGroup(this.value);">
-                        <i class="dropdown icon"></i>
-                        <div class="default text">Default Group</div>
-                        <div id="grouplist" class="menu">
-                           
-                        </div>
-                    </div>
-                    <small>Not recommend using administrator as the default user group unless under very specific conditions.</small>
-                </div>
-                
-            </form>
-            <button class="ui primary button" onclick="formsubmit(event);">Apply</button>
-            <div id="loader" class="ui inverted active dimmer" style="display:none;">
-                <div class="ui text loader">Updating Configuration</div>
-            </div>
-            <br><br>
-        </div>
-        <script>
-            var selectedGroup = "";
-
-            $(document).ready(function(){
-                $.ajax({
-                    url: "../../public/register/settings",
-                    success: function(data){
-                        if (data.AllowPublicRegister == true){
-                            $("#apr")[0].checked = true;
-                        }else{
-                            $("#apr")[0].checked = false;
-                        }
-                        if (data.EnableInvitationCode == true){
-                            $("#eivc")[0].checked = true;
-                        }else{
-                            $("#eivc")[0].checked = false;
-                        }
-
-                        if ($("#eivc")[0].checked == true){
-                            toggleInvideCode(true);
-                        }
-
-                        if ($("#apr")[0].checked == true){
-                            toggleRegistrySettings(true);
-                        }
-
-                        if (data.InvitationCode !== ""){
-                            $("#ivc").find("input").val(data.InvitationCode);
-                        }
-
-                        initUserGroupList(data.DefaultUserGroup);
-                    },
-                    error: function(){
-                        window.location.reload();
-                    }
-                });
-                
-            });
-
-            function toggleInvideCode(visable){
-                if (visable){
-                    $("#ivc").slideDown("fast");
-                    if ($("#ivc").find("input").val() == ""){
-                        $("#ivc").find("input").val(makeid(16));
-                    }
-                }else{
-                    $("#ivc").slideUp("fast");
-                }
-            }
-
-            function initUserGroupList(defaultvalue=""){
-                $.get("../../system/permission/listgroup",function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        for (var i =0; i < data.length; i++){
-                            $("#grouplist").append(`<div class="item" value="${data[i]}">${data[i]}</div>`);
-                        }
-                    }
-
-                    $('.ui.dropdown').dropdown();
-                    if (defaultvalue != ""){
-                        console.log(defaultvalue);
-                        $('.ui.dropdown').dropdown('set selected', defaultvalue);
-                    }
-                })
-            }
-
-            function makeid(length) {
-                var result           = '';
-                var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-                var charactersLength = characters.length;
-                for ( var i = 0; i < length; i++ ) {
-                    result += characters.charAt(Math.floor(Math.random() * charactersLength));
-                }
-                return result;
-            }
-
-
-            function updateSelectedGroup(newSelectedGroup){
-                selectedGroup = newSelectedGroup;
-                if (selectedGroup == "administrator"){
-                    //Dangerous! Add warning label
-                    $("#usergroup").parent().addClass("error");
-                }else{
-                    $("#usergroup").parent().removeClass("error");
-                }
-            }
-
-            function toggleRegistrySettings(allowRegistry){
-                if (allowRegistry){
-                    $("#registerSettings").slideDown("fast");
-                }else{
-                    $("#registerSettings").slideUp("fast");
-                }
-            }
-
-            function formsubmit(e){
-                e.preventDefault();
-                //Parse the form data
-                var dataObject = {};
-                if ($("#apr")[0].checked == false){
-                    dataObject = {
-                        apr: false,
-                        eivc: false,
-                        icode: "",
-                        group: ""
-                    }
-                }else{
-                    var usergroup = $("#usergroup").val();
-                    if (usergroup == "administrator"){
-                        if (!confirm("Are you confirm you want to use adminstrator as the default group?")){
-                            return;
-                        }
-                    }
-                    var ivc = $("#eivc")[0].checked;
-                    if (!ivc){
-                        var invitationCode = "";
-                    }else{
-                        var invitationCode = $("#ivc").find("input").val();
-                    }
-                    dataObject = {
-                        apr: true,
-                        eivc: ivc,
-                        icode: invitationCode,
-                        group: usergroup
-                    }
-
-                }
-
-                //Ready to send the config to server
-                $("#loader").show();
-                console.log(dataObject);
-                $.ajax({
-                    url: "../../public/register/settings",
-                    method:"POST",
-                    data: {opr: "write", config: JSON.stringify(dataObject)},
-                    success: function(data){
-                        $("#loader").hide();
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            $("#updateSuccessMsg").slideDown("fast").delay(3000).slideUp("fast");
-                        }
-                        
-                    },
-                    error: function(data){
-                        $("#loader").hide();
-                        alert("Failed to update settings!")
-                    },
-                    timeout:15000
-                })
-            }
-
-            
-            
-           
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Register Settings</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <style>
+
+        </style>
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="registered icon"></i>
+                    <div class="content">
+                        Public Account Registry Settings
+                      <div class="sub header">Setup the public account registry on the current host system</div>
+                    </div>
+                  </div>
+            </div> 
+            <div class="ui red segment">
+                <h3><i class="exclamation icon"></i> Warning</h3>
+                <p>Allow public register flags (public_reg) has been enable on this host. Hence, you can setup an interface for public to register an user account on this host.
+                    Note that this setting interface will change the behavior of the system account creation process and might lead to security issues if this is not configured properly.<br>
+                    Please seek network security professionals for consultation before releasing access to public for registering. </p>
+            </div>
+            <div id="updateSuccessMsg" class="ui green segment" style="display:none">
+                <i class="checkmark icon"></i> Settings Updated
+            </div>
+            <div class="field">
+                <div class="ui toggle checkbox">
+                    <input id="apr" type="checkbox" tabindex="0" onchange="toggleRegistrySettings(this.checked);">
+                    <label>Allow Public Registry</label>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <form id="registerSettings" class="ui form" style="display:none;" onsubmit="formsubmit(event);">
+                <div class="field">
+                    <div class="ui toggle checkbox">
+                        <input id="eivc" type="checkbox" tabindex="0" onchange="toggleInvideCode(this.checked);">
+                        <label>Enable Invitation Code</label>
+                    </div>
+                </div>
+                <div id="ivc" class="field" style="display:none;">
+                    <label>Invitation Code</label>
+                    <input type="text" name="invitecode" placeholder="Invitation Code">
+                    <small>Only user with invitation code can register</small>
+                </div>
+                <div class="field">
+                    <div  class="ui selection dropdown">
+                        <input id="usergroup" type="hidden" name="group" onchange="updateSelectedGroup(this.value);">
+                        <i class="dropdown icon"></i>
+                        <div class="default text">Default Group</div>
+                        <div id="grouplist" class="menu">
+                           
+                        </div>
+                    </div>
+                    <small>Not recommend using administrator as the default user group unless under very specific conditions.</small>
+                </div>
+                
+            </form>
+            <button class="ui primary button" onclick="formsubmit(event);">Apply</button>
+            <div id="loader" class="ui inverted active dimmer" style="display:none;">
+                <div class="ui text loader">Updating Configuration</div>
+            </div>
+            <br><br>
+        </div>
+        <script>
+            var selectedGroup = "";
+
+            $(document).ready(function(){
+                $.ajax({
+                    url: "../../public/register/settings",
+                    success: function(data){
+                        if (data.AllowPublicRegister == true){
+                            $("#apr")[0].checked = true;
+                        }else{
+                            $("#apr")[0].checked = false;
+                        }
+                        if (data.EnableInvitationCode == true){
+                            $("#eivc")[0].checked = true;
+                        }else{
+                            $("#eivc")[0].checked = false;
+                        }
+
+                        if ($("#eivc")[0].checked == true){
+                            toggleInvideCode(true);
+                        }
+
+                        if ($("#apr")[0].checked == true){
+                            toggleRegistrySettings(true);
+                        }
+
+                        if (data.InvitationCode !== ""){
+                            $("#ivc").find("input").val(data.InvitationCode);
+                        }
+
+                        initUserGroupList(data.DefaultUserGroup);
+                    },
+                    error: function(){
+                        window.location.reload();
+                    }
+                });
+                
+            });
+
+            function toggleInvideCode(visable){
+                if (visable){
+                    $("#ivc").slideDown("fast");
+                    if ($("#ivc").find("input").val() == ""){
+                        $("#ivc").find("input").val(makeid(16));
+                    }
+                }else{
+                    $("#ivc").slideUp("fast");
+                }
+            }
+
+            function initUserGroupList(defaultvalue=""){
+                $.get("../../system/permission/listgroup",function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        for (var i =0; i < data.length; i++){
+                            $("#grouplist").append(`<div class="item" value="${data[i]}">${data[i]}</div>`);
+                        }
+                    }
+
+                    $('.ui.dropdown').dropdown();
+                    if (defaultvalue != ""){
+                        console.log(defaultvalue);
+                        $('.ui.dropdown').dropdown('set selected', defaultvalue);
+                    }
+                })
+            }
+
+            function makeid(length) {
+                var result           = '';
+                var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+                var charactersLength = characters.length;
+                for ( var i = 0; i < length; i++ ) {
+                    result += characters.charAt(Math.floor(Math.random() * charactersLength));
+                }
+                return result;
+            }
+
+
+            function updateSelectedGroup(newSelectedGroup){
+                selectedGroup = newSelectedGroup;
+                if (selectedGroup == "administrator"){
+                    //Dangerous! Add warning label
+                    $("#usergroup").parent().addClass("error");
+                }else{
+                    $("#usergroup").parent().removeClass("error");
+                }
+            }
+
+            function toggleRegistrySettings(allowRegistry){
+                if (allowRegistry){
+                    $("#registerSettings").slideDown("fast");
+                }else{
+                    $("#registerSettings").slideUp("fast");
+                }
+            }
+
+            function formsubmit(e){
+                e.preventDefault();
+                //Parse the form data
+                var dataObject = {};
+                if ($("#apr")[0].checked == false){
+                    dataObject = {
+                        apr: false,
+                        eivc: false,
+                        icode: "",
+                        group: ""
+                    }
+                }else{
+                    var usergroup = $("#usergroup").val();
+                    if (usergroup == "administrator"){
+                        if (!confirm("Are you confirm you want to use adminstrator as the default group?")){
+                            return;
+                        }
+                    }
+                    var ivc = $("#eivc")[0].checked;
+                    if (!ivc){
+                        var invitationCode = "";
+                    }else{
+                        var invitationCode = $("#ivc").find("input").val();
+                    }
+                    dataObject = {
+                        apr: true,
+                        eivc: ivc,
+                        icode: invitationCode,
+                        group: usergroup
+                    }
+
+                }
+
+                //Ready to send the config to server
+                $("#loader").show();
+                console.log(dataObject);
+                $.ajax({
+                    url: "../../public/register/settings",
+                    method:"POST",
+                    data: {opr: "write", config: JSON.stringify(dataObject)},
+                    success: function(data){
+                        $("#loader").hide();
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            $("#updateSuccessMsg").slideDown("fast").delay(3000).slideUp("fast");
+                        }
+                        
+                    },
+                    error: function(data){
+                        $("#loader").hide();
+                        alert("Failed to update settings!")
+                    },
+                    timeout:15000
+                })
+            }
+
+            
+            
+           
+        </script>
+    </body>
 </html>

+ 52 - 51
system/errors/alreadyLoggedin.html

@@ -1,52 +1,53 @@
-<html>
-    <head>
-        <title>Already Logged In</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="application/javascript" src="../../script/jquery.min.js"></script>
-        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
-       <style>
-            #game{
-                border: 1px solid black;
-
-            }
-        </style>
-    </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <div class="ui stackable grid">
-                <div class="eight wide column" style="margin-top:8rem;">
-                    <h2 class="ui header">
-                        <i class="remove icon"></i>
-                        <div class="content">
-                            Already Logged In
-                            <div class="sub header">This client has already logged in with another user account.</div>
-                        </div>
-                    </h2>
-                    <div class="ui divider"></div>
-                    <small>Solution: Please log out the previous account before logging into another one.</small>
-                </div>
-                <div class="eight wide column">
-                    <div id="errorpic" class="ui text container">
-                        <img class="ui image" src="/img/public/errors/already_logged_in.png">
-                    </div>
-                    
-                </div>
-            </div>
-            
-            <div class="ui divider"></div>
-            ERROR: DUPLICATE-AUTOLOGIN-STATUS
-        </div>
-        <script>
-            $(window).on("resize", function(){
-                if (window.innerHeight > window.innerWidth){
-                    $("#errorpic").attr("align","center");
-                }else{
-                    $("#errorpic").attr("align","left");
-                }
-            })
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Already Logged In</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <script type="application/javascript" src="../../script/jquery.min.js"></script>
+        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+       <style>
+            #game{
+                border: 1px solid black;
+
+            }
+        </style>
+    </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <div class="ui stackable grid">
+                <div class="eight wide column" style="margin-top:8rem;">
+                    <h2 class="ui header">
+                        <i class="remove icon"></i>
+                        <div class="content">
+                            Already Logged In
+                            <div class="sub header">This client has already logged in with another user account.</div>
+                        </div>
+                    </h2>
+                    <div class="ui divider"></div>
+                    <small>Solution: Please log out the previous account before logging into another one.</small>
+                </div>
+                <div class="eight wide column">
+                    <div id="errorpic" class="ui text container">
+                        <img class="ui image" src="/img/public/errors/already_logged_in.png">
+                    </div>
+                    
+                </div>
+            </div>
+            
+            <div class="ui divider"></div>
+            ERROR: DUPLICATE-AUTOLOGIN-STATUS
+        </div>
+        <script>
+            $(window).on("resize", function(){
+                if (window.innerHeight > window.innerWidth){
+                    $("#errorpic").attr("align","center");
+                }else{
+                    $("#errorpic").attr("align","left");
+                }
+            })
+        </script>
+    </body>
 </html>

+ 51 - 50
system/errors/forbidden.html

@@ -1,51 +1,52 @@
-<html>
-    <head>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <title>Forbidden</title>
-        <style>
-            #msg{
-                position: absolute;
-                top: calc(50% - 150px);
-                left: calc(50% - 250px);
-                width: 500px;
-                height: 300px;
-                text-align: center;
-            }
-
-            #footer{
-                position: fixed;
-                padding: 2em;
-                padding-left: 5em;
-                padding-right: 5em;
-                bottom: 0px;
-                left: 0px;
-                width: 100%;
-            }   
-
-            small{
-                word-break: break-word;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="msg">
-            <h1 style="font-size: 6em; margin-bottom: 0px;">403</h1>
-            <div>
-                <h3 class="">Forbidden</h3>
-                <div class="ui divider"></div>
-                <p>We cannot show you the content of the URL you requested. That is all we know.</p>
-                <div class="ui divider"></div>
-                <div style="text-align: left;">
-                    <small>Request time: <span id="reqtime"></span></small><br>
-                </div>
-            </div>
-        </div>
-        <script>
-            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <title>Forbidden</title>
+        <style>
+            #msg{
+                position: absolute;
+                top: calc(50% - 150px);
+                left: calc(50% - 250px);
+                width: 500px;
+                height: 300px;
+                text-align: center;
+            }
+
+            #footer{
+                position: fixed;
+                padding: 2em;
+                padding-left: 5em;
+                padding-right: 5em;
+                bottom: 0px;
+                left: 0px;
+                width: 100%;
+            }   
+
+            small{
+                word-break: break-word;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="msg">
+            <h1 style="font-size: 6em; margin-bottom: 0px;">403</h1>
+            <div>
+                <h3 class="">Forbidden</h3>
+                <div class="ui divider"></div>
+                <p>We cannot show you the content of the URL you requested. That is all we know.</p>
+                <div class="ui divider"></div>
+                <div style="text-align: left;">
+                    <small>Request time: <span id="reqtime"></span></small><br>
+                </div>
+            </div>
+        </div>
+        <script>
+            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
+        </script>
+    </body>
 </html>

+ 50 - 49
system/errors/invalidToken.html

@@ -1,50 +1,51 @@
-<html>
-    <head>
-        <title>Invalid Autologin Token</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="application/javascript" src="../../script/jquery.min.js"></script>
-        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
-       <style>
-            #game{
-                border: 1px solid black;
-
-            }
-        </style>
-    </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <div class="ui stackable grid">
-                <div class="eight wide column" style="margin-top:8rem;">
-                    <h2 class="ui header">
-                        <i class="remove icon"></i>
-                        <div class="content">
-                            Invalid Auto-Login Token
-                            <div class="sub header">The given token cannot be validated.<br>That is all we know.</div>
-                        </div>
-                    </h2>
-                </div>
-                <div class="eight wide column">
-                    <div id="errorpic" class="ui text container">
-                        <img class="ui image" src="/img/public/errors/error.png">
-                    </div>
-                    
-                </div>
-            </div>
-            
-            <div class="ui divider"></div>
-            ERROR: INVALID-AUTOLOGIN-TOKEN
-        </div>
-        <script>
-            $(window).on("resize", function(){
-                if (window.innerHeight > window.innerWidth){
-                    $("#errorpic").attr("align","center");
-                }else{
-                    $("#errorpic").attr("align","left");
-                }
-            })
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Invalid Autologin Token</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <script type="application/javascript" src="../../script/jquery.min.js"></script>
+        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+       <style>
+            #game{
+                border: 1px solid black;
+
+            }
+        </style>
+    </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <div class="ui stackable grid">
+                <div class="eight wide column" style="margin-top:8rem;">
+                    <h2 class="ui header">
+                        <i class="remove icon"></i>
+                        <div class="content">
+                            Invalid Auto-Login Token
+                            <div class="sub header">The given token cannot be validated.<br>That is all we know.</div>
+                        </div>
+                    </h2>
+                </div>
+                <div class="eight wide column">
+                    <div id="errorpic" class="ui text container">
+                        <img class="ui image" src="/img/public/errors/error.png">
+                    </div>
+                    
+                </div>
+            </div>
+            
+            <div class="ui divider"></div>
+            ERROR: INVALID-AUTOLOGIN-TOKEN
+        </div>
+        <script>
+            $(window).on("resize", function(){
+                if (window.innerHeight > window.innerWidth){
+                    $("#errorpic").attr("align","center");
+                }else{
+                    $("#errorpic").attr("align","left");
+                }
+            })
+        </script>
+    </body>
 </html>

+ 51 - 50
system/errors/notfound.html

@@ -1,51 +1,52 @@
-<html>
-    <head>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <title>Not Found</title>
-        <style>
-            #msg{
-                position: absolute;
-                top: calc(50% - 150px);
-                left: calc(50% - 250px);
-                width: 500px;
-                height: 300px;
-                text-align: center;
-            }
-
-            #footer{
-                position: fixed;
-                padding: 2em;
-                padding-left: 5em;
-                padding-right: 5em;
-                bottom: 0px;
-                left: 0px;
-                width: 100%;
-            }   
-
-            small{
-                word-break: break-word;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="msg">
-            <h1 style="font-size: 6em; margin-bottom: 0px;">404</h1>
-            <div>
-                <h3 class="">Page Not Found</h3>
-                <div class="ui divider"></div>
-                <p>The page or resource you are trying to access does not exist.</p>
-                <div class="ui divider"></div>
-                <div style="text-align: left;">
-                    <small>Request time: <span id="reqtime"></span></small><br>
-                </div>
-            </div>
-        </div>
-        <script>
-            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <title>Not Found</title>
+        <style>
+            #msg{
+                position: absolute;
+                top: calc(50% - 150px);
+                left: calc(50% - 250px);
+                width: 500px;
+                height: 300px;
+                text-align: center;
+            }
+
+            #footer{
+                position: fixed;
+                padding: 2em;
+                padding-left: 5em;
+                padding-right: 5em;
+                bottom: 0px;
+                left: 0px;
+                width: 100%;
+            }   
+
+            small{
+                word-break: break-word;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="msg">
+            <h1 style="font-size: 6em; margin-bottom: 0px;">404</h1>
+            <div>
+                <h3 class="">Page Not Found</h3>
+                <div class="ui divider"></div>
+                <p>The page or resource you are trying to access does not exist.</p>
+                <div class="ui divider"></div>
+                <div style="text-align: left;">
+                    <small>Request time: <span id="reqtime"></span></small><br>
+                </div>
+            </div>
+        </div>
+        <script>
+            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
+        </script>
+    </body>
 </html>

+ 52 - 51
system/errors/unauthorized.html

@@ -1,52 +1,53 @@
-<html>
-    <head>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="{{root_escape}}script/semantic/semantic.min.css">
-        <script type="text/javascript" src="{{root_escape}}script/jquery.min.js"></script>
-        <script type="text/javascript" src="{{root_escape}}script/semantic/semantic.min.js"></script>
-        <title>Unauthorized</title>
-        <style>
-            #msg{
-                position: absolute;
-                top: calc(50% - 150px);
-                left: calc(50% - 250px);
-                width: 500px;
-                height: 300px;
-                text-align: center;
-            }
-
-            #footer{
-                position: fixed;
-                padding: 2em;
-                padding-left: 5em;
-                padding-right: 5em;
-                bottom: 0px;
-                left: 0px;
-                width: 100%;
-            }   
-
-            small{
-                word-break: break-word;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="msg">
-            <h1 style="font-size: 6em; margin-bottom: 0px;">401</h1>
-            <div>
-                <h3 class="">Unauthorized</h3>
-                <div class="ui divider"></div>
-                <p>You do not have permission to view this directory or page. <br><a href="{{root_escape}}">Back</a></p>
-                <div class="ui divider"></div>
-                <div style="text-align: left;">
-                    <small>Request time: <span id="reqtime"></span></small><br>
-                    <small id="reqURLDisplay">Request URI: {{request_url}}</small>
-                </div>
-            </div>
-        </div>
-        <script>
-            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="{{root_escape}}script/semantic/semantic.min.css">
+        <script type="text/javascript" src="{{root_escape}}script/jquery.min.js"></script>
+        <script type="text/javascript" src="{{root_escape}}script/semantic/semantic.min.js"></script>
+        <title>Unauthorized</title>
+        <style>
+            #msg{
+                position: absolute;
+                top: calc(50% - 150px);
+                left: calc(50% - 250px);
+                width: 500px;
+                height: 300px;
+                text-align: center;
+            }
+
+            #footer{
+                position: fixed;
+                padding: 2em;
+                padding-left: 5em;
+                padding-right: 5em;
+                bottom: 0px;
+                left: 0px;
+                width: 100%;
+            }   
+
+            small{
+                word-break: break-word;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="msg">
+            <h1 style="font-size: 6em; margin-bottom: 0px;">401</h1>
+            <div>
+                <h3 class="">Unauthorized</h3>
+                <div class="ui divider"></div>
+                <p>You do not have permission to view this directory or page. <br><a href="{{root_escape}}">Back</a></p>
+                <div class="ui divider"></div>
+                <div style="text-align: left;">
+                    <small>Request time: <span id="reqtime"></span></small><br>
+                    <small id="reqURLDisplay">Request URI: {{request_url}}</small>
+                </div>
+            </div>
+        </div>
+        <script>
+            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
+        </script>
+    </body>
 </html>

+ 8 - 7
system/newitem/Hypertext Markup Language.html

@@ -1,8 +1,9 @@
-<html>
-	<head>
-		<title>Hello World!</title>
-	</head>
-	<body>
-	
-	</body>
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>Hello World!</title>
+	</head>
+	<body>
+	
+	</body>
 </html>

+ 16 - 15
web/Browser/loading.html

@@ -1,16 +1,17 @@
-<html>
-    <head>
-        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
-        <style>
-            body{
-                height: 100vh;
-                width: 100%;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="ui active inverted dimmer">
-            <div class="ui indeterminate text loader"></div>
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
+        <style>
+            body{
+                height: 100vh;
+                width: 100%;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui active inverted dimmer">
+            <div class="ui indeterminate text loader"></div>
+        </div>
+    </body>
 </html>

+ 83 - 82
web/Code Studio/tools/mobipreview/index.html

@@ -1,83 +1,84 @@
-<html>
-    <head>
-		<script src="../../../script/jquery.min.js"></script>
-        <script src="../../../script/ao_module.js"></script>
-		<title>NotepadA Test Window</title>
-        <style>
-            body{
-                font-family: Arial !important;
-				background-color:#ebebeb;
-            }
-            .menu{
-                position:fixed;
-                top:0;
-                left:0;
-                width:100%;
-                height:18px;
-                background-color:#3d3d3d;
-                padding:5px;
-                color:white;
-                font-size:80%;
-            }
-            .urlinput{
-                display:inline;
-            }
-			.toRight{
-				position:absolute;
-				right:15px;
-				top:3px;
-			}
-			.previewArea{
-				width:100%;
-				position:fixed;
-				top:27px;
-				left:0px;
-			}
-			#previewWindow{
-				position:absolute;
-				left:0px;
-				top:0px;
-			}
-        </style>
-    </head>
-    <body>
-        <div id="toolbar" class="menu">
-            Window Size:   <div id="windowsize" style="display:inline;">loading...</div><button class="toRight" onClick="document.getElementById('previewWindow').contentWindow.location.reload();">Refresh</button>
-        </div>
-		<div class="previewArea">
-			<iframe id="previewWindow" frameBorder="0" width="100%" src="nothing.html"></iframe>
-		</div>
-    </body>
-    <script>
-    var bottomPadding = 8; //In pixel
-	adjustIframeSize();
-	function adjustIframeSize(){
-		var w = window.innerWidth;
-		var h = window.innerHeight;
-		$("#windowsize").html(w + "px x " + (h  - parseInt($("#toolbar").height()) - bottomPadding) + "px");
-		$("#previewWindow").attr("width",w + "px");
-		$("#previewWindow").attr("height",(h  - parseInt($("#toolbar").height()) - bottomPadding) + "px")
-		
-	}
-	
-	$( window ).resize(function() {
-		adjustIframeSize();
-	});
-
-	//Check if anyfile are passed in
-	var inputFile = ao_module_loadInputFiles();
-	if (inputFile != null){
-		inputFile = inputFile[0];
-		//Contains input file. Check if it can be opened (.html / .htm)
-		var ext = inputFile.filename.split(".");
-		ext = ext.pop();
-		if (ext == "html" || ext == "htm"){
-			//Open it
-			$("#previewWindow").attr('src', "../../../media?file=" + inputFile.filepath)
-		}else{
-			$("#previewWindow").attr('src', "./notviewable.html");
-		}
-	}
-
-	</script>
+<!DOCTYPE html>
+<html>
+    <head>
+		<script src="../../../script/jquery.min.js"></script>
+        <script src="../../../script/ao_module.js"></script>
+		<title>NotepadA Test Window</title>
+        <style>
+            body{
+                font-family: Arial !important;
+				background-color:#ebebeb;
+            }
+            .menu{
+                position:fixed;
+                top:0;
+                left:0;
+                width:100%;
+                height:18px;
+                background-color:#3d3d3d;
+                padding:5px;
+                color:white;
+                font-size:80%;
+            }
+            .urlinput{
+                display:inline;
+            }
+			.toRight{
+				position:absolute;
+				right:15px;
+				top:3px;
+			}
+			.previewArea{
+				width:100%;
+				position:fixed;
+				top:27px;
+				left:0px;
+			}
+			#previewWindow{
+				position:absolute;
+				left:0px;
+				top:0px;
+			}
+        </style>
+    </head>
+    <body>
+        <div id="toolbar" class="menu">
+            Window Size:   <div id="windowsize" style="display:inline;">loading...</div><button class="toRight" onClick="document.getElementById('previewWindow').contentWindow.location.reload();">Refresh</button>
+        </div>
+		<div class="previewArea">
+			<iframe id="previewWindow" frameBorder="0" width="100%" src="nothing.html"></iframe>
+		</div>
+    </body>
+    <script>
+    var bottomPadding = 8; //In pixel
+	adjustIframeSize();
+	function adjustIframeSize(){
+		var w = window.innerWidth;
+		var h = window.innerHeight;
+		$("#windowsize").html(w + "px x " + (h  - parseInt($("#toolbar").height()) - bottomPadding) + "px");
+		$("#previewWindow").attr("width",w + "px");
+		$("#previewWindow").attr("height",(h  - parseInt($("#toolbar").height()) - bottomPadding) + "px")
+		
+	}
+	
+	$( window ).resize(function() {
+		adjustIframeSize();
+	});
+
+	//Check if anyfile are passed in
+	var inputFile = ao_module_loadInputFiles();
+	if (inputFile != null){
+		inputFile = inputFile[0];
+		//Contains input file. Check if it can be opened (.html / .htm)
+		var ext = inputFile.filename.split(".");
+		ext = ext.pop();
+		if (ext == "html" || ext == "htm"){
+			//Open it
+			$("#previewWindow").attr('src', "../../../media?file=" + inputFile.filepath)
+		}else{
+			$("#previewWindow").attr('src', "./notviewable.html");
+		}
+	}
+
+	</script>
 </html>

+ 40 - 39
web/Code Studio/tools/mobipreview/nothing.html

@@ -1,40 +1,41 @@
-<html>
-<head>
-<title>Nothing here :)</title>
-<style>
-.msgbox{
-	position:fixed;
-	background-color:#e0e0e0;
-	top:30px;
-	left:10%;
-	right:10%;
-	padding:10px;
-}
-body{
-	
-}
-.btmbar{
-	position:fixed;
-	left:0;
-	right:0;
-	bottom:0;
-	height:10px;
-	background-color:#2b2b2b;
-	color:white;
-	font-size:70%;
-	padding-left:5px;
-	padding-right: 5px;
-	padding-bottom:10px;
-}
-</style>
-</head>
-<body>
-<div class="msgbox">
-<h4>Ready!</h4>
-<p>Start the responsive designer with filename and filepath to start viewing :)</p>
-</div>
-<div class="btmbar">
-NotepadA Project feat. ArozOS
-</div>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+<title>Nothing here :)</title>
+<style>
+.msgbox{
+	position:fixed;
+	background-color:#e0e0e0;
+	top:30px;
+	left:10%;
+	right:10%;
+	padding:10px;
+}
+body{
+	
+}
+.btmbar{
+	position:fixed;
+	left:0;
+	right:0;
+	bottom:0;
+	height:10px;
+	background-color:#2b2b2b;
+	color:white;
+	font-size:70%;
+	padding-left:5px;
+	padding-right: 5px;
+	padding-bottom:10px;
+}
+</style>
+</head>
+<body>
+<div class="msgbox">
+<h4>Ready!</h4>
+<p>Start the responsive designer with filename and filepath to start viewing :)</p>
+</div>
+<div class="btmbar">
+NotepadA Project feat. ArozOS
+</div>
+</body>
 </html>

+ 9 - 8
web/Code Studio/tools/mobipreview/notviewable.html

@@ -1,9 +1,10 @@
-<html>
-<head>
-<title>Viewing Not Supported</title>
-</head>
-<body>
-The file you are trying to open cannot be previewed as webpage
-nor it is not supported by ArozOS Web Interface.
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+<title>Viewing Not Supported</title>
+</head>
+<body>
+The file you are trying to open cannot be previewed as webpage
+nor it is not supported by ArozOS Web Interface.
+</body>
 </html>

+ 1 - 0
web/FFmpeg Factory/index.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
 <head>
     <meta name="apple-mobile-web-app-capable" content="yes" />

+ 1 - 0
web/FFmpeg Factory/public/concatDemuxer.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
   <head>
     <link rel="stylesheet" href="style.css">

+ 1 - 0
web/FFmpeg Factory/public/transcode-mt.esm.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
   <head>
     <link rel="stylesheet" href="style.css">

+ 1 - 0
web/FFmpeg Factory/public/transcode-mt.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
   <head>
     <link rel="stylesheet" href="style.css">

+ 1 - 0
web/MDEditor/mde.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Markdown Editor</title>

+ 79 - 78
web/NotepadA/embedded.html

@@ -1,79 +1,80 @@
-<html>
-<head>
-	<style>
-		body{
-			background-color: white;
-		}
-	</style>
-</head>
-<body>
-Initializing Environment...<br>
-<script src="../script/jquery.min.js"></script>
-<script src="../script/ao_module.js"></script>
-<script>
-//Try its best to hide this window
-ao_module_setWindowTitle("NotepadA Initializing...");
-
-var inputFiles = ao_module_loadInputFiles();
-if (inputFiles != null){
-	console.log(inputFiles);
-	let targetOpeningInstances = ao_module_getInstanceByPath("NotepadA/index.html")
-	if (targetOpeningInstances == null){
-		//Open the file in new NotepadA windows
-		let encodedFileObject = encodeURIComponent(JSON.stringify(inputFiles));
-		var url = "NotepadA/index.html#" + encodedFileObject;
-		var title = "NotepadA";
-		ao_module_newfw({
-			url: url,
-			width: 1080,
-			height: 580,
-			title: title,
-			appicon: "NotepadA/img/small_icon.png",
-		});
-
-		setTimeout(function(){
-			ao_module_close();
-		}, 300);
-	}else{
-		//Make the running NotepadA instance to open those files
-		inputFiles.forEach(function(file){
-			console.log($(targetOpeningInstances).find("iframe")[0].contentWindow.newEditor(file.filepath));
-		});
-		
-		setTimeout(function(){
-			ao_module_close();
-		}, 300);
-	}
-
-
-	
-}
-
-/*
-var instances = ao_module_getProcessID("NotepadA");
-var filepath = "<?php echo $filepath;?>";
-var filename = "<?php echo $filename;?>";
-remove(instances,ao_module_windowID);
-if (instances.includes("newWindow")){
-	remove(instances,"newWindow");
-}
-if (instances.length == 0){
-	//Open a new window for the file
-	console.log("[NotepadA] Opening " + filepath + " in a new floatWindow");
-	window.location.href = "index.php?filename=" + filename + "&filepath=" + filepath;
-}else if (instances.length > 0){
-	//Open the new page in the first instances in list
-	var targetWindow = instances[0];
-	console.log("[NotepadA] Opening " + filepath + " in floatWindow " + targetWindow);
-	parent.crossFrameFunctionCall(targetWindow,"newEditor('" + filepath + "');");
-	ao_module_close();
-}
-
-function remove(array, element) {
-    const index = array.indexOf(element);
-    array.splice(index, 1);
-}
-*/
-</script>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+	<style>
+		body{
+			background-color: white;
+		}
+	</style>
+</head>
+<body>
+Initializing Environment...<br>
+<script src="../script/jquery.min.js"></script>
+<script src="../script/ao_module.js"></script>
+<script>
+//Try its best to hide this window
+ao_module_setWindowTitle("NotepadA Initializing...");
+
+var inputFiles = ao_module_loadInputFiles();
+if (inputFiles != null){
+	console.log(inputFiles);
+	let targetOpeningInstances = ao_module_getInstanceByPath("NotepadA/index.html")
+	if (targetOpeningInstances == null){
+		//Open the file in new NotepadA windows
+		let encodedFileObject = encodeURIComponent(JSON.stringify(inputFiles));
+		var url = "NotepadA/index.html#" + encodedFileObject;
+		var title = "NotepadA";
+		ao_module_newfw({
+			url: url,
+			width: 1080,
+			height: 580,
+			title: title,
+			appicon: "NotepadA/img/small_icon.png",
+		});
+
+		setTimeout(function(){
+			ao_module_close();
+		}, 300);
+	}else{
+		//Make the running NotepadA instance to open those files
+		inputFiles.forEach(function(file){
+			console.log($(targetOpeningInstances).find("iframe")[0].contentWindow.newEditor(file.filepath));
+		});
+		
+		setTimeout(function(){
+			ao_module_close();
+		}, 300);
+	}
+
+
+	
+}
+
+/*
+var instances = ao_module_getProcessID("NotepadA");
+var filepath = "<?php echo $filepath;?>";
+var filename = "<?php echo $filename;?>";
+remove(instances,ao_module_windowID);
+if (instances.includes("newWindow")){
+	remove(instances,"newWindow");
+}
+if (instances.length == 0){
+	//Open a new window for the file
+	console.log("[NotepadA] Opening " + filepath + " in a new floatWindow");
+	window.location.href = "index.php?filename=" + filename + "&filepath=" + filepath;
+}else if (instances.length > 0){
+	//Open the new page in the first instances in list
+	var targetWindow = instances[0];
+	console.log("[NotepadA] Opening " + filepath + " in floatWindow " + targetWindow);
+	parent.crossFrameFunctionCall(targetWindow,"newEditor('" + filepath + "');");
+	ao_module_close();
+}
+
+function remove(array, element) {
+    const index = array.indexOf(element);
+    array.splice(index, 1);
+}
+*/
+</script>
+</body>
 </html>

+ 1 - 0
web/SystemAO/boot/bootflags.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Runtime Adjustment</title>

+ 59 - 58
web/SystemAO/boot/interface_disabled.html

@@ -1,59 +1,60 @@
-<html>
-    <head>
-        <title>Interface Not Found</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="application/javascript" src="../../script/jquery.min.js"></script>
-        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
-       <style>
-            #game{
-                border: 1px solid black;
-
-            }
-        </style>
-    </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <div class="ui text container">
-                <div class="ui stackable grid">
-                    <div class="eight wide column" style="margin-top:8rem;">
-                        <h2 class="ui header">
-                            <i class="remove icon"></i>
-                            <div class="content">
-                                 Disabled or Missing Interface Module
-                                <div class="sub header">No worry, your files are still here.</div>
-                            </div>
-                        </h2>
-                        <div class="ui divider"></div>
-                        <p>
-                            You are seeing this message because your defined interface module is being disabled or not found on this server. Please contact technical support for more information.
-                        </p>
-                        <br>
-                        
-                    </div>
-                    <div class="eight wide column">
-                        <div id="errorpic" class="ui text container">
-                            <img class="ui image" src="../../img/public/errors/no_interface_module.png">
-                        </div>
-                        
-                    </div>
-                </div>
-            
-            <br><br>
-            <div class="ui divider"></div>
-            ERROR: DISABLED-INTERFACE-MODULE
-        </div>
-        
-        <script>
-            $(window).on("resize", function(){
-                if (window.innerHeight > window.innerWidth){
-                    $("#errorpic").attr("align","center");
-                }else{
-                    $("#errorpic").attr("align","left");
-                }
-            })
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Interface Not Found</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <script type="application/javascript" src="../../script/jquery.min.js"></script>
+        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+       <style>
+            #game{
+                border: 1px solid black;
+
+            }
+        </style>
+    </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <div class="ui text container">
+                <div class="ui stackable grid">
+                    <div class="eight wide column" style="margin-top:8rem;">
+                        <h2 class="ui header">
+                            <i class="remove icon"></i>
+                            <div class="content">
+                                 Disabled or Missing Interface Module
+                                <div class="sub header">No worry, your files are still here.</div>
+                            </div>
+                        </h2>
+                        <div class="ui divider"></div>
+                        <p>
+                            You are seeing this message because your defined interface module is being disabled or not found on this server. Please contact technical support for more information.
+                        </p>
+                        <br>
+                        
+                    </div>
+                    <div class="eight wide column">
+                        <div id="errorpic" class="ui text container">
+                            <img class="ui image" src="../../img/public/errors/no_interface_module.png">
+                        </div>
+                        
+                    </div>
+                </div>
+            
+            <br><br>
+            <div class="ui divider"></div>
+            ERROR: DISABLED-INTERFACE-MODULE
+        </div>
+        
+        <script>
+            $(window).on("resize", function(){
+                if (window.innerHeight > window.innerWidth){
+                    $("#errorpic").attr("align","center");
+                }else{
+                    $("#errorpic").attr("align","left");
+                }
+            })
+        </script>
+    </body>
 </html>

+ 106 - 105
web/SystemAO/boot/interface_selector.html

@@ -1,106 +1,107 @@
-<html>
-    <head>
-        <title>Interface Selector</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="application/javascript" src="../../script/jquery.min.js"></script>
-        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
-       <style>
-           .interfaceModule{
-               cursor: pointer;
-               border: 2px solid transparent !important;
-               height: 120px;
-           }
-           .interfaceModule:hover{
-                border: 2px solid #407ded !important;
-                background-color: #d1e1ff;
-           }
-           a{
-               cursor: pointer;
-           }
-        </style>
-    </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <h2>Please choose a service to start</h2>
-            <div class="ui divider"></div>
-            <div id="serviceList" class="ui segment">
-                
-            </div>
-            <div id="remember" class="ui checkbox">
-                <input type="checkbox" name="remember">
-                <label>Remember option on this browser</label>
-            </div>
-
-            <div class="ui divider"></div>
-            <p>or <a onclick="logout();">Logout</a></p>
-        </div>
-        <script>
-            var interfaceModules = [];
-         $(".ui.checkbox").checkbox();
-          $.get("../../system/users/interfaceinfo", function(data){
-              $("#serviceList").html("");
-              interfaceModules = data;
-              for (var i = 0; i < data.length; i++){
-                  $("#serviceList").append(`<div class="ui segment interfaceModule" name="${data[i].Name}" path="${data[i].StartDir}" onclick="startModule(this);">
-                    <div class="ui grid">
-                        <div class="four wide column" align="right">
-                            <img src="../../${data[i].IconPath}" class="ui tiny image">
-                        </div>
-                        <div class="twelve wide column">
-                            <h2 class="ui header">
-                                ${data[i].Name}
-                                <div class="sub header">${data[i].Desc}</div>
-                            </h2>
-                        </div>
-                    </div>
-                </div>`);
-              }
-              useDefaultIfExists();
-          });
-
-        function useDefaultIfExists(){
-            var defaultModule = localStorage.getItem("default-interface-module");
-            if (defaultModule !== undefined && defaultModule !== null && defaultModule !== ""){
-                //Get its path
-                interfaceModules.forEach(mod => {
-                    if (mod.Name == defaultModule){
-                        var targetPath = mod.StartDir;
-                        if (targetPath == ""){
-                            //Redirect to desktop
-                            window.location.href = "../../desktop.system";
-                        }else{
-                            //Redirect to module
-                            window.location.href = "../../" + targetPath;
-                        }
-                    }
-                });
-            }
-        }
-
-        function startModule(object){
-            var path = $(object).attr("path");
-            var name = $(object).attr("name");
-            if ($("#remember").checkbox("is checked")){
-                localStorage.setItem("default-interface-module",name)
-            }
-            if (path == ""){
-                //Redirect to desktop
-                window.location.href = "../../desktop.system";
-            }else{
-                //Redirect to module
-                window.location.href = "../../" + path;
-            }
-            
-        }
-
-        function logout() {
-            $.get("../../system/auth/logout", function() {
-                window.location.href = "/";
-            });
-        }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Interface Selector</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <script type="application/javascript" src="../../script/jquery.min.js"></script>
+        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+       <style>
+           .interfaceModule{
+               cursor: pointer;
+               border: 2px solid transparent !important;
+               height: 120px;
+           }
+           .interfaceModule:hover{
+                border: 2px solid #407ded !important;
+                background-color: #d1e1ff;
+           }
+           a{
+               cursor: pointer;
+           }
+        </style>
+    </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <h2>Please choose a service to start</h2>
+            <div class="ui divider"></div>
+            <div id="serviceList" class="ui segment">
+                
+            </div>
+            <div id="remember" class="ui checkbox">
+                <input type="checkbox" name="remember">
+                <label>Remember option on this browser</label>
+            </div>
+
+            <div class="ui divider"></div>
+            <p>or <a onclick="logout();">Logout</a></p>
+        </div>
+        <script>
+            var interfaceModules = [];
+         $(".ui.checkbox").checkbox();
+          $.get("../../system/users/interfaceinfo", function(data){
+              $("#serviceList").html("");
+              interfaceModules = data;
+              for (var i = 0; i < data.length; i++){
+                  $("#serviceList").append(`<div class="ui segment interfaceModule" name="${data[i].Name}" path="${data[i].StartDir}" onclick="startModule(this);">
+                    <div class="ui grid">
+                        <div class="four wide column" align="right">
+                            <img src="../../${data[i].IconPath}" class="ui tiny image">
+                        </div>
+                        <div class="twelve wide column">
+                            <h2 class="ui header">
+                                ${data[i].Name}
+                                <div class="sub header">${data[i].Desc}</div>
+                            </h2>
+                        </div>
+                    </div>
+                </div>`);
+              }
+              useDefaultIfExists();
+          });
+
+        function useDefaultIfExists(){
+            var defaultModule = localStorage.getItem("default-interface-module");
+            if (defaultModule !== undefined && defaultModule !== null && defaultModule !== ""){
+                //Get its path
+                interfaceModules.forEach(mod => {
+                    if (mod.Name == defaultModule){
+                        var targetPath = mod.StartDir;
+                        if (targetPath == ""){
+                            //Redirect to desktop
+                            window.location.href = "../../desktop.system";
+                        }else{
+                            //Redirect to module
+                            window.location.href = "../../" + targetPath;
+                        }
+                    }
+                });
+            }
+        }
+
+        function startModule(object){
+            var path = $(object).attr("path");
+            var name = $(object).attr("name");
+            if ($("#remember").checkbox("is checked")){
+                localStorage.setItem("default-interface-module",name)
+            }
+            if (path == ""){
+                //Redirect to desktop
+                window.location.href = "../../desktop.system";
+            }else{
+                //Redirect to module
+                window.location.href = "../../" + path;
+            }
+            
+        }
+
+        function logout() {
+            $.get("../../system/auth/logout", function() {
+                window.location.href = "/";
+            });
+        }
+        </script>
+    </body>
 </html>

+ 71 - 70
web/SystemAO/boot/no_interfaceing.html

@@ -1,71 +1,72 @@
-<html>
-    <head>
-        <title>No Access Interface</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="application/javascript" src="../../script/jquery.min.js"></script>
-        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
-       <style>
-            #game{
-                border: 1px solid black;
-
-            }
-        </style>
-    </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <div class="ui text container">
-                <div class="ui stackable grid">
-                    <div class="eight wide column" style="margin-top:8rem;">
-                        <h2 class="ui header">
-                            <i class="remove icon"></i>
-                            <div class="content">
-                                 Invalid Interface Module
-                                <div class="sub header">No worry, your files are still here.</div>
-                            </div>
-                        </h2>
-                        <div class="ui divider"></div>
-                        <p>
-                            You are seeing this message because the administrator of this system has removed all your interfacing modules. 
-                            Please contact technical support and show them this page.
-                        </p>
-                        <br>
-                        
-                    </div>
-                    <div class="eight wide column">
-                        <div id="errorpic" class="ui text container">
-                            <img class="ui image" src="../../img/public/errors/no_interface_module.png">
-                        </div>
-                        
-                    </div>
-                </div>
-                
-                <!-- 
-                <div class="ui divider"></div>
-                <p>In the mean time, have some break and play some snakes?</p>
-                <div align="center" style="width:100%;">
-                    <canvas width="400" height="400" id="game"></canvas>
-                </div>
-                 -->
-            
-            <br><br>
-            <div class="ui divider"></div>
-            ERROR: INVALID-INTERFACE-MODULE-SETTINGS
-        </div>
-        
-        <script>
-            //Basic snake game, minified version of https://gist.github.com/straker/ff00b4b49669ad3dec890306d348adc4
-            //var canvas=document.getElementById("game"),context=canvas.getContext("2d"),grid=16,count=0,snake={x:160,y:160,dx:grid,dy:0,cells:[],maxCells:4},apple={x:320,y:320};function getRandomInt(e,n){return Math.floor(Math.random()*(n-e))+e}function loop(){requestAnimationFrame(loop),++count<4||(count=0,context.clearRect(0,0,canvas.width,canvas.height),snake.x+=snake.dx,snake.y+=snake.dy,snake.x<0?snake.x=canvas.width-grid:snake.x>=canvas.width&&(snake.x=0),snake.y<0?snake.y=canvas.height-grid:snake.y>=canvas.height&&(snake.y=0),snake.cells.unshift({x:snake.x,y:snake.y}),snake.cells.length>snake.maxCells&&snake.cells.pop(),context.fillStyle="#4287f5",context.fillRect(apple.x,apple.y,grid-1,grid-1),context.fillStyle="#cccccc",snake.cells.forEach(function(e,n){context.fillRect(e.x,e.y,grid-1,grid-1),e.x===apple.x&&e.y===apple.y&&(snake.maxCells++,apple.x=getRandomInt(0,25)*grid,apple.y=getRandomInt(0,25)*grid);for(var a=n+1;a<snake.cells.length;a++)e.x===snake.cells[a].x&&e.y===snake.cells[a].y&&(snake.x=160,snake.y=160,snake.cells=[],snake.maxCells=4,snake.dx=grid,snake.dy=0,apple.x=getRandomInt(0,25)*grid,apple.y=getRandomInt(0,25)*grid)}))}document.addEventListener("keydown",function(e){37===e.which&&0===snake.dx?(snake.dx=-grid,snake.dy=0):38===e.which&&0===snake.dy?(snake.dy=-grid,snake.dx=0):39===e.which&&0===snake.dx?(snake.dx=grid,snake.dy=0):40===e.which&&0===snake.dy&&(snake.dy=grid,snake.dx=0)}),requestAnimationFrame(loop);
-        
-            $(window).on("resize", function(){
-                if (window.innerHeight > window.innerWidth){
-                    $("#errorpic").attr("align","center");
-                }else{
-                    $("#errorpic").attr("align","left");
-                }
-            })
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>No Access Interface</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <script type="application/javascript" src="../../script/jquery.min.js"></script>
+        <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+       <style>
+            #game{
+                border: 1px solid black;
+
+            }
+        </style>
+    </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <div class="ui text container">
+                <div class="ui stackable grid">
+                    <div class="eight wide column" style="margin-top:8rem;">
+                        <h2 class="ui header">
+                            <i class="remove icon"></i>
+                            <div class="content">
+                                 Invalid Interface Module
+                                <div class="sub header">No worry, your files are still here.</div>
+                            </div>
+                        </h2>
+                        <div class="ui divider"></div>
+                        <p>
+                            You are seeing this message because the administrator of this system has removed all your interfacing modules. 
+                            Please contact technical support and show them this page.
+                        </p>
+                        <br>
+                        
+                    </div>
+                    <div class="eight wide column">
+                        <div id="errorpic" class="ui text container">
+                            <img class="ui image" src="../../img/public/errors/no_interface_module.png">
+                        </div>
+                        
+                    </div>
+                </div>
+                
+                <!-- 
+                <div class="ui divider"></div>
+                <p>In the mean time, have some break and play some snakes?</p>
+                <div align="center" style="width:100%;">
+                    <canvas width="400" height="400" id="game"></canvas>
+                </div>
+                 -->
+            
+            <br><br>
+            <div class="ui divider"></div>
+            ERROR: INVALID-INTERFACE-MODULE-SETTINGS
+        </div>
+        
+        <script>
+            //Basic snake game, minified version of https://gist.github.com/straker/ff00b4b49669ad3dec890306d348adc4
+            //var canvas=document.getElementById("game"),context=canvas.getContext("2d"),grid=16,count=0,snake={x:160,y:160,dx:grid,dy:0,cells:[],maxCells:4},apple={x:320,y:320};function getRandomInt(e,n){return Math.floor(Math.random()*(n-e))+e}function loop(){requestAnimationFrame(loop),++count<4||(count=0,context.clearRect(0,0,canvas.width,canvas.height),snake.x+=snake.dx,snake.y+=snake.dy,snake.x<0?snake.x=canvas.width-grid:snake.x>=canvas.width&&(snake.x=0),snake.y<0?snake.y=canvas.height-grid:snake.y>=canvas.height&&(snake.y=0),snake.cells.unshift({x:snake.x,y:snake.y}),snake.cells.length>snake.maxCells&&snake.cells.pop(),context.fillStyle="#4287f5",context.fillRect(apple.x,apple.y,grid-1,grid-1),context.fillStyle="#cccccc",snake.cells.forEach(function(e,n){context.fillRect(e.x,e.y,grid-1,grid-1),e.x===apple.x&&e.y===apple.y&&(snake.maxCells++,apple.x=getRandomInt(0,25)*grid,apple.y=getRandomInt(0,25)*grid);for(var a=n+1;a<snake.cells.length;a++)e.x===snake.cells[a].x&&e.y===snake.cells[a].y&&(snake.x=160,snake.y=160,snake.cells=[],snake.maxCells=4,snake.dx=grid,snake.dy=0,apple.x=getRandomInt(0,25)*grid,apple.y=getRandomInt(0,25)*grid)}))}document.addEventListener("keydown",function(e){37===e.which&&0===snake.dx?(snake.dx=-grid,snake.dy=0):38===e.which&&0===snake.dy?(snake.dy=-grid,snake.dx=0):39===e.which&&0===snake.dx?(snake.dx=grid,snake.dy=0):40===e.which&&0===snake.dy&&(snake.dy=grid,snake.dx=0)}),requestAnimationFrame(loop);
+        
+            $(window).on("resize", function(){
+                if (window.innerHeight > window.innerWidth){
+                    $("#errorpic").attr("align","center");
+                }else{
+                    $("#errorpic").attr("align","left");
+                }
+            })
+        </script>
+    </body>
 </html>

+ 1 - 0
web/SystemAO/boot/poweroff.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Host Power Options</title>

+ 1 - 0
web/SystemAO/boot/shutdown.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Shutting Down</title>

+ 823 - 822
web/SystemAO/disk/diskmg.html

@@ -1,823 +1,824 @@
-<html>
-    <head>
-        <meta charset="UTF-8">
-        <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
-        <script src="../../script/jquery.min.js"></script>
-        <script type='text/javascript' src="../../semantic/semantic.min.js"></script>
-        <script src="../../script/ao_module.js"></script>
-        <title>Diskmg</title>
-        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-        <style>
-            body{
-                height: 100%;
-            }
-            .customFitted.item{
-                padding-top:5px !important;
-                padding-bottom:5px !important;
-            }
-            #diskListTable{
-                max-height:250px !important;
-                overflow-y: auto;
-            }
-            #diskVisualization{
-                overflow-y:auto;
-            }
-            .diskPartTable{
-                width:100%;
-                border-bottom:1px solid #9c9c9c;
-                overflow-x:hidden;
-            }
-            .sideblock{
-                background-color:#f0f0f0;
-                height:100px;
-                width:100px;
-                padding:8px;
-                /*
-                    border-right:1px solid #9c9c9c;
-                */
-                
-                font-size:90%;
-                display:inline-block;
-            }
-            .partitionRepresentation{
-                border:1px solid #e8e8e8;
-                display:inline-block;
-                height:100px;
-                vertical-align: top;
-                overflow:hidden;
-                /*
-                    border-left:1px solid #6e6e6e;
-                */
-                
-                cursor:pointer;
-            }
-            .partitionTopBar{
-                background-color:#4287f5;
-                width:100%;
-                height:15px;
-                margin-bottom:3px;
-            }
-            .partitionTopBar.unallocate{
-                 background-color:#1f1f1f;
-            }
-            .partitionTopBar.unmounted{
-                 background-color:#ab8a29;
-            }
-            .partitionDescription{
-                padding-left:8px;
-                padding:3px;
-            }
-            #rightClickMenu{
-                position:absolute;
-            }
-            .selectable{
-                cursor: pointer;
-            }
-            .selectable:hover{
-                background-color:#f0f0f0 !important;
-            }
-            .focusedPart{
-                background-color: #e3f0ff;
-            }
-            .disabled{
-                background-color:#e6e6e6;
-                color:#787878 !important;
-                cursor:no-drop !important;
-            }
-            .funcmenu{
-                position:fixed;
-                top:10%;
-                right:20%;
-                left:20%;
-                bottom:10%;
-                overflow-y:auto;
-                z-index:100;
-                background-color:#f7f7f7;
-                padding:12px;
-                display:none;
-                border: 1px solid #9c9c9c;
-            }
-            .functMenuDimmer{
-                z-index:90;
-                position:absolute;
-                width:100%;
-                height:100%;
-                left:0px;
-                top:0px;
-                background:rgba(48,48,48,0.5);
-                display:none;
-            }
-            .funcmenuBottom{
-                position:absolute;
-                width:100%;
-                bottom:0px;
-                left:0px;
-                padding:12px;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="diskListTable" >
-            <table class="ui celled striped unstackable table">
-                <thead>
-                    <tr>
-                        <th>
-                            Volume
-                        </th>
-                        <th class="windowsonly" style="display:none;">
-                            Name
-                        </th>
-                        <th class="windowsonly" style="display:none;">
-                            Type
-                        </th>
-                        <th class="linuxonly" style="display:none;">
-                            Mount Point
-                        </th>
-                        <th>
-                            File System
-                        </th>
-                        <th>
-                            Capacity
-                        </th>
-                        <th class="windowsonly" style="display:none;">
-                            Free Space
-                        </th>
-                        <th class="windowsonly" style="display:none;">
-                            % Free
-                        </th>
-                        <th class="linuxonly" style="display:none;">
-                            Used Space
-                        </th>
-                        <th class="linuxonly" style="display:none;">
-                            % Used
-                        </th>
-                    </tr>
-                </thead>
-                <tbody id="diskInfoTable">
-                    <tr>
-                        <td class="collapsing">
-                            <i class="disk outline icon"></i>/dev/sda1
-                        </td>
-                        <td class="collapsing">/media/storage1</td>
-                        <td class="right aligned collapsing">NTFS</td>
-                        <td class="right aligned collapsing">64 GB</td>
-                        <td class="right aligned collapsing">12.5 GB</td>
-                        <td class="right aligned collapsing">19.7%</td>
-                    </tr>
-                </tbody>
-            </table>
-        </div>
-        <div id="diskVisualization">
-            <div class="diskPartTable">
-                <div class="sideblock">
-                    <i class="disk outline icon" style="margin-right:0px;font-weight: bold;"></i>
-                    <b style="font-weight: bold;">Drive 0</b><br>
-                    N/A
-                </div><div class="partitionRepresentation" style="width:calc(100% - 150px);">
-                    <div class="partitionTopBar"></div>
-                    <div class="partitionDescription">
-                       Connecting to Virtual Disk Services
-                    </div>
-                </div>
-            </div>
-        </div>
-    <div id="rightClickMenu" class="ui vertical menu" style="display: none;">
-        <div id="formatDisk" class="item selectable" onClick="toggleFormatInterface(this);">
-            <i class="disk outline icon"></i> Format Disk
-        </div>
-        <div id="mtbtn" class="item selectable" onClick="toggleMount(this);">
-            <i class="usb icon"></i> Mount
-        </div>
-    </div>
-    <!-- Sections for functional menus-->
-    <div id="formatOptions" class="funcmenu">
-        <h2 class="ui header">
-            <i class="red exclamation circle icon"></i>
-            <div class="content">
-                Format Disk
-                <div class="sub header" style="font-weight:120%;color:red;">Warning! The format process will wipe all data from the selected partition.</div>
-            </div>
-        </h2>
-        <div class="ui red message">
-            <p>Format on any drive or partition will wipe all data within that drive or partition. Please make sure you have backup all necessary files and your drive / partition selection is correct.</p>
-        </div>
-        <div class="ui header">
-            <i class="disk outline icon"></i>
-            <div id="selectedDiskDisplay" class="content">
-                /dev/sda1 (120 GB)
-            </div>
-        </div>
-        <div class="ui form">
-            <div class="field">
-                <label>Target File System Format</label>
-                <div class="ui checkboxes">
-                    <div class="ui radio checkbox">
-                        <input id="ext4" type="radio" name="format" checked>
-                        <label for="ext4">EXT4</label>
-                    </div>
-                    <div class="ui radio checkbox">
-                        <input id="ntfs" type="radio" name="format">
-                        <label for="ntfs">NTFS</label>
-                    </div>
-                    <div class="ui radio checkbox">
-                        <input id="vfat" type="radio" name="format">
-                        <label for="vfat">VFAT</label>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <div class="funcmenuBottom" align="right">
-            <button class="ui tiny negative button" onClick="formatThisDev();">Format</button>
-            <button class="ui tiny button" onClick="hideAllFuncMenu();">Close</button>
-        </div>
-    </div>
-    <div id="mountOptions" class="funcmenu">
-        <div class="ui header">
-            Disk Mount
-            <div class="sub header">Select a mount point for this device</div>
-        </div>
-        <div id="mtptlist" class="ui segmented list" style="max-height:300px;overflow-y:auto;">
-           
-            
-   
-            <div class="mountpt item selectable userdefine" onclick="selectThis(this);">
-                <p>User defined mount point</p>
-                <div class="ui fluid mini input">
-                    <input id="userDefinedMountPoint" type="text" placeholder="/">
-                </div>
-            </div>
-        </div>
-        
-        <div class="funcmenuBottom" align="right">
-            <button class="ui tiny primary button" onClick="mountThisDev();">Mount</button>
-            <button class="ui tiny button" onClick="hideAllFuncMenu();">Close</button>
-        </div>
-    </div>
-    
-    <!-- dimmers-->
-     <div id="loaderUI" class="ui active dimmer" style="display:none;">
-        <div class="ui text loader">Waiting for System Response<br>Do Not Close This Tab</div>
-    </div>
-    <div class="functMenuDimmer" onClick="hideAllFuncMenu();">
-        
-    </div>
-    <script>
-        var mode = "linux";
-        var viewMode = "human"; //Accept {human / raw}
-        var diskInformation; //Tmp variable for holding disk return results
-        var displayScaleRatio = 0.1; //Maxium differential ratio, default 0.3, it means the minium disk will show as 70% screen width
-        var fwmode = false;
-        var formatPendingDevInfo;
-        
-      
-        //Updates Nov 2020, added platform detection
-        $.get(ao_root + "system/disk/diskmg/platform", function(data){
-            if (data == "windows"){
-                mode = "windows";
-            }else{
-                mode = "linux"
-            }
-            //Init data loading process
-            initView();
-            initPartitionTable();
-            initMountPointList();
-        })
-     
-        
-        //Mount pt selection interface
-        $(".mountpt").on('click',function(e){
-            $(".selected").removeClass("selected");
-            $(this).addClass("selected");
-        });
-        
-        function hideAllFuncMenu(){
-            $(".funcmenu").fadeOut('fast');
-            $(".functMenuDimmer").fadeOut('fast');
-        }
-
-        function initMountPointList(){
-            $.get(ao_root + "system/disk/diskmg/mpt", function(data){
-                if (data == null){
-                    //On Windows
-                    return
-                }
-                data.forEach(mpt => {
-                    $("#mtptlist").prepend(`<div class="mountpt selectable item" style="cursor:pointer;" onclick="selectThis(this);" ondblclick="mountThisDev(this)">${mpt}</div>`)
-                });
-                
-            });
-        }
-
-        function selectThis(object){
-            $(".selected.selectable.item").removeClass('selected');
-            $(object).addClass("selected");
-        }
-        
-        function formatThisDev(){
-            var targetFormat = $("input[name='format']:checked").attr("id");
-            var targetDisk = formatPendingDevInfo;
-            console.log("Formating Disk: " + targetDisk + " to " + targetFormat);
-            if(targetFormat){
-                $("#loaderUI").show();
-                if (confirm("THIS OPERATION WILL WIPE ALL DATA ON /dev/" + targetDisk[0] + ". ARE YOU SURE?")){
-                    $("#formatOptions").fadeOut('fast');
-                    $(".functMenuDimmer").fadeOut('fast');
-                    console.log(ao_root + "system/disk/diskmg/format?dev=" + targetDisk[0] + "&format=" + targetFormat);
-                    $.ajax({
-                        url: ao_root + "system/disk/diskmg/format",
-                        data: {"dev": targetDisk[0], "format": targetFormat},
-                        method: "POST",
-                        success: function(data){
-                            if (data.error !== undefined){
-                                alert(data.error);
-                            }
-                            initView();
-                            initPartitionTable();
-                            $("#loaderUI").hide();
-                        }
-                    });
-                }else{
-                    $("#loaderUI").hide();
-                }
-            }
-        }
-        
-        function toggleFormatInterface(btnObject){
-            if ($(btnObject).hasClass("disabled") == true){
-                return;
-            }
-            $("#formatOptions").fadeIn('fast');
-            $(".functMenuDimmer").fadeIn('fast');
-            hideRightclickMenu();
-            var diskInfo = $(".focusedPart").attr("metadata");
-            diskInfo = ao_module_utils.attrToObject(diskInfo);
-            formatPendingDevInfo = diskInfo;
-            $("#selectedDiskDisplay").text(diskInfo[0] + " (" + bytesToSize(parseInt(diskInfo[5])) + ") ");
-        }
-        
-        function mountThisDev(object=null){
-            if (object !== null && !$(object).hasClass(".selected.item")){
-                $(".selected").removeClass("selected");
-                $(object).addClass("selected");
-            }
-            var selectedMpt = $(".selected.item");
-            var mountPoint = $(selectedMpt).text().trim();
-            if (selectedMpt.hasClass("userdefine")){
-                var mountPoint = $("#userDefinedMountPoint").val();
-            }
-             $("#loaderUI").show();
-             $("#mountOptions").fadeOut('fast');
-             $(".functMenuDimmer").fadeOut('fast');
-             var diskInfo = $(".focusedPart").attr("metadata");
-             diskInfo = ao_module_utils.attrToObject(diskInfo);
-             $.get(ao_root + "system/disk/diskmg/mount?dev=" + diskInfo[0] + "&format=" + diskInfo[2] + "&mnt=" + mountPoint,function(data){
-                if (data.error != undefined){
-                    $("#loaderUI").hide();
-                    alert(data.error);
-                    return;
-                }
-                //Reload the UI
-                initView();
-                initPartitionTable();
-                $("#loaderUI").hide();
-            });
-        }
-        
-        function toggleMount(btnObject){
-            if ($(btnObject).hasClass("disabled") == true){
-                return;
-            }
-            var diskInfo = $(".focusedPart").attr("metadata");
-            diskInfo = ao_module_utils.attrToObject(diskInfo);
-            if (diskInfo[3] == false){
-                //Mount disk
-                $("#mountOptions").fadeIn('fast');
-                $(".functMenuDimmer").fadeIn('fast');
-                
-            }else{
-                //Unmount disk
-                var dev = diskInfo[0];
-                var mnt = diskInfo[1];
-                var format = diskInfo[2];
-                hideRightclickMenu();
-                $("#loaderUI").show();
-                $.get(ao_root + "system/disk/diskmg/mount?dev=" + dev + "&format=" + format + "&mnt=" + mnt + "&umount=true",function(data){
-                    console.log(data);
-                    //Reload the UI
-                     initView();
-                     initPartitionTable();
-                     $("#loaderUI").hide();
-                });
-            }
-            hideRightclickMenu();
-        }
-        
-        function hideRightclickMenu(){
-            $("#rightClickMenu").hide();
-        }
-
-        /*
-        function openInFileExplorer(btnObject){
-            if ($(btnObject).hasClass('disabled')){
-                return;
-            }
-            var diskInfo = $(".focusedPart").attr("metadata");
-            diskInfo = ao_module_utils.attrToObject(diskInfo);
-            if (diskInfo[3] == true){
-                //This disk is mounted
-                var uid = Date.now();
-                if (fwmode){
-                    ao_module_newfw("SystemAOB/functions/file_system/index.php?controlLv=2&dir=" + diskInfo[1],"Loading", "folder open outline",uid,1080,580,undefined,undefined,true,true);
-                }else{
-                    window.open("../../functions/file_system/index.php?controlLv=2&dir=" + diskInfo[1]);
-                }
-            }
-            hideRightclickMenu();
-        }
-        */
-        
-        function createEventHooks(){
-            $(".partitionRepresentation").contextmenu(function(e){
-                if (mode == "windows"){
-                    //Switch back to normal menu when under window mode
-                    return true;
-                }
-                var px = e.clientX;
-                var py = e.clientY;
-                var top = py - $("#rightClickMenu").height();
-                if (ao_module_virtualDesktop){
-                    top -= 50;
-                }
-                $("#rightClickMenu").css({"left": px + "px", "top":  top + "px"});
-                $("#rightClickMenu").show();
-                console.log(e.target);
-                $(".focusedPart").removeClass("focusedPart");
-                var partbody =  $(e.target);
-                if ($(e.target).parent().hasClass("partitionRepresentation")){
-                    //Clicked on the child instead.
-                    $(e.target).parent().addClass("focusedPart");
-                    partbody =  $(e.target).parent();
-                }else{
-                    //Click on the representation body.
-                    $(e.target).addClass("focusedPart");
-                }
-                
-                //Create a custom context menu for the operation
-                var partInfo = ao_module_utils.attrToObject(partbody.attr("metadata"));
-                console.log(partInfo);
-                if (partInfo[3] == true){
-                    //This disk is mounted. Provide unmount btn
-                    if (partInfo[1] == "/" || partInfo[1] == "/boot"){
-                        //No, you can't unmount root nor format it
-                        $("#mtbtn").addClass("disabled");
-                        $("#formatDisk").addClass("disabled");
-                    }else{
-                        $("#mtbtn").removeClass("disabled");
-                        $("#formatDisk").removeClass("disabled");
-                    }
-                    $("#mtbtn").html('<i class="usb icon"></i> Unmount');
-                    if (partInfo[1].substring(0,6) == "/media"){
-                         //This can be opened
-                         $("#openbtn").removeClass("disabled");
-                    }else{
-                         $("#openbtn").addClass("disabled");
-                    }
-                }else{
-                    //This disk is not mounted. Provide mount btn
-                    $("#mtbtn").html('<i class="usb icon"></i> Mount Drive');
-                    $("#openbtn").addClass("disabled");
-                    $("#mtbtn").removeClass("disabled");
-                    $("#formatDisk").removeClass("disabled");
-                }
-                //Prevent browser menu from showing
-                return false;
-            });
-        }
-        
-        function adjustPartitionViewHeight(){
-            $("#diskVisualization").css("height",window.innerHeight - $("#diskListTable").height() - 120 + "px");
-        }
-        
-        function initView(){
-            if (mode == "windows"){
-                $(".windowsonly").show();
-                //Runing on top of Window Host
-                $.get(ao_root + "system/disk/diskmg/view",function(data){
-                    $("#diskInfoTable").html("");
-                    if (data.error == undefined){
-                       for (var i = 0; i < data.length; i++){
-                           var thisDisk = data[i];
-                           var driveName = thisDisk[2];
-                           if (thisDisk[2] == "" && thisDisk[0] == "C:\\"){
-                                driveName = "Primary Drive";
-                           }else if (thisDisk[2] == ""){
-                                driveName = "Local Disk";
-                           }else if (driveName == undefined){
-                                driveName = "No Media"
-                           }
-
-                           if (thisDisk[3] == undefined){
-                               //Unknown File System
-                               thisDisk[3] = "N/A"
-                           }
-
-                           var cap = bytesToSize(thisDisk[6]);
-                           if (thisDisk[6] == undefined){
-                                cap =  "N/A";
-                           }
-
-                           var free = bytesToSize(thisDisk[5]);
-                           if (thisDisk[5] == undefined){
-                               free = "N/A";
-                           }
-
-                           var perc = Math.round(thisDisk[5] / thisDisk[6] * 100);
-                           if (isNaN(perc)){
-                               perc = "0";
-                           }
-
-                           $("#diskInfoTable").append('<tr>\
-                                <td class="collapsing">\
-                                    <i class="disk outline icon"></i>' + thisDisk[0] + '\
-                                </td>\
-                                <td class="">' + driveName + '</td>\
-                                <td class="collapsing">' + thisDisk[1] + '</td>\
-                                <td class="right aligned collapsing">' + thisDisk[3] + '</td>\
-                                <td class="right aligned collapsing">' + cap + '</td>\
-                                <td class="right aligned collapsing">' + free + '</td>\
-                                <td class="right aligned collapsing">' + perc + '%</td>\
-                            </tr>');
-                       }
-                    }
-                });
-            }else{
-                //Runing on top of Linux Host
-                $(".linuxonly").show();
-                $.get(ao_root + "system/disk/diskmg/view",function(data){
-                    $("#diskInfoTable").html("");
-                    if (data.error == undefined){
-                        var disks = data[0]["blockdevices"];
-                        var partitions = data[1];
-                       for (var i = 0; i < disks.length; i++){
-                           var thisDisk = disks[i]["children"];
-                           if (thisDisk === undefined || thisDisk === null){
-                                let thisSize = disks[i]["size"] || 0;
-                                let thisPartitionID = disks[i]["name"] || "✖";
-                                let mountPoint = disks[i]["mountpoint"] || "[NO PARTITION]";
-                                $("#diskInfoTable").append('<tr>\
-                                    <td class="collapsing">\
-                                        <i class="disk outline icon"></i>' + thisPartitionID + '\
-                                    </td>\
-                                    <td class="">' +  mountPoint + '</td>\
-                                    <td class="right aligned collapsing"></td>\
-                                    <td class="right aligned collapsing">' + bytesToSize(thisSize) + '</td>\
-                                    <td class="right aligned collapsing">' + bytesToSize(0) + '</td>\
-                                    <td class="right aligned collapsing"></td>\
-                                </tr>');
-                               continue;
-                           }
-                           for (var j =0; j < thisDisk.length; j++){
-                               var thisPartition = thisDisk[j];
-                               var mtPoint = thisPartition["mountpoint"];
-                               if (mtPoint === null){
-                                   mtPoint = "Not Mounted";
-                               }
-                               //Get the filesystem from another command return results
-                               var disksFormats = data[1]["blockdevices"][i]["children"][j];
-                               var fstype = disksFormats["fstype"];
-                               if (fstype === null){
-                                   fstype = "raw";
-                               }
-                               //console.log(disksFormats);
-                               
-                               //Read freesapce from the last command return results
-                               var freeSpacesRatio = "0%";
-                               for (var k =0; k < data[2].length; k++){
-                                   if (data[2][k][5] == thisPartition["mountpoint"]){
-                                       //This device is mounted at the same path as current partition. It should be this volume
-                                       freeSpacesRatio = data[2][k][4];
-                                   }
-                               }
-                               if (freeSpacesRatio === undefined){
-                                   freeSpacesRatio = "0%";
-                               }
-                               var numericalFreeSpace = parseInt(freeSpacesRatio.replace("%","")) * thisPartition["size"] / 100;
-                               
-                               //Print the results to the interface
-                               //console.log(thisPartition);
-                               $("#diskInfoTable").append('<tr>\
-                                    <td class="collapsing">\
-                                        <i class="disk outline icon"></i>' + thisPartition["name"] + '\
-                                    </td>\
-                                    <td class="">' +  mtPoint + '</td>\
-                                    <td class="right aligned collapsing">' + fstype + '</td>\
-                                    <td class="right aligned collapsing">' + bytesToSize(thisPartition["size"]) + '</td>\
-                                    <td class="right aligned collapsing">' + bytesToSize(numericalFreeSpace) + '</td>\
-                                    <td class="right aligned collapsing">' + freeSpacesRatio + '</td>\
-                                </tr>');
-                           }
-                           
-                       }
-                    }
-                });
-            }
-        }
-    
-        
-        function initPartitionTable(){
-             if (mode == "windows"){
-                  $.get(ao_root + "system/disk/diskmg/view?partition=true",function(data){
-                    var disks = {};
-                    for(var i =0; i < data.length; i++){
-                        var thisPart = data[i];
-                        //var diskID = thisPart[9].replace(":","");
-                        var diskID = thisPart[0].replace(/\\+.+\\/,"");
-                        if (disks == undefined || disks[diskID] == undefined){
-                            disks[diskID] = {"partitionsTotalSize":thisPart[14],"partitionNames":[thisPart[16]],"partitionID":[ thisPart[9]],"partitionVolume":[thisPart[14]],"Type":[thisPart[5]],"Mounted":[thisPart[4]=="True"],"Format":[thisPart[12]]};
-                        }else{
-                           disks[diskID]["partitionsTotalSize"] = parseInt(disks[diskID]["partitionsTotalSize"]) + parseInt(thisPart[14]);
-                           disks[diskID]["partitionVolume"].push(thisPart[14]);
-                           disks[diskID]["partitionNames"].push(thisPart[16]);
-                           disks[diskID]["partitionID"].push(thisPart[9]);
-                           disks[diskID]["Type"].push(thisPart[5]);
-                           disks[diskID]["Format"].push(thisPart[12]);
-                           disks[diskID]["Mounted"].push(thisPart[4]=="True");
-                        }
-                    }
-                    diskInformation = JSON.parse(JSON.stringify(disks));
-                    drawDartitionTable();
-                });
-                
-             }else{
-                 //This is a Linux Host
-                  $.get(ao_root + "system/disk/diskmg/view?partition=true",function(data){
-                      var disks = {};
-                      var diskInfo = data[0]["blockdevices"];
-                      for (var i =0; i < diskInfo.length; i++){
-                          let thisDisk = diskInfo[i];
-                          let diskID = thisDisk["name"];
-                          if (thisDisk["children"] === undefined || thisDisk["children"] === null){
-                              //This disk do not have any child. Assume a large read-only raw partition.
-                              let thisSize = thisDisk["size"] || 0;
-                              let thisPartitionID = thisDisk["name"] || "✖";
-                              disks[diskID] = {
-                                  "partitionsTotalSize":thisSize,
-                                  "partitionNames":[""],
-                                  "partitionID":[thisPartitionID],
-                                  "partitionVolume":[thisSize],
-                                  "Type":[thisDisk["type"]],
-                                  "Mounted":[thisDisk["mountpoint"] !== null],
-                                  "Format":["raw"]
-                                };
-                                continue;
-                             
-                          }
-                          for (var j =0; j < thisDisk["children"].length;j++){
-                            var thisPart = thisDisk["children"][j];
-                            var disksFormats = data[1]["blockdevices"][i]["children"][j];
-                            if (disks == undefined || disks[diskID] == undefined){
-                                disks[diskID] = {
-                                        "partitionsTotalSize":thisPart["size"],
-                                        "partitionNames":[thisPart["mountpoint"]],
-                                        "partitionID":[thisPart["name"]],
-                                        "partitionVolume":[thisPart["size"]],
-                                        "Type":[thisPart["type"]],
-                                        "Mounted":[thisPart["mountpoint"] != ""],
-                                        "Format":[disksFormats["fstype"]]
-                                    };
-                            }else{
-                               disks[diskID]["partitionsTotalSize"] = parseInt(disks[diskID]["partitionsTotalSize"]) + parseInt(thisPart["size"]);
-                               disks[diskID]["partitionVolume"].push(thisPart["size"]);
-                               disks[diskID]["partitionNames"].push(thisPart["mountpoint"]);
-                               disks[diskID]["partitionID"].push(thisPart["name"]);
-                               disks[diskID]["Type"].push(thisPart["type"]);
-                               disks[diskID]["Format"].push(disksFormats["fstype"]);
-                               console.log(thisPart["name"], thisPart["mountpoint"]);
-                               disks[diskID]["Mounted"].push(thisPart["mountpoint"] !== "");
-                            }
-                          }
-                      }
-                      diskInformation = JSON.parse(JSON.stringify(disks));
-                      drawDartitionTable();
-                  });
-                 
-             }
-             
-        }
-        
-        function drawDartitionTable(){
-            var disks = JSON.parse(JSON.stringify(diskInformation));
-            console.log(diskInformation);
-            //Clear the old diskpart table
-            $("#diskVisualization").html("");
-            //Render the partition table
-            var maxWidth = window.innerWidth * 0.96 - 200;
-            var maxCapDisk = -1;
-            var keys = [];
-            for (key in disks){
-                keys.push(key);
-                var thisDiskSize = disks[key]["partitionsTotalSize"];
-                if (thisDiskSize > maxCapDisk){
-                    maxCapDisk = parseInt(thisDiskSize);
-                }
-            }
-            
-            keys.sort();
-            for (var i =0; i < keys.length; i++){
-                var diskInfo = disks[keys[i]];
-                var diskID = keys[i];
-                var mountState = "Mounted";
-                var shortenType = diskInfo["Type"][0].split(" ").shift();
-                var thisMaxWidth = maxWidth - (1- (diskInfo["partitionsTotalSize"] / maxCapDisk)) * (window.innerWidth * displayScaleRatio);
-                if (diskInfo["Mounted"].length == 1 && diskInfo["Mounted"][0] == false){
-                    mountState = "Unmounted";
-                }else if(diskInfo["Mounted"].length > 1){
-                    mountState = "Mixed";
-                }
-                console.log(diskID,diskInfo["Mounted"]);
-                //Append the disk info block
-                $("#diskVisualization").append('<div class="diskPartTable">');
-                $("#diskVisualization").append('<div class="sideblock">\
-                    <i class="disk outline icon" style="margin-right:0px;font-weight: bold;"></i>\
-                    <b style="font-weight: bold;">Drive ' + i + '</b><br>\
-                    ' + shortenType + '<br>\
-                    ' + bytesToSize(diskInfo["partitionsTotalSize"]) + '<br>\
-                    ' + mountState + '\
-                </div>');
-                var partitionIDs = diskInfo["partitionID"];
-                for (var k =0; k < partitionIDs.length; k++){
-                    var thisWidth = thisMaxWidth * (parseInt(diskInfo["partitionVolume"][k]) / diskInfo["partitionsTotalSize"]);
-                    var topbarExtraClass = "";
-                    if (diskInfo["partitionVolume"][k] == 0){
-                        topbarExtraClass = " unallocate";
-                    }else if (diskInfo["partitionNames"][k] == "" && mode == "linux"){
-                        topbarExtraClass = " unmounted";
-                        diskInfo["partitionNames"][k] = "Not Mounted";
-                        if (diskInfo.Format.length == 1 && diskInfo.Format[0] == "raw"){
-                            topbarExtraClass = " unallocate"
-                            diskInfo["partitionNames"][k] = "Unallocated / Unpartitioned";
-                        }
-                    }else if (diskInfo["partitionNames"][k] == "" && mode == "windows"){
-                        //All viewable disks on Windows must be mounted
-                        if (diskInfo["partitionID"][0] == "C:"){
-                            diskInfo["partitionNames"][k] = "Primary Disk";
-                        }else{
-                            diskInfo["partitionNames"][k] = "Local Disk";
-                        }
-                    }
-                    
-                    $("#diskVisualization").append('<div class="partitionRepresentation" style="width:' + thisWidth + 'px;" metaData="\
-                    ' + ao_module_utils.objectToAttr([diskInfo["partitionID"][k],diskInfo["partitionNames"][k],diskInfo["Format"][k],diskInfo["Mounted"][k],diskInfo["Type"][k],diskInfo["partitionVolume"][k]]) + '">\
-                        <div class="partitionTopBar' + topbarExtraClass + '"></div>\
-                        <div class="partitionDescription">\
-                            ' + diskInfo["partitionNames"][k] +" (" + diskInfo["partitionID"][k] + ')<br>\
-                            ' + bytesToSize(parseInt(diskInfo["partitionVolume"][k])) + ' ' + diskInfo["Format"][k] + '<br>\
-                        </div>\
-                    </div>');
-                }
-                $("#diskVisualization").append('</div>');
-            }
-            
-            setTimeout(function(){
-                adjustPartitionViewHeight();
-            },500);
-            createEventHooks();
-        }
-        
-        $(window).on("resize",function(){
-            adjustPartitionViewHeight();
-            drawDartitionTable();
-        });
-        
-        $("#diskVisualization").on('click',function(e){
-           var target = e.target;
-           //console.log($(target).parents(".partitionRepresentation"));
-           if ($(target).parents(".partitionRepresentation").length == 0 && !$(target).hasClass("partitionRepresentation")){
-               $("#rightClickMenu").hide();
-           }else if (e.button == 0){
-               if ($(target).parents(".partitionRepresentation").length > 0 || $(target).hasClass("partitionRepresentation")){
-                   $(".focusedPart").removeClass("focusedPart");
-                   if ($(target).parent().hasClass("partitionRepresentation")){
-                       $(target).parent().addClass("focusedPart");
-                   }else{
-                       $(target).addClass("focusedPart");
-                   }
-               }
-               $("#rightClickMenu").hide();
-           }
-        });
-        
-        function bytesToSize(bytes) {
-            if (viewMode == "human"){
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                 if (bytes == 0) return '0 Byte';
-                 var i = parseFloat(Math.floor(Math.log(bytes) / Math.log(1024)));
-                 return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
-            }else if (viewMode == "raw"){
-                return bytes + " B";
-            }
-           
-        }
-    </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+        <script src="../../script/jquery.min.js"></script>
+        <script type='text/javascript' src="../../semantic/semantic.min.js"></script>
+        <script src="../../script/ao_module.js"></script>
+        <title>Diskmg</title>
+        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+        <style>
+            body{
+                height: 100%;
+            }
+            .customFitted.item{
+                padding-top:5px !important;
+                padding-bottom:5px !important;
+            }
+            #diskListTable{
+                max-height:250px !important;
+                overflow-y: auto;
+            }
+            #diskVisualization{
+                overflow-y:auto;
+            }
+            .diskPartTable{
+                width:100%;
+                border-bottom:1px solid #9c9c9c;
+                overflow-x:hidden;
+            }
+            .sideblock{
+                background-color:#f0f0f0;
+                height:100px;
+                width:100px;
+                padding:8px;
+                /*
+                    border-right:1px solid #9c9c9c;
+                */
+                
+                font-size:90%;
+                display:inline-block;
+            }
+            .partitionRepresentation{
+                border:1px solid #e8e8e8;
+                display:inline-block;
+                height:100px;
+                vertical-align: top;
+                overflow:hidden;
+                /*
+                    border-left:1px solid #6e6e6e;
+                */
+                
+                cursor:pointer;
+            }
+            .partitionTopBar{
+                background-color:#4287f5;
+                width:100%;
+                height:15px;
+                margin-bottom:3px;
+            }
+            .partitionTopBar.unallocate{
+                 background-color:#1f1f1f;
+            }
+            .partitionTopBar.unmounted{
+                 background-color:#ab8a29;
+            }
+            .partitionDescription{
+                padding-left:8px;
+                padding:3px;
+            }
+            #rightClickMenu{
+                position:absolute;
+            }
+            .selectable{
+                cursor: pointer;
+            }
+            .selectable:hover{
+                background-color:#f0f0f0 !important;
+            }
+            .focusedPart{
+                background-color: #e3f0ff;
+            }
+            .disabled{
+                background-color:#e6e6e6;
+                color:#787878 !important;
+                cursor:no-drop !important;
+            }
+            .funcmenu{
+                position:fixed;
+                top:10%;
+                right:20%;
+                left:20%;
+                bottom:10%;
+                overflow-y:auto;
+                z-index:100;
+                background-color:#f7f7f7;
+                padding:12px;
+                display:none;
+                border: 1px solid #9c9c9c;
+            }
+            .functMenuDimmer{
+                z-index:90;
+                position:absolute;
+                width:100%;
+                height:100%;
+                left:0px;
+                top:0px;
+                background:rgba(48,48,48,0.5);
+                display:none;
+            }
+            .funcmenuBottom{
+                position:absolute;
+                width:100%;
+                bottom:0px;
+                left:0px;
+                padding:12px;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="diskListTable" >
+            <table class="ui celled striped unstackable table">
+                <thead>
+                    <tr>
+                        <th>
+                            Volume
+                        </th>
+                        <th class="windowsonly" style="display:none;">
+                            Name
+                        </th>
+                        <th class="windowsonly" style="display:none;">
+                            Type
+                        </th>
+                        <th class="linuxonly" style="display:none;">
+                            Mount Point
+                        </th>
+                        <th>
+                            File System
+                        </th>
+                        <th>
+                            Capacity
+                        </th>
+                        <th class="windowsonly" style="display:none;">
+                            Free Space
+                        </th>
+                        <th class="windowsonly" style="display:none;">
+                            % Free
+                        </th>
+                        <th class="linuxonly" style="display:none;">
+                            Used Space
+                        </th>
+                        <th class="linuxonly" style="display:none;">
+                            % Used
+                        </th>
+                    </tr>
+                </thead>
+                <tbody id="diskInfoTable">
+                    <tr>
+                        <td class="collapsing">
+                            <i class="disk outline icon"></i>/dev/sda1
+                        </td>
+                        <td class="collapsing">/media/storage1</td>
+                        <td class="right aligned collapsing">NTFS</td>
+                        <td class="right aligned collapsing">64 GB</td>
+                        <td class="right aligned collapsing">12.5 GB</td>
+                        <td class="right aligned collapsing">19.7%</td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        <div id="diskVisualization">
+            <div class="diskPartTable">
+                <div class="sideblock">
+                    <i class="disk outline icon" style="margin-right:0px;font-weight: bold;"></i>
+                    <b style="font-weight: bold;">Drive 0</b><br>
+                    N/A
+                </div><div class="partitionRepresentation" style="width:calc(100% - 150px);">
+                    <div class="partitionTopBar"></div>
+                    <div class="partitionDescription">
+                       Connecting to Virtual Disk Services
+                    </div>
+                </div>
+            </div>
+        </div>
+    <div id="rightClickMenu" class="ui vertical menu" style="display: none;">
+        <div id="formatDisk" class="item selectable" onClick="toggleFormatInterface(this);">
+            <i class="disk outline icon"></i> Format Disk
+        </div>
+        <div id="mtbtn" class="item selectable" onClick="toggleMount(this);">
+            <i class="usb icon"></i> Mount
+        </div>
+    </div>
+    <!-- Sections for functional menus-->
+    <div id="formatOptions" class="funcmenu">
+        <h2 class="ui header">
+            <i class="red exclamation circle icon"></i>
+            <div class="content">
+                Format Disk
+                <div class="sub header" style="font-weight:120%;color:red;">Warning! The format process will wipe all data from the selected partition.</div>
+            </div>
+        </h2>
+        <div class="ui red message">
+            <p>Format on any drive or partition will wipe all data within that drive or partition. Please make sure you have backup all necessary files and your drive / partition selection is correct.</p>
+        </div>
+        <div class="ui header">
+            <i class="disk outline icon"></i>
+            <div id="selectedDiskDisplay" class="content">
+                /dev/sda1 (120 GB)
+            </div>
+        </div>
+        <div class="ui form">
+            <div class="field">
+                <label>Target File System Format</label>
+                <div class="ui checkboxes">
+                    <div class="ui radio checkbox">
+                        <input id="ext4" type="radio" name="format" checked>
+                        <label for="ext4">EXT4</label>
+                    </div>
+                    <div class="ui radio checkbox">
+                        <input id="ntfs" type="radio" name="format">
+                        <label for="ntfs">NTFS</label>
+                    </div>
+                    <div class="ui radio checkbox">
+                        <input id="vfat" type="radio" name="format">
+                        <label for="vfat">VFAT</label>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="funcmenuBottom" align="right">
+            <button class="ui tiny negative button" onClick="formatThisDev();">Format</button>
+            <button class="ui tiny button" onClick="hideAllFuncMenu();">Close</button>
+        </div>
+    </div>
+    <div id="mountOptions" class="funcmenu">
+        <div class="ui header">
+            Disk Mount
+            <div class="sub header">Select a mount point for this device</div>
+        </div>
+        <div id="mtptlist" class="ui segmented list" style="max-height:300px;overflow-y:auto;">
+           
+            
+   
+            <div class="mountpt item selectable userdefine" onclick="selectThis(this);">
+                <p>User defined mount point</p>
+                <div class="ui fluid mini input">
+                    <input id="userDefinedMountPoint" type="text" placeholder="/">
+                </div>
+            </div>
+        </div>
+        
+        <div class="funcmenuBottom" align="right">
+            <button class="ui tiny primary button" onClick="mountThisDev();">Mount</button>
+            <button class="ui tiny button" onClick="hideAllFuncMenu();">Close</button>
+        </div>
+    </div>
+    
+    <!-- dimmers-->
+     <div id="loaderUI" class="ui active dimmer" style="display:none;">
+        <div class="ui text loader">Waiting for System Response<br>Do Not Close This Tab</div>
+    </div>
+    <div class="functMenuDimmer" onClick="hideAllFuncMenu();">
+        
+    </div>
+    <script>
+        var mode = "linux";
+        var viewMode = "human"; //Accept {human / raw}
+        var diskInformation; //Tmp variable for holding disk return results
+        var displayScaleRatio = 0.1; //Maxium differential ratio, default 0.3, it means the minium disk will show as 70% screen width
+        var fwmode = false;
+        var formatPendingDevInfo;
+        
+      
+        //Updates Nov 2020, added platform detection
+        $.get(ao_root + "system/disk/diskmg/platform", function(data){
+            if (data == "windows"){
+                mode = "windows";
+            }else{
+                mode = "linux"
+            }
+            //Init data loading process
+            initView();
+            initPartitionTable();
+            initMountPointList();
+        })
+     
+        
+        //Mount pt selection interface
+        $(".mountpt").on('click',function(e){
+            $(".selected").removeClass("selected");
+            $(this).addClass("selected");
+        });
+        
+        function hideAllFuncMenu(){
+            $(".funcmenu").fadeOut('fast');
+            $(".functMenuDimmer").fadeOut('fast');
+        }
+
+        function initMountPointList(){
+            $.get(ao_root + "system/disk/diskmg/mpt", function(data){
+                if (data == null){
+                    //On Windows
+                    return
+                }
+                data.forEach(mpt => {
+                    $("#mtptlist").prepend(`<div class="mountpt selectable item" style="cursor:pointer;" onclick="selectThis(this);" ondblclick="mountThisDev(this)">${mpt}</div>`)
+                });
+                
+            });
+        }
+
+        function selectThis(object){
+            $(".selected.selectable.item").removeClass('selected');
+            $(object).addClass("selected");
+        }
+        
+        function formatThisDev(){
+            var targetFormat = $("input[name='format']:checked").attr("id");
+            var targetDisk = formatPendingDevInfo;
+            console.log("Formating Disk: " + targetDisk + " to " + targetFormat);
+            if(targetFormat){
+                $("#loaderUI").show();
+                if (confirm("THIS OPERATION WILL WIPE ALL DATA ON /dev/" + targetDisk[0] + ". ARE YOU SURE?")){
+                    $("#formatOptions").fadeOut('fast');
+                    $(".functMenuDimmer").fadeOut('fast');
+                    console.log(ao_root + "system/disk/diskmg/format?dev=" + targetDisk[0] + "&format=" + targetFormat);
+                    $.ajax({
+                        url: ao_root + "system/disk/diskmg/format",
+                        data: {"dev": targetDisk[0], "format": targetFormat},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error !== undefined){
+                                alert(data.error);
+                            }
+                            initView();
+                            initPartitionTable();
+                            $("#loaderUI").hide();
+                        }
+                    });
+                }else{
+                    $("#loaderUI").hide();
+                }
+            }
+        }
+        
+        function toggleFormatInterface(btnObject){
+            if ($(btnObject).hasClass("disabled") == true){
+                return;
+            }
+            $("#formatOptions").fadeIn('fast');
+            $(".functMenuDimmer").fadeIn('fast');
+            hideRightclickMenu();
+            var diskInfo = $(".focusedPart").attr("metadata");
+            diskInfo = ao_module_utils.attrToObject(diskInfo);
+            formatPendingDevInfo = diskInfo;
+            $("#selectedDiskDisplay").text(diskInfo[0] + " (" + bytesToSize(parseInt(diskInfo[5])) + ") ");
+        }
+        
+        function mountThisDev(object=null){
+            if (object !== null && !$(object).hasClass(".selected.item")){
+                $(".selected").removeClass("selected");
+                $(object).addClass("selected");
+            }
+            var selectedMpt = $(".selected.item");
+            var mountPoint = $(selectedMpt).text().trim();
+            if (selectedMpt.hasClass("userdefine")){
+                var mountPoint = $("#userDefinedMountPoint").val();
+            }
+             $("#loaderUI").show();
+             $("#mountOptions").fadeOut('fast');
+             $(".functMenuDimmer").fadeOut('fast');
+             var diskInfo = $(".focusedPart").attr("metadata");
+             diskInfo = ao_module_utils.attrToObject(diskInfo);
+             $.get(ao_root + "system/disk/diskmg/mount?dev=" + diskInfo[0] + "&format=" + diskInfo[2] + "&mnt=" + mountPoint,function(data){
+                if (data.error != undefined){
+                    $("#loaderUI").hide();
+                    alert(data.error);
+                    return;
+                }
+                //Reload the UI
+                initView();
+                initPartitionTable();
+                $("#loaderUI").hide();
+            });
+        }
+        
+        function toggleMount(btnObject){
+            if ($(btnObject).hasClass("disabled") == true){
+                return;
+            }
+            var diskInfo = $(".focusedPart").attr("metadata");
+            diskInfo = ao_module_utils.attrToObject(diskInfo);
+            if (diskInfo[3] == false){
+                //Mount disk
+                $("#mountOptions").fadeIn('fast');
+                $(".functMenuDimmer").fadeIn('fast');
+                
+            }else{
+                //Unmount disk
+                var dev = diskInfo[0];
+                var mnt = diskInfo[1];
+                var format = diskInfo[2];
+                hideRightclickMenu();
+                $("#loaderUI").show();
+                $.get(ao_root + "system/disk/diskmg/mount?dev=" + dev + "&format=" + format + "&mnt=" + mnt + "&umount=true",function(data){
+                    console.log(data);
+                    //Reload the UI
+                     initView();
+                     initPartitionTable();
+                     $("#loaderUI").hide();
+                });
+            }
+            hideRightclickMenu();
+        }
+        
+        function hideRightclickMenu(){
+            $("#rightClickMenu").hide();
+        }
+
+        /*
+        function openInFileExplorer(btnObject){
+            if ($(btnObject).hasClass('disabled')){
+                return;
+            }
+            var diskInfo = $(".focusedPart").attr("metadata");
+            diskInfo = ao_module_utils.attrToObject(diskInfo);
+            if (diskInfo[3] == true){
+                //This disk is mounted
+                var uid = Date.now();
+                if (fwmode){
+                    ao_module_newfw("SystemAOB/functions/file_system/index.php?controlLv=2&dir=" + diskInfo[1],"Loading", "folder open outline",uid,1080,580,undefined,undefined,true,true);
+                }else{
+                    window.open("../../functions/file_system/index.php?controlLv=2&dir=" + diskInfo[1]);
+                }
+            }
+            hideRightclickMenu();
+        }
+        */
+        
+        function createEventHooks(){
+            $(".partitionRepresentation").contextmenu(function(e){
+                if (mode == "windows"){
+                    //Switch back to normal menu when under window mode
+                    return true;
+                }
+                var px = e.clientX;
+                var py = e.clientY;
+                var top = py - $("#rightClickMenu").height();
+                if (ao_module_virtualDesktop){
+                    top -= 50;
+                }
+                $("#rightClickMenu").css({"left": px + "px", "top":  top + "px"});
+                $("#rightClickMenu").show();
+                console.log(e.target);
+                $(".focusedPart").removeClass("focusedPart");
+                var partbody =  $(e.target);
+                if ($(e.target).parent().hasClass("partitionRepresentation")){
+                    //Clicked on the child instead.
+                    $(e.target).parent().addClass("focusedPart");
+                    partbody =  $(e.target).parent();
+                }else{
+                    //Click on the representation body.
+                    $(e.target).addClass("focusedPart");
+                }
+                
+                //Create a custom context menu for the operation
+                var partInfo = ao_module_utils.attrToObject(partbody.attr("metadata"));
+                console.log(partInfo);
+                if (partInfo[3] == true){
+                    //This disk is mounted. Provide unmount btn
+                    if (partInfo[1] == "/" || partInfo[1] == "/boot"){
+                        //No, you can't unmount root nor format it
+                        $("#mtbtn").addClass("disabled");
+                        $("#formatDisk").addClass("disabled");
+                    }else{
+                        $("#mtbtn").removeClass("disabled");
+                        $("#formatDisk").removeClass("disabled");
+                    }
+                    $("#mtbtn").html('<i class="usb icon"></i> Unmount');
+                    if (partInfo[1].substring(0,6) == "/media"){
+                         //This can be opened
+                         $("#openbtn").removeClass("disabled");
+                    }else{
+                         $("#openbtn").addClass("disabled");
+                    }
+                }else{
+                    //This disk is not mounted. Provide mount btn
+                    $("#mtbtn").html('<i class="usb icon"></i> Mount Drive');
+                    $("#openbtn").addClass("disabled");
+                    $("#mtbtn").removeClass("disabled");
+                    $("#formatDisk").removeClass("disabled");
+                }
+                //Prevent browser menu from showing
+                return false;
+            });
+        }
+        
+        function adjustPartitionViewHeight(){
+            $("#diskVisualization").css("height",window.innerHeight - $("#diskListTable").height() - 120 + "px");
+        }
+        
+        function initView(){
+            if (mode == "windows"){
+                $(".windowsonly").show();
+                //Runing on top of Window Host
+                $.get(ao_root + "system/disk/diskmg/view",function(data){
+                    $("#diskInfoTable").html("");
+                    if (data.error == undefined){
+                       for (var i = 0; i < data.length; i++){
+                           var thisDisk = data[i];
+                           var driveName = thisDisk[2];
+                           if (thisDisk[2] == "" && thisDisk[0] == "C:\\"){
+                                driveName = "Primary Drive";
+                           }else if (thisDisk[2] == ""){
+                                driveName = "Local Disk";
+                           }else if (driveName == undefined){
+                                driveName = "No Media"
+                           }
+
+                           if (thisDisk[3] == undefined){
+                               //Unknown File System
+                               thisDisk[3] = "N/A"
+                           }
+
+                           var cap = bytesToSize(thisDisk[6]);
+                           if (thisDisk[6] == undefined){
+                                cap =  "N/A";
+                           }
+
+                           var free = bytesToSize(thisDisk[5]);
+                           if (thisDisk[5] == undefined){
+                               free = "N/A";
+                           }
+
+                           var perc = Math.round(thisDisk[5] / thisDisk[6] * 100);
+                           if (isNaN(perc)){
+                               perc = "0";
+                           }
+
+                           $("#diskInfoTable").append('<tr>\
+                                <td class="collapsing">\
+                                    <i class="disk outline icon"></i>' + thisDisk[0] + '\
+                                </td>\
+                                <td class="">' + driveName + '</td>\
+                                <td class="collapsing">' + thisDisk[1] + '</td>\
+                                <td class="right aligned collapsing">' + thisDisk[3] + '</td>\
+                                <td class="right aligned collapsing">' + cap + '</td>\
+                                <td class="right aligned collapsing">' + free + '</td>\
+                                <td class="right aligned collapsing">' + perc + '%</td>\
+                            </tr>');
+                       }
+                    }
+                });
+            }else{
+                //Runing on top of Linux Host
+                $(".linuxonly").show();
+                $.get(ao_root + "system/disk/diskmg/view",function(data){
+                    $("#diskInfoTable").html("");
+                    if (data.error == undefined){
+                        var disks = data[0]["blockdevices"];
+                        var partitions = data[1];
+                       for (var i = 0; i < disks.length; i++){
+                           var thisDisk = disks[i]["children"];
+                           if (thisDisk === undefined || thisDisk === null){
+                                let thisSize = disks[i]["size"] || 0;
+                                let thisPartitionID = disks[i]["name"] || "✖";
+                                let mountPoint = disks[i]["mountpoint"] || "[NO PARTITION]";
+                                $("#diskInfoTable").append('<tr>\
+                                    <td class="collapsing">\
+                                        <i class="disk outline icon"></i>' + thisPartitionID + '\
+                                    </td>\
+                                    <td class="">' +  mountPoint + '</td>\
+                                    <td class="right aligned collapsing"></td>\
+                                    <td class="right aligned collapsing">' + bytesToSize(thisSize) + '</td>\
+                                    <td class="right aligned collapsing">' + bytesToSize(0) + '</td>\
+                                    <td class="right aligned collapsing"></td>\
+                                </tr>');
+                               continue;
+                           }
+                           for (var j =0; j < thisDisk.length; j++){
+                               var thisPartition = thisDisk[j];
+                               var mtPoint = thisPartition["mountpoint"];
+                               if (mtPoint === null){
+                                   mtPoint = "Not Mounted";
+                               }
+                               //Get the filesystem from another command return results
+                               var disksFormats = data[1]["blockdevices"][i]["children"][j];
+                               var fstype = disksFormats["fstype"];
+                               if (fstype === null){
+                                   fstype = "raw";
+                               }
+                               //console.log(disksFormats);
+                               
+                               //Read freesapce from the last command return results
+                               var freeSpacesRatio = "0%";
+                               for (var k =0; k < data[2].length; k++){
+                                   if (data[2][k][5] == thisPartition["mountpoint"]){
+                                       //This device is mounted at the same path as current partition. It should be this volume
+                                       freeSpacesRatio = data[2][k][4];
+                                   }
+                               }
+                               if (freeSpacesRatio === undefined){
+                                   freeSpacesRatio = "0%";
+                               }
+                               var numericalFreeSpace = parseInt(freeSpacesRatio.replace("%","")) * thisPartition["size"] / 100;
+                               
+                               //Print the results to the interface
+                               //console.log(thisPartition);
+                               $("#diskInfoTable").append('<tr>\
+                                    <td class="collapsing">\
+                                        <i class="disk outline icon"></i>' + thisPartition["name"] + '\
+                                    </td>\
+                                    <td class="">' +  mtPoint + '</td>\
+                                    <td class="right aligned collapsing">' + fstype + '</td>\
+                                    <td class="right aligned collapsing">' + bytesToSize(thisPartition["size"]) + '</td>\
+                                    <td class="right aligned collapsing">' + bytesToSize(numericalFreeSpace) + '</td>\
+                                    <td class="right aligned collapsing">' + freeSpacesRatio + '</td>\
+                                </tr>');
+                           }
+                           
+                       }
+                    }
+                });
+            }
+        }
+    
+        
+        function initPartitionTable(){
+             if (mode == "windows"){
+                  $.get(ao_root + "system/disk/diskmg/view?partition=true",function(data){
+                    var disks = {};
+                    for(var i =0; i < data.length; i++){
+                        var thisPart = data[i];
+                        //var diskID = thisPart[9].replace(":","");
+                        var diskID = thisPart[0].replace(/\\+.+\\/,"");
+                        if (disks == undefined || disks[diskID] == undefined){
+                            disks[diskID] = {"partitionsTotalSize":thisPart[14],"partitionNames":[thisPart[16]],"partitionID":[ thisPart[9]],"partitionVolume":[thisPart[14]],"Type":[thisPart[5]],"Mounted":[thisPart[4]=="True"],"Format":[thisPart[12]]};
+                        }else{
+                           disks[diskID]["partitionsTotalSize"] = parseInt(disks[diskID]["partitionsTotalSize"]) + parseInt(thisPart[14]);
+                           disks[diskID]["partitionVolume"].push(thisPart[14]);
+                           disks[diskID]["partitionNames"].push(thisPart[16]);
+                           disks[diskID]["partitionID"].push(thisPart[9]);
+                           disks[diskID]["Type"].push(thisPart[5]);
+                           disks[diskID]["Format"].push(thisPart[12]);
+                           disks[diskID]["Mounted"].push(thisPart[4]=="True");
+                        }
+                    }
+                    diskInformation = JSON.parse(JSON.stringify(disks));
+                    drawDartitionTable();
+                });
+                
+             }else{
+                 //This is a Linux Host
+                  $.get(ao_root + "system/disk/diskmg/view?partition=true",function(data){
+                      var disks = {};
+                      var diskInfo = data[0]["blockdevices"];
+                      for (var i =0; i < diskInfo.length; i++){
+                          let thisDisk = diskInfo[i];
+                          let diskID = thisDisk["name"];
+                          if (thisDisk["children"] === undefined || thisDisk["children"] === null){
+                              //This disk do not have any child. Assume a large read-only raw partition.
+                              let thisSize = thisDisk["size"] || 0;
+                              let thisPartitionID = thisDisk["name"] || "✖";
+                              disks[diskID] = {
+                                  "partitionsTotalSize":thisSize,
+                                  "partitionNames":[""],
+                                  "partitionID":[thisPartitionID],
+                                  "partitionVolume":[thisSize],
+                                  "Type":[thisDisk["type"]],
+                                  "Mounted":[thisDisk["mountpoint"] !== null],
+                                  "Format":["raw"]
+                                };
+                                continue;
+                             
+                          }
+                          for (var j =0; j < thisDisk["children"].length;j++){
+                            var thisPart = thisDisk["children"][j];
+                            var disksFormats = data[1]["blockdevices"][i]["children"][j];
+                            if (disks == undefined || disks[diskID] == undefined){
+                                disks[diskID] = {
+                                        "partitionsTotalSize":thisPart["size"],
+                                        "partitionNames":[thisPart["mountpoint"]],
+                                        "partitionID":[thisPart["name"]],
+                                        "partitionVolume":[thisPart["size"]],
+                                        "Type":[thisPart["type"]],
+                                        "Mounted":[thisPart["mountpoint"] != ""],
+                                        "Format":[disksFormats["fstype"]]
+                                    };
+                            }else{
+                               disks[diskID]["partitionsTotalSize"] = parseInt(disks[diskID]["partitionsTotalSize"]) + parseInt(thisPart["size"]);
+                               disks[diskID]["partitionVolume"].push(thisPart["size"]);
+                               disks[diskID]["partitionNames"].push(thisPart["mountpoint"]);
+                               disks[diskID]["partitionID"].push(thisPart["name"]);
+                               disks[diskID]["Type"].push(thisPart["type"]);
+                               disks[diskID]["Format"].push(disksFormats["fstype"]);
+                               console.log(thisPart["name"], thisPart["mountpoint"]);
+                               disks[diskID]["Mounted"].push(thisPart["mountpoint"] !== "");
+                            }
+                          }
+                      }
+                      diskInformation = JSON.parse(JSON.stringify(disks));
+                      drawDartitionTable();
+                  });
+                 
+             }
+             
+        }
+        
+        function drawDartitionTable(){
+            var disks = JSON.parse(JSON.stringify(diskInformation));
+            console.log(diskInformation);
+            //Clear the old diskpart table
+            $("#diskVisualization").html("");
+            //Render the partition table
+            var maxWidth = window.innerWidth * 0.96 - 200;
+            var maxCapDisk = -1;
+            var keys = [];
+            for (key in disks){
+                keys.push(key);
+                var thisDiskSize = disks[key]["partitionsTotalSize"];
+                if (thisDiskSize > maxCapDisk){
+                    maxCapDisk = parseInt(thisDiskSize);
+                }
+            }
+            
+            keys.sort();
+            for (var i =0; i < keys.length; i++){
+                var diskInfo = disks[keys[i]];
+                var diskID = keys[i];
+                var mountState = "Mounted";
+                var shortenType = diskInfo["Type"][0].split(" ").shift();
+                var thisMaxWidth = maxWidth - (1- (diskInfo["partitionsTotalSize"] / maxCapDisk)) * (window.innerWidth * displayScaleRatio);
+                if (diskInfo["Mounted"].length == 1 && diskInfo["Mounted"][0] == false){
+                    mountState = "Unmounted";
+                }else if(diskInfo["Mounted"].length > 1){
+                    mountState = "Mixed";
+                }
+                console.log(diskID,diskInfo["Mounted"]);
+                //Append the disk info block
+                $("#diskVisualization").append('<div class="diskPartTable">');
+                $("#diskVisualization").append('<div class="sideblock">\
+                    <i class="disk outline icon" style="margin-right:0px;font-weight: bold;"></i>\
+                    <b style="font-weight: bold;">Drive ' + i + '</b><br>\
+                    ' + shortenType + '<br>\
+                    ' + bytesToSize(diskInfo["partitionsTotalSize"]) + '<br>\
+                    ' + mountState + '\
+                </div>');
+                var partitionIDs = diskInfo["partitionID"];
+                for (var k =0; k < partitionIDs.length; k++){
+                    var thisWidth = thisMaxWidth * (parseInt(diskInfo["partitionVolume"][k]) / diskInfo["partitionsTotalSize"]);
+                    var topbarExtraClass = "";
+                    if (diskInfo["partitionVolume"][k] == 0){
+                        topbarExtraClass = " unallocate";
+                    }else if (diskInfo["partitionNames"][k] == "" && mode == "linux"){
+                        topbarExtraClass = " unmounted";
+                        diskInfo["partitionNames"][k] = "Not Mounted";
+                        if (diskInfo.Format.length == 1 && diskInfo.Format[0] == "raw"){
+                            topbarExtraClass = " unallocate"
+                            diskInfo["partitionNames"][k] = "Unallocated / Unpartitioned";
+                        }
+                    }else if (diskInfo["partitionNames"][k] == "" && mode == "windows"){
+                        //All viewable disks on Windows must be mounted
+                        if (diskInfo["partitionID"][0] == "C:"){
+                            diskInfo["partitionNames"][k] = "Primary Disk";
+                        }else{
+                            diskInfo["partitionNames"][k] = "Local Disk";
+                        }
+                    }
+                    
+                    $("#diskVisualization").append('<div class="partitionRepresentation" style="width:' + thisWidth + 'px;" metaData="\
+                    ' + ao_module_utils.objectToAttr([diskInfo["partitionID"][k],diskInfo["partitionNames"][k],diskInfo["Format"][k],diskInfo["Mounted"][k],diskInfo["Type"][k],diskInfo["partitionVolume"][k]]) + '">\
+                        <div class="partitionTopBar' + topbarExtraClass + '"></div>\
+                        <div class="partitionDescription">\
+                            ' + diskInfo["partitionNames"][k] +" (" + diskInfo["partitionID"][k] + ')<br>\
+                            ' + bytesToSize(parseInt(diskInfo["partitionVolume"][k])) + ' ' + diskInfo["Format"][k] + '<br>\
+                        </div>\
+                    </div>');
+                }
+                $("#diskVisualization").append('</div>');
+            }
+            
+            setTimeout(function(){
+                adjustPartitionViewHeight();
+            },500);
+            createEventHooks();
+        }
+        
+        $(window).on("resize",function(){
+            adjustPartitionViewHeight();
+            drawDartitionTable();
+        });
+        
+        $("#diskVisualization").on('click',function(e){
+           var target = e.target;
+           //console.log($(target).parents(".partitionRepresentation"));
+           if ($(target).parents(".partitionRepresentation").length == 0 && !$(target).hasClass("partitionRepresentation")){
+               $("#rightClickMenu").hide();
+           }else if (e.button == 0){
+               if ($(target).parents(".partitionRepresentation").length > 0 || $(target).hasClass("partitionRepresentation")){
+                   $(".focusedPart").removeClass("focusedPart");
+                   if ($(target).parent().hasClass("partitionRepresentation")){
+                       $(target).parent().addClass("focusedPart");
+                   }else{
+                       $(target).addClass("focusedPart");
+                   }
+               }
+               $("#rightClickMenu").hide();
+           }
+        });
+        
+        function bytesToSize(bytes) {
+            if (viewMode == "human"){
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+                 if (bytes == 0) return '0 Byte';
+                 var i = parseFloat(Math.floor(Math.log(bytes) / Math.log(1024)));
+                 return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
+            }else if (viewMode == "raw"){
+                return bytes + " B";
+            }
+           
+        }
+    </script>
+    </body>
 </html>

+ 159 - 158
web/SystemAO/disk/quota/manage.html

@@ -1,159 +1,160 @@
-<html>
-    <head>
-        <title>Manage Storage Quota</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <style>
-
-        </style>
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="hdd icon"></i>
-                    <div class="content">
-                        Storage Quota Settings
-                        <div class="sub header">Limit each user groups storage quota</div>
-                    </div>
-                </div>
-            </div>
-            <div id="oprsucc" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i> Quota Updated</div>
-            <div id="oprfailed" style="display:none;" class="ui red inverted segment"><i class="remove icon"></i> Error occured while tring to update storage quota</div>
-            <div id="updateQuotaInterface" class="ui segment" style="display:none;">
-                <p>Group to be Updated: </p>
-                <div class="ui fluid tiny input">
-                    <input id="editingGroup" type="text" readonly="true" value="">
-                </div>
-                <br>
-                <p>New Quota: </p>
-                <div class="ui tiny input">
-                    <input id="newQuotaValue" type="number" value="15">
-                </div>
-                <div class="ui selection tiny dropdown">
-                    <input id="newquota" type="hidden" name="size">
-                    <i class="dropdown icon"></i>
-                    <div class="default text" >GB</div>
-                    <div class="menu">
-                        <div class="item" data-value="1">MB</div>
-                        <div class="item" data-value="1024">GB</div>
-                        <div class="item" data-value="1048576">TB</div>
-                    </div>
-                  </div>
-                  <div class="ui divider"></div>
-                  <div style="width:100%;" align="right">
-                    <button class="ui button" onclick="$('#updateQuotaInterface').transition('fade down');">Cancel</button>
-                    <button class="ui primary button" onclick="confirmUpdate();"><i class="checkmark icon"></i> Confirm</button>
-                  </div>
-            </div>
-            <table class="ui celled striped table">
-                <thead>
-                    <tr>
-                        <th colspan="3">
-                        Current Storage Quota
-                        </th>
-                    </tr>
-                    <tr>
-                        <th>User Group</th>
-                        <th>Storage Quota</th>
-                        <th>Edit Quota</th>
-                    </tr>
-                </thead>
-                <tbody id="quotaTable">
-                    <tr>
-                    <td colspan="3">Initializing Storage Quota Table</td>
-                    </tr>
-                    
-                </tbody>
-              </table>
-              <p><i class="info circle icon"></i> All users in the given group will have the same storage quota. If you want to create a special storage quota for a given user, please create a new group.</p>
-        </div>
-        <script>
-            var usergroup = "";
-            $('.ui.dropdown').dropdown();
-
-            initQuotaTable();
-            function initQuotaTable(){
-                $("#quotaTable").html("Loading...");
-                $.get("../../system/disk/quota/listQuota",function(data){
-                    $("#quotaTable").html("");
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        for (const [key, value] of Object.entries(data)) {
-                            var quota = "Read Only";
-                            if (value == -1){
-                                quota = "Unlimited";
-                            }else if (value > 0){
-                                quota = formatBytes(value);
-                            }
-
-                            var updateButton = `<button class="ui teal tiny button" group="${key}" onclick="updateGroupQuota(this);">Update</button>`;
-                            if (key == "administrator"){
-                                updateButton = "";
-                            }
-                            $("#quotaTable").append(`<tr>
-                                <td class="collapsing">
-                                    <i class="user icon"></i> ${key}
-                                </td>
-                                <td>${quota}</td>
-                                <td class="right aligned collapsing">${updateButton}</td>
-                            </tr>`);
-                        }
-                    }
-                });
-            }
-
-            function confirmUpdate(){
-                var targetUsergroup = usergroup;
-                var quotaMultipler = $("#newquota").val();
-                if (quotaMultipler == ""){
-                    quotaMultipler = 1024;
-                }
-                var quotaNumeric = $("#newQuotaValue").val();
-                var actualQuota = quotaNumeric * quotaMultipler;
-                $.ajax({
-                    url: "../../../system/disk/quota/setQuota",
-                    data: {"groupname": targetUsergroup, "quota":actualQuota},
-                    method: "POST",
-                    success: function(data){
-                        $('#updateQuotaInterface').transition('fade down');
-                        if (data.error !== undefined){
-                            $("#oprfailed").text(data.error);
-                            $("#oprfailed").slideDown("fast").delay(3000).slideUp("fast");
-                        }else{
-                            initQuotaTable();
-                            $("#oprsucc").slideDown("fast").delay(3000).slideUp("fast");
-                        }
-                    }
-                });
-
-            }
-
-            function updateGroupQuota(object){
-                var groupname = $(object).attr("group");
-                $("#editingGroup").val(groupname);
-                usergroup = groupname;
-                if ($("#updateQuotaInterface").is(":hidden")){
-                    $("#updateQuotaInterface").transition('fade down');
-                }
-                
-            }
-
-            function formatBytes(bytes, decimals = 2) {
-                if (bytes === 0) return '0 Bytes';
-
-                const k = 1024;
-                const dm = decimals < 0 ? 0 : decimals;
-                const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-
-                const i = Math.floor(Math.log(bytes) / Math.log(k));
-
-                return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-            }
-        </script>  
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Manage Storage Quota</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <style>
+
+        </style>
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="hdd icon"></i>
+                    <div class="content">
+                        Storage Quota Settings
+                        <div class="sub header">Limit each user groups storage quota</div>
+                    </div>
+                </div>
+            </div>
+            <div id="oprsucc" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i> Quota Updated</div>
+            <div id="oprfailed" style="display:none;" class="ui red inverted segment"><i class="remove icon"></i> Error occured while tring to update storage quota</div>
+            <div id="updateQuotaInterface" class="ui segment" style="display:none;">
+                <p>Group to be Updated: </p>
+                <div class="ui fluid tiny input">
+                    <input id="editingGroup" type="text" readonly="true" value="">
+                </div>
+                <br>
+                <p>New Quota: </p>
+                <div class="ui tiny input">
+                    <input id="newQuotaValue" type="number" value="15">
+                </div>
+                <div class="ui selection tiny dropdown">
+                    <input id="newquota" type="hidden" name="size">
+                    <i class="dropdown icon"></i>
+                    <div class="default text" >GB</div>
+                    <div class="menu">
+                        <div class="item" data-value="1">MB</div>
+                        <div class="item" data-value="1024">GB</div>
+                        <div class="item" data-value="1048576">TB</div>
+                    </div>
+                  </div>
+                  <div class="ui divider"></div>
+                  <div style="width:100%;" align="right">
+                    <button class="ui button" onclick="$('#updateQuotaInterface').transition('fade down');">Cancel</button>
+                    <button class="ui primary button" onclick="confirmUpdate();"><i class="checkmark icon"></i> Confirm</button>
+                  </div>
+            </div>
+            <table class="ui celled striped table">
+                <thead>
+                    <tr>
+                        <th colspan="3">
+                        Current Storage Quota
+                        </th>
+                    </tr>
+                    <tr>
+                        <th>User Group</th>
+                        <th>Storage Quota</th>
+                        <th>Edit Quota</th>
+                    </tr>
+                </thead>
+                <tbody id="quotaTable">
+                    <tr>
+                    <td colspan="3">Initializing Storage Quota Table</td>
+                    </tr>
+                    
+                </tbody>
+              </table>
+              <p><i class="info circle icon"></i> All users in the given group will have the same storage quota. If you want to create a special storage quota for a given user, please create a new group.</p>
+        </div>
+        <script>
+            var usergroup = "";
+            $('.ui.dropdown').dropdown();
+
+            initQuotaTable();
+            function initQuotaTable(){
+                $("#quotaTable").html("Loading...");
+                $.get("../../system/disk/quota/listQuota",function(data){
+                    $("#quotaTable").html("");
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        for (const [key, value] of Object.entries(data)) {
+                            var quota = "Read Only";
+                            if (value == -1){
+                                quota = "Unlimited";
+                            }else if (value > 0){
+                                quota = formatBytes(value);
+                            }
+
+                            var updateButton = `<button class="ui teal tiny button" group="${key}" onclick="updateGroupQuota(this);">Update</button>`;
+                            if (key == "administrator"){
+                                updateButton = "";
+                            }
+                            $("#quotaTable").append(`<tr>
+                                <td class="collapsing">
+                                    <i class="user icon"></i> ${key}
+                                </td>
+                                <td>${quota}</td>
+                                <td class="right aligned collapsing">${updateButton}</td>
+                            </tr>`);
+                        }
+                    }
+                });
+            }
+
+            function confirmUpdate(){
+                var targetUsergroup = usergroup;
+                var quotaMultipler = $("#newquota").val();
+                if (quotaMultipler == ""){
+                    quotaMultipler = 1024;
+                }
+                var quotaNumeric = $("#newQuotaValue").val();
+                var actualQuota = quotaNumeric * quotaMultipler;
+                $.ajax({
+                    url: "../../../system/disk/quota/setQuota",
+                    data: {"groupname": targetUsergroup, "quota":actualQuota},
+                    method: "POST",
+                    success: function(data){
+                        $('#updateQuotaInterface').transition('fade down');
+                        if (data.error !== undefined){
+                            $("#oprfailed").text(data.error);
+                            $("#oprfailed").slideDown("fast").delay(3000).slideUp("fast");
+                        }else{
+                            initQuotaTable();
+                            $("#oprsucc").slideDown("fast").delay(3000).slideUp("fast");
+                        }
+                    }
+                });
+
+            }
+
+            function updateGroupQuota(object){
+                var groupname = $(object).attr("group");
+                $("#editingGroup").val(groupname);
+                usergroup = groupname;
+                if ($("#updateQuotaInterface").is(":hidden")){
+                    $("#updateQuotaInterface").transition('fade down');
+                }
+                
+            }
+
+            function formatBytes(bytes, decimals = 2) {
+                if (bytes === 0) return '0 Bytes';
+
+                const k = 1024;
+                const dm = decimals < 0 ? 0 : decimals;
+                const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+                const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+                return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+            }
+        </script>  
+    </body>
 </html>

+ 126 - 125
web/SystemAO/disk/quota/quota.html

@@ -1,126 +1,127 @@
-<html>
-    <head>
-        <title>Disk Quota</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
-        <link rel="stylesheet" href="../disk/quota/script/Chart.min.css">
-        <script type="text/javascript" src="../../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
-        <script src="../disk/quota/script/Chart.min.js"></script>
-        <style>
-
-        </style>
-    </head>
-    <body>
-        <div class="ui container">
-            <p>System Storage Space Quota</p>
-            <h1 class="ui header">
-                <span id="usedSpace">Loading</span>
-                <div class="sub header">Total used of <span id="remaining">N/A</span></div>
-            </h1>
-            <div class="ui divider"></div>
-            <p id="loadWarning"><i class="ui loading spinner icon"></i> File Category Graph might take some time to load...</p>
-            <div id="canvas-holder">
-                <canvas id="chart-area"></canvas>
-            </div>
-            <br>
-            <div class="ui divider"></div>
-        </div>
-        <script>
-            var c = document.getElementById("chart-area");
-            var ctx = c.getContext("2d");
-            var accurateUsedSpace = 0;
-
-            initQuotaInfo();
-            initFileTypeDistribution();
-            
-            function initQuotaInfo(){
-                //This will initalize the accumulative update quota information (Not accuratem, just estimation)
-                $.get("../../../system/disk/quota/quotaInfo",function(data){
-                    if (data.error !== undefined){
-
-                    }else{
-                        var used = ao_module_utils.formatBytes(data.Used);
-                        var total = "Unlimited";
-                        if (data.Total > 0){
-                            total = ao_module_utils.formatBytes(data.Total);
-                        }else if (data.Total == 0){
-                            total = "Read Only";
-                        }
-
-                        $("#usedSpace").html(used + ` <i style="color: #dedede;" class="tiny info circle icon" title="Cumulative Estimation"></i>`);
-                        $("#remaining").text(total);
-                    }
-                }); 
-            }
-
-            function initFileTypeDistribution(){
-                var dynamicColors = function() {
-                    var r = Math.floor(Math.random() * 180 + 75);
-                    var g = Math.floor(Math.random() * 180 + 75);
-                    var b = Math.floor(Math.random() * 180 + 75);
-                    return "rgb(" + r + "," + g + "," + b + ")";
-                };
-                $.get("../../../system/disk/quota/quotaDist",function(list){
-                    accurateUsedSpace = 0;
-                    if (list == null){
-                        return;
-                    }
-                    //Create the corret data structure for the pie chart
-                    data = {};
-                    var dataset = [];
-                    var labels = [];
-                    var colors = [];
-                    var stopping = list.length;
-                    for (var i = 0; i < stopping; i++){
-                        labels.push(list[i].Mime);
-                        dataset.push(list[i].Size);
-                        accurateUsedSpace += list[i].Size;
-                        colors.push(dynamicColors());
-                    }
-
-                    //Update the actual used spaces
-                    var actualSpaceUsed = ao_module_utils.formatBytes(accurateUsedSpace);
-                    $("#usedSpace").text(actualSpaceUsed);
-                
-                   
-                    data["datasets"] = [{
-                        data: dataset, 
-                        backgroundColor: colors
-                    }];
-                    data["labels"] = labels;
-                    var fileDistChart = new Chart(ctx, {
-                        type: 'doughnut',
-                        data: data,
-                        options: {
-                            responsive: true,
-                            tooltips: {
-                                callbacks: {
-                                    label: function(tooltipItem, data){
-                                        var partitionsize = (data.datasets[0].data[tooltipItem.index]);
-                                        var labeltext = data.labels[tooltipItem.index];
-                                        var label = labeltext + ": " +ao_module_utils.formatBytes(partitionsize, 2);
-                                        return label;
-                                    }
-                                },
-                            },
-                            legend: {
-                                position: 'right',
-                                labels: {
-                                    boxWidth: 20,
-                                    padding: 20
-                                }
-                            }
-                        }
-                    });
-
-                    $("#loadWarning").hide();
-                });
-               
-            }
-
-        </script>
-   
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Disk Quota</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
+        <link rel="stylesheet" href="../disk/quota/script/Chart.min.css">
+        <script type="text/javascript" src="../../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
+        <script src="../disk/quota/script/Chart.min.js"></script>
+        <style>
+
+        </style>
+    </head>
+    <body>
+        <div class="ui container">
+            <p>System Storage Space Quota</p>
+            <h1 class="ui header">
+                <span id="usedSpace">Loading</span>
+                <div class="sub header">Total used of <span id="remaining">N/A</span></div>
+            </h1>
+            <div class="ui divider"></div>
+            <p id="loadWarning"><i class="ui loading spinner icon"></i> File Category Graph might take some time to load...</p>
+            <div id="canvas-holder">
+                <canvas id="chart-area"></canvas>
+            </div>
+            <br>
+            <div class="ui divider"></div>
+        </div>
+        <script>
+            var c = document.getElementById("chart-area");
+            var ctx = c.getContext("2d");
+            var accurateUsedSpace = 0;
+
+            initQuotaInfo();
+            initFileTypeDistribution();
+            
+            function initQuotaInfo(){
+                //This will initalize the accumulative update quota information (Not accuratem, just estimation)
+                $.get("../../../system/disk/quota/quotaInfo",function(data){
+                    if (data.error !== undefined){
+
+                    }else{
+                        var used = ao_module_utils.formatBytes(data.Used);
+                        var total = "Unlimited";
+                        if (data.Total > 0){
+                            total = ao_module_utils.formatBytes(data.Total);
+                        }else if (data.Total == 0){
+                            total = "Read Only";
+                        }
+
+                        $("#usedSpace").html(used + ` <i style="color: #dedede;" class="tiny info circle icon" title="Cumulative Estimation"></i>`);
+                        $("#remaining").text(total);
+                    }
+                }); 
+            }
+
+            function initFileTypeDistribution(){
+                var dynamicColors = function() {
+                    var r = Math.floor(Math.random() * 180 + 75);
+                    var g = Math.floor(Math.random() * 180 + 75);
+                    var b = Math.floor(Math.random() * 180 + 75);
+                    return "rgb(" + r + "," + g + "," + b + ")";
+                };
+                $.get("../../../system/disk/quota/quotaDist",function(list){
+                    accurateUsedSpace = 0;
+                    if (list == null){
+                        return;
+                    }
+                    //Create the corret data structure for the pie chart
+                    data = {};
+                    var dataset = [];
+                    var labels = [];
+                    var colors = [];
+                    var stopping = list.length;
+                    for (var i = 0; i < stopping; i++){
+                        labels.push(list[i].Mime);
+                        dataset.push(list[i].Size);
+                        accurateUsedSpace += list[i].Size;
+                        colors.push(dynamicColors());
+                    }
+
+                    //Update the actual used spaces
+                    var actualSpaceUsed = ao_module_utils.formatBytes(accurateUsedSpace);
+                    $("#usedSpace").text(actualSpaceUsed);
+                
+                   
+                    data["datasets"] = [{
+                        data: dataset, 
+                        backgroundColor: colors
+                    }];
+                    data["labels"] = labels;
+                    var fileDistChart = new Chart(ctx, {
+                        type: 'doughnut',
+                        data: data,
+                        options: {
+                            responsive: true,
+                            tooltips: {
+                                callbacks: {
+                                    label: function(tooltipItem, data){
+                                        var partitionsize = (data.datasets[0].data[tooltipItem.index]);
+                                        var labeltext = data.labels[tooltipItem.index];
+                                        var label = labeltext + ": " +ao_module_utils.formatBytes(partitionsize, 2);
+                                        return label;
+                                    }
+                                },
+                            },
+                            legend: {
+                                position: 'right',
+                                labels: {
+                                    boxWidth: 20,
+                                    padding: 20
+                                }
+                            }
+                        }
+                    });
+
+                    $("#loadWarning").hide();
+                });
+               
+            }
+
+        </script>
+   
+    </body>
 </html>

+ 60 - 0
web/SystemAO/disk/raid/index.html

@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>RAID</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
+        <link rel="stylesheet" href="../disk/quota/script/Chart.min.css">
+        <script type="text/javascript" src="../../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
+        <script src="../disk/quota/script/Chart.min.js"></script>
+        <style>
+
+        </style>
+    </head>
+    <body>
+        <div class="ui container">
+            <br>
+            <h1 class="ui header">
+                <span id="healthy"><i class="ui loading circle notch icon"></i> Loading</span>
+                <div class="sub header"><span id="totalVolume">0</span> RAID volumes Loaded</div>
+            </h1>
+            <div class="ui divider"></div>
+        </div>
+        <div class="ui container">
+            <div class="ui stackable grid">
+                <div class="six wide column" style="border-right: 1px solid #e0e0e0;">
+                    <p>List of RAID Volumes</p>
+                    <div id="poolNameList">
+                        <div><i class="ui green check circle icon"></i> No RAID Volumes</div>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div style="width: 100%;" align="center">
+                        <button title="Add new RAID volume" onclick="addNewRaidVolume()" class="circular basic green large ui icon button">
+                            <i class="green add icon"></i>
+                        </button>
+                    </div>
+                </div>
+                <div class="ten wide column">
+                    <div id="disklist">
+    
+                    </div>
+                    <div class="ui divider"></div>
+                    <div style="width: 100%;" align="center">
+                        <button onclick="newFsh();" title="Add Storage" class="circular basic large ui icon button">
+                            <i class="add icon"></i>
+                        </button>
+                        <button onclick="bridgeFsh();" title="Bridge Storage" class="circular basic blue large ui icon button">
+                            <i class="linkify icon"></i>
+                        </button>
+                    </div>
+                </div>
+            </div>
+        <script>
+
+
+        </script>
+   
+    </body>
+</html>

+ 148 - 148
web/SystemAO/disk/smart/smart.html

@@ -1,149 +1,149 @@
-
-<html>
-    <head>
-        <title>Disk SMART</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
-        <link rel="stylesheet" href="../disk/quota/script/Chart.min.css">
-        <script type="text/javascript" src="../../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
-        <script src="../disk/quota/script/Chart.min.js"></script>
-        <style>
-
-        </style>
-    </head>
-    <body>
-        <div class="ui container">
-            <p>Drive SMART</p>
-            <h1 class="ui header">
-                <span id="healthy">Loading</span>
-                <div class="sub header"><span id="totaldisk">N/A</span> disks detected.</div>
-            </h1>
-            <div class="ui divider"></div>
-            <p id="loadWarning"><i class="ui loading spinner icon"></i> Loading SMART data...</p>
-                <div id="smart-holder" class="ui unstackable items">
-                  </div>
-            <br>
-            <div class="ui divider"></div>
-        </div>
-        <div class="ui modal">
-          <i class="close icon"></i>
-          <div class="header">
-            SMART Information (<span id="modal_model"></span>)
-          </div>
-          <div class="content">
-            <table class="ui very basic fluid celled table">
-              <thead>
-                <tr><th>ID</th>
-                <th>Name</th>
-                <th>Value</th>
-                <th>Worst</th>
-                <th>Thresh</th>
-                <th>Status</th>
-              </tr></thead>
-              <tbody id="modal_smart">
-                <tr>
-                  <td>
-                    ...
-                  </td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-          <div class="actions">
-            <div class="ui black deny button">
-              Close
-            </div>
-          </div>
-        </div>
-        <script>
-          var smart_template = `<div class="item">
-              <div class="tiny image" style="width: auto !important; margin-top: -10px;">
-                <img src="/SystemAO/disk/smart/img/hard-drive.svg" style="height:60px;width:auto; padding: 8px;">
-              </div>
-              <div class="content">
-                <p class="header">{model}</p>
-                <button class="ui right floated icon button" id="{id}" onclick="showDetails(this)"><i class="info icon"></i></button>
-                <div class="meta"><span>{status}</span></div>
-                <div class="description"><p></p></div>
-                <div class="extra">{capacity}</div>
-              </div>
-            </div>`;
-          var smartDATA;  
-          $.getJSON("/system/disk/smart/getSMART", function(data) {
-                smartDATA = data;
-
-                $("#mainmenu").html("");
-                if (data.devices == null || $(data).length == 0) {
-                    //$("#smart-holder").append(append("-1","No disk find!","",-1));
-                    $("#healthy").text("No disks detected.");
-                    $("#totaldisk").text("0");
-                }else{
-                  $.each(data.devices, function(index, value) {
-                    $("#smart-holder").append(append(index,value.smart.model_name,value.smart.healthy,value.smart.user_capacity.bytes));
-                  });
-                  $("#healthy").text(data.healthy);
-                  $("#totaldisk").text(data.devices.length);
-                }
-                $("#loadWarning").remove();
-            });
-
-            //Execute once when the page load, to destroy the old model
-            /*
-              This section was added because once the page changed, multiple modal
-              is appended to the page which lead to duplication of modal when calling
-              modal("show"). Do not remove the 3 lines below.
-            */
-            $(".loadedModal").modal('destroy');
-            $(".loadedModal").remove();
-            $(".ui.modal").addClass("loadedModal");
-
-            
-            function append(id, name, status, capacity){
-              var tmp = smart_template;
-              tmp = tmp.replaceAll("{id}",id);
-              tmp = tmp.replaceAll("{model}",name);
-              tmp = tmp.replaceAll("{status}",status);
-              tmp = tmp.replaceAll("{capacity}",disksize(capacity));
-              return tmp;
-            }
-
-          function disksize(size) {
-            if (size >= 1000000000000) {
-                return Math.floor(size / 1000000000000) + " TB";
-            } else if (size >= 1000000000) {
-                return Math.floor(size / 1000000000) + " GB";
-            } else if (size >= 1000000) {
-                return Math.floor(size / 1000000) + " MB";
-            } else if (size >= 1024) {
-                return Math.floor(size / 1000) + " KB";
-            } else if (size > 0) {
-                return size + " Bytes";
-            } else if (size == 0) {
-                return "Unknown capacity";
-            } else {
-                return "";
-            }
-          }
-
-          function showDetails(selector) {
-            $("#modal_smart").html("");
-            var id = $(selector).attr("id");
-            $("#modal_model").text(smartDATA.devices[id].smart.model_name);
-            $.each(smartDATA.devices[id].smart.ata_smart_attributes.table, function(index, value) {
-                    $("#modal_smart").append("<tr>");
-                    $("#modal_smart").append("<td>" + value.id + "</td>");
-                    $("#modal_smart").append("<td>" + value.name + "</td>");
-                    $("#modal_smart").append("<td>" + value.value + "</td>");
-                    $("#modal_smart").append("<td>" + value.worst + "</td>");
-                    $("#modal_smart").append("<td>" + value.thresh + "</td>");
-                    $("#modal_smart").append("<td>" + value.healthy + "</td>");
-                    $("#modal_smart").append("</tr>");
-            });
-            $('.ui.modal').modal('show');
-          }
-        </script>
-   
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Disk SMART</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
+        <link rel="stylesheet" href="../disk/quota/script/Chart.min.css">
+        <script type="text/javascript" src="../../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
+        <script src="../disk/quota/script/Chart.min.js"></script>
+        <style>
+
+        </style>
+    </head>
+    <body>
+        <div class="ui container">
+            <p>Drive SMART</p>
+            <h1 class="ui header">
+                <span id="healthy">Loading</span>
+                <div class="sub header"><span id="totaldisk">N/A</span> disks detected.</div>
+            </h1>
+            <div class="ui divider"></div>
+            <p id="loadWarning"><i class="ui loading spinner icon"></i> Loading SMART data...</p>
+                <div id="smart-holder" class="ui unstackable items">
+                  </div>
+            <br>
+            <div class="ui divider"></div>
+        </div>
+        <div class="ui modal">
+          <i class="close icon"></i>
+          <div class="header">
+            SMART Information (<span id="modal_model"></span>)
+          </div>
+          <div class="content">
+            <table class="ui very basic fluid celled table">
+              <thead>
+                <tr><th>ID</th>
+                <th>Name</th>
+                <th>Value</th>
+                <th>Worst</th>
+                <th>Thresh</th>
+                <th>Status</th>
+              </tr></thead>
+              <tbody id="modal_smart">
+                <tr>
+                  <td>
+                    ...
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+          <div class="actions">
+            <div class="ui black deny button">
+              Close
+            </div>
+          </div>
+        </div>
+        <script>
+          var smart_template = `<div class="item">
+              <div class="tiny image" style="width: auto !important; margin-top: -10px;">
+                <img src="/SystemAO/disk/smart/img/hard-drive.svg" style="height:60px;width:auto; padding: 8px;">
+              </div>
+              <div class="content">
+                <p class="header">{model}</p>
+                <button class="ui right floated icon button" id="{id}" onclick="showDetails(this)"><i class="info icon"></i></button>
+                <div class="meta"><span>{status}</span></div>
+                <div class="description"><p></p></div>
+                <div class="extra">{capacity}</div>
+              </div>
+            </div>`;
+          var smartDATA;  
+          $.getJSON("/system/disk/smart/getSMART", function(data) {
+                smartDATA = data;
+
+                $("#mainmenu").html("");
+                if (data.devices == null || $(data).length == 0) {
+                    //$("#smart-holder").append(append("-1","No disk find!","",-1));
+                    $("#healthy").text("No disks detected.");
+                    $("#totaldisk").text("0");
+                }else{
+                  $.each(data.devices, function(index, value) {
+                    $("#smart-holder").append(append(index,value.smart.model_name,value.smart.healthy,value.smart.user_capacity.bytes));
+                  });
+                  $("#healthy").text(data.healthy);
+                  $("#totaldisk").text(data.devices.length);
+                }
+                $("#loadWarning").remove();
+            });
+
+            //Execute once when the page load, to destroy the old model
+            /*
+              This section was added because once the page changed, multiple modal
+              is appended to the page which lead to duplication of modal when calling
+              modal("show"). Do not remove the 3 lines below.
+            */
+            $(".loadedModal").modal('destroy');
+            $(".loadedModal").remove();
+            $(".ui.modal").addClass("loadedModal");
+
+            
+            function append(id, name, status, capacity){
+              var tmp = smart_template;
+              tmp = tmp.replaceAll("{id}",id);
+              tmp = tmp.replaceAll("{model}",name);
+              tmp = tmp.replaceAll("{status}",status);
+              tmp = tmp.replaceAll("{capacity}",disksize(capacity));
+              return tmp;
+            }
+
+          function disksize(size) {
+            if (size >= 1000000000000) {
+                return Math.floor(size / 1000000000000) + " TB";
+            } else if (size >= 1000000000) {
+                return Math.floor(size / 1000000000) + " GB";
+            } else if (size >= 1000000) {
+                return Math.floor(size / 1000000) + " MB";
+            } else if (size >= 1024) {
+                return Math.floor(size / 1000) + " KB";
+            } else if (size > 0) {
+                return size + " Bytes";
+            } else if (size == 0) {
+                return "Unknown capacity";
+            } else {
+                return "";
+            }
+          }
+
+          function showDetails(selector) {
+            $("#modal_smart").html("");
+            var id = $(selector).attr("id");
+            $("#modal_model").text(smartDATA.devices[id].smart.model_name);
+            $.each(smartDATA.devices[id].smart.ata_smart_attributes.table, function(index, value) {
+                    $("#modal_smart").append("<tr>");
+                    $("#modal_smart").append("<td>" + value.id + "</td>");
+                    $("#modal_smart").append("<td>" + value.name + "</td>");
+                    $("#modal_smart").append("<td>" + value.value + "</td>");
+                    $("#modal_smart").append("<td>" + value.worst + "</td>");
+                    $("#modal_smart").append("<td>" + value.thresh + "</td>");
+                    $("#modal_smart").append("<td>" + value.healthy + "</td>");
+                    $("#modal_smart").append("</tr>");
+            });
+            $('.ui.modal').modal('show');
+          }
+        </script>
+   
+    </body>
 </html>

+ 1 - 0
web/SystemAO/disk/space/diskspace.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Disk Space</title>

+ 1 - 0
web/SystemAO/disk/space/finder.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Space Finder</title>

+ 1 - 0
web/SystemAO/file_system/defaultOpener.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>Select Default Opening WebApp</title>

+ 1 - 0
web/SystemAO/file_system/file_explorer.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>File Manager</title>

+ 1 - 0
web/SystemAO/file_system/file_operation.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title locale="title">File Operation</title>

+ 255 - 254
web/SystemAO/file_system/file_permission.html

@@ -1,255 +1,256 @@
-<html>
-    <head>
-        <title>File Permissions</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.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>
-        <style>
-            body{
-            }
-            .fitted.checkbox{
-                height: 20px;
-            }
-        </style>
-    </head>
-    <body id="filePropertiesWindow">
-        <br>
-        <div class="ui container">
-            <h3 class="ui header">
-                File Permissions
-                <div class="sub header">Update file permissions for other gateways</div>
-            </h3>
-            <div class="ui divider"></div>
-            <p id="filename"></p>
-            <div class="ui grid">
-                <div class="five wide column">
-                    <br>
-                    <div class="ui divider"></div>
-                    User
-                    <div class="ui divider"></div>
-                    Group
-                    <div class="ui divider"></div>
-                    Other
-                </div>
-                <div class="three wide column" style="text-align: center;">
-                    Read
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="1" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="4" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted checkbox">
-                        <input id="7" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                </div>
-                <div class="three wide column" style="text-align: center;">
-                    Write
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="2" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="5" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted checkbox">
-                        <input id="8" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                </div>
-                <div class="three wide column" style="text-align: center;">
-                    Execute
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="3" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted disabled checkbox">
-                        <input id="6" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                    <div class="ui divider"></div>
-                    <div class="ui fitted checkbox">
-                        <input id="9" type="checkbox" autocomplete="false">
-                        <label></label>
-                    </div>
-                </div>
-            </div>
-            <br>
-            <div class="ui accordion">
-                <div class="title">
-                  <i class="dropdown icon"></i>
-                  Advance Settings
-                </div>
-                <div class="content">
-                    <div class="ui toggle checkbox">
-                        <input type="checkbox" name="removeProtection" onchange="toggleProtection(this);" autocomplete="false" checked="false">
-                        <label>Allow Edit User / Group Permission</label>
-                    </div>
-                    <br>
-                    <small>Removing User / Group permission to critical files might lead to system failure. Please make sure you know what you are doing.</small>
-                </div>
-            </div>
-            <div class="ui divider"></div>
-            <button class="ui right floated button" onclick="ao_module_close();">Cancel</button>
-            <button class="ui green right floated button" onclick="update();">Update</button>
-            <br> <br>
-        </div>
-
-
-        <div id="filesizeLoader" class="ui active dimmer">
-            <div class="ui indeterminate text loader">Requesting File System</div>
-        </div>
-
-        <div id="success" class="ui basic modal">
-            <div class="ui icon header">
-              <i class="green checkmark icon"></i>
-              Pernmission Updated
-            </div>
-            <div class="actions">
-              <div class="ui ok inverted button">
-                OK
-              </div>
-            </div>
-          </div>
-
-          <div id="failed" class="ui basic modal">
-            <div class="ui icon header">
-              <i class="red remove icon"></i>
-              Pernmission Update Failed
-            </div>
-            <div class="content">
-                <p id="errmsg"></p>
-              </div>
-            <div class="actions">
-              <div class="ui ok inverted button">
-                OK
-              </div>
-            </div>
-          </div>
-
-        <script>
-            $(".ui.checkbox").checkbox();
-            $('.ui.accordion').accordion();
-            $(".ui.modal").modal();
-            //Initiate the view model
-            var files = ao_module_loadInputFiles();
-            var fileProperties = [];
-            var groupID = 0;
-
-            for (var i =0; i < files.length; i++){
-               getPermission(files[i]);
-            }
-
-            if (files.length == 1){
-                var filename = files[0].split("/").pop();
-                $("#filename").append(filename);
-            }else{
-                $("#filename").append(`<i class="file icon"></i>` + files.length + " files selected");
-            }
-            
-            function getPermission(filepath){
-                $.ajax({
-                    url: "../../system/file_system/handleFilePermission",
-                    data: {file: filepath},
-                    success: function(data){
-                        var permissionBites = [];
-                        var perChar = data.split("");
-                        //Shift out the group ID, currently useless
-                        groupID = perChar.shift();
-                        for (var i = 0; i < perChar.length; i++){
-                            //i: 0 - 2 => (7)55
-                            var permissionSettingBit = parseInt(perChar[i]).toString(2).padStart(3, "0").split("")
-                            for (var j = 0; j < permissionSettingBit.length; j++){
-                                //j: 0 - 2 => 111
-                                permissionBites.push(permissionSettingBit[j]);
-                            }
-                        }
-
-                        //Render the file permission
-                        console.log(data);
-
-                        //Assign the permission checkbox
-                        for (var i = 0; i < permissionBites.length; i++){
-                            if (permissionBites[i] == "1"){
-                                $("#" + (i + 1))[0].checked = true;
-                            }
-                        }
-                        
-                        $("#filesizeLoader").hide();
-                    }
-                });
-            }
-
-            function toggleProtection(button){
-                if (button.checked == true){
-                    $(".disabled.checkbox").removeClass("disabled");
-                }else{
-
-                    for (var i = 1; i < 7; i++){
-                        $("#" + i).parent().addClass("disabled");
-                    }
-                }
-            }
-
-            function resetCheckboxes(){
-                for (var i = 1; i < 10; i++){
-                    $("#" + i)[0].checked = false;
-                }
-            }
-
-            function update(){
-                var newPermission = generatePermissionString();
-                files.forEach(filepath => {
-                    $.ajax({
-                        url: "../../system/file_system/handleFilePermission",
-                        data: {file: filepath, mode: newPermission},
-                        success: function(data){
-                            if (data.error !== undefined){
-                                $("#failed").modal("show");
-                                $("#errmsg").text(data.error);
-                            }else{
-                                $("#success").modal("show");
-                            }
-                            console.log(data);
-                        }
-                    });
-                })
-               
-            }
-
-
-            function generatePermissionString(){
-                var binary = [];
-                for (var i = 1; i < 10; i++){
-                    if ($("#" + i)[0].checked == true){
-                        binary.push(1);
-                    }else{
-                        binary.push(0);
-                    }
-                }
-
-                //Convert the binary into hex
-                var octalRepresentation =  parseInt(binary.join(""), 2).toString(8);
-                
-                return groupID + octalRepresentation;
-            }
-
-
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>File Permissions</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.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>
+        <style>
+            body{
+            }
+            .fitted.checkbox{
+                height: 20px;
+            }
+        </style>
+    </head>
+    <body id="filePropertiesWindow">
+        <br>
+        <div class="ui container">
+            <h3 class="ui header">
+                File Permissions
+                <div class="sub header">Update file permissions for other gateways</div>
+            </h3>
+            <div class="ui divider"></div>
+            <p id="filename"></p>
+            <div class="ui grid">
+                <div class="five wide column">
+                    <br>
+                    <div class="ui divider"></div>
+                    User
+                    <div class="ui divider"></div>
+                    Group
+                    <div class="ui divider"></div>
+                    Other
+                </div>
+                <div class="three wide column" style="text-align: center;">
+                    Read
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="1" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="4" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted checkbox">
+                        <input id="7" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                </div>
+                <div class="three wide column" style="text-align: center;">
+                    Write
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="2" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="5" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted checkbox">
+                        <input id="8" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                </div>
+                <div class="three wide column" style="text-align: center;">
+                    Execute
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="3" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted disabled checkbox">
+                        <input id="6" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                    <div class="ui divider"></div>
+                    <div class="ui fitted checkbox">
+                        <input id="9" type="checkbox" autocomplete="false">
+                        <label></label>
+                    </div>
+                </div>
+            </div>
+            <br>
+            <div class="ui accordion">
+                <div class="title">
+                  <i class="dropdown icon"></i>
+                  Advance Settings
+                </div>
+                <div class="content">
+                    <div class="ui toggle checkbox">
+                        <input type="checkbox" name="removeProtection" onchange="toggleProtection(this);" autocomplete="false" checked="false">
+                        <label>Allow Edit User / Group Permission</label>
+                    </div>
+                    <br>
+                    <small>Removing User / Group permission to critical files might lead to system failure. Please make sure you know what you are doing.</small>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <button class="ui right floated button" onclick="ao_module_close();">Cancel</button>
+            <button class="ui green right floated button" onclick="update();">Update</button>
+            <br> <br>
+        </div>
+
+
+        <div id="filesizeLoader" class="ui active dimmer">
+            <div class="ui indeterminate text loader">Requesting File System</div>
+        </div>
+
+        <div id="success" class="ui basic modal">
+            <div class="ui icon header">
+              <i class="green checkmark icon"></i>
+              Pernmission Updated
+            </div>
+            <div class="actions">
+              <div class="ui ok inverted button">
+                OK
+              </div>
+            </div>
+          </div>
+
+          <div id="failed" class="ui basic modal">
+            <div class="ui icon header">
+              <i class="red remove icon"></i>
+              Pernmission Update Failed
+            </div>
+            <div class="content">
+                <p id="errmsg"></p>
+              </div>
+            <div class="actions">
+              <div class="ui ok inverted button">
+                OK
+              </div>
+            </div>
+          </div>
+
+        <script>
+            $(".ui.checkbox").checkbox();
+            $('.ui.accordion').accordion();
+            $(".ui.modal").modal();
+            //Initiate the view model
+            var files = ao_module_loadInputFiles();
+            var fileProperties = [];
+            var groupID = 0;
+
+            for (var i =0; i < files.length; i++){
+               getPermission(files[i]);
+            }
+
+            if (files.length == 1){
+                var filename = files[0].split("/").pop();
+                $("#filename").append(filename);
+            }else{
+                $("#filename").append(`<i class="file icon"></i>` + files.length + " files selected");
+            }
+            
+            function getPermission(filepath){
+                $.ajax({
+                    url: "../../system/file_system/handleFilePermission",
+                    data: {file: filepath},
+                    success: function(data){
+                        var permissionBites = [];
+                        var perChar = data.split("");
+                        //Shift out the group ID, currently useless
+                        groupID = perChar.shift();
+                        for (var i = 0; i < perChar.length; i++){
+                            //i: 0 - 2 => (7)55
+                            var permissionSettingBit = parseInt(perChar[i]).toString(2).padStart(3, "0").split("")
+                            for (var j = 0; j < permissionSettingBit.length; j++){
+                                //j: 0 - 2 => 111
+                                permissionBites.push(permissionSettingBit[j]);
+                            }
+                        }
+
+                        //Render the file permission
+                        console.log(data);
+
+                        //Assign the permission checkbox
+                        for (var i = 0; i < permissionBites.length; i++){
+                            if (permissionBites[i] == "1"){
+                                $("#" + (i + 1))[0].checked = true;
+                            }
+                        }
+                        
+                        $("#filesizeLoader").hide();
+                    }
+                });
+            }
+
+            function toggleProtection(button){
+                if (button.checked == true){
+                    $(".disabled.checkbox").removeClass("disabled");
+                }else{
+
+                    for (var i = 1; i < 7; i++){
+                        $("#" + i).parent().addClass("disabled");
+                    }
+                }
+            }
+
+            function resetCheckboxes(){
+                for (var i = 1; i < 10; i++){
+                    $("#" + i)[0].checked = false;
+                }
+            }
+
+            function update(){
+                var newPermission = generatePermissionString();
+                files.forEach(filepath => {
+                    $.ajax({
+                        url: "../../system/file_system/handleFilePermission",
+                        data: {file: filepath, mode: newPermission},
+                        success: function(data){
+                            if (data.error !== undefined){
+                                $("#failed").modal("show");
+                                $("#errmsg").text(data.error);
+                            }else{
+                                $("#success").modal("show");
+                            }
+                            console.log(data);
+                        }
+                    });
+                })
+               
+            }
+
+
+            function generatePermissionString(){
+                var binary = [];
+                for (var i = 1; i < 10; i++){
+                    if ($("#" + i)[0].checked == true){
+                        binary.push(1);
+                    }else{
+                        binary.push(0);
+                    }
+                }
+
+                //Convert the binary into hex
+                var octalRepresentation =  parseInt(binary.join(""), 2).toString(8);
+                
+                return groupID + octalRepresentation;
+            }
+
+
+        </script>
+    </body>
 </html>

+ 1 - 0
web/SystemAO/file_system/file_properties.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title locale="title/title">File Properties</title>

+ 1 - 0
web/SystemAO/file_system/file_selector.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title locale="title">File Selector</title>

+ 1 - 0
web/SystemAO/file_system/file_share.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title locale="title/title">File Share</title>

+ 242 - 241
web/SystemAO/file_system/file_versions.html

@@ -1,242 +1,243 @@
-<html>
-    <head>
-        <title locale="title/title">File Version History</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.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>
-        <script type="text/javascript" src="../../script/applocale.js"></script>
-        <style>
-            .backgroundIcon{
-                position: fixed;
-                bottom: 0px;
-                right: 0px;
-                opacity: 0.3;
-                margin-right: -8em;
-                margin-bottom: -5em;
-                z-index: -99;
-                pointer-events: none;
-                user-select: none;
-            }
-        </style>
-    </head>
-    <body id="filePropertiesWindow">
-        <br>
-        <div class="backgroundIcon">
-            <img class="ui medium image" src="../../img/system/file-restore.svg">
-        </div>
-        <div class="ui container">
-            <h3 class="ui header">
-                <div class="content">
-                    <span locale="title/title">File Version History</span>
-                    <div class="sub header" locale="title/desc">Any file versions that is more than 30 days old will be automatically removed from the system.</div>
-                </div>
-            </h3>
-            <button onclick="createSnapshot();" class="ui basic green button"><i class="ui add icon"></i> <span locale="button/newver">Create New Version</span></button>
-            
-            <div class="ui divider"></div>
-            <div id="succ" style="display:none;" class="ui green message">
-                <i class="ui checkmark icon"></i> <span id="msg">File Restore Succeed</span>
-            </div>
-            <div>
-                <table class="ui very basic fluid celled table unstackable">
-                    <tbody id="versions">
-                      <tr>
-                        <td>
-                          <h4 class="ui header">
-                                <div class="content">
-                                <span locale="message/nofile/title">No File</span>
-                                <div locale="message/nofile/desc" class="sub header">Invalid usage</div>
-                            </div>
-                            </h4>
-                        </td>
-                      </tr>
-                    </tbody>
-                  </table>
-            </div>
-            <div class="ui divider"></div>
-            <button class="ui red  button" onclick="deleteAllVersions();"><i class="ui trash icon"></i> <span locale="button/removeAll">Remove All Version Histories</span></button>
-            <br>
-        </div>
-        <script>
-            var files = ao_module_loadInputFiles();
-            var targetFile = "";
-            var currentVersionList = [];
-            if (files.length >= 1){
-                targetFile = files[0];
-                applocale.init("../../SystemAO/locale/file_versions.json", function(){
-                    applocale.translate();
-                    loadVersionHistory(targetFile);
-                });
-                
-                setInterval(function(){
-                    checkHistoryListUpdate(targetFile);
-                }, 3000)
-            }else{
-                //No file selected
-                applocale.init("../../SystemAO/locale/file_versions.json", function(){
-                    applocale.translate();
-                });
-            }
-
-            
-
-          
-
-            function checkHistoryListUpdate(vpath){
-                $.ajax({
-                    url: "../../system/file_system/versionHistory",
-                    data: {path: vpath},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error == undefined){
-                            if (currentVersionList.length != data.Versions.length){
-                                loadVersionHistory(vpath);
-                            }
-                        }
-                    }
-                });
-            }
-
-            function loadVersionHistory(vpath){
-                $("#versions").html("Loading...");
-                $.ajax({
-                    url: "../../system/file_system/versionHistory",
-                    data: {path: vpath},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            $("#versions").html("");
-                            currentVersionList = data.Versions;
-                            data.Versions.forEach(fileVersionEntry => {
-                                var filesize = ao_module_utils.formatBytes(fileVersionEntry.Filesize, 1);
-                                $("#versions").append(`<tr>
-                                    <td>
-                                    <h4 class="ui header">
-                                        <div class="content">
-                                        <span>${fileVersionEntry.Filename}</span>
-                                        <div class="sub header">${fileVersionEntry.OverwriteTime} / ${filesize}</div>
-                                    </div>
-                                    </h4></td>
-                                    <td>
-                                        <div class="ui icon mini buttons">
-                                            <button relpath="${fileVersionEntry.Relpath}" onclick="downloadVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/download", "Download Version")}"><i class="ui blue download icon"></i></button>
-                                            <button verid="${fileVersionEntry.HistoryID}" onclick="restoreVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/restore", "Restore This Version")}"><i class="ui green history icon"></i></button>
-                                            <button verid="${fileVersionEntry.HistoryID}" onclick="deleteVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/delete", "Delete")}"><i class="ui red trash icon"></i></button>
-                                        </div>
-                                        
-                                    </td>
-                                </tr>`);
-                            });
-
-                            if (data.Versions.length == 0){
-                                //This file has no version history
-                                $("#versions").html(`<tr>
-                                    <td>
-                                        <h4 class="ui header">
-                                            <div class="content">
-                                                <i class="ui history icon"></i><span> ${applocale.getString("message/nohist/title", "No History")}</span>
-                                                <div class="sub header">${applocale.getString("message/nohist/desc", "This file has no previous version histories.")}</div>
-                                            </div>
-                                        </h4>
-                                    </td>
-                                </tr>`);
-                            }
-                            applocale.translate();
-                        }
-                    }
-                });
-            }
-
-            function downloadVersion(object){
-                var relpath = $(object).attr("relpath");
-                var dirname = targetFile.split("/");
-                dirname.pop();
-                var accessPath = dirname.join("/") + "/" + relpath;
-                window.open("../../media/download?file=" + accessPath)
-            }
-
-            function createSnapshot(){
-                //Create a snapshot of the current object
-                $.ajax({
-                    url: "../../system/file_system/versionHistory",
-                    data: {path: targetFile, opr: "new"},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error != undefined){
-                            alert(data.error)
-                        }else{
-                            msgbox(applocale.getString("msgbox/created","New Version Created"));
-                            loadVersionHistory(targetFile);
-                        }
-                    }
-                });
-            }
-
-            function restoreVersion(object){
-                var versionID = $(object).attr("verid");
-                if (confirm(applocale.getString("warning/restore", "Restoring this file will remove all newer version histories of this file. Confirm?"))){
-                    $.ajax({
-                        url: "../../system/file_system/versionHistory",
-                        data: {path: targetFile, opr: "restore", histid: versionID},
-                        method: "POST",
-                        success: function(data){
-                            if (data.error != undefined){
-                                alert(data.error)
-                            }else{
-                                loadVersionHistory(targetFile);
-                                msgbox(applocale.getString("msgbox/restored","Restore Succeed"));
-                            }
-                        }
-                    });
-                }
-            }
-
-            function msgbox(message){
-                $("#msg").text(message);
-                $("#succ").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
-            }
-
-            function deleteVersion(object){
-                var versionID = $(object).attr("verid");
-                $.ajax({
-                    url: "../../system/file_system/versionHistory",
-                    data: {path: targetFile, opr: "delete", histid: versionID},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error != undefined){
-                            alert(data.error)
-                        }else{
-                            loadVersionHistory(targetFile);
-                        }
-                    }
-                });
-            }
-
-            function deleteAllVersions(object){
-                var versionID = $(object).attr("verid");
-                if (confirm(applocale.getString("warning/deleteall", "This operation will **PERMANENTLY DELETE ALL** version history of this file. Confirm?"))){
-                    $.ajax({
-                        url: "../../system/file_system/versionHistory",
-                        data: {path: targetFile, opr: "deleteAll", histid: versionID},
-                        method: "POST",
-                        success: function(data){
-                            if (data.error != undefined){
-                                alert(data.error)
-                            }else{
-                                msgbox(applocale.getString("msgbox/deletedall","All version history deleted"));
-                                loadVersionHistory(targetFile);
-                            }
-                        }
-                    });
-                }
-                
-            }
-             
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title locale="title/title">File Version History</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.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>
+        <script type="text/javascript" src="../../script/applocale.js"></script>
+        <style>
+            .backgroundIcon{
+                position: fixed;
+                bottom: 0px;
+                right: 0px;
+                opacity: 0.3;
+                margin-right: -8em;
+                margin-bottom: -5em;
+                z-index: -99;
+                pointer-events: none;
+                user-select: none;
+            }
+        </style>
+    </head>
+    <body id="filePropertiesWindow">
+        <br>
+        <div class="backgroundIcon">
+            <img class="ui medium image" src="../../img/system/file-restore.svg">
+        </div>
+        <div class="ui container">
+            <h3 class="ui header">
+                <div class="content">
+                    <span locale="title/title">File Version History</span>
+                    <div class="sub header" locale="title/desc">Any file versions that is more than 30 days old will be automatically removed from the system.</div>
+                </div>
+            </h3>
+            <button onclick="createSnapshot();" class="ui basic green button"><i class="ui add icon"></i> <span locale="button/newver">Create New Version</span></button>
+            
+            <div class="ui divider"></div>
+            <div id="succ" style="display:none;" class="ui green message">
+                <i class="ui checkmark icon"></i> <span id="msg">File Restore Succeed</span>
+            </div>
+            <div>
+                <table class="ui very basic fluid celled table unstackable">
+                    <tbody id="versions">
+                      <tr>
+                        <td>
+                          <h4 class="ui header">
+                                <div class="content">
+                                <span locale="message/nofile/title">No File</span>
+                                <div locale="message/nofile/desc" class="sub header">Invalid usage</div>
+                            </div>
+                            </h4>
+                        </td>
+                      </tr>
+                    </tbody>
+                  </table>
+            </div>
+            <div class="ui divider"></div>
+            <button class="ui red  button" onclick="deleteAllVersions();"><i class="ui trash icon"></i> <span locale="button/removeAll">Remove All Version Histories</span></button>
+            <br>
+        </div>
+        <script>
+            var files = ao_module_loadInputFiles();
+            var targetFile = "";
+            var currentVersionList = [];
+            if (files.length >= 1){
+                targetFile = files[0];
+                applocale.init("../../SystemAO/locale/file_versions.json", function(){
+                    applocale.translate();
+                    loadVersionHistory(targetFile);
+                });
+                
+                setInterval(function(){
+                    checkHistoryListUpdate(targetFile);
+                }, 3000)
+            }else{
+                //No file selected
+                applocale.init("../../SystemAO/locale/file_versions.json", function(){
+                    applocale.translate();
+                });
+            }
+
+            
+
+          
+
+            function checkHistoryListUpdate(vpath){
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: vpath},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error == undefined){
+                            if (currentVersionList.length != data.Versions.length){
+                                loadVersionHistory(vpath);
+                            }
+                        }
+                    }
+                });
+            }
+
+            function loadVersionHistory(vpath){
+                $("#versions").html("Loading...");
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: vpath},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            $("#versions").html("");
+                            currentVersionList = data.Versions;
+                            data.Versions.forEach(fileVersionEntry => {
+                                var filesize = ao_module_utils.formatBytes(fileVersionEntry.Filesize, 1);
+                                $("#versions").append(`<tr>
+                                    <td>
+                                    <h4 class="ui header">
+                                        <div class="content">
+                                        <span>${fileVersionEntry.Filename}</span>
+                                        <div class="sub header">${fileVersionEntry.OverwriteTime} / ${filesize}</div>
+                                    </div>
+                                    </h4></td>
+                                    <td>
+                                        <div class="ui icon mini buttons">
+                                            <button relpath="${fileVersionEntry.Relpath}" onclick="downloadVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/download", "Download Version")}"><i class="ui blue download icon"></i></button>
+                                            <button verid="${fileVersionEntry.HistoryID}" onclick="restoreVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/restore", "Restore This Version")}"><i class="ui green history icon"></i></button>
+                                            <button verid="${fileVersionEntry.HistoryID}" onclick="deleteVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/delete", "Delete")}"><i class="ui red trash icon"></i></button>
+                                        </div>
+                                        
+                                    </td>
+                                </tr>`);
+                            });
+
+                            if (data.Versions.length == 0){
+                                //This file has no version history
+                                $("#versions").html(`<tr>
+                                    <td>
+                                        <h4 class="ui header">
+                                            <div class="content">
+                                                <i class="ui history icon"></i><span> ${applocale.getString("message/nohist/title", "No History")}</span>
+                                                <div class="sub header">${applocale.getString("message/nohist/desc", "This file has no previous version histories.")}</div>
+                                            </div>
+                                        </h4>
+                                    </td>
+                                </tr>`);
+                            }
+                            applocale.translate();
+                        }
+                    }
+                });
+            }
+
+            function downloadVersion(object){
+                var relpath = $(object).attr("relpath");
+                var dirname = targetFile.split("/");
+                dirname.pop();
+                var accessPath = dirname.join("/") + "/" + relpath;
+                window.open("../../media/download?file=" + accessPath)
+            }
+
+            function createSnapshot(){
+                //Create a snapshot of the current object
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: targetFile, opr: "new"},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error != undefined){
+                            alert(data.error)
+                        }else{
+                            msgbox(applocale.getString("msgbox/created","New Version Created"));
+                            loadVersionHistory(targetFile);
+                        }
+                    }
+                });
+            }
+
+            function restoreVersion(object){
+                var versionID = $(object).attr("verid");
+                if (confirm(applocale.getString("warning/restore", "Restoring this file will remove all newer version histories of this file. Confirm?"))){
+                    $.ajax({
+                        url: "../../system/file_system/versionHistory",
+                        data: {path: targetFile, opr: "restore", histid: versionID},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error != undefined){
+                                alert(data.error)
+                            }else{
+                                loadVersionHistory(targetFile);
+                                msgbox(applocale.getString("msgbox/restored","Restore Succeed"));
+                            }
+                        }
+                    });
+                }
+            }
+
+            function msgbox(message){
+                $("#msg").text(message);
+                $("#succ").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
+            }
+
+            function deleteVersion(object){
+                var versionID = $(object).attr("verid");
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: targetFile, opr: "delete", histid: versionID},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error != undefined){
+                            alert(data.error)
+                        }else{
+                            loadVersionHistory(targetFile);
+                        }
+                    }
+                });
+            }
+
+            function deleteAllVersions(object){
+                var versionID = $(object).attr("verid");
+                if (confirm(applocale.getString("warning/deleteall", "This operation will **PERMANENTLY DELETE ALL** version history of this file. Confirm?"))){
+                    $.ajax({
+                        url: "../../system/file_system/versionHistory",
+                        data: {path: targetFile, opr: "deleteAll", histid: versionID},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error != undefined){
+                                alert(data.error)
+                            }else{
+                                msgbox(applocale.getString("msgbox/deletedall","All version history deleted"));
+                                loadVersionHistory(targetFile);
+                            }
+                        }
+                    });
+                }
+                
+            }
+             
+        </script>
+    </body>
 </html> 

+ 185 - 184
web/SystemAO/file_system/sharelist.html

@@ -1,185 +1,186 @@
-<html>
-    <head>
-        <title locale="title/title">Share Entry List</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.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>
-        <script type="text/javascript" src="../../script/applocale.js"></script>
-        <script type="text/javascript" src="../../script/clipboard.min.js"></script>
-        <style>
-            .backgroundIcon{
-                position: fixed;
-                bottom: 0px;
-                right: 0px;
-                opacity: 0.4;
-                margin-right: -5em;
-                margin-bottom: -5em;
-                z-index: -99;
-                pointer-events: none;
-                user-select: none;
-            }
-        </style>
-    </head>
-    <body id="filePropertiesWindow">
-        <div class="backgroundIcon">
-            <img class="ui medium image" src="../../img/system/share.svg">
-        </div>
-        <br>
-        <div class="ui container">
-            <h3 class="ui header">
-                <i class="share alternate icon"></i>
-                <div class="content">
-                    <span locale="title/title">Share Entries</span> <span id="vrootname"></span>
-                    <div class="sub header" locale="title/desc">Shared files in this drive</div>
-                </div>
-            </h3>
-            <div class="ui divider"></div>
-            <div id="succ" style="display:none;" class="ui green message">
-                <i class="ui checkmark icon"></i> <span id="msg" locale="message/removed">Share Removed</span>
-            </div>
-            <div style="max-height: calc(100vh - 120px); overflow-y: auto;">
-                <table class="ui very basic fluid celled compact table unstackable">
-                    <tbody id="shares">
-                      <tr>
-                        <td>
-                          <h4 class="ui header">
-                                <div class="content">
-                                <span locale="message/noshare/title">No Shares</span>
-                                <div locale="message/noshare/desc" class="sub header">Try select a file using File Manager and right click share</div>
-                            </div>
-                            </h4>
-                        </td>
-                      </tr>
-                    </tbody>
-                  </table>
-            </div>
-            <br>
-        </div>
-        <script>
-            //Get fsh id from hash if exists
-            let fshId = "";
-            if (window.location.hash.length > 1){
-                var fshIds = window.location.hash.substr(1);
-                fshIds = JSON.parse(decodeURIComponent(fshIds));
-                fshId = fshIds[0];
-                $("#vrootname").text("(" + fshId + ")");
-            }
-
-            applocale.init("../../SystemAO/locale/sharelist.json", function(){
-                applocale.translate();
-                listSharedItems();
-            });
-
-            
-            function listSharedItems(){
-                $("#shares").html("");
-                $.get("../../system/file_system/share/list?fsh=" + fshId, function(data){
-                    console.log(data);
-                    data.forEach(function(entry){
-                        let filename = entry.FileVirtualPath.split("/").pop();
-                        let port = window.location.port;
-                        if (window.location.port == ""){
-                            port = "";
-                        }
-
-                        let openShareButton = ` <a title="Open Share" href="/share/${entry.UUID}" target="_blank" class="ui icon basic button"><i class="external icon"></i></a>`;
-                        if (!entry.CanAccess){
-                            openShareButton = "";
-                        }
-                        let openButton = `<button title="Open in File Manager" path="${entry.FileVirtualPath}" isfolder="${entry.IsFolder}" onclick="openThis(this);" class="ui icon basic button"><i class="folder open icon"></i></button>`;
-                        if (!entry.CanOpenInFileManager){
-                            openButton = "";
-                        }
-                        let deleteButton = `<button title="Delete Share" uuid="${entry.UUID}" onclick="deleteShare(this);" class="ui red icon button"><i class="trash icon"></i></button>`;
-                        if (!entry.CanDelete){
-                            deleteButton = "";
-                        }
-
-                        $("#shares").append(`
-                            <tr>
-                                <td>
-                                    <h4 class="ui header">
-                                        <div class="content">
-                                            <span>${filename} </span>
-                                            <div class="sub header">${applocale.getString("item/creator", "Creator: ")} ${entry.Owner} / ${applocale.getString("item/perm", "Permission: ")} ${entry.Permission} / <span class="linkCopier" style="cursor:pointer; color: #3452eb;" title="Copy Link" data-clipboard-text="${window.location.protocol + '//' + window.location.hostname + ":" + port + "/share/" + entry.UUID}"><i class="linkify icon"></i></span>
-                                        </div>
-                                    </h4>
-                                </td>
-                                <td style="padding-right: 0.6em;">
-                                    <div class="ui small vertical buttons">
-                                        ${openShareButton}
-                                        ${openButton}
-                                        ${deleteButton}
-                                    </div>
-                                </td>
-                       </tr>`);
-                    });
-
-                    var clipboard = new ClipboardJS('.linkCopier');
-                    clipboard.on('success', function(e) {
-                        //console.info('Action:', e.action);
-                        // console.info('Text:', e.text);
-                        // console.info('Trigger:', e.trigger);
-                        let originalContent =  $(e.trigger).html();
-                        $(e.trigger).html(`<i class="ui green checkmark icon"></i>`);
-                        $(e.trigger).css("pointer-events", "none");
-                        setTimeout(function(){
-                            $(e.trigger).html(originalContent);
-                            $(e.trigger).css("pointer-events", "auto");
-                        }, 1500);
-                        e.clearSelection();
-                    });
-
-                    if (data.length == 0){
-                        $("#shares").html(`<tr>
-                        <td>
-                          <h4 class="ui header">
-                                <div class="content">
-                                <span locale="message/noshare/title">No Shares</span>
-                                <div locale="message/noshare/desc" class="sub header">Try select a file using File Manager and right click share</div>
-                            </div>
-                            </h4>
-                        </td>
-                      </tr>`);
-                    }
-
-                    applocale.translate();
-                });
-            }
-
-            function openThis(object){
-                var vpath = $(object).attr("path");
-                var isFolder = $(object).attr("isfolder") == "true";
-                let openingPath = vpath;
-                if (isFolder){
-                    ao_module_openPath(vpath);
-                }else{
-                    let c = vpath.split("/");
-                    let filename = c.pop();
-                    let folderpath = c.join("/");
-                    ao_module_openPath(folderpath, filename);
-                }
-                
-            }
-
-            function deleteShare(object){
-                let deleteUUID = $(object).attr("uuid");
-                if (confirm(applocale.getString("message/delwarning", "All collaborators will lose access to this file via File Share interface. Confirm?"))){
-                    $.ajax({
-                        url: "../../system/file_system/share/delete",
-                        method: "POST",
-                        data: {uuid: deleteUUID},
-                        success: function(data){
-                            console.log(data);
-                            listSharedItems();
-                            $("#succ").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
-                        }
-                    });
-                }
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title locale="title/title">Share Entry List</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.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>
+        <script type="text/javascript" src="../../script/applocale.js"></script>
+        <script type="text/javascript" src="../../script/clipboard.min.js"></script>
+        <style>
+            .backgroundIcon{
+                position: fixed;
+                bottom: 0px;
+                right: 0px;
+                opacity: 0.4;
+                margin-right: -5em;
+                margin-bottom: -5em;
+                z-index: -99;
+                pointer-events: none;
+                user-select: none;
+            }
+        </style>
+    </head>
+    <body id="filePropertiesWindow">
+        <div class="backgroundIcon">
+            <img class="ui medium image" src="../../img/system/share.svg">
+        </div>
+        <br>
+        <div class="ui container">
+            <h3 class="ui header">
+                <i class="share alternate icon"></i>
+                <div class="content">
+                    <span locale="title/title">Share Entries</span> <span id="vrootname"></span>
+                    <div class="sub header" locale="title/desc">Shared files in this drive</div>
+                </div>
+            </h3>
+            <div class="ui divider"></div>
+            <div id="succ" style="display:none;" class="ui green message">
+                <i class="ui checkmark icon"></i> <span id="msg" locale="message/removed">Share Removed</span>
+            </div>
+            <div style="max-height: calc(100vh - 120px); overflow-y: auto;">
+                <table class="ui very basic fluid celled compact table unstackable">
+                    <tbody id="shares">
+                      <tr>
+                        <td>
+                          <h4 class="ui header">
+                                <div class="content">
+                                <span locale="message/noshare/title">No Shares</span>
+                                <div locale="message/noshare/desc" class="sub header">Try select a file using File Manager and right click share</div>
+                            </div>
+                            </h4>
+                        </td>
+                      </tr>
+                    </tbody>
+                  </table>
+            </div>
+            <br>
+        </div>
+        <script>
+            //Get fsh id from hash if exists
+            let fshId = "";
+            if (window.location.hash.length > 1){
+                var fshIds = window.location.hash.substr(1);
+                fshIds = JSON.parse(decodeURIComponent(fshIds));
+                fshId = fshIds[0];
+                $("#vrootname").text("(" + fshId + ")");
+            }
+
+            applocale.init("../../SystemAO/locale/sharelist.json", function(){
+                applocale.translate();
+                listSharedItems();
+            });
+
+            
+            function listSharedItems(){
+                $("#shares").html("");
+                $.get("../../system/file_system/share/list?fsh=" + fshId, function(data){
+                    console.log(data);
+                    data.forEach(function(entry){
+                        let filename = entry.FileVirtualPath.split("/").pop();
+                        let port = window.location.port;
+                        if (window.location.port == ""){
+                            port = "";
+                        }
+
+                        let openShareButton = ` <a title="Open Share" href="/share/${entry.UUID}" target="_blank" class="ui icon basic button"><i class="external icon"></i></a>`;
+                        if (!entry.CanAccess){
+                            openShareButton = "";
+                        }
+                        let openButton = `<button title="Open in File Manager" path="${entry.FileVirtualPath}" isfolder="${entry.IsFolder}" onclick="openThis(this);" class="ui icon basic button"><i class="folder open icon"></i></button>`;
+                        if (!entry.CanOpenInFileManager){
+                            openButton = "";
+                        }
+                        let deleteButton = `<button title="Delete Share" uuid="${entry.UUID}" onclick="deleteShare(this);" class="ui red icon button"><i class="trash icon"></i></button>`;
+                        if (!entry.CanDelete){
+                            deleteButton = "";
+                        }
+
+                        $("#shares").append(`
+                            <tr>
+                                <td>
+                                    <h4 class="ui header">
+                                        <div class="content">
+                                            <span>${filename} </span>
+                                            <div class="sub header">${applocale.getString("item/creator", "Creator: ")} ${entry.Owner} / ${applocale.getString("item/perm", "Permission: ")} ${entry.Permission} / <span class="linkCopier" style="cursor:pointer; color: #3452eb;" title="Copy Link" data-clipboard-text="${window.location.protocol + '//' + window.location.hostname + ":" + port + "/share/" + entry.UUID}"><i class="linkify icon"></i></span>
+                                        </div>
+                                    </h4>
+                                </td>
+                                <td style="padding-right: 0.6em;">
+                                    <div class="ui small vertical buttons">
+                                        ${openShareButton}
+                                        ${openButton}
+                                        ${deleteButton}
+                                    </div>
+                                </td>
+                       </tr>`);
+                    });
+
+                    var clipboard = new ClipboardJS('.linkCopier');
+                    clipboard.on('success', function(e) {
+                        //console.info('Action:', e.action);
+                        // console.info('Text:', e.text);
+                        // console.info('Trigger:', e.trigger);
+                        let originalContent =  $(e.trigger).html();
+                        $(e.trigger).html(`<i class="ui green checkmark icon"></i>`);
+                        $(e.trigger).css("pointer-events", "none");
+                        setTimeout(function(){
+                            $(e.trigger).html(originalContent);
+                            $(e.trigger).css("pointer-events", "auto");
+                        }, 1500);
+                        e.clearSelection();
+                    });
+
+                    if (data.length == 0){
+                        $("#shares").html(`<tr>
+                        <td>
+                          <h4 class="ui header">
+                                <div class="content">
+                                <span locale="message/noshare/title">No Shares</span>
+                                <div locale="message/noshare/desc" class="sub header">Try select a file using File Manager and right click share</div>
+                            </div>
+                            </h4>
+                        </td>
+                      </tr>`);
+                    }
+
+                    applocale.translate();
+                });
+            }
+
+            function openThis(object){
+                var vpath = $(object).attr("path");
+                var isFolder = $(object).attr("isfolder") == "true";
+                let openingPath = vpath;
+                if (isFolder){
+                    ao_module_openPath(vpath);
+                }else{
+                    let c = vpath.split("/");
+                    let filename = c.pop();
+                    let folderpath = c.join("/");
+                    ao_module_openPath(folderpath, filename);
+                }
+                
+            }
+
+            function deleteShare(object){
+                let deleteUUID = $(object).attr("uuid");
+                if (confirm(applocale.getString("message/delwarning", "All collaborators will lose access to this file via File Share interface. Confirm?"))){
+                    $.ajax({
+                        url: "../../system/file_system/share/delete",
+                        method: "POST",
+                        data: {uuid: deleteUUID},
+                        success: function(data){
+                            console.log(data);
+                            listSharedItems();
+                            $("#succ").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
+                        }
+                    });
+                }
+            }
+        </script>
+    </body>
 </html> 

+ 362 - 361
web/SystemAO/file_system/trashbin.html

@@ -1,362 +1,363 @@
-<html>
-    <head>
-        <title>Trash Bin</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <script type="text/javascript" src="../../script/applocale.js"></script>
-        <style>
-            body{
-                background-color:white;
-            }
-            .banner{
-                background-color:#4287f5;
-                height:50px;
-                padding:12px;
-                padding-left:20px;
-                padding-top:16px;
-            }
-
-            #opricon{
-                position:absolute;
-                top:0px;
-                right:0px;
-                width:80px;
-                height:80px;
-            }
-            .title{
-                color:white;
-                font-size:130%;
-            }
-            .content{
-                padding:12px;
-            }
-            
-            .info{
-                margin-top:3px;
-                width:80%;
-            }
-            
-        </style>
-    </head>
-    <body>
-        <span class="normalview">
-        <div class="ui menu">
-            <div class="header item">
-               <img class="ui middle aligned mini image" src="trashbin_img/small_icon.png" style="margin-right:8px; height:28px; width:28px;"></img> <span locale="title/title">Trash Bin</span>
-            </div>
-            <div class="right item">
-                <button class="ui negative small button" onclick="clearAll();" locale="button/deleteAll">Delete All</button>
-            </div>
-        </div>
-        <div class="ui container">
-            <div class="ui segment">
-                <table class="ui celled table">
-                    <thead>
-                        <tr>
-                            <th locale="list/filename">Filename</th>
-                            <th locale="list/deleteTime">Delete Time</th>
-                            <th locale="list/details">Details</th>
-                            <th locale="list/restore">Restore</th>
-                        </tr>
-                    </thead>
-                    <tbody id="trashList">
-                    </tbody>
-                </table>
-                <div class="ui message" id="scanning">
-                    <i class="loading spinner icon"></i> <span locale="list/scanning">Scanning for trash in all disks...</span>
-                </div>
-            </div>
-        </div>
-        </span>
-        <div class="confirmRemove" style="display:none;">
-            <div class="banner">
-                <div class="title"><span locale="opr/move">Move</span> <span id="fcount">0 File</span> <span locale="opr/toTrashBin">to Trash Bin?</span></div>
-                <img id="opricon" src="trashbin_img/trashopr.png" class="ui image"></img>
-            </div>
-            <div class="content">
-                <div class="info"><span locale="opr/delete">Deleting:</span> <span class="filename"></span></div>
-                <div style="position:absolute; bottom:8px;right:4px;">
-                    <button class="ui small button" onclick="ao_module_close();" locale="opr/cancel">Cancel</button>
-                    <button class="ui primary small button" onclick="confirmDelete();" locale="opr/confirm">Confirm</button>
-                </div>
-            </div>
-        </div>
-        <div class="ui modal" style="overflow-y:auto; overflow-x:hidden;">
-            <i class="close icon"></i>
-            <div class="header filename" locale="message/nofileselected">
-                No File Selected
-            </div>
-            <div class="ui basic segment">
-                <table class="ui very basic celled table">
-                    <thead>
-                        <tr>
-                            <th locale="message/property">Property</th>
-                            <th locale="message/value">Value</th>
-                        </tr>
-                    </thead>
-                    <tbody id="detailList">
-                       
-                    </tbody>
-                </table>
-            </div>
-            <div class="actions">
-                <div class="ui button" onclick=" $('.ui.modal').modal('hide');" locale="detail/ok">
-                 OK
-                </div>
-            </div>
-        </div>
-
-        <script>
-            var deletePendingFiles = ao_module_loadInputFiles();
-            var previousTrashbinFilelist = [];
-            var legacyMode = !('WebSocket' in window || 'MozWebSocket' in window); 
-
-            if (!applocale){
-                applocale = {
-                    init: function(json, callback){
-                        if (callback){
-                            callback();
-                        }
-                    }
-                }
-            }
-            applocale.init("../../SystemAO/locale/trashbin.json", function(){
-                applocale.translate();
-
-                if (deletePendingFiles != null && deletePendingFiles.length > 0){
-                    //Handle trash treatment
-                    console.log(deletePendingFiles);
-                    ao_module_setFixedWindowSize();
-                    //ao_module_setWindowSize(400,200);
-                    $(".normalview").hide();
-                    $(".confirmRemove").show();
-
-                    //Update graphical information
-                    var displayname = deletePendingFiles[0].filename;
-                    $("#fcount").text("1 " + applocale.getString("opr/file", "File"));
-                    if (deletePendingFiles.length > 1){
-                        displayname = displayname + applocale.getString("opr/and", " and ") + (deletePendingFiles.length - 1) + applocale.getString("opr/more", " more");
-                        $("#fcount").text(deletePendingFiles.length + " " + applocale.getString("opr/files", "Files"));
-                    }
-                    $(".filename").text(displayname);
-
-                }else{
-                    //List all trash mode
-                    ao_module_setWindowSize(1080, 580);
-                    initList();
-                }
-
-            });
-
-           
-
-            //Initialize the trash list
-            function initList(){
-                $("#trashList").html("");
-                $("#scanning").show();
-                if (!legacyMode){
-                    //Use WebSocket for rendering
-                    var ws = new WebSocket(getWSEndpoint() + `/system/file_system/ws/listTrash`);
-                   
-                    ws.onopen = function() {
-                        previousTrashbinFilelist = [];
-                        console.log("TrashBin WebSocket Opened");
-                    };
-                    
-                    ws.onmessage = function (evt) { 
-                        var data = evt.data;
-                        var thisFile = JSON.parse(data);
-                        var filedata = encodeURIComponent(JSON.stringify(thisFile));
-                        //console.log(thisFile);
-
-                        $("#trashList").append(`<tr>
-                            <td>${thisFile.OriginalFilename}</td>
-                            <td>${thisFile.RemoveDate}</td>
-                            <td><button filedata="${filedata}" class="ui tiny button" onclick="showDetail(this);">${applocale.getString("button/details","Details")}</button></td>
-                            <td><button filedata="${filedata}" class="ui green tiny button" onclick="restore(this);">${applocale.getString("button/restore", "Restore")}</button></td>
-                        </tr>`);
-                        previousTrashbinFilelist.push(thisFile);
-                    };
-                    
-                    ws.onclose = function() { 
-                        console.log("TrashBin WebSocket transfer completed");
-                        if (previousTrashbinFilelist.length == 0){
-                            $("#trashList").append(`<tr><td colspan="4"><i class="checkmark icon"></i> ${applocale.getString("list/notrash", "There are no files or folders in Trashbin.")}</td></tr>`);
-                        }
-                        $("#scanning").hide();
-                    };
-
-                    ws.onerror = function(){
-                        console.log("Open failed. Fallback to legacy mode");
-                        legacyMode = true;
-                        initList();
-                    }
-                }else{
-                    //Use AJAX (Slower)
-                    $.get("../../system/file_system/listTrash",function(data){
-                        if (data.error !== undefined){
-                            $("#scanning").hide();
-                            alert(data.error);
-                        }else{
-                            previousTrashbinFilelist = [];
-                            for (var i =0; i < data.length; i++){
-                                var thisFile = data[i];
-                                var filedata = encodeURIComponent(JSON.stringify(thisFile));
-                                $("#trashList").append(`<tr>
-                                    <td>${thisFile.OriginalFilename}</td>
-                                    <td>${thisFile.RemoveDate}</td>
-                                    <td><button filedata="${filedata}" class="ui tiny button" onclick="showDetail(this);">${applocale.getString("button/details","Details")}</button></td>
-                                    <td><button filedata="${filedata}" class="ui green tiny button" onclick="restore(this);">${applocale.getString("button/restore", "Restore")}</button></td>
-                                </tr>`);
-                                previousTrashbinFilelist.push(thisFile);
-                            }
-
-                            if (data.length == 0){
-                                $("#trashList").append(`<tr><td colspan="4"><i class="checkmark icon"></i> ${applocale.getString("message/notrash","There are no files or folders in Trashbin.")}</td></tr>`);
-                            }
-                            $("#scanning").hide();
-                        }
-                    });
-                }
-            }
-
-            function getWSEndpoint(){
-                //Open opeartion in websocket
-                let protocol = "wss://";
-                if (location.protocol !== 'https:') {
-                    protocol = "ws://";
-                }
-
-                var port = window.location.port;
-                if (window.location.port == ""){
-                    if (location.protocol !== 'https:') {
-                        port = "80";
-                    }else{
-                        port = "443";
-                    }
-                    
-                }
-
-                wsControlEndpoint = (protocol + window.location.hostname + ":" + port);
-                return wsControlEndpoint;
-            }
-            
-
-            //Listen to the change of the trashbin
-            setInterval(function(){
-                $.get("../../system/file_system/listTrash",function(data){
-                    if (data.length != previousTrashbinFilelist.length){
-                        //Change of the trashbin file. Update the list
-                        initList();
-                    }
-                });
-            }, 30000);
-
-            function showDetail(object){
-                var filedata = JSON.parse(decodeURIComponent($(object).attr("filedata")));
-                $(".filename").text(filedata.OriginalFilename);
-                $("#detailList").html("");
-                var keyMapping = {
-                    "Filename": applocale.getString("detail/storageFilename", "Storage Filename"),
-                    "Filepath": applocale.getString("detail/storageFilepath","Storage Filepath"),
-                    "FileExt": applocale.getString("detail/fileExt","File Extension"),
-                    "IsDir": applocale.getString("detail/isDir","Is Directory"),
-                    "Filesize": applocale.getString("detail/filesize","File Size"),
-                    "RemoveTimestamp": applocale.getString("detail/removeTimestamp","Remove Timestamp"),
-                    "RemoveDate": applocale.getString("detail/removeDate","Remove Datetime"),
-                    "OriginalPath": applocale.getString("detail/originalPath","Original Path"),
-                    "OriginalFilename": applocale.getString("detail/originalFilename","Original Filename")
-                }
-                for (var [key, value] of Object.entries(filedata)) {
-                    console.log(key, value);
-                    var displayKey = keyMapping[key];
-                    if (key == "Filesize"){
-                        value = bytesToSize(value);
-                    }
-                    $("#detailList").append(`<tr>
-                            <td>
-                                ${displayKey}
-                            </td>
-                            <td>
-                                ${value}
-                            </td>
-                        </tr>`);
-                }
-                
-                $('.ui.modal').modal('show').css({"overflow-y":"auto","overflow-x": "hidden", "max-height":window.innerHeight - 30 + "px"});
-
-            }
-
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
-            }
-
-            function clearAll(){
-                if (confirm(applocale.getString("message/confirmDelete", "Confirm deleting ALL FILE PERMANENTLY?"))){
-                    $.get("../../system/file_system/clearTrash",function(data){
-                        initList();
-                    });
-                }
-            }
-
-            function restore(obj){
-                var filedata = JSON.parse(decodeURIComponent($(obj).attr("filedata")));
-                var filepath = filedata.Filepath;
-                $.ajax({
-                    url: "../../system/file_system/restoreTrash",
-                    method: "POST",
-                    data: {"src" : filepath},
-                    success: function(data){
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            initList();
-                            parent.refresh(undefined, true);
-                        }
-                    }
-                })
-            }
-
-            function confirmDelete(){
-                //Confirm delete of the files in the list
-                var deleteFileList = [];
-                for (var i =0; i <deletePendingFiles.length; i++){
-                    deleteFileList.push(deletePendingFiles[i].filepath);
-                }
-                requestCSRFToken(function(token){
-                    $.ajax({
-                        url: "../../system/file_system/fileOpr",
-                        method:"POST",
-                        data: {opr: "recycle", src: JSON.stringify(deleteFileList), csrft: token},
-                        success: function(data){
-                            if (data.error !== undefined){
-                                console.log("Delete failed! " + data.error)
-                            }else{
-                                //Delete completed. Close this window
-                                parent.refresh(undefined, true);
-                                ao_module_close();
-                            }
-                        }
-                    });
-                });
-                
-            }
-
-            function requestCSRFToken(callback){
-                $.ajax({
-                    url: "../../system/csrf/new",
-                    success: function(token){
-                        callback(token);
-                    }
-                })
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Trash Bin</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <script type="text/javascript" src="../../script/applocale.js"></script>
+        <style>
+            body{
+                background-color:white;
+            }
+            .banner{
+                background-color:#4287f5;
+                height:50px;
+                padding:12px;
+                padding-left:20px;
+                padding-top:16px;
+            }
+
+            #opricon{
+                position:absolute;
+                top:0px;
+                right:0px;
+                width:80px;
+                height:80px;
+            }
+            .title{
+                color:white;
+                font-size:130%;
+            }
+            .content{
+                padding:12px;
+            }
+            
+            .info{
+                margin-top:3px;
+                width:80%;
+            }
+            
+        </style>
+    </head>
+    <body>
+        <span class="normalview">
+        <div class="ui menu">
+            <div class="header item">
+               <img class="ui middle aligned mini image" src="trashbin_img/small_icon.png" style="margin-right:8px; height:28px; width:28px;"></img> <span locale="title/title">Trash Bin</span>
+            </div>
+            <div class="right item">
+                <button class="ui negative small button" onclick="clearAll();" locale="button/deleteAll">Delete All</button>
+            </div>
+        </div>
+        <div class="ui container">
+            <div class="ui segment">
+                <table class="ui celled table">
+                    <thead>
+                        <tr>
+                            <th locale="list/filename">Filename</th>
+                            <th locale="list/deleteTime">Delete Time</th>
+                            <th locale="list/details">Details</th>
+                            <th locale="list/restore">Restore</th>
+                        </tr>
+                    </thead>
+                    <tbody id="trashList">
+                    </tbody>
+                </table>
+                <div class="ui message" id="scanning">
+                    <i class="loading spinner icon"></i> <span locale="list/scanning">Scanning for trash in all disks...</span>
+                </div>
+            </div>
+        </div>
+        </span>
+        <div class="confirmRemove" style="display:none;">
+            <div class="banner">
+                <div class="title"><span locale="opr/move">Move</span> <span id="fcount">0 File</span> <span locale="opr/toTrashBin">to Trash Bin?</span></div>
+                <img id="opricon" src="trashbin_img/trashopr.png" class="ui image"></img>
+            </div>
+            <div class="content">
+                <div class="info"><span locale="opr/delete">Deleting:</span> <span class="filename"></span></div>
+                <div style="position:absolute; bottom:8px;right:4px;">
+                    <button class="ui small button" onclick="ao_module_close();" locale="opr/cancel">Cancel</button>
+                    <button class="ui primary small button" onclick="confirmDelete();" locale="opr/confirm">Confirm</button>
+                </div>
+            </div>
+        </div>
+        <div class="ui modal" style="overflow-y:auto; overflow-x:hidden;">
+            <i class="close icon"></i>
+            <div class="header filename" locale="message/nofileselected">
+                No File Selected
+            </div>
+            <div class="ui basic segment">
+                <table class="ui very basic celled table">
+                    <thead>
+                        <tr>
+                            <th locale="message/property">Property</th>
+                            <th locale="message/value">Value</th>
+                        </tr>
+                    </thead>
+                    <tbody id="detailList">
+                       
+                    </tbody>
+                </table>
+            </div>
+            <div class="actions">
+                <div class="ui button" onclick=" $('.ui.modal').modal('hide');" locale="detail/ok">
+                 OK
+                </div>
+            </div>
+        </div>
+
+        <script>
+            var deletePendingFiles = ao_module_loadInputFiles();
+            var previousTrashbinFilelist = [];
+            var legacyMode = !('WebSocket' in window || 'MozWebSocket' in window); 
+
+            if (!applocale){
+                applocale = {
+                    init: function(json, callback){
+                        if (callback){
+                            callback();
+                        }
+                    }
+                }
+            }
+            applocale.init("../../SystemAO/locale/trashbin.json", function(){
+                applocale.translate();
+
+                if (deletePendingFiles != null && deletePendingFiles.length > 0){
+                    //Handle trash treatment
+                    console.log(deletePendingFiles);
+                    ao_module_setFixedWindowSize();
+                    //ao_module_setWindowSize(400,200);
+                    $(".normalview").hide();
+                    $(".confirmRemove").show();
+
+                    //Update graphical information
+                    var displayname = deletePendingFiles[0].filename;
+                    $("#fcount").text("1 " + applocale.getString("opr/file", "File"));
+                    if (deletePendingFiles.length > 1){
+                        displayname = displayname + applocale.getString("opr/and", " and ") + (deletePendingFiles.length - 1) + applocale.getString("opr/more", " more");
+                        $("#fcount").text(deletePendingFiles.length + " " + applocale.getString("opr/files", "Files"));
+                    }
+                    $(".filename").text(displayname);
+
+                }else{
+                    //List all trash mode
+                    ao_module_setWindowSize(1080, 580);
+                    initList();
+                }
+
+            });
+
+           
+
+            //Initialize the trash list
+            function initList(){
+                $("#trashList").html("");
+                $("#scanning").show();
+                if (!legacyMode){
+                    //Use WebSocket for rendering
+                    var ws = new WebSocket(getWSEndpoint() + `/system/file_system/ws/listTrash`);
+                   
+                    ws.onopen = function() {
+                        previousTrashbinFilelist = [];
+                        console.log("TrashBin WebSocket Opened");
+                    };
+                    
+                    ws.onmessage = function (evt) { 
+                        var data = evt.data;
+                        var thisFile = JSON.parse(data);
+                        var filedata = encodeURIComponent(JSON.stringify(thisFile));
+                        //console.log(thisFile);
+
+                        $("#trashList").append(`<tr>
+                            <td>${thisFile.OriginalFilename}</td>
+                            <td>${thisFile.RemoveDate}</td>
+                            <td><button filedata="${filedata}" class="ui tiny button" onclick="showDetail(this);">${applocale.getString("button/details","Details")}</button></td>
+                            <td><button filedata="${filedata}" class="ui green tiny button" onclick="restore(this);">${applocale.getString("button/restore", "Restore")}</button></td>
+                        </tr>`);
+                        previousTrashbinFilelist.push(thisFile);
+                    };
+                    
+                    ws.onclose = function() { 
+                        console.log("TrashBin WebSocket transfer completed");
+                        if (previousTrashbinFilelist.length == 0){
+                            $("#trashList").append(`<tr><td colspan="4"><i class="checkmark icon"></i> ${applocale.getString("list/notrash", "There are no files or folders in Trashbin.")}</td></tr>`);
+                        }
+                        $("#scanning").hide();
+                    };
+
+                    ws.onerror = function(){
+                        console.log("Open failed. Fallback to legacy mode");
+                        legacyMode = true;
+                        initList();
+                    }
+                }else{
+                    //Use AJAX (Slower)
+                    $.get("../../system/file_system/listTrash",function(data){
+                        if (data.error !== undefined){
+                            $("#scanning").hide();
+                            alert(data.error);
+                        }else{
+                            previousTrashbinFilelist = [];
+                            for (var i =0; i < data.length; i++){
+                                var thisFile = data[i];
+                                var filedata = encodeURIComponent(JSON.stringify(thisFile));
+                                $("#trashList").append(`<tr>
+                                    <td>${thisFile.OriginalFilename}</td>
+                                    <td>${thisFile.RemoveDate}</td>
+                                    <td><button filedata="${filedata}" class="ui tiny button" onclick="showDetail(this);">${applocale.getString("button/details","Details")}</button></td>
+                                    <td><button filedata="${filedata}" class="ui green tiny button" onclick="restore(this);">${applocale.getString("button/restore", "Restore")}</button></td>
+                                </tr>`);
+                                previousTrashbinFilelist.push(thisFile);
+                            }
+
+                            if (data.length == 0){
+                                $("#trashList").append(`<tr><td colspan="4"><i class="checkmark icon"></i> ${applocale.getString("message/notrash","There are no files or folders in Trashbin.")}</td></tr>`);
+                            }
+                            $("#scanning").hide();
+                        }
+                    });
+                }
+            }
+
+            function getWSEndpoint(){
+                //Open opeartion in websocket
+                let protocol = "wss://";
+                if (location.protocol !== 'https:') {
+                    protocol = "ws://";
+                }
+
+                var port = window.location.port;
+                if (window.location.port == ""){
+                    if (location.protocol !== 'https:') {
+                        port = "80";
+                    }else{
+                        port = "443";
+                    }
+                    
+                }
+
+                wsControlEndpoint = (protocol + window.location.hostname + ":" + port);
+                return wsControlEndpoint;
+            }
+            
+
+            //Listen to the change of the trashbin
+            setInterval(function(){
+                $.get("../../system/file_system/listTrash",function(data){
+                    if (data.length != previousTrashbinFilelist.length){
+                        //Change of the trashbin file. Update the list
+                        initList();
+                    }
+                });
+            }, 30000);
+
+            function showDetail(object){
+                var filedata = JSON.parse(decodeURIComponent($(object).attr("filedata")));
+                $(".filename").text(filedata.OriginalFilename);
+                $("#detailList").html("");
+                var keyMapping = {
+                    "Filename": applocale.getString("detail/storageFilename", "Storage Filename"),
+                    "Filepath": applocale.getString("detail/storageFilepath","Storage Filepath"),
+                    "FileExt": applocale.getString("detail/fileExt","File Extension"),
+                    "IsDir": applocale.getString("detail/isDir","Is Directory"),
+                    "Filesize": applocale.getString("detail/filesize","File Size"),
+                    "RemoveTimestamp": applocale.getString("detail/removeTimestamp","Remove Timestamp"),
+                    "RemoveDate": applocale.getString("detail/removeDate","Remove Datetime"),
+                    "OriginalPath": applocale.getString("detail/originalPath","Original Path"),
+                    "OriginalFilename": applocale.getString("detail/originalFilename","Original Filename")
+                }
+                for (var [key, value] of Object.entries(filedata)) {
+                    console.log(key, value);
+                    var displayKey = keyMapping[key];
+                    if (key == "Filesize"){
+                        value = bytesToSize(value);
+                    }
+                    $("#detailList").append(`<tr>
+                            <td>
+                                ${displayKey}
+                            </td>
+                            <td>
+                                ${value}
+                            </td>
+                        </tr>`);
+                }
+                
+                $('.ui.modal').modal('show').css({"overflow-y":"auto","overflow-x": "hidden", "max-height":window.innerHeight - 30 + "px"});
+
+            }
+
+            function bytesToSize(bytes) {
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+                if (bytes == 0) return '0 Byte';
+                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+            }
+
+            function clearAll(){
+                if (confirm(applocale.getString("message/confirmDelete", "Confirm deleting ALL FILE PERMANENTLY?"))){
+                    $.get("../../system/file_system/clearTrash",function(data){
+                        initList();
+                    });
+                }
+            }
+
+            function restore(obj){
+                var filedata = JSON.parse(decodeURIComponent($(obj).attr("filedata")));
+                var filepath = filedata.Filepath;
+                $.ajax({
+                    url: "../../system/file_system/restoreTrash",
+                    method: "POST",
+                    data: {"src" : filepath},
+                    success: function(data){
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            initList();
+                            parent.refresh(undefined, true);
+                        }
+                    }
+                })
+            }
+
+            function confirmDelete(){
+                //Confirm delete of the files in the list
+                var deleteFileList = [];
+                for (var i =0; i <deletePendingFiles.length; i++){
+                    deleteFileList.push(deletePendingFiles[i].filepath);
+                }
+                requestCSRFToken(function(token){
+                    $.ajax({
+                        url: "../../system/file_system/fileOpr",
+                        method:"POST",
+                        data: {opr: "recycle", src: JSON.stringify(deleteFileList), csrft: token},
+                        success: function(data){
+                            if (data.error !== undefined){
+                                console.log("Delete failed! " + data.error)
+                            }else{
+                                //Delete completed. Close this window
+                                parent.refresh(undefined, true);
+                                ao_module_close();
+                            }
+                        }
+                    });
+                });
+                
+            }
+
+            function requestCSRFToken(callback){
+                $.ajax({
+                    url: "../../system/csrf/new",
+                    success: function(token){
+                        callback(token);
+                    }
+                })
+            }
+        </script>
+    </body>
 </html>

+ 65 - 64
web/SystemAO/file_system/zip_extractor.html

@@ -1,65 +1,66 @@
-<html>
-    <head>
-        <title>Zip Extractor</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-        <style>
-            body{
-                background-color: rgba(255,255,255,0.7);
-            }
-        </style>
-    </head>
-    <body>
-        <br>
-        <div class="ui active inverted dimmer">
-            <div class="ui text loader">Loading Archives</div>
-        </div>
-        <script>
-            ao_module_setFixedWindowSize();
-
-            //Load openeing file from ao_module inputs
-            var targetFiles = ao_module_loadInputFiles();
-            console.log("Opening Zip Archives", targetFiles);
-            
-            //Generate an filepath array from file objects
-            var targetFilePaths = [];
-            targetFiles.forEach(file => {
-                targetFilePaths.push(file.filepath);
-            });
-
-            //Unzip and open them to tmp:/
-            var oprConfig = {
-                opr: "unzipAndOpen",
-                src: targetFilePaths,
-                dest: "tmp:/tmp_" + Date.now() + "/",
-                overwriteMode: "overwrite",
-            }
-
-            //Render the dialog title name
-            var configHash = encodeURIComponent(JSON.stringify(oprConfig));
-            var title = "Unzipping " + targetFilePaths.length;
-            if (targetFilePaths.length > 1){
-                title += " files";
-            }else{
-                title += " file";
-            }
-
-            //Open the operation dialog in new floatWindow
-            ao_module_newfw({
-                url: "SystemAO/file_system/file_operation.html#" + configHash,
-                width: 400,
-                height: 220,
-                appicon: "SystemAO/file_system/img/zip_extractor.png",
-                title: title
-            });
-
-            //CLose this window
-            ao_module_close();
-        
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Zip Extractor</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+        <style>
+            body{
+                background-color: rgba(255,255,255,0.7);
+            }
+        </style>
+    </head>
+    <body>
+        <br>
+        <div class="ui active inverted dimmer">
+            <div class="ui text loader">Loading Archives</div>
+        </div>
+        <script>
+            ao_module_setFixedWindowSize();
+
+            //Load openeing file from ao_module inputs
+            var targetFiles = ao_module_loadInputFiles();
+            console.log("Opening Zip Archives", targetFiles);
+            
+            //Generate an filepath array from file objects
+            var targetFilePaths = [];
+            targetFiles.forEach(file => {
+                targetFilePaths.push(file.filepath);
+            });
+
+            //Unzip and open them to tmp:/
+            var oprConfig = {
+                opr: "unzipAndOpen",
+                src: targetFilePaths,
+                dest: "tmp:/tmp_" + Date.now() + "/",
+                overwriteMode: "overwrite",
+            }
+
+            //Render the dialog title name
+            var configHash = encodeURIComponent(JSON.stringify(oprConfig));
+            var title = "Unzipping " + targetFilePaths.length;
+            if (targetFilePaths.length > 1){
+                title += " files";
+            }else{
+                title += " file";
+            }
+
+            //Open the operation dialog in new floatWindow
+            ao_module_newfw({
+                url: "SystemAO/file_system/file_operation.html#" + configHash,
+                width: 400,
+                height: 220,
+                appicon: "SystemAO/file_system/img/zip_extractor.png",
+                title: title
+            });
+
+            //CLose this window
+            ao_module_close();
+        
+        </script>
+    </body>
 </html>

+ 575 - 574
web/SystemAO/iot/hub/index.html

@@ -1,575 +1,576 @@
-<html>
-<head>
-    <meta name="apple-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">
-    <title>ArozOS IoT Hub</title>
-    <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
-    <link rel="manifest" href="manifest.json">
-	<script src="../../../script/jquery.min.js"></script>
-    <script src="../../../script/semantic/semantic.min.js"></script>
-    <script src="../../../script/ao_module.js"></script>
-    <style>
-        .ultrasmall.image{
-            height:35px;
-            margin:0px !important;
-            margin-right:10px !important;
-        }
-        .selectable{
-            cursor:pointer;
-        }
-        .selectable:hover{
-            background-color:#f0f0f0;
-            
-        }
-        .noborder{
-            border: 1px solid transparent !important;
-        }
-        .controlBtn{
-            position:absolute;
-            right:8px;
-            bottom:8px;
-        }
-        .devIcon{
-            border-radius: 10px;
-        }
-        .primary.button{
-            background-color: #4aa9eb !important;
-        }
-        .bottom.item{
-            position:absolute;
-            bottom: 0px;
-            left:0px;
-            width:100%;
-            font-size:80%;
-        }
-        #sideMenu{
-            height: calc(100% - 85px);
-        }
-		.primary.button{
-			background-color: #4287f5 !important;
-		}
-        body{
-            height:100%;
-            background:rgba(247,247,247,0.95);
-        }
-
-		.popupInterface{
-			height:80%;
-			width:95%;
-			overflow-y:auto;
-		}
-    </style>
-</head>
-
-
-<body>
-	<div id="sidemenu" class="ui right vertical menu sidebar">
-	   <div class="item">
-		  <div class="ui header">
-			 ArozOS IoT Hub
-			 <div class="sub header">Universal IoT Controller</div>
-		  </div>
-	   </div>
-	   <a class="selectable item" onClick="loadDevList();hideSideMenu();">
-	   <i class="refresh icon"></i> Refresh List
-	   </a>
-	   <a class="selectable item" onClick="scanDevices();hideSideMenu();">
-	   <i class="search icon"></i> Scan Devices
-	   </a>
-	   <div class="bottom item">
-		  CopyRight ArozOS Project 2020 - <span class="thisYear"></span>
-	   </div>
-	</div>
-	<div class="pusher">
-	   <div class="ui menu">
-		  <a class="item noborder" href="index.html"><img class="ui ultrasmall circular image" src="img/menu_icon.svg"> IoT Hub</a>
-		  <a class="right item" onClick="toggleSideMenu();"><i class="content icon"></i></a>
-	   </div>
-	   <div id="devList" class="ui container">
-			<div class="ui basic segment HDSDev">
-				<div class="ui grid">
-					<div class="four wide column"><img class="ui tiny devIcon image" src="img/system/loading.gif"></div>
-					<div class="twelve wide column">
-						<div class="ui container">
-							<div class="ui header">
-								<span class="devHeader">Scanning</span>
-								<div class="sub devProperty header">IoT Hub take a while to scan your network for the first startup.</div>
-							</div>
-						</div>
-					</div>
-				</div>
-			</div>
-	   </div>
-	   <br><br><br>
-	</div>
-
-	<!-- Show more information about thsi device-->
-	<div id="moreInfoInterface" class="ui active dimmer" style="display:none;">
-	   <div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#moreInfoInterface").fadeOut("fast");'>
-	   </div>
-	   <div id="informationItnerface" class="ui segment mainUI popupInterface" style="text-align: left;">
-		  <div class="ui header">
-			 Device Properties
-		  </div>
-		  <br>
-		  <div class="ui horizontal form">
-			 <div class="field">
-				<label>Device UUID</label>
-				<input id="duid" type="text" readonly="true">
-			 </div>
-			 <div class="field">
-				<label>IP Address</label>
-				<input id="ipaddr" type="text"  readonly="true">
-			 </div>
-			 <div class="field">
-				<label>Communication Port</label>
-				<input id="comport" type="text"  readonly="true">
-			 </div>
-			 <div class="field">
-				<label>Manufacturer</label>
-				<input id="manufacturer" type="text"  readonly="true">
-			 </div>
-			 <div class="field">
-				<label>Version</label>
-				<input id="version" type="text"  readonly="true">
-			 </div>
-		  </div>
-		  <br>
-		  <button class="ui primary button"  onClick='$("#moreInfoInterface").fadeOut("fast");'>Close</button>
-		  <br><br>
-	   </div>
-	</div>
-
-	<!-- Editing can be done on this device -->
-	<div id="editInterface" class="ui active dimmer" style="display:none;">
-		<div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#editInterface").fadeOut("fast");'>
-		</div>
-		<div class="ui segment mainUI popupInterface" >
-			<div class="ui header">
-				Edit Device Records
-			</div>
-			<p>Set Device Nickname</p>
-			<div class="ui action fluid small input">
-				<input class="deviceNickname" type="text" uuid="" placeholder="New Nickname" autocomplete="off">
-				<button class="ui positive button" onclick="setNickName(this)">Update</button>
-			</div>
-			<div class="ui inverted positive segment nicnameSetConfirm" style="display:none;">
-				<p><i class="checkmark icon"></i> Device Nickname Updated</p>
-			</div>
-
-			<div class="ui divider"></div>
-			<br>
-			<button class="ui primary button"  onClick='$("#editInterface").fadeOut("fast");'>Close</button>
-			<br><br>
-
-		</div>
-	</div>
-
-	<!-- Action can be done on this device -->
-	<div id="actioninterface" class="ui active dimmer" style="display:none;">
-		<div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#actioninterface").fadeOut("fast");'>
-		</div>
-		<div id="informationItnerface" class="ui segment mainUI popupInterface" style="text-align: left;">
-		   <div class="ui header">
-			  Device Actions
-		   </div>
-		   <br>
-		   <div class="ui form" id="statusList">
-				<h3><i class="ui loading spinner icon"></i> Loading</h3>
-		   </div>
-		   <div class="ui divider"></div>
-		   <div class="ui horizontal form" id="actionForm">
-
-			</div>
-			<br>
-			<button class="ui primary button"  onClick='$("#actioninterface").fadeOut("fast");'>Close</button>
-			<br><br>
-		</div>
-	</div>
-
-	<div id="loadingMask" class="ui active dimmer" style="display:none;">
-	   <div class="ui text loader">Scanning in Progress</div>
-	</div> 
-	<script>
-		var currentlyViewingDevices = "";
-		var uselocal = false; //Use Local as command sender or use Host as command sender
-		var username = $("#data_session_username").text().trim();
-		//ao_module Float Window functions
-		ao_module_setWindowTitle("IoT Hub");
-		ao_module_setWindowSize(465,730,true);
-		if (!ao_module_virtualDesktop){
-			$("body").css("background-color","white");
-		}
-
-		//Initiate the page content
-		loadDevList();
-
-			
-		function inputbox(message, placeholder = ""){
-			var input = prompt(message, placeholder);
-			if (input != null) {
-			return input;
-			}else{
-			return false;
-			}
-		}
-
-
-		function scanDevices(){
-			$("#loadingMask").show();
-			$.get("../../../system/iot/scan", function(data){
-				loadDevList();
-				$("#loadingMask").hide();
-			});
-		}
-
-
-		function hideSideMenu(){
-			$('.right.sidebar').sidebar('hide');
-		}
-
-		function showMore(object){
-			var device = $(object).parent().parent();
-			var duid = device.attr("uuid");
-			var lastseen = device.attr("devip");
-			var deviceData = device.attr("devicedata");
-			deviceData = JSON.parse(decodeURIComponent(deviceData))
-			console.log(deviceData);
-			$("#duid").val(duid);
-			$("#ipaddr").val(lastseen);
-			$("#comport").val(deviceData.Port);
-			$("#manufacturer").val(deviceData.Manufacturer);
-			$("#version").val(deviceData.Version);
-			currentlyViewingDevices = duid;
-			$("#moreInfoInterface").fadeIn('fast');
-		}
-
-		function loadDevList(){
-			$.get("../../../system/iot/list", function(data){
-				$("#devList").html("");
-				if (data.error !== undefined){
-					alert(data.error);
-				}else{
-					data.forEach(device => {
-						var deviceData = encodeURIComponent(JSON.stringify(device));
-						$("#devList").append(`<div class="ui segment HDSDev" devicedata="${deviceData}" uuid="${device.DeviceUUID}" devIp="${device.IPAddr}" port="${device.Port}" location="local">
-							<div class="ui grid">
-								<div class="four wide column"><img class="ui tiny devIcon image" src="../../../system/iot/icon?devid=${device.DeviceUUID}"></div>
-								<div class="twelve wide column">
-									<div class="ui container">
-										<div class="ui header">
-											<span class="devHeader">${device.Name}</span>
-											<div class="sub devProperty header">${device.Model}</div>
-										</div>
-									</div>
-								</div>
-							</div>
-							<div class="controlBtn infoMount">
-								<button class="ui icon button" title="Edit Device Nickname" onClick="edit(this);"><i class="edit icon"></i></button>
-								<button class="ui icon button" title="Show More" onClick="showMore(this);"><i class="info circle icon"></i></button>
-								<button class="ui primary icon button" title="Actions" onClick="action(this);"><i class="options icon"></i></button>
-							</div>
-						</div>`);
-					});
-
-					if (data.length == 0){
-						//No device found
-						$("#devList").html(`<div class="ui basic segment HDSDev">
-							<div class="ui grid">
-								<div class="four wide column"><img class="ui tiny devIcon image" src="img/system/not-found.svg"></div>
-								<div class="twelve wide column">
-									<div class="ui container">
-										<div class="ui header">
-											<span class="devHeader">No Device Found</span>
-											<div class="sub devProperty header">Want to create your own IoT devices with ESP8266 & Arduino IDE? <br>Check out the <a href="https://github.com/tobychui/Home-Dynamic" target="_blank">HomeDynamic</a> System!</div>
-										</div>
-									</div>
-								</div>
-							</div>
-						</div>`);
-					}
-
-					//Load the nickname of each device if it exists
-					$(".HDSDev").each(function(){
-						//Get iui nickname 
-						var devUUID = $(this).attr("uuid");
-						var targetDOMElement = $(this);
-						$.ajax({
-							url: "../../../system/iot/nickname",
-							data: {opr: "get", uuid: devUUID},
-							success: function(data){
-								if (data.error == undefined){
-									//No error. Render it to the header
-									var currentHeader = $(targetDOMElement).find(".devHeader").text();
-									var newHeader = data + " (" + currentHeader + ") ";
-									$(targetDOMElement).find(".devHeader").text(newHeader);
-								}
-							}
-						});
-					})
-					
-				}
-			});
-		}
-
-		function toggleSideMenu(){
-			$("#sidemenu").sidebar('toggle');
-		}
-
-		function setNickName(input){
-			var inputValue = $(input).parent().find(".deviceNickname").val();
-			var devUUID = $(input).parent().find(".deviceNickname").attr("uuid");
-			if (inputValue !== ""){
-				//Set the new value
-				$.ajax({
-					url: "../../../system/iot/nickname",
-					data: {opr: "set", uuid: devUUID, name: inputValue},
-					success: function(data){
-						if (data.error !== undefined){
-							alert(data.error);
-						}else{
-							//OK!
-							$("#editInterface").find(".nicnameSetConfirm").slideDown("fast").delay(3000).slideUp("fast");
-
-							//Update the device list
-							loadDevList();
-						}
-					}
-				});
-			}
-		}
-
-		function edit(object){
-			//get the device UUID
-			var devdata = $(object).parent().parent().attr("devicedata");
-			devdata = JSON.parse(decodeURIComponent(devdata));
-			var devUUID = devdata.DeviceUUID;
-			$("#editInterface").find(".deviceNickname").attr("uuid",devUUID);
-
-			//Get the device nickname from server side
-			$.ajax({
-				url: "../../../system/iot/nickname",
-				data: {opr: "get", uuid: devUUID},
-				success: function(data){
-					if (data.error !== undefined){
-						//Nickname not set yet. 
-						$("#editInterface").find(".deviceNickname").val("");
-					}else{
-						$("#editInterface").find(".deviceNickname").val(data);
-					}
-				}
-			})
-
-			//Show the interface
-			$("#editInterface").fadeIn('fast');
-
-
-
-		}
-
-		function executeEndpoint(object, targetValue=""){
-			var deviceID = $(object).attr("devid");
-			var epd = JSON.parse(decodeURIComponent($(object).attr("epd")));
-			if (epd.Type == "integer" || epd.Type == "float"){
-				//Check if it is in range
-				if (epd.Max && targetValue > epd.Max ){
-					//Snap to max value if over max
-					targetValue = epd.Max;
-				}
-
-				if (epd.Min && targetValue < epd.Min){
-					//Snap to min value if under min
-					targetValue = epd.Min;
-				}
-			}else if (epd.Type == "string"){
-				//Check if regex
-				if (epd.Regex && targetValue.match(stringToRegex(epd.Regex)) == null){
-					//Invalid string input. Reject operation
-					alert("Input string does not match request regex structure: " + epd.Regex);
-					return;
-				}
-			}
-
-			//Request the backend to activate the endpoint
-			console.log(deviceID, epd, targetValue);
-			$.ajax({
-				url: "../../../system/iot/execute",
-				data: {devid: deviceID, eptname: epd.Name, payload: targetValue},
-				success: function(data){
-					if (data.error !== undefined){
-						alert(data.error);
-					}else{
-						//OK. Reload status
-						updateStatus(deviceID);
-					}
-				}
-			})
-		}
-
-		const stringToRegex = str => {
-			// Main regex
-			const main = str.match(/\/(.+)\/.*/)[1]
-			
-			// Regex options
-			const options = str.match(/\/.+\/(.*)/)[1]
-			
-			// Compiled regex
-			return new RegExp(main, options)
-		}
-
-		function action(object){
-			//Clear the action form
-			$("#actionForm").html("");
-
-			//Generate the list of endpoinui from the device data
-			var device = $(object).parent().parent();
-			var deviceData = device.attr("devicedata");
-			deviceData = JSON.parse(decodeURIComponent(deviceData));
-
-			var epts = deviceData.ControlEndpoints;
-			if (epts == null || typeof(epts) == undefined || epts.length == 0 ){
-				//This device has no control endpoints. Show status info only.
-				updateStatus(deviceData.DeviceUUID);
-			}else{
-				epts.forEach(ept => {
-					//Check which type of ept is this. Accept {string, integer, float, bool, none}
-					var encodedEptData = encodeURIComponent(JSON.stringify(ept));
-					var deviceID = deviceData.DeviceUUID;
-					var name = ept.Name;
-					if (ept.Type == "string"){
-						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
-							<label>${ept.Desc}</label>
-							<div class="ui action input">
-								<input type="text" placeholder="${name}">
-								<button class="ui primary icon button" title="Send" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)"><i class="send icon"></i></button>
-							</div>
-						</div>`);
-					}else if (ept.Type == "integer"){
-						var min = "";
-						var max = "";
-						if (ept.Min != undefined){
-							min = ept.Min;
-						}
-
-						if (ept.Max != undefined){
-							max = ept.Max;
-						}
-
-						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
-							<label>${ept.Desc}</label>
-							<div class="ui action input">
-								<input type="number" min="${min}" max="${max}" placeholder="${name}">
-								<button class="ui primary icon button" title="Send" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)"><i class="send icon"></i></button>
-							</div>
-						</div>`);
-					}else if (ept.Type == "float"){
-						var min = "";
-						var max = "";
-						var step = "0.1";
-						if (ept.Min != undefined){
-							min = ept.Min;
-						}
-
-						if (ept.Max != undefined){
-							max = ept.Max;
-						}
-
-						if (ept.Steps != undefined){
-							step = ept.Steps;
-						}
-
-						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
-							<label>${ept.Desc}</label>
-							<div class="ui action input">
-								<input type="number" min="${min}" max="${max}" step="${step}" placeholder="${name}">
-								<button class="ui primary icon button" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)" title="Send"><i class="send icon"></i></button>
-							</div>
-						</div>`);
-					}else if (ept.Type == "bool"){
-						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
-							<div class="ui toggle checkbox">
-								<input type="checkbox" id="${encodeURIComponent(ept.Name)}" onchange="executeEndpoint(this.parentNode.parentNode, this.checked);">
-								<label for="${encodeURIComponent(ept.Name)}">${ept.Name}</label>
-						</div></div>`);
-						
-					}else if (ept.Type == "none"){
-						//No action. (aka just a GET request endpoint)
-						$("#actionForm").append(`<div devid="${deviceID}" epd="${encodedEptData}" class="field">
-							<button class="ui info fluid button" title="${ept.Desc}" onclick="executeEndpoint(this.parentNode);">${name}</button>
-						</div>`);
-					}
-				});
-
-				updateStatus(deviceData.DeviceUUID);
-			}
-
-			console.log(deviceData);
-
-			$("#actioninterface").fadeIn('fast');
-		}
-
-		function updateStatus(deviceUUID){
-			//Get iui status
-			$.ajax({
-					url: "../../../system/iot/status",
-					data: {devid: deviceUUID},
-					success: function(data){
-						//Look for fields that have the same name. If not, append it to status field
-						if (data.error !== undefined){
-							$("#statusList").html(`<h3>Connection Lost</h3><br><p>${data.error}</p>`);
-						}else{
-							//OK! Append it
-							$("#statusList").html("");
-							for (var key in data) {
-								var found = false;
-								$("#actionForm").find(".field").each(function(){
-									var thisName = $(this).attr("name");
-									if (thisName == key){
-										//Set iui value
-										var targetInput = $(this).find("input");
-										if (targetInput.attr('type') == "checkbox"){
-											//For handling checkbox
-											if (data[key] == true){
-												targetInput[0].checked = true;
-											}else{
-												targetInput[0].checked = false;
-											}
-										}else{
-											//For handling other input fields
-											$(this).find("input").val(data[key]);
-										}
-										
-										found = true;
-									}
-								});
-
-								if (found == false){
-									//Append to status field
-									$("#statusList").append(`<div class="ui header">
-										${data[key]}
-										<div class="sub header">${key}</div>
-									</div>`);
-								}
-							}
-
-						}
-					}
-				})
-		}
-
-		function updateIframeSize(){
-			$("#controlUI").attr("width",$("#actionMainUI").width());
-			$("#controlUI").attr("height",$("#actionMainUI").height());
-			$("#controlUI").css("width",$("#actionMainUI").width());
-			$("#controlUI").css("height",$("#actionMainUI").height());
-		}
-
-		$(window).on("resize",function(){
-			updateIframeSize();
-		});
-
-		$(".thisYear").text(new Date().getFullYear());
-	</script>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="apple-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">
+    <title>ArozOS IoT Hub</title>
+    <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
+    <link rel="manifest" href="manifest.json">
+	<script src="../../../script/jquery.min.js"></script>
+    <script src="../../../script/semantic/semantic.min.js"></script>
+    <script src="../../../script/ao_module.js"></script>
+    <style>
+        .ultrasmall.image{
+            height:35px;
+            margin:0px !important;
+            margin-right:10px !important;
+        }
+        .selectable{
+            cursor:pointer;
+        }
+        .selectable:hover{
+            background-color:#f0f0f0;
+            
+        }
+        .noborder{
+            border: 1px solid transparent !important;
+        }
+        .controlBtn{
+            position:absolute;
+            right:8px;
+            bottom:8px;
+        }
+        .devIcon{
+            border-radius: 10px;
+        }
+        .primary.button{
+            background-color: #4aa9eb !important;
+        }
+        .bottom.item{
+            position:absolute;
+            bottom: 0px;
+            left:0px;
+            width:100%;
+            font-size:80%;
+        }
+        #sideMenu{
+            height: calc(100% - 85px);
+        }
+		.primary.button{
+			background-color: #4287f5 !important;
+		}
+        body{
+            height:100%;
+            background:rgba(247,247,247,0.95);
+        }
+
+		.popupInterface{
+			height:80%;
+			width:95%;
+			overflow-y:auto;
+		}
+    </style>
+</head>
+
+
+<body>
+	<div id="sidemenu" class="ui right vertical menu sidebar">
+	   <div class="item">
+		  <div class="ui header">
+			 ArozOS IoT Hub
+			 <div class="sub header">Universal IoT Controller</div>
+		  </div>
+	   </div>
+	   <a class="selectable item" onClick="loadDevList();hideSideMenu();">
+	   <i class="refresh icon"></i> Refresh List
+	   </a>
+	   <a class="selectable item" onClick="scanDevices();hideSideMenu();">
+	   <i class="search icon"></i> Scan Devices
+	   </a>
+	   <div class="bottom item">
+		  CopyRight ArozOS Project 2020 - <span class="thisYear"></span>
+	   </div>
+	</div>
+	<div class="pusher">
+	   <div class="ui menu">
+		  <a class="item noborder" href="index.html"><img class="ui ultrasmall circular image" src="img/menu_icon.svg"> IoT Hub</a>
+		  <a class="right item" onClick="toggleSideMenu();"><i class="content icon"></i></a>
+	   </div>
+	   <div id="devList" class="ui container">
+			<div class="ui basic segment HDSDev">
+				<div class="ui grid">
+					<div class="four wide column"><img class="ui tiny devIcon image" src="img/system/loading.gif"></div>
+					<div class="twelve wide column">
+						<div class="ui container">
+							<div class="ui header">
+								<span class="devHeader">Scanning</span>
+								<div class="sub devProperty header">IoT Hub take a while to scan your network for the first startup.</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+	   </div>
+	   <br><br><br>
+	</div>
+
+	<!-- Show more information about thsi device-->
+	<div id="moreInfoInterface" class="ui active dimmer" style="display:none;">
+	   <div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#moreInfoInterface").fadeOut("fast");'>
+	   </div>
+	   <div id="informationItnerface" class="ui segment mainUI popupInterface" style="text-align: left;">
+		  <div class="ui header">
+			 Device Properties
+		  </div>
+		  <br>
+		  <div class="ui horizontal form">
+			 <div class="field">
+				<label>Device UUID</label>
+				<input id="duid" type="text" readonly="true">
+			 </div>
+			 <div class="field">
+				<label>IP Address</label>
+				<input id="ipaddr" type="text"  readonly="true">
+			 </div>
+			 <div class="field">
+				<label>Communication Port</label>
+				<input id="comport" type="text"  readonly="true">
+			 </div>
+			 <div class="field">
+				<label>Manufacturer</label>
+				<input id="manufacturer" type="text"  readonly="true">
+			 </div>
+			 <div class="field">
+				<label>Version</label>
+				<input id="version" type="text"  readonly="true">
+			 </div>
+		  </div>
+		  <br>
+		  <button class="ui primary button"  onClick='$("#moreInfoInterface").fadeOut("fast");'>Close</button>
+		  <br><br>
+	   </div>
+	</div>
+
+	<!-- Editing can be done on this device -->
+	<div id="editInterface" class="ui active dimmer" style="display:none;">
+		<div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#editInterface").fadeOut("fast");'>
+		</div>
+		<div class="ui segment mainUI popupInterface" >
+			<div class="ui header">
+				Edit Device Records
+			</div>
+			<p>Set Device Nickname</p>
+			<div class="ui action fluid small input">
+				<input class="deviceNickname" type="text" uuid="" placeholder="New Nickname" autocomplete="off">
+				<button class="ui positive button" onclick="setNickName(this)">Update</button>
+			</div>
+			<div class="ui inverted positive segment nicnameSetConfirm" style="display:none;">
+				<p><i class="checkmark icon"></i> Device Nickname Updated</p>
+			</div>
+
+			<div class="ui divider"></div>
+			<br>
+			<button class="ui primary button"  onClick='$("#editInterface").fadeOut("fast");'>Close</button>
+			<br><br>
+
+		</div>
+	</div>
+
+	<!-- Action can be done on this device -->
+	<div id="actioninterface" class="ui active dimmer" style="display:none;">
+		<div style="position:absolute;width:100%;height:100%;left:0px;top:0px;" onClick='$("#actioninterface").fadeOut("fast");'>
+		</div>
+		<div id="informationItnerface" class="ui segment mainUI popupInterface" style="text-align: left;">
+		   <div class="ui header">
+			  Device Actions
+		   </div>
+		   <br>
+		   <div class="ui form" id="statusList">
+				<h3><i class="ui loading spinner icon"></i> Loading</h3>
+		   </div>
+		   <div class="ui divider"></div>
+		   <div class="ui horizontal form" id="actionForm">
+
+			</div>
+			<br>
+			<button class="ui primary button"  onClick='$("#actioninterface").fadeOut("fast");'>Close</button>
+			<br><br>
+		</div>
+	</div>
+
+	<div id="loadingMask" class="ui active dimmer" style="display:none;">
+	   <div class="ui text loader">Scanning in Progress</div>
+	</div> 
+	<script>
+		var currentlyViewingDevices = "";
+		var uselocal = false; //Use Local as command sender or use Host as command sender
+		var username = $("#data_session_username").text().trim();
+		//ao_module Float Window functions
+		ao_module_setWindowTitle("IoT Hub");
+		ao_module_setWindowSize(465,730,true);
+		if (!ao_module_virtualDesktop){
+			$("body").css("background-color","white");
+		}
+
+		//Initiate the page content
+		loadDevList();
+
+			
+		function inputbox(message, placeholder = ""){
+			var input = prompt(message, placeholder);
+			if (input != null) {
+			return input;
+			}else{
+			return false;
+			}
+		}
+
+
+		function scanDevices(){
+			$("#loadingMask").show();
+			$.get("../../../system/iot/scan", function(data){
+				loadDevList();
+				$("#loadingMask").hide();
+			});
+		}
+
+
+		function hideSideMenu(){
+			$('.right.sidebar').sidebar('hide');
+		}
+
+		function showMore(object){
+			var device = $(object).parent().parent();
+			var duid = device.attr("uuid");
+			var lastseen = device.attr("devip");
+			var deviceData = device.attr("devicedata");
+			deviceData = JSON.parse(decodeURIComponent(deviceData))
+			console.log(deviceData);
+			$("#duid").val(duid);
+			$("#ipaddr").val(lastseen);
+			$("#comport").val(deviceData.Port);
+			$("#manufacturer").val(deviceData.Manufacturer);
+			$("#version").val(deviceData.Version);
+			currentlyViewingDevices = duid;
+			$("#moreInfoInterface").fadeIn('fast');
+		}
+
+		function loadDevList(){
+			$.get("../../../system/iot/list", function(data){
+				$("#devList").html("");
+				if (data.error !== undefined){
+					alert(data.error);
+				}else{
+					data.forEach(device => {
+						var deviceData = encodeURIComponent(JSON.stringify(device));
+						$("#devList").append(`<div class="ui segment HDSDev" devicedata="${deviceData}" uuid="${device.DeviceUUID}" devIp="${device.IPAddr}" port="${device.Port}" location="local">
+							<div class="ui grid">
+								<div class="four wide column"><img class="ui tiny devIcon image" src="../../../system/iot/icon?devid=${device.DeviceUUID}"></div>
+								<div class="twelve wide column">
+									<div class="ui container">
+										<div class="ui header">
+											<span class="devHeader">${device.Name}</span>
+											<div class="sub devProperty header">${device.Model}</div>
+										</div>
+									</div>
+								</div>
+							</div>
+							<div class="controlBtn infoMount">
+								<button class="ui icon button" title="Edit Device Nickname" onClick="edit(this);"><i class="edit icon"></i></button>
+								<button class="ui icon button" title="Show More" onClick="showMore(this);"><i class="info circle icon"></i></button>
+								<button class="ui primary icon button" title="Actions" onClick="action(this);"><i class="options icon"></i></button>
+							</div>
+						</div>`);
+					});
+
+					if (data.length == 0){
+						//No device found
+						$("#devList").html(`<div class="ui basic segment HDSDev">
+							<div class="ui grid">
+								<div class="four wide column"><img class="ui tiny devIcon image" src="img/system/not-found.svg"></div>
+								<div class="twelve wide column">
+									<div class="ui container">
+										<div class="ui header">
+											<span class="devHeader">No Device Found</span>
+											<div class="sub devProperty header">Want to create your own IoT devices with ESP8266 & Arduino IDE? <br>Check out the <a href="https://github.com/tobychui/Home-Dynamic" target="_blank">HomeDynamic</a> System!</div>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>`);
+					}
+
+					//Load the nickname of each device if it exists
+					$(".HDSDev").each(function(){
+						//Get iui nickname 
+						var devUUID = $(this).attr("uuid");
+						var targetDOMElement = $(this);
+						$.ajax({
+							url: "../../../system/iot/nickname",
+							data: {opr: "get", uuid: devUUID},
+							success: function(data){
+								if (data.error == undefined){
+									//No error. Render it to the header
+									var currentHeader = $(targetDOMElement).find(".devHeader").text();
+									var newHeader = data + " (" + currentHeader + ") ";
+									$(targetDOMElement).find(".devHeader").text(newHeader);
+								}
+							}
+						});
+					})
+					
+				}
+			});
+		}
+
+		function toggleSideMenu(){
+			$("#sidemenu").sidebar('toggle');
+		}
+
+		function setNickName(input){
+			var inputValue = $(input).parent().find(".deviceNickname").val();
+			var devUUID = $(input).parent().find(".deviceNickname").attr("uuid");
+			if (inputValue !== ""){
+				//Set the new value
+				$.ajax({
+					url: "../../../system/iot/nickname",
+					data: {opr: "set", uuid: devUUID, name: inputValue},
+					success: function(data){
+						if (data.error !== undefined){
+							alert(data.error);
+						}else{
+							//OK!
+							$("#editInterface").find(".nicnameSetConfirm").slideDown("fast").delay(3000).slideUp("fast");
+
+							//Update the device list
+							loadDevList();
+						}
+					}
+				});
+			}
+		}
+
+		function edit(object){
+			//get the device UUID
+			var devdata = $(object).parent().parent().attr("devicedata");
+			devdata = JSON.parse(decodeURIComponent(devdata));
+			var devUUID = devdata.DeviceUUID;
+			$("#editInterface").find(".deviceNickname").attr("uuid",devUUID);
+
+			//Get the device nickname from server side
+			$.ajax({
+				url: "../../../system/iot/nickname",
+				data: {opr: "get", uuid: devUUID},
+				success: function(data){
+					if (data.error !== undefined){
+						//Nickname not set yet. 
+						$("#editInterface").find(".deviceNickname").val("");
+					}else{
+						$("#editInterface").find(".deviceNickname").val(data);
+					}
+				}
+			})
+
+			//Show the interface
+			$("#editInterface").fadeIn('fast');
+
+
+
+		}
+
+		function executeEndpoint(object, targetValue=""){
+			var deviceID = $(object).attr("devid");
+			var epd = JSON.parse(decodeURIComponent($(object).attr("epd")));
+			if (epd.Type == "integer" || epd.Type == "float"){
+				//Check if it is in range
+				if (epd.Max && targetValue > epd.Max ){
+					//Snap to max value if over max
+					targetValue = epd.Max;
+				}
+
+				if (epd.Min && targetValue < epd.Min){
+					//Snap to min value if under min
+					targetValue = epd.Min;
+				}
+			}else if (epd.Type == "string"){
+				//Check if regex
+				if (epd.Regex && targetValue.match(stringToRegex(epd.Regex)) == null){
+					//Invalid string input. Reject operation
+					alert("Input string does not match request regex structure: " + epd.Regex);
+					return;
+				}
+			}
+
+			//Request the backend to activate the endpoint
+			console.log(deviceID, epd, targetValue);
+			$.ajax({
+				url: "../../../system/iot/execute",
+				data: {devid: deviceID, eptname: epd.Name, payload: targetValue},
+				success: function(data){
+					if (data.error !== undefined){
+						alert(data.error);
+					}else{
+						//OK. Reload status
+						updateStatus(deviceID);
+					}
+				}
+			})
+		}
+
+		const stringToRegex = str => {
+			// Main regex
+			const main = str.match(/\/(.+)\/.*/)[1]
+			
+			// Regex options
+			const options = str.match(/\/.+\/(.*)/)[1]
+			
+			// Compiled regex
+			return new RegExp(main, options)
+		}
+
+		function action(object){
+			//Clear the action form
+			$("#actionForm").html("");
+
+			//Generate the list of endpoinui from the device data
+			var device = $(object).parent().parent();
+			var deviceData = device.attr("devicedata");
+			deviceData = JSON.parse(decodeURIComponent(deviceData));
+
+			var epts = deviceData.ControlEndpoints;
+			if (epts == null || typeof(epts) == undefined || epts.length == 0 ){
+				//This device has no control endpoints. Show status info only.
+				updateStatus(deviceData.DeviceUUID);
+			}else{
+				epts.forEach(ept => {
+					//Check which type of ept is this. Accept {string, integer, float, bool, none}
+					var encodedEptData = encodeURIComponent(JSON.stringify(ept));
+					var deviceID = deviceData.DeviceUUID;
+					var name = ept.Name;
+					if (ept.Type == "string"){
+						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
+							<label>${ept.Desc}</label>
+							<div class="ui action input">
+								<input type="text" placeholder="${name}">
+								<button class="ui primary icon button" title="Send" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)"><i class="send icon"></i></button>
+							</div>
+						</div>`);
+					}else if (ept.Type == "integer"){
+						var min = "";
+						var max = "";
+						if (ept.Min != undefined){
+							min = ept.Min;
+						}
+
+						if (ept.Max != undefined){
+							max = ept.Max;
+						}
+
+						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
+							<label>${ept.Desc}</label>
+							<div class="ui action input">
+								<input type="number" min="${min}" max="${max}" placeholder="${name}">
+								<button class="ui primary icon button" title="Send" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)"><i class="send icon"></i></button>
+							</div>
+						</div>`);
+					}else if (ept.Type == "float"){
+						var min = "";
+						var max = "";
+						var step = "0.1";
+						if (ept.Min != undefined){
+							min = ept.Min;
+						}
+
+						if (ept.Max != undefined){
+							max = ept.Max;
+						}
+
+						if (ept.Steps != undefined){
+							step = ept.Steps;
+						}
+
+						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
+							<label>${ept.Desc}</label>
+							<div class="ui action input">
+								<input type="number" min="${min}" max="${max}" step="${step}" placeholder="${name}">
+								<button class="ui primary icon button" onclick="executeEndpoint(this.parentNode.parentNode, this.parentNode.parentNode.getElementsByTagName('input')[0].value)" title="Send"><i class="send icon"></i></button>
+							</div>
+						</div>`);
+					}else if (ept.Type == "bool"){
+						$("#actionForm").append(`<div name="${ept.Name}" devid="${deviceID}" epd="${encodedEptData}" class="field">
+							<div class="ui toggle checkbox">
+								<input type="checkbox" id="${encodeURIComponent(ept.Name)}" onchange="executeEndpoint(this.parentNode.parentNode, this.checked);">
+								<label for="${encodeURIComponent(ept.Name)}">${ept.Name}</label>
+						</div></div>`);
+						
+					}else if (ept.Type == "none"){
+						//No action. (aka just a GET request endpoint)
+						$("#actionForm").append(`<div devid="${deviceID}" epd="${encodedEptData}" class="field">
+							<button class="ui info fluid button" title="${ept.Desc}" onclick="executeEndpoint(this.parentNode);">${name}</button>
+						</div>`);
+					}
+				});
+
+				updateStatus(deviceData.DeviceUUID);
+			}
+
+			console.log(deviceData);
+
+			$("#actioninterface").fadeIn('fast');
+		}
+
+		function updateStatus(deviceUUID){
+			//Get iui status
+			$.ajax({
+					url: "../../../system/iot/status",
+					data: {devid: deviceUUID},
+					success: function(data){
+						//Look for fields that have the same name. If not, append it to status field
+						if (data.error !== undefined){
+							$("#statusList").html(`<h3>Connection Lost</h3><br><p>${data.error}</p>`);
+						}else{
+							//OK! Append it
+							$("#statusList").html("");
+							for (var key in data) {
+								var found = false;
+								$("#actionForm").find(".field").each(function(){
+									var thisName = $(this).attr("name");
+									if (thisName == key){
+										//Set iui value
+										var targetInput = $(this).find("input");
+										if (targetInput.attr('type') == "checkbox"){
+											//For handling checkbox
+											if (data[key] == true){
+												targetInput[0].checked = true;
+											}else{
+												targetInput[0].checked = false;
+											}
+										}else{
+											//For handling other input fields
+											$(this).find("input").val(data[key]);
+										}
+										
+										found = true;
+									}
+								});
+
+								if (found == false){
+									//Append to status field
+									$("#statusList").append(`<div class="ui header">
+										${data[key]}
+										<div class="sub header">${key}</div>
+									</div>`);
+								}
+							}
+
+						}
+					}
+				})
+		}
+
+		function updateIframeSize(){
+			$("#controlUI").attr("width",$("#actionMainUI").width());
+			$("#controlUI").attr("height",$("#actionMainUI").height());
+			$("#controlUI").css("width",$("#actionMainUI").width());
+			$("#controlUI").css("height",$("#actionMainUI").height());
+		}
+
+		$(window).on("resize",function(){
+			updateIframeSize();
+		});
+
+		$(".thisYear").text(new Date().getFullYear());
+	</script>
+</body>
 </html>

+ 268 - 267
web/SystemAO/modules/addAndRemove.html

@@ -1,268 +1,269 @@
-<html>
-    <head>
-        <title>Subservices</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-        <style>
-            .ui.active.segment{
-                background-color: #f5f5f5 !important;
-            }
-
-            .ui.segment.installedModule{
-                cursor: pointer;
-                margin: 0px !important;
-            }
-
-            .ui.segment.installedModule:hover{
-                background-color: #e3e3e3;
-            }
-        </style>
-    
-    </head>
-    <body>
-        <br>
-        <div class="ui container">
-            <div class="ui container">
-                <div class="ui header">
-                    <i class="add icon"></i>
-                    <div class="content">
-                        Add & Remove Module
-                        <div class="sub header">Install or Uninstall WebApp Modules from this Host</div>
-                    </div>
-                </div>
-            </div>
-            <div class="ui divider"></div>
-            <div class="ui red message" style="display:none;" id="errmsgbox">
-                <h4 class="ui header">
-                    <i class="remove icon"></i>
-                    <div class="content">
-                        Module Installation Failed
-                        <div class="sub header" id="errmsg"></div>
-                    </div>
-                </h4>
-            </div>
-            <div class="ui green message" style="display:none;" id="ok">
-                <h4 class="ui header">
-                    <i class="checkmark icon"></i>
-                    <div class="content">
-                        Module Installation Completed
-                        <div class="sub header" >You should be able to see your new module in the list below.</div>
-                    </div>
-                </h4>
-            </div>
-            <div class="ui blue segment">
-                <h4 class="ui header">
-                    New WebApp
-                    <div class="sub header">Download new WebApp to your ArozOS Host</div>
-                </h4>
-                <div class="ui divider"></div>
-                <h5>Install via Git Repository</h5>
-                <p>Enter the Github repo link in the field below and click "Install"</p>
-                <div class="ui small fluid action input" id="installViaGitInput">
-                    <input id="gitlink" type="text" placeholder="http://github.com/....">
-                    <button class="ui blue button" onclick="installViaGit();"><i class="download icon"></i> Install</button>
-                </div>
-                <div class="ui blue message" style="display:none;" id="installingDialog">
-                    <p><i class="ui loading spinner icon"></i> Module installing in the background. Please wait until this dialog is closed.</p>
-                </div>
-                <div class="ui divider"></div>
-                <h4>Install via Zip File</h4>
-              
-                
-                <p>Select your module zip file and upload it to the system for installation.</p>
-                <div class="ui small fluid action input">
-                    <input type="text" id="installpendingFile" placeholder="New Module.zip">
-                    <button class="ui button" onclick="selectInstaller()"><i class="folder open icon"></i> Select Installer</button>
-                    <button class="ui blue button" onclick="InstallViaZipFile(this)"><i class="zip icon" ></i> Install</button>
-                </div>
-                
-            
-            </div>
-            <div class="ui red segment">
-                <h4 class="ui header">
-                    Remove WebApp
-                    <div class="sub header">Remove WebApp from your ArozOS Host</div>
-                </h4>
-                <div class="ui green message" id="succ" style="display:none;">
-                    <i class="check icon"></i> WebApp module uninstalled successfully. 
-                </div>
-                <div class="ui divider"></div>
-                <div id="modulelist">
-                    No Module Installed
-                </div>
-            </div>
-        </div>
-
-        <div id="loadingUI" class="ui dimmer">
-            <div class="ui indeterminate text loader">Fetching Files</div>
-        </div>
-        <script>
-            var moduleList = [];
-
-            initModuleUninstallList();
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
-            }
-
-            function initModuleUninstallList(){
-                $.get("../../system/module/install", function(data){
-                    console.log(data);
-                    moduleList = data;
-                    $("#modulelist").html("");
-                    data.forEach(mod => {
-                        var uninstallButtonClass = "";
-                        if (mod.Uninstallable == false){
-                            uninstallButtonClass = "disabled"
-                        }
-                        $("#modulelist").append(`<div class="ui basic segment installedModule" onclick="selectThisModule(event, this);">
-                        <img class="ui top aligned image" style="margin-right: 12px; width: 50px;" src="../../${mod.IconPath}">
-                        <div style="display:inline-block;">
-                            <b>${mod.Name}</b>
-                            <p>${mod.Desc}</p>
-                            <div style="position: absolute;top: 0.6em; right: 2em; text-align: right;">
-                                <b>${bytesToSize(mod.DiskSpace)}</b><br>
-                                <span>${mod.InstallDate}</span>
-                            </div>
-                        </div>
-                        <div style="text-align: right; display:none;" class="actionField">
-                            <button class="ui small ${uninstallButtonClass} button" name="${mod.Name}" onclick="removeModule(event,this);">Uninstall</button>
-                            <div class="ui red message errordialog" style="text-align:left; display:none;">
-                                <i class="remove icon"></i> WebApp Removal Failed: <span class="errmsg"></span>
-                            </div>
-                        </div>
-                    </div> `);
-                    });
-                });
-            }
-
-            function removeModule(e, btn){
-                var modulename = $(btn).attr("name");
-                //Ask for confirmation
-                if (confirm("Confirm permanently remove " + modulename + " ?")){
-                    $.ajax({
-                        url: "../../system/module/install",
-                        data: {opr: "remove", module: modulename},
-                        success: function(data){
-                            if (data.error !== undefined){
-                                $(btn).parent().find(".errmsg").text(data.error);
-                                $(btn).parent().find(".errordialog").slideDown("fast").delay(10000).slideUp("fast");
-                            }else{
-                                //Reload list
-                                initModuleUninstallList();
-
-                                //Reload desktop module list
-                                if (parent && parent.initModuleList != undefined){
-                                    parent.initModuleList();
-                                }
-
-                                $("#succ").slideDown("fast").delay(3000).slideUp('fast');
-                            }
-
-                        }
-                    });
-                }
-            }
-
-            function selectInstaller(){
-                ao_module_openFileSelector(fileSelected, "user:/Desktop", "file",true, {
-                    filter: ["zip"]
-                });
-            }
-
-            function fileSelected(filedata){
-                for (var i=0; i < filedata.length; i++){
-                    var filename = filedata[i].filename;
-                    var filepath = filedata[i].filepath;
-                    $("#installpendingFile").val(filepath);
-                }
-            }
-
-            function InstallViaZipFile(btn){
-                //Select the upload module zip file
-                var installerPath = $("#installpendingFile").val();
-                if (installerPath == ""){
-                    $("#installpendingFile").parent().addClass("error");
-                }else{
-                    $("#installpendingFile").parent().removeClass("error");
-                }
-
-                $(btn).addClass("loading");
-                //Install it
-                $.ajax({
-                    url: "../../system/modules/installViaZip",
-                    data: {path: installerPath},
-                    success: function(data){
-                        console.log(data);
-                        if (data.error !== undefined){
-                            $("#errmsg").text(data.error);
-                            $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
-                        }else{
-                            //Install completed.
-                            if (parent && parent.initModuleList != undefined){
-                                parent.initModuleList();
-                            }
-
-                            initModuleUninstallList();
-                            $("#ok").slideDown('fast').delay(5000).slideUp('fast');
-                        }
-                        $(btn).removeClass("loading");
-                    },
-                    error: function(){
-                        $(btn).removeClass("loading");
-                        alert("Installation failed due to unknown reason")
-                    }
-                })
-            }
-            
-            function installViaGit(){
-                var url = $("#gitlink").val();
-                $("#installingDialog").show();
-                $("#installViaGitInput").addClass("disabled");
-                $.ajax({
-                    url: "../../system/module/install",
-                    data: {opr: "gitinstall", url: url},
-                    success: function(data){
-                        console.log(data);
-                        if (data.error !== undefined){
-                            $("#errmsg").text(data.error);
-                            $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
-                        }else{
-                            //OK. Reload the list
-                            if (parent && parent.initModuleList != undefined){
-                                parent.initModuleList();
-                            }
-
-                            //Reload the uninstall list
-                            initModuleUninstallList();
-                           
-                        }
-                        $("#installingDialog").hide();
-                        $("#installViaGitInput").removeClass("disabled");
-                    },
-                    error: function(){
-                        $("#errmsg").text(data.error);
-                        $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
-                        $("#installingDialog").hide();
-                        $("#installViaGitInput").removeClass("disabled");
-                    }
-                });
-
-            }
-
-            function selectThisModule(e, obj){
-                e.preventDefault();
-                $(".ui.segment.installedModule.active").removeClass('active');
-                $(".actionField").hide();
-                $(obj).addClass("active");
-                $(obj).find(".actionField").show();
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Subservices</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+        <style>
+            .ui.active.segment{
+                background-color: #f5f5f5 !important;
+            }
+
+            .ui.segment.installedModule{
+                cursor: pointer;
+                margin: 0px !important;
+            }
+
+            .ui.segment.installedModule:hover{
+                background-color: #e3e3e3;
+            }
+        </style>
+    
+    </head>
+    <body>
+        <br>
+        <div class="ui container">
+            <div class="ui container">
+                <div class="ui header">
+                    <i class="add icon"></i>
+                    <div class="content">
+                        Add & Remove Module
+                        <div class="sub header">Install or Uninstall WebApp Modules from this Host</div>
+                    </div>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <div class="ui red message" style="display:none;" id="errmsgbox">
+                <h4 class="ui header">
+                    <i class="remove icon"></i>
+                    <div class="content">
+                        Module Installation Failed
+                        <div class="sub header" id="errmsg"></div>
+                    </div>
+                </h4>
+            </div>
+            <div class="ui green message" style="display:none;" id="ok">
+                <h4 class="ui header">
+                    <i class="checkmark icon"></i>
+                    <div class="content">
+                        Module Installation Completed
+                        <div class="sub header" >You should be able to see your new module in the list below.</div>
+                    </div>
+                </h4>
+            </div>
+            <div class="ui blue segment">
+                <h4 class="ui header">
+                    New WebApp
+                    <div class="sub header">Download new WebApp to your ArozOS Host</div>
+                </h4>
+                <div class="ui divider"></div>
+                <h5>Install via Git Repository</h5>
+                <p>Enter the Github repo link in the field below and click "Install"</p>
+                <div class="ui small fluid action input" id="installViaGitInput">
+                    <input id="gitlink" type="text" placeholder="http://github.com/....">
+                    <button class="ui blue button" onclick="installViaGit();"><i class="download icon"></i> Install</button>
+                </div>
+                <div class="ui blue message" style="display:none;" id="installingDialog">
+                    <p><i class="ui loading spinner icon"></i> Module installing in the background. Please wait until this dialog is closed.</p>
+                </div>
+                <div class="ui divider"></div>
+                <h4>Install via Zip File</h4>
+              
+                
+                <p>Select your module zip file and upload it to the system for installation.</p>
+                <div class="ui small fluid action input">
+                    <input type="text" id="installpendingFile" placeholder="New Module.zip">
+                    <button class="ui button" onclick="selectInstaller()"><i class="folder open icon"></i> Select Installer</button>
+                    <button class="ui blue button" onclick="InstallViaZipFile(this)"><i class="zip icon" ></i> Install</button>
+                </div>
+                
+            
+            </div>
+            <div class="ui red segment">
+                <h4 class="ui header">
+                    Remove WebApp
+                    <div class="sub header">Remove WebApp from your ArozOS Host</div>
+                </h4>
+                <div class="ui green message" id="succ" style="display:none;">
+                    <i class="check icon"></i> WebApp module uninstalled successfully. 
+                </div>
+                <div class="ui divider"></div>
+                <div id="modulelist">
+                    No Module Installed
+                </div>
+            </div>
+        </div>
+
+        <div id="loadingUI" class="ui dimmer">
+            <div class="ui indeterminate text loader">Fetching Files</div>
+        </div>
+        <script>
+            var moduleList = [];
+
+            initModuleUninstallList();
+            function bytesToSize(bytes) {
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+                if (bytes == 0) return '0 Byte';
+                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+            }
+
+            function initModuleUninstallList(){
+                $.get("../../system/module/install", function(data){
+                    console.log(data);
+                    moduleList = data;
+                    $("#modulelist").html("");
+                    data.forEach(mod => {
+                        var uninstallButtonClass = "";
+                        if (mod.Uninstallable == false){
+                            uninstallButtonClass = "disabled"
+                        }
+                        $("#modulelist").append(`<div class="ui basic segment installedModule" onclick="selectThisModule(event, this);">
+                        <img class="ui top aligned image" style="margin-right: 12px; width: 50px;" src="../../${mod.IconPath}">
+                        <div style="display:inline-block;">
+                            <b>${mod.Name}</b>
+                            <p>${mod.Desc}</p>
+                            <div style="position: absolute;top: 0.6em; right: 2em; text-align: right;">
+                                <b>${bytesToSize(mod.DiskSpace)}</b><br>
+                                <span>${mod.InstallDate}</span>
+                            </div>
+                        </div>
+                        <div style="text-align: right; display:none;" class="actionField">
+                            <button class="ui small ${uninstallButtonClass} button" name="${mod.Name}" onclick="removeModule(event,this);">Uninstall</button>
+                            <div class="ui red message errordialog" style="text-align:left; display:none;">
+                                <i class="remove icon"></i> WebApp Removal Failed: <span class="errmsg"></span>
+                            </div>
+                        </div>
+                    </div> `);
+                    });
+                });
+            }
+
+            function removeModule(e, btn){
+                var modulename = $(btn).attr("name");
+                //Ask for confirmation
+                if (confirm("Confirm permanently remove " + modulename + " ?")){
+                    $.ajax({
+                        url: "../../system/module/install",
+                        data: {opr: "remove", module: modulename},
+                        success: function(data){
+                            if (data.error !== undefined){
+                                $(btn).parent().find(".errmsg").text(data.error);
+                                $(btn).parent().find(".errordialog").slideDown("fast").delay(10000).slideUp("fast");
+                            }else{
+                                //Reload list
+                                initModuleUninstallList();
+
+                                //Reload desktop module list
+                                if (parent && parent.initModuleList != undefined){
+                                    parent.initModuleList();
+                                }
+
+                                $("#succ").slideDown("fast").delay(3000).slideUp('fast');
+                            }
+
+                        }
+                    });
+                }
+            }
+
+            function selectInstaller(){
+                ao_module_openFileSelector(fileSelected, "user:/Desktop", "file",true, {
+                    filter: ["zip"]
+                });
+            }
+
+            function fileSelected(filedata){
+                for (var i=0; i < filedata.length; i++){
+                    var filename = filedata[i].filename;
+                    var filepath = filedata[i].filepath;
+                    $("#installpendingFile").val(filepath);
+                }
+            }
+
+            function InstallViaZipFile(btn){
+                //Select the upload module zip file
+                var installerPath = $("#installpendingFile").val();
+                if (installerPath == ""){
+                    $("#installpendingFile").parent().addClass("error");
+                }else{
+                    $("#installpendingFile").parent().removeClass("error");
+                }
+
+                $(btn).addClass("loading");
+                //Install it
+                $.ajax({
+                    url: "../../system/modules/installViaZip",
+                    data: {path: installerPath},
+                    success: function(data){
+                        console.log(data);
+                        if (data.error !== undefined){
+                            $("#errmsg").text(data.error);
+                            $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
+                        }else{
+                            //Install completed.
+                            if (parent && parent.initModuleList != undefined){
+                                parent.initModuleList();
+                            }
+
+                            initModuleUninstallList();
+                            $("#ok").slideDown('fast').delay(5000).slideUp('fast');
+                        }
+                        $(btn).removeClass("loading");
+                    },
+                    error: function(){
+                        $(btn).removeClass("loading");
+                        alert("Installation failed due to unknown reason")
+                    }
+                })
+            }
+            
+            function installViaGit(){
+                var url = $("#gitlink").val();
+                $("#installingDialog").show();
+                $("#installViaGitInput").addClass("disabled");
+                $.ajax({
+                    url: "../../system/module/install",
+                    data: {opr: "gitinstall", url: url},
+                    success: function(data){
+                        console.log(data);
+                        if (data.error !== undefined){
+                            $("#errmsg").text(data.error);
+                            $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
+                        }else{
+                            //OK. Reload the list
+                            if (parent && parent.initModuleList != undefined){
+                                parent.initModuleList();
+                            }
+
+                            //Reload the uninstall list
+                            initModuleUninstallList();
+                           
+                        }
+                        $("#installingDialog").hide();
+                        $("#installViaGitInput").removeClass("disabled");
+                    },
+                    error: function(){
+                        $("#errmsg").text(data.error);
+                        $("#errmsgbox").slideDown("fast").delay(10000).slideUp("fast");
+                        $("#installingDialog").hide();
+                        $("#installViaGitInput").removeClass("disabled");
+                    }
+                });
+
+            }
+
+            function selectThisModule(e, obj){
+                e.preventDefault();
+                $(".ui.segment.installedModule.active").removeClass('active');
+                $(".actionField").hide();
+                $(obj).addClass("active");
+                $(obj).find(".actionField").show();
+            }
+        </script>
+    </body>
 </html>

+ 106 - 105
web/SystemAO/modules/defaultOpener.html

@@ -1,106 +1,107 @@
-<html>
-    <head>
-        <title>Module List</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="external icon"></i>
-                    <div class="content">
-                        Default Opening Module
-                        <div class="sub header">Manage your default modules to open files with given extensions</div>
-                    </div>
-                </div>
-            </div>
-            <div id="msgbox" class="ui icon positive message" style="display:none;">
-                <i class="checkmark icon"></i>
-                <div class="content">
-                    <div class="header">
-                    Default Opening Module Updated
-                    </div>
-                    <p>Try to open your file again and see if it is working</p>
-                </div>
-            </div>
-            <table class="ui celled table">
-                <thead>
-                  <tr><th>File Extension</th>
-                  <th>Current Default </th>
-                  <th>Modify Default (Save on Change)</th>
-                </tr></thead>
-                <tbody id="defaultOpenerList">
-              
-                </tbody>
-              </table>
-        </div>
-        <script>
-            loadDefaultOpener();
-
-            function loadDefaultOpener(){
-                $("#defaultOpenerList").html("");
-                $.ajax({
-                    url: "../../system/modules/getDefault?opr=list",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            console.log(data.error);
-                            return;
-                        }
-                        for (var i =0; i < data.length; i++){
-                            var ext = data[i][0];
-                            var module = encodeURIComponent(data[i][1]);
-                            var displayModuleName = data[i][1];
-                            $("#defaultOpenerList").append(`<tr>
-                                <td>${ext}</td>
-                                <td>${displayModuleName}</td>
-                                <td><select class="ui search selection dropdown" ext="${ext}" module="${module}" onchange="updateDefaultOpener(this);">
-                                    <option value="default">${displayModuleName}</option>
-                                </select>
-                                </td>
-                            </tr>`);
-                        }
-
-                        //Load modules into search dropdowns
-                        $.get("../../system/modules/list",function(moudleList){
-                            for (var i=0; i < moudleList.length; i++){
-                                var thisModule = moudleList[i];
-                                $(".search.dropdown").each(function(){
-                                    var thisExt = $(this).attr("ext");
-                                    var thisDefaultModule = decodeURIComponent($(this).attr("module"));
-                                    if (thisModule.SupportedExt !== null && thisModule.SupportedExt.includes(thisExt)){
-                                        //Accept this file type
-                                    }
-                                    if (thisDefaultModule !== thisModule.Name){
-                                        $(this).append(`<option value="${thisModule.Name}">${thisModule.Name}</option>`);
-                                    }
-                                    
-                                });
-                            }
-
-                            $('.ui.dropdown').dropdown();
-                        });
-                    }
-                });
-            }
-
-            function updateDefaultOpener(object){
-                if ($(object).val() !== "default"){
-                    var ext = $(object).attr('ext');
-                    //Refresh the list
-                    $.ajax({
-                        url: "../../system/modules/getDefault?opr=set&ext=" + ext + "&module=" + $(object).val(),
-                        success: function(data){
-                            setTimeout(loadDefaultOpener,500);
-                        }
-                    });
-                    $("#msgbox").stop().finish().slideDown('fast').delay(5000).slideUp('fast');
-                }
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Module List</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="external icon"></i>
+                    <div class="content">
+                        Default Opening Module
+                        <div class="sub header">Manage your default modules to open files with given extensions</div>
+                    </div>
+                </div>
+            </div>
+            <div id="msgbox" class="ui icon positive message" style="display:none;">
+                <i class="checkmark icon"></i>
+                <div class="content">
+                    <div class="header">
+                    Default Opening Module Updated
+                    </div>
+                    <p>Try to open your file again and see if it is working</p>
+                </div>
+            </div>
+            <table class="ui celled table">
+                <thead>
+                  <tr><th>File Extension</th>
+                  <th>Current Default </th>
+                  <th>Modify Default (Save on Change)</th>
+                </tr></thead>
+                <tbody id="defaultOpenerList">
+              
+                </tbody>
+              </table>
+        </div>
+        <script>
+            loadDefaultOpener();
+
+            function loadDefaultOpener(){
+                $("#defaultOpenerList").html("");
+                $.ajax({
+                    url: "../../system/modules/getDefault?opr=list",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            console.log(data.error);
+                            return;
+                        }
+                        for (var i =0; i < data.length; i++){
+                            var ext = data[i][0];
+                            var module = encodeURIComponent(data[i][1]);
+                            var displayModuleName = data[i][1];
+                            $("#defaultOpenerList").append(`<tr>
+                                <td>${ext}</td>
+                                <td>${displayModuleName}</td>
+                                <td><select class="ui search selection dropdown" ext="${ext}" module="${module}" onchange="updateDefaultOpener(this);">
+                                    <option value="default">${displayModuleName}</option>
+                                </select>
+                                </td>
+                            </tr>`);
+                        }
+
+                        //Load modules into search dropdowns
+                        $.get("../../system/modules/list",function(moudleList){
+                            for (var i=0; i < moudleList.length; i++){
+                                var thisModule = moudleList[i];
+                                $(".search.dropdown").each(function(){
+                                    var thisExt = $(this).attr("ext");
+                                    var thisDefaultModule = decodeURIComponent($(this).attr("module"));
+                                    if (thisModule.SupportedExt !== null && thisModule.SupportedExt.includes(thisExt)){
+                                        //Accept this file type
+                                    }
+                                    if (thisDefaultModule !== thisModule.Name){
+                                        $(this).append(`<option value="${thisModule.Name}">${thisModule.Name}</option>`);
+                                    }
+                                    
+                                });
+                            }
+
+                            $('.ui.dropdown').dropdown();
+                        });
+                    }
+                });
+            }
+
+            function updateDefaultOpener(object){
+                if ($(object).val() !== "default"){
+                    var ext = $(object).attr('ext');
+                    //Refresh the list
+                    $.ajax({
+                        url: "../../system/modules/getDefault?opr=set&ext=" + ext + "&module=" + $(object).val(),
+                        success: function(data){
+                            setTimeout(loadDefaultOpener,500);
+                        }
+                    });
+                    $("#msgbox").stop().finish().slideDown('fast').delay(5000).slideUp('fast');
+                }
+            }
+        </script>
+    </body>
 </html>

+ 121 - 120
web/SystemAO/modules/moduleList.html

@@ -1,121 +1,122 @@
-<html>
-    <head>
-        <title>Module List</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <div class="ui container">
-            <div style="width: 100%; overflow-y: auto;">
-                <table class="ui basic celled structured unstackable table">
-                    <thead>
-                    <tr>
-                        <th rowspan="2">Module</th>
-                        <th rowspan="2">Group</th>
-                        <th rowspan="2">Supported Extensions</th>
-                        <th colspan="3">Supported Mode</th>
-                    </tr>
-                    <tr>
-                        <th>Default</th>
-                        <th>FloatWindow</th>
-                        <th>Embedded</th>
-                    </tr>
-                    </thead>
-                    <tbody id="moduleList">
-                    
-                    </tbody>
-                </table>
-            </div>
-              <div class="ui divider"></div>
-              <p>If you have installed WebApps manually, you can click the "Reload WebApps" button to load it without restarting ArozOS.</p>
-              <button id="reloadWebappButton" class="ui basic small blue button" onclick="reloadWebapps();">
-                <i class="refresh icon"></i> Reload WebApps
-              </button>
-              <br><br>
-        </div>
-        <script>
-            initModuleList();
-
-            function reloadWebapps(){
-                let moduleListBackup = $("#moduleList").html();
-                $("#moduleList").html(`<tr><td colspan="6"><i class="ui loading spinner icon"></i> Reloading...</tr></td>`);
-                $.ajax({
-                   url: "../../system/modules/reload", 
-                   success: function(data){
-                        initModuleList();
-                   },
-                   error: function(){
-                       //Reload failed (Permission denied?)
-                       $("#moduleList").html(moduleListBackup);
-                       $("#reloadWebappButton").addClass("disabled").html("<i class='ui remove icon'></i> No Permission");
-                   }    
-                });
-            }
-
-            function initModuleList(){
-                $("#moduleList").html("");
-                $.ajax({
-                    url: "../../system/modules/list",
-                    success: function(data){
-                        console.log(data);
-                        for (var i =0; i < data.length; i++){
-                            var thisModule = data[i];
-                            var iconURL = "../../img/system/service.png";
-                            if (thisModule.IconPath !== ""){
-                                iconURL = "../../" + thisModule.IconPath;
-                            }
-                            var supportMode = ['<i class="large red remove icon"></i>', '<i class="large red remove icon"></i>', '<i class="large red remove icon"></i>'];
-                            var group = "Unknown";
-                            if (thisModule.Group != ""){
-                                group = thisModule.Group;
-                            }
-                            if (thisModule.StartDir != ""){
-                                supportMode[0] = '<i class="large green checkmark icon"></i>';
-                            }
-                            if (thisModule.SupportFW == true){
-                                supportMode[1] = '<i class="large green checkmark icon"></i>';
-                            }
-                            if (thisModule.SupportEmb == true){
-                                supportMode[2] = '<i class="large green checkmark icon"></i>';
-                            }
-                            var supportedExt = "N/A";
-                            if (thisModule.SupportedExt !== null){
-                                supportedExt = thisModule.SupportedExt.join("<br>");
-                            }
-                            $("#moduleList").append(`<tr>
-                                <td>
-                                    <h4 class="ui image header" style="white-space: nowrap;">
-                                        <img src="${iconURL}" class="ui mini rounded image">
-                                        <div class="content">
-                                            ${thisModule.Name}
-                                            <div class="sub header">Version: ${thisModule.Version} </div>
-                                        </div>
-                                    </h4>
-                                </td>
-                                <td>
-                                ${group}
-                                </td>
-                                <td>
-                                ${supportedExt}
-                                </td>
-                                <td>
-                                ${supportMode[0]}
-                                </td>
-                                <td>
-                                ${supportMode[1]}
-                                </td>
-                                <td>
-                                ${supportMode[2]}
-                                </td>
-                            </tr>`);
-                        }
-                    }
-                });
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Module List</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <div class="ui container">
+            <div style="width: 100%; overflow-y: auto;">
+                <table class="ui basic celled structured unstackable table">
+                    <thead>
+                    <tr>
+                        <th rowspan="2">Module</th>
+                        <th rowspan="2">Group</th>
+                        <th rowspan="2">Supported Extensions</th>
+                        <th colspan="3">Supported Mode</th>
+                    </tr>
+                    <tr>
+                        <th>Default</th>
+                        <th>FloatWindow</th>
+                        <th>Embedded</th>
+                    </tr>
+                    </thead>
+                    <tbody id="moduleList">
+                    
+                    </tbody>
+                </table>
+            </div>
+              <div class="ui divider"></div>
+              <p>If you have installed WebApps manually, you can click the "Reload WebApps" button to load it without restarting ArozOS.</p>
+              <button id="reloadWebappButton" class="ui basic small blue button" onclick="reloadWebapps();">
+                <i class="refresh icon"></i> Reload WebApps
+              </button>
+              <br><br>
+        </div>
+        <script>
+            initModuleList();
+
+            function reloadWebapps(){
+                let moduleListBackup = $("#moduleList").html();
+                $("#moduleList").html(`<tr><td colspan="6"><i class="ui loading spinner icon"></i> Reloading...</tr></td>`);
+                $.ajax({
+                   url: "../../system/modules/reload", 
+                   success: function(data){
+                        initModuleList();
+                   },
+                   error: function(){
+                       //Reload failed (Permission denied?)
+                       $("#moduleList").html(moduleListBackup);
+                       $("#reloadWebappButton").addClass("disabled").html("<i class='ui remove icon'></i> No Permission");
+                   }    
+                });
+            }
+
+            function initModuleList(){
+                $("#moduleList").html("");
+                $.ajax({
+                    url: "../../system/modules/list",
+                    success: function(data){
+                        console.log(data);
+                        for (var i =0; i < data.length; i++){
+                            var thisModule = data[i];
+                            var iconURL = "../../img/system/service.png";
+                            if (thisModule.IconPath !== ""){
+                                iconURL = "../../" + thisModule.IconPath;
+                            }
+                            var supportMode = ['<i class="large red remove icon"></i>', '<i class="large red remove icon"></i>', '<i class="large red remove icon"></i>'];
+                            var group = "Unknown";
+                            if (thisModule.Group != ""){
+                                group = thisModule.Group;
+                            }
+                            if (thisModule.StartDir != ""){
+                                supportMode[0] = '<i class="large green checkmark icon"></i>';
+                            }
+                            if (thisModule.SupportFW == true){
+                                supportMode[1] = '<i class="large green checkmark icon"></i>';
+                            }
+                            if (thisModule.SupportEmb == true){
+                                supportMode[2] = '<i class="large green checkmark icon"></i>';
+                            }
+                            var supportedExt = "N/A";
+                            if (thisModule.SupportedExt !== null){
+                                supportedExt = thisModule.SupportedExt.join("<br>");
+                            }
+                            $("#moduleList").append(`<tr>
+                                <td>
+                                    <h4 class="ui image header" style="white-space: nowrap;">
+                                        <img src="${iconURL}" class="ui mini rounded image">
+                                        <div class="content">
+                                            ${thisModule.Name}
+                                            <div class="sub header">Version: ${thisModule.Version} </div>
+                                        </div>
+                                    </h4>
+                                </td>
+                                <td>
+                                ${group}
+                                </td>
+                                <td>
+                                ${supportedExt}
+                                </td>
+                                <td>
+                                ${supportMode[0]}
+                                </td>
+                                <td>
+                                ${supportMode[1]}
+                                </td>
+                                <td>
+                                ${supportMode[2]}
+                                </td>
+                            </tr>`);
+                        }
+                    }
+                });
+            }
+        </script>
+    </body>
 </html>

+ 162 - 161
web/SystemAO/modules/subservices.html

@@ -1,162 +1,163 @@
-<html>
-    <head>
-        <title>Subservices</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <br>
-        <div class="ui container">
-            <div class="ui container">
-                <div class="ui header">
-                    <i class="server icon"></i>
-                    <div class="content">
-                        Manage Subservice
-                        <div class="sub header">Glue services together using subservices</div>
-                    </div>
-                </div>
-                <div class="ui accordion">
-                    <div class="title">
-                        <i class="dropdown icon"></i>
-                        What is Subservice?
-                    </div>
-                    <div class="content">
-                        <p class="transition hidden">Subservice is one type of ArOZ Online Module that is not build in to the system core but act like one. It is powered by reverse proxy build into the ArOZ Online Core so it can serve web content just like a build in module. Unlike the core modules that cannot be toggle off, you can switch subservices off to save power when needed.</p>
-                    </div>
-                </div>
-            </div>
-            <h4>Running Services</h4>
-            <table class="ui celled striped table">
-                <thead>
-                    <tr>
-                        <th>
-                            Corresponding Module
-                        </th>
-                        <th>
-                            Port
-                        </th>
-                        <th>
-                            Reverse Proxy Path
-                        </th>
-                        <th>
-                            Executable (Process ID)
-                        </th>
-                        <th>
-                            Action
-                        </th>
-                    </tr>
-                </thead>
-                <tbody id="sslist">
-                   
-                </tbody>
-            </table>
-            <h4>Disabled Services</h4>
-            <table class="ui celled table">
-                <thead>
-                  <tr><th>Service Name</th>
-                  <th>Executable</th>
-                  <th>Action</th>
-                </tr></thead>
-                <tbody id="disServiceList">
-               
-                </tbody>
-        </div>
-        <script>
-            $('.ui.accordion').accordion();
-
-            initSubserviceList();
-            function initSubserviceList(){
-                $("#sslist").html("");
-                $("#disServiceList").html("");
-                $.get("../../system/subservice/list",function(data){
-                    if (data.error !== undefined){
-
-                    }else{
-                        for (var i = 0; i < data.Enabled.length; i++){
-                            var ss = data.Enabled[i];
-                            var port = ss.Port;
-                            if (port == 0){
-                                //This module is not using reverse proxy
-                                port = "NO_PROXY";
-                            }
-                            var rpe = ss.RpEndpoint;
-                            if (rpe == ""){
-                                rpe = "N/A";
-                            }else{
-                                rpe = "/" + rpe;
-                            }
-                            $("#sslist").append(`<tr>
-                            <td>
-                                <h4 class="ui image header">
-                                    <img src="../../${ss.Info.IconPath}" class="ui mini rounded image">
-                                    <div class="content">
-                                        ${ss.Info.Name}
-                                        <div class="sub header"${ss.Info.Group}
-                                    </div>
-                                    </div>
-                                </h4>
-                            </td>
-                            <td class="">${port}</td>
-                            <td class="left aligned">${rpe}</td>
-                            <td class="">${ss.Path} (${ss.ProcessID})</td>
-                            <td class=""><button name="${ss.Info.Name}" sd="${ss.ServiceDir}" class="ui primary tiny button" onclick="kill(this);">DISABLE</button></td>
-                            </tr>`);
-                        }
-
-                        for (var i = 0; i < data.Disabled.length; i++){
-                            var thisDisabledService = data.Disabled[i];
-                            $("#disServiceList").append(` <tr>
-                                <td>${thisDisabledService.ServiceDir}</td>
-                                <td>${thisDisabledService.Path}</td>
-                                <td><button onclick="start(this);" dir="${thisDisabledService.ServiceDir}" class="ui positive tiny button">Start</button></td>
-                            </tr>`);
-                        }
-                    }
-                });
-            }
-
-            function kill(object){
-                var name = $(object).attr("name");
-                var sd = $(object).attr("sd");
-                $.ajax({
-                    url: "../../system/subservice/kill",
-                    data: {serviceDir: sd, moduleName: name},
-                    method: "POST",
-                    success: function(data){
-                        console.log(data);
-                        //Update the launchMenu
-                        parent.initModuleList();
-
-                        //Update the list
-                        initSubserviceList();
-                    }
-                });
-            }
-
-            function start(object){
-                var dir = $(object).attr("dir");
-                $.ajax({
-                    url: "../../system/subservice/start",
-                    data: {serviceDir: dir},
-                    method: "POST",
-                    success: function(data){
-                        console.log(data);
-                        //Update the launchMenu
-                        parent.initModuleList();
-
-                        //Update the list
-                        setTimeout(function(){
-                            //Allow 1 seconds to it to startup
-                            initSubserviceList();
-                        }, 1000);
-                        
-                    }
-                });
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Subservices</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <br>
+        <div class="ui container">
+            <div class="ui container">
+                <div class="ui header">
+                    <i class="server icon"></i>
+                    <div class="content">
+                        Manage Subservice
+                        <div class="sub header">Glue services together using subservices</div>
+                    </div>
+                </div>
+                <div class="ui accordion">
+                    <div class="title">
+                        <i class="dropdown icon"></i>
+                        What is Subservice?
+                    </div>
+                    <div class="content">
+                        <p class="transition hidden">Subservice is one type of ArOZ Online Module that is not build in to the system core but act like one. It is powered by reverse proxy build into the ArOZ Online Core so it can serve web content just like a build in module. Unlike the core modules that cannot be toggle off, you can switch subservices off to save power when needed.</p>
+                    </div>
+                </div>
+            </div>
+            <h4>Running Services</h4>
+            <table class="ui celled striped table">
+                <thead>
+                    <tr>
+                        <th>
+                            Corresponding Module
+                        </th>
+                        <th>
+                            Port
+                        </th>
+                        <th>
+                            Reverse Proxy Path
+                        </th>
+                        <th>
+                            Executable (Process ID)
+                        </th>
+                        <th>
+                            Action
+                        </th>
+                    </tr>
+                </thead>
+                <tbody id="sslist">
+                   
+                </tbody>
+            </table>
+            <h4>Disabled Services</h4>
+            <table class="ui celled table">
+                <thead>
+                  <tr><th>Service Name</th>
+                  <th>Executable</th>
+                  <th>Action</th>
+                </tr></thead>
+                <tbody id="disServiceList">
+               
+                </tbody>
+        </div>
+        <script>
+            $('.ui.accordion').accordion();
+
+            initSubserviceList();
+            function initSubserviceList(){
+                $("#sslist").html("");
+                $("#disServiceList").html("");
+                $.get("../../system/subservice/list",function(data){
+                    if (data.error !== undefined){
+
+                    }else{
+                        for (var i = 0; i < data.Enabled.length; i++){
+                            var ss = data.Enabled[i];
+                            var port = ss.Port;
+                            if (port == 0){
+                                //This module is not using reverse proxy
+                                port = "NO_PROXY";
+                            }
+                            var rpe = ss.RpEndpoint;
+                            if (rpe == ""){
+                                rpe = "N/A";
+                            }else{
+                                rpe = "/" + rpe;
+                            }
+                            $("#sslist").append(`<tr>
+                            <td>
+                                <h4 class="ui image header">
+                                    <img src="../../${ss.Info.IconPath}" class="ui mini rounded image">
+                                    <div class="content">
+                                        ${ss.Info.Name}
+                                        <div class="sub header"${ss.Info.Group}
+                                    </div>
+                                    </div>
+                                </h4>
+                            </td>
+                            <td class="">${port}</td>
+                            <td class="left aligned">${rpe}</td>
+                            <td class="">${ss.Path} (${ss.ProcessID})</td>
+                            <td class=""><button name="${ss.Info.Name}" sd="${ss.ServiceDir}" class="ui primary tiny button" onclick="kill(this);">DISABLE</button></td>
+                            </tr>`);
+                        }
+
+                        for (var i = 0; i < data.Disabled.length; i++){
+                            var thisDisabledService = data.Disabled[i];
+                            $("#disServiceList").append(` <tr>
+                                <td>${thisDisabledService.ServiceDir}</td>
+                                <td>${thisDisabledService.Path}</td>
+                                <td><button onclick="start(this);" dir="${thisDisabledService.ServiceDir}" class="ui positive tiny button">Start</button></td>
+                            </tr>`);
+                        }
+                    }
+                });
+            }
+
+            function kill(object){
+                var name = $(object).attr("name");
+                var sd = $(object).attr("sd");
+                $.ajax({
+                    url: "../../system/subservice/kill",
+                    data: {serviceDir: sd, moduleName: name},
+                    method: "POST",
+                    success: function(data){
+                        console.log(data);
+                        //Update the launchMenu
+                        parent.initModuleList();
+
+                        //Update the list
+                        initSubserviceList();
+                    }
+                });
+            }
+
+            function start(object){
+                var dir = $(object).attr("dir");
+                $.ajax({
+                    url: "../../system/subservice/start",
+                    data: {serviceDir: dir},
+                    method: "POST",
+                    success: function(data){
+                        console.log(data);
+                        //Update the launchMenu
+                        parent.initModuleList();
+
+                        //Update the list
+                        setTimeout(function(){
+                            //Allow 1 seconds to it to startup
+                            initSubserviceList();
+                        }, 1000);
+                        
+                    }
+                });
+            }
+        </script>
+    </body>
 </html>

+ 1 - 0
web/SystemAO/system_setting/index.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>System Setting</title>

+ 204 - 203
web/SystemAO/users/account.html

@@ -1,204 +1,205 @@
-<html>
-    <head>
-        <title>User Account</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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>
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="user circle icon"></i>
-                        <div class="content">
-                        My Account
-                        <div class="sub header">Manage your account information and password</div>
-                    </div>
-                </div>
-            </div>
-            <div class="ui stackable grid">
-                <div class="six wide column">
-                    <div class="ui card">
-                        <div class="image">
-                            <img id="profileIcon" src="../users/img/user.svg">
-                        </div>
-                        <div class="content">
-                            <a id="username" class="header"></a>
-                            <div class="meta">
-                                <p><i class="ui envelope icon"></i><span id="email" class="date"></span><br>
-                                <i class="ui user circle icon"></i><span id="usergroup" class="date"></span></p>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-                <div class="ten wide column">
-                    <h4 class="ui header">
-                        Change Profile Picture
-                    </h4>
-                    <button class="ui blue button" onclick="handleProfilePic();">Upload</button>
-                    <div class="ui divider"></div>
-                    <h4 class="ui header">
-                        Change Contact Email
-                    </h4>
-                    <form class="ui form" onsubmit="handleEmailChange(event);">
-                        <div class="field">
-                            <label>New Email</label>
-                            <input id="newemail" type="text"  placeholder="New Email">
-                        </div>
-                        <button class="ui blue button" type="submit">Update</button>
-                    </form>
-
-                    <div class="ui divider"></div>
-                    <h4 class="ui header">
-                        Change Password
-                    </h4>
-                    <form class="ui form" onsubmit="handleSubmit(event);">
-                        <div class="field">
-                            <label>Old Password</label>
-                            <input id="opw" type="password"  placeholder="Old Password">
-                        </div>
-                        <div class="field">
-                            <label>New password</label>
-                            <input id="npw" type="password" placeholder="New Password">
-                        </div>
-                        <div class="field">
-                            <label>Confirm New Password</label>
-                            <input id="cpw" type="password" placeholder="Confirm New Password">
-                        </div>
-                        <button class="ui blue button" type="submit">Update</button>
-                    </form>
-                    <div id="msgbox" class="ui green message" style="display:none;">
-                        <i class="close icon"></i>
-                        <div class="header">
-                            Welcome back!
-                        </div>
-                        <p class="message">This is a special notification which you can dismiss if you're bored with it.</p>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <script>
-            //Initiate user information
-            $.get("../../system/users/userinfo",function(data){
-                if (data.error !== undefined){
-                    //Not logged in?
-
-                }else{
-                    if (data.Icondata == ""){
-                        $("#profileIcon").attr('src', "../users/img/noprofileicon.svg");
-                    }else{
-                        $("#profileIcon").attr('src', data.Icondata);
-                    }
-                    
-                    $("#username").text(data.Username);
-                    console.log(data.Usergroup);
-                    $("#usergroup").text(data.Usergroup.join(" / "));
-                }
-                
-            });
-
-            function initUserEmail(){
-                $.get("../../system/register/email", function(data){
-                    if (data == ""){
-                        //Not set
-                        $("#email").text("( Not Configured )");
-                    }else{
-                        $("#email").text(data);
-                    }
-                });
-            }
-            initUserEmail();
-           
-
-            //Handle change password form submit
-            function handleSubmit(event){
-                event.preventDefault();
-                //Check if new password and confirm matches
-                var oldPassword = $("#opw").val();
-                var newPassword = $("#npw").val();
-                var confirmPassword = $("#cpw").val();
-                if (newPassword != confirmPassword){
-                    $("#cpw").parent().addClass("error");
-                    return;
-                }else{
-                    $("#cpw").parent().removeClass("error");
-                }
-
-                //OK to proceed.
-                $.ajax({
-                    url: "../../system/users/userinfo",
-                    method: "POST",
-                    data: {opr: "changepw", oldpw: oldPassword, newpw: newPassword},
-                    success: function(data){
-                        if (data.error != undefined){
-                            msgbox("Update Failed", data.error);
-                        }else{
-                            //Updated suceed.
-                            msgbox("Update Succeed", "Your password has been updated.");
-                        }
-                    }
-                });
-
-            }
-
-            function handleEmailChange(e){
-                e.preventDefault();
-                var newEmail = $("#newemail").val();
-                $.ajax({
-                    url: "../../system/register/email",
-                    data: {email: newEmail},
-                    success: function(data){
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            msgbox("Update Succeed", "Your email has been updated.");
-                            initUserEmail();
-                        }
-                       
-                    }
-                });
-            }
-
-            function handleProfilePic(){
-                ao_module_selectFiles(function(files){
-                    if (FileReader && files && files.length) {
-                        var fr = new FileReader();
-                        fr.onload = function () {
-                            //Update the database for the new image
-                            var fullImageDatapath = fr.result;
-                            $.ajax({
-                                url: "../../system/users/userinfo",
-                                method: "POST",
-                                data: {opr: "changeprofilepic", picdata: fullImageDatapath},
-                                success: function(data){
-                                    if (data.error !== undefined){
-                                        console.log(data.error);
-                                    }else{
-                                        //Load the image into the src div
-                                        document.getElementById("profileIcon").src = fullImageDatapath;
-                                    }
-                                    
-                                }
-                            })
-                        }
-                        fr.readAsDataURL(files[0]);
-                    }
-                },"file", ".png",false);
-            }
-
-            function msgbox(header, message){
-                $("#msgbox").fadeIn('fast');
-                $("#msgbox").find(".header").text(header);
-                $("#msgbox").find(".message").text(message);
-            }
-
-            //Hook close msgbox event
-            $('.message .close')
-            .on('click', function() {
-                $(this).parent().fadeOut('fast');
-            });
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>User Account</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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>
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="user circle icon"></i>
+                        <div class="content">
+                        My Account
+                        <div class="sub header">Manage your account information and password</div>
+                    </div>
+                </div>
+            </div>
+            <div class="ui stackable grid">
+                <div class="six wide column">
+                    <div class="ui card">
+                        <div class="image">
+                            <img id="profileIcon" src="../users/img/user.svg">
+                        </div>
+                        <div class="content">
+                            <a id="username" class="header"></a>
+                            <div class="meta">
+                                <p><i class="ui envelope icon"></i><span id="email" class="date"></span><br>
+                                <i class="ui user circle icon"></i><span id="usergroup" class="date"></span></p>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="ten wide column">
+                    <h4 class="ui header">
+                        Change Profile Picture
+                    </h4>
+                    <button class="ui blue button" onclick="handleProfilePic();">Upload</button>
+                    <div class="ui divider"></div>
+                    <h4 class="ui header">
+                        Change Contact Email
+                    </h4>
+                    <form class="ui form" onsubmit="handleEmailChange(event);">
+                        <div class="field">
+                            <label>New Email</label>
+                            <input id="newemail" type="text"  placeholder="New Email">
+                        </div>
+                        <button class="ui blue button" type="submit">Update</button>
+                    </form>
+
+                    <div class="ui divider"></div>
+                    <h4 class="ui header">
+                        Change Password
+                    </h4>
+                    <form class="ui form" onsubmit="handleSubmit(event);">
+                        <div class="field">
+                            <label>Old Password</label>
+                            <input id="opw" type="password"  placeholder="Old Password">
+                        </div>
+                        <div class="field">
+                            <label>New password</label>
+                            <input id="npw" type="password" placeholder="New Password">
+                        </div>
+                        <div class="field">
+                            <label>Confirm New Password</label>
+                            <input id="cpw" type="password" placeholder="Confirm New Password">
+                        </div>
+                        <button class="ui blue button" type="submit">Update</button>
+                    </form>
+                    <div id="msgbox" class="ui green message" style="display:none;">
+                        <i class="close icon"></i>
+                        <div class="header">
+                            Welcome back!
+                        </div>
+                        <p class="message">This is a special notification which you can dismiss if you're bored with it.</p>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <script>
+            //Initiate user information
+            $.get("../../system/users/userinfo",function(data){
+                if (data.error !== undefined){
+                    //Not logged in?
+
+                }else{
+                    if (data.Icondata == ""){
+                        $("#profileIcon").attr('src', "../users/img/noprofileicon.svg");
+                    }else{
+                        $("#profileIcon").attr('src', data.Icondata);
+                    }
+                    
+                    $("#username").text(data.Username);
+                    console.log(data.Usergroup);
+                    $("#usergroup").text(data.Usergroup.join(" / "));
+                }
+                
+            });
+
+            function initUserEmail(){
+                $.get("../../system/register/email", function(data){
+                    if (data == ""){
+                        //Not set
+                        $("#email").text("( Not Configured )");
+                    }else{
+                        $("#email").text(data);
+                    }
+                });
+            }
+            initUserEmail();
+           
+
+            //Handle change password form submit
+            function handleSubmit(event){
+                event.preventDefault();
+                //Check if new password and confirm matches
+                var oldPassword = $("#opw").val();
+                var newPassword = $("#npw").val();
+                var confirmPassword = $("#cpw").val();
+                if (newPassword != confirmPassword){
+                    $("#cpw").parent().addClass("error");
+                    return;
+                }else{
+                    $("#cpw").parent().removeClass("error");
+                }
+
+                //OK to proceed.
+                $.ajax({
+                    url: "../../system/users/userinfo",
+                    method: "POST",
+                    data: {opr: "changepw", oldpw: oldPassword, newpw: newPassword},
+                    success: function(data){
+                        if (data.error != undefined){
+                            msgbox("Update Failed", data.error);
+                        }else{
+                            //Updated suceed.
+                            msgbox("Update Succeed", "Your password has been updated.");
+                        }
+                    }
+                });
+
+            }
+
+            function handleEmailChange(e){
+                e.preventDefault();
+                var newEmail = $("#newemail").val();
+                $.ajax({
+                    url: "../../system/register/email",
+                    data: {email: newEmail},
+                    success: function(data){
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            msgbox("Update Succeed", "Your email has been updated.");
+                            initUserEmail();
+                        }
+                       
+                    }
+                });
+            }
+
+            function handleProfilePic(){
+                ao_module_selectFiles(function(files){
+                    if (FileReader && files && files.length) {
+                        var fr = new FileReader();
+                        fr.onload = function () {
+                            //Update the database for the new image
+                            var fullImageDatapath = fr.result;
+                            $.ajax({
+                                url: "../../system/users/userinfo",
+                                method: "POST",
+                                data: {opr: "changeprofilepic", picdata: fullImageDatapath},
+                                success: function(data){
+                                    if (data.error !== undefined){
+                                        console.log(data.error);
+                                    }else{
+                                        //Load the image into the src div
+                                        document.getElementById("profileIcon").src = fullImageDatapath;
+                                    }
+                                    
+                                }
+                            })
+                        }
+                        fr.readAsDataURL(files[0]);
+                    }
+                },"file", ".png",false);
+            }
+
+            function msgbox(header, message){
+                $("#msgbox").fadeIn('fast');
+                $("#msgbox").find(".header").text(header);
+                $("#msgbox").find(".message").text(message);
+            }
+
+            //Hook close msgbox event
+            $('.message .close')
+            .on('click', function() {
+                $(this).parent().fadeOut('fast');
+            });
+        </script>
+    </body>
 </html>

+ 288 - 287
web/SystemAO/users/editUser.html

@@ -1,288 +1,289 @@
-<html>
-    <head>
-        <title>Edit User</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <link rel="stylesheet" href="../../script/ao.css">
-        <script type="text/javascript" src="../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
-        <script type="text/javascript" src="../../script/ao_module.js"></script>
-        <style>
-            body{
-                background-color:white;
-            }
-            .themebackground{
-                background-color:#588ce0 !important;
-                color:white !important;
-                background-image: url("/img/public/slate.png") !important;
-                background-repeat: no-repeat !important;
-                background-attachment: fixed !important;
-                height:100px;
-                border: 0px solid transparent !important;
-                padding:24px !important;
-            }
-            .required{
-                color:red;
-            }
-            .actionbtns{
-                text-align:right;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="ui fluid attached segment themebackground" >
-            <h24 class="ui inverted header">
-                <i class="edit icon"></i>
-                <div class="content">
-                    Edit User
-                <div class="sub header">Edit user profile and permissions</div>
-                </div>
-            </h4>
-        </div>
-        <div class="ui container">
-            <div id="confirmUpdate" class="ui green inverted segment" style="display:none; margin-top:12px;">
-                <i class="checkmark icon"></i> Profile Updated
-            </div>
-            <div id="updateError" class="ui red inverted segment" style="display:none; margin-top:12px;">
-                <i class="remove icon"></i> <span id="errmsg"></span>
-            </div>
-            <h4>User Profile & Permission</h4>
-            <form class="ui form" onsubmit="handleUserProfileUpdate(event);">
-                <div class="field">
-                  <label>Username</label>
-                  <input id="username" type="text" name="username" placeholder="Username" readonly=true>
-                </div>
-                <div class="field">
-                    <div class="ui divder"></div>
-                    <label>Permission Groups</label>
-                    <select id="pgroups" multiple="" class="ui fluid dropdown">
-                        
-                    </select>
-                </div>
-
-                <table class="ui celled table">
-                    <thead>
-                        <tr><th>Group Name</th>
-                        <th>Is Admin</th>
-                        <th>Accessible Modules</th>
-                    </tr></thead>
-                    <tbody id="permissionTable">
-                        
-                    </tbody>
-                </table>
-
-                <div class="two fields">
-                    <div class="field">
-                        <label>Storage Quota <span class="required">*</span></label>
-                        <input id="quota" type="text" value="15">
-                        <small>Set to 0 for READONLY and -1 for Unlimited Storage</small>
-                    </div>
-                    <div class="field">
-                        <label>Unit <span class="required">*</span></label>
-                        <select class="ui fluid search dropdown" id="unit">
-                            <option value="1">Bytes</option>
-                            <option value="1024">KB</option>
-                            <option value="1048576">MB</option>
-                            <option value="1073741824">GB</option>
-                            <option value="1099511627776">TB</option>
-                            <option value="1125899906842624">PB</option>
-                            </select>
-                    </div>
-                </div>
-                
-                <button class="ui right floated button" onclick="event.preventDefault(); handleEditorExit();">Close</button>
-                <button class="ui primary right floated button" type="submit">Update</button>
-                <br><br>
-                <div class="ui divider"></div>
-                <h4>Password & Security</h4>
-                <p>Reset Password</p>
-                <button class="ui red button" onclick="restartPassword();">RESET PASSWORD</button>
-                <p>A temporary password will be generated once reset is requested. Administrator should provide the user with the password reset link generated below for the user to reset their password.</p>
-                <div id="tmppwui" class="ui green segment" style="margin-top:12px; display:none;">
-                    <p><i class="checkmark icon"></i> Password Reset Link Generated</p>
-                    <div class="ui labeled fluid input">
-                        <label for="tmpw" class="ui label"><i class="key icon"></i></label>
-                        <input id="tmppw" type="text" placeholder="Temporary Password" id="tmpw">
-                      </div>
-                      <small>Please provide the password reset link listed above to the user and request user to update their password through the password reset tool.</small>
-                </div>
-       
-            </div>
-        <script>
-            var currentEditingUsername = "";
-            var userdata;
-            var groupList = {};
-            //Get username from window hash
-            if (window.location.hash.length > 0){
-                var username = window.location.hash.substring(1);
-                username = decodeURIComponent(username);
-                $("#username").val(username);
-                currentEditingUsername = username;
-            }
-            
-            $("#pgroups").on("change", function(e){
-                updatePGTable();
-            });
-
-            $("#unit").dropdown("set selected","GB");
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return [bytes / Math.pow(1024, i), sizes[i]];
-            }
-
-            initContents(function(){
-                initGroupList(function(){
-                    $('.ui.dropdown').dropdown();
-                });
-            });
-
-            function handleEditorExit(){
-                ao_module_parentCallback(true);
-                ao_module_close();
-            }
-
-            function initContents(callback=undefined){
-                //Update the table contents
-                $.ajax({
-                    url: "../../system/users/editUser",
-                    data: {username: currentEditingUsername},
-                    method:"POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            userdata = data;
-                            var displaysize = bytesToSize(data.Quota)
-                            
-                            if (data.Quota == -1){
-                                //Unlimited
-                                $("#quota").val(-1);
-                            }else if (data.Quota == -2){
-                                //Not initialized. Set it to 0
-                                $("#quota").val(0);
-                            }else{
-                                $("#quota").val(displaysize[0]);
-                            }
-                            
-                            $("#unit").dropdown("set selected",displaysize[1])
-                            if (callback !== undefined){
-                                callback();
-                            }
-                        }
-                    }
-                });
-            }
-
-            function initGroupList(callback=undefined){
-                //Initialize group list
-                $.ajax({
-                    url: "../../system/permission/listgroup?showper=true",
-                    success: function(data){
-                        $("#pgroups").html("");
-                        if (data.error !== undefined){
-                            alert(data.error)
-                        }else{
-                            groupList = data;
-                            for (const [key, value] of Object.entries(data)) {
-                                $("#pgroups").append(`<option value="${key}">${key}</option>`);
-                            }
-                            $('#pgroups').dropdown('set selected', userdata.Usergroup);
-                            updatePGTable();
-
-                            if (callback !== undefined){
-                                callback();
-                            }
-                            
-                        }
-                        
-                    }
-                });
-            }
-
-            function updatePGTable(){
-                $("#permissionTable").html("");
-                var pgList = $("#pgroups").val();
-                pgList.forEach(pgid => {
-                    var groupInfo = groupList[pgid];
-                    var isAdmin = '<i class="green checkmark icon"></i>';
-                    if (groupInfo[1] == false){
-                        isAdmin = '<i class="red remove icon"></i>';
-                    }
-                    $("#permissionTable").append(`
-                        <tr>
-                            <td data-label="groupname">${pgid}</td>
-                            <td data-label="isdamin">${isAdmin}</td>
-                            <td data-label="modules">${groupInfo[0].join("/")}</td>
-                        </tr>`);
-                });
-
-            }
-
-            function restartPassword(){
-                var username = $("#username").val();
-                $.ajax({
-                    url: "../../system/users/editUser",
-                    data: {opr: "resetPassword", username: username},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            //Update suceed
-                            initContents();
-                            $("#confirmUpdate").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
-                            console.log(data);
-                            $("#tmppw").val(location.protocol + '//' + location.host + "/reset.system?acc=" + username +  "&rkey=" + data);
-                            $("#tmppwui").slideDown('fast');
-                        }
-                    }
-                });
-            }
-
-            function handleUserProfileUpdate(e){
-                e.preventDefault();
-                //Get all the value from the form
-                var username = $("#username").val();
-                var pgs = JSON.stringify($("#pgroups").val());
-                
-                //Parse Quota
-                var userStorageQuota = parseFloat($("#quota").val()) * $("#unit").val();
-                if (isNaN(userStorageQuota)){
-                    $("#quota").parent().addClass("error");
-                    return
-                }else{
-                    $("#quota").parent().removeClass("error");
-                }
-
-                if (parseInt($("#quota").val()) == -1){
-                    userStorageQuota = -1;
-                }
-                
-                $.ajax({
-                    url: "../../system/users/editUser",
-                    data: {opr: "updateUserGroup", newgroup: pgs, username: username, quota: userStorageQuota},
-                    method: "POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            $("#errmsg").text(data.error);
-                            $("#updateError").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
-                        }else{
-                            //Update suceed
-                            initContents();
-                            updatePGTable();
-                            $("#confirmUpdate").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
-                            ao_module_parentCallback(true);
-
-                        }
-                        window.scrollTo(0,0);
-                    }
-                });
-            }
-
-           
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Edit User</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <link rel="stylesheet" href="../../script/ao.css">
+        <script type="text/javascript" src="../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
+        <script type="text/javascript" src="../../script/ao_module.js"></script>
+        <style>
+            body{
+                background-color:white;
+            }
+            .themebackground{
+                background-color:#588ce0 !important;
+                color:white !important;
+                background-image: url("/img/public/slate.png") !important;
+                background-repeat: no-repeat !important;
+                background-attachment: fixed !important;
+                height:100px;
+                border: 0px solid transparent !important;
+                padding:24px !important;
+            }
+            .required{
+                color:red;
+            }
+            .actionbtns{
+                text-align:right;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui fluid attached segment themebackground" >
+            <h24 class="ui inverted header">
+                <i class="edit icon"></i>
+                <div class="content">
+                    Edit User
+                <div class="sub header">Edit user profile and permissions</div>
+                </div>
+            </h4>
+        </div>
+        <div class="ui container">
+            <div id="confirmUpdate" class="ui green inverted segment" style="display:none; margin-top:12px;">
+                <i class="checkmark icon"></i> Profile Updated
+            </div>
+            <div id="updateError" class="ui red inverted segment" style="display:none; margin-top:12px;">
+                <i class="remove icon"></i> <span id="errmsg"></span>
+            </div>
+            <h4>User Profile & Permission</h4>
+            <form class="ui form" onsubmit="handleUserProfileUpdate(event);">
+                <div class="field">
+                  <label>Username</label>
+                  <input id="username" type="text" name="username" placeholder="Username" readonly=true>
+                </div>
+                <div class="field">
+                    <div class="ui divder"></div>
+                    <label>Permission Groups</label>
+                    <select id="pgroups" multiple="" class="ui fluid dropdown">
+                        
+                    </select>
+                </div>
+
+                <table class="ui celled table">
+                    <thead>
+                        <tr><th>Group Name</th>
+                        <th>Is Admin</th>
+                        <th>Accessible Modules</th>
+                    </tr></thead>
+                    <tbody id="permissionTable">
+                        
+                    </tbody>
+                </table>
+
+                <div class="two fields">
+                    <div class="field">
+                        <label>Storage Quota <span class="required">*</span></label>
+                        <input id="quota" type="text" value="15">
+                        <small>Set to 0 for READONLY and -1 for Unlimited Storage</small>
+                    </div>
+                    <div class="field">
+                        <label>Unit <span class="required">*</span></label>
+                        <select class="ui fluid search dropdown" id="unit">
+                            <option value="1">Bytes</option>
+                            <option value="1024">KB</option>
+                            <option value="1048576">MB</option>
+                            <option value="1073741824">GB</option>
+                            <option value="1099511627776">TB</option>
+                            <option value="1125899906842624">PB</option>
+                            </select>
+                    </div>
+                </div>
+                
+                <button class="ui right floated button" onclick="event.preventDefault(); handleEditorExit();">Close</button>
+                <button class="ui primary right floated button" type="submit">Update</button>
+                <br><br>
+                <div class="ui divider"></div>
+                <h4>Password & Security</h4>
+                <p>Reset Password</p>
+                <button class="ui red button" onclick="restartPassword();">RESET PASSWORD</button>
+                <p>A temporary password will be generated once reset is requested. Administrator should provide the user with the password reset link generated below for the user to reset their password.</p>
+                <div id="tmppwui" class="ui green segment" style="margin-top:12px; display:none;">
+                    <p><i class="checkmark icon"></i> Password Reset Link Generated</p>
+                    <div class="ui labeled fluid input">
+                        <label for="tmpw" class="ui label"><i class="key icon"></i></label>
+                        <input id="tmppw" type="text" placeholder="Temporary Password" id="tmpw">
+                      </div>
+                      <small>Please provide the password reset link listed above to the user and request user to update their password through the password reset tool.</small>
+                </div>
+       
+            </div>
+        <script>
+            var currentEditingUsername = "";
+            var userdata;
+            var groupList = {};
+            //Get username from window hash
+            if (window.location.hash.length > 0){
+                var username = window.location.hash.substring(1);
+                username = decodeURIComponent(username);
+                $("#username").val(username);
+                currentEditingUsername = username;
+            }
+            
+            $("#pgroups").on("change", function(e){
+                updatePGTable();
+            });
+
+            $("#unit").dropdown("set selected","GB");
+            function bytesToSize(bytes) {
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+                if (bytes == 0) return '0 Byte';
+                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+                return [bytes / Math.pow(1024, i), sizes[i]];
+            }
+
+            initContents(function(){
+                initGroupList(function(){
+                    $('.ui.dropdown').dropdown();
+                });
+            });
+
+            function handleEditorExit(){
+                ao_module_parentCallback(true);
+                ao_module_close();
+            }
+
+            function initContents(callback=undefined){
+                //Update the table contents
+                $.ajax({
+                    url: "../../system/users/editUser",
+                    data: {username: currentEditingUsername},
+                    method:"POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            userdata = data;
+                            var displaysize = bytesToSize(data.Quota)
+                            
+                            if (data.Quota == -1){
+                                //Unlimited
+                                $("#quota").val(-1);
+                            }else if (data.Quota == -2){
+                                //Not initialized. Set it to 0
+                                $("#quota").val(0);
+                            }else{
+                                $("#quota").val(displaysize[0]);
+                            }
+                            
+                            $("#unit").dropdown("set selected",displaysize[1])
+                            if (callback !== undefined){
+                                callback();
+                            }
+                        }
+                    }
+                });
+            }
+
+            function initGroupList(callback=undefined){
+                //Initialize group list
+                $.ajax({
+                    url: "../../system/permission/listgroup?showper=true",
+                    success: function(data){
+                        $("#pgroups").html("");
+                        if (data.error !== undefined){
+                            alert(data.error)
+                        }else{
+                            groupList = data;
+                            for (const [key, value] of Object.entries(data)) {
+                                $("#pgroups").append(`<option value="${key}">${key}</option>`);
+                            }
+                            $('#pgroups').dropdown('set selected', userdata.Usergroup);
+                            updatePGTable();
+
+                            if (callback !== undefined){
+                                callback();
+                            }
+                            
+                        }
+                        
+                    }
+                });
+            }
+
+            function updatePGTable(){
+                $("#permissionTable").html("");
+                var pgList = $("#pgroups").val();
+                pgList.forEach(pgid => {
+                    var groupInfo = groupList[pgid];
+                    var isAdmin = '<i class="green checkmark icon"></i>';
+                    if (groupInfo[1] == false){
+                        isAdmin = '<i class="red remove icon"></i>';
+                    }
+                    $("#permissionTable").append(`
+                        <tr>
+                            <td data-label="groupname">${pgid}</td>
+                            <td data-label="isdamin">${isAdmin}</td>
+                            <td data-label="modules">${groupInfo[0].join("/")}</td>
+                        </tr>`);
+                });
+
+            }
+
+            function restartPassword(){
+                var username = $("#username").val();
+                $.ajax({
+                    url: "../../system/users/editUser",
+                    data: {opr: "resetPassword", username: username},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            //Update suceed
+                            initContents();
+                            $("#confirmUpdate").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
+                            console.log(data);
+                            $("#tmppw").val(location.protocol + '//' + location.host + "/reset.system?acc=" + username +  "&rkey=" + data);
+                            $("#tmppwui").slideDown('fast');
+                        }
+                    }
+                });
+            }
+
+            function handleUserProfileUpdate(e){
+                e.preventDefault();
+                //Get all the value from the form
+                var username = $("#username").val();
+                var pgs = JSON.stringify($("#pgroups").val());
+                
+                //Parse Quota
+                var userStorageQuota = parseFloat($("#quota").val()) * $("#unit").val();
+                if (isNaN(userStorageQuota)){
+                    $("#quota").parent().addClass("error");
+                    return
+                }else{
+                    $("#quota").parent().removeClass("error");
+                }
+
+                if (parseInt($("#quota").val()) == -1){
+                    userStorageQuota = -1;
+                }
+                
+                $.ajax({
+                    url: "../../system/users/editUser",
+                    data: {opr: "updateUserGroup", newgroup: pgs, username: username, quota: userStorageQuota},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            $("#errmsg").text(data.error);
+                            $("#updateError").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
+                        }else{
+                            //Update suceed
+                            initContents();
+                            updatePGTable();
+                            $("#confirmUpdate").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
+                            ao_module_parentCallback(true);
+
+                        }
+                        window.scrollTo(0,0);
+                    }
+                });
+            }
+
+           
+        </script>
+    </body>
 </html>

+ 329 - 328
web/SystemAO/users/editgroup.html

@@ -1,329 +1,330 @@
-<html>
-    <head>
-        <title>Edit Group</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <link rel="stylesheet" href="../../script/ao.css">
-        <script type="text/javascript" src="../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
-        <script type="text/javascript" src="../../script/ao_module.js"></script>
-        <style>
-            body{
-                background-color:white;
-            }
-            .themebackground{
-                background-color:#588ce0 !important;
-                color:white !important;
-                background-image: url("/img/public/slate.png") !important;
-                background-repeat: no-repeat !important;
-                background-attachment: fixed !important;
-            }
-            .ui.padded.slate{
-                width: 100%;
-                display: flex;
-                flex-direction: column;
-                padding: 4em;
-            }
-
-            .ui.heading.slate{
-                align-items: flex-start;
-            }
-
-            .ui.slate .header:not(.ui):not(.sub):not(.item){
-                font-size: 1.6em;
-                line-height: 1.42857em;
-                font-weight: 500;
-                display: block;
-            }
-
-            .required{
-                color:red;
-            }
-            .actionbtns{
-                text-align:right;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="ui heading fluid padded slate themebackground" >
-            <span class="header">
-            <i class="users icon"></i> Edit Users Group</span>
-            <span class="description">Fill in the following group information to proceed.</span>
-        </div>
-        <br>
-        <div class="ui container">
-            <div id="confirmUpdate" class="ui green inverted segment" style="display:none; margin-top:12px;">
-                <i class="checkmark icon"></i> Group Permissions Updated
-            </div>
-            <div class="ui horizontal form">
-                <div class="field">
-                    <label>Group Name <span class="required">(READ ONLY)</span></label>
-                    <input id="groupname" class="disabled" type="text" readonly="true">
-                </div>
-                <div class="two fields">
-                    <div class="field">
-                        <label>Default Storage Quota <span class="required">*</span></label>
-                        <input id="quota" type="text" value="15">
-                    </div>
-                    <div class="field">
-                        <label>Unit <span class="required">*</span></label>
-                        <select class="ui fluid search dropdown" id="unit">
-                            <option value="1">Bytes</option>
-                            <option value="1024">KB</option>
-                            <option value="1048576">MB</option>
-                            <option value="1073741824">GB</option>
-                            <option value="1099511627776">TB</option>
-                            <option value="1125899906842624">PB</option>
-                          </select>
-                    </div>
-                  </div>
-                <div class="field">
-                    <label>Default Interface Module <span class="required">*</span></label>
-                    <div class="ui fluid selection dropdown">
-                        <input type="hidden" name="dim">
-                        <i class="dropdown icon"></i>
-                        <div class="default text">Select Interface Module</div>
-                        <div class="menu" id="interfaceModuleList">
-                            
-                        </div>
-                      </div>
-                    <small>The module that the user land once they logged in. Default Desktop</small>
-                </div>
-                <div class="field">
-                    <label>Allow Access <span class="required">*</span></label>
-                    <select id="allowAccessList" multiple="" class="ui fluid dropdown">
-                        
-                    </select>
-                    <small>Allow this user group to access the selected modules and their APIs.</small>
-                </div>
-                <div class="field">
-                    <div class="ui checkbox">
-                        <input id="setAsAdmin" type="checkbox" tabindex="0" class="">
-                        <label>Assign Administrator Privileges to Group</label>
-                    </div>
-                </div>
-                <div class="ui divider"></div>
-                <table class="ui celled striped unstackable table">
-                    <thead>
-                        <tr>
-                            <th >#</th>
-                            <th>Module Name</th>
-                        </tr>
-                    </thead>
-                    <tbody id="selectedModuleList">
-                       
-                    </tbody>
-                </table>
-                <div class="ui divider"></div>
-                <div align="right">
-                    <button class="ui primary button" onclick="updateGroup();">Update</button>
-                    <button id="cancelbtn" class="ui button" onclick="cancel();">Close</button>
-                </div>
-                <div id="errorbox" class="ui inverted red segment" style="display:none;">
-                    <p><i class="remove icon"></i><span class="errormessage"></span></p>
-                </div>
-            </div>
-            <br><br>
-        </div>
-        <script>
-            var selectedModules = [];
-            var moduleList = [];
-            if (window.location.hash.length == 0){
-                //Invalid use of editor
-                window.location.href = "../closeTabInsturction.html";
-            }
-            var targetUserGroup = window.location.hash.substr(1);
-            targetUserGroup = JSON.parse(decodeURIComponent(targetUserGroup));
-            var originalGroupData;
-
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return [bytes / Math.pow(1024, i), sizes[i]];
-            }
-
-            //Init functions
-            initModuleList(function(){
-                //Set the contents of the selector to the current group one
-                $('.ui.checkbox').checkbox();
-                $.ajax({
-                    url: "../../system/permission/editgroup?list=true",
-                    data: {groupname: targetUserGroup},
-                    success: function(data){
-                        console.log(data);
-                        originalGroupData = data;
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }else{
-                            $("#groupname").val(data.Name);
-                            $("#interfaceModuleList").parent().dropdown("set selected",data.DefaultInterfaceModule);
-                            
-                            //Set accessable module list
-                            if (data.AccessibleModules.length > 0 && data.AccessibleModules[0] == "*"){
-                                $("#allowAccessList").parent().hide();
-                                $("#allowAccessList").parent().after("<p>This account has been set to have all permissions to all modules</p>");
-                                selectedModules = moduleList;
-                                renderSelectedModuleList();
-                            }else{
-                                $("#allowAccessList").dropdown("set selected",data.AccessibleModules);
-                            }
-
-                            //Set storage quota
-                            var defaultStorage = bytesToSize(data.DefaultStorageQuota);
-                            if (data.DefaultStorageQuota == -1){
-                                defaultStorage[0] = -1;
-                            }
-                            if (data.Quota == -1){
-                                $("#quota").val(-1);
-                            }else{
-                                $("#quota").val(defaultStorage[0]);
-                                $("#unit").dropdown("set selected",defaultStorage[1])
-                            }
-
-                            //Check admin checkbox
-                            if (data.IsAdmin == true){
-                                $("#setAsAdmin").parent().checkbox("check");
-                            }else{
-                                $("#setAsAdmin").parent().checkbox("uncheck");
-                            }
-                        }
-                    }
-                })
-            });
-            $(".ui.dropdown").dropdown();
-            $("#unit").dropdown("set selected","GB");
-
-            function updateGroup(){
-                var groupname = originalGroupData.Name;
-
-                //Continue to create usergroup
-                targetModuleList = [];
-                if (originalGroupData.AccessibleModules[0] == "*"){
-                    //Continue to use * as the accessable modules
-                    targetModuleList = originalGroupData.AccessibleModules;
-                }else{
-                    for (var i =0; i < selectedModules.length; i++){
-                        targetModuleList.push(selectedModules[i].Name);
-                    }
-                }
-
-                var defaultStorageSize = parseFloat($("#quota").val()) * $("#unit").val();
-                if (isNaN(defaultStorageSize)){
-                    $("#quota").parent().addClass("error");
-                    return
-                }else{
-                    $("#quota").parent().removeClass("error");
-                }
-
-                if ($("#quota").val() == -1){
-                    defaultStorageSize = -1;
-                }
-
-                var interfaceModule = $("#interfaceModuleList").parent().dropdown("get value");
-                if (interfaceModule == ""){
-                    interfaceModule = "Desktop";
-                }
-                
-                //Send Request to server side
-                $.ajax({
-                    url: "../../system/permission/editgroup",
-                    data: {
-                        "groupname": groupname, 
-                        "permission": JSON.stringify(targetModuleList),
-                        "isAdmin": $("#setAsAdmin").is(":checked"),
-                        "defaultQuota": defaultStorageSize,
-                        "interfaceModule": interfaceModule,
-                    },
-                    traditional: true,
-                    method: "POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            $("#errorbox").slideDown("fast");
-                            $("#errorbox").find(".errormessage").text(data.error);
-                        }else{
-                            $("#confirmUpdate").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
-                            ao_module_parentCallback(true);
-                            window.scrollTo(0,0);
-                            //ao_module_close();
-                        }
-                    }
-                })
-            }
-
-            function initModuleList(callback=undefined){
-                $("#interfaceModuleList").html("");
-                var firstInterfaceModule = null;
-                $.get("../../system/modules/list",function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        for (var i =0; i < data.length; i++){
-                            if (data[i].StartDir !== "" && data[i].Group != "Interface Module"){
-                                $("#allowAccessList").append(`<option value="${data[i].Name}" icon="${data[i].IconPath}">${data[i].Name}</option>`);
-                            }else if (data[i].Group == "Interface Module"){
-                                //Add to interface module list
-                                $("#interfaceModuleList").append(`
-                                <div class="item" data-value="${data[i].Name}">
-                                    <img class="ui mini avatar image" style="border-radius: 0px !important;" src="../../${data[i].IconPath}">
-                                    ${data[i].Name}
-                                </div>
-                                `);
-
-                                if (firstInterfaceModule == null){
-                                    firstInterfaceModule = data[i].Name;
-                                }
-                            }else{
-                                //Utlities modules. Always allow access
-
-                            }
-                        }
-                        //Select the first interface modules
-                        $("#interfaceModuleList").parent().dropdown();
-                        $("#interfaceModuleList").parent().dropdown("set selected",firstInterfaceModule);
-                        moduleList = data;
-
-                        if (callback !== undefined){
-                            //Handle callback events
-                            callback();
-                        }
-                    }
-                });
-
-                $("#allowAccessList").on("change", function(e){
-                    var currentSelected = $(this).val();
-                    selectedModules = [];
-                    moduleList.forEach(mod => {
-                        if (currentSelected.includes(mod.Name)){
-                            selectedModules.push(mod);
-                        }
-                    });
-                    renderSelectedModuleList();
-                });
-            }
-
-            renderSelectedModuleList();
-            function renderSelectedModuleList(){
-                $("#selectedModuleList").html("");
-                for (var i = 0; i < selectedModules.length; i++){
-                    $("#selectedModuleList").append(`<tr>
-                            <td class="collapsing"><img class="ui mini image" src="../../${selectedModules[i].IconPath}"/></td>
-                            <td>${selectedModules[i].Name}</td>
-                        </tr>`);
-                }
-                if (selectedModules.length == 0){
-                    $("#selectedModuleList").append(`<tr>
-                        <td class="collapsing"><img class="ui mini image" src="img/nomodule.png"></img></td>
-                        <td>No Module Selected</td>
-                       </tr>`);
-                }
-            }
-
-            function cancel(){
-                ao_module_close();
-            }
-            
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Edit Group</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <link rel="stylesheet" href="../../script/ao.css">
+        <script type="text/javascript" src="../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
+        <script type="text/javascript" src="../../script/ao_module.js"></script>
+        <style>
+            body{
+                background-color:white;
+            }
+            .themebackground{
+                background-color:#588ce0 !important;
+                color:white !important;
+                background-image: url("/img/public/slate.png") !important;
+                background-repeat: no-repeat !important;
+                background-attachment: fixed !important;
+            }
+            .ui.padded.slate{
+                width: 100%;
+                display: flex;
+                flex-direction: column;
+                padding: 4em;
+            }
+
+            .ui.heading.slate{
+                align-items: flex-start;
+            }
+
+            .ui.slate .header:not(.ui):not(.sub):not(.item){
+                font-size: 1.6em;
+                line-height: 1.42857em;
+                font-weight: 500;
+                display: block;
+            }
+
+            .required{
+                color:red;
+            }
+            .actionbtns{
+                text-align:right;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui heading fluid padded slate themebackground" >
+            <span class="header">
+            <i class="users icon"></i> Edit Users Group</span>
+            <span class="description">Fill in the following group information to proceed.</span>
+        </div>
+        <br>
+        <div class="ui container">
+            <div id="confirmUpdate" class="ui green inverted segment" style="display:none; margin-top:12px;">
+                <i class="checkmark icon"></i> Group Permissions Updated
+            </div>
+            <div class="ui horizontal form">
+                <div class="field">
+                    <label>Group Name <span class="required">(READ ONLY)</span></label>
+                    <input id="groupname" class="disabled" type="text" readonly="true">
+                </div>
+                <div class="two fields">
+                    <div class="field">
+                        <label>Default Storage Quota <span class="required">*</span></label>
+                        <input id="quota" type="text" value="15">
+                    </div>
+                    <div class="field">
+                        <label>Unit <span class="required">*</span></label>
+                        <select class="ui fluid search dropdown" id="unit">
+                            <option value="1">Bytes</option>
+                            <option value="1024">KB</option>
+                            <option value="1048576">MB</option>
+                            <option value="1073741824">GB</option>
+                            <option value="1099511627776">TB</option>
+                            <option value="1125899906842624">PB</option>
+                          </select>
+                    </div>
+                  </div>
+                <div class="field">
+                    <label>Default Interface Module <span class="required">*</span></label>
+                    <div class="ui fluid selection dropdown">
+                        <input type="hidden" name="dim">
+                        <i class="dropdown icon"></i>
+                        <div class="default text">Select Interface Module</div>
+                        <div class="menu" id="interfaceModuleList">
+                            
+                        </div>
+                      </div>
+                    <small>The module that the user land once they logged in. Default Desktop</small>
+                </div>
+                <div class="field">
+                    <label>Allow Access <span class="required">*</span></label>
+                    <select id="allowAccessList" multiple="" class="ui fluid dropdown">
+                        
+                    </select>
+                    <small>Allow this user group to access the selected modules and their APIs.</small>
+                </div>
+                <div class="field">
+                    <div class="ui checkbox">
+                        <input id="setAsAdmin" type="checkbox" tabindex="0" class="">
+                        <label>Assign Administrator Privileges to Group</label>
+                    </div>
+                </div>
+                <div class="ui divider"></div>
+                <table class="ui celled striped unstackable table">
+                    <thead>
+                        <tr>
+                            <th >#</th>
+                            <th>Module Name</th>
+                        </tr>
+                    </thead>
+                    <tbody id="selectedModuleList">
+                       
+                    </tbody>
+                </table>
+                <div class="ui divider"></div>
+                <div align="right">
+                    <button class="ui primary button" onclick="updateGroup();">Update</button>
+                    <button id="cancelbtn" class="ui button" onclick="cancel();">Close</button>
+                </div>
+                <div id="errorbox" class="ui inverted red segment" style="display:none;">
+                    <p><i class="remove icon"></i><span class="errormessage"></span></p>
+                </div>
+            </div>
+            <br><br>
+        </div>
+        <script>
+            var selectedModules = [];
+            var moduleList = [];
+            if (window.location.hash.length == 0){
+                //Invalid use of editor
+                window.location.href = "../closeTabInsturction.html";
+            }
+            var targetUserGroup = window.location.hash.substr(1);
+            targetUserGroup = JSON.parse(decodeURIComponent(targetUserGroup));
+            var originalGroupData;
+
+            function bytesToSize(bytes) {
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+                if (bytes == 0) return '0 Byte';
+                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+                return [bytes / Math.pow(1024, i), sizes[i]];
+            }
+
+            //Init functions
+            initModuleList(function(){
+                //Set the contents of the selector to the current group one
+                $('.ui.checkbox').checkbox();
+                $.ajax({
+                    url: "../../system/permission/editgroup?list=true",
+                    data: {groupname: targetUserGroup},
+                    success: function(data){
+                        console.log(data);
+                        originalGroupData = data;
+                        if (data.error !== undefined){
+                            alert(data.error);
+                        }else{
+                            $("#groupname").val(data.Name);
+                            $("#interfaceModuleList").parent().dropdown("set selected",data.DefaultInterfaceModule);
+                            
+                            //Set accessable module list
+                            if (data.AccessibleModules.length > 0 && data.AccessibleModules[0] == "*"){
+                                $("#allowAccessList").parent().hide();
+                                $("#allowAccessList").parent().after("<p>This account has been set to have all permissions to all modules</p>");
+                                selectedModules = moduleList;
+                                renderSelectedModuleList();
+                            }else{
+                                $("#allowAccessList").dropdown("set selected",data.AccessibleModules);
+                            }
+
+                            //Set storage quota
+                            var defaultStorage = bytesToSize(data.DefaultStorageQuota);
+                            if (data.DefaultStorageQuota == -1){
+                                defaultStorage[0] = -1;
+                            }
+                            if (data.Quota == -1){
+                                $("#quota").val(-1);
+                            }else{
+                                $("#quota").val(defaultStorage[0]);
+                                $("#unit").dropdown("set selected",defaultStorage[1])
+                            }
+
+                            //Check admin checkbox
+                            if (data.IsAdmin == true){
+                                $("#setAsAdmin").parent().checkbox("check");
+                            }else{
+                                $("#setAsAdmin").parent().checkbox("uncheck");
+                            }
+                        }
+                    }
+                })
+            });
+            $(".ui.dropdown").dropdown();
+            $("#unit").dropdown("set selected","GB");
+
+            function updateGroup(){
+                var groupname = originalGroupData.Name;
+
+                //Continue to create usergroup
+                targetModuleList = [];
+                if (originalGroupData.AccessibleModules[0] == "*"){
+                    //Continue to use * as the accessable modules
+                    targetModuleList = originalGroupData.AccessibleModules;
+                }else{
+                    for (var i =0; i < selectedModules.length; i++){
+                        targetModuleList.push(selectedModules[i].Name);
+                    }
+                }
+
+                var defaultStorageSize = parseFloat($("#quota").val()) * $("#unit").val();
+                if (isNaN(defaultStorageSize)){
+                    $("#quota").parent().addClass("error");
+                    return
+                }else{
+                    $("#quota").parent().removeClass("error");
+                }
+
+                if ($("#quota").val() == -1){
+                    defaultStorageSize = -1;
+                }
+
+                var interfaceModule = $("#interfaceModuleList").parent().dropdown("get value");
+                if (interfaceModule == ""){
+                    interfaceModule = "Desktop";
+                }
+                
+                //Send Request to server side
+                $.ajax({
+                    url: "../../system/permission/editgroup",
+                    data: {
+                        "groupname": groupname, 
+                        "permission": JSON.stringify(targetModuleList),
+                        "isAdmin": $("#setAsAdmin").is(":checked"),
+                        "defaultQuota": defaultStorageSize,
+                        "interfaceModule": interfaceModule,
+                    },
+                    traditional: true,
+                    method: "POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            $("#errorbox").slideDown("fast");
+                            $("#errorbox").find(".errormessage").text(data.error);
+                        }else{
+                            $("#confirmUpdate").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
+                            ao_module_parentCallback(true);
+                            window.scrollTo(0,0);
+                            //ao_module_close();
+                        }
+                    }
+                })
+            }
+
+            function initModuleList(callback=undefined){
+                $("#interfaceModuleList").html("");
+                var firstInterfaceModule = null;
+                $.get("../../system/modules/list",function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        for (var i =0; i < data.length; i++){
+                            if (data[i].StartDir !== "" && data[i].Group != "Interface Module"){
+                                $("#allowAccessList").append(`<option value="${data[i].Name}" icon="${data[i].IconPath}">${data[i].Name}</option>`);
+                            }else if (data[i].Group == "Interface Module"){
+                                //Add to interface module list
+                                $("#interfaceModuleList").append(`
+                                <div class="item" data-value="${data[i].Name}">
+                                    <img class="ui mini avatar image" style="border-radius: 0px !important;" src="../../${data[i].IconPath}">
+                                    ${data[i].Name}
+                                </div>
+                                `);
+
+                                if (firstInterfaceModule == null){
+                                    firstInterfaceModule = data[i].Name;
+                                }
+                            }else{
+                                //Utlities modules. Always allow access
+
+                            }
+                        }
+                        //Select the first interface modules
+                        $("#interfaceModuleList").parent().dropdown();
+                        $("#interfaceModuleList").parent().dropdown("set selected",firstInterfaceModule);
+                        moduleList = data;
+
+                        if (callback !== undefined){
+                            //Handle callback events
+                            callback();
+                        }
+                    }
+                });
+
+                $("#allowAccessList").on("change", function(e){
+                    var currentSelected = $(this).val();
+                    selectedModules = [];
+                    moduleList.forEach(mod => {
+                        if (currentSelected.includes(mod.Name)){
+                            selectedModules.push(mod);
+                        }
+                    });
+                    renderSelectedModuleList();
+                });
+            }
+
+            renderSelectedModuleList();
+            function renderSelectedModuleList(){
+                $("#selectedModuleList").html("");
+                for (var i = 0; i < selectedModules.length; i++){
+                    $("#selectedModuleList").append(`<tr>
+                            <td class="collapsing"><img class="ui mini image" src="../../${selectedModules[i].IconPath}"/></td>
+                            <td>${selectedModules[i].Name}</td>
+                        </tr>`);
+                }
+                if (selectedModules.length == 0){
+                    $("#selectedModuleList").append(`<tr>
+                        <td class="collapsing"><img class="ui mini image" src="img/nomodule.png"></img></td>
+                        <td>No Module Selected</td>
+                       </tr>`);
+                }
+            }
+
+            function cancel(){
+                ao_module_close();
+            }
+            
+        </script>
+    </body>
 </html>

+ 196 - 195
web/SystemAO/users/group.html

@@ -1,196 +1,197 @@
-<html>
-    <head>
-        <title>Group List</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="users icon"></i>
-                        <div class="content">
-                        Group Permissions
-                        <div class="sub header">Manage group permission and access right</div>
-                    </div>
-                </div>
-                <button class="ui primary small button" onclick="newgroupInterface();">
-                    <i class="add icon"></i>New Group
-                </button>
-                <button id="editGroupButton" class="ui small disabled button" onclick="openEditWindow();">
-                    Edit
-                </button>
-                <button id="removeGroupButton" class="ui small negative disabled right floated button" onclick="removeGroup();">
-                    Remove
-                </button>
-            </div>
-
-            <table class="ui very basic celled table">
-                <thead>
-                    <tr>
-                        <th>Group Name</th>
-                        <th>Access Permission</th>
-                        <th>Admin Privileges</th>
-                        <th>Default Storage Quota</th>
-                        <th>Select</th>
-                    </tr>
-                </thead>
-                <tbody id="groupList" >
-                    <tr>
-                        <td>administrator</td>
-                        <td>All Modules (Administrator)</td>
-                        <td>
-                            <div class="ui checkbox">
-                                <input type="radio" name="selectUser" onchange="enableEdit(this);">
-                                <label></label>
-                            </div>
-                        </td>
-                    </tr>
-                </tbody>
-              </table>
-        </div>
-        <script>
-            var groupPermissionInfo = [];
-            var currentSelectedGroup = "";
-
-            loadGroupList();
-            function loadGroupList(){
-                $.get("../../system/permission/listgroup?showper=true",function(data){
-                   $("#groupList").html("");
-                   $("#editGroupButton").addClass("disabled");
-                   $("#removeGroupButton").addClass('disabled');
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        for (const [key, value] of Object.entries(data)) {
-                            //Check for module list
-                            var accessPermission = "No Module Accessible";
-                            if (value[0].includes("*")){
-                                accessPermission = "All Modules (Administrator)";
-                            }else{
-                                accessPermission = '<div class="ui bulleted list">';
-                                for (var i =0; i < value[0].length; i++){
-                                    accessPermission += '<div class="item">' + value[0][i] + '</div>';
-                                }
-                                accessPermission += '</div>'
-                            }
-
-                            //Check for admin permission
-                            var isAdminIcon = "<i class='red remove icon'></i>"
-                            if (value[1] == true){
-                                isAdminIcon = "<i class='green checkmark icon'></i>"
-                            }
-
-                            //Check for storage quota
-                            var quota = bytesToSize(value[2]);
-                            if (value[2] == 0){
-                                quota = "Read Only";
-                            }else if (value[2] == -1){
-                                quota = "Unlimited";
-                            }
-
-                            $("#groupList").append(`
-                            <tr>
-                                <td>${key}</td>
-                                <td>${accessPermission}</td>
-                                <td>${isAdminIcon}</td>
-                                <td>${quota}</td>
-                                <td>
-                                    <div class="ui checkbox">
-                                        <input type="radio" name="selectUser" value="${key}" onchange="enableEdit(this);">
-                                        <label></label>
-                                    </div>
-                                </td>
-                            </tr>
-                            `);
-                        }
-                        groupPermissionInfo = data;
-                    }
-
-                    
-                });
-            }
-
-            function openEditWindow(){
-                ao_module_newfw({
-                    url: "SystemAO/users/editgroup.html#" + encodeURIComponent(JSON.stringify(currentSelectedGroup)),
-                    width: 560,
-                    height: 768,
-                    appicon: "SystemAO/users/img/users-white.svg",
-                    title: "Edit User Group",
-                    parent: ao_module_windowID,
-                    callback: "confirmCreateHandler"
-                });
-            }
-
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
-            }
-
-
-            function enableEdit(obj){
-                //console.log($(obj).val());
-                $("#editGroupButton").removeClass('disabled');
-                if ($(obj).val() != "administrator"){
-                    $("#removeGroupButton").removeClass('disabled');
-                }else{
-                    $("#removeGroupButton").addClass('disabled');
-                }
-                currentSelectedGroup = $(obj).val();
-                
-            }
-
-            function newgroupInterface(){
-                ao_module_newfw({
-                    url: "SystemAO/users/newgroup.html",
-                    width: 560,
-                    height: 768,
-                    appicon: "SystemAO/users/img/users-white.svg",
-                    title: "Create User Group",
-                    parent: ao_module_windowID,
-                    callback: "confirmCreateHandler"
-                });
-            }
-
-            //Finished adding group. Update grouplist
-            function confirmCreateHandler(data){
-                if (data){
-                    loadGroupList();
-                }
-            }
-
-            //Remove the selected group
-            function removeGroup(){
-                if (currentSelectedGroup == "administrator"){
-                    //This group cannot be removed
-                    alert("You cannot remove the administrator group.")
-                }else{
-                    //Remove the selected group
-                    if (confirm("Confirm removing group: " + currentSelectedGroup + " ?")){
-                        $.ajax({
-                            url: "../../system/permission/delgroup",
-                            data: {groupname: currentSelectedGroup},
-                            method: "GET",
-                            success: function(data){
-                                if (data.error !== undefined){
-                                    alert(data.error);
-                                }else{
-                                    //Remove succeed. refresh list
-                                    loadGroupList();
-                                }
-                            }
-
-                        })
-                    }
-                }
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Group List</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="users icon"></i>
+                        <div class="content">
+                        Group Permissions
+                        <div class="sub header">Manage group permission and access right</div>
+                    </div>
+                </div>
+                <button class="ui primary small button" onclick="newgroupInterface();">
+                    <i class="add icon"></i>New Group
+                </button>
+                <button id="editGroupButton" class="ui small disabled button" onclick="openEditWindow();">
+                    Edit
+                </button>
+                <button id="removeGroupButton" class="ui small negative disabled right floated button" onclick="removeGroup();">
+                    Remove
+                </button>
+            </div>
+
+            <table class="ui very basic celled table">
+                <thead>
+                    <tr>
+                        <th>Group Name</th>
+                        <th>Access Permission</th>
+                        <th>Admin Privileges</th>
+                        <th>Default Storage Quota</th>
+                        <th>Select</th>
+                    </tr>
+                </thead>
+                <tbody id="groupList" >
+                    <tr>
+                        <td>administrator</td>
+                        <td>All Modules (Administrator)</td>
+                        <td>
+                            <div class="ui checkbox">
+                                <input type="radio" name="selectUser" onchange="enableEdit(this);">
+                                <label></label>
+                            </div>
+                        </td>
+                    </tr>
+                </tbody>
+              </table>
+        </div>
+        <script>
+            var groupPermissionInfo = [];
+            var currentSelectedGroup = "";
+
+            loadGroupList();
+            function loadGroupList(){
+                $.get("../../system/permission/listgroup?showper=true",function(data){
+                   $("#groupList").html("");
+                   $("#editGroupButton").addClass("disabled");
+                   $("#removeGroupButton").addClass('disabled');
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        for (const [key, value] of Object.entries(data)) {
+                            //Check for module list
+                            var accessPermission = "No Module Accessible";
+                            if (value[0].includes("*")){
+                                accessPermission = "All Modules (Administrator)";
+                            }else{
+                                accessPermission = '<div class="ui bulleted list">';
+                                for (var i =0; i < value[0].length; i++){
+                                    accessPermission += '<div class="item">' + value[0][i] + '</div>';
+                                }
+                                accessPermission += '</div>'
+                            }
+
+                            //Check for admin permission
+                            var isAdminIcon = "<i class='red remove icon'></i>"
+                            if (value[1] == true){
+                                isAdminIcon = "<i class='green checkmark icon'></i>"
+                            }
+
+                            //Check for storage quota
+                            var quota = bytesToSize(value[2]);
+                            if (value[2] == 0){
+                                quota = "Read Only";
+                            }else if (value[2] == -1){
+                                quota = "Unlimited";
+                            }
+
+                            $("#groupList").append(`
+                            <tr>
+                                <td>${key}</td>
+                                <td>${accessPermission}</td>
+                                <td>${isAdminIcon}</td>
+                                <td>${quota}</td>
+                                <td>
+                                    <div class="ui checkbox">
+                                        <input type="radio" name="selectUser" value="${key}" onchange="enableEdit(this);">
+                                        <label></label>
+                                    </div>
+                                </td>
+                            </tr>
+                            `);
+                        }
+                        groupPermissionInfo = data;
+                    }
+
+                    
+                });
+            }
+
+            function openEditWindow(){
+                ao_module_newfw({
+                    url: "SystemAO/users/editgroup.html#" + encodeURIComponent(JSON.stringify(currentSelectedGroup)),
+                    width: 560,
+                    height: 768,
+                    appicon: "SystemAO/users/img/users-white.svg",
+                    title: "Edit User Group",
+                    parent: ao_module_windowID,
+                    callback: "confirmCreateHandler"
+                });
+            }
+
+            function bytesToSize(bytes) {
+                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+                if (bytes == 0) return '0 Byte';
+                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+            }
+
+
+            function enableEdit(obj){
+                //console.log($(obj).val());
+                $("#editGroupButton").removeClass('disabled');
+                if ($(obj).val() != "administrator"){
+                    $("#removeGroupButton").removeClass('disabled');
+                }else{
+                    $("#removeGroupButton").addClass('disabled');
+                }
+                currentSelectedGroup = $(obj).val();
+                
+            }
+
+            function newgroupInterface(){
+                ao_module_newfw({
+                    url: "SystemAO/users/newgroup.html",
+                    width: 560,
+                    height: 768,
+                    appicon: "SystemAO/users/img/users-white.svg",
+                    title: "Create User Group",
+                    parent: ao_module_windowID,
+                    callback: "confirmCreateHandler"
+                });
+            }
+
+            //Finished adding group. Update grouplist
+            function confirmCreateHandler(data){
+                if (data){
+                    loadGroupList();
+                }
+            }
+
+            //Remove the selected group
+            function removeGroup(){
+                if (currentSelectedGroup == "administrator"){
+                    //This group cannot be removed
+                    alert("You cannot remove the administrator group.")
+                }else{
+                    //Remove the selected group
+                    if (confirm("Confirm removing group: " + currentSelectedGroup + " ?")){
+                        $.ajax({
+                            url: "../../system/permission/delgroup",
+                            data: {groupname: currentSelectedGroup},
+                            method: "GET",
+                            success: function(data){
+                                if (data.error !== undefined){
+                                    alert(data.error);
+                                }else{
+                                    //Remove succeed. refresh list
+                                    loadGroupList();
+                                }
+                            }
+
+                        })
+                    }
+                }
+            }
+        </script>
+    </body>
 </html>

+ 253 - 252
web/SystemAO/users/newgroup.html

@@ -1,253 +1,254 @@
-<html>
-    <head>
-        <title>Create Group</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <link rel="stylesheet" href="../../script/ao.css">
-        <script type="text/javascript" src="../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
-        <script type="text/javascript" src="../../script/ao_module.js"></script>
-        <style>
-            body{
-                background-color:white;
-            }
-            .themebackground{
-                background-color:#588ce0 !important;
-                color:white !important;
-                background-image: url("/img/public/slate.png") !important;
-                background-repeat: no-repeat !important;
-                background-attachment: fixed !important;
-            }
-            .ui.padded.slate{
-                width: 100%;
-                display: flex;
-                flex-direction: column;
-                padding: 4em;
-            }
-
-            .ui.heading.slate{
-                align-items: flex-start;
-            }
-
-            .ui.slate .header:not(.ui):not(.sub):not(.item){
-                font-size: 1.6em;
-                line-height: 1.42857em;
-                font-weight: 500;
-                display: block;
-            }
-
-            .required{
-                color:red;
-            }
-            .actionbtns{
-                text-align:right;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="ui heading fluid padded slate themebackground" >
-            <span class="header">
-            <i class="users icon"></i> Create Users Group</span>
-            <span class="description">Fill in the following group information to proceed.</span>
-        </div>
-        <br>
-        <div class="ui container">
-            <div class="ui horizontal form">
-                <div class="field">
-                    <label>Group Name <span class="required">*</span></label>
-                    <input id="groupname" type="text">
-                </div>
-                <div class="two fields">
-                    <div class="field">
-                        <label>Default Storage Quota <span class="required">*</span></label>
-                        <input id="quota" type="text" value="15">
-                    </div>
-                    <div class="field">
-                        <label>Unit <span class="required">*</span></label>
-                        <select class="ui fluid search dropdown" id="unit">
-                            <option value="1">Bytes</option>
-                            <option value="1024">KB</option>
-                            <option value="1048576">MB</option>
-                            <option value="1073741824">GB</option>
-                            <option value="1099511627776">TB</option>
-                            <option value="1125899906842624">PB</option>
-                          </select>
-                    </div>
-                  </div>
-                <div class="field">
-                    <label>Default Interface Module <span class="required">*</span></label>
-                    <div class="ui fluid selection dropdown">
-                        <input type="hidden" name="dim">
-                        <i class="dropdown icon"></i>
-                        <div class="default text">Select Interface Module</div>
-                        <div class="menu" id="interfaceModuleList">
-                            
-                        </div>
-                      </div>
-                    <small>The module that the user land once they logged in. Default Desktop</small>
-                </div>
-                <div class="field">
-                    <label>Allow Access <span class="required">*</span></label>
-                    <select id="allowAccessList" multiple="" class="ui fluid dropdown">
-                        
-                    </select>
-                    <small>Allow this user group to access the selected modules and their APIs.</small>
-                </div>
-                <div class="field">
-                    <div class="ui checkbox">
-                        <input id="setAsAdmin" type="checkbox" tabindex="0" class="">
-                        <label>Assign Administrator Privileges to Group</label>
-                    </div>
-                </div>
-                <div class="ui divider"></div>
-                <table class="ui celled striped compact unstackable table">
-                    <thead>
-                        <tr>
-                            <th >#</th>
-                            <th>Module Name</th>
-                        </tr>
-                    </thead>
-                    <tbody id="selectedModuleList">
-                       
-                    </tbody>
-                </table>
-                <div class="ui divider"></div>
-                <div align="right">
-                    <button class="ui primary button" onclick="createGroup();">Create</button>
-                    <button id="cancelbtn" class="ui button" onclick="cancel();">Cancel</button>
-                </div>
-                <div id="errorbox" class="ui inverted red segment" style="display:none;">
-                    <p><i class="remove icon"></i><span class="errormessage"></span></p>
-                </div>
-            </div>
-            <br><br>
-        </div>
-        <script>
-            var selectedModules = [];
-            var moduleList = [];
-            //Init functions
-            initModuleList();
-            $(".ui.dropdown").dropdown();
-            $("#unit").dropdown("set selected","GB");
-            function createGroup(){
-                var groupname = $("#groupname").val();
-                if (groupname == ""){
-                    $("#groupname").parent().removeClass("success").addClass("error");
-                    return
-                }else{
-                    $("#groupname").parent().removeClass("error").addClass("success");
-                }
-
-                //Continue to create usergroup
-                targetModuleList = [];
-                for (var i =0; i < selectedModules.length; i++){
-                    targetModuleList.push(selectedModules[i].Name);
-                }
-
-                var defaultStorageSize = parseFloat($("#quota").val()) * $("#unit").val();
-                if (isNaN(defaultStorageSize)){
-                    $("#quota").parent().addClass("error");
-                    return
-                }else{
-                    $("#quota").parent().removeClass("error");
-                }
-
-                var interfaceModule = $("#interfaceModuleList").parent().dropdown("get value");
-                if (interfaceModule == ""){
-                    interfaceModule = "Desktop";
-                }
-                //Send Request to server side
-                $.ajax({
-                    url: "../../system/permission/newgroup",
-                    data: {
-                        "groupname": groupname, 
-                        "permission": JSON.stringify(targetModuleList),
-                        "isAdmin": $("#setAsAdmin").is(":checked"),
-                        "defaultQuota": defaultStorageSize,
-                        "interfaceModule": interfaceModule,
-                    },
-                    traditional: true,
-                    method: "POST",
-                    success: function(data){
-                        if (data.error !== undefined){
-                            $("#errorbox").slideDown("fast");
-                            $("#errorbox").find(".errormessage").text(data.error);
-                        }else{
-                            ao_module_parentCallback(true);
-                            ao_module_close();
-                        }
-                    }
-                })
-            }
-
-            function initModuleList(){
-                $("#interfaceModuleList").html("");
-                var firstInterfaceModule = null;
-                $.get("../../system/modules/list",function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        for (var i =0; i < data.length; i++){
-                            if (data[i].StartDir !== ""  && data[i].Group != "Interface Module"){
-                                $("#allowAccessList").append(`<option value="${data[i].Name}" icon="${data[i].IconPath}">${data[i].Name}</option>`);
-                            }else if (data[i].Group == "Interface Module"){
-                                //Add to interface module list
-                                $("#interfaceModuleList").append(`
-                                <div class="item" data-value="${data[i].Name}">
-                                    <img class="ui mini avatar image" style="border-radius: 0px !important;" src="../../${data[i].IconPath}">
-                                    ${data[i].Name}
-                                </div>
-                                `);
-
-                                if (firstInterfaceModule == null){
-                                    firstInterfaceModule = data[i].Name;
-                                }
-                            }else{
-                                //Utlities modules. Always allow access
-
-                            }
-                        }
-                        //Select the first interface modules
-                        $("#interfaceModuleList").parent().dropdown();
-                        $("#interfaceModuleList").parent().dropdown("set selected",firstInterfaceModule);
-                        moduleList = data;
-                    }
-                });
-
-                $("#allowAccessList").on("change", function(e){
-                    var currentSelected = $(this).val();
-                    selectedModules = [];
-                    moduleList.forEach(mod => {
-                        if (currentSelected.includes(mod.Name)){
-                            selectedModules.push(mod);
-                        }
-                    });
-                    renderSelectedModuleList();
-                });
-            }
-
-            renderSelectedModuleList();
-            function renderSelectedModuleList(){
-                $("#selectedModuleList").html("");
-                for (var i = 0; i < selectedModules.length; i++){
-                    $("#selectedModuleList").append(`<tr>
-                            <td class="collapsing"><img class="ui mini image" src="../../${selectedModules[i].IconPath}"/></td>
-                            <td>${selectedModules[i].Name}</td>
-                        </tr>`);
-                }
-                if (selectedModules.length == 0){
-                    $("#selectedModuleList").append(`<tr>
-                        <td class="collapsing"><img class="ui mini image" src="img/nomodule.png"></img></td>
-                        <td>No Module Selected</td>
-                       </tr>`);
-                }
-            }
-
-            function cancel(){
-                ao_module_close();
-            }
-            
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Create Group</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <link rel="stylesheet" href="../../script/ao.css">
+        <script type="text/javascript" src="../../script/jquery.min.js"></script>
+        <script type="text/javascript" src="../../script/semantic/semantic.js"></script>
+        <script type="text/javascript" src="../../script/ao_module.js"></script>
+        <style>
+            body{
+                background-color:white;
+            }
+            .themebackground{
+                background-color:#588ce0 !important;
+                color:white !important;
+                background-image: url("/img/public/slate.png") !important;
+                background-repeat: no-repeat !important;
+                background-attachment: fixed !important;
+            }
+            .ui.padded.slate{
+                width: 100%;
+                display: flex;
+                flex-direction: column;
+                padding: 4em;
+            }
+
+            .ui.heading.slate{
+                align-items: flex-start;
+            }
+
+            .ui.slate .header:not(.ui):not(.sub):not(.item){
+                font-size: 1.6em;
+                line-height: 1.42857em;
+                font-weight: 500;
+                display: block;
+            }
+
+            .required{
+                color:red;
+            }
+            .actionbtns{
+                text-align:right;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui heading fluid padded slate themebackground" >
+            <span class="header">
+            <i class="users icon"></i> Create Users Group</span>
+            <span class="description">Fill in the following group information to proceed.</span>
+        </div>
+        <br>
+        <div class="ui container">
+            <div class="ui horizontal form">
+                <div class="field">
+                    <label>Group Name <span class="required">*</span></label>
+                    <input id="groupname" type="text">
+                </div>
+                <div class="two fields">
+                    <div class="field">
+                        <label>Default Storage Quota <span class="required">*</span></label>
+                        <input id="quota" type="text" value="15">
+                    </div>
+                    <div class="field">
+                        <label>Unit <span class="required">*</span></label>
+                        <select class="ui fluid search dropdown" id="unit">
+                            <option value="1">Bytes</option>
+                            <option value="1024">KB</option>
+                            <option value="1048576">MB</option>
+                            <option value="1073741824">GB</option>
+                            <option value="1099511627776">TB</option>
+                            <option value="1125899906842624">PB</option>
+                          </select>
+                    </div>
+                  </div>
+                <div class="field">
+                    <label>Default Interface Module <span class="required">*</span></label>
+                    <div class="ui fluid selection dropdown">
+                        <input type="hidden" name="dim">
+                        <i class="dropdown icon"></i>
+                        <div class="default text">Select Interface Module</div>
+                        <div class="menu" id="interfaceModuleList">
+                            
+                        </div>
+                      </div>
+                    <small>The module that the user land once they logged in. Default Desktop</small>
+                </div>
+                <div class="field">
+                    <label>Allow Access <span class="required">*</span></label>
+                    <select id="allowAccessList" multiple="" class="ui fluid dropdown">
+                        
+                    </select>
+                    <small>Allow this user group to access the selected modules and their APIs.</small>
+                </div>
+                <div class="field">
+                    <div class="ui checkbox">
+                        <input id="setAsAdmin" type="checkbox" tabindex="0" class="">
+                        <label>Assign Administrator Privileges to Group</label>
+                    </div>
+                </div>
+                <div class="ui divider"></div>
+                <table class="ui celled striped compact unstackable table">
+                    <thead>
+                        <tr>
+                            <th >#</th>
+                            <th>Module Name</th>
+                        </tr>
+                    </thead>
+                    <tbody id="selectedModuleList">
+                       
+                    </tbody>
+                </table>
+                <div class="ui divider"></div>
+                <div align="right">
+                    <button class="ui primary button" onclick="createGroup();">Create</button>
+                    <button id="cancelbtn" class="ui button" onclick="cancel();">Cancel</button>
+                </div>
+                <div id="errorbox" class="ui inverted red segment" style="display:none;">
+                    <p><i class="remove icon"></i><span class="errormessage"></span></p>
+                </div>
+            </div>
+            <br><br>
+        </div>
+        <script>
+            var selectedModules = [];
+            var moduleList = [];
+            //Init functions
+            initModuleList();
+            $(".ui.dropdown").dropdown();
+            $("#unit").dropdown("set selected","GB");
+            function createGroup(){
+                var groupname = $("#groupname").val();
+                if (groupname == ""){
+                    $("#groupname").parent().removeClass("success").addClass("error");
+                    return
+                }else{
+                    $("#groupname").parent().removeClass("error").addClass("success");
+                }
+
+                //Continue to create usergroup
+                targetModuleList = [];
+                for (var i =0; i < selectedModules.length; i++){
+                    targetModuleList.push(selectedModules[i].Name);
+                }
+
+                var defaultStorageSize = parseFloat($("#quota").val()) * $("#unit").val();
+                if (isNaN(defaultStorageSize)){
+                    $("#quota").parent().addClass("error");
+                    return
+                }else{
+                    $("#quota").parent().removeClass("error");
+                }
+
+                var interfaceModule = $("#interfaceModuleList").parent().dropdown("get value");
+                if (interfaceModule == ""){
+                    interfaceModule = "Desktop";
+                }
+                //Send Request to server side
+                $.ajax({
+                    url: "../../system/permission/newgroup",
+                    data: {
+                        "groupname": groupname, 
+                        "permission": JSON.stringify(targetModuleList),
+                        "isAdmin": $("#setAsAdmin").is(":checked"),
+                        "defaultQuota": defaultStorageSize,
+                        "interfaceModule": interfaceModule,
+                    },
+                    traditional: true,
+                    method: "POST",
+                    success: function(data){
+                        if (data.error !== undefined){
+                            $("#errorbox").slideDown("fast");
+                            $("#errorbox").find(".errormessage").text(data.error);
+                        }else{
+                            ao_module_parentCallback(true);
+                            ao_module_close();
+                        }
+                    }
+                })
+            }
+
+            function initModuleList(){
+                $("#interfaceModuleList").html("");
+                var firstInterfaceModule = null;
+                $.get("../../system/modules/list",function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        for (var i =0; i < data.length; i++){
+                            if (data[i].StartDir !== ""  && data[i].Group != "Interface Module"){
+                                $("#allowAccessList").append(`<option value="${data[i].Name}" icon="${data[i].IconPath}">${data[i].Name}</option>`);
+                            }else if (data[i].Group == "Interface Module"){
+                                //Add to interface module list
+                                $("#interfaceModuleList").append(`
+                                <div class="item" data-value="${data[i].Name}">
+                                    <img class="ui mini avatar image" style="border-radius: 0px !important;" src="../../${data[i].IconPath}">
+                                    ${data[i].Name}
+                                </div>
+                                `);
+
+                                if (firstInterfaceModule == null){
+                                    firstInterfaceModule = data[i].Name;
+                                }
+                            }else{
+                                //Utlities modules. Always allow access
+
+                            }
+                        }
+                        //Select the first interface modules
+                        $("#interfaceModuleList").parent().dropdown();
+                        $("#interfaceModuleList").parent().dropdown("set selected",firstInterfaceModule);
+                        moduleList = data;
+                    }
+                });
+
+                $("#allowAccessList").on("change", function(e){
+                    var currentSelected = $(this).val();
+                    selectedModules = [];
+                    moduleList.forEach(mod => {
+                        if (currentSelected.includes(mod.Name)){
+                            selectedModules.push(mod);
+                        }
+                    });
+                    renderSelectedModuleList();
+                });
+            }
+
+            renderSelectedModuleList();
+            function renderSelectedModuleList(){
+                $("#selectedModuleList").html("");
+                for (var i = 0; i < selectedModules.length; i++){
+                    $("#selectedModuleList").append(`<tr>
+                            <td class="collapsing"><img class="ui mini image" src="../../${selectedModules[i].IconPath}"/></td>
+                            <td>${selectedModules[i].Name}</td>
+                        </tr>`);
+                }
+                if (selectedModules.length == 0){
+                    $("#selectedModuleList").append(`<tr>
+                        <td class="collapsing"><img class="ui mini image" src="img/nomodule.png"></img></td>
+                        <td>No Module Selected</td>
+                       </tr>`);
+                }
+            }
+
+            function cancel(){
+                ao_module_close();
+            }
+            
+        </script>
+    </body>
 </html>

+ 180 - 179
web/SystemAO/users/pubreg.html

@@ -1,180 +1,181 @@
-<html>
-    <head>
-        <title>Public Registry Settings</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="key icon"></i>
-                        <div class="content">
-                        Public Registry
-                        <div class="sub header">Allow public to register an account on this system</div>
-                    </div>
-                </div>
-            </div>
-            <div id="succdialog" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i> Registry Setting Updated</div>
-            <div id="errdialog" style="display:none;" class="ui red inverted segment">Update Failed: <span id="errmsg"></span></div>
-            <form class="ui form" onsubmit="handleFormSubmit(event);">
-                <div class="ui segment">
-                    <div class="field">
-                        <div class="ui toggle checkbox">
-                            <input type="checkbox" id="enableReg" tabindex="0" class="hidden">
-                            <label>Enable Public Account Registry</label>
-                        </div>
-                    </div>
-                </div>
-                <div class="field">
-                    <label>Default Permission Group for New User</label>
-                    <div class="ui fluid search selection dropdown">
-                            <input type="hidden" name="pg" id="pg">
-                            <i class="dropdown icon"></i>
-                            <div class="default text">Select Permission Group</div>
-                            <div id="pglist" class="menu">
-                                    
-                            </div>
-                    </div>
-                </div>
-                <div class="ui blue button" tabindex="0" onclick="handleFormSubmit(event);">Update</div>
-            </form>
-            <div class="ui divider"></div>
-            <div class="ui basic segment">
-                <h4>Registered User List</h4>
-                <a class="ui green right floated button" href="../../system/register/listUserEmails?csv=true" target="_blank">Download as CSV</a>
-                <p>A list of registered users. Be noted that the user registration information will not be deleted when a user account is deregistered. The status of the user account status will be shown in the "Still Registered" collum.</p>
-            </div>
-            <table class="ui celled table">
-                <thead>
-                    <tr><th>Username</th>
-                    <th>Email</th>
-                    <th>Still Registered</th>
-                </tr></thead>
-                <tbody id="userlist">
-                    
-                </tbody>
-                </table>
-                <br>
-                <button class="ui blue button" onclick="initUserRegisterInfoList();"><i class="refresh icon"></i>Refresh Table</button>
-                <button class="ui right floated red button" onclick="clearUnregisteredUserInfo();"><i class="remove icon"></i> Clear Unregistered Users Information</button>
-                
-                <br><br><br>
-        
-        </div>
-        <script>
-            $(".ui.checkbox").checkbox();
-
-            initRegStatus();
-            function initRegStatus(){
-                $.get("../../system/register/getAllowRegistry", function(data){
-                    if (data == true){
-                        $("#enableReg").parent().checkbox("check");
-                    }
-                });
-                $.get("../../system/permission/listgroup", function(data){
-                    if (data.error !== undefined){
-                        alert(data.error);
-                    }else{
-                        $("#pglist").html("");
-                        data.forEach(gp => {
-                            $("#pglist").append(`<div class="item" data-value="${gp}">${gp}</div>`);
-                        })
-                    }
-                    $(".ui.dropdown").dropdown();
-
-                    //Get the current default group
-                    $.get("../../system/register/setDefaultGroup?get=true", function(data){
-                        $("#pglist").parent().dropdown("set selected",data);
-                    });
-                });
-
-            }
-
-            function handleFormSubmit(e){
-                e.preventDefault();
-                var pg = $("#pg").val();
-                var regEnabled = $("#enableReg")[0].checked;
-                
-                $.ajax({
-                    url: "../../system/register/setDefaultGroup",
-                    data: {"defaultGroup": pg},
-                    success: function(data){
-                        if (data.error !== undefined){
-                            showErrorMessage(data.error);
-                        }else{
-                            showSuccessMessage();
-                        }
-                    },
-                    error: function(){
-                        showErrorMessage("Server Communication Error");
-                    }
-                });
-
-                $.ajax({
-                    url: "../../system/register/setAllowRegistry",
-                    data: {"allow": regEnabled},
-                    success: function(data){
-                        if (data.error !== undefined){
-                            showErrorMessage(data.error);
-                        }else{
-                            showSuccessMessage();
-                        }
-                    },
-                    error: function(){
-                        showErrorMessage("Server Communication Error");
-                    }
-                });
-            }
-
-            function showSuccessMessage(){
-                $("#succdialog").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
-            }
-
-            function showErrorMessage(err){
-                $("#errmsg").text(err);
-                $("#errdialog").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
-
-            }
-
-            initUserRegisterInfoList();
-            function initUserRegisterInfoList(){
-                $("#userlist").html("");
-                $.get("../../system/register/listUserEmails", function(data){
-                    if (data.error !== undefined){
-                        alert(data.error)
-                    }else{
-                        data.forEach(userinfo => {
-                            var icon = "green checkmark icon";
-                            if (userinfo[2] == false){
-                                icon = "red remove icon"
-                            }
-                            $("#userlist").append(`<tr>
-                                <td>${userinfo[0]}</td>
-                                <td><a href="mailto:${userinfo[1]}">${userinfo[1]}</a></td>
-                                <td><i class="${icon}"></i></td>
-                            </tr>`);
-                        });
-
-                        if (data.length == 0){
-                            $("#userlist").append(`<tr><td col="3"><i class="remove icon"></i> There is no user register record in the database.</td></tr>`);
-                        }
-                    }
-                })
-            }
-
-            //Remove all unregistered user information
-            function clearUnregisteredUserInfo(){
-                if (confirm("Confirm Removing ALL UNREGISTERED USERS INFORMATION?")){
-                    $.get("../../system/register/cleanUserRegisterInfo", function(data){
-                        initUserRegisterInfoList();
-                    });
-                }
-            }
-        </script>
-    </body>
+<!DOCTYPE html><h
+tml>
+    <head>
+        <title>Public Registry Settings</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="key icon"></i>
+                        <div class="content">
+                        Public Registry
+                        <div class="sub header">Allow public to register an account on this system</div>
+                    </div>
+                </div>
+            </div>
+            <div id="succdialog" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i> Registry Setting Updated</div>
+            <div id="errdialog" style="display:none;" class="ui red inverted segment">Update Failed: <span id="errmsg"></span></div>
+            <form class="ui form" onsubmit="handleFormSubmit(event);">
+                <div class="ui segment">
+                    <div class="field">
+                        <div class="ui toggle checkbox">
+                            <input type="checkbox" id="enableReg" tabindex="0" class="hidden">
+                            <label>Enable Public Account Registry</label>
+                        </div>
+                    </div>
+                </div>
+                <div class="field">
+                    <label>Default Permission Group for New User</label>
+                    <div class="ui fluid search selection dropdown">
+                            <input type="hidden" name="pg" id="pg">
+                            <i class="dropdown icon"></i>
+                            <div class="default text">Select Permission Group</div>
+                            <div id="pglist" class="menu">
+                                    
+                            </div>
+                    </div>
+                </div>
+                <div class="ui blue button" tabindex="0" onclick="handleFormSubmit(event);">Update</div>
+            </form>
+            <div class="ui divider"></div>
+            <div class="ui basic segment">
+                <h4>Registered User List</h4>
+                <a class="ui green right floated button" href="../../system/register/listUserEmails?csv=true" target="_blank">Download as CSV</a>
+                <p>A list of registered users. Be noted that the user registration information will not be deleted when a user account is deregistered. The status of the user account status will be shown in the "Still Registered" collum.</p>
+            </div>
+            <table class="ui celled table">
+                <thead>
+                    <tr><th>Username</th>
+                    <th>Email</th>
+                    <th>Still Registered</th>
+                </tr></thead>
+                <tbody id="userlist">
+                    
+                </tbody>
+                </table>
+                <br>
+                <button class="ui blue button" onclick="initUserRegisterInfoList();"><i class="refresh icon"></i>Refresh Table</button>
+                <button class="ui right floated red button" onclick="clearUnregisteredUserInfo();"><i class="remove icon"></i> Clear Unregistered Users Information</button>
+                
+                <br><br><br>
+        
+        </div>
+        <script>
+            $(".ui.checkbox").checkbox();
+
+            initRegStatus();
+            function initRegStatus(){
+                $.get("../../system/register/getAllowRegistry", function(data){
+                    if (data == true){
+                        $("#enableReg").parent().checkbox("check");
+                    }
+                });
+                $.get("../../system/permission/listgroup", function(data){
+                    if (data.error !== undefined){
+                        alert(data.error);
+                    }else{
+                        $("#pglist").html("");
+                        data.forEach(gp => {
+                            $("#pglist").append(`<div class="item" data-value="${gp}">${gp}</div>`);
+                        })
+                    }
+                    $(".ui.dropdown").dropdown();
+
+                    //Get the current default group
+                    $.get("../../system/register/setDefaultGroup?get=true", function(data){
+                        $("#pglist").parent().dropdown("set selected",data);
+                    });
+                });
+
+            }
+
+            function handleFormSubmit(e){
+                e.preventDefault();
+                var pg = $("#pg").val();
+                var regEnabled = $("#enableReg")[0].checked;
+                
+                $.ajax({
+                    url: "../../system/register/setDefaultGroup",
+                    data: {"defaultGroup": pg},
+                    success: function(data){
+                        if (data.error !== undefined){
+                            showErrorMessage(data.error);
+                        }else{
+                            showSuccessMessage();
+                        }
+                    },
+                    error: function(){
+                        showErrorMessage("Server Communication Error");
+                    }
+                });
+
+                $.ajax({
+                    url: "../../system/register/setAllowRegistry",
+                    data: {"allow": regEnabled},
+                    success: function(data){
+                        if (data.error !== undefined){
+                            showErrorMessage(data.error);
+                        }else{
+                            showSuccessMessage();
+                        }
+                    },
+                    error: function(){
+                        showErrorMessage("Server Communication Error");
+                    }
+                });
+            }
+
+            function showSuccessMessage(){
+                $("#succdialog").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
+            }
+
+            function showErrorMessage(err){
+                $("#errmsg").text(err);
+                $("#errdialog").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
+
+            }
+
+            initUserRegisterInfoList();
+            function initUserRegisterInfoList(){
+                $("#userlist").html("");
+                $.get("../../system/register/listUserEmails", function(data){
+                    if (data.error !== undefined){
+                        alert(data.error)
+                    }else{
+                        data.forEach(userinfo => {
+                            var icon = "green checkmark icon";
+                            if (userinfo[2] == false){
+                                icon = "red remove icon"
+                            }
+                            $("#userlist").append(`<tr>
+                                <td>${userinfo[0]}</td>
+                                <td><a href="mailto:${userinfo[1]}">${userinfo[1]}</a></td>
+                                <td><i class="${icon}"></i></td>
+                            </tr>`);
+                        });
+
+                        if (data.length == 0){
+                            $("#userlist").append(`<tr><td col="3"><i class="remove icon"></i> There is no user register record in the database.</td></tr>`);
+                        }
+                    }
+                })
+            }
+
+            //Remove all unregistered user information
+            function clearUnregisteredUserInfo(){
+                if (confirm("Confirm Removing ALL UNREGISTERED USERS INFORMATION?")){
+                    $.get("../../system/register/cleanUserRegisterInfo", function(data){
+                        initUserRegisterInfoList();
+                    });
+                }
+            }
+        </script>
+    </body>
 </html>

+ 384 - 383
web/SystemAO/users/userList.html

@@ -1,384 +1,385 @@
-<html>
-    <head>
-        <title>User List</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <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> -->
-    </head>
-    <body>
-        <div class="ui container">
-            <div class="ui basic segment">
-                <div class="ui header">
-                    <i class="user icon"></i>
-                        <div class="content">
-                        Manage Users
-                        <div class="sub header">Manage registered users</div>
-                    </div>
-                </div>
-                <button class="ui primary small button" onclick="showNewUserUI();">
-                    <i class="add icon"></i>Create
-                </button>
-                <button id="editbtn" class="ui small disabled button" onclick="showEditUI();">
-                    <i class="edit icon"></i>Edit
-                </button>
-               
-                <button id="editUserButton" class="ui small negative disabled right floated button" onclick="removeUser();">
-                    Remove
-                </button>
-            </div>
-            <div class="ui styled fluid accordion">
-                <div class="title">
-                    <i class="dropdown icon"></i>
-                    Advance Operations
-                </div>
-                <div class="content">
-                    <h3>Import from CSV</h3>
-                    <p>Example Structure of the csv format (one user per row)</p>
-                    <table class="ui mini celled table" style="max-width: 500px;">
-                        <tbody>
-                            <tr>
-                              <td>User Name</td>
-                              <td>Default Password</td>
-                              <td>Default Group</td>
-                            </tr>
-                        </tbody>
-                    </table>
-                    <p>(Please make sure the group exists before import)</p>
-                    <button id="importbtn" class="ui small green button" onclick="importFromCSV();">
-                        <i class="upload icon"></i>Import from CSV
-                    </button>
-                    <button id="importbtn" class="ui small button" onclick="window.open('../users/template.csv');">
-                        Download CSV Template
-                    </button>
-                    <div class="ui divider"></div>
-                    <h3>Multi-Selection Remove</h3>
-                    <p>Select multiple users by their user group</p>
-                    <div class="ui selection dropdown">
-                        <input id="usergroupselector" type="hidden" name="Group">
-                        <i class="dropdown icon"></i>
-                        <div class="default text">Group</div>
-                        <div id="grouplist" class="menu">
-                        </div>
-                    </div>
-                    <button class="ui red button" onclick="deleteUsersInGroup();">Remove All Users in Group</button>
-                    <br><br>
-                    <div class="ui checkbox">
-                        <input id="mustbeonegroup" type="checkbox" name="iff">
-                        <label>only if the user is in exactly one group</label>
-                    </div>
-                </div>
-            </div>
-
-            <table class="ui very basic celled table">
-                <thead>
-                    <tr>
-                        <th>Username</th>
-                        <th>Permission Group</th>
-                        <th>Select</th>
-                    </tr>
-                </thead>
-                <tbody id="userTable" >
-                 
-                </tbody>
-              </table>
-              <br><br>
-        </div>
-        <!-- Import Confirm Dialog-->
-        <div class="ui modal" id="confirmImport">
-            <i class="close icon"></i>
-            <div class="header">
-                Import Users
-            </div>
-            <div class="content">
-                <div class="description">
-                <div class="ui header">Confirm importing the following users?</div>
-                <table class="ui celled table">
-                    <thead>
-                      <tr><th>Username</th>
-                      <th>Default Password</th>
-                      <th>Group</th>
-                    </tr></thead>
-                    <tbody id="userImportPreview">
-                     
-                    </tbody>
-                  </table>
-                </div>
-            </div>
-            <div class="actions">
-                <div class="ui black deny button">
-                Cancel
-                </div>
-                <div class="ui positive right labeled icon button" onclick="confirmImportUsers();">
-                OK
-                <i class="checkmark icon"></i>
-                </div>
-            </div>
-        </div>
-        <!-- Delete Confirm Dialog -->
-        <div class="ui modal" id="deleteConfirmDialog">
-            <i class="close icon"></i>
-            <div class="header" style="color: #eb4034;">
-              <i class="user times icon"></i> Remove Users
-            </div>
-            <div class="content">
-              <div class="description">
-                <div class="ui header">The following users will be removed from the system. This action is <span style="color: #eb4034;">NOT REVERSIBLE</span>.</div>
-                <div id="removePendingUsers" class="ui bulleted list">
-
-                </div>
-              </div>
-            </div>
-            <div class="actions">
-              <div class="ui black deny button">
-                Cancel
-              </div>
-              <div class="ui red right labeled icon button" onclick="confirmRemoval(this);">
-                Confirm Removal
-                <i class="checkmark icon"></i>
-              </div>
-            </div>
-          </div>
-        <!-- Error Display Dialog-->
-        <div class="ui basic modal errormodal">
-            <div class="ui icon header">
-                <i class="red remove icon"></i>
-                Some accounts cannot be created
-            </div>
-            <div class="content">
-                <p class="errormessage"></p>
-            </div>
-            <div class="actions">
-                <div class="ui ok inverted button">
-                    <i class="checkmark icon"></i>
-                    OK
-                </div>
-            </div>
-        </div>
-        <script>
-            //Init UI elements
-            $('.ui.accordion').accordion();
-            $('.ui.dropdown').dropdown();
-
-            //Init group list
-            $.get("../../system/permission/listgroup", function(data){
-                $("#grouplist").html("");
-                if (data.error !== undefined){
-                    console.log(data.error);
-                }else{
-                    data.forEach(group => {
-                        $("#grouplist").append(`<div class="item" data-value="${group}">${group}</div>`);
-                    });
-                }
-            });
-
-
-            //Init User List
-            initUserList();
-            function initUserList(){
-                $("#userTable").html("");
-                $("#editbtn").addClass('disabled');
-                $.ajax({
-                    url: "../../system/users/list",
-                    success: function(data){
-                        for (var i =0; i < data.length; i++){
-                            var username = data[i][0];
-                            var group = data[i][1];
-                            var profilePic = data[i][2];
-                            if (profilePic == ""){
-                                profilePic = "../users/img/noprofileicon.png"
-                            }
-                            var accountStatus = data[i][3];
-                            $("#userTable").append(`<tr>
-                                <td>
-                                    <h4 class="ui image header">
-                                        <img src="${profilePic}" class="ui mini rounded image">
-                                        <div class="content">
-                                        ${username}
-                                        </div>
-                                    </h4>
-                                </td>
-                                <td>
-                                    ${group.join("/")}
-                                </td>
-                                <td>
-                                    <div class="ui checkbox">
-                                        <input type="radio" name="selectUser" value="${username}" self="${data[i][3]}" onchange="enableEdit(this);">
-                                        <label></label>
-                                    </div>
-                                </td>
-                            </tr>`);
-                        }
-                    },
-                    error: function(data){
-                        $("#userTable").html("<p> Failed to load user database. </p>");
-                    }
-                });
-            }
-
-            var importPendingData = "";
-            function importFromCSV(){
-                //Open the native file selector for uploading a csv file
-                ao_module_selectFiles(function(files){
-                    var targetFile = files[0];
-                    console.log(targetFile, files);
-                    //Read the csv file into string
-                    ao_module_utils.readFileFromFileObject(targetFile, function(content){
-                        //Render the preview user list
-                        $("#userImportPreview").html("");
-                        var lines = content.split("\r\n").join("\n").split("\n");
-                        lines.forEach(line => {
-                            var data = line.split(",");
-                            if (line.length < 3){
-                                return;
-                            }
-                            $("#userImportPreview").append(`
-                                <tr>
-                                    <td data-label="Name">${data[0]}</td>
-                                    <td data-label="DP">${data[1]}</td>
-                                    <td data-label="Group">${data[2]}</td>
-                                </tr>`);
-                        });
-                        $("#confirmImport").modal("show");
-                        //Post the content to server side
-                        importPendingData = content;
-                    });
-                }, "file", ".csv", false);
-            }
-
-            var deletePendingGroup = "";
-            function deleteUsersInGroup(){
-                var targetGroup = $("#usergroupselector").val();
-                if (targetGroup == ""){
-                    return;
-                }
-                deletePendingGroup = targetGroup;
-                
-                $.get("../../system/users/list", function(data){
-                    var username = [];
-                    data.forEach(user => {
-                        var userGroup = user[1];
-                        if ($("#mustbeonegroup")[0].checked == true){
-                            if (userGroup.length == 1 && userGroup[0] == (deletePendingGroup)){
-                                username.push(user[0]);
-                            }
-                        }else{
-                            if (userGroup.includes(deletePendingGroup)){
-                                username.push(user[0]);
-                            }
-                        }
-                       
-                    });
-
-                    $("#removePendingUsers").html("");
-                    username.forEach(thisuser => {
-                        $("#removePendingUsers").append(`<div class="item">${thisuser}</div>`);
-                    });
-                });
-
-
-                $("#deleteConfirmDialog").modal("show");
-
-            }
-
-            function confirmRemoval(btn){
-                let group = deletePendingGroup;
-                let removeUsersWithOneGroupOnly = $("#mustbeonegroup")[0].checked;
-                $(btn).addClass("loading");
-                $.ajax({
-                    url :"../../system/auth/groupdel",
-                    data: {group: group, exact: removeUsersWithOneGroupOnly},
-                    success: function(data){
-                        $("#deleteConfirmDialog").modal("hide");
-                        $(btn).removeClass("loading");
-                        initUserList();
-                    }
-                })
-            }
-
-            function confirmImportUsers(){
-                $.ajax({
-                    url: "../../system/auth/csvimport",
-                    data: {csv: importPendingData},
-                    success: function(data){
-                        //The returned data should ba list of errors (if any)
-                        if (data.length == 0){
-                            //Reload the list
-                            initUserList();
-                        }else{
-                            //Display error list
-                            console.log(data);
-                            $(".errormodal").modal("show");
-                            var errmsg = data.join("<br>");
-                            $(".errormessage").html(errmsg); 
-                            initUserList();
-                        }
-                    }
-                });
-            }
-
-            function showNewUserUI(){
-                ao_module_newfw({
-                    url: "user.system",
-                    width: 530,
-                    height: 740,
-                    appicon: "SystemAO/users/img/user-white.svg",
-                    title: "Create New User",
-                    callback: "finishCreateHandler",
-                    parent: ao_module_windowID
-                });
-            }
-
-            function showEditUI(){
-                var username = $("input[name='selectUser']:checked").val();
-                ao_module_newfw({
-                    url:"SystemAO/users/editUser.html#" + encodeURIComponent(username),
-                    width: 530,
-                    height: 740,
-                    appicon: "SystemAO/users/img/user-white.svg",
-                    title: "Edit User",
-                    callback: "finishCreateHandler",
-                    parent: ao_module_windowID
-                });
-            }
-
-            function removeUser(){
-                var username = $("input[name='selectUser']:checked").val();
-                if (confirm("Remove " + username + " from the system PERMANENTLY?")){
-                    $.ajax({
-                        url: "../../system/users/removeUser",
-                        data: {username: username},
-                        method: "POST",
-                        success: function(data){
-                            if (data.error !== undefined){
-                                alert(data.error);
-                            }else{
-                                //Reload the list
-                                initUserList();
-                            }
-                        }
-                    });
-                }
-            }
-
-            function finishCreateHandler(state){
-                if (state){
-                    initUserList();
-                }
-            }
-
-            function enableEdit(object){
-                if ($(object).attr("self") != "true"){
-                    $("#editUserButton").removeClass("disabled");
-                    $("#editbtn").removeClass("disabled");
-                }else{
-                    $("#editUserButton").addClass("disabled");
-                    $("#editbtn").removeClass("disabled");
-                }
-               
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>User List</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <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> -->
+    </head>
+    <body>
+        <div class="ui container">
+            <div class="ui basic segment">
+                <div class="ui header">
+                    <i class="user icon"></i>
+                        <div class="content">
+                        Manage Users
+                        <div class="sub header">Manage registered users</div>
+                    </div>
+                </div>
+                <button class="ui primary small button" onclick="showNewUserUI();">
+                    <i class="add icon"></i>Create
+                </button>
+                <button id="editbtn" class="ui small disabled button" onclick="showEditUI();">
+                    <i class="edit icon"></i>Edit
+                </button>
+               
+                <button id="editUserButton" class="ui small negative disabled right floated button" onclick="removeUser();">
+                    Remove
+                </button>
+            </div>
+            <div class="ui styled fluid accordion">
+                <div class="title">
+                    <i class="dropdown icon"></i>
+                    Advance Operations
+                </div>
+                <div class="content">
+                    <h3>Import from CSV</h3>
+                    <p>Example Structure of the csv format (one user per row)</p>
+                    <table class="ui mini celled table" style="max-width: 500px;">
+                        <tbody>
+                            <tr>
+                              <td>User Name</td>
+                              <td>Default Password</td>
+                              <td>Default Group</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <p>(Please make sure the group exists before import)</p>
+                    <button id="importbtn" class="ui small green button" onclick="importFromCSV();">
+                        <i class="upload icon"></i>Import from CSV
+                    </button>
+                    <button id="importbtn" class="ui small button" onclick="window.open('../users/template.csv');">
+                        Download CSV Template
+                    </button>
+                    <div class="ui divider"></div>
+                    <h3>Multi-Selection Remove</h3>
+                    <p>Select multiple users by their user group</p>
+                    <div class="ui selection dropdown">
+                        <input id="usergroupselector" type="hidden" name="Group">
+                        <i class="dropdown icon"></i>
+                        <div class="default text">Group</div>
+                        <div id="grouplist" class="menu">
+                        </div>
+                    </div>
+                    <button class="ui red button" onclick="deleteUsersInGroup();">Remove All Users in Group</button>
+                    <br><br>
+                    <div class="ui checkbox">
+                        <input id="mustbeonegroup" type="checkbox" name="iff">
+                        <label>only if the user is in exactly one group</label>
+                    </div>
+                </div>
+            </div>
+
+            <table class="ui very basic celled table">
+                <thead>
+                    <tr>
+                        <th>Username</th>
+                        <th>Permission Group</th>
+                        <th>Select</th>
+                    </tr>
+                </thead>
+                <tbody id="userTable" >
+                 
+                </tbody>
+              </table>
+              <br><br>
+        </div>
+        <!-- Import Confirm Dialog-->
+        <div class="ui modal" id="confirmImport">
+            <i class="close icon"></i>
+            <div class="header">
+                Import Users
+            </div>
+            <div class="content">
+                <div class="description">
+                <div class="ui header">Confirm importing the following users?</div>
+                <table class="ui celled table">
+                    <thead>
+                      <tr><th>Username</th>
+                      <th>Default Password</th>
+                      <th>Group</th>
+                    </tr></thead>
+                    <tbody id="userImportPreview">
+                     
+                    </tbody>
+                  </table>
+                </div>
+            </div>
+            <div class="actions">
+                <div class="ui black deny button">
+                Cancel
+                </div>
+                <div class="ui positive right labeled icon button" onclick="confirmImportUsers();">
+                OK
+                <i class="checkmark icon"></i>
+                </div>
+            </div>
+        </div>
+        <!-- Delete Confirm Dialog -->
+        <div class="ui modal" id="deleteConfirmDialog">
+            <i class="close icon"></i>
+            <div class="header" style="color: #eb4034;">
+              <i class="user times icon"></i> Remove Users
+            </div>
+            <div class="content">
+              <div class="description">
+                <div class="ui header">The following users will be removed from the system. This action is <span style="color: #eb4034;">NOT REVERSIBLE</span>.</div>
+                <div id="removePendingUsers" class="ui bulleted list">
+
+                </div>
+              </div>
+            </div>
+            <div class="actions">
+              <div class="ui black deny button">
+                Cancel
+              </div>
+              <div class="ui red right labeled icon button" onclick="confirmRemoval(this);">
+                Confirm Removal
+                <i class="checkmark icon"></i>
+              </div>
+            </div>
+          </div>
+        <!-- Error Display Dialog-->
+        <div class="ui basic modal errormodal">
+            <div class="ui icon header">
+                <i class="red remove icon"></i>
+                Some accounts cannot be created
+            </div>
+            <div class="content">
+                <p class="errormessage"></p>
+            </div>
+            <div class="actions">
+                <div class="ui ok inverted button">
+                    <i class="checkmark icon"></i>
+                    OK
+                </div>
+            </div>
+        </div>
+        <script>
+            //Init UI elements
+            $('.ui.accordion').accordion();
+            $('.ui.dropdown').dropdown();
+
+            //Init group list
+            $.get("../../system/permission/listgroup", function(data){
+                $("#grouplist").html("");
+                if (data.error !== undefined){
+                    console.log(data.error);
+                }else{
+                    data.forEach(group => {
+                        $("#grouplist").append(`<div class="item" data-value="${group}">${group}</div>`);
+                    });
+                }
+            });
+
+
+            //Init User List
+            initUserList();
+            function initUserList(){
+                $("#userTable").html("");
+                $("#editbtn").addClass('disabled');
+                $.ajax({
+                    url: "../../system/users/list",
+                    success: function(data){
+                        for (var i =0; i < data.length; i++){
+                            var username = data[i][0];
+                            var group = data[i][1];
+                            var profilePic = data[i][2];
+                            if (profilePic == ""){
+                                profilePic = "../users/img/noprofileicon.png"
+                            }
+                            var accountStatus = data[i][3];
+                            $("#userTable").append(`<tr>
+                                <td>
+                                    <h4 class="ui image header">
+                                        <img src="${profilePic}" class="ui mini rounded image">
+                                        <div class="content">
+                                        ${username}
+                                        </div>
+                                    </h4>
+                                </td>
+                                <td>
+                                    ${group.join("/")}
+                                </td>
+                                <td>
+                                    <div class="ui checkbox">
+                                        <input type="radio" name="selectUser" value="${username}" self="${data[i][3]}" onchange="enableEdit(this);">
+                                        <label></label>
+                                    </div>
+                                </td>
+                            </tr>`);
+                        }
+                    },
+                    error: function(data){
+                        $("#userTable").html("<p> Failed to load user database. </p>");
+                    }
+                });
+            }
+
+            var importPendingData = "";
+            function importFromCSV(){
+                //Open the native file selector for uploading a csv file
+                ao_module_selectFiles(function(files){
+                    var targetFile = files[0];
+                    console.log(targetFile, files);
+                    //Read the csv file into string
+                    ao_module_utils.readFileFromFileObject(targetFile, function(content){
+                        //Render the preview user list
+                        $("#userImportPreview").html("");
+                        var lines = content.split("\r\n").join("\n").split("\n");
+                        lines.forEach(line => {
+                            var data = line.split(",");
+                            if (line.length < 3){
+                                return;
+                            }
+                            $("#userImportPreview").append(`
+                                <tr>
+                                    <td data-label="Name">${data[0]}</td>
+                                    <td data-label="DP">${data[1]}</td>
+                                    <td data-label="Group">${data[2]}</td>
+                                </tr>`);
+                        });
+                        $("#confirmImport").modal("show");
+                        //Post the content to server side
+                        importPendingData = content;
+                    });
+                }, "file", ".csv", false);
+            }
+
+            var deletePendingGroup = "";
+            function deleteUsersInGroup(){
+                var targetGroup = $("#usergroupselector").val();
+                if (targetGroup == ""){
+                    return;
+                }
+                deletePendingGroup = targetGroup;
+                
+                $.get("../../system/users/list", function(data){
+                    var username = [];
+                    data.forEach(user => {
+                        var userGroup = user[1];
+                        if ($("#mustbeonegroup")[0].checked == true){
+                            if (userGroup.length == 1 && userGroup[0] == (deletePendingGroup)){
+                                username.push(user[0]);
+                            }
+                        }else{
+                            if (userGroup.includes(deletePendingGroup)){
+                                username.push(user[0]);
+                            }
+                        }
+                       
+                    });
+
+                    $("#removePendingUsers").html("");
+                    username.forEach(thisuser => {
+                        $("#removePendingUsers").append(`<div class="item">${thisuser}</div>`);
+                    });
+                });
+
+
+                $("#deleteConfirmDialog").modal("show");
+
+            }
+
+            function confirmRemoval(btn){
+                let group = deletePendingGroup;
+                let removeUsersWithOneGroupOnly = $("#mustbeonegroup")[0].checked;
+                $(btn).addClass("loading");
+                $.ajax({
+                    url :"../../system/auth/groupdel",
+                    data: {group: group, exact: removeUsersWithOneGroupOnly},
+                    success: function(data){
+                        $("#deleteConfirmDialog").modal("hide");
+                        $(btn).removeClass("loading");
+                        initUserList();
+                    }
+                })
+            }
+
+            function confirmImportUsers(){
+                $.ajax({
+                    url: "../../system/auth/csvimport",
+                    data: {csv: importPendingData},
+                    success: function(data){
+                        //The returned data should ba list of errors (if any)
+                        if (data.length == 0){
+                            //Reload the list
+                            initUserList();
+                        }else{
+                            //Display error list
+                            console.log(data);
+                            $(".errormodal").modal("show");
+                            var errmsg = data.join("<br>");
+                            $(".errormessage").html(errmsg); 
+                            initUserList();
+                        }
+                    }
+                });
+            }
+
+            function showNewUserUI(){
+                ao_module_newfw({
+                    url: "user.system",
+                    width: 530,
+                    height: 740,
+                    appicon: "SystemAO/users/img/user-white.svg",
+                    title: "Create New User",
+                    callback: "finishCreateHandler",
+                    parent: ao_module_windowID
+                });
+            }
+
+            function showEditUI(){
+                var username = $("input[name='selectUser']:checked").val();
+                ao_module_newfw({
+                    url:"SystemAO/users/editUser.html#" + encodeURIComponent(username),
+                    width: 530,
+                    height: 740,
+                    appicon: "SystemAO/users/img/user-white.svg",
+                    title: "Edit User",
+                    callback: "finishCreateHandler",
+                    parent: ao_module_windowID
+                });
+            }
+
+            function removeUser(){
+                var username = $("input[name='selectUser']:checked").val();
+                if (confirm("Remove " + username + " from the system PERMANENTLY?")){
+                    $.ajax({
+                        url: "../../system/users/removeUser",
+                        data: {username: username},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error !== undefined){
+                                alert(data.error);
+                            }else{
+                                //Reload the list
+                                initUserList();
+                            }
+                        }
+                    });
+                }
+            }
+
+            function finishCreateHandler(state){
+                if (state){
+                    initUserList();
+                }
+            }
+
+            function enableEdit(object){
+                if ($(object).attr("self") != "true"){
+                    $("#editUserButton").removeClass("disabled");
+                    $("#editbtn").removeClass("disabled");
+                }else{
+                    $("#editUserButton").addClass("disabled");
+                    $("#editbtn").removeClass("disabled");
+                }
+               
+            }
+        </script>
+    </body>
 </html>

+ 25 - 24
web/SystemAO/vendor/index.html

@@ -1,25 +1,26 @@
-<html>
-        <head>
-                <meta charset="UTF-8">
-                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-                <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>
-                <title>arozos Platform</title>
-        </head>
-    <body>
-        <div class="ui container">
-            <br>
-            <img class="ui fluid image" src="../vendor/img/banner.png">
-            <h4>What is this page?</h4>
-            <p>This is a branding page if you are making a devices with ArozOS pre-installed or forking your own version of ArozOS.</p>
-            <div class="ui divider"></div>
-            <h4>What is arozos.com?</h4>
-            <p>arozos.com is the entity for building, testing and releasing the offical ArozOS for different platforms and devices. If your lab is interested to release a customized version of ArozOS, please modify this page to fill your Lab's information.<br>
-            You can find the source of this page under <code>web/SystemAO/vendor/index.html</code></p>
-            <div class="ui divider"></div>
-            System released by <a href="http://arozos.com" target="_blank">http://arozos.com</a>
-            <br><br><br>
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+        <head>
+                <meta charset="UTF-8">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+                <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>
+                <title>arozos Platform</title>
+        </head>
+    <body>
+        <div class="ui container">
+            <br>
+            <img class="ui fluid image" src="../vendor/img/banner.png">
+            <h4>What is this page?</h4>
+            <p>This is a branding page if you are making a devices with ArozOS pre-installed or forking your own version of ArozOS.</p>
+            <div class="ui divider"></div>
+            <h4>What is arozos.com?</h4>
+            <p>arozos.com is the entity for building, testing and releasing the offical ArozOS for different platforms and devices. If your lab is interested to release a customized version of ArozOS, please modify this page to fill your Lab's information.<br>
+            You can find the source of this page under <code>web/SystemAO/vendor/index.html</code></p>
+            <div class="ui divider"></div>
+            System released by <a href="http://arozos.com" target="_blank">http://arozos.com</a>
+            <br><br><br>
+        </div>
+    </body>
 </html>

+ 32 - 31
web/SystemAO/vendor/public/autoLoginForAdmin.html

@@ -1,32 +1,33 @@
-<html>
-        <head>
-                <meta charset="UTF-8">
-                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-                <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>
-                <title>Auto Login for Administrator</title>
-        </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <h2 class="ui header">
-                <i class="info circle icon"></i>
-                <div class="content">
-                    Auto Login Mode for Administrator
-                </div>
-            </h2>
-            <div class="ui divider"></div>
-            <h4>What is Auto Login Mode?</h4>
-            <p>Auto Login mode is an alternative authentication method designed for bots and machines that is designed for automate processes. It is called the "Auto Login Mode" is because of its main purposes is designed for Auto Logins and other simple logic bot programs that launch into Interface Modules.</p>
-            <div class="ui divider"></div>
-            <h4>Why administrator account(s) is not suitable for Auto Login Mode?</h4>
-            <p>Auto Login mode provides a lower security protection than other login methods due to the token generated can be easily misplaced or disclosed by URL parameters and network request headers. If you enable Auto Login mode on your administrator account, hackers might be able to access your system using the Auto Login mode token and damage to the data stored on the system or even cause hardware damage. </p>
-            <h4>What is the correct way to configure Auto Login Mode account?</h4>
-            <p>The correct way to configure Auto Login mode account is to create a <mark>READONLY, NON ADMINISTRATOR</mark>  account without any module permission except the interface module and file system module (if your interface module require reading / writing to the arozos virtual file system). </p>
-            <div class="ui divider"></div>
-            Last Updates: 16 Oct 2020
-            <br><br><br>
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+        <head>
+                <meta charset="UTF-8">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+                <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>
+                <title>Auto Login for Administrator</title>
+        </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <h2 class="ui header">
+                <i class="info circle icon"></i>
+                <div class="content">
+                    Auto Login Mode for Administrator
+                </div>
+            </h2>
+            <div class="ui divider"></div>
+            <h4>What is Auto Login Mode?</h4>
+            <p>Auto Login mode is an alternative authentication method designed for bots and machines that is designed for automate processes. It is called the "Auto Login Mode" is because of its main purposes is designed for Auto Logins and other simple logic bot programs that launch into Interface Modules.</p>
+            <div class="ui divider"></div>
+            <h4>Why administrator account(s) is not suitable for Auto Login Mode?</h4>
+            <p>Auto Login mode provides a lower security protection than other login methods due to the token generated can be easily misplaced or disclosed by URL parameters and network request headers. If you enable Auto Login mode on your administrator account, hackers might be able to access your system using the Auto Login mode token and damage to the data stored on the system or even cause hardware damage. </p>
+            <h4>What is the correct way to configure Auto Login Mode account?</h4>
+            <p>The correct way to configure Auto Login mode account is to create a <mark>READONLY, NON ADMINISTRATOR</mark>  account without any module permission except the interface module and file system module (if your interface module require reading / writing to the arozos virtual file system). </p>
+            <div class="ui divider"></div>
+            Last Updates: 16 Oct 2020
+            <br><br><br>
+        </div>
+    </body>
 </html>

+ 29 - 28
web/SystemAO/vendor/public/ftpForAdmin.html

@@ -1,29 +1,30 @@
-<html>
-        <head>
-                <meta charset="UTF-8">
-                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-                <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>
-                <title>FTP for Administrator</title>
-        </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <h2 class="ui header">
-                <i class="info circle icon"></i>
-                <div class="content">
-                    FTP Access for Administrator
-                </div>
-            </h2>
-            <div class="ui divider"></div>
-            <h4>Why Enabling FTP Access to Admin Account is not a good idea?</h4>
-            <p>Arozos uses the same username and password in all its main account and FTP accounts. In simple words, you can login to your FTP account using the same username and password you used in logging in to the web desktop interface.</p>
-            <p>FTP protocol send your username and password in plain text to server side for validation. <b>This process is unsafe and easily hackable by hackers.</b> </p>
-            <p>To ensure the safety of your account, only enable administrator FTP access within a trusted network environment (e.g. in offline LAN or at home).</p>
-            <div class="ui divider"></div>
-            Last Updates: 1 Nov 2020
-            <br><br><br>
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+        <head>
+                <meta charset="UTF-8">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+                <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>
+                <title>FTP for Administrator</title>
+        </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <h2 class="ui header">
+                <i class="info circle icon"></i>
+                <div class="content">
+                    FTP Access for Administrator
+                </div>
+            </h2>
+            <div class="ui divider"></div>
+            <h4>Why Enabling FTP Access to Admin Account is not a good idea?</h4>
+            <p>Arozos uses the same username and password in all its main account and FTP accounts. In simple words, you can login to your FTP account using the same username and password you used in logging in to the web desktop interface.</p>
+            <p>FTP protocol send your username and password in plain text to server side for validation. <b>This process is unsafe and easily hackable by hackers.</b> </p>
+            <p>To ensure the safety of your account, only enable administrator FTP access within a trusted network environment (e.g. in offline LAN or at home).</p>
+            <div class="ui divider"></div>
+            Last Updates: 1 Nov 2020
+            <br><br><br>
+        </div>
+    </body>
 </html>

+ 223 - 222
web/SystemAO/vendor/public/termsAndConditions.html

@@ -1,223 +1,224 @@
-<html>
-        <head>
-                <meta charset="UTF-8">
-                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-                <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>
-                <title>TERMS AND CONDITIONS</title>
-        </head>
-    <body>
-        <br><br>
-        <div class="ui text container">
-            <h2 class="ui header">
-                <i class="info circle icon"></i>
-                <div class="content">
-                    AROZOS TERMS AND CONDITIONS
-                </div>
-            </h2>
-            <div class="ui divider"></div>
-            <h4>AGREEMENT TO TERMS</h4>
-            <p>
-                    <br>These Terms and Conditions constitute a legally binding agreement made between you, whether personally or on behalf of an entity (“you”) and [business entity name](“we,” “us” or “our”), concerning your access to and use of the AROZOS (or ArOZ Online System) as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the “Platform”). 
-
-                    <br><br>You agree that by accessing the Platform, you have read, understood, and agree to be bound by all of these Terms and Conditions. If you do not agree with all of these Terms and Conditions, then you are expressly prohibited from using the Platform and you must discontinue use immediately.
-                    
-                    <br><br>Supplemental terms and conditions or documents that may be posted on the Platform from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Terms and Conditions at any time and for any reason.
-                    
-                    <br><br>It is your responsibility to periodically review these Terms and Conditions to stay informed of updates.You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Terms and Conditions by your continued use of the Platform after the date such revised Terms and Conditions are posted.
-                    
-                    <br><br> The information provided on the Platform is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. 
-                    
-                    <br><br>Accordingly, those persons who choose to access the Platform from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable. 
-            </p>
-            <div class="ui divider"></div>
-            <h4>INTELLECTUAL PROPERTY RIGHTS</h4>
-            <p>Unless otherwise indicated, the Platform is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Platform (collectively, the “Content”) and the trademarks, service marks, and logos contained therein (the “Marks”) are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of the United States, foreign jurisdictions, and international conventions.
-
-                <br><br>The Content and the Marks are provided on the Platform “AS IS” for your information and personal use only.Except as expressly provided in these Terms and Conditions, no part of the Platform and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.
-                
-                <br><br> Provided that you are eligible to use the Platform, you are granted a limited license to access and use the Platform and to download or print a copy of any portion of the Content to which you have properly gained access solely for your personal, non-commercial use. We reserve all rights not expressly granted to you in and to the Platform, the Content and the Marks.
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>USER REPRESENTATIONS</h4>
-            <p>
-                    By using the Platform, you represent and warrant that: 
-
-                <div class="ui ordered list">
-                    <div class="item">All registration information you submit will be true, accurate, current, and complete;</div>
-                    <div class="item">You will maintain the accuracy of such information and promptly update such registration information as necessary</div>
-                    <div class="item">You have the legal capacity and you agree to comply with these Terms and Conditions; </div>
-                    <div class="item">You will not access the Platform through automated or non-human means, whether through a bot, script, or otherwise; </div>
-                    <div class="item">You will not use the Platform for any illegal or unauthorized purpose;</div>
-                    <div class="item">Your use of the Platform will not violate any applicable law or regulation.</div>
-                </div>
-
-                If you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Platform (or any portion thereof).
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>USER REGISTRATION</h4>
-            <p>
-                    You may be required to register with the Platform. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>PROHIBITED ACTIVITIES</h4>
-            <p>
-                    You may not access or use the Platform for any purpose other than that for which we make the Platform available. The Platform may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us. 
-                    <br>As a user of the Platform, you agree not to:
-                    <div class="ui ordered list">
-                        <div class="item"> Systematically retrieve data or other content from the Platform to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us</div>
-                        <div class="item"> Make any unauthorized use of the Platform, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses.</div>
-                        <div class="item"> Use the Platform to advertise or offer to sell goods and services.</div>
-                        <div class="item"> Circumvent, disable, or otherwise interfere with security-related features of the Platform, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Platform and/or the Content contained therein.</div>
-                        <div class="item"> Engage in unauthorized framing of or linking to the Platform.</div>
-                        <div class="item"> Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords;</div>
-                        <div class="item"> Make improper use of our support services or submit false reports of abuse or misconduct.</div>
-                        <div class="item"> Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools.</div>
-                        <div class="item"> Interfere with, disrupt, or create an undue burden on the Platform or the networks or services connected to the Platform.</div>
-                        <div class="item"> Attempt to impersonate another user or person or use the username of another user.</div>
-                        <div class="item"> Use any information obtained from the Platform in order to harass, abuse, or harm another person.</div>
-                        <div class="item"> Use the Platform as part of any effort to compete with us or otherwise use the Platform and/or the Content for any revenue-generating endeavor or commercial enterprise.</div>
-                        <div class="item"> Decipher, decompile, disassemble, or reverse engineer any of the software comprising or in any way making up a part of the Platform.</div>
-                        <div class="item"> Attempt to bypass any measures of the Platform designed to prevent or restrict access to the Platform, or any portion of the Platform.</div>
-                        <div class="item"> Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Platform to you.</div>
-                        <div class="item"> Delete the copyright or other proprietary rights notice from any Content.</div>
-                        <div class="item"> Copy or adapt the Platform’s software, including but not limited to Flash, PHP, HTML, JavaScript, or other code.</div>
-                        <div class="item"> Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Platform or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Platform.</div>
-                        <div class="item"> Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats (“gifs”), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as “spyware” or “passive collection mechanisms” or “pcms”).</div>
-                        <div class="item"> Except as may be the result of standard search engine or Internet browser usage, use, launch, develop, or distribute any automated system, including without limitation, any spider, robot, cheat utility, scraper, or offline reader that accesses the Platform, or using or launching any unauthorized script or other software</div>
-                        <div class="item"> Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Platform.</div>
-                        <div class="item"> Use the Platform in a manner inconsistent with any applicable laws or regulations.</div>
-                    </div>
-
-            </p>
-            <div class="ui divider"></div>
-            <h4>CONTRIBUTION LICENSE</h4>
-            <p>
-                    By posting your Contributions to any part of the Platform [or making Contributions accessible to the Platform by linking your account from the Platform to any of your social networking accounts], you automatically grant, and you represent and warrant that you have the right to grant, to us an unrestricted, unlimited, irrevocable, perpetual, non-exclusive, transferable, royalty-free, fully-paid, worldwide right, and license to host, use, copy, reproduce, disclose, sell, resell, publish, broadcast, retitle, archive, store, cache, publicly perform, publicly display, reformat, translate, transmit, excerpt (in whole or in part), and distribute such Contributions (including, without limitation, your image and voice) for any purpose, commercial, advertising, or otherwise, and to prepare derivative works of, or incorporate into other works, such Contributions, and grant and authorize sublicenses of the foregoing. The use and distribution may occur in any media formats and through any media channels. 
-
-                    <br><br>This license will apply to any form, media, or technology now known or hereafter developed, and includes our use of your name, company name, and franchise name, as applicable, and any of the trademarks, service marks, trade names, logos, and personal and commercial images you provide.You waive all moral rights in your Contributions, and you warrant that moral rights have not otherwise been asserted in your Contributions. 
-                    
-                    <br><br>We do not assert any ownership over your Contributions.You retain full ownership of all of your Contributions and any intellectual property rights or other proprietary rights associated with your Contributions.We are not liable for any statements or representations in your Contributions provided by you in any area on the Platform.
-                    
-                    <br><br>You are solely responsible for your Contributions to the Platform and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.
-                    
-                    <br><br>We have the right, in our sole and absolute discretion, (1) to edit, redact, or otherwise change any Contributions; (2) to re-categorize any Contributions to place them in more appropriate locations on the Platform; and (3) to pre-screen or delete any Contributions at any time and for any reason, without notice. We have no obligation to monitor your Contributions.
-                    
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>SOCIAL MEDIA</h4>
-            <p>
-                    As part of the functionality of the Platform, you may link your account with online accounts you have with third-party service providers (each such account, a “Third-Party Account”) by either: (1) providing your Third-Party Account login information through the Platform; or (2) allowing us to access your Third-Party Account, as is permitted under the applicable terms and conditions that govern your use of each Third-Party Account. 
-
-                    <br><br>You represent and warrant that you are entitled to disclose your Third-Party Account login information to us and/or grant us access to your Third-Party Account, without breach by you of any of the terms and conditions that govern your use of the applicable Third-Party Account, and without obligating us to pay any fees or making us subject to any usage limitations imposed by the third-party service provider of the Third-Party Account.
-                    
-                    <br><br>By granting us access to any Third-Party Accounts, you understand that (1) we may access, make available, and store (if applicable) any content that you have provided to and stored in your Third-Party Account (the “Social Network Content”) so that it is available on and through the Platform via your account, including without limitation any friend lists and (2) we may submit to and receive from your Third-Party Account additional information to the extent you are notified when you link your account with the Third-Party Account.
-                    
-                    <br><br>Depending on the Third-Party Accounts you choose and subject to the privacy settings that you have set in such Third-Party Accounts, personally identifiable information that you post to your Third-Party Accounts may be available on and through your account on the Platform. 
-                    
-                    <br><br>Please note that if a Third-Party Account or associated service becomes unavailable or our access to such Third-Party Account is terminated by the third-party service provider, then Social Network Content may no longer be available on and through the Platform. You will have the ability to disable the connection between your account on the Platform and your Third-Party Accounts at any time. 
-                    
-                    <br><br>PLEASE NOTE THAT YOUR RELATIONSHIP WITH THE THIRD-PARTY SERVICE PROVIDERS ASSOCIATED WITH YOUR THIRD-PARTY ACCOUNTS IS GOVERNED SOLELY BY YOUR AGREEMENT(S) WITH SUCH THIRD-PARTY SERVICE PROVIDERS.
-                    <br><br>We make no effort to review any Social Network Content for any purpose, including but not limited to, for accuracy, legality, or non-infringement, and we are not responsible for any Social Network Content. 
-                    
-                    <br><br>You acknowledge and agree that we may access your email address book associated with a Third-Party Account and your contacts list stored on your mobile device or tablet computer solely for purposes of identifying and informing you of those contacts who have also registered to use the Platform. 
-                    
-                    <br><br>You can deactivate the connection between the Platform and your Third-Party Accountby contacting us using the contact information below or through your account settings (if applicable). We will attempt to delete any information stored on our servers that was obtained through such Third-Party Account, except the username and profile picture that become associated with your account.
-                    
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>THIRD-PARTY WEBSITES AND CONTENT</h4>
-            <p>
-                    The Platform may contain (or you may be sent via the Platform) links to other websites ("Third-Party Websites") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties ("Third-Party Content"). 
-
-                    <br><br>Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third-Party Websites accessed through the Platform or any Third-Party Content posted on, available through, or installed from the Platform, including the content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content.
-                    
-                    <br><br>Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement thereof by us.If you decide to leave the Platform and access the Third-Party Websites or to use or install any Third-Party Content, you do so at your own risk, and you should be aware these Terms and Conditions no longer govern. 
-                    
-                    <br><br>You should review the applicable terms and policies, including privacy and data gathering practices, of any website to which you navigate from the Platform or relating to any applications you use or install from the Platform. Any purchases you make through Third-Party Websites will be through other websites and from other companies, and we take no responsibility whatsoever in relation to such purchases which are exclusively between you and the applicable third party.
-                    
-                    <br><br>You agree and acknowledge that we do not endorse the products or services offered on Third-Party Websites and you shall hold us harmless from any harm caused by your purchase of such products or services.Additionally, you shall hold us harmless from any losses sustained by you or harm caused to you relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites. 
-                    
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>ADVERTISERS</h4>
-            <p>
-                    We allow advertisers to display their advertisements and other information in certain areas of the Platform, such as sidebar advertisements or banner advertisements.If you are an advertiser, you shall take full responsibility for any advertisements you place on the Platform and any services provided on the Platform or products sold through those advertisements.
-
-                    <br><br>Further, as an advertiser, you warrant and represent that you possess all rights and authority to place advertisements on the Platform, including, but not limited to, intellectual property rights, publicity rights, and contractual rights.
-                    
-                    <br><br>[As an advertiser, you agree that such advertisements are subject to our Digital Millennium Copyright Act (“DMCA”) Notice and Policy provisions as described below, and you understand and agree there will be no refund or other compensation for DMCA takedown-related issues.]We simply provide the space to place such advertisements, and we have no other relationship with advertisers.
-                     
-                    
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>TERM AND TERMINATION</h4>
-            <p>
-                    These Terms and Conditions shall remain in full force and effect while you use the Platform. WITHOUT LIMITING ANY OTHER PROVISION OF THESE TERMS AND CONDITIONS, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE Platform (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE TERMS AND CONDITIONS OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE Platform OR DELETE [YOUR ACCOUNT AND] ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.
-
-                    <br><br>If we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. 
-                    
-                    <br><br>In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.
-                    
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>CORRECTIONS</h4>
-            <p>
-                    There may be information on the Platform that contains typographical errors, inaccuracies, or omissions that may relate to the Platform, including descriptions, pricing, availability, and various other information.We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Platform at any time, without prior notice. 
-            </p>
-
-            <div class="ui divider"></div>
-            <h4>DISCLAIMER</h4>
-            <p>
-                    THE Platform IS PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS.YOU AGREE THAT YOUR USE OF THE Platform AND OUR SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE Platform AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE Platform’S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THE Platform AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE Platform, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE Platform, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE Platform BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE Platform. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE Platform, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES.
-
-                    <br><br>AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.
-                    
-            </p>
-
-
-            <div class="ui divider"></div>
-            <h4>USER DATA</h4>
-            <p>
-                    We will maintain certain data that you transmit to the Platform for the purpose of managing the Platform, as well as data relating to your use of the Platform.Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Platform.
-
-                    <br><br>You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.
-                    
-            </p>
-            <div class="ui divider"></div>
-            <h4>MISCELLANEOUS</h4>
-            <p>
-                    These Terms and Conditions and any policies or operating rules posted by us on the Platform constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms and Conditions shall not operate as a waiver of such right or provision.
-
-                    <br><br>These Terms and Conditions operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time.We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control.
-                    
-                    <br><br>If any provision or part of a provision of these Terms and Conditions is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms and Conditions and does not affect the validity and enforceability of any remaining provisions. 
-                    
-                    <br><br>There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms and Conditions or use of the Platform.You agree that these Terms and Conditions will not be construed against us by virtue of having drafted them. 
-                    
-                    <br><br>You hereby waive any and all defenses you may have based on the electronic form of these Terms and Conditions and the lack of signing by the parties hereto to execute these Terms and Conditions.
-                    
-            </p>
-            <div class="ui divider"></div>
-            <h4>CONTACT US</h4>
-            <p>
-                    In order to resolve a complaint regarding the Platform or to receive further information regarding use of the Platform, please contact us at:
-
-                    <br><br><a href="http://arozos.com">http://arozos.com</a>
-            </p>
-
-            <div class="ui divider"></div>
-            Last Updates: 4 Oct 2020
-            <br><br><br>
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+        <head>
+                <meta charset="UTF-8">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+                <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>
+                <title>TERMS AND CONDITIONS</title>
+        </head>
+    <body>
+        <br><br>
+        <div class="ui text container">
+            <h2 class="ui header">
+                <i class="info circle icon"></i>
+                <div class="content">
+                    AROZOS TERMS AND CONDITIONS
+                </div>
+            </h2>
+            <div class="ui divider"></div>
+            <h4>AGREEMENT TO TERMS</h4>
+            <p>
+                    <br>These Terms and Conditions constitute a legally binding agreement made between you, whether personally or on behalf of an entity (“you”) and [business entity name](“we,” “us” or “our”), concerning your access to and use of the AROZOS (or ArOZ Online System) as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the “Platform”). 
+
+                    <br><br>You agree that by accessing the Platform, you have read, understood, and agree to be bound by all of these Terms and Conditions. If you do not agree with all of these Terms and Conditions, then you are expressly prohibited from using the Platform and you must discontinue use immediately.
+                    
+                    <br><br>Supplemental terms and conditions or documents that may be posted on the Platform from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Terms and Conditions at any time and for any reason.
+                    
+                    <br><br>It is your responsibility to periodically review these Terms and Conditions to stay informed of updates.You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Terms and Conditions by your continued use of the Platform after the date such revised Terms and Conditions are posted.
+                    
+                    <br><br> The information provided on the Platform is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. 
+                    
+                    <br><br>Accordingly, those persons who choose to access the Platform from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable. 
+            </p>
+            <div class="ui divider"></div>
+            <h4>INTELLECTUAL PROPERTY RIGHTS</h4>
+            <p>Unless otherwise indicated, the Platform is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Platform (collectively, the “Content”) and the trademarks, service marks, and logos contained therein (the “Marks”) are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of the United States, foreign jurisdictions, and international conventions.
+
+                <br><br>The Content and the Marks are provided on the Platform “AS IS” for your information and personal use only.Except as expressly provided in these Terms and Conditions, no part of the Platform and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.
+                
+                <br><br> Provided that you are eligible to use the Platform, you are granted a limited license to access and use the Platform and to download or print a copy of any portion of the Content to which you have properly gained access solely for your personal, non-commercial use. We reserve all rights not expressly granted to you in and to the Platform, the Content and the Marks.
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>USER REPRESENTATIONS</h4>
+            <p>
+                    By using the Platform, you represent and warrant that: 
+
+                <div class="ui ordered list">
+                    <div class="item">All registration information you submit will be true, accurate, current, and complete;</div>
+                    <div class="item">You will maintain the accuracy of such information and promptly update such registration information as necessary</div>
+                    <div class="item">You have the legal capacity and you agree to comply with these Terms and Conditions; </div>
+                    <div class="item">You will not access the Platform through automated or non-human means, whether through a bot, script, or otherwise; </div>
+                    <div class="item">You will not use the Platform for any illegal or unauthorized purpose;</div>
+                    <div class="item">Your use of the Platform will not violate any applicable law or regulation.</div>
+                </div>
+
+                If you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Platform (or any portion thereof).
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>USER REGISTRATION</h4>
+            <p>
+                    You may be required to register with the Platform. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>PROHIBITED ACTIVITIES</h4>
+            <p>
+                    You may not access or use the Platform for any purpose other than that for which we make the Platform available. The Platform may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us. 
+                    <br>As a user of the Platform, you agree not to:
+                    <div class="ui ordered list">
+                        <div class="item"> Systematically retrieve data or other content from the Platform to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us</div>
+                        <div class="item"> Make any unauthorized use of the Platform, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses.</div>
+                        <div class="item"> Use the Platform to advertise or offer to sell goods and services.</div>
+                        <div class="item"> Circumvent, disable, or otherwise interfere with security-related features of the Platform, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Platform and/or the Content contained therein.</div>
+                        <div class="item"> Engage in unauthorized framing of or linking to the Platform.</div>
+                        <div class="item"> Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords;</div>
+                        <div class="item"> Make improper use of our support services or submit false reports of abuse or misconduct.</div>
+                        <div class="item"> Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools.</div>
+                        <div class="item"> Interfere with, disrupt, or create an undue burden on the Platform or the networks or services connected to the Platform.</div>
+                        <div class="item"> Attempt to impersonate another user or person or use the username of another user.</div>
+                        <div class="item"> Use any information obtained from the Platform in order to harass, abuse, or harm another person.</div>
+                        <div class="item"> Use the Platform as part of any effort to compete with us or otherwise use the Platform and/or the Content for any revenue-generating endeavor or commercial enterprise.</div>
+                        <div class="item"> Decipher, decompile, disassemble, or reverse engineer any of the software comprising or in any way making up a part of the Platform.</div>
+                        <div class="item"> Attempt to bypass any measures of the Platform designed to prevent or restrict access to the Platform, or any portion of the Platform.</div>
+                        <div class="item"> Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Platform to you.</div>
+                        <div class="item"> Delete the copyright or other proprietary rights notice from any Content.</div>
+                        <div class="item"> Copy or adapt the Platform’s software, including but not limited to Flash, PHP, HTML, JavaScript, or other code.</div>
+                        <div class="item"> Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Platform or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Platform.</div>
+                        <div class="item"> Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats (“gifs”), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as “spyware” or “passive collection mechanisms” or “pcms”).</div>
+                        <div class="item"> Except as may be the result of standard search engine or Internet browser usage, use, launch, develop, or distribute any automated system, including without limitation, any spider, robot, cheat utility, scraper, or offline reader that accesses the Platform, or using or launching any unauthorized script or other software</div>
+                        <div class="item"> Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Platform.</div>
+                        <div class="item"> Use the Platform in a manner inconsistent with any applicable laws or regulations.</div>
+                    </div>
+
+            </p>
+            <div class="ui divider"></div>
+            <h4>CONTRIBUTION LICENSE</h4>
+            <p>
+                    By posting your Contributions to any part of the Platform [or making Contributions accessible to the Platform by linking your account from the Platform to any of your social networking accounts], you automatically grant, and you represent and warrant that you have the right to grant, to us an unrestricted, unlimited, irrevocable, perpetual, non-exclusive, transferable, royalty-free, fully-paid, worldwide right, and license to host, use, copy, reproduce, disclose, sell, resell, publish, broadcast, retitle, archive, store, cache, publicly perform, publicly display, reformat, translate, transmit, excerpt (in whole or in part), and distribute such Contributions (including, without limitation, your image and voice) for any purpose, commercial, advertising, or otherwise, and to prepare derivative works of, or incorporate into other works, such Contributions, and grant and authorize sublicenses of the foregoing. The use and distribution may occur in any media formats and through any media channels. 
+
+                    <br><br>This license will apply to any form, media, or technology now known or hereafter developed, and includes our use of your name, company name, and franchise name, as applicable, and any of the trademarks, service marks, trade names, logos, and personal and commercial images you provide.You waive all moral rights in your Contributions, and you warrant that moral rights have not otherwise been asserted in your Contributions. 
+                    
+                    <br><br>We do not assert any ownership over your Contributions.You retain full ownership of all of your Contributions and any intellectual property rights or other proprietary rights associated with your Contributions.We are not liable for any statements or representations in your Contributions provided by you in any area on the Platform.
+                    
+                    <br><br>You are solely responsible for your Contributions to the Platform and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.
+                    
+                    <br><br>We have the right, in our sole and absolute discretion, (1) to edit, redact, or otherwise change any Contributions; (2) to re-categorize any Contributions to place them in more appropriate locations on the Platform; and (3) to pre-screen or delete any Contributions at any time and for any reason, without notice. We have no obligation to monitor your Contributions.
+                    
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>SOCIAL MEDIA</h4>
+            <p>
+                    As part of the functionality of the Platform, you may link your account with online accounts you have with third-party service providers (each such account, a “Third-Party Account”) by either: (1) providing your Third-Party Account login information through the Platform; or (2) allowing us to access your Third-Party Account, as is permitted under the applicable terms and conditions that govern your use of each Third-Party Account. 
+
+                    <br><br>You represent and warrant that you are entitled to disclose your Third-Party Account login information to us and/or grant us access to your Third-Party Account, without breach by you of any of the terms and conditions that govern your use of the applicable Third-Party Account, and without obligating us to pay any fees or making us subject to any usage limitations imposed by the third-party service provider of the Third-Party Account.
+                    
+                    <br><br>By granting us access to any Third-Party Accounts, you understand that (1) we may access, make available, and store (if applicable) any content that you have provided to and stored in your Third-Party Account (the “Social Network Content”) so that it is available on and through the Platform via your account, including without limitation any friend lists and (2) we may submit to and receive from your Third-Party Account additional information to the extent you are notified when you link your account with the Third-Party Account.
+                    
+                    <br><br>Depending on the Third-Party Accounts you choose and subject to the privacy settings that you have set in such Third-Party Accounts, personally identifiable information that you post to your Third-Party Accounts may be available on and through your account on the Platform. 
+                    
+                    <br><br>Please note that if a Third-Party Account or associated service becomes unavailable or our access to such Third-Party Account is terminated by the third-party service provider, then Social Network Content may no longer be available on and through the Platform. You will have the ability to disable the connection between your account on the Platform and your Third-Party Accounts at any time. 
+                    
+                    <br><br>PLEASE NOTE THAT YOUR RELATIONSHIP WITH THE THIRD-PARTY SERVICE PROVIDERS ASSOCIATED WITH YOUR THIRD-PARTY ACCOUNTS IS GOVERNED SOLELY BY YOUR AGREEMENT(S) WITH SUCH THIRD-PARTY SERVICE PROVIDERS.
+                    <br><br>We make no effort to review any Social Network Content for any purpose, including but not limited to, for accuracy, legality, or non-infringement, and we are not responsible for any Social Network Content. 
+                    
+                    <br><br>You acknowledge and agree that we may access your email address book associated with a Third-Party Account and your contacts list stored on your mobile device or tablet computer solely for purposes of identifying and informing you of those contacts who have also registered to use the Platform. 
+                    
+                    <br><br>You can deactivate the connection between the Platform and your Third-Party Accountby contacting us using the contact information below or through your account settings (if applicable). We will attempt to delete any information stored on our servers that was obtained through such Third-Party Account, except the username and profile picture that become associated with your account.
+                    
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>THIRD-PARTY WEBSITES AND CONTENT</h4>
+            <p>
+                    The Platform may contain (or you may be sent via the Platform) links to other websites ("Third-Party Websites") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties ("Third-Party Content"). 
+
+                    <br><br>Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third-Party Websites accessed through the Platform or any Third-Party Content posted on, available through, or installed from the Platform, including the content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content.
+                    
+                    <br><br>Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement thereof by us.If you decide to leave the Platform and access the Third-Party Websites or to use or install any Third-Party Content, you do so at your own risk, and you should be aware these Terms and Conditions no longer govern. 
+                    
+                    <br><br>You should review the applicable terms and policies, including privacy and data gathering practices, of any website to which you navigate from the Platform or relating to any applications you use or install from the Platform. Any purchases you make through Third-Party Websites will be through other websites and from other companies, and we take no responsibility whatsoever in relation to such purchases which are exclusively between you and the applicable third party.
+                    
+                    <br><br>You agree and acknowledge that we do not endorse the products or services offered on Third-Party Websites and you shall hold us harmless from any harm caused by your purchase of such products or services.Additionally, you shall hold us harmless from any losses sustained by you or harm caused to you relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites. 
+                    
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>ADVERTISERS</h4>
+            <p>
+                    We allow advertisers to display their advertisements and other information in certain areas of the Platform, such as sidebar advertisements or banner advertisements.If you are an advertiser, you shall take full responsibility for any advertisements you place on the Platform and any services provided on the Platform or products sold through those advertisements.
+
+                    <br><br>Further, as an advertiser, you warrant and represent that you possess all rights and authority to place advertisements on the Platform, including, but not limited to, intellectual property rights, publicity rights, and contractual rights.
+                    
+                    <br><br>[As an advertiser, you agree that such advertisements are subject to our Digital Millennium Copyright Act (“DMCA”) Notice and Policy provisions as described below, and you understand and agree there will be no refund or other compensation for DMCA takedown-related issues.]We simply provide the space to place such advertisements, and we have no other relationship with advertisers.
+                     
+                    
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>TERM AND TERMINATION</h4>
+            <p>
+                    These Terms and Conditions shall remain in full force and effect while you use the Platform. WITHOUT LIMITING ANY OTHER PROVISION OF THESE TERMS AND CONDITIONS, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE Platform (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE TERMS AND CONDITIONS OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE Platform OR DELETE [YOUR ACCOUNT AND] ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.
+
+                    <br><br>If we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. 
+                    
+                    <br><br>In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.
+                    
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>CORRECTIONS</h4>
+            <p>
+                    There may be information on the Platform that contains typographical errors, inaccuracies, or omissions that may relate to the Platform, including descriptions, pricing, availability, and various other information.We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Platform at any time, without prior notice. 
+            </p>
+
+            <div class="ui divider"></div>
+            <h4>DISCLAIMER</h4>
+            <p>
+                    THE Platform IS PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS.YOU AGREE THAT YOUR USE OF THE Platform AND OUR SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE Platform AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE Platform’S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THE Platform AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE Platform, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE Platform, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE Platform BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE Platform. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE Platform, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES.
+
+                    <br><br>AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.
+                    
+            </p>
+
+
+            <div class="ui divider"></div>
+            <h4>USER DATA</h4>
+            <p>
+                    We will maintain certain data that you transmit to the Platform for the purpose of managing the Platform, as well as data relating to your use of the Platform.Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Platform.
+
+                    <br><br>You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.
+                    
+            </p>
+            <div class="ui divider"></div>
+            <h4>MISCELLANEOUS</h4>
+            <p>
+                    These Terms and Conditions and any policies or operating rules posted by us on the Platform constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms and Conditions shall not operate as a waiver of such right or provision.
+
+                    <br><br>These Terms and Conditions operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time.We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control.
+                    
+                    <br><br>If any provision or part of a provision of these Terms and Conditions is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms and Conditions and does not affect the validity and enforceability of any remaining provisions. 
+                    
+                    <br><br>There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms and Conditions or use of the Platform.You agree that these Terms and Conditions will not be construed against us by virtue of having drafted them. 
+                    
+                    <br><br>You hereby waive any and all defenses you may have based on the electronic form of these Terms and Conditions and the lack of signing by the parties hereto to execute these Terms and Conditions.
+                    
+            </p>
+            <div class="ui divider"></div>
+            <h4>CONTACT US</h4>
+            <p>
+                    In order to resolve a complaint regarding the Platform or to receive further information regarding use of the Platform, please contact us at:
+
+                    <br><br><a href="http://arozos.com">http://arozos.com</a>
+            </p>
+
+            <div class="ui divider"></div>
+            Last Updates: 4 Oct 2020
+            <br><br><br>
+        </div>
+    </body>
 </html>

+ 214 - 213
web/Timer/timer.html

@@ -1,214 +1,215 @@
-<html>
-    <head>
-        <title>Timer</title>
-        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
-        <script src="../script/jquery.min.js"></script>
-        <script src="../script/ao_module.js"></script>
-        <style>
-            body{
-                padding-top:40px;
-                padding-bottom:50px;
-                background-color:rgba(242, 242, 242, 0.85);
-                backdrop-filter: blur(4px) !important;
-                overflow: hidden;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="ui container" style="position:absolute;left:0;right:0;width:100%;" align="center">
-            <div class="ui grid" style="margin-top: -30px;">
-                <div class="five wide column">
-                    <div class="ui statistic">
-                        <div id="hour" class="value">00</div>
-                        <div class="label">Hours</div>
-                    </div><br>
-                    <div class="ui icon tiny buttons">
-                        <button class="ui button" onClick="adjustValue('hour',-1);"><i class="minus icon"></i></button>
-                        <button class="ui button" onClick="resetTimer('hour');"><i class="undo icon"></i></button>
-                        <button class="ui button"onClick="adjustValue('hour',1);"><i class="plus icon"></i></button>
-                    </div>
-                </div>
-                <div class="five wide column">
-                    <div class="ui statistic">
-                        <div id="min" class="value">00</div>
-                        <div class="label">Minutes</div>
-                    </div><br>
-                     <div class="ui icon tiny buttons">
-                        <button class="ui button" onClick="adjustValue('min',-1);"><i class="minus icon"></i></button>
-                        <button class="ui button" onClick="resetTimer('min');"><i class="undo icon"></i></button>
-                        <button class="ui button"onClick="adjustValue('min',1);"><i class="plus icon"></i></button>
-                    </div>
-                </div>
-                <div class="five wide column">
-                    <div class="ui statistic">
-                        <div id="sec" class="value">00</div>
-                        <div class="label">Seconds</div>
-                    </div><br>
-                     <div class="ui icon tiny buttons">
-                        <button class="ui button" onClick="adjustValue('sec',-1);"><i class="minus icon"></i></button>
-                        <button class="ui button" onClick="resetTimer('sec');"><i class="undo icon"></i></button>
-                        <button class="ui button"onClick="adjustValue('sec',1);"><i class="plus icon"></i></button>
-                    </div>
-                </div>
-                <div class="one wide column">
-                    <button id="startbtn" class="ui positive tiny icon button" onClick="startCountDown();" style="position:fixed;top:3px;right:3px;">
-                        <i class="play icon"></i>
-                    </button>
-                    <button id="pausebtn" class="ui basic tiny icon button disabled" onClick="pauseCountDown();" style="position:fixed;top:40px;right:3px;">
-                        <i class="pause icon"></i>
-                    </button>
-                    <button id="stopalarm" class="ui negative tiny icon button disabled" onClick="stopAlarm();" style="position:fixed;top:80px;right:3px;">
-                        <i class="alarm mute icon"></i>
-                    </button>
-                </div>
-            </div>
-        </div>
-        <script>
-            //Init the timer window
-            ao_module_setFixedWindowSize();
-            ao_module_setWindowSize(380,190);
-            ao_module_setWindowTitle("Countdown Timer - Ready");
-            if (ao_module_windowID == false){
-                $("body").append("<div style='position:fixed;bottom:10px;left:10px;'>[Warning] Seems you are not opening this module under virtual desktop mode. Some functions might be limited or not fully supported.</div>");
-            }
-            //Global variable
-            var countingDown = false;
-            var counter;
-            setInterval(timerTick,1000);
-            var alarmStartTime = 0;
-            var volIncreaseInterval;
-            var audio;
-             
-            //Handle button pressed
-            function adjustValue(target,offset){
-                if ($("#" + target).length == 0){
-                    return;
-                }
-                var currentSec = parseInt($("#" + target).text());
-                if (target == "sec" || target == "min"){
-                    if (currentSec == 0 && offset < 0){
-                        currentSec = 60;
-                    }else if (currentSec == 59 && offset > 0){
-                        currentSec = -1;
-                    }
-                    currentSec += offset;
-                    updateCounterValue(target,currentSec);
-                }else{
-                    //This logic loop controls the hour counter
-                    if (currentSec == 0 && offset < 0){
-                        currentSec = 24;
-                    }else if (currentSec == 23 && offset > 0){
-                        currentSec = -1;
-                    }
-                    currentSec += offset;
-                    updateCounterValue(target,currentSec);
-                }
-                
-            }
-            
-            function resetTimer(target){
-                $("#" + target).text("00");
-            }
-            
-            function updateCounterValue(target,num){
-                
-                $("#" + target).text(fillZero(num));
-            }
-            
-            function fillZero(value){
-                if (value < 10){
-                    return "0" + value;
-                }else{
-                    return value + "";
-                }
-            }
-            
-            function startCountDown(){
-                countingDown = true;
-                ao_module_setWindowTitle("Countdown Timer - Counting");
-            }
-            
-            function pauseCountDown(){
-                 countingDown = false;
-                 ao_module_setWindowTitle("Countdown Timer - Ready");
-            }
-            
-            function resetAllTimer(){
-                updateCounterValue("sec",0);
-                updateCounterValue("min",0);
-                updateCounterValue("hour",0);
-            }
-            
-            //Looping functions for timer ticks
-            function timerTick(){
-                //This function should be running every 1 second
-                if (countingDown == true){
-                    $("#startbtn").addClass("disabled");
-                    $("#pausebtn").removeClass("disabled");
-                    var sec = parseInt($("#sec").text());
-                    var min = parseInt($("#min").text());
-                    var hour = parseInt($("#hour").text());
-                    //console.log("Coutning down");
-                    if (sec > 0){
-                        sec = sec - 1;
-                    }else if (sec == 0){
-                        sec = 59;
-                        if (min > 0){
-                            min = min -1;
-                        }else if (min == 0){
-                            min = 59;
-                            if (hour > 0){
-                                hour = hour -1;
-                            }else if (hour == 0){
-                                countingDown = false;
-                                //parent.msgbox("Timeout!","Timer Notification");
-                                startTimeoutEvents();
-                                resetAllTimer();
-                                return;
-                            }
-                        }
-                    }
-                    updateCounterValue("sec",sec);
-                    updateCounterValue("min",min);
-                    updateCounterValue("hour",hour);
-                    
-                }else{
-                    $("#startbtn").removeClass("disabled");
-                    $("#pausebtn").addClass("disabled");
-                    
-                }
-            }
-            
-            function startTimeoutEvents(){
-                if (alarmStartTime != 0){
-                    stopAlarm();
-                }
-                audio = new Audio('sound/imuslab_theme.mp3');
-                audio.volume = 0.02;
-                audio.play();
-                alarmStartTime = time();
-                volIncreaseInterval = setInterval( increaseVolumeALittleBit, 5000);
-                $("#stopalarm").removeClass("disabled");
-                ao_module_setWindowTitle("Countdown Timer - Time Out!");
-            }
-            
-            function stopAlarm(){
-                $("#stopalarm").addClass("disabled");
-                clearInterval(volIncreaseInterval);
-                audio.pause();
-                audio.currentTime = 0;
-                ao_module_setWindowTitle("Countdown Timer - Ready");
-                alarmStartTime = 0;
-            }
-            
-            function increaseVolumeALittleBit(){
-                audio.volume += 0.02;
-                console.log("[Timer] Alarm volume increased to: " + audio.volume);
-            }
-            
-            function time(){
-                return Math.floor(Date.now() / 1000);    
-            }
-        </script>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Timer</title>
+        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
+        <script src="../script/jquery.min.js"></script>
+        <script src="../script/ao_module.js"></script>
+        <style>
+            body{
+                padding-top:40px;
+                padding-bottom:50px;
+                background-color:rgba(242, 242, 242, 0.85);
+                backdrop-filter: blur(4px) !important;
+                overflow: hidden;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui container" style="position:absolute;left:0;right:0;width:100%;" align="center">
+            <div class="ui grid" style="margin-top: -30px;">
+                <div class="five wide column">
+                    <div class="ui statistic">
+                        <div id="hour" class="value">00</div>
+                        <div class="label">Hours</div>
+                    </div><br>
+                    <div class="ui icon tiny buttons">
+                        <button class="ui button" onClick="adjustValue('hour',-1);"><i class="minus icon"></i></button>
+                        <button class="ui button" onClick="resetTimer('hour');"><i class="undo icon"></i></button>
+                        <button class="ui button"onClick="adjustValue('hour',1);"><i class="plus icon"></i></button>
+                    </div>
+                </div>
+                <div class="five wide column">
+                    <div class="ui statistic">
+                        <div id="min" class="value">00</div>
+                        <div class="label">Minutes</div>
+                    </div><br>
+                     <div class="ui icon tiny buttons">
+                        <button class="ui button" onClick="adjustValue('min',-1);"><i class="minus icon"></i></button>
+                        <button class="ui button" onClick="resetTimer('min');"><i class="undo icon"></i></button>
+                        <button class="ui button"onClick="adjustValue('min',1);"><i class="plus icon"></i></button>
+                    </div>
+                </div>
+                <div class="five wide column">
+                    <div class="ui statistic">
+                        <div id="sec" class="value">00</div>
+                        <div class="label">Seconds</div>
+                    </div><br>
+                     <div class="ui icon tiny buttons">
+                        <button class="ui button" onClick="adjustValue('sec',-1);"><i class="minus icon"></i></button>
+                        <button class="ui button" onClick="resetTimer('sec');"><i class="undo icon"></i></button>
+                        <button class="ui button"onClick="adjustValue('sec',1);"><i class="plus icon"></i></button>
+                    </div>
+                </div>
+                <div class="one wide column">
+                    <button id="startbtn" class="ui positive tiny icon button" onClick="startCountDown();" style="position:fixed;top:3px;right:3px;">
+                        <i class="play icon"></i>
+                    </button>
+                    <button id="pausebtn" class="ui basic tiny icon button disabled" onClick="pauseCountDown();" style="position:fixed;top:40px;right:3px;">
+                        <i class="pause icon"></i>
+                    </button>
+                    <button id="stopalarm" class="ui negative tiny icon button disabled" onClick="stopAlarm();" style="position:fixed;top:80px;right:3px;">
+                        <i class="alarm mute icon"></i>
+                    </button>
+                </div>
+            </div>
+        </div>
+        <script>
+            //Init the timer window
+            ao_module_setFixedWindowSize();
+            ao_module_setWindowSize(380,190);
+            ao_module_setWindowTitle("Countdown Timer - Ready");
+            if (ao_module_windowID == false){
+                $("body").append("<div style='position:fixed;bottom:10px;left:10px;'>[Warning] Seems you are not opening this module under virtual desktop mode. Some functions might be limited or not fully supported.</div>");
+            }
+            //Global variable
+            var countingDown = false;
+            var counter;
+            setInterval(timerTick,1000);
+            var alarmStartTime = 0;
+            var volIncreaseInterval;
+            var audio;
+             
+            //Handle button pressed
+            function adjustValue(target,offset){
+                if ($("#" + target).length == 0){
+                    return;
+                }
+                var currentSec = parseInt($("#" + target).text());
+                if (target == "sec" || target == "min"){
+                    if (currentSec == 0 && offset < 0){
+                        currentSec = 60;
+                    }else if (currentSec == 59 && offset > 0){
+                        currentSec = -1;
+                    }
+                    currentSec += offset;
+                    updateCounterValue(target,currentSec);
+                }else{
+                    //This logic loop controls the hour counter
+                    if (currentSec == 0 && offset < 0){
+                        currentSec = 24;
+                    }else if (currentSec == 23 && offset > 0){
+                        currentSec = -1;
+                    }
+                    currentSec += offset;
+                    updateCounterValue(target,currentSec);
+                }
+                
+            }
+            
+            function resetTimer(target){
+                $("#" + target).text("00");
+            }
+            
+            function updateCounterValue(target,num){
+                
+                $("#" + target).text(fillZero(num));
+            }
+            
+            function fillZero(value){
+                if (value < 10){
+                    return "0" + value;
+                }else{
+                    return value + "";
+                }
+            }
+            
+            function startCountDown(){
+                countingDown = true;
+                ao_module_setWindowTitle("Countdown Timer - Counting");
+            }
+            
+            function pauseCountDown(){
+                 countingDown = false;
+                 ao_module_setWindowTitle("Countdown Timer - Ready");
+            }
+            
+            function resetAllTimer(){
+                updateCounterValue("sec",0);
+                updateCounterValue("min",0);
+                updateCounterValue("hour",0);
+            }
+            
+            //Looping functions for timer ticks
+            function timerTick(){
+                //This function should be running every 1 second
+                if (countingDown == true){
+                    $("#startbtn").addClass("disabled");
+                    $("#pausebtn").removeClass("disabled");
+                    var sec = parseInt($("#sec").text());
+                    var min = parseInt($("#min").text());
+                    var hour = parseInt($("#hour").text());
+                    //console.log("Coutning down");
+                    if (sec > 0){
+                        sec = sec - 1;
+                    }else if (sec == 0){
+                        sec = 59;
+                        if (min > 0){
+                            min = min -1;
+                        }else if (min == 0){
+                            min = 59;
+                            if (hour > 0){
+                                hour = hour -1;
+                            }else if (hour == 0){
+                                countingDown = false;
+                                //parent.msgbox("Timeout!","Timer Notification");
+                                startTimeoutEvents();
+                                resetAllTimer();
+                                return;
+                            }
+                        }
+                    }
+                    updateCounterValue("sec",sec);
+                    updateCounterValue("min",min);
+                    updateCounterValue("hour",hour);
+                    
+                }else{
+                    $("#startbtn").removeClass("disabled");
+                    $("#pausebtn").addClass("disabled");
+                    
+                }
+            }
+            
+            function startTimeoutEvents(){
+                if (alarmStartTime != 0){
+                    stopAlarm();
+                }
+                audio = new Audio('sound/imuslab_theme.mp3');
+                audio.volume = 0.02;
+                audio.play();
+                alarmStartTime = time();
+                volIncreaseInterval = setInterval( increaseVolumeALittleBit, 5000);
+                $("#stopalarm").removeClass("disabled");
+                ao_module_setWindowTitle("Countdown Timer - Time Out!");
+            }
+            
+            function stopAlarm(){
+                $("#stopalarm").addClass("disabled");
+                clearInterval(volIncreaseInterval);
+                audio.pause();
+                audio.currentTime = 0;
+                ao_module_setWindowTitle("Countdown Timer - Ready");
+                alarmStartTime = 0;
+            }
+            
+            function increaseVolumeALittleBit(){
+                audio.volume += 0.02;
+                console.log("[Timer] Alarm volume increased to: " + audio.volume);
+            }
+            
+            function time(){
+                return Math.floor(Date.now() / 1000);    
+            }
+        </script>
+    </body>
 </html>

+ 1 - 1
web/desktop.system

@@ -1,5 +1,5 @@
+<!DOCTYPE html>
 <html>
-
 <head>
     <title>ArozOS Desktop</title>
     <meta charset="UTF-8">

+ 42 - 41
web/index.html

@@ -1,42 +1,43 @@
-<html>
-    <head>
-        <title>ArOZ Online Bootloader</title>
-        <style>
-            .container{
-                margin-left:4em;
-                margin-right: 4em;
-            }
-
-            .divider{
-                border-bottom: 1px solid #d9d9d9;
-                width: 100%;
-                margin-top:12px;
-                margin-bottom:12px;
-            }
-        </style>
-    </head>
-    <body>
-        <br>
-        <div class="container">
-            <h1>Hi there! It seems you have screwed up your system really badly. </h1>
-            <p>You are seeing this error because you try to do the followings</p>
-            <ul>
-                <li>Reinstalling ArozOS on the same host with the same IP address and session key</li>
-                <li>Trying to copy the ArozOS files to the new image without reinstalling the system</li>
-                <li>The browser cached the old login session key and ask the new server to login with the old key</li>
-            </ul> 
-            <p>And hence, the bootloader blocked your access to the main system due to security reasons.</p>
-            <div class="divider"></div>
-            <p>In any case, this can be easily solved by pressing the Logout link below and re-login again.</p>
-            <p>Press the link below to logout, after seeing "OK", get back to the login page and login again.</p>
-            1. <a href="./system/auth/logout" target="_blank">Logout API Link</a>
-            <br><br>
-            2. <a href="./login.system">Back to Login Page</a>
-            <br><br>
-            If the above instruction still unable to resolve your issue, find me on <a href="https://github.com/tobychui/arozos" target="_blank">https://github.com/tobychui/arozos</a>
-            <div class="divider"></div>
-            ArozOS Cloud Operating System, Developed by tobychui since 2016.<br>
-            
-        </div>
-    </body>
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>ArOZ Online Bootloader</title>
+        <style>
+            .container{
+                margin-left:4em;
+                margin-right: 4em;
+            }
+
+            .divider{
+                border-bottom: 1px solid #d9d9d9;
+                width: 100%;
+                margin-top:12px;
+                margin-bottom:12px;
+            }
+        </style>
+    </head>
+    <body>
+        <br>
+        <div class="container">
+            <h1>Hi there! It seems you have screwed up your system really badly. </h1>
+            <p>You are seeing this error because you try to do the followings</p>
+            <ul>
+                <li>Reinstalling ArozOS on the same host with the same IP address and session key</li>
+                <li>Trying to copy the ArozOS files to the new image without reinstalling the system</li>
+                <li>The browser cached the old login session key and ask the new server to login with the old key</li>
+            </ul> 
+            <p>And hence, the bootloader blocked your access to the main system due to security reasons.</p>
+            <div class="divider"></div>
+            <p>In any case, this can be easily solved by pressing the Logout link below and re-login again.</p>
+            <p>Press the link below to logout, after seeing "OK", get back to the login page and login again.</p>
+            1. <a href="./system/auth/logout" target="_blank">Logout API Link</a>
+            <br><br>
+            2. <a href="./login.system">Back to Login Page</a>
+            <br><br>
+            If the above instruction still unable to resolve your issue, find me on <a href="https://github.com/tobychui/arozos" target="_blank">https://github.com/tobychui/arozos</a>
+            <div class="divider"></div>
+            ArozOS Cloud Operating System, Developed by tobychui since 2016.<br>
+            
+        </div>
+    </body>
 </html>

+ 1 - 0
web/mobile.system

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <title>ArozOS Mobile</title>