diff --git a/docs/widgets/services/customapi.md b/docs/widgets/services/customapi.md index 0deb8294..79345008 100644 --- a/docs/widgets/services/customapi.md +++ b/docs/widgets/services/customapi.md @@ -138,7 +138,15 @@ You can manipulate data with the following tools `remap`, `scale`, `prefix` and prefix: "$" ``` -## List View +## Display Options + +The widget supports different display modes that can be set using the `display` property. + +### Block View (Default) + +The default display mode is `block`, which shows fields in a block format. + +### List View You can change the default block view to a list view by setting the `display` option to `list`. @@ -169,6 +177,48 @@ The list view can optionally display an additional field next to the primary fie format: date ``` +### Dynamic List View + +To display a list of items from an array in the API response, set the `display` property to `dynamic-list` and configure the `mappings` object with the following properties: + +```yaml +widget: + type: customapi + url: https://example.com/api/servers + display: dynamic-list + mappings: + items: data # optional, the path to the array in the API response. Omit this option if the array is at the root level + name: id # required, field in each item to use as the item name (left side) + label: ip_address # required, field in each item to use as the item label (right side) + limit: 5 # optional, limit the number of items to display + target: https://example.com/server/{id} # optional, makes items clickable with template support +``` + +This configuration would work with an API that returns a response like: + +```json +{ + "data": [ + { "id": "server1", "name": "Server 1", "ip_address": "192.168.0.1" }, + { "id": "server2", "name": "Server 2", "ip_address": "192.168.0.2" } + ] +} +``` + +The widget would display a list with two items: + +- "Server 1" on the left and "192.168.0.1" on the right, clickable to "https://example.com/server/server1" +- "Server 2" on the left and "192.168.0.2" on the right, clickable to "https://example.com/server/server2" + +For nested fields in the items, you can use dot notation: + +```yaml +mappings: + items: data.results.servers + name: details.id + label: details.name +``` + ## Custom Headers Pass custom headers using the `headers` option, for example: diff --git a/src/widgets/customapi/component.jsx b/src/widgets/customapi/component.jsx index 6d0a63cd..3a6ac069 100644 --- a/src/widgets/customapi/component.jsx +++ b/src/widgets/customapi/component.jsx @@ -1,8 +1,10 @@ import { useTranslation } from "next-i18next"; import Container from "components/services/widget/container"; import Block from "components/services/widget/block"; +import classNames from "classnames"; import useWidgetAPI from "utils/proxy/use-widget-api"; +import * as shvl from "utils/config/shvl"; function getValue(field, data) { let value = data; @@ -165,6 +167,16 @@ export default function Component({ service }) { if (!customData) { switch (display) { + case "dynamic-list": + return ( + +
+
+
Loading...
+
+
+
+ ); case "list": return ( @@ -196,6 +208,68 @@ export default function Component({ service }) { } switch (display) { + case "dynamic-list": + let listItems = customData; + if (mappings.items) listItems = shvl.get(customData, mappings.items, null); + let error; + if (!listItems || !Array.isArray(listItems)) { + error = { message: "Unable to find items" }; + } + const name = mappings.name; + const label = mappings.label; + if (!name || !label) { + error = { message: "Name and label properties are required" }; + } + if (error) { + return ; + } + + const target = mappings.target; + if (mappings.limit && parseInt(mappings.limit, 10) > 0) { + listItems.splice(mappings.limit); + } + + return ( + +
+ {listItems.length === 0 ? ( +
+
No items found
+
+ ) : ( + listItems.map((item, index) => { + const itemName = shvl.get(item, name, ""); + const itemLabel = shvl.get(item, label, ""); + const itemUrl = target ? target.replace(/\{([^}]+)\}/g, (_, key) => item[key] || "") : null; + const className = + "bg-theme-200/50 dark:bg-theme-900/20 rounded-sm m-1 flex-1 flex flex-row items-center justify-between p-1 text-xs"; + + return itemUrl ? ( + +
{itemName}
+
+
{itemLabel}
+
+
+ ) : ( +
+
{itemName}
+
+
{itemLabel}
+
+
+ ); + }) + )} +
+
+ ); case "list": return (