Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
mappu | 24cef16b4e | |
mappu | a147d03839 | |
mappu | 70e17a48ba | |
mappu | d730fe65f6 | |
mappu | 63b573fd57 | |
mappu | 304baa8020 | |
mappu | 0fe8dc8f1b | |
mappu | 570cffe84e | |
mappu | 5802def7bc | |
mappu | 5e6010bede | |
mappu | 21829f0c1f | |
mappu | 7e4a90920d | |
mappu | 7951e09238 | |
mappu | eab781794e | |
mappu | f7c70603f2 | |
mappu | b7ebd321d0 | |
mappu | ef252d5cab | |
mappu | 0768e10403 | |
mappu | 820805b063 | |
mappu | 194c0b0405 | |
mappu | e7689eabad | |
mappu | 0e22171779 | |
mappu | d47dea6933 |
|
@ -0,0 +1,5 @@
|
||||||
|
cmd/webcmd/webcmd
|
||||||
|
cmd/webcmd/webcmd.exe
|
||||||
|
cmd/webcmd/webcmd.conf
|
||||||
|
|
||||||
|
_dist/*
|
|
@ -1,6 +0,0 @@
|
||||||
mode:regex
|
|
||||||
|
|
||||||
^webcmd\.
|
|
||||||
^cmd/webcmd/webcmd$
|
|
||||||
^cmd/webcmd/webcmd.conf$
|
|
||||||
^_dist/.+\.(?:zip|tar\.xz)$
|
|
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
VERSION := 1.0.0
|
VERSION := 1.0.2
|
||||||
|
|
||||||
OBJS := webcmd-$(VERSION)-linux64.tar.xz \
|
OBJS := webcmd-$(VERSION)-linux64.tar.xz \
|
||||||
webcmd-$(VERSION)-linux32.tar.xz \
|
webcmd-$(VERSION)-linux32.tar.xz \
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# webcmd
|
||||||
|
|
||||||
|
A web-based interface for arbitrary command-line tools.
|
||||||
|
|
||||||
|
Configure the possible options for your command (e.g. optional flags, custom arguments) and then remotely invoke the tool via a web interface. The web interface displays stdout/stderr of recent and in-progress tasks, and can cancel a long-running command at any time.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage of ./webcmd:
|
||||||
|
-config string
|
||||||
|
Path to configuration file (default "webcmd.conf")
|
||||||
|
-version
|
||||||
|
Display version number and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See the included `webcmd.conf-sample` file for an example configuration implementing a Looking Glass server over the top of `/bin/ping`.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
2017-03-27 1.0.1
|
||||||
|
- Enhancement: Simplify configuration file format, allow parsing const strings directly
|
||||||
|
- Enhancement: Improve time format display
|
||||||
|
- Fix an issue with pasting into form fields on Firefox Mobile
|
||||||
|
- Fix an issue with not consistently interpreting `MaxHistoryLines:0` as infinite history
|
||||||
|
- Fix a cosmetic issue with mobile viewports
|
||||||
|
- Fix a cosmetic issue with task ordering
|
||||||
|
- [⬇️ webcmd-win64-1.0.1.zip](https://git.ivysaur.me/attachments/627d5246-0f51-4546-99b1-61d9b9b0cc0b) *(1.46 MiB)*
|
||||||
|
- [⬇️ webcmd-win32-1.0.1.zip](https://git.ivysaur.me/attachments/92de57b3-6f5c-4ba4-ac43-9f85c89041dd) *(1.38 MiB)*
|
||||||
|
- [⬇️ webcmd-src-1.0.1.zip](https://git.ivysaur.me/attachments/120a0a7c-52ab-4bb9-94fe-40d269b0669d) *(11.51 KiB)*
|
||||||
|
- [⬇️ webcmd-linux64-1.0.1.tar.xz](https://git.ivysaur.me/attachments/41d001ed-11b7-499c-ba18-1a22fe0ed140) *(1.14 MiB)*
|
||||||
|
- [⬇️ webcmd-linux32-1.0.1.tar.xz](https://git.ivysaur.me/attachments/0cb47392-36d1-4f48-9ee6-271f06dba952) *(1.06 MiB)*
|
||||||
|
|
||||||
|
2017-03-25 1.0.0
|
||||||
|
- Initial public release
|
||||||
|
- [⬇️ webcmd-win64-1.0.0.zip](https://git.ivysaur.me/attachments/6442cde4-0dfe-432d-9cb3-42272c8e6551) *(1.46 MiB)*
|
||||||
|
- [⬇️ webcmd-win32-1.0.0.zip](https://git.ivysaur.me/attachments/261489d4-8393-450c-9f72-1bca009e0942) *(1.37 MiB)*
|
||||||
|
- [⬇️ webcmd-src-1.0.0.zip](https://git.ivysaur.me/attachments/cbac2a73-2af0-4c60-ab0a-9970250c9a6e) *(10.50 KiB)*
|
||||||
|
- [⬇️ webcmd-linux64-1.0.0.tar.xz](https://git.ivysaur.me/attachments/a713a0f5-e3de-442c-a090-26ae49b03ac9) *(1.14 MiB)*
|
||||||
|
- [⬇️ webcmd-linux32-1.0.0.tar.xz](https://git.ivysaur.me/attachments/86d5e65e-c3da-4741-a0c0-f4ec2e123689) *(1.06 MiB)*
|
2
Task.go
2
Task.go
|
@ -87,7 +87,7 @@ func (this *App) LaunchTask(workDir string, params []string) (taskRef string, er
|
||||||
|
|
||||||
task := this.tasks[ref]
|
task := this.tasks[ref]
|
||||||
task.output = append(task.output, OutputLine{isError: isError, text: text})
|
task.output = append(task.output, OutputLine{isError: isError, text: text})
|
||||||
if len(task.output) > 2*this.cfg.MaxHistoryLines {
|
if this.cfg.MaxHistoryLines > 0 && len(task.output) > 2*this.cfg.MaxHistoryLines {
|
||||||
task.output = task.output[this.cfg.MaxHistoryLines:]
|
task.output = task.output[this.cfg.MaxHistoryLines:]
|
||||||
}
|
}
|
||||||
this.tasks[ref] = task
|
this.tasks[ref] = task
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
A web-based interface for arbitrary command-line tools.
|
|
||||||
|
|
||||||
Configure the possible options for your command (e.g. optional flags, custom arguments) and remotely invoke the tool via a web interface. The web interface displays stdout/stderr of recent tasks.
|
|
||||||
|
|
||||||
=USAGE=
|
|
||||||
|
|
||||||
`Usage of ./webcmd:
|
|
||||||
-config string
|
|
||||||
Path to configuration file (default "webcmd.conf")
|
|
||||||
-version
|
|
||||||
Display version number and exit`
|
|
||||||
|
|
||||||
=CONFIGURATION=
|
|
||||||
|
|
||||||
See the included `webcmd.conf-sample` file for an example configuration implementing a Looking Glass server over the top of `/bin/ping`.
|
|
||||||
|
|
||||||
=CHANGELOG=
|
|
||||||
|
|
||||||
2017-03-25 1.0.0
|
|
||||||
- Initial public release
|
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
|
@ -10,10 +10,10 @@
|
||||||
{
|
{
|
||||||
"Title": "Ping",
|
"Title": "Ping",
|
||||||
"Execution": [
|
"Execution": [
|
||||||
{"ParamType": 0, "Value": "/bin/ping"},
|
"/bin/ping",
|
||||||
{"ParamType": 0, "Value": "-c"},
|
"-c",
|
||||||
{"ParamType": 0, "Value": "4"},
|
"4",
|
||||||
{"ParamType": 0, "Value": "--"},
|
"--",
|
||||||
{"ParamType": 1, "Value": "example.com", "Description": "Target host"}
|
{"ParamType": 1, "Value": "example.com", "Description": "Target host"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
41
config.go
41
config.go
|
@ -1,5 +1,10 @@
|
||||||
package webcmd
|
package webcmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
type ParamType int
|
type ParamType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -16,15 +21,47 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type InputParam struct {
|
type InputParam struct {
|
||||||
Description string `json:",omitempty"` // only use for editable parameters
|
Description string `json:",omitempty"` // only used for editable parameters
|
||||||
ParamType ParamType
|
ParamType ParamType
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ip *InputParam) UnmarshalJSON(b []byte) error {
|
||||||
|
switch b[0] {
|
||||||
|
case '"':
|
||||||
|
ip.Description = ""
|
||||||
|
ip.ParamType = PARAMTYPE_CONST
|
||||||
|
tmp := ""
|
||||||
|
err := json.Unmarshal(b, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ip.Value = tmp
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
read := struct {
|
||||||
|
Description string `json:",omitempty"`
|
||||||
|
ParamType ParamType
|
||||||
|
Value string
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(b, &read)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*ip = read
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("Malformed InputParam")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type CommandConfig struct {
|
type CommandConfig struct {
|
||||||
Title string
|
Title string
|
||||||
WorkingDir string // default empty-string: getcwd()
|
WorkingDir string // default empty-string: getcwd()
|
||||||
Execution []InputParam // TODO allow plain strings as a shorthand for PARAMTYPE_CONST
|
Execution []InputParam // Can be unmarshalled using plain strings
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
|
@ -23,8 +23,8 @@ func (this *App) Serve_Homepage(w http.ResponseWriter) {
|
||||||
case PARAMTYPE_CONST:
|
case PARAMTYPE_CONST:
|
||||||
// not configurable parameter
|
// not configurable parameter
|
||||||
case PARAMTYPE_STRING:
|
case PARAMTYPE_STRING:
|
||||||
fmt.Fprintf(w, `<input type="text" name="param[%d]" placeholder="%s" title="%s" value="%s"><br>`,
|
fmt.Fprintf(w, `<input type="text" name="param[%d]" placeholder="%s" value="%s"><br>`,
|
||||||
i, hesc(param.Description), hesc(param.Description), hesc(param.Value))
|
i, hesc(param.Description), hesc(param.Value))
|
||||||
case PARAMTYPE_OPTIONAL:
|
case PARAMTYPE_OPTIONAL:
|
||||||
fmt.Fprintf(w, `<input type="hidden" name="param[%d]" value="off"><label><input type="checkbox" name="param[%d]" value="on">%s</label><br>`,
|
fmt.Fprintf(w, `<input type="hidden" name="param[%d]" value="off"><label><input type="checkbox" name="param[%d]" value="on">%s</label><br>`,
|
||||||
i, i, hesc(param.Description))
|
i, i, hesc(param.Description))
|
||||||
|
|
37
tpl_Tasks.go
37
tpl_Tasks.go
|
@ -3,9 +3,29 @@ package webcmd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TaskListItem struct {
|
||||||
|
ref string
|
||||||
|
start int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskList []TaskListItem
|
||||||
|
|
||||||
|
func (tl TaskList) Len() int {
|
||||||
|
return len(tl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl TaskList) Swap(i, j int) {
|
||||||
|
tl[i], tl[j] = tl[j], tl[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl TaskList) Less(i, j int) bool {
|
||||||
|
return tl[i].start < tl[j].start
|
||||||
|
}
|
||||||
|
|
||||||
func (this *App) Serve_Tasks(w http.ResponseWriter) {
|
func (this *App) Serve_Tasks(w http.ResponseWriter) {
|
||||||
w.Header().Set("Content-Type", "text/html;charset=UTF-8")
|
w.Header().Set("Content-Type", "text/html;charset=UTF-8")
|
||||||
|
|
||||||
|
@ -26,15 +46,28 @@ func (this *App) Serve_Tasks(w http.ResponseWriter) {
|
||||||
|
|
||||||
this.tasksMtx.RLock()
|
this.tasksMtx.RLock()
|
||||||
defer this.tasksMtx.RUnlock()
|
defer this.tasksMtx.RUnlock()
|
||||||
|
|
||||||
|
tl := make(TaskList, 0, len(this.tasks))
|
||||||
for ref, t := range this.tasks {
|
for ref, t := range this.tasks {
|
||||||
|
tl = append(tl, TaskListItem{ref, t.started})
|
||||||
|
}
|
||||||
|
sort.Sort(tl)
|
||||||
|
|
||||||
|
for _, tlRef := range tl {
|
||||||
|
ref := tlRef.ref
|
||||||
|
t := this.tasks[ref]
|
||||||
|
|
||||||
|
startTime := time.Unix(t.started, 0)
|
||||||
|
|
||||||
fmt.Fprintf(w,
|
fmt.Fprintf(w,
|
||||||
`<tr>
|
`<tr>
|
||||||
<td><a href="/task/%s">%s</td>
|
<td><a href="/task/%s">%s</td>
|
||||||
<td>%s</td>
|
<td><span title="%s">%s</span></td>
|
||||||
<td>
|
<td>
|
||||||
`,
|
`,
|
||||||
hesc(ref), hesc(ref),
|
hesc(ref), hesc(ref),
|
||||||
hesc(time.Unix(t.started, 0).Format(time.RFC822Z)),
|
hesc(startTime.Format(time.RFC3339)),
|
||||||
|
hesc(startTime.Format(time.RFC822)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.Finished() {
|
if t.Finished() {
|
||||||
|
|
|
@ -13,6 +13,7 @@ func (this *App) ServePartial_Header(w http.ResponseWriter, slug string) {
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>`+hesc(this.cfg.AppTitle)+`</title>
|
<title>`+hesc(this.cfg.AppTitle)+`</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
|
<meta viewport="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>`+hesc(this.cfg.AppTitle)+`</h2>
|
<h2>`+hesc(this.cfg.AppTitle)+`</h2>
|
||||||
|
|
Loading…
Reference in New Issue