diff --git a/docs/widgets/index.md b/docs/widgets/index.md index faa30975..4bd45af7 100644 --- a/docs/widgets/index.md +++ b/docs/widgets/index.md @@ -20,14 +20,12 @@ Service widgets are used to display the status of a service, often a web service server: localhost container: plex widgets: - - Tautulli: - type: tautulli - url: http://172.16.1.1:8181 - key: aabbccddeeffgghhiijjkkllmmnnoo - - UptimeKuma: - type: uptimekuma - url: http://172.16.1.2:8080 - slug: aaaaaaabbbbb + - type: tautulli + url: http://172.16.1.1:8181 + key: aabbccddeeffgghhiijjkkllmmnnoo + - type: uptimekuma + url: http://172.16.1.2:8080 + slug: aaaaaaabbbbb ``` ## Info Widgets diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 09e74425..54560d6f 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -155,7 +155,7 @@ export default function Item({ service, group, useEqualHeights }) { )} {service.widgets.map((widget) => ( - + ))} diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx index 628ff8c2..61a21a66 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -8,7 +8,7 @@ export default function Widget({ widget, service }) { const ServiceWidget = components[widget.type]; - const fullService = service; + const fullService = Object.apply({}, service); fullService.widget = widget; if (ServiceWidget) { return ( diff --git a/src/pages/api/services/proxy.js b/src/pages/api/services/proxy.js index e7439ced..3f8adc88 100644 --- a/src/pages/api/services/proxy.js +++ b/src/pages/api/services/proxy.js @@ -9,8 +9,8 @@ const logger = createLogger("servicesProxy"); export default async function handler(req, res) { try { - const { service, group, name } = req.query; - const serviceWidget = await getServiceWidget(group, service, name); + const { service, group, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); let type = serviceWidget?.type; // exceptions @@ -41,7 +41,7 @@ export default async function handler(req, res) { const endpoint = mapping?.endpoint; const endpointProxy = mapping?.proxyHandler || serviceProxyHandler; - if (mapping.method && mapping.method !== req.method) { + if (mapping?.method && mapping.method !== req.method) { logger.debug("Unsupported method: %s", req.method); return res.status(403).json({ error: "Unsupported method" }); } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 2d709065..e6ef6173 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -354,317 +354,317 @@ export function cleanServiceGroups(groups) { if (typeof cleanedService.weight !== "number") { cleanedService.weight = 0; } - cleanedService.widgets = cleanedService.widgets ? cleanedService.widgets : []; - if (cleanedService.widget != undefined) cleanedService.widgets.push(cleanedService.widget); - if (cleanedService.widgets != []) { - cleanedService.widgets = cleanedService.widgets.map((widget) => { - // whitelisted set of keys to pass to the frontend - // alphabetical, grouped by widget(s) - const { - // all widgets - fields, - hideErrors, - type, - - // azuredevops - repositoryId, - userEmail, - - // beszel - systemId, - - // calendar - firstDayInWeek, - integrations, - maxEvents, - showTime, - previousDays, - view, - timezone, - - // coinmarketcap - currency, - defaultinterval, - slugs, - symbols, - - // customapi - mappings, - display, - - // diskstation - volume, - - // docker - container, - server, - - // emby, jellyfin - enableBlocks, - enableNowPlaying, - - // emby, jellyfin, tautulli - enableUser, - expandOneStreamToTwoRows, - showEpisodeNumber, - - // frigate - enableRecentEvents, - - // glances, immich, mealie, pihole, pfsense - version, - - // glances - chart, - metric, - pointsLimit, - diskUnits, - - // glances, customapi, iframe, prometheusmetric - refreshInterval, - - // hdhomerun - tuner, - - // healthchecks - uuid, - - // iframe - allowFullscreen, - allowPolicy, - allowScrolling, - classes, - loadingStrategy, - referrerPolicy, - src, - - // kopia - snapshotHost, - snapshotPath, - - // kubernetes - app, - namespace, - podSelector, - - // lubelogger - vehicleID, - - // mjpeg - fit, - stream, - - // openmediavault - method, - - // openwrt - interfaceName, - - // opnsense, pfsense - wan, - - // prometheusmetric - metrics, - - // proxmox - node, - - // speedtest - bitratePrecision, - - // sonarr, radarr - enableQueue, - - // stocks - watchlist, - showUSMarketStatus, - - // truenas - enablePools, - nasType, - - // unifi - site, - - // vikunja - enableTaskList, - - // wgeasy - threshold, - - // technitium - range, - - // spoolman - spoolIds, - } = Object.keys(widget)[0] != "type" ? widget[Object.keys(widget)[0]] : widget; - - let fieldsList = fields; - if (typeof fields === "string") { - try { - fieldsList = JSON.parse(fields); - } catch (e) { - logger.error("Invalid fields list detected in config for service '%s'", service.name); - fieldsList = null; - } - } - - widget = { - type, - fields: fieldsList || null, - hide_errors: hideErrors || false, - service_name: service.name, - service_group: serviceGroup.name, - widget_name: Object.keys(widget)[0] != "type" ? Object.keys(widget)[0] : "", - }; - - if (type === "azuredevops") { - if (userEmail) widget.userEmail = userEmail; - if (repositoryId) widget.repositoryId = repositoryId; - } - - if (type === "beszel") { - if (systemId) widget.systemId = systemId; - } - - if (type === "coinmarketcap") { - if (currency) widget.currency = currency; - if (symbols) widget.symbols = symbols; - if (slugs) widget.slugs = slugs; - if (defaultinterval) widget.defaultinterval = defaultinterval; - } - - if (type === "docker") { - if (server) widget.server = server; - if (container) widget.container = container; - } - if (type === "unifi") { - if (site) widget.site = site; - } - if (type === "proxmox") { - if (node) widget.node = node; - } - if (type === "kubernetes") { - if (namespace) widget.namespace = namespace; - if (app) widget.app = app; - if (podSelector) widget.podSelector = podSelector; - } - if (type === "iframe") { - if (src) widget.src = src; - if (classes) widget.classes = classes; - if (referrerPolicy) widget.referrerPolicy = referrerPolicy; - if (allowPolicy) widget.allowPolicy = allowPolicy; - if (allowFullscreen) widget.allowFullscreen = allowFullscreen; - if (loadingStrategy) widget.loadingStrategy = loadingStrategy; - if (allowScrolling) widget.allowScrolling = allowScrolling; - if (refreshInterval) widget.refreshInterval = refreshInterval; - } - if (["opnsense", "pfsense"].includes(type)) { - if (wan) widget.wan = wan; - } - if (["emby", "jellyfin"].includes(type)) { - if (enableBlocks !== undefined) widget.enableBlocks = JSON.parse(enableBlocks); - if (enableNowPlaying !== undefined) widget.enableNowPlaying = JSON.parse(enableNowPlaying); - } - if (["emby", "jellyfin", "tautulli"].includes(type)) { - if (expandOneStreamToTwoRows !== undefined) - widget.expandOneStreamToTwoRows = !!JSON.parse(expandOneStreamToTwoRows); - if (showEpisodeNumber !== undefined) widget.showEpisodeNumber = !!JSON.parse(showEpisodeNumber); - if (enableUser !== undefined) widget.enableUser = !!JSON.parse(enableUser); - } - if (["sonarr", "radarr"].includes(type)) { - if (enableQueue !== undefined) widget.enableQueue = JSON.parse(enableQueue); - } - if (type === "truenas") { - if (enablePools !== undefined) widget.enablePools = JSON.parse(enablePools); - if (nasType !== undefined) widget.nasType = nasType; - } - if (["diskstation", "qnap"].includes(type)) { - if (volume) widget.volume = volume; - } - if (type === "kopia") { - if (snapshotHost) widget.snapshotHost = snapshotHost; - if (snapshotPath) widget.snapshotPath = snapshotPath; - } - if (["glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) { - if (version) widget.version = parseInt(version, 10); - } - if (type === "glances") { - if (metric) widget.metric = metric; - if (chart !== undefined) { - widget.chart = chart; - } else { - widget.chart = true; - } - if (refreshInterval) widget.refreshInterval = refreshInterval; - if (pointsLimit) widget.pointsLimit = pointsLimit; - if (diskUnits) widget.diskUnits = diskUnits; - } - if (type === "mjpeg") { - if (stream) widget.stream = stream; - if (fit) widget.fit = fit; - } - if (type === "openmediavault") { - if (method) widget.method = method; - } - if (type === "openwrt") { - if (interfaceName) widget.interfaceName = interfaceName; - } - if (type === "customapi") { - if (mappings) widget.mappings = mappings; - if (display) widget.display = display; - if (refreshInterval) widget.refreshInterval = refreshInterval; - } - if (type === "calendar") { - if (integrations) widget.integrations = integrations; - if (firstDayInWeek) widget.firstDayInWeek = firstDayInWeek; - if (view) widget.view = view; - if (maxEvents) widget.maxEvents = maxEvents; - if (previousDays) widget.previousDays = previousDays; - if (showTime) widget.showTime = showTime; - if (timezone) widget.timezone = timezone; - } - if (type === "hdhomerun") { - if (tuner !== undefined) widget.tuner = tuner; - } - if (type === "healthchecks") { - if (uuid !== undefined) widget.uuid = uuid; - } - if (type === "speedtest") { - if (bitratePrecision !== undefined) { - widget.bitratePrecision = parseInt(bitratePrecision, 10); - } - } - if (type === "stocks") { - if (watchlist) widget.watchlist = watchlist; - if (showUSMarketStatus) widget.showUSMarketStatus = showUSMarketStatus; - } - if (type === "wgeasy") { - if (threshold !== undefined) widget.threshold = parseInt(threshold, 10); - } - if (type === "frigate") { - if (enableRecentEvents !== undefined) widget.enableRecentEvents = enableRecentEvents; - } - if (type === "technitium") { - if (range !== undefined) widget.range = range; - } - if (type === "lubelogger") { - if (vehicleID !== undefined) widget.vehicleID = parseInt(vehicleID, 10); - } - if (type === "vikunja") { - if (enableTaskList !== undefined) widget.enableTaskList = !!enableTaskList; - } - if (type === "prometheusmetric") { - if (metrics) widget.metrics = metrics; - if (refreshInterval) widget.refreshInterval = refreshInterval; - } - if (type === "spoolman") { - if (spoolIds !== undefined) widget.spoolIds = spoolIds; - } - return widget; - }); + if (!cleanedService.widgets) cleanedService.widgets = []; + if (cleanedService.widget) { + cleanedService.widgets.push(cleanedService.widget); + delete cleanedService.widget; } - cleanedService.widget = null; + cleanedService.widgets = cleanedService.widgets.map((widgetData, index) => { + // whitelisted set of keys to pass to the frontend + // alphabetical, grouped by widget(s) + const { + // all widgets + fields, + hideErrors, + type, + + // azuredevops + repositoryId, + userEmail, + + // beszel + systemId, + + // calendar + firstDayInWeek, + integrations, + maxEvents, + showTime, + previousDays, + view, + timezone, + + // coinmarketcap + currency, + defaultinterval, + slugs, + symbols, + + // customapi + mappings, + display, + + // diskstation + volume, + + // docker + container, + server, + + // emby, jellyfin + enableBlocks, + enableNowPlaying, + + // emby, jellyfin, tautulli + enableUser, + expandOneStreamToTwoRows, + showEpisodeNumber, + + // frigate + enableRecentEvents, + + // glances, immich, mealie, pihole, pfsense + version, + + // glances + chart, + metric, + pointsLimit, + diskUnits, + + // glances, customapi, iframe, prometheusmetric + refreshInterval, + + // hdhomerun + tuner, + + // healthchecks + uuid, + + // iframe + allowFullscreen, + allowPolicy, + allowScrolling, + classes, + loadingStrategy, + referrerPolicy, + src, + + // kopia + snapshotHost, + snapshotPath, + + // kubernetes + app, + namespace, + podSelector, + + // lubelogger + vehicleID, + + // mjpeg + fit, + stream, + + // openmediavault + method, + + // openwrt + interfaceName, + + // opnsense, pfsense + wan, + + // prometheusmetric + metrics, + + // proxmox + node, + + // speedtest + bitratePrecision, + + // sonarr, radarr + enableQueue, + + // stocks + watchlist, + showUSMarketStatus, + + // truenas + enablePools, + nasType, + + // unifi + site, + + // vikunja + enableTaskList, + + // wgeasy + threshold, + + // technitium + range, + + // spoolman + spoolIds, + } = widgetData; + + let fieldsList = fields; + if (typeof fields === "string") { + try { + fieldsList = JSON.parse(fields); + } catch (e) { + logger.error("Invalid fields list detected in config for service '%s'", service.name); + fieldsList = null; + } + } + + const widget = { + type, + fields: fieldsList || null, + hide_errors: hideErrors || false, + service_name: service.name, + service_group: serviceGroup.name, + index, + }; + + if (type === "azuredevops") { + if (userEmail) widget.userEmail = userEmail; + if (repositoryId) widget.repositoryId = repositoryId; + } + + if (type === "beszel") { + if (systemId) widget.systemId = systemId; + } + + if (type === "coinmarketcap") { + if (currency) widget.currency = currency; + if (symbols) widget.symbols = symbols; + if (slugs) widget.slugs = slugs; + if (defaultinterval) widget.defaultinterval = defaultinterval; + } + + if (type === "docker") { + if (server) widget.server = server; + if (container) widget.container = container; + } + if (type === "unifi") { + if (site) widget.site = site; + } + if (type === "proxmox") { + if (node) widget.node = node; + } + if (type === "kubernetes") { + if (namespace) widget.namespace = namespace; + if (app) widget.app = app; + if (podSelector) widget.podSelector = podSelector; + } + if (type === "iframe") { + if (src) widget.src = src; + if (classes) widget.classes = classes; + if (referrerPolicy) widget.referrerPolicy = referrerPolicy; + if (allowPolicy) widget.allowPolicy = allowPolicy; + if (allowFullscreen) widget.allowFullscreen = allowFullscreen; + if (loadingStrategy) widget.loadingStrategy = loadingStrategy; + if (allowScrolling) widget.allowScrolling = allowScrolling; + if (refreshInterval) widget.refreshInterval = refreshInterval; + } + if (["opnsense", "pfsense"].includes(type)) { + if (wan) widget.wan = wan; + } + if (["emby", "jellyfin"].includes(type)) { + if (enableBlocks !== undefined) widget.enableBlocks = JSON.parse(enableBlocks); + if (enableNowPlaying !== undefined) widget.enableNowPlaying = JSON.parse(enableNowPlaying); + } + if (["emby", "jellyfin", "tautulli"].includes(type)) { + if (expandOneStreamToTwoRows !== undefined) + widget.expandOneStreamToTwoRows = !!JSON.parse(expandOneStreamToTwoRows); + if (showEpisodeNumber !== undefined) widget.showEpisodeNumber = !!JSON.parse(showEpisodeNumber); + if (enableUser !== undefined) widget.enableUser = !!JSON.parse(enableUser); + } + if (["sonarr", "radarr"].includes(type)) { + if (enableQueue !== undefined) widget.enableQueue = JSON.parse(enableQueue); + } + if (type === "truenas") { + if (enablePools !== undefined) widget.enablePools = JSON.parse(enablePools); + if (nasType !== undefined) widget.nasType = nasType; + } + if (["diskstation", "qnap"].includes(type)) { + if (volume) widget.volume = volume; + } + if (type === "kopia") { + if (snapshotHost) widget.snapshotHost = snapshotHost; + if (snapshotPath) widget.snapshotPath = snapshotPath; + } + if (["glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) { + if (version) widget.version = parseInt(version, 10); + } + if (type === "glances") { + if (metric) widget.metric = metric; + if (chart !== undefined) { + widget.chart = chart; + } else { + widget.chart = true; + } + if (refreshInterval) widget.refreshInterval = refreshInterval; + if (pointsLimit) widget.pointsLimit = pointsLimit; + if (diskUnits) widget.diskUnits = diskUnits; + } + if (type === "mjpeg") { + if (stream) widget.stream = stream; + if (fit) widget.fit = fit; + } + if (type === "openmediavault") { + if (method) widget.method = method; + } + if (type === "openwrt") { + if (interfaceName) widget.interfaceName = interfaceName; + } + if (type === "customapi") { + if (mappings) widget.mappings = mappings; + if (display) widget.display = display; + if (refreshInterval) widget.refreshInterval = refreshInterval; + } + if (type === "calendar") { + if (integrations) widget.integrations = integrations; + if (firstDayInWeek) widget.firstDayInWeek = firstDayInWeek; + if (view) widget.view = view; + if (maxEvents) widget.maxEvents = maxEvents; + if (previousDays) widget.previousDays = previousDays; + if (showTime) widget.showTime = showTime; + if (timezone) widget.timezone = timezone; + } + if (type === "hdhomerun") { + if (tuner !== undefined) widget.tuner = tuner; + } + if (type === "healthchecks") { + if (uuid !== undefined) widget.uuid = uuid; + } + if (type === "speedtest") { + if (bitratePrecision !== undefined) { + widget.bitratePrecision = parseInt(bitratePrecision, 10); + } + } + if (type === "stocks") { + if (watchlist) widget.watchlist = watchlist; + if (showUSMarketStatus) widget.showUSMarketStatus = showUSMarketStatus; + } + if (type === "wgeasy") { + if (threshold !== undefined) widget.threshold = parseInt(threshold, 10); + } + if (type === "frigate") { + if (enableRecentEvents !== undefined) widget.enableRecentEvents = enableRecentEvents; + } + if (type === "technitium") { + if (range !== undefined) widget.range = range; + } + if (type === "lubelogger") { + if (vehicleID !== undefined) widget.vehicleID = parseInt(vehicleID, 10); + } + if (type === "vikunja") { + if (enableTaskList !== undefined) widget.enableTaskList = !!enableTaskList; + } + if (type === "prometheusmetric") { + if (metrics) widget.metrics = metrics; + if (refreshInterval) widget.refreshInterval = refreshInterval; + } + if (type === "spoolman") { + if (spoolIds !== undefined) widget.spoolIds = spoolIds; + } + return widget; + }); return cleanedService; }), })); @@ -697,15 +697,11 @@ export async function getServiceItem(group, service) { return false; } -export default async function getServiceWidget(group, service, name = null) { +export default async function getServiceWidget(group, service, index) { const serviceItem = await getServiceItem(group, service); - if (serviceItem && (name == null || name === "undefined")) { - const { widget } = serviceItem; - return widget; - } - if (serviceItem && name != null && name !== "undefined") { - const { widgets } = serviceItem; - return widgets.filter((widget) => Object.keys(widget)[0] == name)[0][name]; + if (serviceItem) { + const { widget, widgets } = serviceItem; + return index > -1 && widgets ? widgets[index] : widget; } return false; } diff --git a/src/utils/proxy/api-helpers.js b/src/utils/proxy/api-helpers.js index c762d0ba..a02ea623 100644 --- a/src/utils/proxy/api-helpers.js +++ b/src/utils/proxy/api-helpers.js @@ -12,7 +12,7 @@ export function getURLSearchParams(widget, endpoint) { const params = new URLSearchParams({ group: widget.service_group, service: widget.service_name, - ...(widget.widget_name != "" && { name: widget.widget_name }), + index: widget.index, }); if (endpoint) { params.append("endpoint", endpoint); diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 416e38fc..cea95196 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -9,10 +9,10 @@ import widgets from "widgets/widgets"; const logger = createLogger("credentialedProxyHandler"); export default async function credentialedProxyHandler(req, res, map) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index 7dc7fb53..2e788a98 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -8,10 +8,10 @@ import widgets from "widgets/widgets"; const logger = createLogger("genericProxyHandler"); export default async function genericProxyHandler(req, res, map) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/utils/proxy/handlers/jsonrpc.js b/src/utils/proxy/handlers/jsonrpc.js index e95264bd..f9fb1883 100644 --- a/src/utils/proxy/handlers/jsonrpc.js +++ b/src/utils/proxy/handlers/jsonrpc.js @@ -65,10 +65,10 @@ export async function sendJsonRpcRequest(url, method, params, widget) { } export default async function jsonrpcProxyHandler(req, res) { - const { group, service, endpoint: method, name } = req.query; + const { group, service, endpoint: method, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); const api = widgets?.[widget.type]?.api; const [, mapping] = Object.entries(widgets?.[widget.type]?.mappings).find(([, value]) => value.endpoint === method); diff --git a/src/utils/proxy/handlers/synology.js b/src/utils/proxy/handlers/synology.js index e711db7e..030e53ba 100644 --- a/src/utils/proxy/handlers/synology.js +++ b/src/utils/proxy/handlers/synology.js @@ -131,13 +131,13 @@ function toError(url, synologyError) { } export default async function synologyProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const serviceWidget = await getServiceWidget(group, service, name ? name : null); + const serviceWidget = await getServiceWidget(group, service, index); const widget = widgets?.[serviceWidget.type]; const mapping = widget?.mappings?.[endpoint]; if (!widget.api || !mapping) { diff --git a/src/widgets/audiobookshelf/proxy.js b/src/widgets/audiobookshelf/proxy.js index 3c40ffd1..1a89736b 100644 --- a/src/widgets/audiobookshelf/proxy.js +++ b/src/widgets/audiobookshelf/proxy.js @@ -23,14 +23,14 @@ async function retrieveFromAPI(url, key) { } export default async function audiobookshelfProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/beszel/proxy.js b/src/widgets/beszel/proxy.js index f25e6384..61bc969b 100644 --- a/src/widgets/beszel/proxy.js +++ b/src/widgets/beszel/proxy.js @@ -34,10 +34,10 @@ async function login(loginUrl, username, password, service) { } export default async function beszelProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/widgets/calendar/proxy.js b/src/widgets/calendar/proxy.js index 568edb65..d36f30c9 100644 --- a/src/widgets/calendar/proxy.js +++ b/src/widgets/calendar/proxy.js @@ -5,10 +5,10 @@ import createLogger from "utils/logger"; const logger = createLogger("calendarProxyHandler"); export default async function calendarProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); const integration = widget.integrations?.find((i) => i.name === endpoint); if (integration) { diff --git a/src/widgets/crowdsec/proxy.js b/src/widgets/crowdsec/proxy.js index cc481064..85803845 100644 --- a/src/widgets/crowdsec/proxy.js +++ b/src/widgets/crowdsec/proxy.js @@ -35,14 +35,14 @@ async function login(widget, service) { } export default async function crowdsecProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.error("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget || !widgets[widget.type].api) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/deluge/proxy.js b/src/widgets/deluge/proxy.js index aa4ccb05..61329697 100644 --- a/src/widgets/deluge/proxy.js +++ b/src/widgets/deluge/proxy.js @@ -40,14 +40,14 @@ function login(url, password) { } export default async function delugeProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/flood/proxy.js b/src/widgets/flood/proxy.js index d0e34430..e0c10173 100644 --- a/src/widgets/flood/proxy.js +++ b/src/widgets/flood/proxy.js @@ -28,14 +28,14 @@ async function login(widget) { } export default async function floodProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/freshrss/proxy.js b/src/widgets/freshrss/proxy.js index 5ce98f34..881094bd 100644 --- a/src/widgets/freshrss/proxy.js +++ b/src/widgets/freshrss/proxy.js @@ -74,14 +74,14 @@ async function apiCall(widget, endpoint, service) { } export default async function freshrssProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/fritzbox/proxy.js b/src/widgets/fritzbox/proxy.js index 3599229b..d1a66d97 100644 --- a/src/widgets/fritzbox/proxy.js +++ b/src/widgets/fritzbox/proxy.js @@ -46,8 +46,8 @@ async function requestEndpoint(apiBaseUrl, service, action) { } export default async function fritzboxProxyHandler(req, res) { - const { group, service, name } = req.query; - const serviceWidget = await getServiceWidget(group, service, name ? name : null); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); if (!serviceWidget) { res.status(500).json({ error: { message: "Service widget not found" } }); diff --git a/src/widgets/gamedig/proxy.js b/src/widgets/gamedig/proxy.js index 819cd13d..ecf6e4c6 100644 --- a/src/widgets/gamedig/proxy.js +++ b/src/widgets/gamedig/proxy.js @@ -7,8 +7,8 @@ const proxyName = "gamedigProxyHandler"; const logger = createLogger(proxyName); export default async function gamedigProxyHandler(req, res) { - const { group, service, name } = req.query; - const serviceWidget = await getServiceWidget(group, service, name ? name : null); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const url = new URL(serviceWidget.url); try { diff --git a/src/widgets/homeassistant/proxy.js b/src/widgets/homeassistant/proxy.js index 7719b13e..e1f02ddb 100644 --- a/src/widgets/homeassistant/proxy.js +++ b/src/widgets/homeassistant/proxy.js @@ -62,14 +62,14 @@ async function getQuery(query, { url, key }) { } export default async function homeassistantProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/homebox/proxy.js b/src/widgets/homebox/proxy.js index 0f6d6e7e..c91ce552 100644 --- a/src/widgets/homebox/proxy.js +++ b/src/widgets/homebox/proxy.js @@ -68,14 +68,14 @@ async function apiCall(widget, endpoint, service) { } export default async function homeboxProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/homebridge/proxy.js b/src/widgets/homebridge/proxy.js index 0985b269..4da9197b 100644 --- a/src/widgets/homebridge/proxy.js +++ b/src/widgets/homebridge/proxy.js @@ -71,14 +71,14 @@ async function apiCall(widget, endpoint, service) { } export default async function homebridgeProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/jackett/proxy.js b/src/widgets/jackett/proxy.js index 4c3b02fb..035309b3 100644 --- a/src/widgets/jackett/proxy.js +++ b/src/widgets/jackett/proxy.js @@ -25,14 +25,14 @@ async function fetchJackettCookie(widget, loginURL) { } export default async function jackettProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.error("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget || !widgets[widget.type].api) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/jdownloader/proxy.js b/src/widgets/jdownloader/proxy.js index 77a7a65f..ae8c845c 100644 --- a/src/widgets/jdownloader/proxy.js +++ b/src/widgets/jdownloader/proxy.js @@ -12,12 +12,12 @@ const proxyName = "jdownloaderProxyHandler"; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return null; diff --git a/src/widgets/kavita/proxy.js b/src/widgets/kavita/proxy.js index ef991037..1c41c45f 100644 --- a/src/widgets/kavita/proxy.js +++ b/src/widgets/kavita/proxy.js @@ -70,14 +70,14 @@ async function apiCall(widget, endpoint, service) { } export default async function KavitaProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/minecraft/proxy.js b/src/widgets/minecraft/proxy.js index 17d845ef..98d1be88 100644 --- a/src/widgets/minecraft/proxy.js +++ b/src/widgets/minecraft/proxy.js @@ -7,8 +7,8 @@ const proxyName = "minecraftProxyHandler"; const logger = createLogger(proxyName); export default async function minecraftProxyHandler(req, res) { - const { group, service, name } = req.query; - const serviceWidget = await getServiceWidget(group, service, name ? name : null); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const url = new URL(serviceWidget.url); try { const pingResponse = await pingWithPromise(url.hostname, url.port || 25565); diff --git a/src/widgets/npm/proxy.js b/src/widgets/npm/proxy.js index d672b35f..6c7ba09e 100644 --- a/src/widgets/npm/proxy.js +++ b/src/widgets/npm/proxy.js @@ -36,10 +36,10 @@ async function login(loginUrl, username, password, service) { } export default async function npmProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index 5d9110d9..f4da1293 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -33,10 +33,10 @@ async function login(loginUrl, username, password, controllerVersionMajor) { } export default async function omadaProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (widget) { const { url } = widget; diff --git a/src/widgets/openmediavault/proxy.js b/src/widgets/openmediavault/proxy.js index 9463ffce..9cda42e8 100644 --- a/src/widgets/openmediavault/proxy.js +++ b/src/widgets/openmediavault/proxy.js @@ -12,14 +12,14 @@ const BG_POLL_PERIOD = 500; const logger = createLogger(PROXY_NAME); async function getWidget(req) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/openwrt/proxy.js b/src/widgets/openwrt/proxy.js index 197b4396..0a0da3ff 100644 --- a/src/widgets/openwrt/proxy.js +++ b/src/widgets/openwrt/proxy.js @@ -17,14 +17,14 @@ const PARAMS = { }; async function getWidget(req) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/photoprism/proxy.js b/src/widgets/photoprism/proxy.js index 1d849e4b..fe5096b3 100644 --- a/src/widgets/photoprism/proxy.js +++ b/src/widgets/photoprism/proxy.js @@ -6,14 +6,14 @@ import createLogger from "utils/logger"; const logger = createLogger("photoprismProxyHandler"); export default async function photoprismProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/pihole/proxy.js b/src/widgets/pihole/proxy.js index bf117235..bf24624d 100644 --- a/src/widgets/pihole/proxy.js +++ b/src/widgets/pihole/proxy.js @@ -33,7 +33,7 @@ async function login(widget, service) { } export default async function piholeProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; let endpoint = "stats/summary"; if (!group || !service) { @@ -41,7 +41,7 @@ export default async function piholeProxyHandler(req, res) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/plex/proxy.js b/src/widgets/plex/proxy.js index e93e8709..2956f280 100644 --- a/src/widgets/plex/proxy.js +++ b/src/widgets/plex/proxy.js @@ -16,14 +16,14 @@ const tvCacheKey = `${proxyName}__tv`; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/pyload/proxy.js b/src/widgets/pyload/proxy.js index 23809ebe..a380c865 100644 --- a/src/widgets/pyload/proxy.js +++ b/src/widgets/pyload/proxy.js @@ -67,11 +67,11 @@ async function login(loginUrl, service, username, password = "") { } export default async function pyloadProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; try { if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (widget) { const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); diff --git a/src/widgets/qbittorrent/proxy.js b/src/widgets/qbittorrent/proxy.js index 36ebe419..aead7582 100644 --- a/src/widgets/qbittorrent/proxy.js +++ b/src/widgets/qbittorrent/proxy.js @@ -21,14 +21,14 @@ async function login(widget) { } export default async function qbittorrentProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/qnap/proxy.js b/src/widgets/qnap/proxy.js index 4e647487..07917d28 100644 --- a/src/widgets/qnap/proxy.js +++ b/src/widgets/qnap/proxy.js @@ -77,14 +77,14 @@ async function apiCall(widget, endpoint, service) { } export default async function qnapProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/rutorrent/proxy.js b/src/widgets/rutorrent/proxy.js index d371d2ba..e0ae44fe 100644 --- a/src/widgets/rutorrent/proxy.js +++ b/src/widgets/rutorrent/proxy.js @@ -45,10 +45,10 @@ const getTorrentInfo = (data) => ({ }); export default async function rutorrentProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (widget) { const api = widgets?.[widget.type]?.api; diff --git a/src/widgets/suwayomi/proxy.js b/src/widgets/suwayomi/proxy.js index 082a5d6a..def811cc 100644 --- a/src/widgets/suwayomi/proxy.js +++ b/src/widgets/suwayomi/proxy.js @@ -114,14 +114,14 @@ function extractCounts(responseJSON, fields) { } export default async function suwayomiProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/tdarr/proxy.js b/src/widgets/tdarr/proxy.js index 04b5c04d..88da30fd 100644 --- a/src/widgets/tdarr/proxy.js +++ b/src/widgets/tdarr/proxy.js @@ -8,14 +8,14 @@ const proxyName = "tdarrProxyHandler"; const logger = createLogger(proxyName); export default async function tdarrProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/transmission/proxy.js b/src/widgets/transmission/proxy.js index ce8d138a..8b8049bc 100644 --- a/src/widgets/transmission/proxy.js +++ b/src/widgets/transmission/proxy.js @@ -11,14 +11,14 @@ const headerCacheKey = `${proxyName}__headers`; const logger = createLogger(proxyName); export default async function transmissionProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index bef6f340..559065e3 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -14,13 +14,13 @@ const prefixCacheKey = `${proxyName}__prefix`; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; let widget = null; if (group === "unifi_console" && service === "unifi_console") { // info widget - const index = req.query?.query ? JSON.parse(req.query.query).index : undefined; - widget = await getPrivateWidgetOptions("unifi_console", index); + const infowidgetIndex = req.query?.query ? JSON.parse(req.query.query).index : undefined; + widget = await getPrivateWidgetOptions("unifi_console", infowidgetIndex); if (!widget) { logger.debug("Error retrieving settings for this Unifi widget"); return null; @@ -32,7 +32,7 @@ async function getWidget(req) { return null; } - widget = await getServiceWidget(group, service, name ? name : null); + widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/urbackup/proxy.js b/src/widgets/urbackup/proxy.js index 09e8814a..4e7a0a8d 100644 --- a/src/widgets/urbackup/proxy.js +++ b/src/widgets/urbackup/proxy.js @@ -3,8 +3,8 @@ import { UrbackupServer } from "urbackup-server-api"; import getServiceWidget from "utils/config/service-helpers"; export default async function urbackupProxyHandler(req, res) { - const { group, service, name } = req.query; - const serviceWidget = await getServiceWidget(group, service, name ? name : null); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const server = new UrbackupServer({ url: serviceWidget.url, diff --git a/src/widgets/watchtower/proxy.js b/src/widgets/watchtower/proxy.js index 71b787f7..588d08ee 100644 --- a/src/widgets/watchtower/proxy.js +++ b/src/widgets/watchtower/proxy.js @@ -8,14 +8,14 @@ const proxyName = "watchtowerProxyHandler"; const logger = createLogger(proxyName); export default async function watchtowerProxyHandler(req, res) { - const { group, service, endpoint, name } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/xteve/proxy.js b/src/widgets/xteve/proxy.js index 73487b98..453e3645 100644 --- a/src/widgets/xteve/proxy.js +++ b/src/widgets/xteve/proxy.js @@ -7,13 +7,13 @@ import getServiceWidget from "utils/config/service-helpers"; const logger = createLogger("xteveProxyHandler"); export default async function xteveProxyHandler(req, res) { - const { group, service, name } = req.query; + const { group, service, index } = req.query; if (!group || !service) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service, name ? name : null); + const widget = await getServiceWidget(group, service, index); const api = widgets?.[widget.type]?.api; if (!api) { return res.status(403).json({ error: "Service does not support API calls" });