Переглянути джерело

Added appdata API for accessing files in web root

TC pushbot 5 4 роки тому
батько
коміт
c1893318cc

+ 130 - 0
mod/agi/agi.appdata.go

@@ -0,0 +1,130 @@
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI Appdata Access Library
+	Author: tobychui
+
+	This library allow agi script to access files located in the web root
+	*This library provide READ ONLY function*
+	You cannot write to web folder due to security reasons. If you need to read write
+	web root (which is not recommended), ask the user to mount it top web:/ manually
+*/
+
+var webRoot string = "./web" //The web folder root
+
+func (g *Gateway) AppdataLibRegister() {
+	err := g.RegisterLib("appdata", g.injectAppdataLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectAppdataLibFunctions(vm *otto.Otto, u *user.User) {
+	vm.Set("_appdata_readfile", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFile := filepath.Join(webRoot, relpath)
+		if fileExists(targetFile) && !filesystem.IsDir(targetFile) {
+			content, err := ioutil.ReadFile(targetFile)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(content))
+			return result
+		} else if filesystem.IsDir(targetFile) {
+			g.raiseError(errors.New("Cannot read from directory"))
+			return otto.FalseValue()
+
+		} else {
+			g.raiseError(errors.New("File not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	vm.Set("_appdata_listdir", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFolder := filepath.Join(webRoot, relpath)
+		if fileExists(targetFolder) && filesystem.IsDir(targetFolder) {
+			//Glob the directory for filelist
+			files, err := filepath.Glob(filepath.ToSlash(filepath.Clean(targetFolder)) + "/*")
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			results := []string{}
+			for _, file := range files {
+				rel, _ := filepath.Rel(webRoot, file)
+				rel = filepath.ToSlash(rel)
+				results = append(results, rel)
+			}
+
+			js, _ := json.Marshal(results)
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(js))
+			return result
+
+		} else {
+			g.raiseError(errors.New("Directory not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var appdata = {};
+		appdata.readFile = _appdata_readfile;
+		appdata.listDir = _appdata_listdir;
+	`)
+}

+ 2 - 1
mod/agi/agi.go

@@ -26,7 +26,7 @@ import (
 */
 
 var (
-	AgiVersion string = "1.4" //Defination of the agi runtime version. Update this when new function is added
+	AgiVersion string = "1.5" //Defination of the agi runtime version. Update this when new function is added
 )
 
 type AgiLibIntergface func(*otto.Otto, *user.User) //Define the lib loader interface for AGI Libraries
@@ -94,6 +94,7 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 	gatewayObject.FileLibRegister()
 	gatewayObject.HTTPLibRegister()
 	gatewayObject.IoTLibRegister()
+	gatewayObject.AppdataLibRegister()
 
 	return &gatewayObject, nil
 }

+ 21 - 15
mod/agi/common.go

@@ -8,27 +8,13 @@ import (
 	"log"
 	"net/http"
 	"os"
+	"path/filepath"
 	"strconv"
 	"time"
 
 	uuid "github.com/satori/go.uuid"
 )
 
-/*
-	SYSTEM COMMON FUNCTIONS
-
-	This is a system function that put those we usually use function but not belongs to
-	any module / system.
-
-	E.g. fileExists / IsDir etc
-
-*/
-
-/*
-	Basic Response Functions
-
-	Send response with ease
-*/
 //Send text response with given w and message as string
 func sendTextResponse(w http.ResponseWriter, msg string) {
 	w.Write([]byte(msg))
@@ -176,3 +162,23 @@ func newUUIDv4() string {
 	thisuuid := uuid.NewV4().String()
 	return thisuuid
 }
+
+//Check if the target path is escaping the rootpath, accept relative and absolute path
+func (g *Gateway) checkRootEscape(rootPath string, targetPath string) (bool, error) {
+	rootAbs, err := filepath.Abs(rootPath)
+	if err != nil {
+		return true, err
+	}
+
+	targetAbs, err := filepath.Abs(targetPath)
+	if err != nil {
+		return true, err
+	}
+
+	if len(targetAbs) < len(rootAbs) || targetAbs[:len(rootAbs)] != rootAbs {
+		//Potential path escape. Return true
+		return true, nil
+	}
+
+	return false, nil
+}

+ 7 - 1
web/Blog/backend/config.js

@@ -31,8 +31,14 @@ function setBlogPostStore(newpath){
     return writeDBItem("blog", "post-store", newpath);
 }
 
+//Get blog release path relative to the webroot.
+//If not defined, it will be {webroot}/blog/
 function getBlogReleasePath(){
-    return readDBItem("blog", "post-release");
+    var releasePath = readDBItem("blog", "post-release");
+    if (releasePath ==""){
+        releasePath = "blog/"
+    }
+    return 
 }
 
 function setBlogReleasePath(newpath){

+ 25 - 0
web/Blog/backend/publish.js

@@ -0,0 +1,25 @@
+/*
+    Publish.js
+
+    This script publish the blog post object to a static HTML into the webroot
+
+    Require paramter:
+    filepath //The filepath of the post object
+    webroot //The webroot of the current ArozOS user
+*/
+
+requirelib("filelib");
+includes("config.js");
+
+//Inject content into template
+function inject(template, key, value){
+    template = template.split("{{" + key + "}}").join(value);
+    return template;
+}
+
+//Main function to publish the file to webroot
+function publish(){
+    
+}
+
+publish();

+ 2 - 1
web/Blog/editor.html

@@ -87,7 +87,7 @@
 		<br><br>
 		<div class="ts container">
 			<div>
-				<h4><img class="ts mini spaced image" src="img/module_icon.png" style="margin-right: 12px;">  Blog Writer</h4>
+				<h4><a href="index.html" style="color: inherit;"><img class="ts mini spaced image" src="img/module_icon.png" style="margin-right: 12px;">  Blog Writer</h4></a>
 			</div>
 			<div class="ts divider"></div>
 			<div class="ts stackable grid">
@@ -142,6 +142,7 @@
 				editorWindow.newEditor();
 			}
 
+
 			function handleWindowResize(){
 				$(editorWindow.document).find("#suneditor_maineditor").css("width", "100%");
 				var newHeight = window.innerHeight - 300;

+ 69 - 9
web/Blog/index.html

@@ -59,15 +59,15 @@
 				color: white !important;
 			}
 
-			.blog.red{
-				background-color: #c4413d;
-				border: 1px solid #c4413d;
+			.blog.blue{
+				background-color: #4287f5;
+				border: 1px solid #4287f5;
 				color: white;
 			}
 
-			.blog.red:hover{
-				background-color: #8c302d !important;
-				border: 1px solid #8c302d !important;
+			.blog.blue:hover{
+				background-color: #336bc4 !important;
+				border: 1px solid #336bc4 !important;
 				color: white !important;
 			}
 
@@ -94,19 +94,25 @@
 				<div class="four wide column">
 					<div class="ts segment">
 						<a class="ts fluid small button blog main" href="editor.html"><i class="add icon"></i> New Post</a>
-						<button class="ts fluid small button blog green topPad"><i class="text file outline icon"></i> Browse Blog</button>
+						<button class="ts fluid small button blog green topPad"><i class="text file outline icon"></i> Update Post Renders</button>
 						<div class="ts divider"></div>
-						<button class="ts fluid small button"><i class="folder open icon"></i> Open Folder</button>
-						<button class="ts fluid small button topPad"><i class="setting icon"></i> Settings</button>
+						<button class="ts fluid small button blog" onclick="openBlog();"><i class="text file outline icon"></i> View Blog</button>
+						<button class="ts fluid small button blog topPad" onclick="openWebRoot();"><i class="folder open icon"></i> Open www</button>
+						<button class="ts fluid small button topPad" onclick="openPersonalPageTab();"><i class="setting icon"></i> Homepage Settings</button>
 					</div>
 				</div>
 			</div>
 			<br><br><br>
 		</div>
 		<script>
+			var currentWebDeployRoot = "";
+
 			//List all the post stored in the system
 			listPost();
 
+			//Get the current web root path
+			getWebRoot();
+
 			function openPost(object){
 				//Parse the file object following ArozOS file descriptor protocol
 				var filepath = $(object).attr("filepath");
@@ -119,6 +125,60 @@
 				window.location.href = "editor.html#" + fd;
 			}
 
+
+			function openWebRoot(){
+				getWebRoot(function(webroot){
+					if (webroot != ""){
+						ao_module_openPath(webroot);
+					}else{
+						alert("Web Root not set. Please go to Homepage Settings to setup your webroot before using B Blog Writer.")
+					}
+				});
+				
+			}
+
+			function publishPost(){
+				
+			}
+
+			function openBlog(){
+				checkIfWebRootEnabled(function(data){
+					if (data == true){
+						//Get the username of the current session
+						$.get("../system/desktop/user", function(userinfo){
+							//Open the page in new tab
+							window.open("../www/" + userinfo.Username + "/");
+						});
+					}else{
+						alert("Personal page not enabled");
+					}
+				})
+			}
+
+			function openPersonalPageTab(){
+				ao_module_openSetting("Network", "Personal Page");
+			}
+
+			function checkIfWebRootEnabled(callback){
+				$.get("../system/network/www/toggle", function(data){
+					callback(data);
+				});
+			}
+
+			function getWebRoot(callback = undefined){
+				$.get("../system/network/www/webRoot", function(data){
+					if (data == null || data == "" || data == undefined){
+						callback("");
+						return;
+					}
+					currentWebDeployRoot = data;
+					if (callback != undefined){
+						callback(data);
+					}
+					
+				});
+			}
+
 			function listPost(){
 				ao_module_agirun("Blog/backend/listPosts.js", {}, function(data){
 					if (data.length == 0){

+ 0 - 0
web/Notebook/filesaver.js → web/MDEditor/filesaver.js


+ 0 - 0
web/Notebook/img/notebook.png → web/MDEditor/img/notebook.png


+ 0 - 0
web/Notebook/img/notebook.psd → web/MDEditor/img/notebook.psd


+ 4 - 4
web/Notebook/init.agi → web/MDEditor/init.agi

@@ -9,14 +9,14 @@
 
 //Define the launchInfo for the module
 var moduleLaunchInfo = {
-    Name: "Notebook",
+    Name: "MDEditor",
 	Desc: "Basic Text Editor",
 	Group: "Utilities",
-	IconPath: "Notebook/img/notebook.png",
+	IconPath: "MDEditor/img/notebook.png",
 	Version: "3.0",
-	StartDir: "Notebook/notebook.html",
+	StartDir: "MDEditor/mde.html",
 	SupportFW: true,
-	LaunchFWDir: "Notebook/notebook.html",
+	LaunchFWDir: "MDEditor/mde.html",
 	SupportEmb: false,
 	InitFWSize: [1080, 580],
 	SupportedExt: [".txt", ".md", ".html",".xml",".json"]

+ 12 - 22
web/Notebook/notebook.html → web/MDEditor/mde.html

@@ -1,6 +1,6 @@
 <html>
     <head>
-        <title>Notebook</title>
+        <title>Markdown Editor</title>
         <script src="../script/jquery.min.js"></script>
         <script src="../script/ao_module.js"></script>
         <link rel="stylesheet" href="script/SimpleMDE/simplemde.min.css">
@@ -21,7 +21,7 @@
             var simplemde;          //SimpleMDE editor object
             var filepath = "";      //Current editing filepath
             var files = ao_module_loadInputFiles();
-            var originalTitle = "Notebook";
+            var originalTitle = "MDEditor";
             var lastSaveContent = ""; //Content for last saved content
             
             $(window).on("keydown",function(event) {
@@ -42,8 +42,8 @@
 
             if (files !== null && files.length > 0){
                 //Set the window name
-                ao_module_setWindowTitle("Notebook - " + files[0].filename);
-                originalTitle = "Notebook - " + files[0].filename;
+                ao_module_setWindowTitle("MDEditor - " + files[0].filename);
+                originalTitle = "MDEditor - " + files[0].filename;
                 //Check if there are more than 1 text files to be opened. If yes, open new windows.
                 if (files.length > 1){
                     for (var i = 1; i < files.length; i++){
@@ -53,11 +53,11 @@
                         }];
                         thisFilelist = encodeURIComponent(JSON.stringify(thisFilelist));
                         ao_module_newfw({
-                            url: "SystemAO/utilities/notebook.html#" + thisFilelist,
+                            url: "MDEditor/mde.html#" + thisFilelist,
                             width: 1080,
                             height: 580,
-                            appicon: "SystemAO/utilities/img/notebook.png",
-                            title: "Notebook",
+                            appicon: "MDEditor/img/notebook.png",
+                            title: "MDEditor",
                         });
                     }
                 }
@@ -119,6 +119,9 @@
                     
                     //Update the current editing filepath
                     filepath = thisFilepath;
+
+                    ao_module_setWindowTitle("MDEditor - " + thisFilename);
+                    originalTitle = "MDEditor - " + thisFilename;
                 }
 
                 saveText();
@@ -128,12 +131,12 @@
                 if (filepath == ""){
                     //This is a new file. Ask for save directory.
                     ao_module_openFileSelector(handleNewFileSave, "user:/Desktop", "new",false, {
-                        defaultName: "Untitled.txt"
+                        defaultName: "Untitled.md"
                     });
                     return;
                 }
                 var newcontent = simplemde.value();
-                ao_module_agirun("Notebook/filesaver.js", {
+                ao_module_agirun("MDEditor/filesaver.js", {
                     filepath: filepath, 
                     content: newcontent
                 }, function(data){
@@ -155,19 +158,6 @@
                     alert("Save File Failed!")
                 });
 
-
-                /*
-                $.ajax({
-                    url: "../../system/utils/notebook/save",
-                    data: {filepath: filepath, content: newcontent},
-                    success: function(data){
-                        console.log(data);
-                        if (data.error !== undefined){
-                            alert(data.error);
-                        }
-                    }
-                });
-                */
             }
 
 

+ 0 - 0
web/Notebook/script/SimpleMDE/simplemde.min.css → web/MDEditor/script/SimpleMDE/simplemde.min.css


+ 0 - 0
web/Notebook/script/SimpleMDE/simplemde.min.js → web/MDEditor/script/SimpleMDE/simplemde.min.js


+ 30 - 3
web/SystemAO/system_setting/index.html

@@ -68,11 +68,35 @@
           <script>
               var currentSettingModuleList = [];
               var loadViaSystemSetting = true; //Check for this parameter to see if launching in Setting Interface
+              var loadToPage = undefined; //Load setting page directly to the given tab, passed in via window hash
+
+              if (window.location.hash.length > 0){
+                var hashObject = window.location.hash.substr(1);
+                hashObject = JSON.parse(decodeURIComponent(hashObject));
+                if (typeof(hashObject.group) != 'undefined' && typeof(hashObject.name) != 'undefined'){
+                    loadToPage = hashObject;
+                }
+              }
 
                 //Initiation Functions
                 initMainSideMenu();
                 hideToolBar();
-                initSettingGroup("Info");
+                
+                if (loadToPage == undefined){
+                    initSettingGroup("Info");
+                }else{
+                    initSettingGroup(loadToPage.group, function(settingList){
+                        let reversedList = JSON.parse(JSON.stringify(settingList)).reverse();
+                        for (var i = 0; i < settingList.length; i++){
+                            var settingTab = settingList[i];
+                            if (settingTab.Name == loadToPage.name){
+                                //This is the tab we are looking for
+                                initSettingModules(i - 1);
+                            }
+                        }
+                           
+                    });
+                }
                 
                 function initMainSideMenu(){
                     $.get("../../system/setting/list",function(data){
@@ -83,10 +107,11 @@
                             var toolTip = data[i].Desc;
                             $("#mainSideMenu").append(`<a class="item" group="${groupUUID}" title="${toolTip}" style="padding:4px;" onclick="menuClicked(this);"><img class="ui middle aligned mini image" src="${iconPath}" style="padding: 2px;"></img> ${settingGroupName}</a>`);
                         }
+
                     });
                 }
 
-                function initSettingGroup(group){
+                function initSettingGroup(group, callback=undefined){
                     $.get("../../system/setting/list?listGroup=" + group,function(data){
                         if (data.error !== undefined){
                             console.log(data.error);
@@ -94,7 +119,9 @@
                             currentSettingModuleList = data;
                             initSettingModuleTabs();
                             initSettingModules(0);
-                            
+                            if (callback != undefined){
+                                callback(data);
+                            }
                         }
                     });
                 }

+ 2 - 0
web/UnitTest/appdata.txt

@@ -0,0 +1,2 @@
+Hello World!
+This is an appdata file that should be accessiable via appdata API call

+ 32 - 0
web/UnitTest/backend/appdata.listDir.js

@@ -0,0 +1,32 @@
+/*
+    appdata.readFile.js
+
+    This script test the appdata read file function.
+    This should be able to read file 
+*/
+
+//Require the appdata library
+var succ = requirelib("appdata");
+
+function main(){
+    //List all files within the UnitTest backend example library
+    var backendExamples = appdata.listDir("UnitTest/backend/");
+
+    //Check if there are any error for reading the file
+    if (backendExamples == false){
+        sendJSONResp(JSON.stringify({
+            error: "Unable to list backend example library"
+        }));
+    }else{
+        //Success. Return the file list of the folder
+        sendJSONResp(backendExamples);
+    }
+}
+
+if (!succ){
+    //Library include failed.
+    sendResp("Include Appdata lib failed. Is your ArozOS version too old?")
+}else{
+    //Library include succeed. Start reading from webroot
+    main();
+}

+ 32 - 0
web/UnitTest/backend/appdata.readFile.js

@@ -0,0 +1,32 @@
+/*
+    appdata.readFile.js
+
+    This script test the appdata read file function.
+    This should be able to read file 
+*/
+
+//Require the appdata library
+var succ = requirelib("appdata");
+
+function main(){
+    //Get a file from the UnitTest WebApp. This path is relative from the web root
+    var webAppDataFileContent = appdata.readFile("UnitTest/appdata.txt");
+
+    //Check if there are any error for reading the file
+    if (webAppDataFileContent == false){
+        sendJSONResp(JSON.stringify({
+            error: "Unable to get appdata from app folder"
+        }));
+    }else{
+        //Success. Return the content of the file
+        sendResp(webAppDataFileContent)
+    }
+}
+
+if (!succ){
+    //Library include failed.
+    sendResp("Include Appdata lib failed. Is your ArozOS version too old?")
+}else{
+    //Library include succeed. Start reading from webroot
+    main();
+}

+ 3 - 7
web/UnitTest/list.agi

@@ -3,10 +3,6 @@
 
 */
 
-requirelib("filelib");
-var tests = filelib.glob("web:/UnitTest/backend/*.js")
-var results = [];
-for (var i = 0; i < tests.length; i++){
-	results.push(tests[i].replace("web:/",""))
-}
-sendJSONResp(JSON.stringify(results));
+requirelib("appdata");
+var tests = appdata.listDir("UnitTest/backend/");
+sendJSONResp(tests);

+ 24 - 0
web/script/ao_module.js

@@ -248,6 +248,30 @@ function ao_module_openPath(path, filename=undefined){
    
 }
 
+//Open a particular tab using System Setting module. Require 
+//1) Setting Group
+//2) Setting Name
+function ao_module_openSetting(group, name){
+    var requestObject = {
+        group: group,
+        name: name
+    }
+
+    requestObject = encodeURIComponent(JSON.stringify(requestObject));
+    var openURL = "SystemAO/system_setting/index.html#" + requestObject;
+    if (ao_module_virtualDesktop){
+        ao_module_newfw({
+            url: openURL,
+            width: 1080,
+            height: 580,
+            appicon: "SystemAO/system_setting/img/small_icon.png",
+            title: "System Setting"
+        });
+    }else{
+        window.open(ao_root + openURL)
+    }
+}
+
 
 /*
     ao_module_newfw(launchConfig) => Create a new floatWindow object from the given paramters