12 Commits

10 changed files with 93 additions and 12 deletions

1
.hgtags Normal file
View File

@@ -0,0 +1 @@
b851907a3b9abbf00ed0dab049478ab4770e23e0 release-1.0.0

View File

@@ -1,6 +1,6 @@
.PHONY: all clean .PHONY: all clean
VERSION := 1.0.0 VERSION := 1.0.1
OBJS := webcmd-$(VERSION)-linux64.tar.xz \ OBJS := webcmd-$(VERSION)-linux64.tar.xz \
webcmd-$(VERSION)-linux32.tar.xz \ webcmd-$(VERSION)-linux32.tar.xz \

1
TODO.txt Normal file
View File

@@ -0,0 +1 @@
- support more complex argument parsing

View File

@@ -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

View File

@@ -16,5 +16,13 @@ See the included `webcmd.conf-sample` file for an example configuration implemen
=CHANGELOG= =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
2017-03-25 1.0.0 2017-03-25 1.0.0
- Initial public release - Initial public release

View File

@@ -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"}
] ]
} }

View File

@@ -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 {

View File

@@ -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))

View File

@@ -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() {

View File

@@ -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>