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:
parent
c14ae6ee72
commit
40de43b2da
@ -298,5 +298,9 @@
|
|||||||
"rejectedPushes": "Rejected",
|
"rejectedPushes": "Rejected",
|
||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"indexers": "Indexers"
|
"indexers": "Indexers"
|
||||||
|
},
|
||||||
|
"scripted": {
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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")),
|
||||||
|
|||||||
50
src/widgets/scripted/README.md
Normal file
50
src/widgets/scripted/README.md
Normal 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.
|
||||||
49
src/widgets/scripted/component.jsx
Normal file
49
src/widgets/scripted/component.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
src/widgets/scripted/proxy.js
Normal file
36
src/widgets/scripted/proxy.js
Normal 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" });
|
||||||
|
}
|
||||||
7
src/widgets/scripted/widget.js
Normal file
7
src/widgets/scripted/widget.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import scriptedProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
proxyHandler: scriptedProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user