From 863b489412bc3c87f3c1803958458db65117d157 Mon Sep 17 00:00:00 2001
From: Evan Chen
Date: Thu, 21 Oct 2021 21:49:15 +0800
Subject: [PATCH 1/5] refacting
---
Dockerfile | 2 +-
Makefile | 18 ++-
README.md | 6 +-
api.go | 155 -------------------
app.go | 100 +++++++++++++
main.go => cmd/configui/main.go | 81 +++-------
configui.go | 121 +++++++++++++++
insomnia.json => docs/insomnia.json | 0
configui/file.go => file.go | 27 ++--
handler.go | 1 +
netutil.go | 61 ++------
public/ace/js/mode-css.js | 2 +-
route.go | 101 -------------
server.go | 221 ++++++++++++++++++++++++++++
src/main.js | 14 +-
templates/components/menu.tmpl | 8 +-
templates/components/result.tmpl | 2 +-
templates/components/toolbar.tmpl | 24 +--
templates/home.tmpl | 4 +-
util.go | 31 ++--
20 files changed, 549 insertions(+), 430 deletions(-)
delete mode 100644 api.go
create mode 100644 app.go
rename main.go => cmd/configui/main.go (52%)
create mode 100644 configui.go
rename insomnia.json => docs/insomnia.json (100%)
rename configui/file.go => file.go (80%)
create mode 100644 handler.go
delete mode 100644 route.go
create mode 100644 server.go
diff --git a/Dockerfile b/Dockerfile
index 200032e..7c80cdf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@ COPY . .
RUN VERSION=$(git describe --tags --abbrev=0) BUILD=$(git rev-parse --short HEAD) && \
GOOS=linux GOARCH=amd64 \
go build -ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w" \
- -o /go/bin/configui
+ -o /go/bin/configui cmd/configui/main.go
FROM alpine:3.14
diff --git a/Makefile b/Makefile
index b0b47ee..e44149f 100644
--- a/Makefile
+++ b/Makefile
@@ -20,29 +20,33 @@ clean:
run: build
$(shell cd dist; ./${PROJ} -log configui.log)
-.PHONY: build
-build:
+.PHONY: web
+web:
npm run build
- go build ${LDFLAGS} -o dist/${PROJ}
+ # npm run js-dev
+
+.PHONY: build
+build: web
+ go build ${LDFLAGS} -o dist/${PROJ} cmd/$(PROJ)/main.go
build-unix:
$(foreach GOOS, $(PLATFORMS), $(foreach GOARCH, $(ARCHITECTURES), $(foreach APP, $(APPS),\
- $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); go build ${LDFLAGS} -o dist/$(APP)) \
+ $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); go build ${LDFLAGS} -o dist/$(APP) cmd/$(APP)/main.go) \
$(shell cd dist; tar -czf ${APP}_$(VERSION)_$(GOOS)_$(GOARCH).tar.gz ${APP}) \
$(shell rm dist/${APP}) \
)))
build-win:
$(foreach APP, $(APPS), \
- $(shell export GOOS=windows; export GOARCH=amd64; go build ${LDFLAGS} -o dist/${APP}.exe) \
+ $(shell export GOOS=windows; export GOARCH=amd64; go build ${LDFLAGS} -o dist/${APP}.exe cmd/$(APP)/main.go) \
$(shell cd dist; tar -czf ${APP}_$(VERSION)_windows_amd64.tar.gz ${APP}.exe) \
$(shell rm dist/${APP}.exe) \
)
build-mac-m1:
$(foreach APP, $(APPS),\
- $(shell export GOOS=darwin; export GOARCH=arm64; go build ${LDFLAGS} -o dist/$(APP)) \
+ $(shell export GOOS=darwin; export GOARCH=arm64; go build ${LDFLAGS} -o dist/$(APP) cmd/$(APP)/main.go) \
$(shell cd dist; tar -czf ${APP}_$(VERSION)_darwin_arm64.tar.gz ${APP}) \
$(shell rm dist/${APP}) \
)
@@ -67,4 +71,4 @@ docker-save:
.PHONY: release
-release: clean binary docker docker-save
\ No newline at end of file
+release: clean web binary docker docker-save
\ No newline at end of file
diff --git a/README.md b/README.md
index f2c1af2..ccecef4 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Usage: configui [options]
-log string
log to file
-n string
- alias of file
+ Name of file
-p string
path to file, precedence over config
-static
@@ -84,7 +84,7 @@ res:
### File
-`GET /api/file?name=ALIAS`
+`GET /api/file?name=Name`
res:
```json
@@ -110,4 +110,4 @@ req:
### Apply
-`POST /api/apply?name=ALIAS`
+`POST /api/apply?name=Name`
diff --git a/api.go b/api.go
deleted file mode 100644
index f5ac9b8..0000000
--- a/api.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "path/filepath"
-
- "kumoly.io/tools/configui/configui"
-)
-
-func ListFiles(w http.ResponseWriter, r *http.Request) {
- data, err := json.Marshal(files)
- if err != nil {
- panic(err)
- }
- w.Write(data)
-}
-
-func GetFile(w http.ResponseWriter, r *http.Request) {
- name := r.URL.Query().Get("name")
- file, ok := files[name]
- if name == "" || !ok {
- MakeResponse(w, 404, []byte("file not found"))
- return
- }
- data, err := file.Read()
- if err != nil {
- panic(err)
- }
- response, err := json.Marshal(map[string]string{
- "path": file.Path,
- "name": file.Alias,
- "action": file.Action,
- "data": string(data),
- })
- if err != nil {
- panic(err)
- }
- 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 {
- panic(err)
- }
- f := configui.File{}
- if err := json.Unmarshal(data, &f); err != nil {
- panic(err)
- }
- file, ok := files[f.Alias]
- if !ok {
- MakeResponse(w, 404, []byte("file not found"))
- return
- }
- if err := file.Write([]byte(f.Data)); err != nil {
- panic(err)
- }
- 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 {
- MakeResponse(w, 404, []byte("file not found"))
- return
- }
- result, err := file.Do()
- log.Println(err)
- if err != nil {
- panic(err)
- }
- w.Write([]byte(result))
-}
-
-func Download(w http.ResponseWriter, r *http.Request) {
- if name := r.URL.Query().Get("name"); name != "" {
- if name == "ConfigUI" {
- data, err := GetConfig()
- if err != nil {
- panic(err)
- }
- w.Header().Set("Content-Disposition", `attachment; filename="ConfigUI.json"`)
- w.Write(data)
- return
- }
- file, ok := files[name]
- if !ok {
- MakeResponse(w, 404, []byte("file not found"))
- return
- }
- data, err := file.Read()
- if err != nil {
- panic(err)
- }
- w.Header().Set("Content-Disposition", `attachment; filename="`+filepath.Base(file.Path)+`"`)
- w.Write(data)
- return
- }
- fs := []string{}
- for _, v := range files {
- fs = append(fs, v.Path)
- }
- if flagConfigPath != "" {
- fs = append(fs, flagConfigPath)
- }
- w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
- bundle(w, fs, false)
-}
-
-func LoadConfig(w http.ResponseWriter, r *http.Request) {
- if flagNoReconfig {
- panic("system reconfig is disabled")
- }
- data, err := ioutil.ReadAll(r.Body)
- r.Body.Close()
- if err != nil {
- panic(err)
- }
- ftmp, err := configui.ReadConfig(string(data))
- if err != nil {
- panic(err)
- }
- if flagConfigPath != "" {
- info, err := os.Stat(flagConfigPath)
- if err != nil {
- panic(err)
- }
- os.WriteFile(flagConfigPath, data, info.Mode())
- }
- files = configui.GetFileMap(ftmp)
- w.Write([]byte("ok"))
-}
-
-func getConfigHandler(w http.ResponseWriter, r *http.Request) {
- data, err := GetConfig()
- if err != nil {
- panic(err)
- }
- w.Write(data)
-}
-
-func GetConfig() ([]byte, error) {
- config := []configui.File{}
- for _, f := range files {
- config = append(config, *f)
- }
- return json.MarshalIndent(config, "", " ")
-}
diff --git a/app.go b/app.go
new file mode 100644
index 0000000..b69178e
--- /dev/null
+++ b/app.go
@@ -0,0 +1,100 @@
+package configui
+
+import (
+ "net/http"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+)
+
+type ActiveFile struct {
+ RO bool
+ Path string
+ Name string
+ Action string
+ Content string
+}
+
+type Editor struct {
+ Lang string
+ Platform string
+}
+
+type Page struct {
+ AppName string
+ Files []ActiveFile
+ Error string
+ File ActiveFile
+ Editor Editor
+ Static bool
+}
+
+func (cui *ConfigUI) App(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+
+ Files := []ActiveFile{}
+ for _, i := range cui.fileIndex {
+ Files = append(Files, ActiveFile{
+ Name: cui.Files[i].Name,
+ Path: cui.Files[i].Path,
+ })
+ }
+ sort.Slice(Files, func(i, j int) bool { return Files[i].Name < Files[j].Name })
+ plat := "unix"
+ if runtime.GOOS == "windows" {
+ plat = "windows"
+ }
+ data := Page{
+ AppName: cui.AppName,
+ File: ActiveFile{},
+ Files: Files,
+ Editor: Editor{
+ Platform: plat,
+ },
+ Static: cui.NoReconfig,
+ }
+
+ content := ""
+ var tmp []byte
+ var err error
+ name := r.URL.Query().Get("name")
+ file, err := cui.File(name)
+ if name == "" || err != nil {
+ tmp, err = cui.Config()
+ data.File.Name = "ConfigUI"
+ data.File.Path = ":mem:"
+ data.Editor.Lang = "json"
+ if name != "" {
+ data.Error = name + " not found\n"
+ }
+ } else {
+ tmp, err = file.Read()
+ data.File.Action = file.Action
+ data.File.Name = file.Name
+ if file.Lang != "" {
+ data.Editor.Lang = file.Lang
+ } else {
+ ext := strings.TrimPrefix(filepath.Ext(file.Path), ".")
+ if Ext2Mode[ext] != "" {
+ ext = Ext2Mode[ext]
+ }
+ data.Editor.Lang = ext
+ }
+ data.File.RO = file.RO
+ data.File.Path = file.Path
+ }
+ if err != nil {
+ data.Error = err.Error()
+ data.Editor.Lang = ""
+ } else {
+ content = string(tmp)
+ }
+
+ data.File.Content = content
+
+ cui.Parse(w, "home", data)
+}
diff --git a/main.go b/cmd/configui/main.go
similarity index 52%
rename from main.go
rename to cmd/configui/main.go
index 7291790..60a3bff 100644
--- a/main.go
+++ b/cmd/configui/main.go
@@ -1,28 +1,21 @@
package main
import (
- "embed"
"flag"
"fmt"
- "html/template"
"io"
"log"
"net/http"
"os"
"time"
- "kumoly.io/tools/configui/configui"
- "kumoly.io/tools/configui/public"
+ "kumoly.io/tools/configui"
)
-//go:embed templates
-var tmplFS embed.FS
-var tmpl *template.Template
-
var (
flagPath string
flagAction string
- flagAlias string
+ flagName string
flagConfigPath string
flagBind string
@@ -34,14 +27,13 @@ var (
var Version = "0.0.0"
var Build = "alpha"
-var files = map[string]*configui.File{}
func init() {
// log.SetFlags(0)
flag.StringVar(&flagConfigPath, "f", "", "path to config file")
flag.StringVar(&flagPath, "p", "", "path to file, precedence over config")
- flag.StringVar(&flagAlias, "n", "", "alias of file")
+ flag.StringVar(&flagName, "n", "", "Name of file")
flag.StringVar(&flagAction, "c", "", "cmd to apply")
flag.StringVar(&flagLogFile, "log", "", "log to file")
flag.StringVar(&flagAllow, "allow", "", "IPs to allow, blank to allow all")
@@ -62,6 +54,8 @@ func main() {
return
}
+ cui := configui.New()
+
// setup logging
if flagLogFile != "" {
f, err := os.OpenFile(flagLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
@@ -75,78 +69,39 @@ func main() {
// setup values
if flagPath != "" {
- if flagAlias == "" {
- flagAlias = flagPath
+ if flagName == "" {
+ flagName = flagPath
}
- files[flagAlias] = &configui.File{
+ file := &configui.File{
Path: flagPath,
- Alias: flagAlias,
+ Name: flagName,
Action: flagAction,
}
+ if err := cui.AppendFile(file); err != nil {
+ log.Fatalln(err)
+ }
} else if flagConfigPath == "" {
- log.Println("no config found")
+ log.Println("no config specified")
} else {
conf, err := os.ReadFile(flagConfigPath)
if err != nil {
log.Fatalln(err)
}
- ftmp, err := configui.ReadConfig(string(conf))
+ cui.LoadConfig(string(conf))
if err != nil {
log.Fatalln(err)
}
- files = configui.GetFileMap(ftmp)
}
- // setup routes
- mux := http.NewServeMux()
- mux.HandleFunc("/api/conf", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == "GET" {
- getConfigHandler(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)
- } else {
- w.WriteHeader(404)
- }
- })
- mux.HandleFunc("/api/file", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == "GET" {
- GetFile(w, r)
- } else if r.Method == "POST" {
- PostFile(w, r)
- } else {
- w.WriteHeader(404)
- }
- })
- mux.HandleFunc("/api/apply", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == "POST" {
- Apply(w, r)
- } else {
- w.WriteHeader(404)
- }
- })
- mux.HandleFunc("/api/export", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == "GET" {
- Download(w, r)
- } else {
- w.WriteHeader(404)
- }
- })
+ cui.LogPath = flagLogFile
+ cui.ConfigPath = flagConfigPath
- mux.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(public.FS))))
- tmpl = template.Must(template.New("").ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
- setRoutes(mux)
+ // setup routes
server := &http.Server{
Addr: flagBind,
WriteTimeout: time.Second * 3,
ReadTimeout: time.Second * 30,
- Handler: Middleware(mux),
+ Handler: cui,
}
// start server
diff --git a/configui.go b/configui.go
new file mode 100644
index 0000000..8e22cc3
--- /dev/null
+++ b/configui.go
@@ -0,0 +1,121 @@
+package configui
+
+import (
+ "embed"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "html/template"
+
+ "kumoly.io/tools/configui/public"
+)
+
+var UNIX_SHELL = "sh"
+var WIN_SHELL = "cmd"
+
+//go:embed templates
+var tmplFS embed.FS
+
+var Ext2Mode map[string]string = map[string]string{
+ "go": "golang",
+ "log": "sh",
+ "txt": "text",
+ "yml": "yaml",
+ "conf": "ini",
+}
+
+type ConfigUI struct {
+ AppName string `json:"app_name"`
+ ConfigPath string `json:"config_path"`
+
+ NoReconfig bool `json:"no_reconfig"`
+ AllowIP string `json:"allow_ip"`
+
+ Files []*File `json:"files"`
+ fileIndex map[string]int
+
+ // TODO
+ ResultBellow bool `json:"result_bellow"`
+ HideConfig bool `json:"hide_config"`
+
+ // Should be in main app
+ LogPath string `json:"log_path"`
+ SilentSysOut bool `json:"silent_sys_out"`
+
+ TmplFS embed.FS `json:"-"`
+ tmpl *template.Template
+ PublicFS embed.FS `json:"-"`
+}
+
+func New() *ConfigUI {
+ tmpl := template.Must(template.New("").ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
+ return &ConfigUI{
+ fileIndex: map[string]int{},
+ AppName: "ConfigUI",
+ PublicFS: public.FS,
+ TmplFS: tmplFS,
+ tmpl: tmpl,
+ }
+}
+
+func (cui *ConfigUI) File(file_name string) (*File, error) {
+ if file_name == cui.AppName {
+ return &File{
+ Path: cui.ConfigPath,
+ Name: cui.AppName,
+ Lang: "json",
+ }, nil
+ }
+ index, ok := cui.fileIndex[file_name]
+ if !ok {
+ return nil, errors.New("no file found")
+ }
+ return cui.Files[index], nil
+}
+
+func (cui *ConfigUI) LoadConfig(confstr string) error {
+ tmpConf := &ConfigUI{}
+ err := json.Unmarshal([]byte(confstr), tmpConf)
+ if err != nil {
+ return nil
+ }
+
+ // construct fileIndex
+ tmpIndex := map[string]int{}
+ for i, f := range tmpConf.Files {
+ if f.Name == "" {
+ f.Name = f.Path
+ }
+ tmpIndex[f.Name] = i
+ }
+
+ // copy
+ cui.fileIndex = tmpIndex
+ cui.Files = tmpConf.Files
+ cui.AllowIP = tmpConf.AllowIP
+ cui.ConfigPath = tmpConf.ConfigPath
+ cui.HideConfig = tmpConf.HideConfig
+ cui.LogPath = tmpConf.LogPath
+ cui.NoReconfig = tmpConf.NoReconfig
+ cui.ResultBellow = tmpConf.ResultBellow
+ cui.SilentSysOut = tmpConf.SilentSysOut
+ // fmt.Printf("%+v", cui)
+ return nil
+}
+
+func (cui *ConfigUI) Config() ([]byte, error) {
+ return json.MarshalIndent(cui, "", " ")
+}
+
+func (cui *ConfigUI) AppendFile(file *File) error {
+ if file.Name == "" {
+ file.Name = file.Path
+ }
+ i, ok := cui.fileIndex[file.Name]
+ if ok {
+ return fmt.Errorf("%v already exists at %d", file.Name, i)
+ }
+ cui.fileIndex[file.Name] = len(cui.Files)
+ cui.Files = append(cui.Files, file)
+ return nil
+}
diff --git a/insomnia.json b/docs/insomnia.json
similarity index 100%
rename from insomnia.json
rename to docs/insomnia.json
diff --git a/configui/file.go b/file.go
similarity index 80%
rename from configui/file.go
rename to file.go
index b43d83d..a9dd62c 100644
--- a/configui/file.go
+++ b/file.go
@@ -11,12 +11,9 @@ import (
"time"
)
-var UNIX_SHELL = "sh"
-var WIN_SHELL = "cmd"
-
type File struct {
Path string `json:"path"`
- Alias string `json:"name"`
+ Name string `json:"name"`
Action string `json:"action"`
RO bool `json:"ro"`
Lang string `json:"lang"`
@@ -42,6 +39,7 @@ func (f *File) Write(data []byte) error {
if f.RO {
return errors.New("this file has readonly set")
}
+ log.Println("dfsdf")
f.lock.Lock()
defer f.lock.Unlock()
info, err := os.Stat(f.Path)
@@ -52,6 +50,7 @@ func (f *File) Write(data []byte) error {
}
func (f *File) Do() (string, error) {
+ log.Println("do ", f.Action)
if f.Action == "" {
return "", nil
}
@@ -62,22 +61,24 @@ func (f *File) Do() (string, error) {
} else {
exec.Command(UNIX_SHELL, "-c", f.Action)
}
- var out []byte
- done := make(chan struct{}, 1)
+ done := make(chan string, 1)
+ log.Println("start ", f.Action)
go func() {
- out, _ = cmd.CombinedOutput()
+ out, _ := cmd.CombinedOutput()
// real cmd err is only pass down
// if err != nil {
// return string(out), err
// }
- done <- struct{}{}
+ done <- string(out)
}()
select {
case <-timeout:
cmd.Process.Kill()
+ log.Println("timeout")
return "", errors.New("command timeout")
- case <-done:
- return string(out), nil
+ case out := <-done:
+ log.Println(out)
+ return out, nil
}
}
@@ -88,8 +89,8 @@ func ReadConfig(confstr string) ([]File, error) {
return nil, err
}
for i := range conf {
- if conf[i].Alias == "" {
- conf[i].Alias = conf[i].Path
+ if conf[i].Name == "" {
+ conf[i].Name = conf[i].Path
}
}
return conf, nil
@@ -98,7 +99,7 @@ func ReadConfig(confstr string) ([]File, error) {
func GetFileMap(files []File) map[string]*File {
fileMap := map[string]*File{}
for i := range files {
- fileMap[files[i].Alias] = &files[i]
+ fileMap[files[i].Name] = &files[i]
}
return fileMap
}
diff --git a/handler.go b/handler.go
new file mode 100644
index 0000000..73d4077
--- /dev/null
+++ b/handler.go
@@ -0,0 +1 @@
+package configui
diff --git a/netutil.go b/netutil.go
index 3727b76..dbd54a1 100644
--- a/netutil.go
+++ b/netutil.go
@@ -1,20 +1,18 @@
-package main
+package configui
import (
"bytes"
"log"
- "net"
"net/http"
"strconv"
- "strings"
)
-type ResponseWriter struct {
+type CuiResponseWriter struct {
http.ResponseWriter
StatueCode int
}
-func (w *ResponseWriter) WriteHeader(statusCode int) {
+func (w *CuiResponseWriter) WriteHeader(statusCode int) {
if w.StatueCode != 0 {
return
}
@@ -22,19 +20,19 @@ func (w *ResponseWriter) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
}
-func (w *ResponseWriter) Write(body []byte) (int, error) {
+func (w *CuiResponseWriter) Write(body []byte) (int, error) {
if w.StatueCode == 0 {
w.WriteHeader(200)
}
return w.ResponseWriter.Write(body)
}
-func MakeResponse(w http.ResponseWriter, status int, body []byte) (int, error) {
+func response(w http.ResponseWriter, status int, body []byte) (int, error) {
w.WriteHeader(status)
return w.Write(body)
}
-func AbortError(w http.ResponseWriter, err interface{}) (int, error) {
+func abort(w http.ResponseWriter, err interface{}) (int, error) {
switch v := err.(type) {
case int:
w.WriteHeader(v)
@@ -51,56 +49,17 @@ func AbortError(w http.ResponseWriter, err interface{}) (int, error) {
}
}
-func Catch(rw *ResponseWriter, r *http.Request) {
+func catch(rw *CuiResponseWriter, r *http.Request) {
ex := recover()
if ex != nil {
- AbortError(rw, ex)
+ 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 Middleware(next http.Handler) http.Handler {
- return http.HandlerFunc(
- func(w http.ResponseWriter, r *http.Request) {
- rw := &ResponseWriter{w, 0}
- defer Catch(rw, r)
- abort := false
- if flagAllow != "" {
- if !matchIPGlob(GetIP(r), flagAllow) {
- MakeResponse(rw, 403, []byte("permission denyed"))
- abort = true
- }
- }
- if !abort {
- next.ServeHTTP(rw, r)
- }
- if !strings.HasPrefix(r.URL.Path, "/public") && r.URL.Query().Get("f") != "true" {
- log.Printf("%s %s %d %s %s\n", GetIP(r), r.Method, rw.StatueCode, r.URL, r.Header.Get("User-Agent"))
- }
- },
- )
-}
-
-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
-}
-
-func Parse(w http.ResponseWriter, name string, data interface{}) error {
+func (cui *ConfigUI) Parse(w http.ResponseWriter, name string, data interface{}) error {
buf := &bytes.Buffer{}
- err := tmpl.ExecuteTemplate(buf, "home", data)
+ err := cui.tmpl.ExecuteTemplate(buf, "home", data)
if err != nil {
panic(err)
}
diff --git a/public/ace/js/mode-css.js b/public/ace/js/mode-css.js
index f8d6f3c..13cdcc4 100644
--- a/public/ace/js/mode-css.js
+++ b/public/ace/js/mode-css.js
@@ -1,4 +1,4 @@
-ace.define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom",f=t.supportConstantColor="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"paren.rparen",regex:"\\}"},{token:"string",regex:"@(?!viewport)",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"keyword",regex:"%"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant.numeric",regex:c},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"start"},{token:"paren.rparen",regex:"\\}",next:"start"},{token:"string",regex:";",next:"start"},{token:"keyword",regex:"(?:media|supports|document|charset|import|namespace|media|supports|document|page|font|keyframes|viewport|counter-style|font-feature-values|swash|ornaments|annotation|stylistic|styleset|character-variant)"}],comments:[{token:"comment",regex:"\\/\\*",push:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}]}],ruleset:[{regex:"-(webkit|ms|moz|o)-",token:"text"},{token:"punctuation.operator",regex:"[:;]"},{token:"paren.rparen",regex:"\\}",next:"start"},{include:["strings","url","comments"]},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{include:"url"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}],url:[{token:"support.function",regex:"(?:url(:?-prefix)?|domain|regexp)\\(",push:[{token:"support.function",regex:"\\)",next:"pop"},{defaultToken:"string"}]}],strings:[{token:"string.start",regex:"'",push:[{token:"string.end",regex:"'|$",next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]},{token:"string.start",regex:'"',push:[{token:"string.end",regex:'"|$',next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]}],escapes:[{token:"constant.language.escape",regex:/\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();if(e==="ruleset"||t.$mode.$id=="ace/mode/scss"){var i=t.getLine(n.row).substr(0,n.column);return/:[^;]+$/.test(i)?(/([\w\-]+):[^:]*$/.test(i),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r)}return[]},this.getPropertyCompletions=function(e,t,n,i){var s=Object.keys(r);return s.map(function(e){return{caption:e,snippet:e+": $0;",meta:"property",score:1e6}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:1e6}})}}).call(i.prototype),t.CssCompletions=i}),ace.define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"&&n.selection.isEmpty()){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(/^(\s+[^;]|\s*$)/.test(f.substring(s.column)))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}}),this.add("!important","insertion",function(e,t,n,r,i){if(i==="!"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row);if(/^\s*(;|}|$)/.test(o.substring(s.column)))return{text:"!important",selection:[10,10]}}})};r.inherits(u,s),t.CssBehaviour=u}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css",this.snippetFileId="ace/snippets/css"}.call(c.prototype),t.Mode=c}); (function() {
+ace.define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antiNameed|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom",f=t.supportConstantColor="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"paren.rparen",regex:"\\}"},{token:"string",regex:"@(?!viewport)",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"keyword",regex:"%"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant.numeric",regex:c},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"start"},{token:"paren.rparen",regex:"\\}",next:"start"},{token:"string",regex:";",next:"start"},{token:"keyword",regex:"(?:media|supports|document|charset|import|namespace|media|supports|document|page|font|keyframes|viewport|counter-style|font-feature-values|swash|ornaments|annotation|stylistic|styleset|character-variant)"}],comments:[{token:"comment",regex:"\\/\\*",push:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}]}],ruleset:[{regex:"-(webkit|ms|moz|o)-",token:"text"},{token:"punctuation.operator",regex:"[:;]"},{token:"paren.rparen",regex:"\\}",next:"start"},{include:["strings","url","comments"]},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{include:"url"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}],url:[{token:"support.function",regex:"(?:url(:?-prefix)?|domain|regexp)\\(",push:[{token:"support.function",regex:"\\)",next:"pop"},{defaultToken:"string"}]}],strings:[{token:"string.start",regex:"'",push:[{token:"string.end",regex:"'|$",next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]},{token:"string.start",regex:'"',push:[{token:"string.end",regex:'"|$',next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]}],escapes:[{token:"constant.language.escape",regex:/\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();if(e==="ruleset"||t.$mode.$id=="ace/mode/scss"){var i=t.getLine(n.row).substr(0,n.column);return/:[^;]+$/.test(i)?(/([\w\-]+):[^:]*$/.test(i),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r)}return[]},this.getPropertyCompletions=function(e,t,n,i){var s=Object.keys(r);return s.map(function(e){return{caption:e,snippet:e+": $0;",meta:"property",score:1e6}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:1e6}})}}).call(i.prototype),t.CssCompletions=i}),ace.define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"&&n.selection.isEmpty()){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(/^(\s+[^;]|\s*$)/.test(f.substring(s.column)))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}}),this.add("!important","insertion",function(e,t,n,r,i){if(i==="!"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row);if(/^\s*(;|}|$)/.test(o.substring(s.column)))return{text:"!important",selection:[10,10]}}})};r.inherits(u,s),t.CssBehaviour=u}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css",this.snippetFileId="ace/snippets/css"}.call(c.prototype),t.Mode=c}); (function() {
ace.require(["ace/mode/css"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
diff --git a/route.go b/route.go
deleted file mode 100644
index 1c0b9ae..0000000
--- a/route.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package main
-
-import (
- "net/http"
- "path/filepath"
- "runtime"
- "sort"
- "strings"
-)
-
-type OnPageFile struct {
- RO bool
- Path string
- Alias string
- Action string
- Content string
-}
-
-type Editor struct {
- Lang string
- Platform string
-}
-
-type Page struct {
- Files []OnPageFile
- Error string
- File OnPageFile
- Editor Editor
- Static bool
-}
-
-func setRoutes(mux *http.ServeMux) {
-
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
-
- Files := []OnPageFile{}
- for file := range files {
- Files = append(Files, OnPageFile{
- Alias: files[file].Alias,
- Path: files[file].Path,
- })
- }
- sort.Slice(Files, func(i, j int) bool { return Files[i].Alias < Files[j].Alias })
- plat := "unix"
- if runtime.GOOS == "windows" {
- plat = "windows"
- }
- data := Page{
- File: OnPageFile{},
- Files: Files,
- Editor: Editor{
- Platform: plat,
- },
- Static: flagNoReconfig,
- }
-
- content := ""
- var tmp []byte
- var err error
- name := r.URL.Query().Get("name")
- file, ok := files[name]
- if name == "" || !ok {
- tmp, err = GetConfig()
- data.File.Alias = "ConfigUI"
- data.File.Path = ":mem:"
- data.Editor.Lang = "json"
- if name != "" {
- data.Error = name + " not found\n"
- }
- } else {
- tmp, err = file.Read()
- data.File.Action = file.Action
- data.File.Alias = file.Alias
- if file.Lang != "" {
- data.Editor.Lang = file.Lang
- } else {
- ext := strings.TrimPrefix(filepath.Ext(file.Path), ".")
- if ext2mode[ext] != "" {
- ext = ext2mode[ext]
- }
- data.Editor.Lang = ext
- }
- data.File.RO = file.RO
- data.File.Path = file.Path
- }
- if err != nil {
- data.Error = err.Error()
- data.Editor.Lang = ""
- } else {
- content = string(tmp)
- }
-
- data.File.Content = content
-
- Parse(w, "home", data)
- })
-}
diff --git a/server.go b/server.go
new file mode 100644
index 0000000..d7c1ba4
--- /dev/null
+++ b/server.go
@@ -0,0 +1,221 @@
+package configui
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func (cui *ConfigUI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ cui.middleware(cui.mux()).ServeHTTP(w, r)
+}
+
+func (cui *ConfigUI) middleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ rw := &CuiResponseWriter{w, 0}
+ defer catch(rw, r)
+ ip := GetIP(r)
+ if cui.AllowIP != "" {
+ if !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 {
+ cuiR := http.NewServeMux()
+ cuiR.HandleFunc("/api/conf", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ cui.GetConfig(w, r)
+ } else if r.Method == "POST" {
+ cui.PostConfig(w, r)
+ } else {
+ w.WriteHeader(404)
+ }
+ })
+ cuiR.HandleFunc("/api/files", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ cui.ListFiles(w, r)
+ } else {
+ w.WriteHeader(404)
+ }
+ })
+ cuiR.HandleFunc("/api/file", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ cui.GetFile(w, r)
+ } else if r.Method == "POST" {
+ cui.PostFile(w, r)
+ } else {
+ w.WriteHeader(404)
+ }
+ })
+ cuiR.HandleFunc("/api/apply", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
+ cui.Apply(w, r)
+ } else {
+ w.WriteHeader(404)
+ }
+ })
+ cuiR.HandleFunc("/api/export", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" {
+ cui.Download(w, r)
+ } else {
+ w.WriteHeader(404)
+ }
+ })
+ cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS))))
+ cuiR.HandleFunc("/", cui.App)
+ return cuiR
+}
+
+func (cui *ConfigUI) ListFiles(w http.ResponseWriter, r *http.Request) {
+ data, err := json.Marshal(cui.Files)
+ if err != nil {
+ panic(err)
+ }
+ w.Write(data)
+}
+
+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"))
+ return
+ }
+ data, err := file.Read()
+ if err != nil {
+ panic(err)
+ }
+ response, err := json.Marshal(map[string]string{
+ "path": file.Path,
+ "name": file.Name,
+ "action": file.Action,
+ "data": string(data),
+ })
+ if err != nil {
+ panic(err)
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(response)
+}
+
+func (cui *ConfigUI) PostFile(w http.ResponseWriter, r *http.Request) {
+ data, err := ioutil.ReadAll(r.Body)
+ r.Body.Close()
+ if err != nil {
+ panic(err)
+ }
+ f := File{}
+ if err := json.Unmarshal(data, &f); err != nil {
+ panic(err)
+ }
+ file, err := cui.File(f.Name)
+ if err != nil {
+ response(w, 404, []byte("file not found"))
+ return
+ }
+ if err := file.Write([]byte(f.Data)); err != nil {
+ panic(err)
+ }
+ w.Write([]byte("ok"))
+}
+
+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"))
+ return
+ }
+ result, err := file.Do()
+ if err != nil {
+ panic(err)
+ }
+ w.Write([]byte(result))
+}
+
+func (cui *ConfigUI) Download(w http.ResponseWriter, r *http.Request) {
+ if name := r.URL.Query().Get("name"); name != "" {
+ if name == cui.AppName {
+ data, err := cui.Config()
+ if err != nil {
+ panic(err)
+ }
+ w.Header().Set(
+ "Content-Disposition", `
+ attachment; filename="`+cui.AppName+`.json"`,
+ )
+ w.Write(data)
+ return
+ }
+ file, err := cui.File(name)
+ if err != nil {
+ response(w, 404, []byte("file not found"))
+ return
+ }
+ data, err := file.Read()
+ if err != nil {
+ panic(err)
+ }
+ w.Header().Set("Content-Disposition", `attachment; filename="`+filepath.Base(file.Path)+`"`)
+ w.Write(data)
+ return
+ }
+ fs := []string{}
+ for _, i := range cui.fileIndex {
+ fs = append(fs, cui.Files[i].Path)
+ }
+ if cui.ConfigPath != "" {
+ fs = append(fs, cui.ConfigPath)
+ }
+ w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
+ bundle(w, fs, cui.AppName, false)
+}
+
+func (cui *ConfigUI) PostConfig(w http.ResponseWriter, r *http.Request) {
+ if cui.NoReconfig {
+ panic("system reconfig is disabled")
+ }
+ data, err := ioutil.ReadAll(r.Body)
+ r.Body.Close()
+ if err != nil {
+ panic(err)
+ }
+ err = cui.LoadConfig(string(data))
+ if err != nil {
+ panic(err)
+ }
+ if cui.ConfigPath != "" {
+ info, err := os.Stat(cui.ConfigPath)
+ if err != nil {
+ panic(err)
+ }
+ err = os.WriteFile(cui.ConfigPath, data, info.Mode())
+ if err != nil {
+ panic(err)
+ }
+ }
+ w.Write([]byte("ok"))
+}
+
+func (cui *ConfigUI) GetConfig(w http.ResponseWriter, r *http.Request) {
+ data, err := cui.Config()
+ if err != nil {
+ panic(err)
+ }
+ w.Write(data)
+}
diff --git a/src/main.js b/src/main.js
index ca00f8b..83b2515 100644
--- a/src/main.js
+++ b/src/main.js
@@ -9,7 +9,7 @@ async function FileGet(follower=false){
const res = await fetch('/api/conf'+f);
const body = await res.text();
if(!res.ok){
- handleError(res)
+ Catch(res)
return
}
editor.session.setValue(body);
@@ -19,7 +19,7 @@ async function FileGet(follower=false){
const res = await fetch('/api/file?name=' + Active + f);
const body = await res.json();
if(!res.ok){
- handleError(res)
+ Catch(res)
return
}
editor.session.setValue(body.data);
@@ -36,7 +36,7 @@ async function FileSave(){
})
})
if (res.ok) window.location.reload();
- else handleError(res)
+ else Catch(res)
}
else {
const res = await fetch('/api/file', {
@@ -46,7 +46,7 @@ async function FileSave(){
'Content-Type': 'application/json'
})
})
- if(!res.ok) handleError(res)
+ if(!res.ok) Catch(res)
}
}
@@ -59,7 +59,7 @@ async function FileApply(){
method: 'POST',
})
if(!res.ok){
- const result = await handleError(res)
+ const result = await Catch(res)
result_editor.session.setValue(result)
return
}
@@ -68,7 +68,9 @@ async function FileApply(){
}
}
-async function handleError(res){
+async function Catch(res){
+ console.trace()
+ console.log(res)
const msg = await res.text()
ShowError(msg)
return msg
diff --git a/templates/components/menu.tmpl b/templates/components/menu.tmpl
index 6e2716a..42d1aff 100644
--- a/templates/components/menu.tmpl
+++ b/templates/components/menu.tmpl
@@ -7,10 +7,10 @@
{{ end }}
@@ -19,7 +19,7 @@