Przeglądaj źródła

Merge branch 'lambda-20221106' of tmp/arozos into master

LGTM
TC 2 lat temu
rodzic
commit
9e92d5300b

+ 14 - 0
agi.go

@@ -5,6 +5,7 @@ import (
 
 	agi "imuslab.com/arozos/mod/agi"
 	"imuslab.com/arozos/mod/common"
+	prout "imuslab.com/arozos/mod/prouter"
 )
 
 var (
@@ -74,5 +75,18 @@ func AGIInit() {
 
 	http.HandleFunc("/api/ajgi/exec", gw.HandleAgiExecutionRequestWithToken)
 
+	// external AGI related function
+	externalAGIRouter := prout.NewModuleRouter(prout.RouterOption{
+		ModuleName:  "ARZ Serverless",
+		AdminOnly:   false,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			errorHandlePermissionDenied(w, r)
+		},
+	})
+	externalAGIRouter.HandleFunc("/api/ajgi/listExt", gw.ListExternalEndpoint)
+	externalAGIRouter.HandleFunc("/api/ajgi/addExt", gw.AddExternalEndPoint)
+	externalAGIRouter.HandleFunc("/api/ajgi/rmExt", gw.RemoveExternalEndPoint)
+
 	AGIGateway = gw
 }

+ 1 - 0
go.mod

