浏览代码

auto update script executed

Toby Chui 1 年之前
父节点
当前提交
a29f27ef71

+ 52 - 17
api.go

@@ -1,9 +1,11 @@
 package main
 
 import (
+	"encoding/json"
 	"net/http"
 
 	"imuslab.com/arozos/ReverseProxy/mod/auth"
+	"imuslab.com/arozos/ReverseProxy/mod/utils"
 )
 
 /*
@@ -14,9 +16,10 @@ import (
 */
 
 func initAPIs() {
+	requireAuth := !(*noauth || handler.IsUsingExternalPermissionManager())
 	authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
 		AuthAgent:   authAgent,
-		RequireAuth: !(*noauth || handler.IsUsingExternalPermissionManager()),
+		RequireAuth: requireAuth,
 		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
 			http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
 		},
@@ -24,26 +27,58 @@ func initAPIs() {
 
 	//Register the standard web services urls
 	fs := http.FileServer(http.Dir("./web"))
-	http.Handle("/", fs)
+	if requireAuth {
+		//Add a layer of middleware for auth control
+		authHandler := AuthFsHandler(fs)
+		http.Handle("/", authHandler)
+	} else {
+		http.Handle("/", fs)
+	}
+
+	//Auth APIs
+	authRouter.HandleFunc("/api/auth/login", authAgent.HandleLogin)
+	authRouter.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
+	authRouter.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
+		if requireAuth {
+			authAgent.CheckLogin(w, r)
+		} else {
+			utils.SendJSONResponse(w, "true")
+		}
+	})
+	authRouter.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
+		username, err := authAgent.GetUserName(w, r)
+		if err != nil {
+			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
+			return
+		}
+
+		js, _ := json.Marshal(username)
+		utils.SendJSONResponse(w, string(js))
+	})
+	http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
+		uc := authAgent.GetUserCounts()
+		js, _ := json.Marshal(uc)
+		utils.SendJSONResponse(w, string(js))
+	})
 
 	//Reverse proxy
-	authRouter.HandleFunc("/enable", ReverseProxyHandleOnOff)
-	authRouter.HandleFunc("/add", ReverseProxyHandleAddEndpoint)
-	authRouter.HandleFunc("/status", ReverseProxyStatus)
-	authRouter.HandleFunc("/list", ReverseProxyList)
-	authRouter.HandleFunc("/del", DeleteProxyEndpoint)
-	authRouter.HandleFunc("/setIncoming", HandleIncomingPortSet)
-	authRouter.HandleFunc("/useHttpsRedirect", HandleUpdateHttpsRedirect)
+	authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
+	authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
+	authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
+	authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
+	authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
+	authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
+	authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
 
 	//TLS / SSL config
-	authRouter.HandleFunc("/cert/tls", handleToggleTLSProxy)
-	authRouter.HandleFunc("/cert/upload", handleCertUpload)
-	authRouter.HandleFunc("/cert/list", handleListCertificate)
-	authRouter.HandleFunc("/cert/checkDefault", handleDefaultCertCheck)
-	authRouter.HandleFunc("/cert/delete", handleCertRemove)
+	authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
+	authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
+	authRouter.HandleFunc("/api/cert/list", handleListCertificate)
+	authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
+	authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
 
 	//Redirection config
-	authRouter.HandleFunc("/redirect/list", handleListRedirectionRules)
-	authRouter.HandleFunc("/redirect/add", handleAddRedirectionRule)
-	authRouter.HandleFunc("/redirect/delete", handleDeleteRedirectionRule)
+	authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
+	authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
+	authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
 }

+ 1 - 1
main.go

@@ -22,7 +22,7 @@ var noauth = flag.Bool("noauth", false, "Disable authentication for management i
 var showver = flag.Bool("version", false, "Show version of this server")
 
 var (
-	name    = "Yutoriprox"
+	name    = "Zoraxy"
 	version = "2.0"
 
 	handler        *aroz.ArozHandler

+ 1 - 1
mod/dynamicproxy/dpcore/dpcore.go

@@ -297,7 +297,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request) erro
 
 	//Custom header rewriter functions
 	if res.Header.Get("Location") != "" {
-		//Custom redirection fto this rproxy relative path
+		//Custom redirection to this rproxy relative path
 		fmt.Println(res.Header.Get("Location"))
 		res.Header.Set("Location", filepath.ToSlash(filepath.Join(p.Prepender, res.Header.Get("Location"))))
 	}

+ 1 - 2
mod/dynamicproxy/dynamicproxy.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"crypto/tls"
 	"errors"
-	"fmt"
 	"log"
 	"net"
 	"net/http"
@@ -257,7 +256,7 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st
 Remove routing from RP
 */
 func (router *Router) RemoveProxy(ptype string, key string) error {
-	fmt.Println(ptype, key)
+	//fmt.Println(ptype, key)
 	if ptype == "vdir" {
 		router.ProxyEndpoints.Delete(key)
 		return nil

+ 10 - 0
reverseproxy.go

@@ -5,6 +5,7 @@ import (
 	"log"
 	"net/http"
 	"path/filepath"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -209,6 +210,10 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
 			return true
 		})
 
+		sort.Slice(results, func(i, j int) bool {
+			return results[i].Domain < results[j].Domain
+		})
+
 		js, _ := json.Marshal(results)
 		utils.SendJSONResponse(w, string(js))
 	} else if eptype == "subd" {
@@ -217,6 +222,11 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
 			results = append(results, value.(*dynamicproxy.SubdomainEndpoint))
 			return true
 		})
