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": "" | ||||
|     } | ||||
|   ], | ||||
|   "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 | ||||
|  |  | |||
							
								
								
									
										28
									
								
								app.go
								
								
								
								
							
							
						
						
									
										28
									
								
								app.go
								
								
								
								
							|  | @ -23,14 +23,16 @@ type Editor struct { | |||
| } | ||||
| 
 | ||||
| type Page struct { | ||||
| 	AppName string | ||||
| 	BaseUrl string | ||||
| 	Version string | ||||
| 	Build   string | ||||
| 	Files   []ActiveFile | ||||
| 	Error   string | ||||
| 	File    ActiveFile | ||||
| 	Editor  Editor | ||||
| 	AppName      string | ||||
| 	BaseUrl      string | ||||
| 	Actions      []Action | ||||
| 	Integrations []*Integration | ||||
| 	Version      string | ||||
| 	Build        string | ||||
| 	Files        []ActiveFile | ||||
| 	Error        string | ||||
| 	File         ActiveFile | ||||
| 	Editor       Editor | ||||
| 
 | ||||
| 	Static       bool | ||||
| 	HideConfig   bool | ||||
|  | @ -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" | ||||
|  |  | |||
							
								
								
									
										39
									
								
								configui.go
								
								
								
								
							
							
						
						
									
										39
									
								
								configui.go
								
								
								
								
							|  | @ -7,6 +7,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
|  | @ -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"` | ||||
|  | @ -43,8 +55,10 @@ type ConfigUI struct { | |||
| 	CmdTimeout string `json:"timeout"` | ||||
| 	cmdTimeout time.Duration | ||||
| 
 | ||||
| 	Files     []*File `json:"files"` | ||||
| 	fileIndex map[string]int | ||||
| 	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"` | ||||
|  | @ -55,11 +69,11 @@ 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:"-"` | ||||
| 	PublicFS   embed.FS `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" | ||||
|  |  | |||
							
								
								
									
										31
									
								
								handler.go
								
								
								
								
							
							
						
						
									
										31
									
								
								handler.go
								
								
								
								
							|  | @ -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 { | ||||
|  |  | |||
							
								
								
									
										14
									
								
								server.go
								
								
								
								
							
							
						
						
									
										14
									
								
								server.go
								
								
								
								
							|  | @ -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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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}} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue