Compare commits
59 Commits
feat/prism
...
master
Author | SHA1 | Date |
---|---|---|
Evan Chen | 639e3e97a5 | |
Evan Chen | 49b28eef61 | |
Evan Chen | 9bbabf6479 | |
Evan Chen | dcc140b722 | |
Evan Chen | 5634bf108f | |
Evan | 591fb2cfa1 | |
Evan Chen | 6801e56013 | |
Evan Chen | 93410f9ddf | |
Evan Chen | 0b0a54085c | |
Evan Chen | 221b52da75 | |
Evan Chen | 3895e90876 | |
Evan Chen | b0d76dff3e | |
Evan Chen | 4b1ad5b791 | |
Evan Chen | 3327182dc0 | |
Evan Chen | 69ad2a961c | |
Evan Chen | ef76eff228 | |
Evan Chen | 4415ffe2ee | |
Evan Chen | acec4a6af9 | |
Evan Chen | eddd813846 | |
Evan Chen | 091c026a46 | |
Evan Chen | 85b5416db5 | |
Evan Chen | ac7c25dde3 | |
Evan Chen | 94dfb39d59 | |
Evan Chen | 060b112eb3 | |
Evan Chen | 8b85b8e89b | |
Evan Chen | f76cc2f75e | |
Evan Chen | 0aa278efa0 | |
Evan Chen | 04580e4672 | |
Evan Chen | 239be22094 | |
Evan Chen | cff4c13b78 | |
Evan Chen | 3a64217a10 | |
Evan Chen | 38afcf64cf | |
Evan Chen | c027d486c1 | |
Evan | 9814f61015 | |
evanchen | c9031c6f3f | |
Evan | 54f1d3278f | |
Evan | 40549e6552 | |
Evan Chen | e985a8f30e | |
evanchen | 02b7446064 | |
Evan Chen | 45d425c78b | |
Evan Chen | 1832e2c4ff | |
Evan Chen | d3c7ed256f | |
Evan Chen | 1510bea15b | |
Evan Chen | 3c40c2396e | |
Evan Chen | 12db987421 | |
Evan Chen | 44fc4c5686 | |
Evan Chen | 1a9e4c8d67 | |
evanchen | db1f9f3ee8 | |
Evan Chen | a2c16da520 | |
Evan Chen | 442aad83ec | |
Evan Chen | e90f1e8e87 | |
Evan Chen | b1b7ca737b | |
Evan Chen | 42b92ff5bc | |
Evan Chen | 82bcd3155b | |
Evan Chen | b60ccb0a61 | |
Evan Chen | 0a750b700f | |
Evan Chen | 539bdbf25e | |
Evan Chen | 01562e5389 | |
evanchen | 1d55d83799 |
|
@ -0,0 +1,36 @@
|
||||||
|
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
|
|
@ -1,5 +1,5 @@
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
public/css/main.css*
|
public/css/main.css.map
|
||||||
public/js/main.js*
|
public/js/main.js*
|
|
@ -0,0 +1,40 @@
|
||||||
|
# 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)
|
13
Dockerfile
13
Dockerfile
|
@ -1,14 +1,17 @@
|
||||||
FROM golang:1.17.2-alpine3.14 as builder
|
FROM golang:1.17.2-alpine3.14 as builder
|
||||||
RUN apk update && apk add --no-cache git tzdata
|
RUN apk update && apk add --no-cache git tzdata
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum /src/
|
|
||||||
RUN go mod download
|
# not using any libs, for now
|
||||||
|
|
||||||
|
# COPY go.mod go.sum /src/
|
||||||
|
# RUN go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN VERSION=$(git describe --tags) BUILD=$(git rev-parse --short HEAD) && \
|
RUN VERSION=$(git describe --tags --abbrev=0) BUILD=$(git rev-parse --short HEAD) && \
|
||||||
GOOS=linux GOARCH=amd64 \
|
GOOS=linux GOARCH=amd64 \
|
||||||
go build -ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w" \
|
go build -ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w" \
|
||||||
-o /go/bin/configui main.go
|
-o /go/bin/configui cmd/configui/main.go
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.14
|
FROM alpine:3.14
|
||||||
|
@ -16,4 +19,4 @@ EXPOSE 5080
|
||||||
ENV PATH="/go/bin:${PATH}"
|
ENV PATH="/go/bin:${PATH}"
|
||||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
COPY --from=builder /go/bin/configui /go/bin/configui
|
COPY --from=builder /go/bin/configui /go/bin/configui
|
||||||
CMD ["/go/bin/configui"]
|
CMD ["/go/bin/configui","-log","/var/log/configui.log","-f","/data/configui.json"]
|
36
Makefile
36
Makefile
|
@ -5,15 +5,49 @@ HUB=hub.kumoly.io
|
||||||
HUB_PROJECT=tools
|
HUB_PROJECT=tools
|
||||||
|
|
||||||
LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w"
|
LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w"
|
||||||
|
PLATFORMS=darwin linux
|
||||||
|
ARCHITECTURES=amd64
|
||||||
|
APPS=configui
|
||||||
|
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
|
install:
|
||||||
|
npm install
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
$(shell cd dist; ./${PROJ} -log configui.log)
|
$(shell cd dist; ./${PROJ} -log configui.log)
|
||||||
|
|
||||||
|
.PHONY: web
|
||||||
|
web:
|
||||||
|
npm run build
|
||||||
|
# npm run js-dev
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
go build ${LDFLAGS} -o dist/${PROJ}
|
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
|
133
README.md
133
README.md
|
@ -1,11 +1,13 @@
|
||||||
# Config UI
|
# Config UI
|
||||||
|
|
||||||
a web app to edit and action on update
|
a web app to edit and action on update powered by [ACE](https://ace.c9.io/#nav=howto&api=edit_session)
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: configui [options]
|
Usage: configui [options]
|
||||||
|
-allow string
|
||||||
|
IPs to allow, blank to allow all
|
||||||
-bind string
|
-bind string
|
||||||
address to bind (default "localhost:8000")
|
address to bind (default "0.0.0.0:8000")
|
||||||
-c string
|
-c string
|
||||||
cmd to apply
|
cmd to apply
|
||||||
-f string
|
-f string
|
||||||
|
@ -13,30 +15,133 @@ Usage: configui [options]
|
||||||
-log string
|
-log string
|
||||||
log to file
|
log to file
|
||||||
-n string
|
-n string
|
||||||
alias of file
|
Name of file
|
||||||
-p string
|
-p string
|
||||||
path to file, precedence over config
|
path to file, precedence over config
|
||||||
|
-static
|
||||||
|
disable config api
|
||||||
-v show version
|
-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
|
## Config
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"app_name": "ConfigUI",
|
||||||
|
"base_url": "/",
|
||||||
|
"config_path": "conf.json",
|
||||||
|
"no_reconfig": false,
|
||||||
|
"allow_ip": "",
|
||||||
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "configui.log",
|
"path": "main.go",
|
||||||
"ro": true
|
"name": "GoPlayground",
|
||||||
},
|
"action": "go run main.go",
|
||||||
{
|
"ro": false,
|
||||||
"path": "etc/test.ini",
|
"lang": "",
|
||||||
"name": "test",
|
"order": 0,
|
||||||
"action": "myip local -P"
|
"data": ""
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"Actions": [
|
||||||
|
{
|
||||||
|
"name": "User",
|
||||||
|
"cmd": "whoami"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result_bellow": false,
|
||||||
|
"hide_config": false,
|
||||||
|
"log_path": "access.log",
|
||||||
|
"silent_sys_out": false
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`configui -f PATH/TO/CONFIG`
|
`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
|
## Api
|
||||||
|
|
||||||
### Files
|
### Files
|
||||||
|
@ -57,7 +162,7 @@ res:
|
||||||
|
|
||||||
### File
|
### File
|
||||||
|
|
||||||
`GET /api/file?name=ALIAS`
|
`GET /api/file?name=Name`
|
||||||
|
|
||||||
res:
|
res:
|
||||||
```json
|
```json
|
||||||
|
@ -83,4 +188,4 @@ req:
|
||||||
|
|
||||||
### Apply
|
### Apply
|
||||||
|
|
||||||
`POST /api/apply?name=ALIAS`
|
`POST /api/apply?name=Name`
|
||||||
|
|
125
api.go
125
api.go
|
@ -1,125 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,276 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package configui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"kumoly.io/lib/ksrv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrorServerReloading = ksrv.Error{
|
||||||
|
Code: http.StatusServiceUnavailable,
|
||||||
|
ID: "ErrorServerReloading",
|
||||||
|
Message: "server is reloading",
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
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
14
go.mod
|
@ -1,3 +1,17 @@
|
||||||
module kumoly.io/tools/configui
|
module kumoly.io/tools/configui
|
||||||
|
|
||||||
go 1.17
|
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
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
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=
|
|
@ -0,0 +1,260 @@
|
||||||
|
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
152
main.go
|
@ -1,152 +0,0 @@
|
||||||
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
108
netutil.go
|
@ -1,108 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
|
@ -3,12 +3,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "a web app to edit and action on update",
|
"description": "a web app to edit and action on update",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-sass": "sass --style compressed src/main.scss public/css/main.css",
|
"build": "sass --style compressed src/main.scss public/css/main.css",
|
||||||
"build-js": "parcel build src/main.js --dist-dir public/js",
|
"start": "sass src/main.scss public/css/main.css",
|
||||||
"build-sass-dev": "sass src/main.scss public/css/main.css",
|
"js-dev": "parcel build src/main.js --dist-dir public/js --no-content-hash",
|
||||||
"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"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -18,12 +15,8 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@creativebulma/bulma-tooltip": "^1.2.0",
|
||||||
"bulma": "^0.9.3",
|
"bulma": "^0.9.3",
|
||||||
"prismjs": "^1.25.0",
|
|
||||||
"sass": "^1.43.2"
|
"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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (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
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/asciidoc"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/batchfile"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/dockerfile"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/gitignore"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (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
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/ini"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/json"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/json5"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/nginx"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/plain_text"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/powershell"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/sass"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/scss"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/text"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/xml"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
; (function() {
|
||||||
|
ace.require(["ace/snippets/yaml"], function(m) {
|
||||||
|
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||||
|
module.exports = m;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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.
File diff suppressed because one or more lines are too long
|
@ -2,5 +2,5 @@ package public
|
||||||
|
|
||||||
import "embed"
|
import "embed"
|
||||||
|
|
||||||
//go:embed js css
|
//go:embed css ace assets
|
||||||
var FS embed.FS
|
var FS embed.FS
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
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
42
route.go
|
@ -1,42 +0,0 @@
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// pre.ace_editor {
|
||||||
|
// padding: 1px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
#editor,#result_editor {
|
||||||
|
font-size: 1rem;
|
||||||
|
position: relative;
|
||||||
|
width: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result_editor {
|
||||||
|
min-height: 50vh;
|
||||||
|
}
|
105
src/main.js
105
src/main.js
|
@ -1 +1,104 @@
|
||||||
import "./editor"
|
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}())
|
|
@ -1,5 +1,55 @@
|
||||||
@charset "utf-8";
|
@charset "utf-8";
|
||||||
@import "../node_modules/bulma/bulma.sass";
|
|
||||||
|
|
||||||
@import "./scss/prism.scss";
|
|
||||||
@import "./scss/editor.scss";
|
$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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
233
src/tmp.css
233
src/tmp.css
|
@ -1,233 +0,0 @@
|
||||||
/* Code-Input Compability */
|
|
||||||
/* By WebCoder49 */
|
|
||||||
/* First Published on CSS-Tricks.com */
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
border: none;
|
|
||||||
overflow: auto;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
resize: none; /*remove the resize handle on the bottom right*/
|
|
||||||
}
|
|
||||||
.content .tag, .content .number {
|
|
||||||
display: inline;
|
|
||||||
padding: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
text-align: inherit;
|
|
||||||
vertical-align: inherit;
|
|
||||||
border-radius: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
white-space: inherit;
|
|
||||||
background: inherit;
|
|
||||||
margin: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Please see the article */
|
|
||||||
|
|
||||||
#editing, #highlighting {
|
|
||||||
/* Both elements need the same text and space styling so they are directly on top of each other */
|
|
||||||
margin: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 0;
|
|
||||||
width: calc(100% - 32px);
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
#editing, #highlighting, #highlighting * {
|
|
||||||
/* Also add text styles to highlighing tokens */
|
|
||||||
font-size: 15pt;
|
|
||||||
font-family: monospace;
|
|
||||||
line-height: 20pt;
|
|
||||||
tab-size: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#editing, #highlighting {
|
|
||||||
/* In the same place */
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Move the textarea in front of the result */
|
|
||||||
|
|
||||||
#editing {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
#highlighting {
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Make textarea almost completely transparent */
|
|
||||||
|
|
||||||
#editing {
|
|
||||||
color: transparent;
|
|
||||||
background: transparent;
|
|
||||||
caret-color: white; /* Or choose your favourite color */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Can be scrolled */
|
|
||||||
#editing, #highlighting {
|
|
||||||
overflow: auto;
|
|
||||||
white-space: nowrap; /* Allows textarea to scroll horizontally */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No resize on textarea */
|
|
||||||
#editing {
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paragraphs; First Image */
|
|
||||||
* {
|
|
||||||
font-family: "Fira Code", monospace;
|
|
||||||
}
|
|
||||||
p code {
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #eee;
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Syntax Highlighting from prism.js starts below, partly modified: */
|
|
||||||
|
|
||||||
/* PrismJS 1.23.0
|
|
||||||
https://prismjs.com/download.html#themes=prism-funky&languages=markup */
|
|
||||||
/**
|
|
||||||
* prism.js Funky theme
|
|
||||||
* Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/
|
|
||||||
* @author Lea Verou
|
|
||||||
*/
|
|
||||||
|
|
||||||
code[class*="language-"],
|
|
||||||
pre[class*="language-"] {
|
|
||||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
text-align: left;
|
|
||||||
white-space: pre;
|
|
||||||
word-spacing: normal;
|
|
||||||
word-break: normal;
|
|
||||||
word-wrap: normal;
|
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
|
|
||||||
-webkit-hyphens: none;
|
|
||||||
-moz-hyphens: none;
|
|
||||||
-ms-hyphens: none;
|
|
||||||
hyphens: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code blocks */
|
|
||||||
pre[class*="language-"] {
|
|
||||||
padding: .4em .8em;
|
|
||||||
margin: .5em 0;
|
|
||||||
overflow: auto;
|
|
||||||
/* background: url('data:image/svg+xml;charset=utf-8,<svg%20version%3D"1.1"%20xmlns%3D"http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg"%20width%3D"100"%20height%3D"100"%20fill%3D"rgba(0%2C0%2C0%2C.2)">%0D%0A<polygon%20points%3D"0%2C50%2050%2C0%200%2C0"%20%2F>%0D%0A<polygon%20points%3D"0%2C100%2050%2C100%20100%2C50%20100%2C0"%20%2F>%0D%0A<%2Fsvg>');
|
|
||||||
background-size: 1em 1em; - WebCoder49*/
|
|
||||||
background: black; /* - WebCoder49 */
|
|
||||||
}
|
|
||||||
|
|
||||||
code[class*="language-"] {
|
|
||||||
background: black;
|
|
||||||
color: white;
|
|
||||||
box-shadow: -.3em 0 0 .3em black, .3em 0 0 .3em black;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inline code */
|
|
||||||
:not(pre) > code[class*="language-"] {
|
|
||||||
padding: .2em;
|
|
||||||
border-radius: .3em;
|
|
||||||
box-shadow: none;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.comment,
|
|
||||||
.token.prolog,
|
|
||||||
.token.doctype,
|
|
||||||
.token.cdata {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.punctuation {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.namespace {
|
|
||||||
opacity: .7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.property,
|
|
||||||
.token.tag,
|
|
||||||
.token.boolean,
|
|
||||||
.token.number,
|
|
||||||
.token.constant,
|
|
||||||
.token.symbol {
|
|
||||||
color: #0cf;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.selector,
|
|
||||||
.token.attr-name,
|
|
||||||
.token.string,
|
|
||||||
.token.char,
|
|
||||||
.token.builtin {
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.operator,
|
|
||||||
.token.entity,
|
|
||||||
.token.url,
|
|
||||||
.language-css .token.string,
|
|
||||||
.token.variable,
|
|
||||||
.token.inserted {
|
|
||||||
color: yellowgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.atrule,
|
|
||||||
.token.attr-value,
|
|
||||||
.token.keyword {
|
|
||||||
color: deeppink;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.regex,
|
|
||||||
.token.important {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.important,
|
|
||||||
.token.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.token.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.entity {
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.deleted {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Plugin styles: Diff Highlight */
|
|
||||||
pre.diff-highlight.diff-highlight > code .token.deleted:not(.prefix),
|
|
||||||
pre > code.diff-highlight.diff-highlight .token.deleted:not(.prefix) {
|
|
||||||
background-color: rgba(255, 0, 0, .3);
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.diff-highlight.diff-highlight > code .token.inserted:not(.prefix),
|
|
||||||
pre > code.diff-highlight.diff-highlight .token.inserted:not(.prefix) {
|
|
||||||
background-color: rgba(0, 255, 128, .3);
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* End of prism.js syntax highlighting*/
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue