From 44f8e9d4da4d53e143735e22cc9f02047deca399 Mon Sep 17 00:00:00 2001 From: Mitchell <37925797+ping-localhost@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:45:05 +0200 Subject: [PATCH] Feature: Zabbix service widget (#3905) Co-Authored-By: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/authoring/proxies.md | 27 ++++++++++++++++- docs/widgets/services/index.md | 1 + docs/widgets/services/zabbix.md | 19 ++++++++++++ mkdocs.yml | 1 + public/locales/en/common.json | 6 ++++ src/utils/proxy/handlers/jsonrpc.js | 16 ++++++---- src/widgets/components.js | 1 + src/widgets/widgets.js | 2 ++ src/widgets/zabbix/component.jsx | 46 +++++++++++++++++++++++++++++ src/widgets/zabbix/widget.js | 23 +++++++++++++++ 10 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 docs/widgets/services/zabbix.md create mode 100644 src/widgets/zabbix/component.jsx create mode 100644 src/widgets/zabbix/widget.js diff --git a/docs/widgets/authoring/proxies.md b/docs/widgets/authoring/proxies.md index 220af4ad0..15cdb6703 100644 --- a/docs/widgets/authoring/proxies.md +++ b/docs/widgets/authoring/proxies.md @@ -79,7 +79,21 @@ By default the key is passed as an `X-API-Key` header. If you need to pass the k ### `jsonrpcProxyHandler` -A proxy handler that makes authenticated JSON-RPC requests to the specified API endpoint. Where the endpoint is the method to call. +A proxy handler that makes authenticated JSON-RPC requests to the specified API endpoint, either using username + password or an API token. +The endpoint is the method to call and queryParams are used as the parameters. + +=== "component.js" + + ```js + import Container from "components/services/widget/container"; + import useWidgetAPI from "utils/proxy/use-widget-api"; + + export default function Component({ service }) { + const { widget } = service; + + const { data, error } = useWidgetAPI(widget, 'trigger', { "triggerids": "14062", "output": "extend", "selectFunctions": "extend" }); + } + ``` === "widget.js" @@ -93,6 +107,7 @@ A proxy handler that makes authenticated JSON-RPC requests to the specified API mappings: { total: { endpoint: "total" }, average: { endpoint: "average" }, + trigger: { endpoint: "trigger.get" }, }, }; ``` @@ -110,6 +125,16 @@ A proxy handler that makes authenticated JSON-RPC requests to the specified API password: your-password ``` + ```yaml + - Your Widget: + icon: yourwidget.svg + href: https://example.com/ + widget: + type: yourwidget + url: http://127.0.0.1:1337 + key: your-api-token + ``` + ### `synologyProxyHandler` A proxy handler that makes authenticated requests to the specified Synology API endpoint. This is used exclusively for Synology DSM services. diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 61d11df23..35d0fdcf9 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -128,3 +128,4 @@ You can also find a list of all available service widgets in the sidebar navigat - [WGEasy](wgeasy.md) - [WhatsUpDocker](whatsupdocker.md) - [xTeVe](xteve.md) +- [Zabbix](zabbix.md) diff --git a/docs/widgets/services/zabbix.md b/docs/widgets/services/zabbix.md new file mode 100644 index 000000000..e7f18c416 --- /dev/null +++ b/docs/widgets/services/zabbix.md @@ -0,0 +1,19 @@ +--- +title: Zabbix +description: Zabbix Widget Configuration +--- + +Learn more about [Zabbix](https://github.com/zabbix/zabbix). + +See the [Zabbix documentation](https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/users/api_tokens) for details on generating API tokens. + +The widget supports (at least) Zibbax server version 7.0. + +Allowed fields: `["warning", "average", "high", "disaster"]`. + +```yaml +widget: + type: zabbix + url: http://zabbix.host.or.ip/zabbix + key: your-api-key +``` diff --git a/mkdocs.yml b/mkdocs.yml index a8096ad23..7c2d88e1a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -152,6 +152,7 @@ nav: - widgets/services/wgeasy.md - widgets/services/whatsupdocker.md - widgets/services/xteve.md + - widgets/services/zabbix.md - "Information Widgets": - widgets/info/index.md - widgets/info/datetime.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 786d98f0b..e54a0a776 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -931,5 +931,11 @@ "links": "Links", "collections": "Collections", "tags": "Tags" + }, + "zabbix": { + "warning": "Warning", + "average": "Average", + "high": "High", + "disaster": "Disaster" } } diff --git a/src/utils/proxy/handlers/jsonrpc.js b/src/utils/proxy/handlers/jsonrpc.js index b1b080fd0..41bbf866d 100644 --- a/src/utils/proxy/handlers/jsonrpc.js +++ b/src/utils/proxy/handlers/jsonrpc.js @@ -8,14 +8,18 @@ import widgets from "widgets/widgets"; const logger = createLogger("jsonrpcProxyHandler"); -export async function sendJsonRpcRequest(url, method, params, username, password) { +export async function sendJsonRpcRequest(url, method, params, widget) { const headers = { "content-type": "application/json", accept: "application/json", }; - if (username && password) { - headers.authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`; + if (widget.username && widget.password) { + headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; + } + + if (widget.key) { + headers.Authorization = `Bearer ${widget.key}`; } const client = new JSONRPCClient(async (rpcRequest) => { @@ -67,6 +71,9 @@ export default async function jsonrpcProxyHandler(req, res) { const widget = await getServiceWidget(group, service); const api = widgets?.[widget.type]?.api; + const [, mapping] = Object.entries(widgets?.[widget.type]?.mappings).find(([, value]) => value.endpoint === method); + const params = mapping?.params ?? null; + if (!api) { return res.status(403).json({ error: "Service does not support API calls" }); } @@ -74,8 +81,7 @@ export default async function jsonrpcProxyHandler(req, res) { if (widget) { const url = formatApiCall(api, { ...widget }); - // eslint-disable-next-line no-unused-vars - const [status, contentType, data] = await sendJsonRpcRequest(url, method, null, widget.username, widget.password); + const [status, , data] = await sendJsonRpcRequest(url, method, params, widget); return res.status(status).end(data); } } diff --git a/src/widgets/components.js b/src/widgets/components.js index 3d800d0ed..9f0218938 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -127,6 +127,7 @@ const components = { wgeasy: dynamic(() => import("./wgeasy/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")), xteve: dynamic(() => import("./xteve/component")), + zabbix: dynamic(() => import("./zabbix/component")), }; export default components; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index ed468e889..909fea844 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -119,6 +119,7 @@ import whatsupdocker from "./whatsupdocker/widget"; import xteve from "./xteve/widget"; import urbackup from "./urbackup/widget"; import romm from "./romm/widget"; +import zabbix from "./zabbix/widget"; const widgets = { adguard, @@ -245,6 +246,7 @@ const widgets = { wgeasy, whatsupdocker, xteve, + zabbix, }; export default widgets; diff --git a/src/widgets/zabbix/component.jsx b/src/widgets/zabbix/component.jsx new file mode 100644 index 000000000..620edb618 --- /dev/null +++ b/src/widgets/zabbix/component.jsx @@ -0,0 +1,46 @@ +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"; + +const PriorityWarning = "2"; +const PriorityAverage = "3"; +const PriorityHigh = "4"; +const PriorityDisaster = "5"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: zabbixData, error: zabbixError } = useWidgetAPI(widget, "trigger"); + + if (zabbixError) { + return ; + } + + if (!zabbixData) { + return ( + + + + + + + ); + } + + const warning = zabbixData.filter((item) => item.priority === PriorityWarning).length; + const average = zabbixData.filter((item) => item.priority === PriorityAverage).length; + const high = zabbixData.filter((item) => item.priority === PriorityHigh).length; + const disaster = zabbixData.filter((item) => item.priority === PriorityDisaster).length; + + return ( + + + + + + + ); +} diff --git a/src/widgets/zabbix/widget.js b/src/widgets/zabbix/widget.js new file mode 100644 index 000000000..537253595 --- /dev/null +++ b/src/widgets/zabbix/widget.js @@ -0,0 +1,23 @@ +import jsonrpcProxyHandler from "utils/proxy/handlers/jsonrpc"; + +const widget = { + api: "{url}/api_jsonrpc.php", + proxyHandler: jsonrpcProxyHandler, + + mappings: { + trigger: { + endpoint: "trigger.get", + params: { + output: ["triggerid", "description", "priority"], + filter: { + value: 1, + }, + sortfield: "priority", + sortorder: "DESC", + monitored: "true", + }, + }, + }, +}; + +export default widget;