parent
e90f1e8e87
commit
442aad83ec
|
@ -2,9 +2,13 @@
|
||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
|
|
||||||
|
* download files #26
|
||||||
|
* add goto last line toolbar #21
|
||||||
|
|
||||||
## Fix
|
## Fix
|
||||||
|
|
||||||
* all cmds should be pass straight back to user
|
* all cmds should be pass straight back to user
|
||||||
|
* timeout on none ending cmds #34
|
||||||
|
|
||||||
# 0.1.1
|
# 0.1.1
|
||||||
|
|
||||||
|
|
29
api.go
29
api.go
|
@ -3,8 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"kumoly.io/tools/configui/configui"
|
"kumoly.io/tools/configui/configui"
|
||||||
)
|
)
|
||||||
|
@ -70,6 +72,7 @@ func Apply(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result, err := file.Do()
|
result, err := file.Do()
|
||||||
|
log.Println(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -77,10 +80,36 @@ func Apply(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Download(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{}
|
fs := []string{}
|
||||||
for _, v := range files {
|
for _, v := range files {
|
||||||
fs = append(fs, v.Path)
|
fs = append(fs, v.Path)
|
||||||
}
|
}
|
||||||
|
if flagConfigPath != "" {
|
||||||
|
fs = append(fs, flagConfigPath)
|
||||||
|
}
|
||||||
w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
|
w.Header().Set("Content-Disposition", `attachment; filename="export.tar.gz"`)
|
||||||
bundle(w, fs, false)
|
bundle(w, fs, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var UNIX_SHELL = "sh"
|
var UNIX_SHELL = "sh"
|
||||||
|
@ -54,16 +55,31 @@ func (f *File) Do() (string, error) {
|
||||||
if f.Action == "" {
|
if f.Action == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
cmd := exec.Command(UNIX_SHELL, "-c", f.Action)
|
timeout := time.After(2 * time.Second)
|
||||||
|
cmd := &exec.Cmd{}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
cmd = exec.Command(WIN_SHELL, "/c", f.Action)
|
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 {
|
// 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
|
return string(out), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ReadConfig(confstr string) ([]File, error) {
|
func ReadConfig(confstr string) ([]File, error) {
|
||||||
conf := []File{}
|
conf := []File{}
|
||||||
|
|
|
@ -58,7 +58,8 @@ async function FileApply(){
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
if(!res.ok){
|
if(!res.ok){
|
||||||
handleError(res)
|
const result = await handleError(res)
|
||||||
|
result_editor.session.setValue(result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const result = await res.text()
|
const result = await res.text()
|
||||||
|
@ -69,6 +70,7 @@ async function FileApply(){
|
||||||
async function handleError(res){
|
async function handleError(res){
|
||||||
const msg = await res.text()
|
const msg = await res.text()
|
||||||
ShowError(msg)
|
ShowError(msg)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// starting point
|
// 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 {
|
#editor,#result_editor {
|
||||||
height: 75vh;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: inherit !important;
|
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 */
|
position: fixed; /* Sit on top of the page content */
|
||||||
top: 1;
|
top: 1;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
z-index: 2;
|
z-index: 50;
|
||||||
transform: translateX(-50%)
|
transform: translateX(-50%)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
{{define "components/editor"}}
|
{{define "components/editor"}}
|
||||||
{{template "components/toolbar" .}}
|
{{template "components/toolbar" .}}
|
||||||
|
|
||||||
<container class="container is-max-desktop">
|
<!-- <container class="container is-max-desktop"> -->
|
||||||
<div id="editor">{{.File.Content}}</div>
|
<div id="editor">{{.File.Content}}</div>
|
||||||
</container>
|
<!-- </container> -->
|
||||||
<script>
|
<script>
|
||||||
function setEditor(){
|
function setEditor(){
|
||||||
var beautify = ace.require("ace/ext/beautify");
|
var beautify = ace.require("ace/ext/beautify");
|
||||||
|
|
|
@ -29,6 +29,9 @@ function setResult(){
|
||||||
result_editor.setTheme("ace/theme/monokai");
|
result_editor.setTheme("ace/theme/monokai");
|
||||||
result_editor.session.setMode("ace/mode/sh");
|
result_editor.session.setMode("ace/mode/sh");
|
||||||
result_editor.setReadOnly(true);
|
result_editor.setReadOnly(true);
|
||||||
|
result_editor.setOptions({
|
||||||
|
maxLines: 1000
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResultViewTog(){
|
function ResultViewTog(){
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<p class="control">
|
<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)">
|
<select onchange="SetTabstop(this)">
|
||||||
<option value="0">Tabstop</option>
|
<option value="0">Tabstop</option>
|
||||||
<option value="4">Tabstop 4</option>
|
<option value="4">Tabstop 4</option>
|
||||||
|
@ -13,13 +16,13 @@
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
<p class="control">
|
<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>
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button" onclick="toolFormat()">Format</button>
|
<button class="button is-small" onclick="toolFormat()">Format</button>
|
||||||
</p>
|
</p>
|
||||||
<p class="control">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,29 +30,30 @@
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button" id="toolRefresh" onclick="toolRefresh()">Refresh</button>
|
<button class="button is-small" id="toolRefresh" onclick="toolRefresh()">Refresh</button>
|
||||||
</p>
|
</p>
|
||||||
{{if not .File.RO}}
|
{{if not .File.RO}}
|
||||||
<p class="control">
|
<p class="control">
|
||||||
{{if eq .File.Alias "ConfigUI"}}
|
{{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}}
|
{{else}}
|
||||||
<button class="button" id="toolSave" onclick="toolSave()">Save</button>
|
<button class="button is-small" id="toolSave" onclick="toolSave()">Save</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .File.Action}}
|
{{if .File.Action}}
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button has-tooltip-arrow"
|
<button class="button is-small has-tooltip-arrow"
|
||||||
data-tooltip="{{.File.Action}}"
|
data-tooltip="{{.File.Action}}"
|
||||||
id="toolApply" onclick="toolApply()"
|
id="toolApply" onclick="toolApply()"
|
||||||
>Apply</button>
|
>Apply</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{/* <p class="control">
|
|
||||||
<a class="button" href="/api/dl?name">Download</a>
|
|
||||||
</p> */}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{/* TEST */}}
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-small" href="/api/export?name={{.File.Alias}}">Download</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,7 @@ var Active = "{{.File.Alias}}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-small is-primary">
|
<section class="hero is-small is-primary">
|
||||||
<div class="hero-body">
|
<div class="hero-body" id="title">
|
||||||
<p class="title">
|
<p class="title">
|
||||||
ConfigUI
|
ConfigUI
|
||||||
</p>
|
</p>
|
||||||
|
@ -19,7 +19,7 @@ var Active = "{{.File.Alias}}";
|
||||||
{{template "components/menu" .}}
|
{{template "components/menu" .}}
|
||||||
</div>
|
</div>
|
||||||
<div class="box has-text-centered">
|
<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>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|
Loading…
Reference in New Issue