diff --git a/Makefile b/Makefile index 0bc9768..0a17e10 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,12 @@ LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w" default: build +clean: + rm -rf dist + +run: build + $(shell cd dist; ./${PROJ} -log configui.log) + .PHONY: build build: go build ${LDFLAGS} -o dist/${PROJ} \ No newline at end of file diff --git a/README.md b/README.md index a9f5cc2..2aeb3e5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,41 @@ a web app to edit and action on update +``` +Usage: configui [options] + -bind string + address to bind (default "localhost:8000") + -c string + cmd to apply + -f string + path to config file + -log string + log to file + -n string + alias of file + -p string + path to file, precedence over config + -v show version +``` + +## Config + +```json +[ + { + "path": "configui.log", + "ro": true + }, + { + "path": "etc/test.ini", + "name": "test", + "action": "myip local -P" + } +] +``` + +`configui -f PATH/TO/CONFIG` + ## Api ### Files diff --git a/api.go b/api.go index 87daf10..cf39599 100644 --- a/api.go +++ b/api.go @@ -11,8 +11,7 @@ import ( func ListFiles(w http.ResponseWriter, r *http.Request) { data, err := json.Marshal(files) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } w.Write(data) @@ -22,14 +21,12 @@ func GetFile(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, ok := files[name] if name == "" || !ok { - w.WriteHeader(404) - w.Write([]byte("file not found")) + HttpWriter(w, 404, "file not found") return } data, err := file.Read() if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } response, err := json.Marshal(map[string]string{ @@ -39,8 +36,7 @@ func GetFile(w http.ResponseWriter, r *http.Request) { "data": string(data), }) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } w.Header().Set("Content-Type", "application/json") @@ -51,25 +47,21 @@ func PostFile(w http.ResponseWriter, r *http.Request) { data, err := ioutil.ReadAll(r.Body) r.Body.Close() if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } f := configui.File{} if err := json.Unmarshal(data, &f); err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } file, ok := files[f.Alias] if !ok { - w.WriteHeader(404) - w.Write([]byte("file not found")) + HttpWriter(w, 404, "file not found") return } if err := file.Write([]byte(f.Data)); err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } w.Write([]byte("ok")) @@ -79,14 +71,12 @@ func Apply(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, ok := files[name] if name == "" || !ok { - w.WriteHeader(404) - w.Write([]byte("file not found")) + HttpWriter(w, 404, "file not found") return } result, err := file.Do() if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + HttpWriter(w, 500, err.Error()) return } w.Write([]byte(result)) @@ -100,3 +90,32 @@ func Download(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`) bundle(w, fs, false) } + +func LoadConfig(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + HttpWriter(w, 500, err.Error()) + return + } + ftmp, err := configui.ReadConfig(string(data)) + if err != nil { + HttpWriter(w, 500, err.Error()) + return + } + files = configui.GetFileMap(ftmp) + w.Write([]byte("ok")) +} + +func GetConfig(w http.ResponseWriter, r *http.Request) { + config := []configui.File{} + for _, f := range files { + config = append(config, *f) + } + data, err := json.Marshal(config) + if err != nil { + HttpWriter(w, 500, err.Error()) + return + } + w.Write(data) +} diff --git a/configui/file.go b/configui/file.go index 78c840c..6e2ffb2 100644 --- a/configui/file.go +++ b/configui/file.go @@ -70,6 +70,11 @@ func ReadConfig(confstr string) ([]File, error) { if err != nil { return nil, err } + for i := range conf { + if conf[i].Alias == "" { + conf[i].Alias = conf[i].Path + } + } return conf, nil } diff --git a/insomnia.json b/insomnia.json new file mode 100644 index 0000000..847343a --- /dev/null +++ b/insomnia.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2021-10-18T15:51:18.310Z","__export_source":"insomnia.desktop.app:v2021.5.3","resources":[{"_id":"req_7eb6d13474e2479e8f5cac3bb28f7f29","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634571223642,"created":1634571078462,"url":"http://localhost:8000/api/conf","name":"Show Config","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1634571078462,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"wrk_46dde22d3b1145ceb7411d9ddc17245a","parentId":null,"modified":1634570161701,"created":1634570161701,"name":"configui","description":"","scope":"collection","_type":"workspace"},{"_id":"req_31c3456a55a24b1580de9f13bf056918","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634571965999,"created":1634571204837,"url":"http://localhost:8000/api/conf","name":"Load Config","description":"","method":"POST","body":{"mimeType":"application/json","text":"[\n\t{\n\t\t\"path\": \"configui.log\",\n\t\t\"ro\": true\n\t},\n\t{\n\t\t\"path\": \"etc/test.ini\",\n\t\t\"name\": \"test\",\n\t\t\"action\": \"myip local -P\"\n\t}\n]"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_dc33e6fdfea940f0af7ab5f925b8b867"}],"authentication":{},"metaSortKey":-1634570625895.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_7f75a3d870ea4a29b10b54b9d2004ea2","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634570198860,"created":1634570173329,"url":"http://localhost:8000/api/files","name":"Files","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1634570173329,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_4519ef23793946d387ed5f98dc507c85","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634570969030,"created":1634570934596,"url":"http://localhost:8000/api/file?name=test","name":"File","description":"","method":"GET","body":{},"parameters":[{"id":"pair_32ef04caabf14895b391647fabc31288","name":"name","value":"test","description":""}],"headers":[],"authentication":{},"metaSortKey":-1634570173279,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0cb4cd8fa9f44e418c793b63835f40ad","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634571759278,"created":1634570991554,"url":"http://localhost:8000/api/file","name":"update","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"data\": \"etsdfdfdf\",\n \"name\": \"test\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_5d66341edfad486c88ac08ecac888db1"}],"authentication":{},"metaSortKey":-1634570173229,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_8b1dfca7c8244afbb10a1f5c3fe64d11","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634571051488,"created":1634571036093,"url":"http://localhost:8000/api/apply","name":"Apply","description":"","method":"POST","body":{},"parameters":[{"id":"pair_bfa413c408bd4cac99f46e628f792a0d","name":"name","value":"test","description":""}],"headers":[],"authentication":{},"metaSortKey":-1634570173179,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_9073006491a14b6a809639e18ff6ca85","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634571246901,"created":1634571234047,"url":"http://localhost:8000/api/export","name":"Export","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1634570173129,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_4e515d96f6c4fa38d50cad941df2c9138d512379","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634570161708,"created":1634570161708,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1634570161708,"_type":"environment"},{"_id":"jar_4e515d96f6c4fa38d50cad941df2c9138d512379","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634570161710,"created":1634570161710,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_ef85b6308d024670ac484f0646a64ebc","parentId":"wrk_46dde22d3b1145ceb7411d9ddc17245a","modified":1634570161703,"created":1634570161703,"fileName":"configui","contents":"","contentType":"yaml","_type":"api_spec"}]} \ No newline at end of file diff --git a/main.go b/main.go index 349ae80..e798662 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "embed" - "errors" "flag" "fmt" "html/template" @@ -28,8 +27,11 @@ var ( flagBind string flagLogFile string + flagVer bool ) +var Version = "0.0.0" +var Build = "alpha" var files = map[string]*configui.File{} func init() { @@ -41,6 +43,7 @@ func init() { flag.StringVar(&flagAction, "c", "", "cmd to apply") flag.StringVar(&flagLogFile, "log", "", "log to file") flag.StringVar(&flagBind, "bind", "localhost:8000", "address to bind") + flag.BoolVar(&flagVer, "v", false, "show version") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: configui [options]\n") flag.PrintDefaults() @@ -50,6 +53,10 @@ func init() { func main() { flag.Parse() + if flagVer { + fmt.Printf("%v - %v\n", Version, Build) + } + // setup logging if flagLogFile != "" { f, err := os.OpenFile(flagLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) @@ -72,7 +79,7 @@ func main() { Action: flagAction, } } else if flagConfigPath == "" { - log.Fatalln(errors.New("no config found")) + log.Println("no config found") } else { conf, err := os.ReadFile(flagConfigPath) if err != nil { @@ -87,7 +94,15 @@ func main() { // setup routes mux := http.NewServeMux() - // mux. + mux.HandleFunc("/api/conf", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + GetConfig(w, r) + } else if r.Method == "POST" { + LoadConfig(w, r) + } else { + w.WriteHeader(404) + } + }) mux.HandleFunc("/api/files", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { ListFiles(w, r) diff --git a/util.go b/util.go index f77bbaa..75c2874 100644 --- a/util.go +++ b/util.go @@ -7,11 +7,19 @@ import ( "log" "net/http" "os" + "strings" ) func LogMiddleware(next http.Handler) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { + defer func() { + r := recover() + if r != nil { + log.Println("panic", r) + w.WriteHeader(500) + } + }() next.ServeHTTP(w, r) log.Printf("%s %s %s %s\n", r.RemoteAddr, r.Method, r.URL, r.Header.Get("User-Agent")) }, @@ -47,6 +55,11 @@ func bundle(buf io.Writer, paths []string, flat bool) error { // not be preserved // https://golang.org/src/archive/tar/common.go?#L626 if !flat { + if !strings.HasPrefix(file, "/") { + file = "configui/" + file + } else { + file = "configui" + file + } header.Name = file } @@ -70,3 +83,8 @@ func bundle(buf io.Writer, paths []string, flat bool) error { return nil } + +func HttpWriter(w http.ResponseWriter, status int, errStr string) { + w.WriteHeader(status) + w.Write([]byte(errStr)) +}