+
+		sort.Slice(results, func(i, j int) bool {
+			return results[i].MatchingDomain < results[j].MatchingDomain
+		})
+
 		js, _ := json.Marshal(results)
 		utils.SendJSONResponse(w, string(js))
 	} else if eptype == "root" {

+ 32 - 0
router.go

@@ -0,0 +1,32 @@
+package main
+
+import (
+	"net/http"
+	"strings"
+)
+
+/*
+	router.go
+
+	This script holds the static resources router
+	for the reverse proxy service
+*/
+
+func AuthFsHandler(handler http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Allow access to /script/*, /img/pubic/* and /login.html without authentication
+		if strings.HasPrefix(r.URL.Path, "/script/") || strings.HasPrefix(r.URL.Path, "/img/public/") || r.URL.Path == "/login.html" || r.URL.Path == "/favicon.png" {
+			handler.ServeHTTP(w, r)
+			return
+		}
+
+		// check authentication
+		if !authAgent.CheckAuth(r) {
+			http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
+			return
+		}
+
+		//Authenticated
+		handler.ServeHTTP(w, r)
+	})
+}

+ 9 - 9
web/components/cert.html

@@ -47,11 +47,11 @@
 </div>
 <br>
 <div>
-    <table class="ui very basic celled table">
+    <table class="ui sortable unstackable celled table">
         <thead>
           <tr><th>Domain</th>
           <th>Last Update</th>
-          <th>Remove</th>
+          <th class="no-sort">Remove</th>
         </tr></thead>
     <tbody id="certifiedDomainList">
 
@@ -73,7 +73,7 @@
     function deleteCertificate(domain){
         if (confirm("Confirm delete certificate for " + domain + " ?")){
             $.ajax({
-                url: "/cert/delete",
+                url: "/api/cert/delete",
                 method: "POST",
                 data: {domain: domain},
                 success: function(data){
@@ -91,7 +91,7 @@
     //List the stored certificates
     function initManagedDomainCertificateList(){
         $("#certifiedDomainList").html("");
-        $.get("/cert/list?date=true", function(data){
+        $.get("/api/cert/list?date=true", function(data){
             if (data.error != undefined){
                 alert(data.error);
             }else{
@@ -138,7 +138,7 @@
             privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey');
 
             const publicKeyRequest = new XMLHttpRequest();
-            publicKeyRequest.open('POST', '/cert/upload?ktype=pub&domain=' + domain);
+            publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain);
             publicKeyRequest.onreadystatechange = function() {
             if (publicKeyRequest.readyState === XMLHttpRequest.DONE) {
                 if (publicKeyRequest.status !== 200) {
@@ -154,7 +154,7 @@
             publicKeyRequest.send(publicKeyForm);
 
             const privateKeyRequest = new XMLHttpRequest();
-            privateKeyRequest.open('POST', '/cert/upload?ktype=pri&domain=' + domain);
+            privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain);
             privateKeyRequest.onreadystatechange = function() {
             if (privateKeyRequest.readyState === XMLHttpRequest.DONE) {
                 if (privateKeyRequest.status !== 200) {
@@ -188,7 +188,7 @@
 
     //Check if the default keypairs exists
     function initDefaultKeypairCheck(){
-        $.get("/cert/checkDefault", function(data){
+        $.get("/api/cert/checkDefault", function(data){
             let tick = `<i class="ui green checkmark icon"></i>`;
             let cross = `<i class="ui red times icon"></i>`;
             $("#pubkeyExists").html(data.DefaultPubExists?tick:cross);
@@ -211,7 +211,7 @@
             formData.append('file', input.files[0]);
 
             // send form data to server
-            fetch('/cert/upload?ktype=pri', {
+            fetch('/api/cert/upload?ktype=pri', {
                 method: 'POST',
                 body: formData
             })
@@ -251,7 +251,7 @@
             formData.append('file', input.files[0]);
 
             // send form data to server
-            fetch('/cert/upload?ktype=pub', {
+            fetch('/api/cert/upload?ktype=pub', {
                 method: 'POST',
                 body: formData
             })

+ 14 - 8
web/components/redirection.html

@@ -1,15 +1,15 @@
 <h3><i class="level up alternate icon"></i> Redirection Rules</h3>
 <p>Add exception case for redirecting any matching URLs</p>
 <div class="ui basic segment">
-    <table class="ui very basic celled table">
+    <table class="ui sortable unstackable celled table">
         <thead>
             <tr>
                 <th>Redirection URL</th>
-                <th></th>
+                <th class="no-sort"></th>
                 <th>Destination URL</th>
-                <th>Copy Pathname</th>
-                <th>Status Code</th>
-                <th>Remove</th>
+                <th class="no-sort">Copy Pathname</th>
+                <th class="no-sort">Status Code</th>
+                <th class="no-sort">Remove</th>
             </tr>
         </thead>
         <tbody id="redirectionRuleList">
@@ -73,6 +73,12 @@
 <script>
     $(".checkbox").checkbox();
 
+    function resetForm() {
+      document.getElementById("rurl").value = "";
+      document.getElementsByName("destination-url")[0].value = "";
+      document.getElementsByName("forward-childpath")[0].checked = true;
+    }
+
     function addRules(){
         let redirectUrl = document.querySelector('input[name="redirection-url"]').value;
         let destUrl = document.querySelector('input[name="destination-url"]').value;
@@ -80,7 +86,7 @@
         let redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
 
         $.ajax({
-            url: "/redirect/add", 
+            url: "/api/redirect/add", 
             method: "POST",
             data: {
                 redirectUrl: redirectUrl,
@@ -104,7 +110,7 @@
       targetURL = JSON.parse(decodeURIComponent(targetURL));
       if (confirm("Confirm remove redirection from " + targetURL + " ?")){
         $.ajax({
-              url: "/redirect/delete", 
+              url: "/api/redirect/delete", 
               method: "POST",
               data: {
                   redirectUrl: targetURL,
@@ -123,7 +129,7 @@
 
     function initRedirectionRuleList(){
         $("#redirectionRuleList").html("");
-        $.get("/redirect/list", function(data){
+        $.get("/api/redirect/list", function(data){
             data.forEach(function(entry){
                 $("#redirectionRuleList").append(`<tr>
                     <td>${entry.RedirectURL} </td>

+ 1 - 1
web/components/rproot.html

@@ -22,7 +22,7 @@
 <script>
      initRootInfo();
     function initRootInfo(){
-        $.get("list?type=root", function(data){
+        $.get("/api/proxy/list?type=root", function(data){
             if (data == null){
 
             }else{

+ 1 - 1
web/components/rules.html

@@ -64,7 +64,7 @@
 
         //Create the endpoint by calling add
         $.ajax({
-            url: "./add",
+            url: "/api/proxy/add",
             data: {type: type, rootname: rootname, tls: useTLS, ep: proxyDomain},
             success: function(data){
                 if (data.error != undefined){

+ 8 - 8
web/components/status.html

@@ -35,7 +35,7 @@
 <script>
     //Get the latest server status from proxy server
     function initRPStaste(){
-        $.get("status", function(data){
+        $.get("/api/proxy/status", function(data){
             if (data.Running == true){
                 $("#startbtn").addClass("disabled");
                 $("#stopbtn").removeClass("disabled");
@@ -55,7 +55,7 @@
 
     //Start and stop service button
     function startService(){
-        $.post("enable", {enable: true}, function(data){
+        $.post("/api/proxy/enable", {enable: true}, function(data){
             if (data.error != undefined){
                 errmsg(data.error);
             }
@@ -64,7 +64,7 @@
     }   
 
     function stopService(){
-        $.post("enable", {enable: false}, function(data){
+        $.post("/api/proxy/enable", {enable: false}, function(data){
             if (data.error != undefined){
                 errmsg(data.error);
             }
@@ -79,7 +79,7 @@
             return;
         }
 
-        $.post("setIncoming", {incoming: newPortValue}, function(data){
+        $.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){
             if (data.error != undefined){
                 errmsg(data.error);
             }
@@ -89,7 +89,7 @@
     }
 
     function initHTTPtoHTTPSRedirectSetting(){
-        $.get("/useHttpsRedirect", function(data){
+        $.get("/api/proxy/useHttpsRedirect", function(data){
             if (data == true){
                 $("#redirect").checkbox("set checked");
             }
@@ -98,7 +98,7 @@
             $("#redirect").find("input").on("change", function(){
                 let thisValue = $("#redirect").checkbox("is checked");
                     $.ajax({
-                        url: "/useHttpsRedirect",
+                        url: "/api/proxy/useHttpsRedirect",
                         data: {set: thisValue},
                         success: function(data){
                             if (data.error != undefined){
@@ -118,7 +118,7 @@
     initHTTPtoHTTPSRedirectSetting();
 
     function initTlsSetting(){
-        $.get("/cert/tls", function(data){
+        $.get("/api/cert/tls", function(data){
             if (data == true){
                 $("#tls").checkbox("set checked");
             }
@@ -127,7 +127,7 @@
             $("#tls").find("input").on("change", function(){
                 let thisValue = $("#tls").checkbox("is checked");
                 $.ajax({
-                    url: "/cert/tls",
+                    url: "/api/cert/tls",
                     data: {set: thisValue},
                     success: function(data){
                         if (data.error != undefined){

+ 3 - 3
web/components/subd.html

@@ -1,9 +1,9 @@
-<table class="ui celled unstackable compact table">
+<table class="ui celled sortable unstackable compact table">
     <thead>
         <tr>
             <th>Matching Domain</th>
             <th>Proxy To</th>
-            <th>Remove</th>
+            <th class="no-sort">Remove</th>
         </tr>
     </thead>
     <tbody id="subdList">
@@ -15,7 +15,7 @@
     listSubd();
     function listSubd(){
         $("#subdList").html(``);
-        $.get("list?type=subd", function(data){
+        $.get("/api/proxy/list?type=subd", function(data){
             if (data.error !== undefined){
                     $("#subdList").append(`<tr>
                     <td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>

+ 3 - 3
web/components/vdir.html

@@ -1,9 +1,9 @@
-<table class="ui celled unstackable compact table">
+<table class="ui celled sortable unstackable compact table">
     <thead>
         <tr>
             <th>Virtual Directory</th>
             <th>Proxy To</th>
-            <th>Remove</th>
+            <th class="no-sort">Remove</th>
         </tr>
     </thead>
     <tbody id="vdirList">
@@ -20,7 +20,7 @@
     listVdirs();
     function listVdirs(){
         $("#vdirList").html(``);
-        $.get("list?type=vdir", function(data){
+        $.get("/api/proxy/list?type=vdir", function(data){
             if (data.error !== undefined){
                 $("#vdirList").append(`<tr>
                     <td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>

二进制
web/favicon.png


二进制
web/img/00233.png


文件差异内容过多而无法显示
+ 27 - 0
web/img/public/icon.ai


二进制
web/img/public/icon.png


+ 15 - 0
web/img/public/icon.svg

@@ -0,0 +1,15 @@
+<?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" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<path fill="#213040" d="M366.358,143.646l-37.493,41.708c22.181,19.953,36.132,48.874,36.132,81.057s-13.952,61.104-36.133,81.057
+		l37.493,41.708c33.594-30.219,54.725-74.022,54.725-122.765C421.082,217.667,399.952,173.865,366.358,143.646z"/>
+	<path fill="#213040" d="M145.64,389.175l37.494-41.707c-22.181-19.953-36.133-48.874-36.133-81.058s13.951-61.104,36.133-81.058
+		l-37.493-41.707c-33.594,30.219-54.725,74.021-54.725,122.765C90.916,315.152,112.047,358.956,145.64,389.175z"/>
+</g>
+<polygon fill="#A9D1F3" points="291.247,122.458 256,164.833 220.753,122.458 "/>
+<polygon fill="#A9D1F3" points="291.247,367.987 256,410.362 220.752,367.987 "/>
+<circle fill="#A9D1F3" cx="256" cy="266.41" r="52.333"/>
+</svg>

文件差异内容过多而无法显示
+ 27 - 0
web/img/public/logo.ai


二进制
web/img/public/logo.png


+ 73 - 0
web/img/public/logo.svg

@@ -0,0 +1,73 @@
+<?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" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
+<g>
+	<path fill="#213040" d="M464.798,122.75c0-4.639,3.26-7.434,7.405-7.434c2.018,0,3.614,0.854,4.611,1.805l-1.374,1.437
+		c-0.865-0.737-1.885-1.281-3.192-1.281c-2.816,0-4.789,2.058-4.789,5.415c0,3.378,1.84,5.474,4.723,5.474
+		c1.485,0,2.615-0.602,3.591-1.533l1.397,1.437c-1.33,1.339-2.971,2.076-5.1,2.076C467.969,130.145,464.798,127.447,464.798,122.75z
+		"/>
+	<path fill="#213040" d="M480.032,115.568h2.572v12.383h6.917v1.922h-9.489V115.568z"/>
+	<path fill="#213040" d="M491.542,123.739v-8.171h2.572v8.308c0,3.241,1.463,4.289,3.503,4.289c2.062,0,3.569-1.048,3.569-4.289
+		v-8.308h2.483v8.171c0,4.658-2.438,6.405-6.053,6.405S491.542,128.397,491.542,123.739z"/>
+	<path fill="#213040" d="M506.489,128.029l1.507-1.553c1.176,1.009,2.771,1.688,4.368,1.688c1.951,0,3.037-0.815,3.037-2.019
+		c0-1.301-1.086-1.708-2.594-2.29l-2.262-0.854c-1.596-0.582-3.325-1.649-3.325-3.746c0-2.251,2.239-3.939,5.365-3.939
+		c1.929,0,3.725,0.698,4.922,1.805l-1.308,1.437c-1.021-0.796-2.173-1.281-3.614-1.281c-1.663,0-2.727,0.719-2.727,1.824
+		c0,1.242,1.308,1.689,2.615,2.174l2.24,0.835c1.95,0.718,3.325,1.767,3.325,3.862c0,2.29-2.173,4.173-5.742,4.173
+		C510.059,130.145,507.975,129.349,506.489,128.029z"/>
+	<path fill="#213040" d="M523.94,117.471h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V117.471z"/>
+	<path fill="#213040" d="M534.187,115.568h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V115.568z"/>
+	<path fill="#213040" d="M552.724,124.108h-2.66v5.765h-2.572v-14.305h5.344c3.37,0,5.897,1.048,5.897,4.153
+		c0,2.213-1.353,3.533-3.415,4.096l3.991,6.056h-2.904L552.724,124.108z M552.524,122.304c2.35,0,3.658-0.854,3.658-2.582
+		c0-1.727-1.309-2.329-3.658-2.329h-2.461v4.911H552.524z"/>
+	<path fill="#213040" d="M464.798,146.035c0-4.639,3.304-7.434,7.649-7.434c2.306,0,3.857,0.893,4.833,1.805l-1.374,1.437
+		c-0.82-0.699-1.818-1.281-3.393-1.281c-3.037,0-5.055,2.058-5.055,5.415c0,3.378,1.796,5.474,5.188,5.474
+		c0.998,0,1.974-0.271,2.527-0.699v-3.338h-3.215v-1.863h5.565v6.191c-1.086,0.951-2.927,1.688-5.144,1.688
+		C468.013,153.43,464.798,150.732,464.798,146.035z"/>
+	<path fill="#213040" d="M484.379,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L484.379,138.854z
+		 M488.037,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
+		H488.037z"/>
+	<path fill="#213040" d="M496.708,140.756h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V140.756z"/>
+	<path fill="#213040" d="M506.954,138.854h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V138.854z"/>
+	<path fill="#213040" d="M518.619,138.854h2.639l1.529,7.434c0.289,1.533,0.577,3.048,0.887,4.601h0.089
+		c0.333-1.553,0.731-3.086,1.108-4.601l2.085-7.434h2.283l2.106,7.434c0.377,1.515,0.731,3.048,1.108,4.601h0.11
+		c0.289-1.553,0.555-3.086,0.82-4.601l1.553-7.434h2.461l-3.215,14.305h-3.171l-2.194-7.938c-0.267-1.126-0.532-2.193-0.754-3.28
+		h-0.089c-0.244,1.087-0.51,2.154-0.776,3.28l-2.15,7.938h-3.126L518.619,138.854z"/>
+	<path fill="#213040" d="M543.345,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L543.345,138.854z
+		 M547.003,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
+		H547.003z"/>
+	<path fill="#213040" d="M556.184,147.763l-4.899-8.909h2.749l1.885,3.805c0.51,1.067,0.976,2.076,1.529,3.144h0.089
+		c0.532-1.067,1.064-2.076,1.552-3.144l1.907-3.805h2.683l-4.922,8.909v5.396h-2.572V147.763z"/>
+</g>
+<g>
+	<path fill="#213040" d="M87.513,84.572l-9.7,10.791c5.739,5.162,9.348,12.645,9.348,20.971c0,8.324-3.609,15.809-9.348,20.971
+		l9.7,10.791c8.691-7.818,14.158-19.152,14.158-31.762C101.671,103.723,96.205,92.391,87.513,84.572z"/>
+	<path fill="#213040" d="M30.41,148.094l9.701-10.789c-5.739-5.162-9.348-12.646-9.348-20.971c0-8.327,3.609-15.809,9.348-20.971
+		l-9.7-10.791c-8.691,7.818-14.158,19.15-14.158,31.762C16.252,128.943,21.719,140.277,30.41,148.094z"/>
+</g>
+<polygon fill="#A9D1F3" points="68.081,79.09 58.962,90.053 49.843,79.09 "/>
+<polygon fill="#A9D1F3" points="68.081,142.613 58.962,153.576 49.843,142.613 "/>
+<circle fill="#A9D1F3" cx="58.962" cy="116.333" r="13.54"/>
+<g>
+	<path d="M120.229,141.305l35.696-55.193h-32.398v-9.506h46.366v6.79l-35.696,55.097h36.084v9.603h-50.052V141.305z"/>
+	<path d="M177.072,121.42c0-17.752,11.931-28.033,25.22-28.033c13.192,0,25.026,10.281,25.026,28.033
+		c0,17.751-11.834,28.033-25.026,28.033C189.002,149.453,177.072,139.171,177.072,121.42z M215.872,121.42
+		c0-11.252-5.238-18.818-13.58-18.818c-8.439,0-13.677,7.566-13.677,18.818c0,11.349,5.238,18.721,13.677,18.721
+		C210.634,140.141,215.872,132.769,215.872,121.42z"/>
+	<path d="M240.509,94.647h9.117l0.971,9.603h0.193c3.783-6.79,9.409-10.863,15.229-10.863c2.619,0,4.268,0.388,5.917,1.067
+		l-1.94,9.796c-1.939-0.582-3.201-0.873-5.432-0.873c-4.365,0-9.604,3.008-12.901,11.544v33.174h-11.154V94.647z"/>
+	<path d="M274.361,133.933c0-11.543,9.7-17.46,31.913-19.885c-0.096-6.111-2.328-11.543-10.184-11.543
+		c-5.626,0-10.864,2.522-15.424,5.432l-4.171-7.566c5.529-3.492,12.998-6.984,21.534-6.984c13.29,0,19.401,8.342,19.401,22.602
+		v32.106h-9.119l-0.873-6.11h-0.387c-4.852,4.17-10.574,7.469-16.976,7.469C280.86,149.453,274.361,143.342,274.361,133.933z
+		 M306.273,134.224v-12.998c-15.81,1.843-21.146,5.917-21.146,11.834c0,5.335,3.59,7.47,8.343,7.47
+		C298.125,140.529,301.908,138.298,306.273,134.224z"/>
+	<path d="M343.521,120.256l-16.004-25.608h12.125l6.305,10.767c1.648,3.008,3.299,6.015,5.045,9.021h0.484
+		c1.357-3.007,2.813-6.014,4.365-9.021l5.625-10.767h11.641l-16.006,26.772l17.17,26.675h-12.125l-6.984-11.349
+		c-1.746-3.104-3.588-6.402-5.529-9.409h-0.484c-1.648,3.007-3.299,6.208-4.947,9.409l-6.207,11.349h-11.641L343.521,120.256z"/>
+	<path d="M379.41,169.338l2.135-8.73c0.971,0.291,2.521,0.775,3.977,0.775c5.723,0,9.119-4.268,11.059-9.99l1.164-3.686
+		l-21.146-53.06h11.35l9.797,27.548c1.648,4.754,3.297,9.991,4.947,15.035h0.484c1.357-4.947,2.813-10.185,4.172-15.035
+		l8.633-27.548h10.766l-19.594,56.648c-4.17,11.446-9.797,19.108-20.951,19.108C383.389,170.404,381.254,170.018,379.41,169.338z"/>
+</g>
+<line fill="none" stroke="#595757" stroke-width="2" stroke-miterlimit="10" x1="450.003" y1="75" x2="450.003" y2="164.666"/>
+</svg>

文件差异内容过多而无法显示
+ 27 - 0
web/img/public/logo_font.ai


+ 3 - 0
web/index.html

@@ -10,6 +10,7 @@
         <script src="script/jquery-3.6.0.min.js"></script>
         <script src="../script/ao_module.js"></script>
         <script src="script/semantic/semantic.min.js"></script>
+        <script src="script/tablesort.js"></script>
         <title>Reverse Proxy</title>
         <style>
             body{
@@ -145,6 +146,8 @@
                     });
                 });
 
+                //Initialize all table that is sortable
+                $('table').tablesort();
             });
 
             function getTabButtonById(targetTabId){

+ 58 - 116
web/login.html

@@ -3,134 +3,67 @@
     <head>
     <meta charset="UTF-8">
     <meta name="robots" content="noindex" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <link rel="author" href="humans.txt"/>
-    <title>Login</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="icon" type="image/png" href="./favicon.png" />
+    <title>Login | Zoraxy</title>
     <link rel="stylesheet" href="script/semantic/semantic.min.css">
-    <script type="application/javascript" src="script/jquery.min.js"></script>
+    <script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
     <script type="application/javascript" src="script/semantic/semantic.min.js"></script>
-    <link rel="preconnect" href="https://fonts.googleapis.com">
-    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet"> 
-    <link rel="stylesheet" href="script/main.css">
     <style>
-    @media only screen and (max-height: 1000px) {
-        .leftPictureFrame {
-        height:auto !important;
+        body {
+            background: rgb(245,245,245);
+            background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%); 
         }
-    }
 
-    .leftPictureFrame{
-        position:fixed;
-        top:0px;
-        left:0px;
-        min-width:calc(100% - 500px);
-        min-height:100%;
-        background-color:#faf7eb;
-        background-image:url("img/authbg.jpg");
-        -webkit-background-size: cover;
-        -moz-background-size: cover;
-        -o-background-size: cover;
-        background-size: cover;
-        background-repeat: no-repeat, no-repeat;
-        background-position:bottom left;
-    }
-
-    .rightLoginFrame{
-        position:fixed;
-        top:0;
-        right:0;
-        height:100%;
-        width:500px;
-        background-color:white;
-        z-index:100%;
-        padding-left: 30px;
-        padding-right: 20px;
-    }
-
-    .fullHeightImage{
-        height:100% !important;
-        position:relative;
-        left:-20px;
-        
-    }
-
-    .bottombar{
-        position:absolute;
-        bottom:1em;
-        left:0;
-        padding-left: 20px;
-        width:100%;
-    }
-
-    #animationFrame{
-        position:absolute;
-        bottom:0px;
-        width:100%;
-    }
-
-    .textbox{
-        margin-bottom:15px;
-    }
-
-    .themecolor{
-        background-color: #5fa0d9 !important;
-    }
-
-    .subthemecolor{
-        background-color: #99d0f2 !important;
-    }
-
-    #loginbtn{
-        color:white !important;
-        margin-top:2em;
-    }
+        form {
+            margin:auto;
+        }
 
-    .oauthbtn{
-        color:white !important;
-        margin-top:1em;
-    }
+        #loginForm{
+            height: 100%;
+            background-color: white;
+            width: 25em;
+            margin-left: 10em;
+            margin-top: 0 !important;
+            margin-bottom: 0 !important;
+        }
 
+        @media all and (max-width: 550px) {
+            /* CSS rules here for screens lower than 750px */
+            #loginForm{
+                width: 100%;
+                margin-left: 0em;
+            }
+        }
     </style>
     </head>
     <body>
