Widget for displaying scripted information

This widget executes a script and displays the script's output. The executed script
must return the data in json format. The returned fields can be filtered, labeled
and formatted.
This commit is contained in:
Stefan Taferner 2022-10-27 15:49:49 +02:00
parent c14ae6ee72
commit 40de43b2da
7 changed files with 149 additions and 0 deletions

View File

@ -298,5 +298,9 @@
"rejectedPushes": "Rejected", "rejectedPushes": "Rejected",
"filters": "Filters", "filters": "Filters",
"indexers": "Indexers" "indexers": "Indexers"
},
"scripted": {
"yes": "Yes",
"no": "No"
} }
} }

View File

@ -30,6 +30,7 @@ const components = {
readarr: dynamic(() => import("./readarr/component")), readarr: dynamic(() => import("./readarr/component")),
rutorrent: dynamic(() => import("./rutorrent/component")), rutorrent: dynamic(() => import("./rutorrent/component")),
sabnzbd: dynamic(() => import("./sabnzbd/component")), sabnzbd: dynamic(() => import("./sabnzbd/component")),
scripted: dynamic(() => import("./scripted/component")),
sonarr: dynamic(() => import("./sonarr/component")), sonarr: dynamic(() => import("./sonarr/component")),
speedtest: dynamic(() => import("./speedtest/component")), speedtest: dynamic(() => import("./speedtest/component")),
strelaysrv: dynamic(() => import("./strelaysrv/component")), strelaysrv: dynamic(() => import("./strelaysrv/component")),

View File

@ -0,0 +1,50 @@
# Widget for displaying scripted information
This widget executes a script and displays the script's output. The executed script must return
the data in json format. The returned fields can be filtered, labeled and formatted.
For example to display the online status and number of players of a minecraft server the
configuration could be:
```yaml
widget:
type: scripted
script: "mcstatus myserver:25565 json"
fields: [ "online", "player_count", "ping" ]
field_labels:
player_count: "players"
field_types:
online: boolean
ping:
type: common.ms
style: unit
unit: millisecond
unitDisplay: narrow
```
The output of the executed script of the above example is
```json
{ "online": true, "version": "1.19.2", "protocol": 760, "motd": "A Minecraft server", "player_count": 0, "player_max": 20, "players": [], "ping": 0.527 }
```
From the scripts output the three fields <code>online</code>, <code>player_count</code> and <code>ping</code>
will be displayed. The field <code>player_count</code> will be named "players". The fields <code>online</code>
and <code>ping</code> will be formatted.
## Configuration
* **script** is the script that will be executed as the user that runs the homepage server.
It's output must be in JSON format.
* **fields** names the fields from the script's output that shall be displayed.
It is recommended to set the fields. Otherwise the widget will be empty if no data can be displayed.
* **field_labels** defines the labels to be displayed for the fields. The field name itself is used if there
is no label for a field, like for the fields <code>online</code> and <code>ping</code> in the example.
* **field_types** defines the types of the fields. If unset then the value is shown unformatted.
The type's value can either be a simple string like <code>common.number</code> or a map if multiple
configuration options shall be used like in the example above for the <code>ping</code> field.
See the "common.XY" translation strings in the file <code>public/locales/en/common.json</code> for
supported field types. In addition the type <code>boolean</code> is supported.

View File

@ -0,0 +1,49 @@
import { useTranslation } from "next-i18next";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: scriptedData, error: scriptedError } = useWidgetAPI(widget, '');
if (scriptedError) {
return <Container error={t("widget.api_error")} />;
}
if (!scriptedData) {
return (
<Container service={service}>
{widget.fields?.map(field => (
<Block key={field} label={field} />
))}
</Container>
);
}
(scriptedData || []).forEach(e => {
if (e.type && e.value !== undefined) {
if (typeof e.type === 'object') {
e.typedValue = t(e.type.type, {...e.type, type: null, value: e.value});
} else if (e.type == 'boolean') {
e.typedValue = t(e.value ? 'scripted.yes' : 'scripted.no');
} else {
e.typedValue = t(e.type, { value: e.value });
}
} else {
e.typedValue = e.value;
}
})
return (
<div className="relative flex flex-row w-full">
{(scriptedData || []).map(e => (
<Block key={e.name} label={e.label} value={e.typedValue} />
))}
</div>
);
}

View File

@ -0,0 +1,36 @@
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
const logger = createLogger("scriptedProxyHandler");
const { execSync } = require('child_process')
export default async function scriptedProxyHandler(req, res, map) {
const { group, service, endpoint } = req.query;
if (group && service) {
const widget = await getServiceWidget(group, service);
let output;
try {
output = JSON.parse(execSync(widget.script).toString());
}
catch (err) {
return res.status(500).send('script failed: ' + err);
}
const fields = widget.fields || Object.keys(output) || [];
const labels = widget.field_labels || [];
const types = widget.field_types || [];
const result = fields.map(field => {
return {
name: field, label: labels[field] || field, value: output[field], type: types[field]
};
});
return res.status(200).json(result);
}
logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
return res.status(400).json({ error: "Invalid proxy service type" });
}

View File

@ -0,0 +1,7 @@
import scriptedProxyHandler from "./proxy";
const widget = {
proxyHandler: scriptedProxyHandler,
};
export default widget;

View File

@ -25,6 +25,7 @@ import radarr from "./radarr/widget";
import readarr from "./readarr/widget"; import readarr from "./readarr/widget";
import rutorrent from "./rutorrent/widget"; import rutorrent from "./rutorrent/widget";
import sabnzbd from "./sabnzbd/widget"; import sabnzbd from "./sabnzbd/widget";
import scripted from './scripted/widget';
import sonarr from "./sonarr/widget"; import sonarr from "./sonarr/widget";
import speedtest from "./speedtest/widget"; import speedtest from "./speedtest/widget";
import strelaysrv from "./strelaysrv/widget"; import strelaysrv from "./strelaysrv/widget";
@ -62,6 +63,7 @@ const widgets = {
readarr, readarr,
rutorrent, rutorrent,
sabnzbd, sabnzbd,
scripted,
sonarr, sonarr,
speedtest, speedtest,
strelaysrv, strelaysrv,