fix: #51
continuous-integration/drone/tag Build is passing Details

feat/muzan
Evan Chen 2021-11-09 11:27:58 +08:00
parent ac7c25dde3
commit 85b5416db5
7 changed files with 177 additions and 17 deletions

View File

@ -50,6 +50,12 @@ sudo sh -c "curl -fsSL RELEASE_URL | tar -C /usr/local/bin/ -xz"
"data": ""
}
],
"Actions": [
{
"name": "User",
"cmd": "whoami"
}
],
"result_bellow": false,
"hide_config": false,
"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`
## 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

12
app.go
View File

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

View File

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

View File

@ -128,6 +128,37 @@ func (cui *ConfigUI) Apply(w http.ResponseWriter, r *http.Request) {
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) {
if name := r.URL.Query().Get("name"); name != "" {
if name == cui.AppName {

View File

@ -84,6 +84,20 @@ func (cui *ConfigUI) mux() *http.ServeMux {
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.HandleFunc("/", cui.App)
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(){
if (Active == '{{.AppName}}') {
await FileGet()

View File

@ -18,6 +18,32 @@ var Active = "{{.File.Name}}";
<div class="box">
{{template "components/menu" .}}
</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">
<a href="{{.BaseUrl}}api/export" class="button is-small">Export Files</a>
</div>
@ -37,6 +63,28 @@ var Active = "{{.File.Name}}";
{{end}}
</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}}
{{template "components/result" .}}
{{end}}