From d68dba13bd2672a028ced57c0f9c0d1379369635 Mon Sep 17 00:00:00 2001 From: jamesread Date: Sun, 17 Oct 2021 20:58:02 +0100 Subject: [PATCH 1/3] Started work on argument form --- OliveTin.proto | 15 +++++++++++++++ internal/config/config.go | 13 +++++++++++++ internal/grpcapi/grpcApi.go | 8 ++++++++ webui/js/ActionButton.js | 14 +++++++++++++- webui/js/ArgumentForm.js | 19 +++++++++++++++++++ webui/style.css | 10 ++++++++++ 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 webui/js/ArgumentForm.js diff --git a/OliveTin.proto b/OliveTin.proto index 4c59617..f348b45 100644 --- a/OliveTin.proto +++ b/OliveTin.proto @@ -9,6 +9,21 @@ message ActionButton { string title = 2; string icon = 3; bool canExec = 4; + + repeated ActionArgument arguments = 5; +} + +message ActionArgument { + string variable = 1; + string label = 2; + string type = 3; + + repeated ActionArgumentValue values = 4; +} + +message ActionArgumentValue { + string value = 1; + string label = 2; } message GetButtonsResponse { diff --git a/internal/config/config.go b/internal/config/config.go index 12e5afe..12efe2f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,19 @@ type ActionButton struct { CSS map[string]string `mapstructure:"omitempty"` Timeout int Permissions []PermissionsEntry + Arguments []ActionArgument +} + +type ActionArgument struct { + Variable string + Label string + Type string + Values []ActionArgumentValue +} + +type ActionArgumentValue struct { + Value string + Label string } // Entity represents a "thing" that can have multiple actions associated with it. diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index 22b5096..fa03211 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -61,6 +61,14 @@ func (api *oliveTinAPI) GetButtons(ctx ctx.Context, req *pb.GetButtonsRequest) ( CanExec: acl.IsAllowedExec(cfg, user, &action), } + for _, cfgArg := range action.Arguments { + pbArg := pb.ActionArgument { + Label: cfgArg.Label, + } + + btn.Arguments = append(btn.Arguments, &pbArg) + } + res.Actions = append(res.Actions, &btn) } diff --git a/webui/js/ActionButton.js b/webui/js/ActionButton.js index b94002e..503b02e 100644 --- a/webui/js/ActionButton.js +++ b/webui/js/ActionButton.js @@ -1,4 +1,5 @@ import { marshalLogsJsonToHtml } from './marshaller.js'; +import "./ArgumentForm.js" class ActionButton extends window.HTMLButtonElement { constructFromJson (json) { @@ -11,7 +12,18 @@ class ActionButton extends window.HTMLButtonElement { this.updateFromJson(json) - this.onclick = () => { this.startAction() } + this.onclick = () => { + if (json.arguments.length > 0) { + let frm = document.createElement('form', { is: 'argument-form' }) + window.frm = frm + console.log(frm) + frm.setup(json, this.startAction) + + document.body.appendChild(frm) + } else { + this.startAction() + } + } this.constructTemplate() diff --git a/webui/js/ArgumentForm.js b/webui/js/ArgumentForm.js new file mode 100644 index 0000000..f47af1b --- /dev/null +++ b/webui/js/ArgumentForm.js @@ -0,0 +1,19 @@ + +class ArgumentForm extends window.HTMLFormElement { + setup(json, callback) { + this.setAttribute('class', 'actionArguments') + this.title = document.createElement("h1") + this.title.innerHTML = "Action Arguments" + + this.appendChild(this.title); + + let a = document.createElement("span") + a.innerText = "Hi" + frm.appendChild(a) + + + console.log(json) + } +} + +window.customElements.define('argument-form', ArgumentForm, { extends: 'form' }) diff --git a/webui/style.css b/webui/style.css index d9e3e8d..3f15482 100644 --- a/webui/style.css +++ b/webui/style.css @@ -247,3 +247,13 @@ details[open] { margin-top: 1em; display: block; } + +form.actionArguments { + border-radius: 1em; + position: absolute; + top: 1em; + left: 1em; + right: 1em; + padding: 1em; + background-color: #fff; +} From 48fe7b7dd4a0e59ed9e9dfd9e11e7a755ab9706a Mon Sep 17 00:00:00 2001 From: jamesread Date: Sun, 17 Oct 2021 23:32:05 +0100 Subject: [PATCH 2/3] Ongoing UI work --- OliveTin.proto | 7 +-- internal/config/config.go | 7 +-- internal/grpcapi/grpcApi.go | 22 ++------ internal/grpcapi/grpcApiButtons.go | 47 ++++++++++++++++ webui/js/ArgumentForm.js | 87 ++++++++++++++++++++++++++---- webui/style.css | 40 ++++++++++++-- 6 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 internal/grpcapi/grpcApiButtons.go diff --git a/OliveTin.proto b/OliveTin.proto index f348b45..91dab54 100644 --- a/OliveTin.proto +++ b/OliveTin.proto @@ -14,14 +14,15 @@ message ActionButton { } message ActionArgument { - string variable = 1; + string name = 1; string label = 2; string type = 3; + string defaultValue = 4; - repeated ActionArgumentValue values = 4; + repeated ActionArgumentChoice choices = 5; } -message ActionArgumentValue { +message ActionArgumentChoice { string value = 1; string label = 2; } diff --git a/internal/config/config.go b/internal/config/config.go index 12efe2f..d93d71c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,13 +15,14 @@ type ActionButton struct { } type ActionArgument struct { - Variable string + Name string Label string Type string - Values []ActionArgumentValue + Default string + Choices []ActionArgumentChoice } -type ActionArgumentValue struct { +type ActionArgumentChoice struct { Value string Label string } diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index fa03211..1605868 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -2,16 +2,14 @@ package grpcapi import ( ctx "context" - "crypto/md5" - "fmt" pb "github.com/jamesread/OliveTin/gen/grpc" log "github.com/sirupsen/logrus" "google.golang.org/grpc" "net" - acl "github.com/jamesread/OliveTin/internal/acl" config "github.com/jamesread/OliveTin/internal/config" executor "github.com/jamesread/OliveTin/internal/executor" + acl "github.com/jamesread/OliveTin/internal/acl" ) var ( @@ -54,22 +52,8 @@ func (api *oliveTinAPI) GetButtons(ctx ctx.Context, req *pb.GetButtonsRequest) ( continue } - btn := pb.ActionButton{ - Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))), - Title: action.Title, - Icon: lookupHTMLIcon(action.Icon), - CanExec: acl.IsAllowedExec(cfg, user, &action), - } - - for _, cfgArg := range action.Arguments { - pbArg := pb.ActionArgument { - Label: cfgArg.Label, - } - - btn.Arguments = append(btn.Arguments, &pbArg) - } - - res.Actions = append(res.Actions, &btn) + btn := buildButton(action, user) + res.Actions = append(res.Actions, btn) } if len(res.Actions) == 0 { diff --git a/internal/grpcapi/grpcApiButtons.go b/internal/grpcapi/grpcApiButtons.go new file mode 100644 index 0000000..e9141a5 --- /dev/null +++ b/internal/grpcapi/grpcApiButtons.go @@ -0,0 +1,47 @@ +package grpcapi + +import ( + "fmt" + "crypto/md5" + pb "github.com/jamesread/OliveTin/gen/grpc" + config "github.com/jamesread/OliveTin/internal/config" + acl "github.com/jamesread/OliveTin/internal/acl" +) + +func buildButton(action config.ActionButton, user *acl.User) (*pb.ActionButton) { + btn := pb.ActionButton{ + Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))), + Title: action.Title, + Icon: lookupHTMLIcon(action.Icon), + CanExec: acl.IsAllowedExec(cfg, user, &action), + } + + for _, cfgArg := range action.Arguments { + pbArg := pb.ActionArgument { + Name: cfgArg.Name, + Label: cfgArg.Label, + Type: cfgArg.Type, + DefaultValue: cfgArg.Default, + Choices: buildChoices(cfgArg.Choices), + } + + btn.Arguments = append(btn.Arguments, &pbArg) + } + + return &btn +} + +func buildChoices(choices []config.ActionArgumentChoice) ([]*pb.ActionArgumentChoice) { + ret := []*pb.ActionArgumentChoice{} + + for _, cfgChoice := range choices { + pbChoice := pb.ActionArgumentChoice { + Value: cfgChoice.Value, + Label: cfgChoice.Label, + } + + ret = append(ret, &pbChoice); + } + + return ret; +} diff --git a/webui/js/ArgumentForm.js b/webui/js/ArgumentForm.js index f47af1b..bf0f706 100644 --- a/webui/js/ArgumentForm.js +++ b/webui/js/ArgumentForm.js @@ -2,17 +2,86 @@ class ArgumentForm extends window.HTMLFormElement { setup(json, callback) { this.setAttribute('class', 'actionArguments') - this.title = document.createElement("h1") - this.title.innerHTML = "Action Arguments" - - this.appendChild(this.title); - - let a = document.createElement("span") - a.innerText = "Hi" - frm.appendChild(a) - console.log(json) + + this.domWrapper = document.createElement('div') + this.domWrapper.classList += 'wrapper' + this.appendChild(this.domWrapper) + + this.domTitle = document.createElement('h2') + this.domTitle.innerText = json.title + ": Arguments" + this.domWrapper.appendChild(this.domTitle); + + this.domIcon = document.createElement('span'); + this.domIcon.classList += 'icon' + this.domIcon.setAttribute('role', 'img') + this.domIcon.innerHTML = json.icon + this.domTitle.prepend(this.domIcon) + + let a = document.createElement("span") + a.innerText = "This is test version of the form." + this.domWrapper.appendChild(a) + + this.createDomFormArguments(json.arguments) + this.domWrapper.appendChild(this.createDomSubmit()) + + console.log(json) + } + + createDomSubmit() { + let el = document.createElement('button') + el.setAttribute('action', 'submit') + el.innerText = "Run" + + return el + } + + createDomFormArguments(args) { + for (let arg of args) { + let domFieldWrapper = document.createElement('p'); + + domFieldWrapper.appendChild(this.createDomLabel(arg)) + domFieldWrapper.appendChild(this.createDomInput(arg)) + + this.domWrapper.appendChild(domFieldWrapper) + } + } + + createDomLabel(arg) { + let domLbl = document.createElement('label') + domLbl.innerText = arg.label + ':'; + domLbl.setAttribute('for', arg.name) + + return domLbl; + } + + createDomInput(arg) { + let domEl = null; + + if (arg.choices.length > 0) { + domEl = document.createElement('select') + + for (let choice of arg.choices) { + domEl.appendChild(this.createSelectOption(choice)) + } + } else { + domEl = document.createElement('input') + } + + domEl.setAttribute('id', arg.name) + domEl.value = arg.defaultValue + + return domEl; + } + + createSelectOption(choice) { + let domEl = document.createElement('option') + + domEl.setAttribute('value', choice.value) + domEl.innerText = choice.label + + return domEl } } diff --git a/webui/style.css b/webui/style.css index 3f15482..93b2ded 100644 --- a/webui/style.css +++ b/webui/style.css @@ -249,11 +249,41 @@ details[open] { } form.actionArguments { - border-radius: 1em; position: absolute; - top: 1em; - left: 1em; - right: 1em; + top: 0; + bottom: 0; + left: 0; + right: 0; padding: 1em; - background-color: #fff; + box-shadow: 0 0 6px 0 #aaa; + background-color: #dee3e7; +} + +h2 { + font-size: 1em; +} + +h2 span.icon { + vertical-align: middle; + padding-right: .2em; +} + +form div.wrapper { + border-radius: 1em; + box-shadow: 0 0 10px 0 #444; + background-color: white; + border: 1px solid #999; + text-align: left; + padding: 1em; +} + +label { + width: 30%; + text-align: right; + display: inline-block; + padding-right: 1em; +} + +input { + padding: .6em; } From c43d4ffd34f3515218e9480517655aa3e8bd4363 Mon Sep 17 00:00:00 2001 From: jamesread Date: Sun, 17 Oct 2021 23:36:55 +0100 Subject: [PATCH 3/3] gofmt --- internal/config/config.go | 16 ++++++++-------- internal/grpcapi/grpcApi.go | 2 +- internal/grpcapi/grpcApiButtons.go | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index d93d71c..2de2755 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,20 +11,20 @@ type ActionButton struct { CSS map[string]string `mapstructure:"omitempty"` Timeout int Permissions []PermissionsEntry - Arguments []ActionArgument + Arguments []ActionArgument } type ActionArgument struct { - Name string - Label string - Type string - Default string - Choices []ActionArgumentChoice + Name string + Label string + Type string + Default string + Choices []ActionArgumentChoice } type ActionArgumentChoice struct { - Value string - Label string + Value string + Label string } // Entity represents a "thing" that can have multiple actions associated with it. diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index 1605868..0b4eb71 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -7,9 +7,9 @@ import ( "google.golang.org/grpc" "net" + acl "github.com/jamesread/OliveTin/internal/acl" config "github.com/jamesread/OliveTin/internal/config" executor "github.com/jamesread/OliveTin/internal/executor" - acl "github.com/jamesread/OliveTin/internal/acl" ) var ( diff --git a/internal/grpcapi/grpcApiButtons.go b/internal/grpcapi/grpcApiButtons.go index e9141a5..87e0fe0 100644 --- a/internal/grpcapi/grpcApiButtons.go +++ b/internal/grpcapi/grpcApiButtons.go @@ -1,14 +1,14 @@ package grpcapi import ( - "fmt" "crypto/md5" + "fmt" pb "github.com/jamesread/OliveTin/gen/grpc" - config "github.com/jamesread/OliveTin/internal/config" acl "github.com/jamesread/OliveTin/internal/acl" + config "github.com/jamesread/OliveTin/internal/config" ) -func buildButton(action config.ActionButton, user *acl.User) (*pb.ActionButton) { +func buildButton(action config.ActionButton, user *acl.User) *pb.ActionButton { btn := pb.ActionButton{ Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))), Title: action.Title, @@ -17,12 +17,12 @@ func buildButton(action config.ActionButton, user *acl.User) (*pb.ActionButton) } for _, cfgArg := range action.Arguments { - pbArg := pb.ActionArgument { - Name: cfgArg.Name, - Label: cfgArg.Label, - Type: cfgArg.Type, + pbArg := pb.ActionArgument{ + Name: cfgArg.Name, + Label: cfgArg.Label, + Type: cfgArg.Type, DefaultValue: cfgArg.Default, - Choices: buildChoices(cfgArg.Choices), + Choices: buildChoices(cfgArg.Choices), } btn.Arguments = append(btn.Arguments, &pbArg) @@ -31,17 +31,17 @@ func buildButton(action config.ActionButton, user *acl.User) (*pb.ActionButton) return &btn } -func buildChoices(choices []config.ActionArgumentChoice) ([]*pb.ActionArgumentChoice) { +func buildChoices(choices []config.ActionArgumentChoice) []*pb.ActionArgumentChoice { ret := []*pb.ActionArgumentChoice{} for _, cfgChoice := range choices { - pbChoice := pb.ActionArgumentChoice { + pbChoice := pb.ActionArgumentChoice{ Value: cfgChoice.Value, Label: cfgChoice.Label, } - ret = append(ret, &pbChoice); + ret = append(ret, &pbChoice) } - return ret; + return ret }