Compare commits

..

1 Commits

Author SHA1 Message Date
Evan Chen 2dd0775272 update 2021-10-19 17:31:23 +08:00
114 changed files with 7214 additions and 2904 deletions

View File

@ -1,36 +0,0 @@
kind: pipeline
name: default
steps:
- name: build
image: golang:1.17.2
commands:
- git tag $DRONE_TAG
- bash release.sh cmd/configui/main.go
- echo -n "latest,${DRONE_TAG#v}" > .tags
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_api_key
base_url: https://kumoly.io
files: dist/*
checksum:
- sha256
- name: docker
image: plugins/docker
settings:
username:
from_secret: hub_user
password:
from_secret: hub_passwd
# auto_tag: true
mtu: 1000
# purge: true
repo: hub.kumoly.io/tools/configui
registry: hub.kumoly.io
trigger:
event: tag

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
dist
node_modules
.parcel-cache
public/css/main.css.map
public/css/main.css*
public/js/main.js*

View File

@ -1,40 +0,0 @@
# 0.1.3
## Feature
* bind key for run #37
## Fix
* Not building latest tag #38
# 0.1.2
## Feature
* download files #26
* add goto last line toolbar #21
* use icons
* bind ctrl-s (cmd-s) to fileSave
* refactor as lib #15
* Set Title to be changeable #24
* show run result below #33
* set font size #31
* Hide Config zone for other usage such as go playground #23
## Fix
* all cmd output should be pass straight back to user
* timeout on none ending cmd #34
* add sys setting to conf #32
* Apply should save before taking action #19
# 0.1.1
## Feature
* Save config back to file
* Add lang directive to specify lang mode
## Fix
* Server started in -v option (#16)

View File

@ -1,17 +1,14 @@
FROM golang:1.17.2-alpine3.14 as builder
RUN apk update && apk add --no-cache git tzdata
WORKDIR /src
# not using any libs, for now
# COPY go.mod go.sum /src/
# RUN go mod download
COPY go.mod go.sum /src/
RUN go mod download
COPY . .
RUN VERSION=$(git describe --tags --abbrev=0) BUILD=$(git rev-parse --short HEAD) && \
RUN VERSION=$(git describe --tags) 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 cmd/configui/main.go
-o /go/bin/configui main.go
FROM alpine:3.14
@ -19,4 +16,4 @@ EXPOSE 5080
ENV PATH="/go/bin:${PATH}"
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /go/bin/configui /go/bin/configui
CMD ["/go/bin/configui","-log","/var/log/configui.log","-f","/data/configui.json"]
CMD ["/go/bin/configui"]

View File

@ -5,49 +5,15 @@ HUB=hub.kumoly.io
HUB_PROJECT=tools
LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w"
PLATFORMS=darwin linux
ARCHITECTURES=amd64
APPS=configui
default: build
install:
npm install
clean:
rm -rf dist
run: build
$(shell cd dist; ./${PROJ} -log configui.log)
.PHONY: web
web:
npm run build
# npm run js-dev
.PHONY: build
build:
go build ${LDFLAGS} -o dist/${PROJ} cmd/$(PROJ)/main.go
.PHONY: binary
binary:
bash release.sh cmd/configui/main.go
.PHONY: docker
docker:
docker build --target builder -t $(HUB)/$(HUB_PROJECT)/$(PROJ):builder .
docker build \
-t $(HUB)/$(HUB_PROJECT)/$(PROJ):$(VERSION) \
.
docker-push:
docker tag $(HUB)/$(HUB_PROJECT)/$(PROJ):$(VERSION) $(HUB)/$(HUB_PROJECT)/$(PROJ):latest
docker push $(HUB)/$(HUB_PROJECT)/$(PROJ):$(VERSION)
docker push $(HUB)/$(HUB_PROJECT)/$(PROJ):latest
docker-save:
docker save $(HUB)/$(HUB_PROJECT)/$(PROJ):$(VERSION) | gzip > dist/$(PROJ)-image-$(VERSION)-${BUILD}.tar.gz
.PHONY: release
release: clean web binary docker docker-save
go build ${LDFLAGS} -o dist/${PROJ}

131
README.md
View File

@ -1,13 +1,11 @@
# Config UI
a web app to edit and action on update powered by [ACE](https://ace.c9.io/#nav=howto&api=edit_session)
a web app to edit and action on update
```
Usage: configui [options]
-allow string
IPs to allow, blank to allow all
-bind string
address to bind (default "0.0.0.0:8000")
address to bind (default "localhost:8000")
-c string
cmd to apply
-f string
@ -15,133 +13,30 @@ Usage: configui [options]
-log string
log to file
-n string
Name of file
alias of file
-p string
path to file, precedence over config
-static
disable config api
-v show version
```
## Install
```sh
sudo rm -f /usr/local/bin/configui
sudo sh -c "curl -fsSL RELEASE_URL | tar -C /usr/local/bin/ -xz"
```
## Config
```json
[
{
"app_name": "ConfigUI",
"base_url": "/",
"config_path": "conf.json",
"no_reconfig": false,
"allow_ip": "",
"files": [
"path": "configui.log",
"ro": true
},
{
"path": "main.go",
"name": "GoPlayground",
"action": "go run main.go",
"ro": false,
"lang": "",
"order": 0,
"data": ""
}
],
"Actions": [
{
"name": "User",
"cmd": "whoami"
}
],
"result_bellow": false,
"hide_config": false,
"log_path": "access.log",
"silent_sys_out": false
"path": "etc/test.ini",
"name": "test",
"action": "myip local -P"
}
]
```
`configui -f PATH/TO/CONFIG`
## Add custom links in nav
**config**
```json
{
"cust":"path/to/dir"
}
```
**No link**
> path/to/dir/none.tmpl
```go
{{ define "links" }}
<!-- none -->
{{end}}
```
**Custom links**
> path/to/dir/links.tmpl
```go
{{ define "links" }}
<a class="button is-white level-item"
href="https://kumoly.io/tools/configui/issues"
>Report Issue</a>
{{end}}
```
## Add integrations to ConfigUI
```go
cui.Integrations = append(cui.Integrations, &configui.Integration{
Name: "test", Description: "test", Cmd: func() (string, error) {
if rand.Int31n(40) > 20 {
return "ok", nil
}
return "", fmt.Errorf("error")
},
})
```
## Systemd
```ini
; /etc/systemd/system/configui.service
[Unit]
Description=CoonfigUI Server
[Service]
WorkingDirectory=/home/ubuntu/ConfigUI
ExecStart=configui -log access.log -f conf.json -bind 0.0.0.0:80
Restart=always
[Install]
WantedBy=multi-user.target
```
## Use as a sub route
```go
cui.BaseUrl = "/configui/"
mux := http.NewServeMux()
mux.Handle("/configui/", http.StripPrefix("/configui", cui))
```
## Use as subpath in caddy
```caddyfile
DOMAIN {
...
route /configui/* {
uri strip_prefix /configui
reverse_proxy localhost:8000
}
...
}
```
## Api
### Files
@ -162,7 +57,7 @@ res:
### File
`GET /api/file?name=Name`
`GET /api/file?name=ALIAS`
res:
```json
@ -188,4 +83,4 @@ req:
### Apply
`POST /api/apply?name=Name`
`POST /api/apply?name=ALIAS`

125
api.go Normal file
View File

@ -0,0 +1,125 @@
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"kumoly.io/tools/configui/configui"
)
func ListFiles(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(files)
if err != nil {
AbortError(w, err)
return
}
w.Write(data)
}
func GetFile(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
file, ok := files[name]
if name == "" || !ok {
MakeResponse(w, 404, []byte("file not found"))
return
}
data, err := file.Read()
if err != nil {
AbortError(w, err)
return
}
response, err := json.Marshal(map[string]string{
"path": file.Path,
"name": file.Alias,
"action": file.Action,
"data": string(data),
})
if err != nil {
AbortError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}
func PostFile(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
AbortError(w, err)
return
}
f := configui.File{}
if err := json.Unmarshal(data, &f); err != nil {
AbortError(w, err)
return
}
file, ok := files[f.Alias]
if !ok {
MakeResponse(w, 404, []byte("file not found"))
return
}
if err := file.Write([]byte(f.Data)); err != nil {
AbortError(w, err)
return
}
w.Write([]byte("ok"))
}
func Apply(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
file, ok := files[name]
if name == "" || !ok {
MakeResponse(w, 404, []byte("file not found"))
return
}
result, err := file.Do()
if err != nil {
AbortError(w, err)
return
}
w.Write([]byte(result))
}
func Download(w http.ResponseWriter, r *http.Request) {
fs := []string{}
for _, v := range files {
fs = append(fs, v.Path)
}
w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
bundle(w, fs, false)
}
func LoadConfig(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
AbortError(w, err)
return
}
ftmp, err := configui.ReadConfig(string(data))
if err != nil {
AbortError(w, err)
return
}
files = configui.GetFileMap(ftmp)
w.Write([]byte("ok"))
}
func getConfigHandler(w http.ResponseWriter, r *http.Request) {
data, err := GetConfig()
if err != nil {
AbortError(w, err)
return
}
w.Write(data)
}
func GetConfig() ([]byte, error) {
config := []configui.File{}
for _, f := range files {
config = append(config, *f)
}
return json.Marshal(config)
}

129
app.go
View File

@ -1,129 +0,0 @@
package configui
import (
"net/http"
"path/filepath"
"runtime"
"sort"
"strings"
)
type ActiveFile struct {
RO bool
Path string
Name string
Cmd string
Content string
Order int
}
type Editor struct {
Lang string
Platform string
}
type Page struct {
AppName string
BaseUrl string
Actions []Action
Integrations []*Integration
Version string
Build string
Files []ActiveFile
Error string
File ActiveFile
Editor Editor
Static bool
HideConfig bool
ResultBellow 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.Files {
// Files = append(Files, ActiveFile{
// Name: cui.Files[i].Name,
// Path: cui.Files[i].Path,
// })
// }
for _, i := range cui.fileIndex {
Files = append(Files, ActiveFile{
Name: cui.Files[i].Name,
Path: cui.Files[i].Path,
Order: cui.Files[i].Order,
})
}
sort.Slice(Files, func(i, j int) bool {
if Files[i].Order == Files[j].Order {
return Files[i].Name < Files[j].Name
}
return Files[i].Order < Files[j].Order
})
plat := "unix"
if runtime.GOOS == "windows" {
plat = "windows"
}
data := Page{
AppName: cui.AppName,
BaseUrl: cui.BaseUrl,
File: ActiveFile{},
Files: Files,
Editor: Editor{
Platform: plat,
},
Actions: cui.Actions,
Integrations: cui.Integrations,
Static: cui.NoReconfig,
HideConfig: cui.HideConfig,
ResultBellow: cui.ResultBellow,
Version: version,
}
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 = cui.AppName
// data.File.Path = ":mem:"
data.Editor.Lang = "json"
if name != "" {
data.Error = name + " not found\n"
}
} else {
tmp, err = file.Read()
data.File.Cmd = file.Cmd
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
err = cui.tmpl.ExecuteTemplate(w, "home", data)
if err != nil {
panic(err)
}
}

View File

@ -1,122 +0,0 @@
package main
import (
"flag"
"fmt"
"net/http"
"os"
log "kumoly.io/lib/klog"
"kumoly.io/tools/configui"
)
var (
flagPath string
flagAction string
flagName string
flagConfigPath string
flagBind string
flagAddr string
flagNoReconfig bool
flagLogFile string
flagAllow string
flagVer bool
)
var Version = "0.0.0"
var Build = "alpha"
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(&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")
flag.StringVar(&flagBind, "bind", "0.0.0.0:8000", "address to bind, (deprecated, use -addr instead)")
flag.StringVar(&flagAddr, "addr", "0.0.0.0:8000", "address to bind")
flag.BoolVar(&flagNoReconfig, "static", false, "disable config api")
flag.BoolVar(&flagVer, "v", false, "show version")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: configui [options]\n")
flag.PrintDefaults()
}
}
func main() {
flag.Parse()
if flagVer {
fmt.Printf("%v - %v\n", Version, Build)
return
}
// deprecate fix
if flagBind != "0.0.0.0:8000" && flagAddr == "0.0.0.0:8000" {
flagAddr = flagBind
}
cui := configui.New()
// setup values
if flagPath != "" {
if flagName == "" {
flagName = flagPath
}
file := &configui.File{
Path: flagPath,
Name: flagName,
Action: flagAction,
}
if err := cui.AppendFile(file); err != nil {
log.Error(err)
os.Exit(1)
}
} else if flagConfigPath == "" {
log.Error("no config specified")
} else {
conf, err := os.ReadFile(flagConfigPath)
if err != nil {
log.Error(err)
os.Exit(1)
}
cui.LoadConfig(string(conf))
if err != nil {
log.Error(err)
os.Exit(1)
}
}
cui.ConfigPath = flagConfigPath
if flagNoReconfig {
cui.NoReconfig = flagNoReconfig
}
if flagLogFile != "" {
cui.LogPath = flagLogFile
}
if flagAllow != "" {
cui.AllowIP = flagAllow
}
// setup routes
server := &http.Server{
Addr: flagAddr,
// disable timeout due to cui controlling cmd timeouts
// WriteTimeout: time.Second * 30,
// ReadTimeout: time.Second * 30,
Handler: cui,
}
// start server
log.Info("Listening on ", flagAddr)
err := server.ListenAndServe()
if err != nil {
log.Error(err)
os.Exit(1)
}
}

View File

@ -1,276 +0,0 @@
package configui
import (
"embed"
"encoding/json"
"errors"
"fmt"
"html/template"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"kumoly.io/lib/klog"
"kumoly.io/lib/ksrv/engine"
"kumoly.io/tools/configui/public"
)
const UNIX_SHELL = "/usr/bin/sh"
const WIN_SHELL = "C:\\Windows\\System32\\cmd"
const DARWIN_SHELL = "/bin/bash"
const version = "v0.1.14"
//go:embed templates
var tmplFS embed.FS
//go:embed schema.json
var SCHEMA []byte
var Ext2Mode map[string]string = map[string]string{
"go": "golang",
"log": "sh",
"txt": "text",
"yml": "yaml",
"conf": "ini",
"md": "markdown",
}
type Action struct {
Name string `json:"name"`
Cmd string `json:"cmd"`
run chan struct{} `json:"-"`
pid int `json:"-"`
}
type Integration struct {
Name string `json:"name"`
Description string `json:"description"`
Cmd func() (string, error) `json:"-"`
run chan struct{} `json:"-"`
}
type ConfigUI struct {
AppName string `json:"app_name"`
Prod bool `json:"production"`
BaseUrl string `json:"base_url"`
ConfigPath string `json:"config_path"`
SHELL string `json:"shell"`
NoReconfig bool `json:"no_reconfig"`
AllowIP string `json:"allow_ip"`
CmdTimeout string `json:"timeout"`
cmdTimeout time.Duration
Files []*File `json:"files"`
fileIndex map[string]int
Actions []Action `json:"actions"`
Integrations []*Integration `json:"integrations,omitempty"`
ResultBellow bool `json:"result_bellow"`
HideConfig bool `json:"hide_config"`
// Should be in main app
LogPath string `json:"log_path"`
LogLevel klog.Llevel `json:"log_level"`
TmplFS embed.FS `json:"-"`
CustTmpl string `json:"cust,omitempty"`
tmpl *engine.Engine
PublicFS embed.FS `json:"-"`
log *klog.Logger
ksrv_log *klog.Logger
f *os.File
configLock sync.Mutex
}
func New() *ConfigUI {
tmpl := engine.Must(engine.New("").Funcs(template.FuncMap{
"step": func(from, to, step uint) []uint {
items := []uint{}
for i := from; i <= to; i += step {
items = append(items, i)
}
return items
},
"normal": func(name string) string {
return strings.ReplaceAll(name, " ", "-")
},
}).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
sh := ""
switch runtime.GOOS {
case "darwin":
sh = DARWIN_SHELL
case "windows":
sh = WIN_SHELL
default:
sh = UNIX_SHELL
}
return &ConfigUI{
fileIndex: map[string]int{},
SHELL: sh,
Prod: true,
AppName: "ConfigUI",
BaseUrl: "/",
Actions: []Action{{Name: "User", Cmd: "whoami"}},
PublicFS: public.FS,
TmplFS: tmplFS,
tmpl: tmpl,
CmdTimeout: "10s",
cmdTimeout: time.Second * 10,
LogLevel: klog.Lerror | klog.Linfo,
log: klog.Sub("ConfigUI"),
}
}
func (cui *ConfigUI) File(file_name string) (*File, error) {
if file_name == cui.AppName {
return &File{
Path: cui.ConfigPath,
Name: cui.AppName,
Lang: "json",
owner: cui,
}, 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.Path == "" {
continue
}
if f.Name == "" {
f.Name = f.Path
}
f.owner = cui
tmpIndex[f.Name] = i
// deprecated fix
if f.Cmd == "" && f.Action != "" {
f.Cmd = f.Action
f.Action = ""
}
}
// copy
cui.configLock.Lock()
defer cui.configLock.Unlock()
cui.fileIndex = tmpIndex
cui.Files = tmpConf.Files
cui.AllowIP = tmpConf.AllowIP
cui.Prod = tmpConf.Prod
klog.PROD = cui.Prod
cui.ConfigPath = tmpConf.ConfigPath
cui.HideConfig = tmpConf.HideConfig
cui.NoReconfig = tmpConf.NoReconfig
cui.ResultBellow = tmpConf.ResultBellow
cui.SHELL = tmpConf.SHELL
cui.CustTmpl = tmpConf.CustTmpl
if cui.CustTmpl != "" {
ntmpl, err := cui.tmpl.OverrideGlob(filepath.Join(cui.CustTmpl, "*.tmpl"))
if err != nil {
cui.log.Error(err)
} else {
cui.tmpl = ntmpl
}
}
cui.Actions = tmpConf.Actions
for i := range cui.Actions {
if cui.Actions[i].Name == "" {
cui.Actions[i].Name = cui.Actions[i].Cmd
}
}
cui.AppName = tmpConf.AppName
if cui.AppName == "" {
cui.AppName = "ConfigUI"
}
cui.BaseUrl = tmpConf.BaseUrl
if cui.BaseUrl == "" {
cui.BaseUrl = "/"
}
ct, err := time.ParseDuration(tmpConf.CmdTimeout)
if err != nil || cui.CmdTimeout == "" {
cui.CmdTimeout = "10s"
cui.cmdTimeout = time.Second * 10
} else {
cui.CmdTimeout = tmpConf.CmdTimeout
cui.cmdTimeout = ct
}
cui.log = klog.Sub(cui.AppName)
cui.LogLevel = tmpConf.LogLevel
if cui.LogLevel == 0 {
cui.LogLevel = klog.Lerror | klog.Linfo
}
klog.LEVEL = cui.LogLevel
cui.LogPath = tmpConf.LogPath
cui.setLog()
// fmt.Printf("%+v", cui)
return nil
}
func (cui *ConfigUI) setLog() {
var err error
if cui.f != nil {
cui.f.Close()
}
if cui.LogPath != "" {
cui.f, err = os.OpenFile(cui.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
cui.log.Error(err)
}
cui.log.SetErrOutput(cui.f)
cui.log.SetOutput(cui.f)
if cui.ksrv_log != nil {
cui.ksrv_log.SetErrOutput(cui.f)
cui.ksrv_log.SetOutput(cui.f)
}
} else {
cui.log.SetErrOutput(os.Stderr)
cui.log.SetOutput(os.Stderr)
if cui.ksrv_log != nil {
cui.ksrv_log.SetErrOutput(os.Stderr)
cui.ksrv_log.SetOutput(os.Stderr)
}
}
}
func (cui *ConfigUI) Config() ([]byte, error) {
return json.MarshalIndent(cui, "", " ")
}
func (cui *ConfigUI) AppendFile(file *File) error {
if file.Name == "" {
file.Name = file.Path
}
file.owner = cui
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
}

87
configui/file.go Normal file
View File

@ -0,0 +1,87 @@
package configui
import (
"encoding/json"
"errors"
"log"
"os"
"os/exec"
"runtime"
"sync"
)
var UNIX_SHELL = "bash"
var WIN_SHELL = "cmd"
type File struct {
Path string `json:"path"`
Alias string `json:"name"`
Action string `json:"action"`
RO bool `json:"ro"`
// used for parsing post data
Data string `json:"data"`
lock sync.RWMutex `json:"-"`
}
func (f *File) Read() ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
data, err := os.ReadFile(f.Path)
if err != nil {
log.Println(err)
return nil, err
}
return data, nil
}
func (f *File) Write(data []byte) error {
if f.RO {
return errors.New("this file has readonly set")
}
f.lock.Lock()
defer f.lock.Unlock()
info, err := os.Stat(f.Path)
if err != nil {
return err
}
return os.WriteFile(f.Path, data, info.Mode())
}
func (f *File) Do() (string, error) {
if f.Action == "" {
return "", nil
}
cmd := exec.Command(UNIX_SHELL, "-c", f.Action)
if runtime.GOOS == "windows" {
cmd = exec.Command(WIN_SHELL, "/c", f.Action)
}
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
return string(out), nil
}
func ReadConfig(confstr string) ([]File, error) {
conf := []File{}
err := json.Unmarshal([]byte(confstr), &conf)
if err != nil {
return nil, err
}
for i := range conf {
if conf[i].Alias == "" {
conf[i].Alias = conf[i].Path
}
}
return conf, nil
}
func GetFileMap(files []File) map[string]*File {
fileMap := map[string]*File{}
for i := range files {
fileMap[files[i].Alias] = &files[i]
}
return fileMap
}

View File

@ -1,13 +0,0 @@
package configui
import (
"net/http"
"kumoly.io/lib/ksrv"
)
var ErrorServerReloading = ksrv.Error{
Code: http.StatusServiceUnavailable,
ID: "ErrorServerReloading",
Message: "server is reloading",
}

112
file.go
View File

@ -1,112 +0,0 @@
package configui
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"sync"
"time"
)
type File struct {
Path string `json:"path"`
Name string `json:"name"`
Cmd string `json:"cmd"`
// RO is readonly
RO bool `json:"ro"`
Lang string `json:"lang,omitempty"`
// Order order of the display on ui
Order int `json:"order"`
// used for parsing post data
Data string `json:"data,omitempty"`
lock sync.RWMutex `json:"-"`
owner *ConfigUI `json:"-"`
run chan struct{} `json:"-"`
pid int `json:"-"`
// deprecated
Action string `json:"action,omitempty"`
}
func (f *File) Read() ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
data, err := os.ReadFile(f.Path)
if err != nil {
f.owner.log.Error(err)
return nil, err
}
return data, nil
}
func (f *File) Write(data []byte) error {
if f.RO {
return errors.New("this file has readonly set")
}
f.lock.Lock()
defer f.lock.Unlock()
info, err := os.Stat(f.Path)
if err != nil {
return err
}
return os.WriteFile(f.Path, data, info.Mode())
}
func (f *File) Do(CmdTimeout time.Duration, report chan int) (string, error) {
if f.Cmd == "" {
return "", nil
}
f.lock.RLock()
defer f.lock.RUnlock()
// limit running instance to 1
if cap(f.run) == 0 {
f.run = make(chan struct{}, 1)
}
select {
case f.run <- struct{}{}:
defer func() { <-f.run }()
default:
f.owner.log.Error("task is running: ", f.Name)
return "", fmt.Errorf("another task of %s is running with pid: %d", f.Name, f.pid)
}
// prepare cmd
cmd := &exec.Cmd{}
if runtime.GOOS == "windows" {
cmd = exec.Command(f.owner.SHELL, "/c", f.Cmd)
} else {
cmd = exec.Command(f.owner.SHELL, "-c", f.Cmd)
}
f.owner.log.Info("DO: ", f.Cmd)
done := make(chan string, 1)
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
err := cmd.Start()
if err != nil {
f.owner.log.Error("cmd start error: ", err)
panic(err)
}
go func() {
f.pid = cmd.Process.Pid
report <- cmd.Process.Pid
cmd.Wait()
done <- b.String()
}()
select {
case <-time.After(CmdTimeout):
cmd.Process.Kill()
f.owner.log.Error("timeout")
return "", errors.New("command timeout")
case out := <-done:
f.owner.log.Info("\n", out)
return out, nil
}
}

14
go.mod
View File

@ -1,17 +1,3 @@
module kumoly.io/tools/configui
go 1.17
require (
kumoly.io/lib/klog v0.0.8
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298
kumoly.io/lib/stat v0.0.1
kumoly.io/tools/kconfig v0.1.3
)
require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/rs/zerolog v1.26.0 // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
kumoly.io/lib/guard v0.1.2-0.20211124052638-9dfd98f9a848 // indirect
)

44
go.sum
View File

@ -1,44 +0,0 @@
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
kumoly.io/lib/guard v0.1.2-0.20211124052638-9dfd98f9a848 h1:ALCeJga3775AJDkk7YbMQEcYxpwHtinUN+YjkpacNhA=
kumoly.io/lib/guard v0.1.2-0.20211124052638-9dfd98f9a848/go.mod h1:yWg9RDSI6YXkOPmP6Ad93aMqzlxhgW8LOe/ZRjjYX3U=
kumoly.io/lib/klog v0.0.8 h1:6hTfDlZh7KGnPrd2tUrauCKRImSnyyN9DHXpey3Czn8=
kumoly.io/lib/klog v0.0.8/go.mod h1:Snm+c1xRrh/RbXsxQf7UGYbAJGPcIa6bEEN+CmzJh7M=
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298 h1:0raqoIXmNpD6s1SrJbieAyIIkDyhe+aqfaXvx8wenrI=
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298/go.mod h1:pwd+NspxnoxPJAETRY2V4i2qZc+orKLxvWzGUBiqBW8=
kumoly.io/lib/stat v0.0.1 h1:Ck596El7Ixk7GZyzQq/86F1YCl7iYffHmzEdFx1sSRM=
kumoly.io/lib/stat v0.0.1/go.mod h1:zqMV9q4TC94VGbpDn/mGBTwRNWBVWlVg1taLlCBAWc8=
kumoly.io/tools/kconfig v0.1.3 h1:okLWqlvASZzfAj3kXzpB/Q8V7WZNd24Y6MLPhWPTwP8=
kumoly.io/tools/kconfig v0.1.3/go.mod h1:j6GYUb50c0pFMxfQgoUl5n4P++AapoG+L5YHxHyyjN4=

View File

@ -1,260 +0,0 @@
package configui
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"kumoly.io/lib/ksrv"
)
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 {
ksrv.Response(w, 404, []byte("file not found"))
return
}
data, err := file.Read()
if err != nil {
panic(err)
}
stat, err := os.Stat(file.Path)
if err != nil {
panic(err)
}
response, err := json.Marshal(map[string]string{
"path": file.Path,
"name": file.Name,
"cmd": file.Cmd,
"data": string(data),
"delta": strconv.Itoa(int(stat.Size())),
})
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}
func (cui *ConfigUI) GetDelta(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
delta, err := strconv.ParseInt(r.URL.Query().Get("delta"), 10, 64)
if err != nil {
panic(err)
}
file, err := cui.File(name)
if err != nil {
ksrv.Response(w, 404, []byte("file not found"))
return
}
f, err := os.Open(file.Path)
if err != nil {
panic(err)
}
defer f.Close()
stat, err := os.Stat(file.Path)
if err != nil {
panic(err)
}
if delta > stat.Size() {
panic(fmt.Errorf("delta(%d) is larger than file size(%d), the file might have been changed", delta, stat.Size()))
}
buf := make([]byte, stat.Size()-delta)
_, err = f.ReadAt(buf, delta)
if err != nil {
panic(err)
}
response, err := json.Marshal(map[string]string{
"path": file.Path,
"name": file.Name,
"cmd": file.Cmd,
"data": string(buf),
"delta": strconv.Itoa(int(stat.Size())),
})
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 {
ksrv.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 {
ksrv.Response(w, 404, []byte("file not found"))
return
}
result, err := file.Do(cui.cmdTimeout, make(chan int, 1))
if err != nil {
panic(err)
}
w.Write([]byte(result))
}
func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
for i, v := range cui.Actions {
if v.Name == name {
// limit running instance to one
if cap(cui.Actions[i].run) != 1 {
cui.Actions[i].run = make(chan struct{}, 1)
}
select {
case cui.Actions[i].run <- struct{}{}:
defer func() { <-cui.Actions[i].run }()
default:
panic(fmt.Errorf("another task of %s is running with pid: %d", name, v.pid))
}
file := &File{Name: name, Cmd: v.Cmd, owner: cui}
pid := make(chan int)
go func() {
cui.Actions[i].pid = <-pid
}()
result, err := file.Do(cui.cmdTimeout, pid)
if err != nil {
panic(err)
}
w.Write([]byte(result))
return
}
}
panic(fmt.Errorf("no action named: %v", name))
}
func (cui *ConfigUI) DoIntegration(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
for i, v := range cui.Integrations {
if v.Name == name {
// limit running instance to one
if cap(cui.Integrations[i].run) != 1 {
cui.Integrations[i].run = make(chan struct{}, 1)
}
select {
case cui.Integrations[i].run <- struct{}{}:
defer func() { <-cui.Integrations[i].run }()
default:
panic(fmt.Errorf("another task of %s is running", name))
}
result, err := v.Cmd()
if err != nil {
panic(err)
}
w.Write([]byte(result))
return
}
}
panic(fmt.Errorf("no integration named: %v", name))
}
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 {
ksrv.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)
}

152
main.go Normal file
View File

@ -0,0 +1,152 @@
package main
import (
"embed"
"flag"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"time"
"kumoly.io/tools/configui/configui"
"kumoly.io/tools/configui/public"
)
//go:embed templates
var tmplFS embed.FS
var tmpl *template.Template
var (
flagPath string
flagAction string
flagAlias string
flagConfigPath string
flagBind string
flagLogFile string
flagAllow string
flagVer bool
)
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(&flagAction, "c", "", "cmd to apply")
flag.StringVar(&flagLogFile, "log", "", "log to file")
flag.StringVar(&flagAllow, "allow", "", "IPs to allow, blank to allow all")
flag.StringVar(&flagBind, "bind", "localhost:8000", "address to bind")
flag.BoolVar(&flagVer, "v", false, "show version")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: configui [options]\n")
flag.PrintDefaults()
}
}
func main() {
flag.Parse()
if flagVer {
fmt.Printf("%v - %v\n", Version, Build)
}
// setup logging
if flagLogFile != "" {
f, err := os.OpenFile(flagLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Error opening file: %v", err)
}
defer f.Close()
mwriter := io.MultiWriter(f, os.Stderr)
log.SetOutput(mwriter)
}
// setup values
if flagPath != "" {
if flagAlias == "" {
flagAlias = flagPath
}
files[flagAlias] = &configui.File{
Path: flagPath,
Alias: flagAlias,
Action: flagAction,
}
} else if flagConfigPath == "" {
log.Println("no config found")
} else {
conf, err := os.ReadFile(flagConfigPath)
if err != nil {
log.Fatalln(err)
}
ftmp, err := configui.ReadConfig(string(conf))
if err != nil {
log.Fatalln(err)
}
files = configui.GetFileMap(ftmp)
}
// setup routes
mux := http.NewServeMux()
mux.HandleFunc("/api/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)
}
})
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)
server := &http.Server{
Addr: flagBind,
WriteTimeout: time.Second * 3,
ReadTimeout: time.Second * 30,
Handler: Middleware(mux),
}
// start server
log.Println("Listening on", flagBind)
log.Fatal(server.ListenAndServe())
}

108
netutil.go Normal file
View File

@ -0,0 +1,108 @@
package main
import (
"bytes"
"log"
"net"
"net/http"
"strconv"
"strings"
)
type ResponseWriter struct {
http.ResponseWriter
StatueCode int
}
func (w *ResponseWriter) WriteHeader(statusCode int) {
if w.StatueCode != 0 {
return
}
w.StatueCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *ResponseWriter) 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) {
w.WriteHeader(status)
return w.Write(body)
}
func AbortError(w http.ResponseWriter, err interface{}) (int, error) {
switch v := err.(type) {
case int:
w.WriteHeader(v)
return w.Write([]byte(strconv.Itoa(v)))
case string:
w.WriteHeader(500)
return w.Write([]byte(v))
case error:
w.WriteHeader(500)
return w.Write([]byte(v.Error()))
default:
w.WriteHeader(500)
return w.Write([]byte(strconv.Itoa(500)))
}
}
func Catch(rw *ResponseWriter, r *http.Request) {
ex := recover()
if ex != nil {
AbortError(rw, r)
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)
}
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 {
buf := &bytes.Buffer{}
err := tmpl.ExecuteTemplate(buf, "home", data)
if err != nil {
AbortError(w, err)
return err
}
_, err = w.Write(buf.Bytes())
return err
}

5983
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,12 @@
"version": "1.0.0",
"description": "a web app to edit and action on update",
"scripts": {
"build": "sass --style compressed src/main.scss public/css/main.css",
"start": "sass src/main.scss public/css/main.css",
"js-dev": "parcel build src/main.js --dist-dir public/js --no-content-hash",
"build-sass": "sass --style compressed src/main.scss public/css/main.css",
"build-js": "parcel build src/main.js --dist-dir public/js",
"build-sass-dev": "sass src/main.scss public/css/main.css",
"build-js-dev": "parcel build src/main.js --dist-dir public/js --no-optimize",
"start":"npm run build-sass && npm run build-js",
"serve":"npm run build-sass-dev && npm run build-js-dev",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
@ -15,8 +18,12 @@
"author": "",
"license": "ISC",
"dependencies": {
"@creativebulma/bulma-tooltip": "^1.2.0",
"bulma": "^0.9.3",
"prismjs": "^1.25.0",
"sass": "^1.43.2"
},
"devDependencies": {
"@parcel/transformer-sass": "^2.0.0",
"parcel": "^2.0.0"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/ext/beautify",["require","exports","module","ace/token_iterator"],function(e,t,n){"use strict";function i(e,t){return e.type.lastIndexOf(t+".xml")>-1}var r=e("../token_iterator").TokenIterator;t.singletonTags=["area","base","br","col","command","embed","hr","html","img","input","keygen","link","meta","param","source","track","wbr"],t.blockTags=["article","aside","blockquote","body","div","dl","fieldset","footer","form","head","header","html","nav","ol","p","script","section","style","table","tbody","tfoot","thead","ul"],t.beautify=function(e){var n=new r(e,0,0),s=n.getCurrentToken(),o=e.getTabString(),u=t.singletonTags,a=t.blockTags,f,l=!1,c=!1,h=!1,p="",d="",v="",m=0,g=0,y=0,b=0,w=0,E=0,S=0,x,T=0,N=0,C=[],k=!1,L,A=!1,O=!1,M=!1,_=!1,D={0:0},P=[],H=!1,B=function(){f&&f.value&&f.type!=="string.regexp"&&(f.value=f.value.replace(/^\s*/,""))},j=function(){p=p.replace(/ +$/,"")},F=function(){p=p.trimRight(),l=!1};while(s!==null){T=n.getCurrentTokenRow(),C=n.$rowTokens,f=n.stepForward();if(typeof s!="undefined"){d=s.value,w=0,M=v==="style"||e.$modeId==="ace/mode/css",i(s,"tag-open")?(O=!0,f&&(_=a.indexOf(f.value)!==-1),d==="</"&&(_&&!l&&N<1&&N++,M&&(N=1),w=1,_=!1)):i(s,"tag-close")?O=!1:i(s,"comment.start")?_=!0:i(s,"comment.end")&&(_=!1),!O&&!N&&s.type==="paren.rparen"&&s.value.substr(0,1)==="}"&&N++,T!==x&&(N=T,x&&(N-=x));if(N){F();for(;N>0;N--)p+="\n";l=!0,!i(s,"comment")&&!s.type.match(/^(comment|string)$/)&&(d=d.trimLeft())}if(d){s.type==="keyword"&&d.match(/^(if|else|elseif|for|foreach|while|switch)$/)?(P[m]=d,B(),h=!0,d.match(/^(else|elseif)$/)&&p.match(/\}[\s]*$/)&&(F(),c=!0)):s.type==="paren.lparen"?(B(),d.substr(-1)==="{"&&(h=!0,A=!1,O||(N=1)),d.substr(0,1)==="{"&&(c=!0,p.substr(-1)!=="["&&p.trimRight().substr(-1)==="["?(F(),c=!1):p.trimRight().substr(-1)===")"?F():j())):s.type==="paren.rparen"?(w=1,d.substr(0,1)==="}"&&(P[m-1]==="case"&&w++,p.trimRight().substr(-1)==="{"?F():(c=!0,M&&(N+=2))),d.substr(0,1)==="]"&&p.substr(-1)!=="}"&&p.trimRight().substr(-1)==="}"&&(c=!1,b++,F()),d.substr(0,1)===")"&&p.substr(-1)!=="("&&p.trimRight().substr(-1)==="("&&(c=!1,b++,F()),j()):s.type!=="keyword.operator"&&s.type!=="keyword"||!d.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)?s.type==="punctuation.operator"&&d===";"?(F(),B(),h=!0,M&&N++):s.type==="punctuation.operator"&&d.match(/^(:|,)$/)?(F(),B(),d.match(/^(,)$/)&&S>0&&E===0?N++:(h=!0,l=!1)):s.type==="support.php_tag"&&d==="?>"&&!l?(F(),c=!0):i(s,"attribute-name")&&p.substr(-1).match(/^\s$/)?c=!0:i(s,"attribute-equals")?(j(),B()):i(s,"tag-close")?(j(),d==="/>"&&(c=!0)):s.type==="keyword"&&d.match(/^(case|default)$/)&&H&&(w=1):(F(),B(),c=!0,h=!0);if(l&&(!s.type.match(/^(comment)$/)||!!d.substr(0,1).match(/^[/#]$/))&&(!s.type.match(/^(string)$/)||!!d.substr(0,1).match(/^['"]$/))){b=y;if(m>g){b++;for(L=m;L>g;L--)D[L]=b}else m<g&&(b=D[m]);g=m,y=b,w&&(b-=w),A&&!E&&(b++,A=!1);for(L=0;L<b;L++)p+=o}s.type==="keyword"&&d.match(/^(case|default)$/)?H===!1&&(P[m]=d,m++,H=!0):s.type==="keyword"&&d.match(/^(break)$/)&&P[m-1]&&P[m-1].match(/^(case|default)$/)&&(m--,H=!1),s.type==="paren.lparen"&&(E+=(d.match(/\(/g)||[]).length,S+=(d.match(/\{/g)||[]).length,m+=d.length),s.type==="keyword"&&d.match(/^(if|else|elseif|for|while)$/)?(A=!0,E=0):!E&&d.trim()&&s.type!=="comment"&&(A=!1);if(s.type==="paren.rparen"){E-=(d.match(/\)/g)||[]).length,S-=(d.match(/\}/g)||[]).length;for(L=0;L<d.length;L++)m--,d.substr(L,1)==="}"&&P[m]==="case"&&m--}s.type=="text"&&(d=d.replace(/\s+$/," ")),c&&!l&&(j(),p.substr(-1)!=="\n"&&(p+=" ")),p+=d,h&&(p+=" "),l=!1,c=!1,h=!1;if(i(s,"tag-close")&&(_||a.indexOf(v)!==-1)||i(s,"doctype")&&d===">")_&&f&&f.value==="</"?N=-1:N=1;i(s,"tag-open")&&d==="</"?m--:i(s,"tag-open")&&d==="<"&&u.indexOf(f.value)===-1?m++:i(s,"tag-name")?v=d:i(s,"tag-close")&&d==="/>"&&u.indexOf(v)===-1&&m--,x=T}}s=f}p=p.trim(),e.doc.setValue(p)},t.commands=[{name:"beautify",description:"Format selection (Beautify)",exec:function(e){t.beautify(e.session)},bindKey:"Ctrl-Shift-B"}]}); (function() {
ace.require(["ace/ext/beautify"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/diff_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{regex:"^(?:\\*{15}|={67}|-{3}|\\+{3})$",token:"punctuation.definition.separator.diff",name:"keyword"},{regex:"^(@@)(\\s*.+?\\s*)(@@)(.*)$",token:["constant","constant.numeric","constant","comment.doc.tag"]},{regex:"^(\\d+)([,\\d]+)(a|d|c)(\\d+)([,\\d]+)(.*)$",token:["constant.numeric","punctuation.definition.range.diff","constant.function","constant.numeric","punctuation.definition.range.diff","invalid"],name:"meta."},{regex:"^(\\-{3}|\\+{3}|\\*{3})( .+)$",token:["constant.numeric","meta.tag"]},{regex:"^([!+>])(.*?)(\\s*)$",token:["support.constant","text","invalid"]},{regex:"^([<\\-])(.*?)(\\s*)$",token:["support.function","string","invalid"]},{regex:"^(diff)(\\s+--\\w+)?(.+?)( .+)?$",token:["variable","variable","keyword","variable"]},{regex:"^Index.+$",token:"variable"},{regex:"^\\s+$",token:"text"},{regex:"\\s*$",token:"invalid"},{defaultToken:"invisible",caseInsensitive:!0}]}};r.inherits(s,i),t.DiffHighlightRules=s}),ace.define("ace/mode/folding/diff",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(e,t){this.regExpList=e,this.flag=t,this.foldingStartMarker=RegExp("^("+e.join("|")+")",this.flag)};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=e.getLine(n),i={row:n,column:r.length},o=this.regExpList;for(var u=1;u<=o.length;u++){var a=RegExp("^("+o.slice(0,u).join("|")+")",this.flag);if(a.test(r))break}for(var f=e.getLength();++n<f;){r=e.getLine(n);if(a.test(r))break}if(n==i.row+1)return;return new s(i.row,i.column,n-1,r.length)}}.call(o.prototype)}),ace.define("ace/mode/diff",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/diff_highlight_rules","ace/mode/folding/diff"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./diff_highlight_rules").DiffHighlightRules,o=e("./folding/diff").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o(["diff","@@|\\*{5}"],"i")};r.inherits(u,i),function(){this.$id="ace/mode/diff",this.snippetFileId="ace/snippets/diff"}.call(u.prototype),t.Mode=u}); (function() {
ace.require(["ace/mode/diff"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/gitignore_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:/^\s*#.*$/},{token:"keyword",regex:/^\s*!.*$/}]},this.normalizeRules()};s.metaData={fileTypes:["gitignore"],name:"Gitignore"},r.inherits(s,i),t.GitignoreHighlightRules=s}),ace.define("ace/mode/gitignore",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/gitignore_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./gitignore_highlight_rules").GitignoreHighlightRules,o=function(){this.HighlightRules=s,this.$behaviour=this.$defaultBehaviour};r.inherits(o,i),function(){this.lineCommentStart="#",this.$id="ace/mode/gitignore"}.call(o.prototype),t.Mode=o}); (function() {
ace.require(["ace/mode/gitignore"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/ini_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s="\\\\(?:[\\\\0abtrn;#=:]|x[a-fA-F\\d]{4})",o=function(){this.$rules={start:[{token:"punctuation.definition.comment.ini",regex:"#.*",push_:[{token:"comment.line.number-sign.ini",regex:"$|^",next:"pop"},{defaultToken:"comment.line.number-sign.ini"}]},{token:"punctuation.definition.comment.ini",regex:";.*",push_:[{token:"comment.line.semicolon.ini",regex:"$|^",next:"pop"},{defaultToken:"comment.line.semicolon.ini"}]},{token:["keyword.other.definition.ini","text","punctuation.separator.key-value.ini"],regex:"\\b([a-zA-Z0-9_.-]+)\\b(\\s*)(=)"},{token:["punctuation.definition.entity.ini","constant.section.group-title.ini","punctuation.definition.entity.ini"],regex:"^(\\[)(.*?)(\\])"},{token:"punctuation.definition.string.begin.ini",regex:"'",push:[{token:"punctuation.definition.string.end.ini",regex:"'",next:"pop"},{token:"constant.language.escape",regex:s},{defaultToken:"string.quoted.single.ini"}]},{token:"punctuation.definition.string.begin.ini",regex:'"',push:[{token:"constant.language.escape",regex:s},{token:"punctuation.definition.string.end.ini",regex:'"',next:"pop"},{defaultToken:"string.quoted.double.ini"}]}]},this.normalizeRules()};o.metaData={fileTypes:["ini","conf"],keyEquivalent:"^~I",name:"Ini",scopeName:"source.ini"},r.inherits(o,i),t.IniHighlightRules=o}),ace.define("ace/mode/folding/ini",["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(){};r.inherits(o,s),function(){this.foldingStartMarker=/^\s*\[([^\])]*)]\s*(?:$|[;#])/,this.getFoldWidgetRange=function(e,t,n){var r=this.foldingStartMarker,s=e.getLine(n),o=s.match(r);if(!o)return;var u=o[1]+".",a=s.length,f=e.getLength(),l=n,c=n;while(++n<f){s=e.getLine(n);if(/^\s*$/.test(s))continue;o=s.match(r);if(o&&o[1].lastIndexOf(u,0)!==0)break;c=n}if(c>l){var h=e.getLine(c).length;return new i(l,a,c,h)}}}.call(o.prototype)}),ace.define("ace/mode/ini",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/ini_highlight_rules","ace/mode/folding/ini"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./ini_highlight_rules").IniHighlightRules,o=e("./folding/ini").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart=";",this.blockComment=null,this.$id="ace/mode/ini"}.call(u.prototype),t.Mode=u}); (function() {
ace.require(["ace/mode/ini"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/plain_text",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/behaviour"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./text_highlight_rules").TextHighlightRules,o=e("./behaviour").Behaviour,u=function(){this.HighlightRules=s,this.$behaviour=new o};r.inherits(u,i),function(){this.type="text",this.getNextLineIndent=function(e,t,n){return""},this.$id="ace/mode/plain_text"}.call(u.prototype),t.Mode=u}); (function() {
ace.require(["ace/mode/plain_text"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/properties_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e=/\\u[0-9a-fA-F]{4}|\\/;this.$rules={start:[{token:"comment",regex:/[!#].*$/},{token:"keyword",regex:/[=:]$/},{token:"keyword",regex:/[=:]/,next:"value"},{token:"constant.language.escape",regex:e},{defaultToken:"variable"}],value:[{regex:/\\$/,token:"string",next:"value"},{regex:/$/,token:"string",next:"start"},{token:"constant.language.escape",regex:e},{defaultToken:"string"}]}};r.inherits(s,i),t.PropertiesHighlightRules=s}),ace.define("ace/mode/properties",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/properties_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./properties_highlight_rules").PropertiesHighlightRules,o=function(){this.HighlightRules=s,this.$behaviour=this.$defaultBehaviour};r.inherits(o,i),function(){this.$id="ace/mode/properties"}.call(o.prototype),t.Mode=o}); (function() {
ace.require(["ace/mode/properties"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/scheme_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="case|do|let|loop|if|else|when",t="eq?|eqv?|equal?|and|or|not|null?",n="#t|#f",r="cons|car|cdr|cond|lambda|lambda*|syntax-rules|format|set!|quote|eval|append|list|list?|member?|load",i=this.createKeywordMapper({"keyword.control":e,"keyword.operator":t,"constant.language":n,"support.function":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:";.*$"},{token:["storage.type.function-type.scheme","text","entity.name.function.scheme"],regex:"(?:\\b(?:(define|define-syntax|define-macro))\\b)(\\s+)((?:\\w|\\-|\\!|\\?)*)"},{token:"punctuation.definition.constant.character.scheme",regex:"#:\\S+"},{token:["punctuation.definition.variable.scheme","variable.other.global.scheme","punctuation.definition.variable.scheme"],regex:"(\\*)(\\S*)(\\*)"},{token:"constant.numeric",regex:"#[xXoObB][0-9a-fA-F]+"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?"},{token:i,regex:"[a-zA-Z_#][a-zA-Z0-9_\\-\\?\\!\\*]*"},{token:"string",regex:'"(?=.)',next:"qqstring"}],qqstring:[{token:"constant.character.escape.scheme",regex:"\\\\."},{token:"string",regex:'[^"\\\\]+',merge:!0},{token:"string",regex:"\\\\$",next:"qqstring",merge:!0},{token:"string",regex:'"|$',next:"start",merge:!0}]}};r.inherits(s,i),t.SchemeHighlightRules=s}),ace.define("ace/mode/matching_parens_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){var t=e.match(/^(\s+)/);return t?t[1]:""}}).call(i.prototype),t.MatchingParensOutdent=i}),ace.define("ace/mode/scheme",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/scheme_highlight_rules","ace/mode/matching_parens_outdent"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./scheme_highlight_rules").SchemeHighlightRules,o=e("./matching_parens_outdent").MatchingParensOutdent,u=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart=";",this.minorIndentFunctions=["define","lambda","define-macro","define-syntax","syntax-rules","define-record-type","define-structure"],this.$toIndent=function(e){return e.split("").map(function(e){return/\s/.exec(e)?e:" "}).join("")},this.$calculateIndent=function(e,t){var n=this.$getIndent(e),r=0,i,s;for(var o=e.length-1;o>=0;o--){s=e[o],s==="("?(r--,i=!0):s==="("||s==="["||s==="{"?(r--,i=!1):(s===")"||s==="]"||s==="}")&&r++;if(r<0)break}if(!(r<0&&i))return r<0&&!i?this.$toIndent(e.substring(0,o+1)):r>0?(n=n.substring(0,n.length-t.length),n):n;o+=1;var u=o,a="";for(;;){s=e[o];if(s===" "||s===" ")return this.minorIndentFunctions.indexOf(a)!==-1?this.$toIndent(e.substring(0,u-1)+t):this.$toIndent(e.substring(0,o+1));if(s===undefined)return this.$toIndent(e.substring(0,u-1)+t);a+=e[o],o++}},this.getNextLineIndent=function(e,t,n){return this.$calculateIndent(t,n)},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/scheme"}.call(u.prototype),t.Mode=u}); (function() {
ace.require(["ace/mode/scheme"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|when|then|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|foreign|not|references|default|null|inner|cross|natural|database|drop|grant",t="true|false",n="avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|coalesce|ifnull|isnull|nvl",r="int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|money|real|number|integer",i=this.createKeywordMapper({"support.function":n,keyword:e,"constant.language":t,"storage.type":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:"--.*$"},{token:"comment",start:"/\\*",end:"\\*/"},{token:"string",regex:'".*?"'},{token:"string",regex:"'.*?'"},{token:"string",regex:"`.*?`"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:i,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\(]"},{token:"paren.rparen",regex:"[\\)]"},{token:"text",regex:"\\s+"}]},this.normalizeRules()};r.inherits(s,i),t.SqlHighlightRules=s}),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(++t<a){n=e.getLine(t);var f=n.search(/\S/);if(f===-1)continue;if(r>f)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(++n<s){t=e.getLine(n);var f=u.exec(t);if(!f)continue;f[1]?a--:a++;if(!a)break}var l=n;if(l>o)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/folding/sql",["require","exports","module","ace/lib/oop","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./cstyle").FoldMode,s=t.FoldMode=function(){};r.inherits(s,i),function(){}.call(s.prototype)}),ace.define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules","ace/mode/folding/sql"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sql_highlight_rules").SqlHighlightRules,o=e("./folding/sql").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="--",this.blockComment={start:"/*",end:"*/"},this.$id="ace/mode/sql",this.snippetFileId="ace/snippets/sql"}.call(u.prototype),t.Mode=u}); (function() {
ace.require(["ace/mode/sql"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/mode/text"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?=\s|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?=\s|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d]*(?:$|\s+(?:$|#))/,onMatch:function(e,t,n,r){r=r.replace(/ #.*/,"");var i=/^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/,"").length,s=parseInt(/\d+[\s+-]*$/.exec(r));return s?(i+=s-1,this.next="mlString"):this.next="mlStringPre",n.length?(n[0]=this.next,n[1]=i):(n.push(this.next),n.push(i)),this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:/[^\s,:\[\]\{\}]+/}],mlStringPre:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.shift(),n.shift()):(n[1]=e.length-1,this.next=n[0]="mlString"),this.token},next:"mlString"},{defaultToken:"string"}],mlString:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),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/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart=["#"],this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return 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.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a}); (function() {
ace.require(["ace/mode/yaml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/asciidoc"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/batchfile"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/c_cpp",["require","exports","module"],function(e,t,n){"use strict";t.snippetText="## STL Collections\n# std::array\nsnippet array\n std::array<${1:T}, ${2:N}> ${3};${4}\n# std::vector\nsnippet vector\n std::vector<${1:T}> ${2};${3}\n# std::deque\nsnippet deque\n std::deque<${1:T}> ${2};${3}\n# std::forward_list\nsnippet flist\n std::forward_list<${1:T}> ${2};${3}\n# std::list\nsnippet list\n std::list<${1:T}> ${2};${3}\n# std::set\nsnippet set\n std::set<${1:T}> ${2};${3}\n# std::map\nsnippet map\n std::map<${1:Key}, ${2:T}> ${3};${4}\n# std::multiset\nsnippet mset\n std::multiset<${1:T}> ${2};${3}\n# std::multimap\nsnippet mmap\n std::multimap<${1:Key}, ${2:T}> ${3};${4}\n# std::unordered_set\nsnippet uset\n std::unordered_set<${1:T}> ${2};${3}\n# std::unordered_map\nsnippet umap\n std::unordered_map<${1:Key}, ${2:T}> ${3};${4}\n# std::unordered_multiset\nsnippet umset\n std::unordered_multiset<${1:T}> ${2};${3}\n# std::unordered_multimap\nsnippet ummap\n std::unordered_multimap<${1:Key}, ${2:T}> ${3};${4}\n# std::stack\nsnippet stack\n std::stack<${1:T}> ${2};${3}\n# std::queue\nsnippet queue\n std::queue<${1:T}> ${2};${3}\n# std::priority_queue\nsnippet pqueue\n std::priority_queue<${1:T}> ${2};${3}\n##\n## Access Modifiers\n# private\nsnippet pri\n private\n# protected\nsnippet pro\n protected\n# public\nsnippet pub\n public\n# friend\nsnippet fr\n friend\n# mutable\nsnippet mu\n mutable\n## \n## Class\n# class\nsnippet cl\n class ${1:`Filename('$1', 'name')`} \n {\n public:\n $1(${2});\n ~$1();\n\n private:\n ${3:/* data */}\n };\n# member function implementation\nsnippet mfun\n ${4:void} ${1:`Filename('$1', 'ClassName')`}::${2:memberFunction}(${3}) {\n ${5:/* code */}\n }\n# namespace\nsnippet ns\n namespace ${1:`Filename('', 'my')`} {\n ${2}\n } /* namespace $1 */\n##\n## Input/Output\n# std::cout\nsnippet cout\n std::cout << ${1} << std::endl;${2}\n# std::cin\nsnippet cin\n std::cin >> ${1};${2}\n##\n## Iteration\n# for i \nsnippet fori\n for (int ${2:i} = 0; $2 < ${1:count}; $2${3:++}) {\n ${4:/* code */}\n }${5}\n\n# foreach\nsnippet fore\n for (${1:auto} ${2:i} : ${3:container}) {\n ${4:/* code */}\n }${5}\n# iterator\nsnippet iter\n for (${1:std::vector}<${2:type}>::${3:const_iterator} ${4:i} = ${5:container}.begin(); $4 != $5.end(); ++$4) {\n ${6}\n }${7}\n\n# auto iterator\nsnippet itera\n for (auto ${1:i} = $1.begin(); $1 != $1.end(); ++$1) {\n ${2:std::cout << *$1 << std::endl;}\n }${3}\n##\n## Lambdas\n# lamda (one line)\nsnippet ld\n [${1}](${2}){${3:/* code */}}${4}\n# lambda (multi-line)\nsnippet lld\n [${1}](${2}){\n ${3:/* code */}\n }${4}\n",t.scope="c_cpp"}); (function() {
ace.require(["ace/snippets/c_cpp"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/diff",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# DEP-3 (http://dep.debian.net/deps/dep3/) style patch header\nsnippet header DEP-3 style header\n Description: ${1}\n Origin: ${2:vendor|upstream|other}, ${3:url of the original patch}\n Bug: ${4:url in upstream bugtracker}\n Forwarded: ${5:no|not-needed|url}\n Author: ${6:`g:snips_author`}\n Reviewed-by: ${7:name and email}\n Last-Update: ${8:`strftime("%Y-%m-%d")`}\n Applied-Upstream: ${9:upstream version|url|commit}\n\n',t.scope="diff"}); (function() {
ace.require(["ace/snippets/diff"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/dockerfile"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/gitignore"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/golang"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/ini"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/java",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='## Access Modifiers\nsnippet po\n protected\nsnippet pu\n public\nsnippet pr\n private\n##\n## Annotations\nsnippet before\n @Before\n static void ${1:intercept}(${2:args}) { ${3} }\nsnippet mm\n @ManyToMany\n ${1}\nsnippet mo\n @ManyToOne\n ${1}\nsnippet om\n @OneToMany${1:(cascade=CascadeType.ALL)}\n ${2}\nsnippet oo\n @OneToOne\n ${1}\n##\n## Basic Java packages and import\nsnippet im\n import\nsnippet j.b\n java.beans.\nsnippet j.i\n java.io.\nsnippet j.m\n java.math.\nsnippet j.n\n java.net.\nsnippet j.u\n java.util.\n##\n## Class\nsnippet cl\n class ${1:`Filename("", "untitled")`} ${2}\nsnippet in\n interface ${1:`Filename("", "untitled")`} ${2:extends Parent}${3}\nsnippet tc\n public class ${1:`Filename()`} extends ${2:TestCase}\n##\n## Class Enhancements\nsnippet ext\n extends \nsnippet imp\n implements\n##\n## Comments\nsnippet /*\n /*\n * ${1}\n */\n##\n## Constants\nsnippet co\n static public final ${1:String} ${2:var} = ${3};${4}\nsnippet cos\n static public final String ${1:var} = "${2}";${3}\n##\n## Control Statements\nsnippet case\n case ${1}:\n ${2}\nsnippet def\n default:\n ${2}\nsnippet el\n else\nsnippet elif\n else if (${1}) ${2}\nsnippet if\n if (${1}) ${2}\nsnippet sw\n switch (${1}) {\n ${2}\n }\n##\n## Create a Method\nsnippet m\n ${1:void} ${2:method}(${3}) ${4:throws }${5}\n##\n## Create a Variable\nsnippet v\n ${1:String} ${2:var}${3: = null}${4};${5}\n##\n## Enhancements to Methods, variables, classes, etc.\nsnippet ab\n abstract\nsnippet fi\n final\nsnippet st\n static\nsnippet sy\n synchronized\n##\n## Error Methods\nsnippet err\n System.err.print("${1:Message}");\nsnippet errf\n System.err.printf("${1:Message}", ${2:exception});\nsnippet errln\n System.err.println("${1:Message}");\n##\n## Exception Handling\nsnippet as\n assert ${1:test} : "${2:Failure message}";${3}\nsnippet ca\n catch(${1:Exception} ${2:e}) ${3}\nsnippet thr\n throw\nsnippet ths\n throws\nsnippet try\n try {\n ${3}\n } catch(${1:Exception} ${2:e}) {\n }\nsnippet tryf\n try {\n ${3}\n } catch(${1:Exception} ${2:e}) {\n } finally {\n }\n##\n## Find Methods\nsnippet findall\n List<${1:listName}> ${2:items} = ${1}.findAll();${3}\nsnippet findbyid\n ${1:var} ${2:item} = ${1}.findById(${3});${4}\n##\n## Javadocs\nsnippet /**\n /**\n * ${1}\n */\nsnippet @au\n @author `system("grep \\`id -un\\` /etc/passwd | cut -d \\":\\" -f5 | cut -d \\",\\" -f1")`\nsnippet @br\n @brief ${1:Description}\nsnippet @fi\n @file ${1:`Filename()`}.java\nsnippet @pa\n @param ${1:param}\nsnippet @re\n @return ${1:param}\n##\n## Logger Methods\nsnippet debug\n Logger.debug(${1:param});${2}\nsnippet error\n Logger.error(${1:param});${2}\nsnippet info\n Logger.info(${1:param});${2}\nsnippet warn\n Logger.warn(${1:param});${2}\n##\n## Loops\nsnippet enfor\n for (${1} : ${2}) ${3}\nsnippet for\n for (${1}; ${2}; ${3}) ${4}\nsnippet wh\n while (${1}) ${2}\n##\n## Main method\nsnippet main\n public static void main (String[] args) {\n ${1:/* code */}\n }\n##\n## Print Methods\nsnippet print\n System.out.print("${1:Message}");\nsnippet printf\n System.out.printf("${1:Message}", ${2:args});\nsnippet println\n System.out.println(${1});\n##\n## Render Methods\nsnippet ren\n render(${1:param});${2}\nsnippet rena\n renderArgs.put("${1}", ${2});${3}\nsnippet renb\n renderBinary(${1:param});${2}\nsnippet renj\n renderJSON(${1:param});${2}\nsnippet renx\n renderXml(${1:param});${2}\n##\n## Setter and Getter Methods\nsnippet set\n ${1:public} void set${3:}(${2:String} ${4:}){\n this.$4 = $4;\n }\nsnippet get\n ${1:public} ${2:String} get${3:}(){\n return this.${4:};\n }\n##\n## Terminate Methods or Loops\nsnippet re\n return\nsnippet br\n break;\n##\n## Test Methods\nsnippet t\n public void test${1:Name}() throws Exception {\n ${2}\n }\nsnippet test\n @Test\n public void test${1:Name}() throws Exception {\n ${2}\n }\n##\n## Utils\nsnippet Sc\n Scanner\n##\n## Miscellaneous\nsnippet action\n public static void ${1:index}(${2:args}) { ${3} }\nsnippet rnf\n notFound(${1:param});${2}\nsnippet rnfin\n notFoundIfNull(${1:param});${2}\nsnippet rr\n redirect(${1:param});${2}\nsnippet ru\n unauthorized(${1:param});${2}\nsnippet unless\n (unless=${1:param});${2}\n',t.scope="java"}); (function() {
ace.require(["ace/snippets/java"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/javascript",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# Get Elements\nsnippet gett\n getElementsBy${1:TagName}(\'${2}\')${3}\n# Get Element\nsnippet get\n getElementBy${1:Id}(\'${2}\')${3}\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# singleton\nsnippet sing\n function ${1:Singleton} (${2:argument}) {\n // the cached instance\n var instance;\n\n // rewrite the constructor\n $1 = function $1($2) {\n return instance;\n };\n \n // carry over the prototype properties\n $1.prototype = this;\n\n // the instance\n instance = new $1();\n\n // reset the constructor pointer\n instance.constructor = $1;\n\n ${3:// code ...}\n\n return instance;\n }\n# class\nsnippet class\nregex /^\\s*/clas{0,2}/\n var ${1:class} = function(${20}) {\n $40$0\n };\n \n (function() {\n ${60:this.prop = ""}\n }).call(${1:class}.prototype);\n \n exports.${1:class} = ${1:class};\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n\n\n#modules\nsnippet def\n define(function(require, exports, module) {\n "use strict";\n var ${1/.*\\///} = require("${1}");\n \n $TM_SELECTED_TEXT\n });\nsnippet req\nguard ^\\s*\n var ${1/.*\\///} = require("${1}");\n $0\nsnippet requ\nguard ^\\s*\n var ${1/.*\\/(.)/\\u$1/} = require("${1}").${1/.*\\/(.)/\\u$1/};\n $0\n',t.scope="javascript"}); (function() {
ace.require(["ace/snippets/javascript"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/json"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/json5"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/makefile",["require","exports","module"],function(e,t,n){"use strict";t.snippetText="snippet ifeq\n ifeq (${1:cond0},${2:cond1})\n ${3:code}\n endif\n",t.scope="makefile"}); (function() {
ace.require(["ace/snippets/makefile"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/markdown",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Markdown\n\n# Includes octopress (http://octopress.org/) snippets\n\nsnippet [\n [${1:text}](http://${2:address} "${3:title}")\nsnippet [*\n [${1:link}](${2:`@*`} "${3:title}")${4}\n\nsnippet [:\n [${1:id}]: http://${2:url} "${3:title}"\nsnippet [:*\n [${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet ![\n ![${1:alttext}](${2:/images/image.jpg} "${3:title}")\nsnippet ![*\n ![${1:alt}](${2:`@*`} "${3:title}")${4}\n\nsnippet ![:\n ![${1:id}]: ${2:url} "${3:title}"\nsnippet ![:*\n ![${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet ===\nregex /^/=+/=*//\n ${PREV_LINE/./=/g}\n \n ${0}\nsnippet ---\nregex /^/-+/-*//\n ${PREV_LINE/./-/g}\n \n ${0}\nsnippet blockquote\n {% blockquote %}\n ${1:quote}\n {% endblockquote %}\n\nsnippet blockquote-author\n {% blockquote ${1:author}, ${2:title} %}\n ${3:quote}\n {% endblockquote %}\n\nsnippet blockquote-link\n {% blockquote ${1:author} ${2:URL} ${3:link_text} %}\n ${4:quote}\n {% endblockquote %}\n\nsnippet bt-codeblock-short\n ```\n ${1:code_snippet}\n ```\n\nsnippet bt-codeblock-full\n ``` ${1:language} ${2:title} ${3:URL} ${4:link_text}\n ${5:code_snippet}\n ```\n\nsnippet codeblock-short\n {% codeblock %}\n ${1:code_snippet}\n {% endcodeblock %}\n\nsnippet codeblock-full\n {% codeblock ${1:title} lang:${2:language} ${3:URL} ${4:link_text} %}\n ${5:code_snippet}\n {% endcodeblock %}\n\nsnippet gist-full\n {% gist ${1:gist_id} ${2:filename} %}\n\nsnippet gist-short\n {% gist ${1:gist_id} %}\n\nsnippet img\n {% img ${1:class} ${2:URL} ${3:width} ${4:height} ${5:title_text} ${6:alt_text} %}\n\nsnippet youtube\n {% youtube ${1:video_id} %}\n\n# The quote should appear only once in the text. It is inherently part of it.\n# See http://octopress.org/docs/plugins/pullquote/ for more info.\n\nsnippet pullquote\n {% pullquote %}\n ${1:text} {" ${2:quote} "} ${3:text}\n {% endpullquote %}\n',t.scope="markdown"}); (function() {
ace.require(["ace/snippets/markdown"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/nginx"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/plain_text"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/powershell"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/python",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='snippet #!\n #!/usr/bin/env python\nsnippet imp\n import ${1:module}\nsnippet from\n from ${1:package} import ${2:module}\n# Module Docstring\nsnippet docs\n \'\'\'\n File: ${1:FILENAME:file_name}\n Author: ${2:author}\n Description: ${3}\n \'\'\'\nsnippet wh\n while ${1:condition}:\n ${2:# TODO: write code...}\n# dowh - does the same as do...while in other languages\nsnippet dowh\n while True:\n ${1:# TODO: write code...}\n if ${2:condition}:\n break\nsnippet with\n with ${1:expr} as ${2:var}:\n ${3:# TODO: write code...}\n# New Class\nsnippet cl\n class ${1:ClassName}(${2:object}):\n """${3:docstring for $1}"""\n def __init__(self, ${4:arg}):\n ${5:super($1, self).__init__()}\n self.$4 = $4\n ${6}\n# New Function\nsnippet def\n def ${1:fname}(${2:`indent(\'.\') ? \'self\' : \'\'`}):\n """${3:docstring for $1}"""\n ${4:# TODO: write code...}\nsnippet deff\n def ${1:fname}(${2:`indent(\'.\') ? \'self\' : \'\'`}):\n ${3:# TODO: write code...}\n# New Method\nsnippet defs\n def ${1:mname}(self, ${2:arg}):\n ${3:# TODO: write code...}\n# New Property\nsnippet property\n def ${1:foo}():\n doc = "${2:The $1 property.}"\n def fget(self):\n ${3:return self._$1}\n def fset(self, value):\n ${4:self._$1 = value}\n# Ifs\nsnippet if\n if ${1:condition}:\n ${2:# TODO: write code...}\nsnippet el\n else:\n ${1:# TODO: write code...}\nsnippet ei\n elif ${1:condition}:\n ${2:# TODO: write code...}\n# For\nsnippet for\n for ${1:item} in ${2:items}:\n ${3:# TODO: write code...}\n# Encodes\nsnippet cutf8\n # -*- coding: utf-8 -*-\nsnippet clatin1\n # -*- coding: latin-1 -*-\nsnippet cascii\n # -*- coding: ascii -*-\n# Lambda\nsnippet ld\n ${1:var} = lambda ${2:vars} : ${3:action}\nsnippet .\n self.\nsnippet try Try/Except\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\nsnippet try Try/Except/Else\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n else:\n ${5:# TODO: write code...}\nsnippet try Try/Except/Finally\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n finally:\n ${5:# TODO: write code...}\nsnippet try Try/Except/Else/Finally\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n else:\n ${5:# TODO: write code...}\n finally:\n ${6:# TODO: write code...}\n# if __name__ == \'__main__\':\nsnippet ifmain\n if __name__ == \'__main__\':\n ${1:main()}\n# __magic__\nsnippet _\n __${1:init}__${2}\n# python debugger (pdb)\nsnippet pdb\n import pdb; pdb.set_trace()\n# ipython debugger (ipdb)\nsnippet ipdb\n import ipdb; ipdb.set_trace()\n# ipython debugger (pdbbb)\nsnippet pdbbb\n import pdbpp; pdbpp.set_trace()\nsnippet pprint\n import pprint; pprint.pprint(${1})${2}\nsnippet "\n """\n ${1:doc}\n """\n# test function/method\nsnippet test\n def test_${1:description}(${2:self}):\n ${3:# TODO: write code...}\n# test case\nsnippet testcase\n class ${1:ExampleCase}(unittest.TestCase):\n \n def test_${2:description}(self):\n ${3:# TODO: write code...}\nsnippet fut\n from __future__ import ${1}\n#getopt\nsnippet getopt\n try:\n # Short option syntax: "hv:"\n # Long option syntax: "help" or "verbose="\n opts, args = getopt.getopt(sys.argv[1:], "${1:short_options}", [${2:long_options}])\n \n except getopt.GetoptError, err:\n # Print debug info\n print str(err)\n ${3:error_action}\n\n for option, argument in opts:\n if option in ("-h", "--help"):\n ${4}\n elif option in ("-v", "--verbose"):\n verbose = argument\n',t.scope="python"}); (function() {
ace.require(["ace/snippets/python"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/sass"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/scss"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/sh",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Shebang. Executing bash via /usr/bin/env makes scripts more portable.\nsnippet #!\n #!/usr/bin/env bash\n \nsnippet if\n if [[ ${1:condition} ]]; then\n ${2:#statements}\n fi\nsnippet elif\n elif [[ ${1:condition} ]]; then\n ${2:#statements}\nsnippet for\n for (( ${2:i} = 0; $2 < ${1:count}; $2++ )); do\n ${3:#statements}\n done\nsnippet fori\n for ${1:needle} in ${2:haystack} ; do\n ${3:#statements}\n done\nsnippet wh\n while [[ ${1:condition} ]]; do\n ${2:#statements}\n done\nsnippet until\n until [[ ${1:condition} ]]; do\n ${2:#statements}\n done\nsnippet case\n case ${1:word} in\n ${2:pattern})\n ${3};;\n esac\nsnippet go \n while getopts \'${1:o}\' ${2:opts} \n do \n case $$2 in\n ${3:o0})\n ${4:#staments};;\n esac\n done\n# Set SCRIPT_DIR variable to directory script is located.\nsnippet sdir\n SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"\n# getopt\nsnippet getopt\n __ScriptVersion="${1:version}"\n\n #=== FUNCTION ================================================================\n # NAME: usage\n # DESCRIPTION: Display usage information.\n #===============================================================================\n function usage ()\n {\n cat <<- EOT\n\n Usage : $${0:0} [options] [--] \n\n Options: \n -h|help Display this message\n -v|version Display script version\n\n EOT\n } # ---------- end of function usage ----------\n\n #-----------------------------------------------------------------------\n # Handle command line arguments\n #-----------------------------------------------------------------------\n\n while getopts ":hv" opt\n do\n case $opt in\n\n h|help ) usage; exit 0 ;;\n\n v|version ) echo "$${0:0} -- Version $__ScriptVersion"; exit 0 ;;\n\n \\? ) echo -e "\\n Option does not exist : $OPTARG\\n"\n usage; exit 1 ;;\n\n esac # --- end of case ---\n done\n shift $(($OPTIND-1))\n\n',t.scope="sh"}); (function() {
ace.require(["ace/snippets/sh"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/snippets/sql",["require","exports","module"],function(e,t,n){"use strict";t.snippetText="snippet tbl\n create table ${1:table} (\n ${2:columns}\n );\nsnippet col\n ${1:name} ${2:type} ${3:default ''} ${4:not null}\nsnippet ccol\n ${1:name} varchar2(${2:size}) ${3:default ''} ${4:not null}\nsnippet ncol\n ${1:name} number ${3:default 0} ${4:not null}\nsnippet dcol\n ${1:name} date ${3:default sysdate} ${4:not null}\nsnippet ind\n create index ${3:$1_$2} on ${1:table}(${2:column});\nsnippet uind\n create unique index ${1:name} on ${2:table}(${3:column});\nsnippet tblcom\n comment on table ${1:table} is '${2:comment}';\nsnippet colcom\n comment on column ${1:table}.${2:column} is '${3:comment}';\nsnippet addcol\n alter table ${1:table} add (${2:column} ${3:type});\nsnippet seq\n create sequence ${1:name} start with ${2:1} increment by ${3:1} minvalue ${4:1};\nsnippet s*\n select * from ${1:table}\n",t.scope="sql"}); (function() {
ace.require(["ace/snippets/sql"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/text"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/xml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/yaml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View File

@ -1,8 +0,0 @@
ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-monokai",t.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() {
ace.require(["ace/theme/monokai"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

0
public/css/.gitkeep Normal file
View File

File diff suppressed because one or more lines are too long

View File

@ -2,5 +2,5 @@ package public
import "embed"
//go:embed css ace assets
//go:embed js css
var FS embed.FS

0
public/js/.gitkeep Normal file
View File

35
release.sh Executable file → Normal file
View File

@ -1,35 +0,0 @@
VERSION=$(git describe --tags --abbrev=0)
if [ $? -ne 0 ]; then VERSION=$DRONE_TAG; fi
BUILD=$(git rev-parse --short HEAD)
if [ $? -ne 0 ]; then BUILD=${DRONE_COMMIT:0:7}; fi
PROJ=configui
HUB=hub.kumoly.io
HUB_PROJECT=tools
DIST=dist
LDFLAGS="-ldflags \"-X main.Version=${VERSION} -X main.Build=${BUILD} -w -s\""
FAILURES=""
PLATFORMS="darwin/amd64 darwin/arm64"
PLATFORMS="$PLATFORMS windows/amd64"
PLATFORMS="$PLATFORMS linux/amd64"
for PLATFORM in $PLATFORMS; do
GOOS=${PLATFORM%/*}
GOARCH=${PLATFORM#*/}
BIN_FILENAME="${PROJ}"
if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi
CMD="GOOS=${GOOS} GOARCH=${GOARCH} go build ${LDFLAGS} -o ${DIST}/${BIN_FILENAME} $@"
echo "${CMD}"
eval $CMD || FAILURES="${FAILURES} ${PLATFORM}"
sh -c "cd ${DIST} && tar -czf ${PROJ}-${VERSION}-${GOOS}-${GOARCH}.tar.gz ${BIN_FILENAME} && rm ${BIN_FILENAME}"
done
if [[ "${FAILURES}" != "" ]]; then
echo ""
echo "${SCRIPT_NAME} failed on: ${FAILURES}"
exit 1
fi

42
route.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"net/http"
)
func setRoutes(mux *http.ServeMux) {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
Files := []string{}
for file := range files {
Files = append(Files, files[file].Alias)
}
contentB, err := GetConfig()
content := ""
if err != nil {
content = err.Error()
} else {
content = string(contentB)
}
data := struct {
Active string
Files []string
Content string
Lang string
}{
Files: Files,
Active: "ConfigUI",
Content: content,
Lang: "json",
}
Parse(w, "home", data)
})
}

View File

@ -1,148 +0,0 @@
{
"title": "ConfigUI",
"type": "object",
"id": "configui",
"properties": {
"app_name": {
"type": "string",
"title": "AppName",
"default": "ConfigUI",
"required":true
},
"files":{
"title":"Files",
"type": "array",
"format":"table",
"items":{
"$ref": "#/definitions/file"
}
},
"actions":{
"title":"Actions",
"type": "array",
"format":"table",
"items":{
"$ref": "#/definitions/action"
}
},
"base_url":{
"type": "string",
"title": "BaseUrl",
"default": "/"
},
"config_path":{
"title":"Config Path",
"type": "string",
"discription":"path to config file"
},
"shell":{
"title":"Shell",
"type": "string",
"description":"shell to use when running cmds",
"required":true,
"options":{
"infoText":"run commands are structured as SHELL -c \"CMD\", \nthe default shell is \nunix:`/usr/bin/sh`\nwindows:`C:\\Windows\\System32\\cmd`\ndarwin:`/bin/bash`"
}
},
"allow_ip":{
"type":"string",
"title":"AllowedIP",
"description":"IPs to allow, blank to allow all"
},
"timeout":{
"type": "string",
"title": "Command Timeout",
"default": "10s",
"description": "timeout to wait for command to finish"
},
"log_path": {
"type":"string",
"title":"Log Path",
"description":"empty for stdout"
},
"log_level": {
"type": "integer",
"title": "log level",
"default": 9
},
"cust":{
"title": "Custom Template",
"type": "string",
"description": "path to custom templates"
},
"result_bellow": {
"title":"Result Bellow",
"type": "boolean",
"format": "checkbox",
"description":"show results bellow editor",
"default": false
},
"production":{
"type": "boolean",
"format": "checkbox",
"title": "Production Mode",
"default": true
},
"no_reconfig":{
"title":"NoReconfig",
"type": "boolean",
"format": "checkbox",
"description":"disable config at runtime",
"default": false
},
"hide_config": {
"title":"Hide Config",
"type": "boolean",
"format": "checkbox",
"description":"hide config panel",
"default": false
}
},
"definitions": {
"file": {
"type": "object",
"properties":{
"name": {
"title": "Name",
"type": "string"
},
"path": {
"title": "Path",
"type": "string"
},
"cmd": {
"title": "Command",
"type": "string"
},
"ro": {
"title": "Read Only",
"format":"checkbox",
"type": "boolean"
},
"lang": {
"title": "Lang",
"type": "string",
"description":"set editor language mode"
},
"order": {
"type": "integer",
"default":0
}
}
},
"action":{
"type": "object",
"properties":{
"name": {
"title": "Name",
"type": "string"
},
"cmd": {
"title": "Command",
"type": "string"
}
}
}
}
}

142
server.go
View File

@ -1,142 +0,0 @@
package configui
import (
"net/http"
"path/filepath"
"kumoly.io/lib/ksrv"
"kumoly.io/lib/stat"
"kumoly.io/tools/kconfig"
)
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 {
k := ksrv.New()
cui.ksrv_log = k.GetLogger()
cui.setLog()
k.SetNoLogCondition(func(r *http.Request) bool {
ext := filepath.Ext(r.URL.Path)
switch ext {
case ".js":
return true
case ".css":
return true
case ".ttf":
return true
case ".ico":
return true
}
return r.URL.Query().Get("f") == "true"
})
return k.Middleware(
http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if cui.AllowIP != "" {
ip := ksrv.GetIP(r)
if !ksrv.MatchIPGlob(ip, cui.AllowIP) {
rw.WriteHeader(403)
panic("permission denied")
}
}
next.ServeHTTP(rw, r)
}),
)
}
func (cui *ConfigUI) mux() *http.ServeMux {
cuiR := http.NewServeMux()
cuiR.HandleFunc("/api/profile", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
ksrv.JSON(w, stat.GetProfile())
} else {
w.WriteHeader(404)
}
})
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/delta", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
cui.GetDelta(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.HandleFunc("/api/action", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
cui.DoAction(w, r)
} else {
w.WriteHeader(404)
}
})
cuiR.HandleFunc("/api/integration", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
cui.DoIntegration(w, r)
} else {
w.WriteHeader(404)
}
})
k := kconfig.New()
k.Schema = SCHEMA
k.Load = func() []byte {
b, err := cui.Config()
if err != nil {
cui.log.Error(err)
return []byte{}
}
return b
}
k.Apply = func(b []byte) error {
err := cui.LoadConfig(string(b))
if err == nil {
k.AppName = cui.AppName
}
return err
}
cuiR.Handle("/kconfig/", http.StripPrefix("/kconfig", k))
cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS))))
cuiR.HandleFunc("/", cui.App)
return cuiR
}

View File

@ -1,17 +0,0 @@
// pre.ace_editor {
// padding: 1px;
// }
#editor,#result_editor {
font-size: 1rem;
position: relative;
width: inherit !important;
}
#editor {
height: 70vh;
}
#result_editor {
min-height: 50vh;
}

139
src/editor.js Normal file
View File

@ -0,0 +1,139 @@
import Prism from "prismjs";
import "prismjs/plugins/custom-class/prism-custom-class";
Prism.plugins.customClass.map({ number: "prism-number", tag: "prism-tag" });
// codeInput
// by WebCoder49
// Based on a CSS-Tricks Post
// Needs Prism.js
var codeInput = {
update: function(text, code_input) {
code_input.setAttribute("value", text);
let result_element = code_input.getElementsByClassName("code-input_highlighting-content")[0];
// Handle final newlines (see article)
if(text[text.length-1] == "\n") {
text += " ";
}
// Update code
result_element.innerHTML = text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
// Syntax Highlight
Prism.highlightElement(result_element);
},
sync_scroll: function(element, code_input) {
/* Scroll result to scroll coords of event - sync with textarea */
let result_element = code_input.getElementsByClassName("code-input_highlighting")[0];
// Get and set x and y
result_element.scrollTop = element.scrollTop;
result_element.scrollLeft = element.scrollLeft;
},
check_tab: function(element, event) {
let code = element.value;
if(event.key == "Tab") {
/* Tab key pressed */
event.preventDefault(); // stop normal
let before_tab = code.slice(0, element.selectionStart); // text before tab
let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab
let cursor_pos = element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab
element.value = before_tab + "\t" + after_tab; // add tab char
// move cursor
element.selectionStart = cursor_pos;
element.selectionEnd = cursor_pos;
}
}
}
window.codeInput = codeInput
class CodeInput extends HTMLElement { // Create code input element
constructor() {
super(); // Element
}
connectedCallback() {
// Added to document
/* Defaults */
let lang = this.getAttribute("lang") || "HTML";
let placeholder = this.getAttribute("placeholder") || "Enter " + this.lang + " Source Code";
let value = this.getAttribute("value") || this.innerHTML || "";
this.innerHTML = ""; // Clear Content
/* Create Textarea */
let textarea = document.createElement("textarea");
textarea.placeholder = placeholder;
textarea.value = value;
textarea.className = "code-input_editing";
textarea.setAttribute("spellcheck", "false");
if(this.getAttribute("name")) {
textarea.setAttribute("name", this.getAttribute("name")); // for use in forms
this.removeAttribute("name");
}
textarea.setAttribute("oninput", "codeInput.update(this.value, this.parentElement); codeInput.sync_scroll(this, this.parentElement);");
textarea.setAttribute("onscroll", "codeInput.sync_scroll(this, this.parentElement);");
textarea.setAttribute("onkeydown", "codeInput.check_tab(this, event);");
this.append(textarea);
/* Create pre code */
let code = document.createElement("code");
code.className = "code-input_highlighting-content language-" + lang; // Language for Prism.js
code.innerText = value;
let pre = document.createElement("pre");
pre.className = "code-input_highlighting";
pre.setAttribute("aria-hidden", "true"); // Hide for screen readers
pre.append(code);
this.append(pre);
/* Add code from value attribute - useful for loading from backend */
let text = this.value;
let result_element = code
// Handle final newlines (see article)
// if(text[text.length-1] == "\n") {
// text += " ";
// }
// Update code
result_element.innerHTML = text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
// Syntax Highlight
Prism.highlightElement(result_element);
}
static get observedAttributes() {
return ["value", "placeholder"]; // Attributes to monitor
}
attributeChangedCallback(name, oldValue, newValue) {
let textarea = this.getElementsByClassName("code-input_editing")[0];
let result_element = this.getElementsByClassName("code-input_highlighting-content")[0];
switch(name) {
case "value":
// Handle final newlines (see article)
// if(text[text.length-1] == "\n") {
// text += " ";
// }
// Update code
result_element.innerHTML = text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
// Syntax Highlight
Prism.highlightElement(result_element);
break;
case "placeholder":
textarea.placeholder = oldValue;
break;
}
}
}
customElements.define("code-input", CodeInput); // Set tag

View File

@ -1,104 +1 @@
window.ToolIsFollow = false;
FileGet= async function(follower=false){
let f = ''
if (Active == 'ConfigUI') {
if (follower) f = '?f=true'
const res = await fetch('/api/conf'+f)
.catch(err=>{console.log(err);return;});
const body = await res.text();
if(!res.ok){
Catch(res)
return
}
editor.session.setValue(body);
}
else {
if (follower) f = '&f=true'
const res = await fetch('/api/file?name=' + Active + f)
.catch(err=>{console.log(err);return;});
const body = await res.json();
if(!res.ok){
Catch(res)
return
}
editor.session.setValue(body.data);
}
}
FileSave = async function(){
if (Active == 'ConfigUI') {
const res = await fetch('/api/conf', {
method: 'POST',
body: editor.getValue(),
headers: new Headers({
'Content-Type': 'application/json'
})
}).catch(err=>{console.log(err);return;});
if (res.ok) window.location.reload();
else Catch(res)
}
else {
const res = await fetch('/api/file', {
method: 'POST',
body: JSON.stringify({"name":Active,"data":editor.getValue()}),
headers: new Headers({
'Content-Type': 'application/json'
})
}).catch(err=>{console.log(err);return;});
if(!res.ok) Catch(res)
}
}
FileApply = async function(){
if (Active == 'ConfigUI') {
return;
}
else {
const res = await fetch('/api/apply?name='+ Active, {
method: 'POST',
}).catch(err=>{console.log(err);return;});
if(!res.ok){
const result = await Catch(res)
result_editor.session.setValue(result)
return
}
const result = await res.text()
result_editor.session.setValue(result)
}
}
Catch=async function(res){
console.trace()
console.log(res)
const msg = await res.text()
ShowError(msg)
return msg
}
// starting point
(function(){
// setup ace editor
setEditor()
// setup result code block
setResult()
// for follow mode
setInterval((async ()=>{
if (ToolIsFollow){
await FileGet(true)
editor.gotoLine(editor.session.getLength());
}
}), 1000)
// block ctl-s
window.addEventListener("keypress", function(event) {
if (!(event.which == 115 && event.ctrlKey) && !(event.which == 19)) return true
alert("Ctrl-S pressed")
event.preventDefault()
return false
})
}())
import "./editor"

View File

@ -1,55 +1,5 @@
@charset "utf-8";
$modal-content-width: 90vw;
$footer-padding: 0.5rem 1.5rem 0.5rem;
@import "../node_modules/bulma/bulma.sass";
@import "../node_modules/@creativebulma/bulma-tooltip/src/sass/index.sass";
@import "./ace.scss";
@import "./material-icon.scss";
#error_view {
position: fixed; /* Sit on top of the page content */
top: 1;
left: 50%;
z-index: 50;
transform: translateX(-50%)
}
#title {
padding: 0.5rem;
}
#kconfigFrame{
border: none;
width: 100%;
height: 100%;
display: block;
}
.icn-spinner {
animation: spin-animation 1s infinite;
display: inline-block;
}
.icn-loading {
animation: spin-animation 3s infinite;
display: inline-block;
}
@keyframes spin-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.ace_selection {
// background: #999900 !important;
background: #7f7f00 !important;
}
@import "./scss/prism.scss";
@import "./scss/editor.scss";

View File

@ -1,21 +0,0 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(../assets/mat.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}

Some files were not shown because too many files have changed in this diff Show More