From 948289a03895d46bc4e32c0cb259bbfb7df645e1 Mon Sep 17 00:00:00 2001 From: Ralph Ocdol Date: Fri, 28 Feb 2025 08:48:07 +0800 Subject: [PATCH 1/4] feat: add parameters and array parameters support --- docs/configuration.md | 12 ++++++++ internal/glance/widget-custom-api.go | 42 +++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8c97123..c4ed1b8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1294,6 +1294,7 @@ Examples: | headers | key (string) & value (string) | no | | | frameless | boolean | no | false | | template | string | yes | | +| parameters | key & value | no | | ##### `url` The URL to fetch the data from. It must be accessible from the server that Glance is running on. @@ -1313,6 +1314,17 @@ When set to `true`, removes the border and padding around the widget. ##### `template` The template that will be used to display the data. It relies on Go's `html/template` package so it's recommended to go through [its documentation](https://pkg.go.dev/text/template) to understand how to do basic things such as conditionals, loops, etc. In addition, it also uses [tidwall's gjson](https://github.com/tidwall/gjson) package to parse the JSON data so it's worth going through its documentation if you want to use more advanced JSON selectors. You can view additional examples with explanations and function definitions [here](custom-api.md). +##### `parameters` +A list of keys and values that will be sent to the custom-api as query paramters. + +```yaml +parameters: + param1: value1 + param2: + - item1 + - item2 +``` + ### Extension Display a widget provided by an external source (3rd party). If you want to learn more about developing extensions, checkout the [extensions documentation](extensions.md) (WIP). diff --git a/internal/glance/widget-custom-api.go b/internal/glance/widget-custom-api.go index c8e3773..9e58f09 100644 --- a/internal/glance/widget-custom-api.go +++ b/internal/glance/widget-custom-api.go @@ -10,6 +10,7 @@ import ( "log/slog" "math" "net/http" + "net/url" "time" "github.com/tidwall/gjson" @@ -19,13 +20,14 @@ var customAPIWidgetTemplate = mustParseTemplate("custom-api.html", "widget-base. type customAPIWidget struct { widgetBase `yaml:",inline"` - URL string `yaml:"url"` - Template string `yaml:"template"` - Frameless bool `yaml:"frameless"` - Headers map[string]string `yaml:"headers"` - APIRequest *http.Request `yaml:"-"` - compiledTemplate *template.Template `yaml:"-"` - CompiledHTML template.HTML `yaml:"-"` + URL string `yaml:"url"` + Template string `yaml:"template"` + Frameless bool `yaml:"frameless"` + Headers map[string]string `yaml:"headers"` + Parameters map[string]interface{} `yaml:"parameters"` + APIRequest *http.Request `yaml:"-"` + compiledTemplate *template.Template `yaml:"-"` + CompiledHTML template.HTML `yaml:"-"` } func (widget *customAPIWidget) initialize() error { @@ -51,6 +53,32 @@ func (widget *customAPIWidget) initialize() error { return err } + query := url.Values{} + + for key, value := range widget.Parameters { + switch v := value.(type) { + case string: + query.Add(key, v) + case int, int8, int16, int32, int64, float32, float64: + query.Add(key, fmt.Sprintf("%v", v)) + case []string: + for _, item := range v { + query.Add(key, item) + } + case []interface{}: + for _, item := range v { + switch item := item.(type) { + case string: + query.Add(key, item) + case int, int8, int16, int32, int64, float32, float64: + query.Add(key, fmt.Sprintf("%v", item)) + } + } + } + } + + req.URL.RawQuery = query.Encode() + for key, value := range widget.Headers { req.Header.Add(key, value) } From 8da26ab4095a40a6866de551c52bb22e23b85563 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Sat, 1 Mar 2025 23:29:28 +0000 Subject: [PATCH 2/4] Make query parameters field reusable --- internal/glance/config-fields.go | 52 ++++++++++++++++++++++++++++ internal/glance/widget-custom-api.go | 43 +++++------------------ 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/internal/glance/config-fields.go b/internal/glance/config-fields.go index f3c836e..966b366 100644 --- a/internal/glance/config-fields.go +++ b/internal/glance/config-fields.go @@ -219,3 +219,55 @@ func (p *proxyOptionsField) UnmarshalYAML(node *yaml.Node) error { return nil } + +type queryParametersField map[string][]string + +func (q *queryParametersField) UnmarshalYAML(node *yaml.Node) error { + var decoded map[string]any + + if err := node.Decode(&decoded); err != nil { + return err + } + + *q = make(queryParametersField) + + for key, value := range decoded { + switch v := value.(type) { + case string: + (*q)[key] = []string{v} + case int, int8, int16, int32, int64, float32, float64: + (*q)[key] = []string{fmt.Sprintf("%v", v)} + case []string: + (*q)[key] = append((*q)[key], v...) + case []any: + for _, item := range v { + switch item := item.(type) { + case string: + (*q)[key] = append((*q)[key], item) + case int, int8, int16, int32, int64, float32, float64: + (*q)[key] = append((*q)[key], fmt.Sprintf("%v", item)) + case bool: + (*q)[key] = append((*q)[key], fmt.Sprintf("%t", item)) + default: + return fmt.Errorf("invalid query parameter value type: %T", item) + } + } + default: + return fmt.Errorf("invalid query parameter value type: %T", value) + } + } + + return nil +} + +func (q *queryParametersField) toQueryString() string { + query := url.Values{} + + for key, values := range *q { + for _, value := range values { + query.Add(key, value) + } + } + + return query.Encode() +} diff --git a/internal/glance/widget-custom-api.go b/internal/glance/widget-custom-api.go index 9e58f09..1c50ad6 100644 --- a/internal/glance/widget-custom-api.go +++ b/internal/glance/widget-custom-api.go @@ -10,7 +10,6 @@ import ( "log/slog" "math" "net/http" - "net/url" "time" "github.com/tidwall/gjson" @@ -20,14 +19,14 @@ var customAPIWidgetTemplate = mustParseTemplate("custom-api.html", "widget-base. type customAPIWidget struct { widgetBase `yaml:",inline"` - URL string `yaml:"url"` - Template string `yaml:"template"` - Frameless bool `yaml:"frameless"` - Headers map[string]string `yaml:"headers"` - Parameters map[string]interface{} `yaml:"parameters"` - APIRequest *http.Request `yaml:"-"` - compiledTemplate *template.Template `yaml:"-"` - CompiledHTML template.HTML `yaml:"-"` + URL string `yaml:"url"` + Template string `yaml:"template"` + Frameless bool `yaml:"frameless"` + Headers map[string]string `yaml:"headers"` + Parameters queryParametersField `yaml:"parameters"` + APIRequest *http.Request `yaml:"-"` + compiledTemplate *template.Template `yaml:"-"` + CompiledHTML template.HTML `yaml:"-"` } func (widget *customAPIWidget) initialize() error { @@ -53,31 +52,7 @@ func (widget *customAPIWidget) initialize() error { return err } - query := url.Values{} - - for key, value := range widget.Parameters { - switch v := value.(type) { - case string: - query.Add(key, v) - case int, int8, int16, int32, int64, float32, float64: - query.Add(key, fmt.Sprintf("%v", v)) - case []string: - for _, item := range v { - query.Add(key, item) - } - case []interface{}: - for _, item := range v { - switch item := item.(type) { - case string: - query.Add(key, item) - case int, int8, int16, int32, int64, float32, float64: - query.Add(key, fmt.Sprintf("%v", item)) - } - } - } - } - - req.URL.RawQuery = query.Encode() + req.URL.RawQuery = widget.Parameters.toQueryString() for key, value := range widget.Headers { req.Header.Add(key, value) From acddaf07db7466c7f1ab73686c81a9380f9ec349 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Sat, 1 Mar 2025 23:29:56 +0000 Subject: [PATCH 3/4] Add note to docs --- docs/configuration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index c4ed1b8..a0ff491 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1317,6 +1317,10 @@ The template that will be used to display the data. It relies on Go's `html/temp ##### `parameters` A list of keys and values that will be sent to the custom-api as query paramters. +> [!NOTE] +> +> Setting this property will override any query parameters that are already in the URL. + ```yaml parameters: param1: value1 From 49668d4ba9153bd05ec169ca6d3876396bda79ad Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Sat, 1 Mar 2025 23:30:20 +0000 Subject: [PATCH 4/4] Also apply to extension widget --- internal/glance/widget-extension.go | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/internal/glance/widget-extension.go b/internal/glance/widget-extension.go index 72a4a09..47034f3 100644 --- a/internal/glance/widget-extension.go +++ b/internal/glance/widget-extension.go @@ -19,12 +19,12 @@ const extensionWidgetDefaultTitle = "Extension" type extensionWidget struct { widgetBase `yaml:",inline"` - URL string `yaml:"url"` - FallbackContentType string `yaml:"fallback-content-type"` - Parameters map[string]string `yaml:"parameters"` - AllowHtml bool `yaml:"allow-potentially-dangerous-html"` - Extension extension `yaml:"-"` - cachedHTML template.HTML `yaml:"-"` + URL string `yaml:"url"` + FallbackContentType string `yaml:"fallback-content-type"` + Parameters queryParametersField `yaml:"parameters"` + AllowHtml bool `yaml:"allow-potentially-dangerous-html"` + Extension extension `yaml:"-"` + cachedHTML template.HTML `yaml:"-"` } func (widget *extensionWidget) initialize() error { @@ -82,10 +82,10 @@ const ( ) type extensionRequestOptions struct { - URL string `yaml:"url"` - FallbackContentType string `yaml:"fallback-content-type"` - Parameters map[string]string `yaml:"parameters"` - AllowHtml bool `yaml:"allow-potentially-dangerous-html"` + URL string `yaml:"url"` + FallbackContentType string `yaml:"fallback-content-type"` + Parameters queryParametersField `yaml:"parameters"` + AllowHtml bool `yaml:"allow-potentially-dangerous-html"` } type extension struct { @@ -109,14 +109,7 @@ func convertExtensionContent(options extensionRequestOptions, content []byte, co func fetchExtension(options extensionRequestOptions) (extension, error) { request, _ := http.NewRequest("GET", options.URL, nil) - - query := url.Values{} - - for key, value := range options.Parameters { - query.Set(key, value) - } - - request.URL.RawQuery = query.Encode() + request.URL.RawQuery = options.Parameters.toQueryString() response, err := http.DefaultClient.Do(request) if err != nil {