parent
							
								
									e90f1e8e87
								
							
						
					
					
						commit
						442aad83ec
					
				|  | @ -2,9 +2,13 @@ | |||
| 
 | ||||
| ## Feature | ||||
| 
 | ||||
| * download files #26 | ||||
| * add goto last line toolbar #21 | ||||
| 
 | ||||
| ## Fix | ||||
| 
 | ||||
| * all cmds should be pass straight back to user | ||||
| * timeout on none ending cmds #34 | ||||
| 
 | ||||
| # 0.1.1 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										29
									
								
								api.go
								
								
								
								
							
							
						
						
									
										29
									
								
								api.go
								
								
								
								
							|  | @ -3,8 +3,10 @@ package main | |||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"kumoly.io/tools/configui/configui" | ||||
| ) | ||||
|  | @ -70,6 +72,7 @@ func Apply(w http.ResponseWriter, r *http.Request) { | |||
| 		return | ||||
| 	} | ||||
| 	result, err := file.Do() | ||||
| 	log.Println(err) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | @ -77,10 +80,36 @@ func Apply(w http.ResponseWriter, r *http.Request) { | |||
| } | ||||
| 
 | ||||
| func Download(w http.ResponseWriter, r *http.Request) { | ||||
| 	if name := r.URL.Query().Get("name"); name != "" { | ||||
| 		if name == "ConfigUI" { | ||||
| 			data, err := GetConfig() | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 			w.Header().Set("Content-Disposition", `attachment; filename="ConfigUI.json"`) | ||||
| 			w.Write(data) | ||||
| 			return | ||||
| 		} | ||||
| 		file, ok := files[name] | ||||
| 		if !ok { | ||||
| 			MakeResponse(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 _, v := range files { | ||||
| 		fs = append(fs, v.Path) | ||||
| 	} | ||||
| 	if flagConfigPath != "" { | ||||
| 		fs = append(fs, flagConfigPath) | ||||
| 	} | ||||
| 	w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`) | ||||
| 	bundle(w, fs, false) | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import ( | |||
| 	"os/exec" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| var UNIX_SHELL = "sh" | ||||
|  | @ -54,15 +55,30 @@ func (f *File) Do() (string, error) { | |||
| 	if f.Action == "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	cmd := exec.Command(UNIX_SHELL, "-c", f.Action) | ||||
| 	timeout := time.After(2 * time.Second) | ||||
| 	cmd := &exec.Cmd{} | ||||
| 	if runtime.GOOS == "windows" { | ||||
| 		cmd = exec.Command(WIN_SHELL, "/c", f.Action) | ||||
| 	} else { | ||||
| 		exec.Command(UNIX_SHELL, "-c", f.Action) | ||||
| 	} | ||||
| 	out, _ := cmd.CombinedOutput() | ||||
| 	var out []byte | ||||
| 	done := make(chan struct{}, 1) | ||||
| 	go func() { | ||||
| 		out, _ = cmd.CombinedOutput() | ||||
| 		// real cmd err is only pass down
 | ||||
| 		// if err != nil {
 | ||||
| 	// 	return "", err
 | ||||
| 		// 	return string(out), err
 | ||||
| 		// }
 | ||||
| 		done <- struct{}{} | ||||
| 	}() | ||||
| 	select { | ||||
| 	case <-timeout: | ||||
| 		cmd.Process.Kill() | ||||
| 		return "", errors.New("command timeout") | ||||
| 	case <-done: | ||||
| 		return string(out), nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ReadConfig(confstr string) ([]File, error) { | ||||
|  |  | |||
|  | @ -58,7 +58,8 @@ async function FileApply(){ | |||
|       method: 'POST', | ||||
|     }) | ||||
|     if(!res.ok){ | ||||
|       handleError(res) | ||||
|       const result = await handleError(res) | ||||
|       result_editor.session.setValue(result) | ||||
|       return | ||||
|     } | ||||
|     const result = await res.text() | ||||
|  | @ -69,6 +70,7 @@ async function FileApply(){ | |||
| async function handleError(res){ | ||||
|   const msg = await res.text() | ||||
|   ShowError(msg) | ||||
|   return msg | ||||
| } | ||||
| 
 | ||||
| // starting point
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								release.sh
								
								
								
								
							
							
						
						
									
										19
									
								
								release.sh
								
								
								
								
							|  | @ -0,0 +1,19 @@ | |||
| VERSION=$(git describe --tags --abbrev=0) | ||||
| BUILD=$(git rev-parse --short HEAD) | ||||
| PROJ=$(basename "$(PWD)") | ||||
| HUB=hub.kumoly.io | ||||
| HUB_PROJECT=tools | ||||
| LDFLAGS='-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w"' | ||||
| 
 | ||||
| PLATFORMS='darwin linux' | ||||
| ARCHITECTURES=amd64 | ||||
| APPS=configui | ||||
| 
 | ||||
| echo $LDFLAGS | ||||
| 
 | ||||
| build(){ | ||||
|     foreach GOOS $1 | ||||
|         echo $GOOS | ||||
| } | ||||
| 
 | ||||
| build $PLATFORMS | ||||
|  | @ -3,8 +3,15 @@ | |||
| // } | ||||
| 
 | ||||
| #editor,#result_editor { | ||||
|   height: 75vh; | ||||
|   font-size: 1rem; | ||||
|   position: relative; | ||||
|   width: inherit !important; | ||||
| } | ||||
| 
 | ||||
| #editor { | ||||
|   height: 70vh; | ||||
| } | ||||
| 
 | ||||
| #result_editor { | ||||
|   min-height: 50vh; | ||||
| } | ||||
|  |  | |||
|  | @ -12,6 +12,10 @@ $modal-content-width: 90vw; | |||
|   position: fixed; /* Sit on top of the page content */ | ||||
|   top: 1; | ||||
|   left: 50%; | ||||
|   z-index: 2; | ||||
|   z-index: 50; | ||||
|   transform: translateX(-50%) | ||||
| } | ||||
| 
 | ||||
| #title { | ||||
|   padding: 0.5rem; | ||||
| } | ||||
|  | @ -1,9 +1,9 @@ | |||
| {{define "components/editor"}} | ||||
| {{template "components/toolbar" .}} | ||||
| 
 | ||||
| <container class="container is-max-desktop"> | ||||
| <!-- <container class="container is-max-desktop"> --> | ||||
|   <div id="editor">{{.File.Content}}</div> | ||||
| </container> | ||||
| <!-- </container> --> | ||||
| <script> | ||||
| function setEditor(){ | ||||
|   var beautify = ace.require("ace/ext/beautify"); | ||||
|  |  | |||
|  | @ -29,6 +29,9 @@ function setResult(){ | |||
|   result_editor.setTheme("ace/theme/monokai"); | ||||
|   result_editor.session.setMode("ace/mode/sh"); | ||||
|   result_editor.setReadOnly(true); | ||||
|   result_editor.setOptions({ | ||||
|     maxLines: 1000 | ||||
| }); | ||||
| } | ||||
| 
 | ||||
| function ResultViewTog(){ | ||||
|  |  | |||
|  | @ -3,7 +3,10 @@ | |||
|   <div class="level-left"> | ||||
|     <div class="field has-addons"> | ||||
|       <p class="control"> | ||||
|         <div class="select"> | ||||
|         <button class="button is-small" onclick="editor.gotoLine(editor.session.getLength())">Bottom</button> | ||||
|       </p> | ||||
|       <p class="control"> | ||||
|         <div class="select is-small"> | ||||
|           <select onchange="SetTabstop(this)"> | ||||
|             <option value="0">Tabstop</option> | ||||
|             <option value="4">Tabstop 4</option> | ||||
|  | @ -13,13 +16,13 @@ | |||
|         </div> | ||||
|       </p> | ||||
|       <p class="control"> | ||||
|         <button class="button" id="toolFollow" onclick="toolFollow()">Follow</button> | ||||
|         <button class="button is-small" id="toolFollow" onclick="toolFollow()">Follow</button> | ||||
|       </p> | ||||
|       <p class="control"> | ||||
|         <button class="button" onclick="toolFormat()">Format</button> | ||||
|         <button class="button is-small" onclick="toolFormat()">Format</button> | ||||
|       </p> | ||||
|       <p class="control"> | ||||
|         <button class="button is-light is-danger" id="toolWrap" onclick="toggleWrap()">NoWrap</button> | ||||
|         <button class="button is-small is-light is-danger" id="toolWrap" onclick="toggleWrap()">NoWrap</button> | ||||
|       </p> | ||||
|     </div> | ||||
|   </div> | ||||
|  | @ -27,29 +30,30 @@ | |||
|   <div class="level-right"> | ||||
|     <div class="field has-addons"> | ||||
|       <p class="control"> | ||||
|         <button class="button" id="toolRefresh" onclick="toolRefresh()">Refresh</button> | ||||
|         <button class="button is-small" id="toolRefresh" onclick="toolRefresh()">Refresh</button> | ||||
|       </p> | ||||
|       {{if not .File.RO}} | ||||
|         <p class="control"> | ||||
|           {{if eq .File.Alias "ConfigUI"}} | ||||
|             <button class="button" id="toolSave" onclick="toolSave()">Apply</button> | ||||
|             <button class="button is-small" id="toolSave" onclick="toolSave()">Apply</button> | ||||
|           {{else}} | ||||
|             <button class="button" id="toolSave" onclick="toolSave()">Save</button> | ||||
|             <button class="button is-small" id="toolSave" onclick="toolSave()">Save</button> | ||||
|           {{end}} | ||||
|         </p> | ||||
|       {{end}} | ||||
|       {{if .File.Action}} | ||||
|       <p class="control"> | ||||
|         <button class="button has-tooltip-arrow" | ||||
|         <button class="button is-small has-tooltip-arrow" | ||||
|           data-tooltip="{{.File.Action}}" | ||||
|           id="toolApply" onclick="toolApply()" | ||||
|         >Apply</button> | ||||
|       </p> | ||||
| 
 | ||||
|       {{/* <p class="control"> | ||||
|         <a class="button" href="/api/dl?name">Download</a> | ||||
|       </p> */}} | ||||
|       {{end}} | ||||
| 
 | ||||
|       {{/* TEST */}}  | ||||
|       <p class="control"> | ||||
|         <a class="button is-small" href="/api/export?name={{.File.Alias}}">Download</a> | ||||
|       </p> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ var Active = "{{.File.Alias}}"; | |||
| </script> | ||||
| 
 | ||||
| <section class="hero is-small is-primary"> | ||||
|   <div class="hero-body"> | ||||
|   <div class="hero-body" id="title"> | ||||
|     <p class="title"> | ||||
|       ConfigUI | ||||
|     </p> | ||||
|  | @ -19,7 +19,7 @@ var Active = "{{.File.Alias}}"; | |||
|       {{template "components/menu" .}} | ||||
|     </div> | ||||
|     <div class="box has-text-centered"> | ||||
|       <a href="/api/export" class="button">Export Files</a> | ||||
|       <a href="/api/export" class="button is-small">Export Files</a> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="column"> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue