package main import ( "bytes" "embed" "encoding/json" "errors" "flag" "fmt" "html/template" "io/ioutil" "log" "net/http" "os" "time" "kumoly.io/tools/configui/configui" ) //go:embed templates var tmplFS embed.FS var tmpl *template.Template var ( cmdPath string cmdAction string cmdAlias string cmdConfigPath string cmdBind string ) var files = map[string]*configui.File{} func init() { log.SetFlags(0) flag.StringVar(&cmdConfigPath, "f", "", "path to config file") flag.StringVar(&cmdPath, "p", "", "path to file, precedence over config") flag.StringVar(&cmdAlias, "n", "", "alias of file") flag.StringVar(&cmdAction, "c", "", "cmd to apply") flag.StringVar(&cmdBind, "b", "localhost:8000", "address to bind") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: configui [options]\n") flag.PrintDefaults() } } func main() { flag.Parse() // setup values if cmdPath != "" { if cmdAlias == "" { cmdAlias = cmdPath } files[cmdAlias] = &configui.File{ Path: cmdPath, Alias: cmdAlias, Action: cmdAction, } } else if cmdConfigPath == "" { log.Fatalln(errors.New("no config found")) } else { conf, err := os.ReadFile(cmdConfigPath) if err != nil { log.Fatalln(err) } ftmp, err := configui.ReadConfig(string(conf)) if err != nil { log.Fatalln(err) } files = configui.GetFileMap(ftmp) } // setup routes mux := http.NewServeMux() mux.HandleFunc("/api/files", func(rw http.ResponseWriter, r *http.Request) { if r.Method == "GET" { ListFiles(rw, r) } else { rw.WriteHeader(404) } }) mux.HandleFunc("/api/file", func(rw http.ResponseWriter, r *http.Request) { if r.Method == "GET" { GetFile(rw, r) } else if r.Method == "POST" { PostFile(rw, r) } else { rw.WriteHeader(404) } }) mux.HandleFunc("/api/apply", func(rw http.ResponseWriter, r *http.Request) { if r.Method == "POST" { Apply(rw, r) } else { rw.WriteHeader(404) } }) mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { buf := &bytes.Buffer{} err := tmpl.ExecuteTemplate(buf, "home", nil) if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) } else { rw.Write(buf.Bytes()) } }) tmpl = template.Must(template.New("").ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl")) server := &http.Server{ Addr: cmdBind, WriteTimeout: time.Second * 3, ReadTimeout: time.Second * 30, Handler: mux, } // start server log.Println("Listening on ", cmdBind) log.Fatal(server.ListenAndServe()) } func ListFiles(rw http.ResponseWriter, r *http.Request) { data, err := json.Marshal(files) if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } rw.Write(data) } func GetFile(rw http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, ok := files[name] if name == "" || !ok { rw.WriteHeader(404) rw.Write([]byte("file not found")) return } data, err := file.Read() if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } response, err := json.Marshal(map[string]string{ "path": file.Path, "name": file.Alias, "action": file.Action, "data": string(data), }) if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } rw.Header().Set("Content-Type", "application/json") rw.Write(response) } func PostFile(rw http.ResponseWriter, r *http.Request) { data, err := ioutil.ReadAll(r.Body) r.Body.Close() if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } f := configui.File{} if err := json.Unmarshal(data, &f); err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } file, ok := files[f.Alias] if !ok { rw.WriteHeader(404) rw.Write([]byte("file not found")) return } if err := file.Write([]byte(f.Data)); err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } rw.Write([]byte("ok")) } func Apply(rw http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") file, ok := files[name] if name == "" || !ok { rw.WriteHeader(404) rw.Write([]byte("file not found")) return } result, err := file.Do() if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } rw.Write([]byte(result)) }