diff --git a/cmd/configui/main.go b/cmd/configui/main.go index 92f2fec..af888f9 100644 --- a/cmd/configui/main.go +++ b/cmd/configui/main.go @@ -3,11 +3,11 @@ package main import ( "flag" "fmt" - "io" - "log" "net/http" "os" + log "kumoly.io/lib/klog" + "kumoly.io/tools/configui" ) @@ -55,17 +55,6 @@ func main() { cui := configui.New() - // setup logging - if flagLogFile != "" { - f, err := os.OpenFile(flagLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - log.Fatalf("Error opening file: %v", err) - } - defer f.Close() - mwriter := io.MultiWriter(f, os.Stderr) - log.SetOutput(mwriter) - } - // setup values if flagPath != "" { if flagName == "" { @@ -77,18 +66,21 @@ func main() { Action: flagAction, } if err := cui.AppendFile(file); err != nil { - log.Fatalln(err) + log.Error(err) + os.Exit(1) } } else if flagConfigPath == "" { - log.Println("no config specified") + log.Error("no config specified") } else { conf, err := os.ReadFile(flagConfigPath) if err != nil { - log.Fatalln(err) + log.Error(err) + os.Exit(1) } cui.LoadConfig(string(conf)) if err != nil { - log.Fatalln(err) + log.Error(err) + os.Exit(1) } } @@ -114,6 +106,10 @@ func main() { } // start server - log.Println("Listening on", flagBind) - log.Fatal(server.ListenAndServe()) + log.Info("Listening on ", flagBind) + err := server.ListenAndServe() + if err != nil { + log.Error(err) + os.Exit(1) + } } diff --git a/configui.go b/configui.go index 4a67ac8..482fd4e 100644 --- a/configui.go +++ b/configui.go @@ -1,13 +1,17 @@ package configui import ( + "bytes" "embed" "encoding/json" "errors" "fmt" "html/template" + "net/http" + "os" "time" + "kumoly.io/lib/klog" "kumoly.io/tools/configui/public" ) @@ -30,6 +34,7 @@ var Ext2Mode map[string]string = map[string]string{ type ConfigUI struct { AppName string `json:"app_name"` + Prod bool `json:"production"` BaseUrl string `json:"base_url"` ConfigPath string `json:"config_path"` @@ -45,12 +50,15 @@ type ConfigUI struct { HideConfig bool `json:"hide_config"` // Should be in main app - LogPath string `json:"log_path"` - SilentSysOut bool `json:"silent_sys_out"` + LogPath string `json:"log_path"` + LogLevel int `json:"log_level"` TmplFS embed.FS `json:"-"` tmpl *template.Template - PublicFS embed.FS `json:"-"` + PublicFS embed.FS `json:"-"` + log *klog.Logger `json:"-"` + ksrv_log *klog.Logger `json:"-"` + f *os.File `json:"-"` } func New() *ConfigUI { @@ -65,6 +73,7 @@ func New() *ConfigUI { }).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl")) return &ConfigUI{ fileIndex: map[string]int{}, + Prod: true, AppName: "ConfigUI", BaseUrl: "/", PublicFS: public.FS, @@ -72,15 +81,18 @@ func New() *ConfigUI { tmpl: tmpl, CmdTimeout: "10s", cmdTimeout: time.Second * 10, + LogLevel: klog.Lerror | klog.Linfo, + log: klog.Sub("ConfigUI"), } } func (cui *ConfigUI) File(file_name string) (*File, error) { if file_name == cui.AppName { return &File{ - Path: cui.ConfigPath, - Name: cui.AppName, - Lang: "json", + Path: cui.ConfigPath, + Name: cui.AppName, + Lang: "json", + owner: cui, }, nil } index, ok := cui.fileIndex[file_name] @@ -103,6 +115,7 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { if f.Name == "" { f.Name = f.Path } + f.owner = cui tmpIndex[f.Name] = i } @@ -110,15 +123,21 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { cui.fileIndex = tmpIndex cui.Files = tmpConf.Files cui.AllowIP = tmpConf.AllowIP + cui.Prod = tmpConf.Prod cui.ConfigPath = tmpConf.ConfigPath cui.HideConfig = tmpConf.HideConfig - cui.LogPath = tmpConf.LogPath cui.NoReconfig = tmpConf.NoReconfig + cui.AppName = tmpConf.AppName + if cui.AppName == "" { + cui.AppName = "ConfigUI" + } + cui.BaseUrl = tmpConf.BaseUrl if cui.BaseUrl == "" { cui.BaseUrl = "/" } + ct, err := time.ParseDuration(tmpConf.CmdTimeout) if err != nil || cui.CmdTimeout == "" { cui.CmdTimeout = "10s" @@ -128,13 +147,48 @@ func (cui *ConfigUI) LoadConfig(confstr string) error { cui.cmdTimeout = ct } + cui.log = klog.Sub(cui.AppName) + // NOT implemented cui.ResultBellow = tmpConf.ResultBellow - cui.SilentSysOut = tmpConf.SilentSysOut + + cui.LogLevel = tmpConf.LogLevel + cui.LogPath = tmpConf.LogPath + cui.setLog() // fmt.Printf("%+v", cui) return nil } +func (cui *ConfigUI) setLog() { + var err error + if cui.f != nil { + cui.f.Close() + } + if cui.LogPath != "" { + cui.f, err = os.OpenFile(cui.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + cui.log.Error(err) + } + cui.log.SetErrOutput(cui.f) + cui.log.SetOutput(cui.f) + cui.log.Reload() + if cui.ksrv_log != nil { + cui.ksrv_log.SetErrOutput(cui.f) + cui.ksrv_log.SetOutput(cui.f) + cui.ksrv_log.Reload() + } + } else { + cui.log.SetErrOutput(os.Stderr) + cui.log.SetOutput(os.Stderr) + cui.log.Reload() + if cui.ksrv_log != nil { + cui.ksrv_log.SetErrOutput(os.Stderr) + cui.ksrv_log.SetOutput(os.Stderr) + cui.ksrv_log.Reload() + } + } +} + func (cui *ConfigUI) Config() ([]byte, error) { return json.MarshalIndent(cui, "", " ") } @@ -143,6 +197,7 @@ func (cui *ConfigUI) AppendFile(file *File) error { if file.Name == "" { file.Name = file.Path } + file.owner = cui i, ok := cui.fileIndex[file.Name] if ok { return fmt.Errorf("%v already exists at %d", file.Name, i) @@ -151,3 +206,13 @@ func (cui *ConfigUI) AppendFile(file *File) error { cui.Files = append(cui.Files, file) return nil } + +func (cui *ConfigUI) Parse(w http.ResponseWriter, name string, data interface{}) error { + buf := &bytes.Buffer{} + err := cui.tmpl.ExecuteTemplate(buf, "home", data) + if err != nil { + panic(err) + } + _, err = w.Write(buf.Bytes()) + return err +} diff --git a/file.go b/file.go index 723183b..b6e2b75 100644 --- a/file.go +++ b/file.go @@ -1,9 +1,7 @@ package configui import ( - "encoding/json" "errors" - "log" "os" "os/exec" "runtime" @@ -25,7 +23,8 @@ type File struct { // used for parsing post data Data string `json:"data"` - lock sync.RWMutex `json:"-"` + lock sync.RWMutex `json:"-"` + owner *ConfigUI `json:"-"` } func (f *File) Read() ([]byte, error) { @@ -33,7 +32,7 @@ func (f *File) Read() ([]byte, error) { defer f.lock.RUnlock() data, err := os.ReadFile(f.Path) if err != nil { - log.Println(err) + f.owner.log.Error(err) return nil, err } return data, nil @@ -64,7 +63,7 @@ func (f *File) Do(CmdTimeout time.Duration) (string, error) { } else { cmd = exec.Command(UNIX_SHELL, "-c", f.Action) } - log.Println("DO: ", f.Action) + f.owner.log.Info("DO: ", f.Action) done := make(chan string, 1) go func() { out, _ := cmd.CombinedOutput() @@ -77,32 +76,10 @@ func (f *File) Do(CmdTimeout time.Duration) (string, error) { select { case <-time.After(CmdTimeout): cmd.Process.Kill() - log.Println("timeout") + f.owner.log.Error("timeout") return "", errors.New("command timeout") case out := <-done: - log.Printf("\n%v", out) + f.owner.log.Info("\n", out) return out, nil } } - -func ReadConfig(confstr string) ([]File, error) { - conf := []File{} - err := json.Unmarshal([]byte(confstr), &conf) - if err != nil { - return nil, err - } - for i := range conf { - if conf[i].Name == "" { - conf[i].Name = conf[i].Path - } - } - return conf, nil -} - -func GetFileMap(files []File) map[string]*File { - fileMap := map[string]*File{} - for i := range files { - fileMap[files[i].Name] = &files[i] - } - return fileMap -} diff --git a/go.mod b/go.mod index 1773852..d068adb 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,13 @@ module kumoly.io/tools/configui go 1.17 + +require ( + kumoly.io/lib/klog v0.1.9 + kumoly.io/lib/ksrv v0.1.4 +) + +require ( + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1ef1c21 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +kumoly.io/lib/klog v0.1.9 h1:rS9PPqfyBIIfeQlPSuMv+7StGPiFVuAdp04HDwwDY3E= +kumoly.io/lib/klog v0.1.9/go.mod h1:Snm+c1xRrh/RbXsxQf7UGYbAJGPcIa6bEEN+CmzJh7M= +kumoly.io/lib/ksrv v0.1.4 h1:8zbslRwdNWHw5Wm1PiyDLr6Mu4xxjz0FTW4u8dZ6ZeI= +kumoly.io/lib/ksrv v0.1.4/go.mod h1:eKfhJR5mOqlQZAy5EUI+Avfxirx2/nNW79r+Ki2C18k= diff --git a/handler.go b/handler.go index d78f85e..e9541e0 100644 --- a/handler.go +++ b/handler.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "strconv" + + "kumoly.io/lib/ksrv" ) func (cui *ConfigUI) ListFiles(w http.ResponseWriter, r *http.Request) { @@ -22,7 +24,7 @@ func (cui *ConfigUI) GetFile(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, err := cui.File(name) if err != nil { - response(w, 404, []byte("file not found")) + ksrv.Response(w, 404, []byte("file not found")) return } data, err := file.Read() @@ -55,7 +57,7 @@ func (cui *ConfigUI) GetDelta(w http.ResponseWriter, r *http.Request) { } file, err := cui.File(name) if err != nil { - response(w, 404, []byte("file not found")) + ksrv.Response(w, 404, []byte("file not found")) return } f, err := os.Open(file.Path) @@ -106,7 +108,7 @@ func (cui *ConfigUI) PostFile(w http.ResponseWriter, r *http.Request) { } file, err := cui.File(f.Name) if err != nil { - response(w, 404, []byte("file not found")) + ksrv.Response(w, 404, []byte("file not found")) return } if err := file.Write([]byte(f.Data)); err != nil { @@ -119,7 +121,7 @@ func (cui *ConfigUI) Apply(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, err := cui.File(name) if err != nil { - response(w, 404, []byte("file not found")) + ksrv.Response(w, 404, []byte("file not found")) return } result, err := file.Do(cui.cmdTimeout) @@ -145,7 +147,7 @@ func (cui *ConfigUI) Download(w http.ResponseWriter, r *http.Request) { } file, err := cui.File(name) if err != nil { - response(w, 404, []byte("file not found")) + ksrv.Response(w, 404, []byte("file not found")) return } data, err := file.Read() diff --git a/log.go b/log.go deleted file mode 100644 index 73d4077..0000000 --- a/log.go +++ /dev/null @@ -1 +0,0 @@ -package configui diff --git a/netutil.go b/netutil.go deleted file mode 100644 index db13ab1..0000000 --- a/netutil.go +++ /dev/null @@ -1,69 +0,0 @@ -package configui - -import ( - "bytes" - "log" - "net/http" - "strconv" -) - -type CuiResponseWriter struct { - http.ResponseWriter - StatueCode int -} - -func (w *CuiResponseWriter) WriteHeader(statusCode int) { - if w.StatueCode != 0 { - return - } - w.StatueCode = statusCode - w.ResponseWriter.WriteHeader(statusCode) -} - -func (w *CuiResponseWriter) Write(body []byte) (int, error) { - if w.StatueCode == 0 { - w.WriteHeader(200) - } - return w.ResponseWriter.Write(body) -} - -func response(w http.ResponseWriter, status int, body []byte) (int, error) { - w.WriteHeader(status) - return w.Write(body) -} - -func abort(w http.ResponseWriter, err interface{}) (int, error) { - log.Println(err) - switch v := err.(type) { - case int: - w.WriteHeader(v) - return w.Write([]byte(strconv.Itoa(v))) - case string: - w.WriteHeader(500) - return w.Write([]byte(v)) - case error: - w.WriteHeader(500) - return w.Write([]byte(v.Error())) - default: - w.WriteHeader(500) - return w.Write([]byte(strconv.Itoa(500))) - } -} - -func catch(rw *CuiResponseWriter, r *http.Request) { - ex := recover() - if ex != nil { - abort(rw, ex) - log.Printf("%s %s %d %s %s\n", GetIP(r), r.Method, rw.StatueCode, r.URL, r.Header.Get("User-Agent")) - } -} - -func (cui *ConfigUI) Parse(w http.ResponseWriter, name string, data interface{}) error { - buf := &bytes.Buffer{} - err := cui.tmpl.ExecuteTemplate(buf, "home", data) - if err != nil { - panic(err) - } - _, err = w.Write(buf.Bytes()) - return err -} diff --git a/server.go b/server.go index d87cb1c..18e5dc5 100644 --- a/server.go +++ b/server.go @@ -1,9 +1,10 @@ package configui import ( - "log" "net/http" "strings" + + "kumoly.io/lib/ksrv" ) func (cui *ConfigUI) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -11,25 +12,28 @@ func (cui *ConfigUI) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (cui *ConfigUI) middleware(next http.Handler) http.Handler { - return http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - // log.Println(r.URL) - rw := &CuiResponseWriter{w, 0} - defer catch(rw, r) - ip := GetIP(r) + k := ksrv.New() + cui.ksrv_log = k.GetLogger() + cui.setLog() + k.SetNoLogCondition(func(r *http.Request) bool { + if strings.HasPrefix(r.URL.Path, "/public") || r.URL.Query().Get("f") == "true" { + return true + } + return false + }) + return k.Middleware( + http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if cui.AllowIP != "" { - if !matchIPGlob(ip, cui.AllowIP) { + ip := ksrv.GetIP(r) + if !ksrv.MatchIPGlob(ip, cui.AllowIP) { rw.WriteHeader(403) panic("permission denied") } } next.ServeHTTP(rw, r) - //logging - if !strings.HasPrefix(r.URL.Path, "/public") && r.URL.Query().Get("f") != "true" { - log.Printf("%s %s %d %s %s\n", ip, r.Method, rw.StatueCode, r.URL, r.Header.Get("User-Agent")) - } - }, + }), ) + } func (cui *ConfigUI) mux() *http.ServeMux { diff --git a/util.go b/util.go index 692c4fa..d4f5ff8 100644 --- a/util.go +++ b/util.go @@ -4,55 +4,10 @@ import ( "archive/tar" "compress/gzip" "io" - "net" - "net/http" "os" "strings" ) -func matchIPGlob(ip, pattern string) bool { - parts := strings.Split(pattern, ".") - seg := strings.Split(ip, ".") - for i, part := range parts { - - // normalize pattern to 3 digits - switch len(part) { - case 1: - if part == "*" { - part = "***" - } else { - part = "00" + part - } - case 2: - if strings.HasPrefix(part, "*") { - part = "*" + part - } else if strings.HasSuffix(part, "*") { - part = part + "*" - } else { - part = "0" + part - } - } - - // normalize ip to 3 digits - switch len(seg[i]) { - case 1: - seg[i] = "00" + seg[i] - case 2: - seg[i] = "0" + seg[i] - } - - for j := range part { - if string(part[j]) == "*" { - continue - } - if part[j] != seg[i][j] { - return false - } - } - } - return true -} - func bundle(buf io.Writer, paths []string, rootDir string, flat bool) error { gw := gzip.NewWriter(buf) defer gw.Close() @@ -110,20 +65,3 @@ func bundle(buf io.Writer, paths []string, rootDir string, flat bool) error { return nil } - -func GetIP(r *http.Request) string { - ip := r.Header.Get("X-Real-Ip") - if ip == "" { - ips := r.Header.Get("X-Forwarded-For") - ipArr := strings.Split(ips, ",") - ip = strings.Trim(ipArr[len(ipArr)-1], " ") - } - if ip == "" { - var err error - ip, _, err = net.SplitHostPort(r.RemoteAddr) - if err != nil { - ip = r.RemoteAddr - } - } - return ip -}