mirror of
https://github.com/karl0ss/homepage.git
synced 2025-04-29 12:03:41 +01:00
Enhancement: support dynamic list rendering in custom api widget (#5012)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
parent
de9c015f7f
commit
8d20f22932
@ -138,7 +138,15 @@ You can manipulate data with the following tools `remap`, `scale`, `prefix` and
|
|||||||
prefix: "$"
|
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`.
|
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
|
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
|
## Custom Headers
|
||||||
|
|
||||||
Pass custom headers using the `headers` option, for example:
|
Pass custom headers using the `headers` option, for example:
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import Container from "components/services/widget/container";
|
import Container from "components/services/widget/container";
|
||||||
import Block from "components/services/widget/block";
|
import Block from "components/services/widget/block";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||||
|
import * as shvl from "utils/config/shvl";
|
||||||
|
|
||||||
function getValue(field, data) {
|
function getValue(field, data) {
|
||||||
let value = data;
|
let value = data;
|
||||||
@ -165,6 +167,16 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
if (!customData) {
|
if (!customData) {
|
||||||
switch (display) {
|
switch (display) {
|
||||||
|
case "dynamic-list":
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
<div 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 animate-pulse">
|
||||||
|
<div className="font-thin pl-2">Loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
case "list":
|
case "list":
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
@ -196,6 +208,68 @@ export default function Component({ service }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (display) {
|
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 <Container service={service} error={error}></Container>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = mappings.target;
|
||||||
|
if (mappings.limit && parseInt(mappings.limit, 10) > 0) {
|
||||||
|
listItems.splice(mappings.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
{listItems.length === 0 ? (
|
||||||
|
<div 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">
|
||||||
|
<div className="font-thin pl-2">No items found</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
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 ? (
|
||||||
|
<a
|
||||||
|
key={`${itemName}-${index}`}
|
||||||
|
className={classNames(className, "hover:bg-theme-300/50 dark:hover:bg-theme-800/20")}
|
||||||
|
href={itemUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="font-thin pl-2">{itemName}</div>
|
||||||
|
<div className="flex flex-row text-right">
|
||||||
|
<div className="font-bold mr-2">{itemLabel}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div key={`${itemName}-${index}`} className={className}>
|
||||||
|
<div className="font-thin pl-2">{itemName}</div>
|
||||||
|
<div className="flex flex-row text-right">
|
||||||
|
<div className="font-bold mr-2">{itemLabel}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
case "list":
|
case "list":
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user