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 @@ @@ -19,7 +19,7 @@