update
parent
ef4a8021a5
commit
95e9383138
|
@ -0,0 +1,102 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"kumoly.io/tools/configui/configui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := json.Marshal(files)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := file.Read()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.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 {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := configui.File{}
|
||||||
|
if err := json.Unmarshal(data, &f); err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, ok := files[f.Alias]
|
||||||
|
if !ok {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
w.Write([]byte("file not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := file.Write([]byte(f.Data)); err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := file.Do()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write([]byte(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Download(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fs := []string{}
|
||||||
|
for _, v := range files {
|
||||||
|
fs = append(fs, v.Path)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
|
||||||
|
bundle(w, fs, false)
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package configui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -16,6 +17,7 @@ type File struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Alias string `json:"name"`
|
Alias string `json:"name"`
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
|
RO bool `json:"ro"`
|
||||||
|
|
||||||
// used for parsing post data
|
// used for parsing post data
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
|
@ -35,6 +37,9 @@ func (f *File) Read() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Write(data []byte) error {
|
func (f *File) Write(data []byte) error {
|
||||||
|
if f.RO {
|
||||||
|
return errors.New("this file has readonly set")
|
||||||
|
}
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
info, err := os.Stat(f.Path)
|
info, err := os.Stat(f.Path)
|
||||||
|
|
186
main.go
186
main.go
|
@ -3,12 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -22,24 +21,26 @@ var tmplFS embed.FS
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cmdPath string
|
flagPath string
|
||||||
cmdAction string
|
flagAction string
|
||||||
cmdAlias string
|
flagAlias string
|
||||||
cmdConfigPath string
|
flagConfigPath string
|
||||||
|
|
||||||
cmdBind string
|
flagBind string
|
||||||
|
flagLogFile string
|
||||||
)
|
)
|
||||||
|
|
||||||
var files = map[string]*configui.File{}
|
var files = map[string]*configui.File{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.SetFlags(0)
|
// log.SetFlags(0)
|
||||||
|
|
||||||
flag.StringVar(&cmdConfigPath, "f", "", "path to config file")
|
flag.StringVar(&flagConfigPath, "f", "", "path to config file")
|
||||||
flag.StringVar(&cmdPath, "p", "", "path to file, precedence over config")
|
flag.StringVar(&flagPath, "p", "", "path to file, precedence over config")
|
||||||
flag.StringVar(&cmdAlias, "n", "", "alias of file")
|
flag.StringVar(&flagAlias, "n", "", "alias of file")
|
||||||
flag.StringVar(&cmdAction, "c", "", "cmd to apply")
|
flag.StringVar(&flagAction, "c", "", "cmd to apply")
|
||||||
flag.StringVar(&cmdBind, "b", "localhost:8000", "address to bind")
|
flag.StringVar(&flagLogFile, "log", "", "log to file")
|
||||||
|
flag.StringVar(&flagBind, "bind", "localhost:8000", "address to bind")
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: configui [options]\n")
|
fmt.Fprintf(os.Stderr, "Usage: configui [options]\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
|
@ -49,20 +50,31 @@ func init() {
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// 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
|
// setup values
|
||||||
if cmdPath != "" {
|
if flagPath != "" {
|
||||||
if cmdAlias == "" {
|
if flagAlias == "" {
|
||||||
cmdAlias = cmdPath
|
flagAlias = flagPath
|
||||||
}
|
}
|
||||||
files[cmdAlias] = &configui.File{
|
files[flagAlias] = &configui.File{
|
||||||
Path: cmdPath,
|
Path: flagPath,
|
||||||
Alias: cmdAlias,
|
Alias: flagAlias,
|
||||||
Action: cmdAction,
|
Action: flagAction,
|
||||||
}
|
}
|
||||||
} else if cmdConfigPath == "" {
|
} else if flagConfigPath == "" {
|
||||||
log.Fatalln(errors.New("no config found"))
|
log.Fatalln(errors.New("no config found"))
|
||||||
} else {
|
} else {
|
||||||
conf, err := os.ReadFile(cmdConfigPath)
|
conf, err := os.ReadFile(flagConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
@ -75,134 +87,62 @@ func main() {
|
||||||
|
|
||||||
// setup routes
|
// setup routes
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/api/files", func(rw http.ResponseWriter, r *http.Request) {
|
// mux.
|
||||||
|
mux.HandleFunc("/api/files", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
ListFiles(rw, r)
|
ListFiles(w, r)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/api/file", func(rw http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/file", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
GetFile(rw, r)
|
GetFile(w, r)
|
||||||
} else if r.Method == "POST" {
|
} else if r.Method == "POST" {
|
||||||
PostFile(rw, r)
|
PostFile(w, r)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/api/apply", func(rw http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/apply", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
Apply(rw, r)
|
Apply(w, r)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/export", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
Download(w, r)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
err := tmpl.ExecuteTemplate(buf, "home", nil)
|
err := tmpl.ExecuteTemplate(buf, "home", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
rw.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
} else {
|
} else {
|
||||||
rw.Write(buf.Bytes())
|
w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tmpl = template.Must(template.New("").ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
|
tmpl = template.Must(template.New("").ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: cmdBind,
|
Addr: flagBind,
|
||||||
WriteTimeout: time.Second * 3,
|
WriteTimeout: time.Second * 3,
|
||||||
ReadTimeout: time.Second * 30,
|
ReadTimeout: time.Second * 30,
|
||||||
Handler: mux,
|
Handler: LogMiddleware(mux),
|
||||||
}
|
}
|
||||||
|
|
||||||
// start server
|
// start server
|
||||||
log.Println("Listening on ", cmdBind)
|
log.Println("Listening on", flagBind)
|
||||||
log.Fatal(server.ListenAndServe())
|
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))
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
log.Printf("%s %s %s %s\n", r.RemoteAddr, r.Method, r.URL, r.Header.Get("User-Agent"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bundle(buf io.Writer, paths []string, flat bool) error {
|
||||||
|
gw := gzip.NewWriter(buf)
|
||||||
|
defer gw.Close()
|
||||||
|
tw := tar.NewWriter(gw)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
for _, file := range paths {
|
||||||
|
if err := func(file string) error {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
info, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use full path as name (FileInfoHeader only takes the basename)
|
||||||
|
// If we don't do this the directory strucuture would
|
||||||
|
// not be preserved
|
||||||
|
// https://golang.org/src/archive/tar/common.go?#L626
|
||||||
|
if !flat {
|
||||||
|
header.Name = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file header to the tar archive
|
||||||
|
err = tw.WriteHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file content to tar archive
|
||||||
|
_, err = io.Copy(tw, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}(file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue