Files
godoxy/src/go-proxy/panel.go

154 lines
3.9 KiB
Go
Executable File

package main
import (
"errors"
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"path"
)
var panelHandler = panelRouter()
func panelRouter() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", panelServeFile)
mux.HandleFunc("GET /{file}", panelServeFile)
mux.HandleFunc("GET /panel/", panelPage)
mux.HandleFunc("GET /panel/{file}", panelServeFile)
mux.HandleFunc("HEAD /checkhealth", panelCheckTargetHealth)
mux.HandleFunc("GET /config_editor/", panelConfigEditor)
mux.HandleFunc("GET /config_editor/{file}", panelServeFile)
mux.HandleFunc("GET /config/{file}", panelConfigGet)
mux.HandleFunc("PUT /config/{file}", panelConfigUpdate)
mux.HandleFunc("POST /reload", configReload)
mux.HandleFunc("GET /codemirror/", panelServeFile)
return mux
}
func panelPage(w http.ResponseWriter, r *http.Request) {
resp := struct {
HTTPRoutes HTTPRoutes
StreamRoutes StreamRoutes
}{httpRoutes, streamRoutes}
panelRenderFile(w, r, panelTemplatePath, resp)
}
func panelCheckTargetHealth(w http.ResponseWriter, r *http.Request) {
targetUrl := r.URL.Query().Get("target")
if targetUrl == "" {
panelHandleErr(w, r, errors.New("target is required"), http.StatusBadRequest)
return
}
url, err := url.Parse(targetUrl)
if err != nil {
err = NewNestedError("failed to parse url").Subject(targetUrl).With(err)
panelHandleErr(w, r, err, http.StatusBadRequest)
return
}
scheme := url.Scheme
if isStreamScheme(scheme) {
err = utils.healthCheckStream(scheme, url.Host)
} else {
err = utils.healthCheckHttp(targetUrl)
}
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusOK)
}
}
func panelConfigEditor(w http.ResponseWriter, r *http.Request) {
cfgFiles := make([]string, 0)
cfgFiles = append(cfgFiles, path.Base(configPath))
for _, p := range cfg.Value().Providers {
if p.Kind != ProviderKind_File {
continue
}
cfgFiles = append(cfgFiles, p.Value)
}
panelRenderFile(w, r, configEditorTemplatePath, cfgFiles)
}
func panelConfigGet(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, path.Join(configBasePath, r.PathValue("file")))
}
func panelConfigUpdate(w http.ResponseWriter, r *http.Request) {
p := r.PathValue("file")
content := make([]byte, r.ContentLength)
_, err := r.Body.Read(content)
if err != nil {
panelHandleErr(w, r, NewNestedError("unable to read request body").Subject(p).With(err))
return
}
if p == path.Base(configPath) {
err = ValidateConfig(content)
} else {
_, err = ValidateFileContent(content)
}
if err != nil {
panelHandleErr(w, r, err)
return
}
p = path.Join(configBasePath, p)
_, err = os.Stat(p)
exists := !errors.Is(err, os.ErrNotExist)
err = os.WriteFile(p, content, 0644)
if err != nil {
panelHandleErr(w, r, NewNestedError("unable to write config file").With(err))
return
}
w.WriteHeader(http.StatusOK)
if !exists {
w.Write([]byte(fmt.Sprintf("Config file %s created, remember to add it to config.yml!", p)))
return
}
w.Write([]byte(fmt.Sprintf("Config file %s updated", p)))
}
func panelServeFile(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, path.Join(templatesBasePath, r.URL.Path))
}
func panelRenderFile(w http.ResponseWriter, r *http.Request, f string, data any) {
tmpl, err := template.ParseFiles(f)
if err != nil {
panelHandleErr(w, r, NewNestedError("unable to parse template").With(err))
return
}
err = tmpl.Execute(w, data)
if err != nil {
panelHandleErr(w, r, NewNestedError("unable to render template").With(err))
}
}
func configReload(w http.ResponseWriter, r *http.Request) {
err := cfg.Reload()
if err != nil {
panelHandleErr(w, r, err)
return
}
w.WriteHeader(http.StatusOK)
}
func panelHandleErr(w http.ResponseWriter, r *http.Request, err error, code ...int) {
err = NewNestedErrorFrom(err).Subjectf("%s %s", r.Method, r.URL)
palog.Error(err)
if len(code) > 0 {
http.Error(w, err.Error(), code[0])
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
}