-        <div class="leftPictureFrame">
-            
-        </div>
-        <div id="loginInterface" class="rightLoginFrame">
-            <br><br><br>
-            <img class="ui medium image" src="vendor/img/banner.png">
-
-            <div class="ui borderless basic segment">
-                <p><i class="key icon"></i> Login with your username & password <span class="hostname"></span></p>
-                <br>
-                <div class="ui fluid input textbox">
-                    <input id="username" type="text" placeholder="Username">
-                </div>
-                <div class="ui fluid input textbox">
-                    <input id="magic" type="password" placeholder="Password">
+        <div id="loginForm" class="ui middle aligned center aligned grid">
+            <div class="column">
+              <form class="ui large form">
+                <div  class="ui basic segment">
+                <img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
+                  <div class="field">
+                    <div class="ui left icon input">
+                      <i class="user icon"></i>
+                      <input type="text" name="username" placeholder="Username">
+                    </div>
+                  </div>
+                  <div class="field">
+                    <div class="ui left icon input">
+                      <i class="lock icon"></i>
+                      <input type="password" name="password" placeholder="Password">
+                    </div>
+                  </div>
+
+                  <div class="ui fluid basic blue submit button">Login</div>
                 </div>
-
-                <div class="ui checkbox">
-                    <input id="rmbme" type="checkbox">
-                    <label for="rmbme">Remember Me</label>
-                </div>
-                
-                <br>
-                <button id="loginbtn" class="ui button loginbtn themecolor" >Login</button><br>
-                <div class="ui breadcrumb" style="margin-top:12px;">
-                    <a class="section signup" style="cursor:pointer;" href="/register.html">Register</a>
-                </div>
-                <p style="margin-top:18px;color:#ff7a70; display:none;font-size:1.2em;"><i class="remove icon"></i><span id="errmsg">Error. Incorrect username or password.</span></p>
-               
-            </div>
-           
-            <div class="bottombar">
-                © <span class="hostname"></span> <span class="thisyear"></span><br>
-                <small style="font-size: 80%">Request: <span id="requestTime"></span></small>
+                <div class="ui error message"></div>
+                <div class="ui divider"></div>
+                <small>Proudly powered by Zoraxy</small>
+              </form>
             </div>
