fix: #51
parent
ac7c25dde3
commit
6d9aa92e7e
19
README.md
19
README.md
|
@ -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
|
||||||
|
|
28
app.go
28
app.go
|
@ -23,14 +23,16 @@ type Editor struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
AppName string
|
AppName string
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
Version string
|
Actions []Action
|
||||||
Build string
|
Integrations []*Integration
|
||||||
Files []ActiveFile
|
Version string
|
||||||
Error string
|
Build string
|
||||||
File ActiveFile
|
Files []ActiveFile
|
||||||
Editor Editor
|
Error string
|
||||||
|
File ActiveFile
|
||||||
|
Editor Editor
|
||||||
|
|
||||||
Static bool
|
Static bool
|
||||||
HideConfig bool
|
HideConfig bool
|
||||||
|
@ -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"
|
||||||
|
|
39
configui.go
39
configui.go
|
@ -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"`
|
||||||
|
@ -43,8 +55,10 @@ type ConfigUI struct {
|
||||||
CmdTimeout string `json:"timeout"`
|
CmdTimeout string `json:"timeout"`
|
||||||
cmdTimeout time.Duration
|
cmdTimeout time.Duration
|
||||||
|
|
||||||
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"`
|
||||||
|
@ -55,11 +69,11 @@ 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"
|
||||||
|
|
31
handler.go
31
handler.go
|
@ -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 {
|
||||||
|
|
14
server.go
14
server.go
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
Loading…
Reference in New Issue