@@ -50,6 +50,7 @@ require (
 	github.com/go-git/go-billy/v5 v5.3.1 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
+	github.com/google/uuid v1.3.0 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gorilla/securecookie v1.1.1 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect

+ 1 - 0
go.sum

@@ -260,6 +260,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
 github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=

+ 2 - 0
main.router.go

@@ -69,6 +69,8 @@ func mrouter(h http.Handler) http.Handler {
 			WebDavHandler.HandleRequest(w, r)
 		} else if len(r.URL.Path) >= len("/share") && r.URL.Path[:6] == "/share" {
 			shareManager.HandleShareAccess(w, r)
+		} else if len(r.URL.Path) >= len("/api/remote") && r.URL.Path[:11] == "/api/remote" {
+			AGIGateway.ExtAPIHandler(w, r)
 		} else if r.URL.Path == "/" && authAgent.CheckAuth(r) {
 			//Use logged in and request the index. Serve the user's interface module
 			w.Header().Set("Cache-Control", "no-cache, no-store, no-transform, must-revalidate, private, max-age=0")

+ 217 - 0
mod/agi/external.agi.go

@@ -0,0 +1,217 @@
+package agi
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/google/uuid"
+	"imuslab.com/arozos/mod/common"
+)
+
+type endpointFormat struct {
+	Username string `json:"username"`
+	Path     string `json:"path"`
+}
+
+//Handle request from EXTERNAL RESTFUL API
+func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
+	// get db
+	sysdb := g.Option.UserHandler.GetDatabase()
+
+	if !sysdb.TableExists("external_agi") {
+		common.SendErrorResponse(w, "Bad Request, invaild database")
+		return
+	}
+
+	// get the request URI from the r.URL
+	requestURI := filepath.ToSlash(filepath.Clean(r.URL.Path))
+	subpathElements := strings.Split(requestURI[1:], "/")
+
+	// check if it contains only two part, [rexec uuid]
+	if len(subpathElements) != 3 {
+		common.SendErrorResponse(w, "Bad Request, invaild request sent")
+		return
+	}
+
+	// check if UUID exists in the database
+	// get the info from the database
+	data, isExist := g.checkIfExternalEndpointExist(subpathElements[2])
+	if !isExist {
+		common.SendErrorResponse(w, "Bad Request, invaild UUID entered")
+		return
+	}
+
+	usernameFromDb := data.Username
+	pathFromDb := data.Path
+
+	// get the userinfo and the realPath
+	userInfo, err := g.Option.UserHandler.GetUserInfoFromUsername(usernameFromDb)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad username")
+		return
+	}
+	_, realPath, err := virtualPathToRealPath(pathFromDb, userInfo)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad filepath")
+		return
+	}
+
+	// execute!
+	start := time.Now()
+	//g.ExecuteAGIScript(scriptContent, "", "", w, r, userInfo)
+	result, err := g.ExecuteAGIScriptAsUser(realPath, userInfo)
+	duration := time.Since(start)
+
+	if err != nil {
+		common.SendErrorResponse(w, err.Error())
+		return
+	}
+	common.SendTextResponse(w, result)
+
+	log.Println("[Remote AGI] IP:", r.RemoteAddr, " executed the script ", pathFromDb, "(", realPath, ")", " on behalf of", userInfo.Username, "with total duration: ", duration)
+
+}
+
+func (g *Gateway) AddExternalEndPoint(w http.ResponseWriter, r *http.Request) {
+	userInfo, err := g.Option.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad user!")
+		return
+	}
+	// get db
+	sysdb := g.Option.UserHandler.GetDatabase()
+	if !sysdb.TableExists("external_agi") {
+		sysdb.NewTable("external_agi")
+	}
+	var dat endpointFormat
+
+	// uuid: [path, id]
+	path, err := common.Mv(r, "path", false)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad parameter")
+		return
+	}
+
+	// put the data in then marshal
+	id := uuid.New().String()
+
+	dat.Path = path
+	dat.Username = userInfo.Username
+
+	jsonStr, err := json.Marshal(dat)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad JSON")
+		return
+	}
+	sysdb.Write("external_agi", id, string(jsonStr))
+
+	// send the uuid to frontend
+	common.SendJSONResponse(w, "\""+id+"\"")
+}
+
+func (g *Gateway) RemoveExternalEndPoint(w http.ResponseWriter, r *http.Request) {
+	userInfo, err := g.Option.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad User")
+		return
+	}
+
+	// get db
+	sysdb := g.Option.UserHandler.GetDatabase()
+	if !sysdb.TableExists("external_agi") {
+		sysdb.NewTable("external_agi")
+	}
+	// get path
+	uuid, err := common.Mv(r, "uuid", false)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad parameter")
+		return
+	}
+
+	// check if endpoint is here
+	data, isExist := g.checkIfExternalEndpointExist(uuid)
+	if !isExist {
+		common.SendErrorResponse(w, "UUID does not exists in the database!")
+		return
+	}
+
+	// make sure user cant see other's endpoint
+	if data.Username != userInfo.Username {
+		common.SendErrorResponse(w, "Bad Request, you have no permission to access this UUID entry!")
+		return
+	}
+
+	// delete record
+	sysdb.Delete("external_agi", uuid)
+
+	common.SendOK(w)
+}
+
+func (g *Gateway) ListExternalEndpoint(w http.ResponseWriter, r *http.Request) {
+	userInfo, err := g.Option.UserHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad User")
+		return
+	}
+
+	// get db
+	sysdb := g.Option.UserHandler.GetDatabase()
+	if !sysdb.TableExists("external_agi") {
+		sysdb.NewTable("external_agi")
+	}
+
+	// declare variable for return
+	dataFromDB := make(map[string]endpointFormat)
+
+	// O(n) method to do the lookup
+	entries, err := sysdb.ListTable("external_agi")
+	if err != nil {
+		common.SendErrorResponse(w, "Bad table")
+		return
+	}
+	for _, keypairs := range entries {
+		//Decode the string
+		var dataFromResult endpointFormat
+		result := ""
+		uuid := string(keypairs[0])
+		json.Unmarshal(keypairs[1], &result)
+		//fmt.Println(result)
+		json.Unmarshal([]byte(result), &dataFromResult)
+		if dataFromResult.Username == userInfo.Username {
+			dataFromDB[uuid] = dataFromResult
+		}
+	}
+
+	// marhsal and return
+	returnJson, err := json.Marshal(dataFromDB)
+	if err != nil {
+		common.SendErrorResponse(w, "Bad JSON")
+		return
+	}
+	sendJSONResponse(w, string(returnJson))
+}
+
+func (g *Gateway) checkIfExternalEndpointExist(uuid string) (endpointFormat, bool) {
+	// get db
+	sysdb := g.Option.UserHandler.GetDatabase()
+	if !sysdb.TableExists("external_agi") {
+		sysdb.NewTable("external_agi")
+	}
+	var dat endpointFormat
+
+	// check if key exist
+	if !sysdb.KeyExists("external_agi", uuid) {
+		return dat, false
+	}
+
+	// if yes then return the value
+	jsonData := ""
+	sysdb.Read("external_agi", uuid, &jsonData)
+	json.Unmarshal([]byte(jsonData), &dat)
+
+	return dat, true
+}

