Merge pull request #378 from ralphocdol/custom-api-array-parameters

feat: add parameters and array parameters support
This commit is contained in:
Svilen Markov 2025-03-01 23:36:29 +00:00 committed by GitHub
commit 652f9ceb5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 25 deletions

View file

@ -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,21 @@ 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.
> [!NOTE]
>
> Setting this property will override any query parameters that are already in the URL.
```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).

View file

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

View file

@ -19,13 +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"`
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 {
@ -51,6 +52,8 @@ func (widget *customAPIWidget) initialize() error {
return err
}
req.URL.RawQuery = widget.Parameters.toQueryString()
for key, value := range widget.Headers {
req.Header.Add(key, value)
}

View file

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