3 Commits 6fc5f9f27a ... f105a00b3c

Author SHA1 Message Date
  Toby Chui f105a00b3c Photo app compression feature 3 weeks ago
  Toby Chui c748c9095e Fixed TFTP UI bug 3 weeks ago
  Toby Chui ea2529f385 Replaced .system with .html file extension 3 weeks ago

+ 2 - 2
src/auth.go

@@ -32,9 +32,9 @@ func AuthInit() {
 
 	//Create an Authentication Agent
 	authAgent = auth.NewAuthenticationAgent("ao_auth", []byte(*session_key), sysdb, *allow_public_registry, func(w http.ResponseWriter, r *http.Request) {
-		//Login Redirection Handler, redirect it login.system
+		//Login Redirection Handler, redirect it login.html
 		w.Header().Set("Cache-Control", "no-cache, no-store, no-transform, must-revalidate, private, max-age=0")
-		http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect="+r.URL.Path, http.StatusTemporaryRedirect)
+		http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect="+r.URL.Path, http.StatusTemporaryRedirect)
 	})
 
 	if *allow_autologin {

+ 1 - 1
src/ldap.go

@@ -44,6 +44,6 @@ func ldapInit() {
 	http.HandleFunc("/system/auth/ldap/login", ldapHandler.HandleLogin)
 	http.HandleFunc("/system/auth/ldap/setPassword", ldapHandler.HandleSetPassword)
 	http.HandleFunc("/system/auth/ldap/newPassword", ldapHandler.HandleNewPasswordPage)
-	http.HandleFunc("/ldapLogin.system", ldapHandler.HandleLoginPage)
+	http.HandleFunc("/ldapLogin.html", ldapHandler.HandleLoginPage)
 	http.HandleFunc("/system/auth/ldap/checkldap", ldapHandler.HandleCheckLDAP)
 }

+ 8 - 8
src/main.router.go

@@ -4,7 +4,7 @@ package main
 	ArOZ Online System Main Request Router
 
 	This is used to check authentication before actually serving file to the target client
-	This function also handle the special page (login.system and user.system) delivery
+	This function also handle the special page (login.html and user.html) delivery
 */
 
 import (
@@ -27,7 +27,7 @@ func mrouter(h http.Handler) http.Handler {
 		if r.URL.Path == "/favicon.ico" || r.URL.Path == "/manifest.webmanifest" || r.URL.Path == "/robots.txt" || r.URL.Path == "/humans.txt" {
 			//Serving web specification files. Allow no auth access.
 			h.ServeHTTP(w, r)
-		} else if r.URL.Path == "/login.system" {
+		} else if r.URL.Path == "/login.html" {
 			//Login page. Require special treatment for template.
 			//Get the redirection address from the request URL
 			red, _ := utils.GetPara(r, "redirect")
@@ -38,7 +38,7 @@ func mrouter(h http.Handler) http.Handler {
 				imgsrc = "./web/img/public/auth_icon.png"
 			}
 			imageBase64, _ := utils.LoadImageAsBase64(imgsrc)
-			parsedPage, err := utils.Templateload("web/login.system", map[string]string{
+			parsedPage, err := utils.Templateload("web/login.html", map[string]string{
 				"redirection_addr": red,
 				"usercount":        strconv.Itoa(authAgent.GetUserCounts()),
 				"service_logo":     imageBase64,
@@ -49,10 +49,10 @@ func mrouter(h http.Handler) http.Handler {
 			}
 			w.Header().Add("Content-Type", "text/html; charset=UTF-8")
 			w.Write([]byte(parsedPage))
-		} else if r.URL.Path == "/reset.system" && authAgent.GetUserCounts() > 0 {
+		} else if r.URL.Path == "/reset.html" && authAgent.GetUserCounts() > 0 {
 			//Password restart page. Allow access only when user number > 0
 			system_resetpw_handlePasswordReset(w, r)
-		} else if r.URL.Path == "/user.system" && authAgent.GetUserCounts() == 0 {
+		} else if r.URL.Path == "/user.html" && authAgent.GetUserCounts() == 0 {
 			//Serve user management page. This only allows serving of such page when the total usercount = 0 (aka System Initiation)
 			h.ServeHTTP(w, r)
 
@@ -102,7 +102,7 @@ func mrouter(h http.Handler) http.Handler {
 			} else {
 				interfaceModule := userinfo.GetInterfaceModules()
 				if len(interfaceModule) == 1 && interfaceModule[0] == "Desktop" {
-					http.Redirect(w, r, "./desktop.system", http.StatusTemporaryRedirect)
+					http.Redirect(w, r, "./desktop.html", http.StatusTemporaryRedirect)
 				} else if len(interfaceModule) == 1 {
 					//User with default interface module not desktop
 					modileInfo := moduleHandler.GetModuleInfoByID(interfaceModule[0])
@@ -120,7 +120,7 @@ func mrouter(h http.Handler) http.Handler {
 					http.Redirect(w, r, "./SystemAO/boot/no_interfaceing.html", http.StatusTemporaryRedirect)
 				} else {
 					//For unknown operations, send it to desktop
-					http.Redirect(w, r, "./desktop.system", http.StatusTemporaryRedirect)
+					http.Redirect(w, r, "./desktop.html", http.StatusTemporaryRedirect)
 				}
 			}
 		} else if ((len(r.URL.Path) >= 5 && r.URL.Path[:5] == "/www/") || r.URL.Path == "/www") && *allow_homepage {
@@ -184,7 +184,7 @@ func mrouter(h http.Handler) http.Handler {
 				//Other paths
 				//Rediect to login page
 				w.Header().Set("Cache-Control", "no-cache, no-store, no-transform, must-revalidate, private, max-age=0")
-				http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect="+r.URL.String(), 307)
+				http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect="+r.URL.String(), 307)
 			}
 
 		}

+ 88 - 0
src/mod/agi/agi.image.go

@@ -2,6 +2,7 @@ package agi
 
 import (
 	"bytes"
+	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -233,6 +234,92 @@ func (g *Gateway) injectImageLibFunctions(payload *static.AgiLibInjectionPayload
 		return otto.TrueValue()
 	})
 
+	//Resize image and return as base64 data URL, require (filepath, width, height, format)
+	vm.Set("_imagelib_resizeImageBase64", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
+		width, err := call.Argument(1).ToInteger()
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
+		height, err := call.Argument(2).ToInteger()
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
+		format := "jpeg"
+		if !call.Argument(3).IsUndefined() {
+			format, err = call.Argument(3).ToString()
+			if err != nil {
+				format = "jpeg"
+			}
+		}
+
+		//Convert the virtual path to real path
+		srcfsh, rsrc, err := static.VirtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
+		resizeOpeningFile := rsrc
+		var srcFile arozfs.File
+		if srcfsh.RequireBuffer {
+			resizeOpeningFile, _, err = g.bufferRemoteResourcesToLocal(srcfsh, u, rsrc)
+			if err != nil {
+				g.RaiseError(err)
+				return otto.FalseValue()
+			}
+
+			srcFile, err = os.Open(resizeOpeningFile)
+			if err != nil {
+				g.RaiseError(err)
+				return otto.FalseValue()
+			}
+		} else {
+			srcFile, err = srcfsh.FileSystemAbstraction.Open(resizeOpeningFile)
+			if err != nil {
+				g.RaiseError(err)
+				return otto.FalseValue()
+			}
+		}
+		defer srcFile.Close()
+
+		//Decode and resize the image
+		src, err := imaging.Decode(srcFile)
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+		src = imaging.Resize(src, int(width), int(height), imaging.Lanczos)
+
+		//Encode to bytes buffer
+		var buf bytes.Buffer
+		if format == "png" {
+			err = png.Encode(&buf, src)
+		} else {
+			err = jpeg.Encode(&buf, src, &jpeg.Options{Quality: 85})
+		}
+		if err != nil {
+			g.RaiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Convert to base64
+		imageBytes := buf.Bytes()
+		base64String := "data:image/" + format + ";base64," + base64.StdEncoding.EncodeToString(imageBytes)
+
+		result, _ := vm.ToValue(base64String)
+		return result
+	})
+
 	//Crop the given image, require (input, output, posx, posy, width, height)
 	vm.Set("_imagelib_cropImage", func(call otto.FunctionCall) otto.Value {
 		vsrc, err := call.Argument(0).ToString()
@@ -516,6 +603,7 @@ func (g *Gateway) injectImageLibFunctions(payload *static.AgiLibInjectionPayload
 		var imagelib = {};
 		imagelib.getImageDimension = _imagelib_getImageDimension;
 		imagelib.resizeImage = _imagelib_resizeImage;
+		imagelib.resizeImageBase64 = _imagelib_resizeImageBase64;
 		imagelib.cropImage = _imagelib_cropImage;
 		imagelib.loadThumbString = _imagelib_loadThumbString;
 		imagelib.hasExif = _imagelib_hasExif;

+ 1 - 1
src/mod/auth/ldap/web_login.go

@@ -26,7 +26,7 @@ func (ldap *ldapHandler) HandleLoginPage(w http.ResponseWriter, r *http.Request)
 		imgsrc = "./web/img/public/auth_icon.png"
 	}
 	imageBase64, _ := utils.LoadImageAsBase64(imgsrc)
-	parsedPage, err := utils.Templateload("web/login.system", map[string]string{
+	parsedPage, err := utils.Templateload("web/login.html", map[string]string{
 		"redirection_addr": red,
 		"usercount":        strconv.Itoa(ldap.ag.GetUserCounts()),
 		"service_logo":     imageBase64,

+ 1 - 1
src/mod/auth/oauth2/oauth2.go

@@ -134,7 +134,7 @@ func (oh *OauthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
 		//also makr the login as fail.
 		if oh.reg.AllowRegistry {
 			oh.ag.Logger.LogAuthByRequestInfo(username, r.RemoteAddr, time.Now().Unix(), false, "web")
-			http.Redirect(w, r, "/public/register/register.system?user="+username, http.StatusFound)
+			http.Redirect(w, r, "/public/register/register.html?user="+username, http.StatusFound)
 		} else {
 			oh.ag.Logger.LogAuthByRequestInfo(username, r.RemoteAddr, time.Now().Unix(), false, "web")
 			w.Header().Set("Content-Type", "text/html")

+ 2 - 2
src/mod/auth/register/register.go

@@ -97,12 +97,12 @@ func (h *RegisterHandler) HandleRegisterInterface(w http.ResponseWriter, r *http
 		//Load the vendor icon as base64
 		imagecontent, _ := readImageFileAsBase64(h.options.VendorIcon)
 
-		s, err := utils.Templateload("./system/auth/register.system", map[string]string{
+		s, err := utils.Templateload("./system/auth/register.html", map[string]string{
 			"host_name":   h.options.Hostname,
 			"vendor_logo": imagecontent,
 		})
 		if err != nil {
-			log.Println("Template not found: system/auth/register.system")
+			log.Println("Template not found: system/auth/register.html")
 			http.NotFound(w, r)
 			return
 		}

+ 5 - 4
src/mod/share/share.go

@@ -390,7 +390,7 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
 					w.WriteHeader(http.StatusUnauthorized)
 					w.Write([]byte("401 - Unauthorized"))
 				} else {
-					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect=/share/"+id, 307)
+					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect=/share/"+id, 307)
 				}
 				return
 			} else {
@@ -403,7 +403,7 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
 					w.WriteHeader(http.StatusUnauthorized)
 					w.Write([]byte("401 - Unauthorized"))
 				} else {
-					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect=/share/"+id, 307)
+					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect=/share/"+id, 307)
 				}
 				return
 			}
@@ -443,7 +443,7 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
 					w.WriteHeader(http.StatusUnauthorized)
 					w.Write([]byte("401 - Unauthorized"))
 				} else {
-					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect=/share/"+id, 307)
+					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect=/share/"+id, 307)
 				}
 				return
 			}
@@ -468,7 +468,7 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
 					w.WriteHeader(http.StatusUnauthorized)
 					w.Write([]byte("401 - Unauthorized"))
 				} else {
-					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.system")+"?redirect=/share/"+id, 307)
+					http.Redirect(w, r, utils.ConstructRelativePathFromRequestURL(r.RequestURI, "login.html")+"?redirect=/share/"+id, 307)
 				}
 				return
 			}
@@ -1417,3 +1417,4 @@ func getPathHashFromUsernameAndVpath(userinfo *user.User, vpath string) (string,
 	}
 	return shareEntry.GetPathHash(fsh, vpath, userinfo.Username)
 }
+

+ 8 - 8
src/mod/storage/tftp/tftp.go

@@ -65,12 +65,12 @@ func NewTFTPHandler(userHandler *user.UserHandler, ServerName string, Port int,
 func (h *Handler) Start() error {
 	if h.server != nil {
 		addr := ":" + strconv.Itoa(h.Port)
-		log.Println("TFTP Server Started, listening at: " + strconv.Itoa(h.Port))
+		log.Println("[TFTP] Server Started, listening at: " + strconv.Itoa(h.Port))
 
 		go func() {
 			err := h.server.ListenAndServe(addr)
 			if err != nil {
-				log.Println("TFTP Server error:", err)
+				log.Println("[TFTP] Server error:", err)
 			}
 		}()
 
@@ -117,7 +117,7 @@ func (d *tftpDriver) readHandler(filename string, rf io.ReaderFrom) error {
 	// Open file for reading
 	file, err := afs.Open(filename)
 	if err != nil {
-		log.Printf("TFTP READ: Failed to open %s: %v", filename, err)
+		log.Printf("[TFTP] READ: Failed to open %s: %v", filename, err)
 		return err
 	}
 	defer file.Close()
@@ -136,11 +136,11 @@ func (d *tftpDriver) readHandler(filename string, rf io.ReaderFrom) error {
 
 	n, err := rf.ReadFrom(file)
 	if err != nil {
-		log.Printf("TFTP READ: Transfer error for %s: %v", filename, err)
+		log.Printf("[TFTP] READ: Transfer error for %s: %v", filename, err)
 		return err
 	}
 
-	log.Printf("TFTP READ: Sent %s (%d bytes)", filename, n)
+	log.Printf("[TFTP] READ: Sent %s (%d bytes)", filename, n)
 	return nil
 }
 
@@ -171,17 +171,17 @@ func (d *tftpDriver) writeHandler(filename string, wt io.WriterTo) error {
 	// Check if user can write
 	file, err := afs.Create(filename)
 	if err != nil {
-		log.Printf("TFTP WRITE: Failed to create %s: %v", filename, err)
+		log.Printf("[TFTP] WRITE: Failed to create %s: %v", filename, err)
 		return err
 	}
 	defer file.Close()
 
 	n, err := wt.WriteTo(file)
 	if err != nil {
-		log.Printf("TFTP WRITE: Transfer error for %s: %v", filename, err)
+		log.Printf("[TFTP] WRITE: Transfer error for %s: %v", filename, err)
 		return err
 	}
 
-	log.Printf("TFTP WRITE: Received %s (%d bytes)", filename, n)
+	log.Printf("[TFTP] WRITE: Received %s (%d bytes)", filename, n)
 	return nil
 }

+ 1 - 1
src/network.go

@@ -450,7 +450,7 @@ func FileServerInit() {
 		ID:                "tftp",
 		Name:              "TFTP",
 		Desc:              "Trivial File Transfer Protocol Server",
-		IconPath:          "img/system/network-folder.svg",
+		IconPath:          "img/system/network-folder-black.svg",
 		DefaultPorts:      []int{69},
 		Ports:             []int{},
 		ForwardPortIfUpnp: false,

+ 2 - 2
src/register.go

@@ -37,8 +37,8 @@ func RegisterSystemInit() {
 		registerHandler.AllowRegistry = false
 	}
 
-	http.HandleFunc("/public/register/register.system", registerHandler.HandleRegisterInterface)
-	http.HandleFunc("/public/register/handleRegister.system", registerHandler.HandleRegisterRequest)
+	http.HandleFunc("/public/register/register.html", registerHandler.HandleRegisterInterface)
+	http.HandleFunc("/public/register/handleRegister.html", registerHandler.HandleRegisterRequest)
 	http.HandleFunc("/public/register/checkPublicRegister", registerHandler.HandleRegisterCheck)
 
 	//General user functions

+ 0 - 0
src/system/auth/register.system → src/system/auth/register.html


+ 77 - 0
src/web/Photo/backend/getCompressedImg.js

@@ -0,0 +1,77 @@
+/*
+    Compressed Image Retrieval Module
+    Retrieves compressed images for display in the Photo application.
+    
+    This module compresses images to a maximum of 2048x2048 pixels
+    while maintaining aspect ratio and returns as base64 data URL.
+*/
+
+requirelib("imagelib");
+
+function main() {
+    // Get the filepath from the request
+    if (typeof(filepath) == "undefined") {
+        sendJSONResp(JSON.stringify({
+            error: "filepath parameter is required"
+        }));
+        return;
+    }
+
+    // Define max dimensions
+    var maxWidth = 1024;
+    var maxHeight = 1024;
+
+    // Get original image dimensions
+    var imgInfo = imagelib.getImageDimension(filepath);
+    if (!imgInfo || !imgInfo[0] || !imgInfo[1]) {
+        // If we can't get dimensions, return error
+        sendJSONResp(JSON.stringify({
+            error: "Cannot read image dimensions"
+        }));
+        return;
+    }
+
+    var originalWidth = imgInfo[0];
+    var originalHeight = imgInfo[1];
+
+    // Calculate new dimensions while maintaining aspect ratio
+    var newWidth = originalWidth;
+    var newHeight = originalHeight;
+
+    if (originalWidth > maxWidth || originalHeight > maxHeight) {
+        var widthRatio = maxWidth / originalWidth;
+        var heightRatio = maxHeight / originalHeight;
+        var ratio = Math.min(widthRatio, heightRatio);
+
+        newWidth = Math.round(originalWidth * ratio);
+        newHeight = Math.round(originalHeight * ratio);
+    }
+
+    // Determine output format from file extension
+    var ext = filepath.split(".").pop().toLowerCase();
+    var format = "jpeg";
+    if (ext == "png") {
+        format = "png";
+    }
+
+    // Resize the image and get base64 data URL
+    try {
+        var base64DataUrl = imagelib.resizeImageBase64(filepath, newWidth, newHeight, format);
+        
+        if (base64DataUrl) {
+            // Return the base64 data URL as plain text
+            sendResp(base64DataUrl);
+        } else {
+            sendJSONResp(JSON.stringify({
+                error: "Failed to resize image"
+            }));
+        }
+    } catch (error) {
+        sendJSONResp(JSON.stringify({
+            error: "Failed to compress image: " + error.toString()
+        }));
+    }
+}
+
+main();
+

+ 12 - 1
src/web/Photo/backend/listFolder.js

@@ -121,7 +121,18 @@ function main(){
     // Filter out JPG duplicates when RAW files exist
     files = filterDuplicates(files);
 
-    sendJSONResp(JSON.stringify([folders, files]));
+    // Add filesize information to each file
+    var filesWithSize = [];
+    for (var i = 0; i < files.length; i++){
+        var filepath = files[i];
+        var filesize = filelib.filesize(filepath);
+        filesWithSize.push({
+            filepath: filepath,
+            filesize: filesize
+        });
+    }
+
+    sendJSONResp(JSON.stringify([folders, filesWithSize]));
 }
 
 main();

+ 180 - 80
src/web/Photo/index.html

@@ -148,6 +148,7 @@
             padding: 20px;
             overflow: hidden;
             cursor: grab;
+            position: relative;
         }
 
         .viewer-left img {
@@ -159,6 +160,21 @@
             user-select: none;
         }
 
+        #compressedImage {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            opacity: 1;
+            transition: opacity 0.3s ease-out;
+            z-index: 2;
+        }
+
+        #compressedImage.hidden {
+            opacity: 0;
+            pointer-events: none;
+        }
+
         .viewer-left img.zoomed {
             max-width: none;
             max-height: none;
@@ -301,37 +317,94 @@
             }
         }
 
-        /* Zoom Snackbar Styles */
-        .zoom-snackbar {
+        /* Zoom Controls Styles */
+        .zoom-controls {
             position: absolute;
             bottom: 20px;
             left: 20px;
-            background: rgba(0, 0, 0, 0.8);
+            display: flex;
+            flex-direction: column;
+            gap: 8px;
+            z-index: 10;
+        }
+
+        .zoom-btn {
+            background: rgba(0, 0, 0, 0.7);
             color: white;
-            padding: 8px 12px;
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            width: 40px;
+            height: 40px;
             border-radius: 4px;
+            cursor: pointer;
+            font-size: 20px;
+            font-weight: bold;
+            transition: all 0.2s;
             display: flex;
             align-items: center;
-            gap: 10px;
-            font-size: 14px;
-            font-family: Arial, sans-serif;
-            z-index: 1001;
+            justify-content: center;
             backdrop-filter: blur(4px);
         }
 
-        .zoom-reset-btn {
-            background: #f76c5d;
+        .zoom-btn:hover {
+            background: rgba(247, 108, 93, 0.9);
+            border-color: #f76c5d;
+        }
+
+        .zoom-btn:active {
+            transform: scale(0.95);
+        }
+
+        .zoom-level-indicator {
+            background: rgba(0, 0, 0, 0.7);
             color: white;
-            border: none;
-            padding: 4px 8px;
-            border-radius: 3px;
-            cursor: pointer;
+            padding: 6px 10px;
+            border-radius: 4px;
             font-size: 12px;
-            transition: background-color 0.2s;
+            text-align: center;
+            backdrop-filter: blur(4px);
+            border: 1px solid rgba(255, 255, 255, 0.2);
         }
 
-        .zoom-reset-btn:hover {
-            background: #e55a4f;
+        /* Navigation Controls Styles */
+        .nav-controls {
+            position: absolute;
+            bottom: 20px;
+            right: 20px;
+            display: flex;
+            flex-direction: row;
+            gap: 8px;
+            z-index: 10;
+        }
+
+        .nav-btn {
+            background: rgba(0, 0, 0, 0.7);
+            color: white;
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            width: 40px;
+            height: 40px;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 20px;
+            font-weight: bold;
+            transition: all 0.2s;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            backdrop-filter: blur(4px);
+        }
+
+        .nav-btn:hover:not(:disabled) {
+            background: rgba(247, 108, 93, 0.9);
+            border-color: #f76c5d;
+        }
+
+        .nav-btn:active:not(:disabled) {
+            transform: scale(0.95);
+        }
+
+        .nav-btn:disabled {
+            opacity: 0.3;
+            cursor: not-allowed;
         }
 
         @media (max-width: 768px) {
@@ -339,6 +412,23 @@
                 display: none !important;
             }
         }
+
+        /* Loading Progress Indicator */
+        .loading-progress {
+            position: absolute;
+            bottom: 20px;
+            left: 50%;
+            transform: translateX(-50%);
+            background: rgba(0, 0, 0, 0.7);
+            color: white;
+            padding: 8px 16px;
+            border-radius: 4px;
+            font-size: 14px;
+            backdrop-filter: blur(4px);
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            z-index: 10;
+            display: none;
+        }
     </style>
 
 </head>
@@ -410,21 +500,21 @@
             <div id="viewboxContainer">
                 <div x-show="viewMode === 'grid'" id="viewbox" class="ui six cards viewbox">
                     <template x-for="image in images">
-                        <div class="imagecard" style="cursor: pointer;" x-on:click="showImage($el); ShowModal();" :style="{width: renderSize + 'px', height: renderSize + 'px'}" :filedata="encodeURIComponent(JSON.stringify({'filename':image.split('/').pop(),'filepath':image}))">
+                        <div class="imagecard" style="cursor: pointer;" x-on:click="showImage($el); ShowModal();" :style="{width: renderSize + 'px', height: renderSize + 'px'}" :filedata="encodeURIComponent(JSON.stringify({'filename':image.filepath.split('/').pop(),'filepath':image.filepath,'filesize':image.filesize}))">
                             <a class="image" x-init="updateImageSizes();">
-                                <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image">
+                                <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image.filepath">
                             </a>
                         </div>
                     </template>
                 </div>
                 <div x-show="viewMode === 'list'" class="ui relaxed divided inverted list">
                     <template x-for="image in images">
-                        <div class="item" style="cursor: pointer; padding-left: 10px; " x-on:click="showImage($el); ShowModal();" :filedata="encodeURIComponent(JSON.stringify({'filename':image.split('/').pop(),'filepath':image}))">
-                            <img class="ui small image" :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image"
+                        <div class="item" style="cursor: pointer; padding-left: 10px; " x-on:click="showImage($el); ShowModal();" :filedata="encodeURIComponent(JSON.stringify({'filename':image.filepath.split('/').pop(),'filepath':image.filepath,'filesize':image.filesize}))">
+                            <img class="ui small image" :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image.filepath"
                                  style="width: 60px; height: 60px;">
                             <div class="content">
-                                <div class="header" x-text="image.split('/').pop()"></div>
-                                <div class="description" x-text="image"></div>
+                                <div class="header" x-text="image.filepath.split('/').pop()"></div>
+                                <div class="description" x-text="image.filepath"></div>
                             </div>
                         </div>
                     </template>
@@ -442,9 +532,26 @@
                 <img id="bg-image" src="" />
             </div>
             <div class="viewer-left">
+                <img id="compressedImage" src="" style="display: none;" />
                 <img id="fullImage" src="img/loading.png" />
                 <button class="close-btn" onclick="closeViewer()">×</button>
                 <button class="show-info-btn" onclick="showInfoPanel()">ℹ</button>
+                
+                <!-- Loading Progress -->
+                <div id="loading-progress" class="loading-progress">Loading 0%</div>
+                
+                <!-- Zoom Controls -->
+                <div id="zoom-controls" class="zoom-controls">
+                    <button class="zoom-btn" onclick="zoomIn()" title="Zoom In">+</button>
+                    <div class="zoom-level-indicator" id="zoom-level-display">100%</div>
+                    <button class="zoom-btn" onclick="zoomOut()" title="Zoom Out">−</button>
+                </div>
+                
+                <!-- Navigation Controls -->
+                <div id="nav-controls" class="nav-controls">
+                    <button id="prev-btn" class="nav-btn" onclick="showPreviousImage()" title="Previous Photo">‹</button>
+                    <button id="next-btn" class="nav-btn" onclick="showNextImage()" title="Next Photo">›</button>
+                </div>
             </div>
             <div class="viewer-right">
                 <div>
@@ -590,12 +697,6 @@
                 </div>
             </div>
         </div>
-        
-        <!-- Zoom Snackbar -->
-        <div id="zoom-snackbar" class="zoom-snackbar" style="display: none;">
-            <span id="zoom-level-display">x1.0</span>
-            <button id="zoom-reset-btn" class="zoom-reset-btn" onclick="resetZoom()">Reset</button>
-        </div>
     </div>
 
 </body>
@@ -622,6 +723,31 @@
         document.querySelector('.viewer-right').style.display = 'block';
     }
 
+    // Navigation functionality
+    function showPreviousImage() {
+        if (typeof prePhoto !== 'undefined' && prePhoto != null) {
+            showImage(prePhoto);
+        }
+    }
+
+    function showNextImage() {
+        if (typeof nextPhoto !== 'undefined' && nextPhoto != null) {
+            showImage(nextPhoto);
+        }
+    }
+
+    function updateNavigationButtons() {
+        const prevBtn = document.getElementById('prev-btn');
+        const nextBtn = document.getElementById('next-btn');
+        
+        if (prevBtn) {
+            prevBtn.disabled = (typeof prePhoto === 'undefined' || prePhoto == null || !$(prePhoto).hasClass("imagecard"));
+        }
+        if (nextBtn) {
+            nextBtn.disabled = (typeof nextPhoto === 'undefined' || nextPhoto == null || !$(nextPhoto).hasClass("imagecard"));
+        }
+    }
+
     // Zoom and Pan functionality
     let zoomLevel = 1;
     let panX = 0;
@@ -642,25 +768,40 @@
 
     function updateImageTransform() {
         const img = document.getElementById('fullImage');
-        const snackbar = document.getElementById('zoom-snackbar');
+        const controls = document.getElementById('zoom-controls');
         const zoomDisplay = document.getElementById('zoom-level-display');
         
         if (zoomLevel > 1) {
             img.style.transform = `scale(${zoomLevel}) translate(${panX}px, ${panY}px)`;
             img.classList.add('zoomed');
-            
-            // Show zoom snackbar
-            zoomDisplay.textContent = 'x' + zoomLevel.toFixed(1);
-            snackbar.style.display = 'flex';
         } else {
             img.style.transform = 'none';
             img.classList.remove('zoomed');
             panX = 0;
             panY = 0;
-            
-            // Hide zoom snackbar
-            snackbar.style.display = 'none';
         }
+        
+        // Always show zoom controls and update display
+        zoomDisplay.textContent = Math.round(zoomLevel * 100) + '%';
+        controls.style.display = 'flex';
+    }
+
+    function zoomIn() {
+        const img = document.getElementById('fullImage');
+        const rect = img.getBoundingClientRect();
+        const centerX = rect.left + rect.width / 2;
+        const centerY = rect.top + rect.height / 2;
+        let newZoom = Math.min(3, zoomLevel + 0.2);
+        zoomAtPoint(newZoom, centerX, centerY);
+    }
+
+    function zoomOut() {
+        const img = document.getElementById('fullImage');
+        const rect = img.getBoundingClientRect();
+        const centerX = rect.left + rect.width / 2;
+        const centerY = rect.top + rect.height / 2;
+        let newZoom = Math.max(1, zoomLevel - 0.2);
+        zoomAtPoint(newZoom, centerX, centerY);
     }
 
     function zoomAtPoint(scale, centerX, centerY) {
@@ -707,26 +848,6 @@
         // Reset zoom state
         resetZoom();
 
-        // Define handler functions to allow removal and re-addition
-        let handleDblClick = function(e) {
-            console.log(e);
-            e.preventDefault();
-            if (zoomLevel > 1) {
-                resetZoom();
-            } else {
-                zoomAtPoint(2, e.clientX, e.clientY);
-            }
-        };
-
-        let handleWheel = function(e) {
-            e.preventDefault();
-            const delta = e.deltaY > 0 ? 0.9 : 1.1;
-            let actualZoomLevel = zoomLevel * delta;
-            if (actualZoomLevel < 1) actualZoomLevel = 1;
-            if (actualZoomLevel > 3.3) actualZoomLevel = 3.3;
-            zoomAtPoint(actualZoomLevel, e.clientX, e.clientY);
-        };
-
         let handleMouseDown = function(e) {
             if (zoomLevel > 1) {
                 e.preventDefault();
@@ -830,8 +951,6 @@
         };
 
         // Remove existing listeners to prevent double triggering
-        img.removeEventListener('dblclick', handleDblClick);
-        img.removeEventListener('wheel', handleWheel);
         img.removeEventListener('mousedown', handleMouseDown);
         document.removeEventListener('mousemove', handleMouseMove);
         document.removeEventListener('mouseup', handleMouseUp);
@@ -840,8 +959,6 @@
         img.removeEventListener('touchend', handleTouchEnd);
 
         // Add listeners back
-        img.addEventListener('dblclick', handleDblClick);
-        img.addEventListener('wheel', handleWheel);
         img.addEventListener('mousedown', handleMouseDown);
         document.addEventListener('mousemove', handleMouseMove);
         document.addEventListener('mouseup', handleMouseUp);
@@ -849,26 +966,9 @@
         img.addEventListener('touchmove', handleTouchMove);
         img.addEventListener('touchend', handleTouchEnd);
         
-        // Double click to zoom
-        img.addEventListener('dblclick', function(e) {
-            console.log(e);
-            e.preventDefault();
-            if (zoomLevel > 1) {
-                resetZoom();
-            } else {
-                zoomAtPoint(2, e.clientX, e.clientY);
-            }
-        });
+        // Double-click zoom removed - use +/- buttons instead
         
-        // Mouse wheel zoom
-        img.addEventListener('wheel', function(e) {
-            e.preventDefault();
-            const delta = e.deltaY > 0 ? 0.9 : 1.1;
-            let actualZoomLevel = zoomLevel * delta;
-            if (actualZoomLevel < 1) actualZoomLevel = 1;
-            if (actualZoomLevel > 3.3) actualZoomLevel = 3.3;;
-            zoomAtPoint(actualZoomLevel, e.clientX, e.clientY);
-        });
+        // Mouse wheel zoom removed - use +/- buttons instead
         
         // Mouse drag pan
         img.addEventListener('mousedown', function(e) {

+ 189 - 20
src/web/Photo/photo.js

@@ -11,6 +11,14 @@ let prePhoto = "";
 let nextPhoto = "";
 let currentModel = "";
 
+// Check if image should use compression (only JPG/PNG)
+function shouldUseCompression(filepath, filesize) {
+    const ext = filepath.split('.').pop().toLowerCase();
+    const isJpgOrPng = (ext === 'jpg' || ext === 'jpeg' || ext === 'png');
+    const COMPRESSION_THRESHOLD = 5 * 1024 * 1024; // 5MB
+    return isJpgOrPng && filesize && filesize > COMPRESSION_THRESHOLD;
+}
+
 // Get viewable image URL (handles RAW files)
 function getViewableImageUrl(filepath, callback) {
     // Both RAW and regular images now use backend rendering
@@ -212,6 +220,7 @@ function closeViewer(){
 
     setTimeout(function(){
         $("#fullImage").attr("src","img/loading.png");
+        $("#compressedImage").attr("src","").hide().removeClass('hidden');
         $("#bg-image").attr("src","");
         $("#info-filename").text("");
         $("#info-filepath").text("");
@@ -243,33 +252,83 @@ function showImage(object){
         resetZoom();
     }
 
+    if (!$(object).hasClass("imagecard")){
+        // Not an image card, do nothing
+        return;
+    }
     var fd = JSON.parse(decodeURIComponent($(object).attr("filedata")));
-
-    // Update image dimensions and generate histogram when loaded
-    $("#fullImage").off("load").on('load', function() {
-        let width = this.naturalWidth;
-        let height = this.naturalHeight;
-        $("#info-dimensions").text(width + ' × ' + height + "px");
-
-        // Wait for image to be ready, then generate histogram
-        const canvas = document.getElementById('histogram-canvas');
-        if (canvas) {
-            generateHistogram(document.getElementById('fullImage'), canvas);
-        }
-    });
+    $("#info-dimensions").text("Calculating...");
+    // Check if we should use compression (only for JPG/PNG > 5MB)
+    const useCompression = shouldUseCompression(fd.filepath, fd.filesize);
 
     // Get image URL (backend handles RAW files automatically)
     getViewableImageUrl(fd.filepath, (imageUrl, isSupported, isBlob, method) => {
-        $("#fullImage").attr('src', imageUrl);
-        $("#bg-image").attr('src', imageUrl);
+        const compressedImg = document.getElementById('compressedImage');
+        const fullImg = document.getElementById('fullImage');
+        const bgImg = document.getElementById('bg-image');
+        
+        // Reset compressed image
+        compressedImg.style.display = 'none';
+        compressedImg.classList.remove('hidden');
+         
+        if (useCompression) {
+            // Use compressed version for large JPG/PNG files
+            console.log('Large JPG/PNG detected (' + (fd.filesize / 1024 / 1024).toFixed(2) + 'MB), loading compressed version first');
+
+            fetch(ao_root + "system/ajgi/interface?script=Photo/backend/getCompressedImg.js", {
+                method: 'POST',
+                cache: 'no-cache',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    "filepath": fd.filepath
+                })
+            }).then(resp => {
+                resp.text().then(dataURL => {
+                    // Show compressed image
+                    compressedImg.src = dataURL;
+                    compressedImg.style.display = 'block';
+                    bgImg.src = dataURL;
+
+                    // Start loading full-size image in background
+                    loadFullSizeImageInBackground(imageUrl, fd);
+                });
+            }).catch(error => {
+                console.error('Failed to load compressed image:', error);
+                // Fall back to full size image
+                fullImg.src = imageUrl;
+                bgImg.src = imageUrl;
+            });
+        } else {
+            $("#compressedImage").hide();
+            // Use full image URL directly for RAW, WEBP, or small JPG/PNG files
+            if (method === 'backend_raw') {
+                console.log('RAW file: Rendered by backend');
+            }
+            fullImg.src = imageUrl;
+            bgImg.src = imageUrl;
+        }
+
+        // Update image dimensions and generate histogram when full image loads
+        $("#fullImage").off("load").on('load', function() {
+            let width = this.naturalWidth;
+            let height = this.naturalHeight;
+            $("#info-dimensions").text(width + ' × ' + height + "px");
+
+            // Hide the compressed image once full image is loaded
+            $("#compressedImage").hide();
+
+            // Wait for image to be ready, then generate histogram
+            const canvas = document.getElementById('histogram-canvas');
+            if (canvas) {
+                generateHistogram(this, canvas);
+            }
+        });
+
         $("#info-filename").text(fd.filename);
         $("#info-filepath").text(fd.filepath);
 
-        // Log the rendering method used
-        if (method === 'backend_raw') {
-            console.log('RAW file: Rendered by backend');
-        }
-
         var nextCard = $(object).next();
         var prevCard = $(object).prev();
         if (nextCard.length > 0){
@@ -284,6 +343,11 @@ function showImage(object){
             prePhoto = null;
         }
 
+        // Update navigation buttons state
+        if (typeof updateNavigationButtons === 'function') {
+            updateNavigationButtons();
+        }
+
         ao_module_setWindowTitle("Photo - " + fd.filename);
 
         window.location.hash = encodeURIComponent(JSON.stringify({filename: fd.filename, filepath: fd.filepath}));
@@ -309,6 +373,111 @@ function showImage(object){
     });
 }
 
+
+// Function to load full-size image in background with progress tracking
+function loadFullSizeImageInBackground(fullSizeUrl, fileData) {
+    console.log('Starting background download of full-size image...');
+    
+  
+
+    const loadingIndicator = document.getElementById('loading-progress');
+    const fullImage = document.getElementById('fullImage');
+    const compressedImage = document.getElementById('compressedImage');
+    const bgImage = document.getElementById('bg-image');
+    fullImage.src = fullSizeUrl;
+
+    return;
+    // Legacy blob loading method
+    // Show loading indicator
+    /*
+    if (loadingIndicator) {
+        loadingIndicator.style.display = 'block';
+        loadingIndicator.textContent = 'Loading 0%';
+    }
+    
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', fullSizeUrl, true);
+    xhr.responseType = 'arraybuffer';
+    
+    // Track download progress
+    xhr.onprogress = function(event) {
+        if (event.lengthComputable) {
+            const percentComplete = (event.loaded / event.total) * 100;
+            console.log('Download progress: ' + percentComplete.toFixed(2) + '% (' + 
+                       (event.loaded / 1024 / 1024).toFixed(2) + 'MB / ' + 
+                       (event.total / 1024 / 1024).toFixed(2) + 'MB)');
+            
+            // Update loading indicator
+            if (loadingIndicator) {
+                loadingIndicator.textContent = 'Loading ' + Math.round(percentComplete) + '%';
+            }
+        } else {
+            console.log('Download progress: ' + (event.loaded / 1024 / 1024).toFixed(2) + 'MB downloaded');
+            
+            // Update loading indicator without percentage
+            if (loadingIndicator) {
+                loadingIndicator.textContent = 'Loading...';
+            }
+        }
+    };
+    
+    // Handle successful download
+    xhr.onload = function() {
+        // Hide loading indicator
+        if (loadingIndicator) {
+            loadingIndicator.style.display = 'none';
+        }
+        
+        if (xhr.status === 200) {
+            console.log('Full-size image downloaded successfully, swapping images...');
+            
+            // Set full image src directly (browser will cache it)
+            fullImage.onload = function() {
+                console.log('Full-size image loaded and cached');
+                
+                // Fade out compressed image
+                if (compressedImage.style.display !== 'none') {
+                    compressedImage.classList.add('hidden');
+                    setTimeout(() => {
+                        compressedImage.style.display = 'none';
+                    }, 300); // Match CSS transition duration
+                }
+                
+                // Update background
+                bgImage.src = fullSizeUrl;
+                
+                // Update dimensions with full-size image dimensions
+                $("#info-dimensions").text(fullImage.naturalWidth + ' × ' + fullImage.naturalHeight + "px");
+                
+                // Regenerate histogram with full-size image
+                const canvas = document.getElementById('histogram-canvas');
+                if (canvas) {
+                    generateHistogram(fullImage, canvas);
+                }
+            };
+            
+            // Set the source to trigger browser caching
+            fullImage.src = fullSizeUrl;
+        }
+    };
+    
+    // Handle errors
+    xhr.onerror = function() {
+        // Hide loading indicator
+        if (loadingIndicator) {
+            loadingIndicator.style.display = 'none';
+        }
+        console.error('Failed to download full-size image');
+        
+        // Try to load directly as fallback
+        fullImage.src = fullSizeUrl;
+    };
+    
+    // Start the download
+    xhr.send();
+    */
+}
+
 $(document).on("keydown", function(e){
     if (e.keyCode == 27){ // Escape
         if ($('#photo-viewer').is(':visible')) {

+ 2 - 2
src/web/SystemAO/boot/interface_selector.html

@@ -71,7 +71,7 @@
                         var targetPath = mod.StartDir;
                         if (targetPath == ""){
                             //Redirect to desktop
-                            window.location.href = "../../desktop.system";
+                            window.location.href = "../../desktop.html";
                         }else{
                             //Redirect to module
                             window.location.href = "../../" + targetPath;
@@ -89,7 +89,7 @@
             }
             if (path == ""){
                 //Redirect to desktop
-                window.location.href = "../../desktop.system";
+                window.location.href = "../../desktop.html";
             }else{
                 //Redirect to module
                 window.location.href = "../../" + path;

+ 3 - 0
src/web/SystemAO/disk/tftp.html

@@ -41,6 +41,9 @@
     <script>
         $(".ui.dropdown").dropdown();
         $(document).ready(function(){
+            //Load current TFTP server status including port
+            initTFTPServerStatus();
+            
             //Load user list for default user selection
             $.get("../../system/users/list?noicon=true", function(data){
                 if (data.error !== undefined){

+ 7 - 3
src/web/SystemAO/system_setting/index.html

@@ -119,6 +119,9 @@
                             
                     });
 
+                }else{
+                    //No hash, load default Info page
+                    initSettingGroup("Info");
                 }
                  pageStateRestored = true;
             }
@@ -129,11 +132,12 @@
                 initMainSideMenu(function(){
                     //Menu is now loaded
                     hideToolBar();
-                     if (!pageStateRestored){
+                    if (!pageStateRestored){
                         restorePageFromHash();
-                    }else{
-                        initSettingGroup("Info");
+                        return;
                     }
+
+                    initSettingGroup("Info");
                 });
             });
  

+ 1 - 1
src/web/SystemAO/users/editUser.html

@@ -236,7 +236,7 @@
                             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);
+                            $("#tmppw").val(location.protocol + '//' + location.host + "/reset.html?acc=" + username +  "&rkey=" + data);
                             $("#tmppwui").slideDown('fast');
                         }
                     }

+ 1 - 1
src/web/SystemAO/users/userList.html

@@ -322,7 +322,7 @@
 
             function showNewUserUI(){
                 ao_module_newfw({
-                    url: "user.system",
+                    url: "user.html",
                     width: 530,
                     height: 740,
                     appicon: "SystemAO/users/img/user-white.svg",

+ 1 - 1
src/web/SystemAO/www/index_legacy.html

@@ -23,7 +23,7 @@
         <!-- Main website content goes here-->
         <br><br>
         <div class="ui container">
-            <a class="ui black right floated button" href="../login.system"><i class="key icon"></i>Login</a>
+            <a class="ui black right floated button" href="../login.html"><i class="key icon"></i>Login</a>
             <h1>My Home</h1>
             <div class="ui divider"></div>
             <div class="ui two column stackable grid">

+ 2 - 2
src/web/desktop.system → src/web/desktop.html

@@ -1094,7 +1094,7 @@
     <script>
         //Force redirection if it is on mobile
         if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
-            window.location.href = "mobile.system";
+            window.location.href = "mobile.html";
         }
     </script>
 </head>
@@ -2025,7 +2025,7 @@
                     console.log("Unable to open module " + moduleName);
                     if (data.error == "Not logged in."){
                         //Session expired
-                        window.location.href = "login.system";
+                        window.location.href = "login.html";
                     }
                 } else {
                     //Launch the given module

File diff suppressed because it is too large
+ 27 - 0
src/web/img/system/network-folder-black.ai


+ 23 - 0
src/web/img/system/network-folder-black.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="128px"
+	 height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<g id="圖層_2">
+	<polygon points="110.998,23.424 110.998,84.064 17.001,84.064 17.001,13.652 48.449,13.469 55.315,23.669 	"/>
+</g>
+<g id="圖層_3">
+	<polygon fill="#3E3A39" points="110.998,84.064 17.001,84.064 17.087,31.401 110.57,31.401 	"/>
+	<g>
+		<path fill="#FFFFFF" d="M41.777,55.843l7.216-2.331l5.772-1.998v-0.148l-5.772-1.999l-7.216-2.331v-5.402l19.648,7.548v4.515
+			l-19.648,7.548V55.843z"/>
+		<path fill="#FFFFFF" d="M63.385,67.869h20.128v3.662H63.385V67.869z"/>
+	</g>
+</g>
+<g id="圖層_4">
+	<rect x="17.001" y="103.51" fill="#443F5D" width="93.997" height="4.691"/>
+	<rect x="60.985" y="84.064" fill="#443F5D" width="6.029" height="19.445"/>
+	<path fill="#55516E" d="M72.935,110.512c0,2.221-1.8,4.02-4.021,4.02h-9.827c-2.221,0-4.021-1.799-4.021-4.02v-9.828
+		c0-2.221,1.8-4.021,4.021-4.021h9.827c2.221,0,4.021,1.801,4.021,4.021V110.512z"/>
+</g>
+</svg>

+ 0 - 0
src/web/login.system → src/web/login.html


+ 0 - 0
src/web/mobile.system → src/web/mobile.html


+ 0 - 0
src/web/reset.system → src/web/reset.html


+ 101 - 100
src/web/robots.txt

@@ -3,74 +3,74 @@
 # If you want SEO on your cloud system, remove this file from the web root.
 
 User-agent: Applebot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: baiduspider
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: Bingbot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: Discordbot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: facebookexternalhit
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: Googlebot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
@@ -79,58 +79,58 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: ia_archiver
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: LinkedInBot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: msnbot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
 User-agent: Naverbot
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
@@ -139,11 +139,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: seznambot
@@ -151,11 +151,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: Slurp
@@ -163,11 +163,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: teoma
@@ -175,11 +175,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: TelegramBot
@@ -188,11 +188,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: Twitterbot
@@ -200,11 +200,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: Yandex
@@ -212,11 +212,11 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: Yeti
@@ -224,22 +224,23 @@ Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /img/
 Disallow: /index.html
-Disallow: /login.system
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /login.html
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 
 User-agent: *
-Allow: /login.system
+Allow: /login.html
 Disallow: /SystemAO/
 Disallow: /system/
 Disallow: /index.html
-Disallow: /mobile.system
-Disallow: /desktop.system
-Disallow: /reset.system
-Disallow: /user.system
+Disallow: /mobile.html
+Disallow: /desktop.html
+Disallow: /reset.html
+Disallow: /user.html
 Disallow: /homepage/
 Disallow: /fileview/
 
+

+ 1 - 1
src/web/user.system → src/web/user.html

@@ -165,7 +165,7 @@
                                     window.location.href = "SystemAO/closeTabInsturction.html"
                                 }
                             }else{
-                                window.location.href = "/login.system";
+                                window.location.href = "/login.html";
                             }
                         });
                        

Some files were not shown because too many files changed in this diff