BIN
web/ARZ Serverless/img/function_icon.png


BIN
web/ARZ Serverless/img/function_icon.psd


BIN
web/ARZ Serverless/img/small_icon.png


BIN
web/ARZ Serverless/img/small_icon.psd


+ 200 - 0
web/ARZ Serverless/index.html

@@ -0,0 +1,200 @@
+<!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">
+        <meta name="theme-color" content="#4b75ff">
+        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
+        <script src="../script/jquery.min.js"></script>
+        <script src="../script/ao_module.js"></script>
+        <script src="../script/semantic/semantic.min.js"></script>
+        <script type="application/javascript" src="../script/clipboard.min.js"></script>
+        <title>ARZ Serverless</title>
+        <style>
+            body{
+                background-color:white;
+            }
+                    /* Tooltip container */
+                   .tooltip {
+                   position: relative;
+                   display: inline-block;
+                   border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
+                   }
+       
+                   /* Tooltip text */
+                   .tooltip .tooltiptext {
+                   visibility: hidden;
+                   width: 120px;
+                   background-color: #555;
+                   color: #fff;
+                   text-align: center;
+                   padding: 5px 0;
+                   border-radius: 6px;
+       
+                   /* Position the tooltip text */
+                   position: absolute;
+                   z-index: 1;
+                   bottom: 125%;
+                   left: 50%;
+                   margin-left: -60px;
+       
+                   /* Fade in tooltip */
+                   opacity: 0;
+                   transition: opacity 0.3s;
+                   }
+       
+                   /* Tooltip arrow */
+                   .tooltip .tooltiptext::after {
+                   content: "";
+                   position: absolute;
+                   top: 100%;
+                   left: 50%;
+                   margin-left: -5px;
+                   border-width: 5px;
+                   border-style: solid;
+                   border-color: #555 transparent transparent transparent;
+                   }
+       
+               </style>
+    </head>
+    <body>
+        <div class="ui container" style="height: 100% !important;">
+            <div>
+                <br>
+                <h1 class="ui header">
+                    <span id="normalStatus">ARZ Serverless</span>
+                    <div class="sub header">
+                       You can run our own AGI script without login by setting up the configuration below
+                    </div>
+                </h1>
+            </div>
+            <div class="ui divider"></div>
+            <div id="deleteSuceed" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i>Deleted</div>
+            <div>
+                <table class="ui celled table">
+                    <thead>
+                        <tr>
+                            <th>UUID (access token)</th>
+                            <th>AGI Path</th>
+                            <th>Action</th>
+                        </tr>
+                    </thead>
+                    <tbody id="records">
+                    
+                    </tbody>
+                </table>
+                <div style="width: 100%" align="center">
+                    <div class="ui breadcrumb" id="pageIndexs">
+                    </div>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <div id="updateSuceed" style="display:none;" class="ui green inverted segment"><i class="checkmark icon"></i>Added</div>
+            <h4>Select AGI script Location</h4>
+            <div class="ui action fluid input">
+                <input id="agiPath" type="text" placeholder="Select Location" readonly="true">
+                <button class="ui black button" onclick="openfileselector();"><i class="folder open icon"></i> Open</button>
+            </div>
+            <br>
+            <button class="ui positive button" onclick="add();">Add</button>
+            <br><br>
+        </div>
+        <script>
+            $.getJSON( "/api/ajgi/listExt", function( data ) {
+                $.each( data, function( key, val ) {
+                    appendTable(key, val.path);
+                });
+                if(Object.keys(data).length == 0) {
+                    $("#records").append(`<tr id="zeroRec"><td>0 record returned.</td></tr>`);
+                }
+            });
+
+            function openfileselector(){
+                ao_module_openFileSelector(fileLoader, "user:/", "file",false, {filter:["agi"]});
+            }
+
+
+            function fileLoader(filedata){
+                for (var i=0; i < filedata.length; i++){
+                    var filename = filedata[i].filename;
+                    var filepath = filedata[i].filepath;
+                    $("#agiPath").val(filepath);
+                }
+            }
+
+            function add() {
+                var path = $("#agiPath").val();
+                $.getJSON( "/api/ajgi/addExt?path=" + path, function( data ) {
+                    if(data.error == undefined) {
+                        $("#updateSuceed").slideDown("fast").delay(3000).slideUp("fast");
+                        appendTable(data, path);
+                    }else{
+                        alert(data.error);
+                    }
+                });
+            }
+
+            function delRecord(element) {
+                $.getJSON( "/api/ajgi/rmExt?uuid=" + $(element).attr("uuid"), function( data ) {
+                    if(data == "OK") {
+                        $("#deleteSuceed").slideDown("fast").delay(3000).slideUp("fast");
+                    }else{
+                        alert(data.error);
+                    }
+                });
+                $(element).parent().parent().remove().slideUp("fast");
+                if($("#records").html().trim() == '') {
+                    $("#records").append(`<tr id="zeroRec"><td>0 record returned.</td></tr>`);
+                }
+            }
+
+            
+            var tokenAccessPath = location.protocol + "//" + window.location.host + "/api/remote/";
+            new ClipboardJS('.copyURL', {
+                text: function(trigger) {
+                        var token = $(trigger).attr("token");
+                        var url = tokenAccessPath + token;
+                        console.log( $(trigger).find(".tooltiptext"));
+                        $(trigger).find(".tooltiptext").css({
+                            "visibility": "visible",
+                            "opacity": 1,
+                        });
+                        setTimeout(function(){
+                            $(trigger).find(".tooltiptext").css({
+                                "visibility": "hidden",
+                                "opacity": 0,
+                            });
+                        }, 3000);
+                    return url;
+                }
+            });
+
+            function generateClipboardText(uuid) {
+                return `
+                    <div>
+                        <div class="content" style="font-family: monospace;">
+                           ${uuid} <a style="margin-left: 12px; font-family: Arial;" token="${uuid}" class="copyURL tooltip">
+                                <i class="copy icon"></i>  Copy
+                                <span class="tooltiptext"><i class="checkmark icon"></i> Copied</span>
+                           </a> 
+                        </div>
+                    </div>
+                `;
+            }
+
+            function appendTable(uuid, path) {
+                $("#zeroRec").remove().slideUp("fast");
+                $("#records").append(`<tr>
+                            <td>` + generateClipboardText(uuid) +`</td>
+                            <td>` + path + `</td>
+                            <td>
+                                <button class="ui icon negative button" uuid="` + uuid + `" onclick="delRecord(this)">
+                                <i class="close icon"></i>
+                                </button>
+                            </td>
+                        </tr>`);
+            }
+        </script>
+    </body>
+</html>

+ 16 - 0
web/ARZ Serverless/init.agi

@@ -0,0 +1,16 @@
+/*
+	Arozos External AGI Services
+*/
+
+
+//Define the launchInfo for the module
+var moduleLaunchInfo = {
+    Name: "ARZ Serverless",
+	Group: "Utilities",
+	IconPath: "ARZ Serverless/img/small_icon.png",
+	Version: "0.1",
+	StartDir: "ARZ Serverless/index.html"
+}
+
+//Register the module
+registerModule(JSON.stringify(moduleLaunchInfo));