-        </div>
-        
+          </div>
     <script>
         var redirectionAddress = "/";
         var loginAddress = "/api/auth/login";
@@ -177,6 +110,15 @@
             
         });
 
+        function updateYear() {
+            const year = new Date().getFullYear();
+            const elements = document.getElementsByClassName("year");
+            for (let i = 0; i < elements.length; i++) {
+                elements[i].textContent = year;
+            }
+        }
+        updateYear();
+
         //Event handlers for buttons
         $("#loginbtn").on("click",function(){
             login();

+ 132 - 0
web/script/tablesort.js

@@ -0,0 +1,132 @@
+/*
+	A simple, lightweight jQuery plugin for creating sortable tables.
+	https://github.com/kylefox/jquery-tablesort
+	Version 0.0.11
+*/
+
+(function($) {
+	$.tablesort = function ($table, settings) {
+		var self = this;
+		this.$table = $table;
+		this.$thead = this.$table.find('thead');
+		this.settings = $.extend({}, $.tablesort.defaults, settings);
+		this.$sortCells = this.$thead.length > 0 ? this.$thead.find('th:not(.no-sort)') : this.$table.find('th:not(.no-sort)');
+		this.$sortCells.on('click.tablesort', function() {
+			self.sort($(this));
+		});
+		this.index = null;
+		this.$th = null;
+		this.direction = null;
+	};
+
+	$.tablesort.prototype = {
+
+		sort: function(th, direction) {
+			var start = new Date(),
+				self = this,
+				table = this.$table,
+				rowsContainer = table.find('tbody').length > 0 ? table.find('tbody') : table,
+				rows = rowsContainer.find('tr').has('td, th'),
+				cells = rows.find(':nth-child(' + (th.index() + 1) + ')').filter('td, th'),
+				sortBy = th.data().sortBy,
+				sortedMap = [];
+
+			var unsortedValues = cells.map(function(idx, cell) {
+				if (sortBy)
+					return (typeof sortBy === 'function') ? sortBy($(th), $(cell), self) : sortBy;
+				return ($(this).data().sortValue != null ? $(this).data().sortValue : $(this).text());
+			});
+			if (unsortedValues.length === 0) return;
+
+			//click on a different column
+			if (this.index !== th.index()) {
+				this.direction = 'asc';
+				this.index = th.index();
+			}
+			else if (direction !== 'asc' && direction !== 'desc')
+				this.direction = this.direction === 'asc' ? 'desc' : 'asc';
+			else
+				this.direction = direction;
+
+			direction = this.direction == 'asc' ? 1 : -1;
+
+			self.$table.trigger('tablesort:start', [self]);
+			self.log("Sorting by " + this.index + ' ' + this.direction);
+
+			// Try to force a browser redraw
+			self.$table.css("display");
+			// Run sorting asynchronously on a timeout to force browser redraw after
+			// `tablesort:start` callback. Also avoids locking up the browser too much.
+			setTimeout(function() {
+				self.$sortCells.removeClass(self.settings.asc + ' ' + self.settings.desc);
+				for (var i = 0, length = unsortedValues.length; i < length; i++)
+				{
+					sortedMap.push({
+						index: i,
+						cell: cells[i],
+						row: rows[i],
+						value: unsortedValues[i]
+					});
+				}
+
+				sortedMap.sort(function(a, b) {
+					return self.settings.compare(a.value, b.value) * direction;
+				});
+
+				$.each(sortedMap, function(i, entry) {
+					rowsContainer.append(entry.row);
+				});
+
+				th.addClass(self.settings[self.direction]);
+
+				self.log('Sort finished in ' + ((new Date()).getTime() - start.getTime()) + 'ms');
+				self.$table.trigger('tablesort:complete', [self]);
+				//Try to force a browser redraw
+				self.$table.css("display");
+			}, unsortedValues.length > 2000 ? 200 : 10);
+		},
+
+		log: function(msg) {
+			if(($.tablesort.DEBUG || this.settings.debug) && console && console.log) {
+				console.log('[tablesort] ' + msg);
+			}
+		},
+
+		destroy: function() {
+			this.$sortCells.off('click.tablesort');
+			this.$table.data('tablesort', null);
+			return null;
+		}
+
+	};
+
+	$.tablesort.DEBUG = false;
+
+	$.tablesort.defaults = {
+		debug: $.tablesort.DEBUG,
+		asc: 'sorted ascending',
+		desc: 'sorted descending',
+		compare: function(a, b) {
+			if (a > b) {
+				return 1;
+			} else if (a < b) {
+				return -1;
+			} else {
+				return 0;
+			}
+		}
+	};
+
+	$.fn.tablesort = function(settings) {
+		var table, sortable, previous;
+		return this.each(function() {
+			table = $(this);
+			previous = table.data('tablesort');
+			if(previous) {
+				previous.destroy();
+			}
+			table.data('tablesort', new $.tablesort(table, settings));
+		});
+	};
+
+})(window.Zepto || window.jQuery);

部分文件因为文件数量过多而无法显示