Kaynağa Gözat

auto update script executed

Toby Chui 1 yıl önce
ebeveyn
işleme
9af36e315b

+ 1 - 0
api.go

@@ -20,4 +20,5 @@ func initAPIs() {
 
 	//TLS / SSL config
 	http.HandleFunc("/cert/upload", handleCertUpload)
+	http.HandleFunc("/cert/list", handleListCertificate)
 }

+ 19 - 0
cert.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -10,6 +11,24 @@ import (
 	"imuslab.com/arozos/ReverseProxy/mod/utils"
 )
 
+func handleListCertificate(w http.ResponseWriter, r *http.Request) {
+	filenames, err := tlsCertManager.ListCertDomains()
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	response, err := json.Marshal(filenames)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(response)
+}
+
+//Handle upload of the certificate
 func handleCertUpload(w http.ResponseWriter, r *http.Request) {
 	// check if request method is POST
 	if r.Method != "POST" {

+ 0 - 0
certs/example.com.crt


+ 34 - 0
certs/localhost.crt

@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----

+ 52 - 0
certs/localhost.key

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
++iSj2pW0Rbdy7oOcFRF/iM2GwFYc1Q==
+-----END PRIVATE KEY-----

+ 11 - 4
main.go

@@ -6,14 +6,17 @@ import (
 	"os"
 	"os/signal"
 	"syscall"
+	"time"
 
 	"imuslab.com/arozos/ReverseProxy/mod/aroz"
 	"imuslab.com/arozos/ReverseProxy/mod/database"
+	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
 )
 
 var (
-	handler *aroz.ArozHandler
-	sysdb   *database.Database
+	handler        *aroz.ArozHandler
+	sysdb          *database.Database
+	tlsCertManager *tlscert.Manager
 )
 
 //Kill signal handler. Do something before the system the core terminate.
@@ -57,20 +60,24 @@ func main() {
 	if err != nil {
 		log.Fatal(err)
 	}
-
 	sysdb = db
 
 	//Create tables for the database
 	sysdb.NewTable("settings")
 
+	//Create a TLS certificate manager
+	tlsCertManager, _ = tlscert.NewManager("./certs")
+
 	//Start the reverse proxy server in go routine
 	go func() {
 		ReverseProxtInit()
 	}()
 
+	time.Sleep(300 * time.Millisecond)
 	//Any log println will be shown in the core system via STDOUT redirection. But not STDIN.
-	log.Println("ReverseProxy started. Control Panel hosted on " + handler.Port)
+	log.Println("ReverseProxy started. Visit control panel at http://localhost" + handler.Port)
 	err = http.ListenAndServe(handler.Port, nil)
+
 	if err != nil {
 		log.Fatal(err)
 	}

+ 0 - 48
mod/certvalidate/certvalidate.go

@@ -1,48 +0,0 @@
-package certvalidate
-
-import (
-	"crypto/x509"
-	"encoding/pem"
-	"io"
-	"mime/multipart"
-	"strings"
-)
-
-func isValidTLSFile(file multipart.File) bool {
-	// Read the contents of the uploaded file
-	contents, err := io.ReadAll(file)
-	if err != nil {
-		// Handle the error
-		return false
-	}
-
-	// Parse the contents of the file as a PEM-encoded certificate or key
-	block, _ := pem.Decode(contents)
-	if block == nil {
-		// The file is not a valid PEM-encoded certificate or key
-		return false
-	}
-
-	// Parse the certificate or key
-	if strings.Contains(block.Type, "CERTIFICATE") {
-		// The file contains a certificate
-		cert, err := x509.ParseCertificate(block.Bytes)
-		if err != nil {
-			// Handle the error
-			return false
-		}
-		// Check if the certificate is a valid TLS/SSL certificate
-		return cert.IsCA == false && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
-	} else if strings.Contains(block.Type, "PRIVATE KEY") {
-		// The file contains a private key
-		_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
-		if err != nil {
-			// Handle the error
-			return false
-		}
-		return true
-	} else {
-		return false
-	}
-
-}

+ 26 - 3
mod/dynamicproxy/dynamicproxy.go

@@ -2,9 +2,11 @@ package dynamicproxy
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"log"
+	"net"
 	"net/http"
 	"net/url"
 	"strconv"
@@ -14,6 +16,7 @@ import (
 
 	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/dpcore"
 	"imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
+	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
 )
 
 /*
@@ -26,9 +29,12 @@ type Router struct {
 	SubdomainEndpoint *sync.Map
 	Running           bool
 	Root              *ProxyEndpoint
+	tlsCertManager    *tlscert.Manager
 	mux               http.Handler
+	TlsManager        *tlscert.Manager
 	useTLS            bool
 	server            *http.Server
+	tlsListener       net.Listener
 }
 
 type RouterOption struct {
@@ -53,7 +59,7 @@ type ProxyHandler struct {
 	Parent *Router
 }
 
-func NewDynamicProxy(port int) (*Router, error) {
+func NewDynamicProxy(port int, tlsManager *tlscert.Manager) (*Router, error) {
 	proxyMap := sync.Map{}
 	domainMap := sync.Map{}
 	thisRouter := Router{
@@ -61,6 +67,7 @@ func NewDynamicProxy(port int) (*Router, error) {
 		ProxyEndpoints:    &proxyMap,
 		SubdomainEndpoint: &domainMap,
 		Running:           false,
+		tlsCertManager:    tlsManager,
 		useTLS:            false,
 		server:            nil,
 	}
@@ -83,11 +90,22 @@ func (router *Router) StartProxyService() error {
 		return errors.New("Reverse proxy router root not set")
 	}
 
+	config := &tls.Config{
+		GetCertificate: router.tlsCertManager.GetCert,
+	}
+	ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.ListenPort), config)
+	if err != nil {
+		log.Println(err)
+		return err
+	}
+	router.tlsListener = ln
 	router.server = &http.Server{Addr: ":" + strconv.Itoa(router.ListenPort), Handler: router.mux}
 	router.Running = true
+
 	go func() {
-		router.server.ListenAndServe()
-		//log.Println("[DynamicProxy] " + err.Error())
+		if err := router.server.Serve(ln); err != nil && err != http.ErrServerClosed {
+			log.Fatalf("Could not start server: %v\n", err)
+		}
 	}()
 
 	return nil
@@ -104,7 +122,12 @@ func (router *Router) StopProxyService() error {
 		return err
 	}
 
+	if router.tlsListener != nil {
+		router.tlsListener.Close()
+	}
+
 	//Discard the server object
+	router.tlsListener = nil
 	router.server = nil
 	router.Running = false
 	return nil

+ 60 - 0
mod/tlscert/helper.go

@@ -0,0 +1,60 @@
+package tlscert
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+//This remove the certificates in the list where either the
+//public key or the private key is missing
+func getCertPairs(certFiles []string) []string {
+	crtMap := make(map[string]bool)
+	keyMap := make(map[string]bool)
+
+	for _, filename := range certFiles {
+		if filepath.Ext(filename) == ".crt" {
+			crtMap[strings.TrimSuffix(filename, ".crt")] = true
+		} else if filepath.Ext(filename) == ".key" {
+			keyMap[strings.TrimSuffix(filename, ".key")] = true
+		}
+	}
+
+	var result []string
+	for domain := range crtMap {
+		if keyMap[domain] {
+			result = append(result, domain)
+		}
+	}
+
+	return result
+}
+
+//Get the cloest subdomain certificate from a list of domains
+func matchClosestDomainCertificate(subdomain string, domains []string) string {
+	var matchingDomain string = ""
+	maxLength := 0
+
+	for _, domain := range domains {
+		if strings.HasSuffix(subdomain, "."+domain) && len(domain) > maxLength {
+			matchingDomain = domain
+			maxLength = len(domain)
+		}
+	}
+
+	return matchingDomain
+}
+
+//Check if a requesting domain is a subdomain of a given domain
+func isSubdomain(subdomain, domain string) bool {
+	subdomainParts := strings.Split(subdomain, ".")
+	domainParts := strings.Split(domain, ".")
+	if len(subdomainParts) < len(domainParts) {
+		return false
+	}
+	for i := range domainParts {
+		if subdomainParts[len(subdomainParts)-1-i] != domainParts[len(domainParts)-1-i] {
+			return false
+		}
+	}
+	return true
+}

+ 129 - 0
mod/tlscert/tlscert.go

@@ -0,0 +1,129 @@
+package tlscert
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/pem"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"imuslab.com/arozos/ReverseProxy/mod/utils"
+)
+
+type Manager struct {
+	CertStore string
+}
+
+func NewManager(certStore string) (*Manager, error) {
+	if !utils.FileExists(certStore) {
+		os.MkdirAll(certStore, 0775)
+	}
+
+	thisManager := Manager{
+		CertStore: certStore,
+	}
+
+	return &thisManager, nil
+}
+
+func (m *Manager) ListCertDomains() ([]string, error) {
+	filenames, err := m.ListCerts()
+	if err != nil {
+		return []string{}, err
+	}
+
+	//Remove certificates where there are missing public key or private key
+	filenames = getCertPairs(filenames)
+
+	return filenames, nil
+}
+
+func (m *Manager) ListCerts() ([]string, error) {
+	certs, err := ioutil.ReadDir(m.CertStore)
+	if err != nil {
+		return []string{}, err
+	}
+
+	filenames := make([]string, 0, len(certs))
+	for _, cert := range certs {
+		if !cert.IsDir() {
+			filenames = append(filenames, cert.Name())
+		}
+	}
+
+	return filenames, nil
+}
+
+func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
+	//Check if the domain corrisponding cert exists
+	pubKey := "./system/localhost.crt"
+	priKey := "./system/localhost.key"
+
+	if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".crt")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
+		pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".crt")
+		priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
+
+	} else {
+		domainCerts, _ := m.ListCertDomains()
+		cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
+		if cloestDomainCert != "" {
+			//There is a matching parent domain for this subdomain. Use this instead.
+			pubKey = filepath.Join(m.CertStore, cloestDomainCert+".crt")
+			priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
+		} else {
+			log.Println("Matching certificate not found. Serving with default. Domain: ", helloInfo.ServerName)
+		}
+
+	}
+	cer, err := tls.LoadX509KeyPair(pubKey, priKey)
+	if err != nil {
+		log.Println(err)
+		return nil, nil
+	}
+
+	return &cer, nil
+}
+
+//Check if the given file is a valid TLS file
+func isValidTLSFile(file io.Reader) bool {
+	// Read the contents of the uploaded file
+	contents, err := io.ReadAll(file)
+	if err != nil {
+		// Handle the error
+		return false
+	}
+
+	// Parse the contents of the file as a PEM-encoded certificate or key
+	block, _ := pem.Decode(contents)
+	if block == nil {
+		// The file is not a valid PEM-encoded certificate or key
+		return false
+	}
+
+	// Parse the certificate or key
+	if strings.Contains(block.Type, "CERTIFICATE") {
+		// The file contains a certificate
+		cert, err := x509.ParseCertificate(block.Bytes)
+		if err != nil {
+			// Handle the error
+			return false
+		}
+		// Check if the certificate is a valid TLS/SSL certificate
+		return cert.IsCA == false && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
+	} else if strings.Contains(block.Type, "PRIVATE KEY") {
+		// The file contains a private key
+		_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+		if err != nil {
+			// Handle the error
+			return false
+		}
+		return true
+	} else {
+		return false
+	}
+
+}

+ 2 - 2
reverseproxy.go

@@ -24,7 +24,8 @@ func ReverseProxtInit() {
 	} else {
 		log.Println("Inbound port not set. Using default (80)")
 	}
-	dprouter, err := dynamicproxy.NewDynamicProxy(inboundPort)
+
+	dprouter, err := dynamicproxy.NewDynamicProxy(inboundPort, tlsCertManager)
 	if err != nil {
 		log.Println(err.Error())
 		return
@@ -226,5 +227,4 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
 	sysdb.Write("settings", "inbound", newIncomingPortInt)
 
 	sendOK((w))
-
 }


+ 34 - 0
system/localhost.crt

@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----

+ 52 - 0
system/localhost.key

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCwGl/259Fg590p
++37nUSJumqED3LDpiy3EvkE2x6XHcRNWe7u9hEiUfTKmA82cqC5n4Fw3iYkWNDfo
+3hWk3vJJV84JIJnPuU+898uJv2K6QGh0FhA0DMyQpwuPKhiBLwwP31yp7uzjyTgk
+qnpvZY6afY61VkhLsApm9WxEk1uWHLsLcuQucBIoXSNv+JowZEAT5BX/POe8aD0W
+E2BBZZSKXY56zHd6WPgrCVwc1f5z2lnPrJWAmt6uPSz3g4MS656KoAU5axmumnvn
+h98Ljk9t5M2J+VDUHdqqRO2XlsvzwRKDAx21vUkZY3kchLVOrZ3/c+Cpu29QSGCB
+oUNt7Q7EbZXfceoR94bcGlmliEIHcjsU+lEL9Onw8AJjXc0dgH0/mFFT8Ocg2EeM
+wIsrEeyiWGKmFa7FK40giiNEvrbOPsnCOcGByCFDNUUng7hMF94wNp0sgjed0O2y
+ZHV0htrt+D4JZZ2/hmzRcaoc5qIc1yVzsGEkXxKCJDGPGOYWHVmwmbcvE+zD+IKX
+sgsTv7VoFCUGMSPTIG8y22QQOFFsgvFcU8wAsb+pqQ+9b07tZYKUBzTd6L8kV2rZ
+xqa5jrb9N/UcH5NKkZZgf5xIiqy7dXm7b2OZ1mOuOe4zsXMYSM1IKysyFItguQwm
+asMZ0VAEHfXHbij0he3Kt8pmLDEyVQIDAQABAoICAATmtwUILqujyGQCu+V0PKEX
+bKPO4J2fYga3xNjhdZu3afJePztnEx4O3foA4RgbFi+N7wMcsNQNYAD7LV8JVXT1
+HKbkYWOGpNF9lAyhZv4IDOAuPQU11fuwqoGxij0OMie+77VLEQzF7OoYVJAFI5Lp
+K6+gVyLEI4X6DqlZ8JKc+he3euJP/DFjZjkXkjMGl0H2dyZDa6+ytwCGSYeIbDnt
+oKmKR0kAcOfBuu6ShiJzUUyWYRLTPJ9c1IOPBXbhV+hDy+FtOanCYvBut6Z6r3s/
+gvj0F2vP6OYURQiTCdoe5YT/8TO9sOsj+Zrxlpo5+svBTd9reA2j9gulkVrd3itN
+c2Ee7fyuyrCRnEcKoT6BI8/LqH5eWGQKKS9WhOz26VkrorcYYZN3g4ayv+MiiSIm
+jeo/kAWCqT5ylvlw2gaCbPjB4kbx7yMI/myjgF0R4+aNQaHpXa2qqEORitGx40M7
+T1V2JIxnsa83TBwumunkYC2pX7bNS0a1VuCNxUafJRKEcvKhWmiRHaWddZn46G8N
+E56qFzSaLbkd+J71jso9llK5joGIQTt2pbKUdV9LIm5Nsbtp2VgF9URIw5RZFftx
+PfSm9XM9DtWuxheO4gNwAuOvtaOxztNMvSkQzhTOggSRpt15hFd7CeBrpK43feAH
+b2pMequB8MHpUieyxlwBAoIBAQC5IRbaKx+fSEbYeIySUwbN8GCJQl+wmvc1gqCC
+DflEQqxTvCGBB5SLHHurTT0ubhXkvbrtuS5f4IC+htzKSuwlqn3lS0aOXtFP2tT6
+D9iiMxLxIId5l6dD+PjMWtQcWc8wUQ7+ieRgxybDqiCWMyTbvNgwlkcIbRxmcqyN
+4/LmmgzTnr5CH0DC/J7xpUJuX9LPVb4ZvBYjz5X++Yb7pCa+kXp0Z6yU48bG3sRe
+yiUKp3Z4vDoOkMLHTPvTQLG81rQuJnBUw2uLWM0kg1AwteZcQ/gH1ilVbJzMBnKm
+mtuJWtoPnM2zIhCsURngmBN+qxOb5kchMSvPzAQBCw7HBjWpAoIBAQDzhLQO434G
+XhyDcdkdMRbDZ8Q8PqtOloAbczMuPGgwHV7rVe/BvnJS7HDDebwlJBD8nhGvgBrp
+CsjNGHjSQC7ydUa8dP4Aw/46izdR8DsAwqGZq+tZhkY5CS88QpflUT5rftW0RObn
+Cb/gDzdxHy35/scSICxa2HwcZnqXqfEwnbjkxFwBYFSt6hRiwNhDhd6ZxKa6gt56
+DS9uIxt1IhKgXZfIw1Vo0mHHFLsB7czGZ0O24ya31Es0bUWGgWIcxvKw6MqKhFWw
+ncCakVg278UYUm/zt6Dcrn3XYnK7Pr944AiKO21PMQhG7Rb+OVwxgjMhk7/BCt+k
+sPR1Dct5pqrNAoIBAAl2jYp9ZdJoiWaLUvQv1ks0nFqnz+hhI33SvY2oVTOODO0C
+0tubnZY20IODITt8WRYmNKXuL1arTSlwD10v0z5hpqnP3T1tz1k7oGNf5/zyi2dT
++FjYza4FzgH0Kp+AX7zih9evCMOBqpOZ4KyM1Ld+wbZKGDtwCGGcPwHJwyLSgRFY
+LfWHT3IoI5/KiMjHkSkUAvGh0afm9o3gB2xZibl4CkBlBEdgFUsZHASUZKxUvxOQ
+247fC3XQk5bK2csDVpZ9VISgsKCg22ugYrr6sVnKB6Wu5tH9CU7MjZPCmrI8uKTP
+qRwdA6krRB1c6LIy4H+5l600rD6k+Rdsj0bRJHECggEAeBXSrRzmAsHaEb/MryaL
+8SR0krjcxU5WMjMm5AAJ6OAy9J5WMxZ1TgsmuF6Jt08HyWsxkXf8zTryNqGAwz2/
+aPUIQtr2fu4nqjsItrFeh0tzYVJ0JpueeXXcAz1bpkvgGiZbwB/SNdCK/DTExFX5
+2DQZewi+lrX2zhKDFdNKCw1cJgPm0w7r8y9hiilK/FFBqlZdWdA7Ybiq0Qci/Som
+QUqmFOyua5iDeybv6U2ZE6XMsJ1ndHON+naAOIoJFePNvguuBYyorQW9+vr9o2mt
+qgbNCkRdYTXy/ImhxlB1H2hrDa+sgcbOLBuyoP8sRYXNLRutDccM7iwNAMQiuQTF
+aQKCAQEAiKPwUodT6LNu4lrSbsDAYIqWwlfM0wwUhudT5UTVHSYI3ap0QOiEuzOl
+IJVdx+vx7rQW7l+JIL6s4shA7mzpzuTVlhRuDuGZx0qQLP7INVpCLzIEbYGI2dL7
+WLhJd4eYKltJ+BG7S51tq9/6rVcUDn5DKzyGNyeGhOnaYkk+eTm483+vpOP2/ITi
+cbVv3mx4qE7zMPIxIufm+c8RonadJzYiq1uMk8t0TrcW/B9RTly/Y96kamjyU5b0
+OcLdRcx3ppKAxHD9AvwAR6SiuNLfNjM9KZM40zM5goMrCJJzwgb7UGeMuw2z7L9F
++iSj2pW0Rbdy7oOcFRF/iM2GwFYc1Q==
+-----END PRIVATE KEY-----

+ 75 - 34
web/components/cert.html

@@ -1,43 +1,84 @@
 
-<button onclick="uploadCert();">Upload</button>
+
+<button onclick="uploadPublicKey();">Upload</button>
+<button onclick="uploadPrivateKey();">Upload Private Key</button>
 
 <script>
-    function uploadCert() {
-    // create file input element
-    const input = document.createElement('input');
-    input.type = 'file';
-    
-    // add change listener to file input
-    input.addEventListener('change', () => {
-        // create form data object
-        const formData = new FormData();
+    function uploadPrivateKey(){
+  // create file input element
+  const input = document.createElement('input');
+        input.type = 'file';
         
-        // add selected file to form data
-        formData.append('file', input.files[0]);
+        // add change listener to file input
+        input.addEventListener('change', () => {
+            // create form data object
+            const formData = new FormData();
+            
+            // add selected file to form data
+            formData.append('file', input.files[0]);
 
-        // send form data to server
-        fetch('/cert/upload', {
-            method: 'POST',
-            body: formData
-        })
-        .then(response => {
-            if (response.ok) {
-                alert('File upload successful!');
-            } else {
-                response.text().then(text => {
-                    alert(text);
-                });
-                //console.log(response.text());
-                //alert('File upload failed!');
-            }
+            // send form data to server
+            fetch('/cert/upload?ktype=pri', {
+                method: 'POST',
+                body: formData
             })
-        .catch(error => {
-            alert('An error occurred while uploading the file.');
-            console.error(error);
+            .then(response => {
+                if (response.ok) {
+                    alert('File upload successful!');
+                } else {
+                    response.text().then(text => {
+                        alert(text);
+                    });
+                    //console.log(response.text());
+                    //alert('File upload failed!');
+                }
+                })
+            .catch(error => {
+                alert('An error occurred while uploading the file.');
+                console.error(error);
+            });
         });
-    });
-    
-    // click file input to open file selector
-    input.click();
+        
+        // click file input to open file selector
+        input.click();
+    }
+
+    function uploadPublicKey() {
+        // create file input element
+        const input = document.createElement('input');
+        input.type = 'file';
+        
+        // add change listener to file input
+        input.addEventListener('change', () => {
+            // create form data object
+            const formData = new FormData();
+            
+            // add selected file to form data
+            formData.append('file', input.files[0]);
+
+            // send form data to server
+            fetch('/cert/upload?ktype=pub', {
+                method: 'POST',
+                body: formData
+            })
+            .then(response => {
+                if (response.ok) {
+                    alert('File upload successful!');
+                } else {
+                    response.text().then(text => {
+                        alert(text);
+                    });
+                    //console.log(response.text());
+                    //alert('File upload failed!');
+                }
+                })
+            .catch(error => {
+                alert('An error occurred while uploading the file.');
+                console.error(error);
+            });
+        });
+        
+        // click file input to open file selector
+        input.click();
     }
 </script>

+ 4 - 0
web/components/status.html

@@ -13,6 +13,9 @@
     <input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
     <button class="ui button" onclick="handlePortChange();">Apply</button>
 </div>
+<div id="portUpdateSucc" class="ui green message" style="display:none;">
+    <i class="ui green checkmark icon"></i> Setting Updated
+</div>
 <Br>
 <button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
 <button id="stopbtn" class="ui red disabled button" onclick="stopService();">Stop Service</button>
@@ -68,6 +71,7 @@
             if (data.error != undefined){
                 errmsg(data.error);
             }
+            $("#portUpdateSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
             initRPStaste();
         });
     }

+ 1 - 1
web/index.html

@@ -164,7 +164,7 @@
                 $("#mainmenu").find(".item").removeClass("active");
                 $(targetBtn).addClass("active");
                 $(".functiontab").hide();
-                $("#" + tabID).slideDown('fast');
+                $("#" + tabID).fadeIn('fast');
                 window.location.hash = tabID;
             }