Compare commits

...

1 Commits

Author SHA1 Message Date
Evan Chen 6d9aa92e7e fix: #51 2021-11-09 11:27:58 +08:00
7 changed files with 176 additions and 16 deletions

View File

@ -50,6 +50,12 @@ sudo sh -c "curl -fsSL RELEASE_URL | tar -C /usr/local/bin/ -xz"
"data": "" "data": ""
} }
], ],
"Actions": [
{
"name": "User",
"cmd": "whoami"
}
],
"result_bellow": false, "result_bellow": false,
"hide_config": false, "hide_config": false,
"log_path": "access.log", "log_path": "access.log",
@ -59,6 +65,19 @@ sudo sh -c "curl -fsSL RELEASE_URL | tar -C /usr/local/bin/ -xz"
`configui -f PATH/TO/CONFIG` `configui -f PATH/TO/CONFIG`
## 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 ## Systemd
```ini ```ini

12
app.go
View File

@ -25,6 +25,8 @@ type Editor struct {
type Page struct { type Page struct {
AppName string AppName string
BaseUrl string BaseUrl string
Actions []Action
Integrations []*Integration
Version string Version string
Build string Build string
Files []ActiveFile Files []ActiveFile
@ -44,6 +46,12 @@ func (cui *ConfigUI) App(w http.ResponseWriter, r *http.Request) {
} }
Files := []ActiveFile{} 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 { for _, i := range cui.fileIndex {
Files = append(Files, ActiveFile{ Files = append(Files, ActiveFile{
Name: cui.Files[i].Name, Name: cui.Files[i].Name,
@ -69,6 +77,8 @@ func (cui *ConfigUI) App(w http.ResponseWriter, r *http.Request) {
Editor: Editor{ Editor: Editor{
Platform: plat, Platform: plat,
}, },
Actions: cui.Actions,
Integrations: cui.Integrations,
Static: cui.NoReconfig, Static: cui.NoReconfig,
HideConfig: cui.HideConfig, HideConfig: cui.HideConfig,
ResultBellow: cui.ResultBellow, ResultBellow: cui.ResultBellow,
@ -83,7 +93,7 @@ func (cui *ConfigUI) App(w http.ResponseWriter, r *http.Request) {
if name == "" || err != nil { if name == "" || err != nil {
tmp, err = cui.Config() tmp, err = cui.Config()
data.File.Name = cui.AppName data.File.Name = cui.AppName
data.File.Path = ":mem:" // data.File.Path = ":mem:"
data.Editor.Lang = "json" data.Editor.Lang = "json"
if name != "" { if name != "" {
data.Error = name + " not found\n" data.Error = name + " not found\n"

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
@ -32,6 +33,17 @@ var Ext2Mode map[string]string = map[string]string{
"md": "markdown", "md": "markdown",
} }
type Action struct {
Name string `json:"name"`
Cmd string `json:"cmd"`
}
type Integration struct {
Name string `json:"name"`
Description string `json:"description"`
Cmd func() (string, error) `json:"-"`
}
type ConfigUI struct { type ConfigUI struct {
AppName string `json:"app_name"` AppName string `json:"app_name"`
Prod bool `json:"production"` Prod bool `json:"production"`
@ -45,6 +57,8 @@ type ConfigUI struct {
Files []*File `json:"files"` Files []*File `json:"files"`
fileIndex map[string]int fileIndex map[string]int
Actions []Action `json:"actions"`
Integrations []*Integration `json:"integrations"`
ResultBellow bool `json:"result_bellow"` ResultBellow bool `json:"result_bellow"`
HideConfig bool `json:"hide_config"` HideConfig bool `json:"hide_config"`
@ -56,10 +70,10 @@ type ConfigUI struct {
TmplFS embed.FS `json:"-"` TmplFS embed.FS `json:"-"`
tmpl *engine.Engine tmpl *engine.Engine
PublicFS embed.FS `json:"-"` PublicFS embed.FS `json:"-"`
log *klog.Logger `json:"-"` log *klog.Logger
ksrv_log *klog.Logger `json:"-"` ksrv_log *klog.Logger
f *os.File `json:"-"` f *os.File
configLock sync.Mutex `json:"-"` configLock sync.Mutex
} }
func New() *ConfigUI { func New() *ConfigUI {
@ -71,12 +85,16 @@ func New() *ConfigUI {
} }
return items return items
}, },
"normal": func(name string) string {
return strings.ReplaceAll(name, " ", "-")
},
}).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl")) }).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
return &ConfigUI{ return &ConfigUI{
fileIndex: map[string]int{}, fileIndex: map[string]int{},
Prod: true, Prod: true,
AppName: "ConfigUI", AppName: "ConfigUI",
BaseUrl: "/", BaseUrl: "/",
Actions: []Action{{Name: "User", Cmd: "whoami"}},
PublicFS: public.FS, PublicFS: public.FS,
TmplFS: tmplFS, TmplFS: tmplFS,
tmpl: tmpl, tmpl: tmpl,
@ -133,6 +151,13 @@ func (cui *ConfigUI) LoadConfig(confstr string) error {
cui.NoReconfig = tmpConf.NoReconfig cui.NoReconfig = tmpConf.NoReconfig
cui.ResultBellow = tmpConf.ResultBellow cui.ResultBellow = tmpConf.ResultBellow
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 cui.AppName = tmpConf.AppName
if cui.AppName == "" { if cui.AppName == "" {
cui.AppName = "ConfigUI" cui.AppName = "ConfigUI"

View File

@ -128,6 +128,37 @@ func (cui *ConfigUI) Apply(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(result)) w.Write([]byte(result))
} }
func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
for _, v := range cui.Actions {
if v.Name == name {
file := &File{Name: name, Action: v.Cmd, owner: cui}
result, err := file.Do(cui.cmdTimeout)
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 _, v := range cui.Integrations {
if v.Name == 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) { func (cui *ConfigUI) Download(w http.ResponseWriter, r *http.Request) {
if name := r.URL.Query().Get("name"); name != "" { if name := r.URL.Query().Get("name"); name != "" {
if name == cui.AppName { if name == cui.AppName {

View File

@ -84,6 +84,20 @@ func (cui *ConfigUI) mux() *http.ServeMux {
w.WriteHeader(404) 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)
}
})
cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS)))) cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS))))
cuiR.HandleFunc("/", cui.App) cuiR.HandleFunc("/", cui.App)
return cuiR return cuiR

View File

@ -75,6 +75,19 @@ async function FileApply(){
} }
} }
async function DoAction(type, name){
const res = await fetch('{{.BaseUrl}}api/'+type+'?name='+ name, {
method: 'POST',
});
if(!res.ok){
const result = await Catch(res)
result_editor.session.setValue(result)
return
}
const result = await res.text()
result_editor.session.setValue(result)
}
async function FileDelta(){ async function FileDelta(){
if (Active == '{{.AppName}}') { if (Active == '{{.AppName}}') {
await FileGet() await FileGet()

View File

@ -18,6 +18,32 @@ var Active = "{{.File.Name}}";
<div class="box"> <div class="box">
{{template "components/menu" .}} {{template "components/menu" .}}
</div> </div>
{{if .Actions}}
<div class="box">
<p class="menu-label has-text-left">Actions</p>
<div class="buttons are-small">
{{- range .Actions -}}
<button class="button has-tooltip-arrow" id="actbtn-{{.Name|normal}}"
data-tooltip="{{.Cmd}}" onclick="toolDoAction('{{.Name}}')">
<span>{{- .Name -}}</span>
</button>
{{- end -}}
</div>
</div>
{{end}}
{{if .Integrations}}
<div class="box">
<p class="menu-label has-text-left">Integrations</p>
<div class="buttons are-small">
{{- range .Integrations -}}
<button class="button has-tooltip-arrow" id="itgbtn-{{.Name|normal}}"
data-tooltip="{{.Description}}" onclick="toolDoIntegration('{{.Name}}')">
<span>{{- .Name -}}</span>
</button>
{{- end -}}
</div>
</div>
{{end}}
<div class="box has-text-centered"> <div class="box has-text-centered">
<a href="{{.BaseUrl}}api/export" class="button is-small">Export Files</a> <a href="{{.BaseUrl}}api/export" class="button is-small">Export Files</a>
</div> </div>
@ -37,6 +63,28 @@ var Active = "{{.File.Name}}";
{{end}} {{end}}
</div> </div>
</div> </div>
<script>
async function toolDoAction(name){
let el = document.getElementById('actbtn-'+name.replaceAll(" ","-"));
el.classList.add("is-loading")
el.classList.remove("has-tooltip-arrow")
await DoAction('action',name)
el.classList.remove("is-loading")
el.classList.add("has-tooltip-arrow")
{{if not .ResultBellow}}ResultViewTog(){{end}}
}
async function toolDoIntegration(name){
let el = document.getElementById('itgbtn-'+name.replaceAll(" ","-"));
el.classList.add("is-loading")
el.classList.remove("has-tooltip-arrow")
await DoAction('integration',name)
el.classList.remove("is-loading")
el.classList.add("has-tooltip-arrow")
{{if not .ResultBellow}}ResultViewTog(){{end}}
}
</script>
{{if not .ResultBellow}} {{if not .ResultBellow}}
{{template "components/result" .}} {{template "components/result" .}}
{{end}} {{end}}