Przeglądaj źródła

feat: custom-api multiple API queries

Ralph Ocdol 4 miesięcy temu
rodzic
commit
8baa07d440
3 zmienionych plików z 141 dodań i 44 usunięć
  1. 24 1
      docs/configuration.md
  2. 22 0
      docs/custom-api.md
  3. 95 43
      internal/glance/widget-custom-api.go

+ 24 - 1
docs/configuration.md

@@ -1290,11 +1290,34 @@ Examples:
 #### Properties
 #### Properties
 | Name | Type | Required | Default |
 | Name | Type | Required | Default |
 | ---- | ---- | -------- | ------- |
 | ---- | ---- | -------- | ------- |
-| url | string | yes | |
+| url | string | yes, unless `api-queries` is set | |
 | headers | key (string) & value (string) | no | |
 | headers | key (string) & value (string) | no | |
 | frameless | boolean | no | false |
 | frameless | boolean | no | false |
 | template | string | yes | |
 | template | string | yes | |
 | parameters | key & value | no | |
 | parameters | key & value | no | |
+| api-queries | list of urls, parameters & headers | no | |
+
+> [!NOTE]
+> 
+> `api-queries` will override `url`, `headers` and `parameters`
+> since it also provides its own options
+
+##### `api-queries`
+A list of API queries, the name set will be the name of the json returned
+```yaml
+api-queries:
+  sample-data1:
+    url: https://domain.com/api
+    parameters:
+      foo: bar
+    headers:
+      x-api-key: your-api-key
+      Accept: application/json
+  sample-data2:
+    url: https://another-domain.com/api
+```
+see [custom-api docs](./custom-api.md#api-queries)
+
 
 
 ##### `url`
 ##### `url`
 The URL to fetch the data from. It must be accessible from the server that Glance is running on.
 The URL to fetch the data from. It must be accessible from the server that Glance is running on.

+ 22 - 0
docs/custom-api.md

@@ -240,6 +240,28 @@ Output:
 
 
 Other operations include `add`, `mul`, and `div`.
 Other operations include `add`, `mul`, and `div`.
 
 
+<hr>
+
+#### API-Queries
+JSON response
+```json
+  {
+    "sample-data1": {
+      "title": "My Title",
+      "content": "My Content"
+    },
+    "sample-data2": [
+      {
+          "name": "John Doe"
+      },
+      {
+          "name": "Jane Doe"
+      }
+    ]
+  }
+```
+
+
 <hr>
 <hr>
 
 
 In some instances, you may want to know the status code of the response. This can be done using the following:
 In some instances, you may want to know the status code of the response. This can be done using the following:

+ 95 - 43
internal/glance/widget-custom-api.go

@@ -3,6 +3,7 @@ package glance
 import (
 import (
 	"bytes"
 	"bytes"
 	"context"
 	"context"
+	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"html/template"
 	"html/template"
@@ -17,28 +18,70 @@ import (
 )
 )
 
 
 var customAPIWidgetTemplate = mustParseTemplate("custom-api.html", "widget-base.html")
 var customAPIWidgetTemplate = mustParseTemplate("custom-api.html", "widget-base.html")
+var customRandomKeyForSingleRequest = fmt.Sprintf("%x", time.Now().UnixNano())
 
 
 type customAPIWidget struct {
 type customAPIWidget struct {
 	widgetBase       `yaml:",inline"`
 	widgetBase       `yaml:",inline"`
+	ApiQueries		 map[string]apiQueries		`yaml:"api-queries"`
+	URL              string               		`yaml:"url"`
+	Template         string               		`yaml:"template"`
+	Frameless        bool                 		`yaml:"frameless"`
+	Headers          map[string]string    		`yaml:"headers"`
+	Parameters       queryParametersField 		`yaml:"parameters"`
+	APIRequest       map[string]*http.Request	`yaml:"-"`
+	compiledTemplate *template.Template   		`yaml:"-"`
+	CompiledHTML     template.HTML        		`yaml:"-"`
+}
+
+type apiQueries struct {
 	URL              string               `yaml:"url"`
 	URL              string               `yaml:"url"`
-	Template         string               `yaml:"template"`
-	Frameless        bool                 `yaml:"frameless"`
 	Headers          map[string]string    `yaml:"headers"`
 	Headers          map[string]string    `yaml:"headers"`
 	Parameters       queryParametersField `yaml:"parameters"`
 	Parameters       queryParametersField `yaml:"parameters"`
-	APIRequest       *http.Request        `yaml:"-"`
-	compiledTemplate *template.Template   `yaml:"-"`
-	CompiledHTML     template.HTML        `yaml:"-"`
 }
 }
 
 
 func (widget *customAPIWidget) initialize() error {
 func (widget *customAPIWidget) initialize() error {
 	widget.withTitle("Custom API").withCacheDuration(1 * time.Hour)
 	widget.withTitle("Custom API").withCacheDuration(1 * time.Hour)
 
 
-	if widget.URL == "" {
-		return errors.New("URL is required")
-	}
+	widget.APIRequest = make(map[string]*http.Request)
+	if len(widget.ApiQueries) != 0 {
+		for object, query := range widget.ApiQueries {
+			if query.URL == "" {
+				return errors.New("URL for each query is required")
+			}
+			req, err := http.NewRequest(http.MethodGet, query.URL, nil)
+			if err != nil {
+				return err
+			}
+
+			req.URL.RawQuery = query.Parameters.toQueryString()
+
+			for key, value := range query.Headers {
+				req.Header.Add(key, value)
+			}
+
+			widget.APIRequest[object] = req
+		}
+	} else {
+		if widget.URL == "" {
+			return errors.New("URL is required")
+		}
 
 
-	if widget.Template == "" {
-		return errors.New("template is required")
+		if widget.Template == "" {
+			return errors.New("template is required")
+		}
+		
+		req, err := http.NewRequest(http.MethodGet, widget.URL, nil)
+		if err != nil {
+			return err
+		}
+		
+		req.URL.RawQuery = widget.Parameters.toQueryString()
+		
+		for key, value := range widget.Headers {
+			req.Header.Add(key, value)
+		}
+		
+		widget.APIRequest[customRandomKeyForSingleRequest] = req
 	}
 	}
 
 
 	compiledTemplate, err := template.New("").Funcs(customAPITemplateFuncs).Parse(widget.Template)
 	compiledTemplate, err := template.New("").Funcs(customAPITemplateFuncs).Parse(widget.Template)
@@ -48,19 +91,6 @@ func (widget *customAPIWidget) initialize() error {
 
 
 	widget.compiledTemplate = compiledTemplate
 	widget.compiledTemplate = compiledTemplate
 
 
-	req, err := http.NewRequest(http.MethodGet, widget.URL, nil)
-	if err != nil {
-		return err
-	}
-
-	req.URL.RawQuery = widget.Parameters.toQueryString()
-
-	for key, value := range widget.Headers {
-		req.Header.Add(key, value)
-	}
-
-	widget.APIRequest = req
-
 	return nil
 	return nil
 }
 }
 
 
@@ -77,36 +107,58 @@ func (widget *customAPIWidget) Render() template.HTML {
 	return widget.renderTemplate(widget, customAPIWidgetTemplate)
 	return widget.renderTemplate(widget, customAPIWidgetTemplate)
 }
 }
 
 
-func fetchAndParseCustomAPI(req *http.Request, tmpl *template.Template) (template.HTML, error) {
+func fetchAndParseCustomAPI(requests map[string]*http.Request, tmpl *template.Template) (template.HTML, error) {
 	emptyBody := template.HTML("")
 	emptyBody := template.HTML("")
 
 
-	resp, err := defaultHTTPClient.Do(req)
-	if err != nil {
-		return emptyBody, err
-	}
-	defer resp.Body.Close()
+	var resp *http.Response
+	var err error
+	body := make(map[string]string)
+	mergedBody := "{}"
+	for key, req := range requests {
+		resp, err = defaultHTTPClient.Do(req)
+		if err != nil {
+			return emptyBody, err
+		}
+		defer resp.Body.Close()
 
 
-	bodyBytes, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return emptyBody, err
+		bodyBytes, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return emptyBody, err
+		}
+	
+		body[key] = strings.TrimSpace(string(bodyBytes))
+	
+		if body[key] != "" && !gjson.Valid(body[key]) {
+			truncatedBody, isTruncated := limitStringLength(body[key], 100)
+			if isTruncated {
+				truncatedBody += "... <truncated>"
+			}
+	
+			slog.Error("Invalid response JSON in custom API widget", "url", req.URL.String(), key, truncatedBody)
+			return emptyBody, errors.New("invalid response JSON")
+		}
 	}
 	}
-
-	body := strings.TrimSpace(string(bodyBytes))
-
-	if body != "" && !gjson.Valid(body) {
-		truncatedBody, isTruncated := limitStringLength(body, 100)
-		if isTruncated {
-			truncatedBody += "... <truncated>"
+	
+	if jsonBody, exists := body[customRandomKeyForSingleRequest]; exists {
+		mergedBody = jsonBody
+	} else {
+		mergedMap := make(map[string]json.RawMessage)
+		for key, jsonBody := range body {
+			if !gjson.Valid(jsonBody) {
+				continue
+			}
+			mergedMap[key] = json.RawMessage(jsonBody)
+		}
+		if len(mergedMap) > 0 {
+			bytes, _ := json.Marshal(mergedMap)
+			mergedBody = string(bytes)
 		}
 		}
-
-		slog.Error("Invalid response JSON in custom API widget", "url", req.URL.String(), "body", truncatedBody)
-		return emptyBody, errors.New("invalid response JSON")
 	}
 	}
 
 
 	var templateBuffer bytes.Buffer
 	var templateBuffer bytes.Buffer
 
 
 	data := customAPITemplateData{
 	data := customAPITemplateData{
-		JSON:     decoratedGJSONResult{gjson.Parse(body)},
+		JSON:     decoratedGJSONResult{gjson.Parse(mergedBody)},
 		Response: resp,
 		Response: resp,
 	}
 	}