Skip to content
This repository was archived by the owner on Sep 2, 2024. It is now read-only.

Commit d60457b

Browse files
committed
web UI to manage functions close #11
1 parent 9bd62e5 commit d60457b

File tree

6 files changed

+287
-3
lines changed

6 files changed

+287
-3
lines changed

function/management.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ type ExecData struct {
2020
Code string `bson:"code" json:"code"`
2121
Version int `bson:"v" json:"version"`
2222
LastUpdated time.Time `bson:"lu" json:"lastUpdated"`
23+
LastRun time.Time `bson:"lr" json:"lastRun"`
2324
History []ExecHistory `bson:"h" json:"history"`
2425
}
2526

2627
// ExecHistory represents a function run ending result
2728
type ExecHistory struct {
29+
ID string `bson:"id" json:"id"`
2830
Version int `bson:"v" json:"version"`
2931
Started time.Time `bson:"s" json:"started"`
3032
Completed time.Time `bson:"c" json:"completed"`
@@ -38,11 +40,16 @@ func Add(db *mongo.Database, data ExecData) (string, error) {
3840
data.LastUpdated = time.Now()
3941

4042
ctx := context.Background()
41-
if _, err := db.Collection("sb_functions").InsertOne(ctx, data); err != nil {
43+
res, err := db.Collection("sb_functions").InsertOne(ctx, data)
44+
if err != nil {
4245
return "", err
4346
}
4447

45-
return data.ID.Hex(), nil
48+
oid, ok := res.InsertedID.(primitive.ObjectID)
49+
if !ok {
50+
return "", nil
51+
}
52+
return oid.Hex(), nil
4653
}
4754

4855
func Update(db *mongo.Database, id, code, trigger string) error {
@@ -108,7 +115,7 @@ func GetByID(db *mongo.Database, id string) (ExecData, error) {
108115

109116
func List(db *mongo.Database) ([]ExecData, error) {
110117
opt := &options.FindOptions{}
111-
opt.SetProjection(bson.M{"h": -1})
118+
opt.SetProjection(bson.M{"h": 0})
112119

113120
ctx := context.Background()
114121
cur, err := db.Collection("sb_functions").Find(ctx, bson.M{}, opt)

server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ func Start(dbHost, port string) {
152152
http.Handle("/ui/db/save", middleware.Chain(http.HandlerFunc(webUI.dbSave), stdRoot...))
153153
http.Handle("/ui/db/del/", middleware.Chain(http.HandlerFunc(webUI.dbDel), stdRoot...))
154154
http.Handle("/ui/db/", middleware.Chain(http.HandlerFunc(webUI.dbDoc), stdRoot...))
155+
http.Handle("/ui/fn/new", middleware.Chain(http.HandlerFunc(webUI.fnNew), stdRoot...))
156+
http.Handle("/ui/fn/save", middleware.Chain(http.HandlerFunc(webUI.fnSave), stdRoot...))
157+
http.Handle("/ui/fn/del/", middleware.Chain(http.HandlerFunc(webUI.fnDel), stdRoot...))
158+
http.Handle("/ui/fn/", middleware.Chain(http.HandlerFunc(webUI.fnEdit), stdRoot...))
159+
http.Handle("/ui/fn", middleware.Chain(http.HandlerFunc(webUI.fnList), stdRoot...))
155160
http.Handle("/ui/forms", middleware.Chain(http.HandlerFunc(webUI.forms), stdRoot...))
156161
http.Handle("/ui/forms/del/", middleware.Chain(http.HandlerFunc(webUI.formDel), stdRoot...))
157162
http.HandleFunc("/", webUI.login)

templates/fn_edit.html

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{{ template "head" .}}
2+
3+
<body>
4+
{{template "navbar" .}}
5+
6+
<div class="container p-6" x-data="{tab: 'edit', log: ''}">
7+
<h2 class="title is-2">
8+
Function: {{if .Data.FunctionName}}{{.Data.FunctionName}}{{else}}"new function"{{end}}
9+
<a href="/ui/fn/del/{{.Data.FunctionName}}" class="pt-5 delete is-large"
10+
onclick="return confirm('Are you sure you want to delete?\n\nThis is irreversible.')">
11+
</a>
12+
</h2>
13+
14+
<div class="tabs">
15+
<ul>
16+
<li :class="{ 'is-active': tab == 'edit'}">
17+
<a @click="tab = 'edit'">Edit</a>
18+
</li>
19+
<li :class="{ 'is-active': tab == 'history'}">
20+
<a @click="tab = 'history'">Run history</a>
21+
</li>
22+
</ul>
23+
</div>
24+
25+
<div x-show="tab == 'edit'">
26+
<form action="/ui/fn/save" method="POST">
27+
<input
28+
type="hidden"
29+
name="id"
30+
value="{{if .Data.FunctionName}}{{.Data.ID.Hex}}{{else}}new{{end}}">
31+
32+
<div class="field">
33+
<label class="label">Function name</label>
34+
<div class="control">
35+
<input type="text" class="input" name="name" value="{{.Data.FunctionName}}" placeholder="Name your function"
36+
required
37+
{{if .Data.FunctionName}}disabled{{end}}>
38+
</div>
39+
</div>
40+
41+
<div class="field">
42+
<label class="label">Trigger (web or topic)</label>
43+
<div class="control">
44+
<input type="text" class="input" name="trigger" value="{{.Data.TriggerTopic}}"
45+
placeholder='Either "web" or "topic"' required>
46+
</div>
47+
</div>
48+
49+
<div class="field">
50+
<label class="label">Code</label>
51+
<div class="control">
52+
<textarea class="textarea" rows="15" name="code" placeholder="Function code"
53+
required>{{.Data.Code}}</textarea>
54+
</div>
55+
</div>
56+
57+
<div class="field">
58+
<div class="control">
59+
<button type="submit" class="button is-primary">Save changes</button>
60+
</div>
61+
</div>
62+
</form>
63+
</div>
64+
65+
<div x-show="tab == 'history'">
66+
<h3 class="subtitle is-3">History</h3>
67+
<table class="table is-bordered is-striped">
68+
<thead>
69+
<tr>
70+
<th>Version</th>
71+
<th>Started</th>
72+
<th>Completed</th>
73+
<th>Status</th>
74+
<th>Output</th>
75+
</tr>
76+
</thead>
77+
<tbody>
78+
{{range .Data.History}}
79+
<tr>
80+
<td>{{.Version}}</td>
81+
<td>{{.Started}}</td>
82+
<td>{{.Completed}}</td>
83+
<td>{{if .Success}}Success{{else}}Failed{{end}}</td>
84+
<td>
85+
<a x-show="log == ''" href="#" @click="log = '{{.ID}}'">View output</a>
86+
<a x-show="log == '{{.ID}}'" @click="log = ''">Hide output</a>
87+
</td>
88+
</tr>
89+
<tr x-show="log == '{{.ID}}'">
90+
<td colspan="5">
91+
<pre>
92+
{{range .Output}}
93+
{{.}}
94+
{{end}}
95+
</pre>
96+
</td>
97+
</tr>
98+
{{end}}
99+
</tbody>
100+
</table>
101+
</div>
102+
</div>
103+
</body>
104+
105+
{{template "foot"}}

templates/fn_list.html

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{{ template "head" .}}
2+
3+
<body>
4+
{{template "navbar" .}}
5+
6+
<div class="container p-6">
7+
<h2 class="title is-2">
8+
Functions
9+
</h2>
10+
<p class="subtitle is-5">
11+
Functions are useful to react to platform events and schedule tasks.
12+
</p>
13+
<p class="py-3">
14+
<a href="/ui/fn/new" class="button is-primary">
15+
Create a new function
16+
</a>
17+
</p>
18+
19+
<table class="table is-bordered is-striped">
20+
<thead>
21+
<tr>
22+
<th>Name</th>
23+
<th>Version</th>
24+
<th>Trigger</th>
25+
<th>Last execution</th>
26+
<th>Last updated</th>
27+
<th></th>
28+
</tr>
29+
</thead>
30+
<tbody>
31+
{{range .Data}}
32+
<tr>
33+
<td>
34+
<a href="/ui/fn/{{.ID.Hex}}">
35+
{{.FunctionName}}
36+
</a>
37+
</td>
38+
<td>{{.Version}}</td>
39+
<td>{{.TriggerTopic}}</td>
40+
<td>
41+
{{if .LastRun}}
42+
{{.LastRun}}
43+
{{else}}
44+
never
45+
{{end}}
46+
</td>
47+
<td>{{.LastUpdated}}</td>
48+
<td>
49+
<a
50+
href="/ui/fn/del/{{.FunctionName}}"
51+
class="delete"
52+
onclick="return confirm('Are you sure you want to delete this function?\n\nThis is irreversible.')">
53+
</a>
54+
</td>
55+
</tr>
56+
{{end}}
57+
</tbody>
58+
</table>
59+
</div>
60+
</body>
61+
62+
{{template "foot"}}

templates/partials/navbar.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
database
1919
</a>
2020

21+
<a class="navbar-item" href="/ui/fn">
22+
functions
23+
</a>
24+
2125
<a class="navbar-item" href="/ui/forms">
2226
forms
2327
</a>

ui.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"staticbackend/db"
8+
"staticbackend/function"
89
"staticbackend/internal"
910
"staticbackend/middleware"
1011
"strconv"
@@ -371,3 +372,103 @@ func (x ui) formDel(w http.ResponseWriter, r *http.Request) {
371372

372373
http.Redirect(w, r, "/ui/forms", http.StatusSeeOther)
373374
}
375+
376+
func (x ui) fnList(w http.ResponseWriter, r *http.Request) {
377+
conf, _, err := middleware.Extract(r, false)
378+
if err != nil {
379+
renderErr(w, r, err)
380+
return
381+
}
382+
383+
curDB := client.Database(conf.Name)
384+
385+
results, err := function.List(curDB)
386+
if err != nil {
387+
renderErr(w, r, err)
388+
return
389+
}
390+
391+
render(w, r, "fn_list.html", results, nil)
392+
}
393+
394+
func (x *ui) fnNew(w http.ResponseWriter, r *http.Request) {
395+
fn := function.ExecData{}
396+
render(w, r, "fn_edit.html", fn, nil)
397+
}
398+
399+
func (x *ui) fnEdit(w http.ResponseWriter, r *http.Request) {
400+
conf, _, err := middleware.Extract(r, false)
401+
if err != nil {
402+
renderErr(w, r, err)
403+
return
404+
}
405+
406+
curDB := client.Database(conf.Name)
407+
408+
id := getURLPart(r.URL.Path, 3)
409+
410+
fn, err := function.GetByID(curDB, id)
411+
if err != nil {
412+
renderErr(w, r, err)
413+
return
414+
}
415+
416+
render(w, r, "fn_edit.html", fn, nil)
417+
}
418+
419+
func (x *ui) fnSave(w http.ResponseWriter, r *http.Request) {
420+
conf, _, err := middleware.Extract(r, false)
421+
if err != nil {
422+
renderErr(w, r, err)
423+
return
424+
}
425+
426+
curDB := client.Database(conf.Name)
427+
428+
r.ParseForm()
429+
430+
id := r.Form.Get("id")
431+
name := r.Form.Get("name")
432+
trigger := r.Form.Get("trigger")
433+
code := r.Form.Get("code")
434+
435+
if id == "new" {
436+
fn := function.ExecData{
437+
FunctionName: name,
438+
Code: code,
439+
TriggerTopic: trigger,
440+
}
441+
newID, err := function.Add(curDB, fn)
442+
if err != nil {
443+
renderErr(w, r, err)
444+
return
445+
}
446+
447+
http.Redirect(w, r, "/ui/fn/"+newID, http.StatusSeeOther)
448+
return
449+
}
450+
451+
if err := function.Update(curDB, id, code, trigger); err != nil {
452+
renderErr(w, r, err)
453+
return
454+
}
455+
456+
http.Redirect(w, r, "/ui/fn/"+id, http.StatusSeeOther)
457+
}
458+
459+
func (x *ui) fnDel(w http.ResponseWriter, r *http.Request) {
460+
conf, _, err := middleware.Extract(r, false)
461+
if err != nil {
462+
renderErr(w, r, err)
463+
return
464+
}
465+
466+
curDB := client.Database(conf.Name)
467+
name := getURLPart(r.URL.Path, 4)
468+
if err := function.Delete(curDB, name); err != nil {
469+
renderErr(w, r, err)
470+
return
471+
}
472+
473+
http.Redirect(w, r, "/ui/fn", http.StatusSeeOther)
474+
}

0 commit comments

Comments
 (0)