From aa7cfa58ffd8517d9762079cf2595c0ee31c1663 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 31 Dec 2023 10:48:10 -0800 Subject: [PATCH 01/11] Better handle malformed docker labels (#2552) --- src/utils/config/service-helpers.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index f96f6509..1c5ee894 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -102,6 +102,16 @@ export async function servicesFromDocker() { } }); + if (!constructedService.name || !constructedService.group) { + logger.error( + `Error constructing service using homepage labels for container '${containerName.replace( + /^\//, + "", + )}'. Ensure required labels are present.`, + ); + return null; + } + return constructedService; }); From 1c47d9d70ed61ff9a6000abbb726f51ab5be2508 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 31 Dec 2023 20:18:17 -0800 Subject: [PATCH 02/11] Fix: pass user/pass as strings with OMV proxy (#2555) --- src/widgets/openmediavault/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/openmediavault/proxy.js b/src/widgets/openmediavault/proxy.js index 2152f305..e1f97a56 100644 --- a/src/widgets/openmediavault/proxy.js +++ b/src/widgets/openmediavault/proxy.js @@ -64,7 +64,7 @@ async function tryLogin(widget) { const resp = await rpc(url, { method: "login", service: "session", - params: { username, password }, + params: { username: username.toString(), password: password.toString() }, }); if (resp.status !== 200) { From 50c989e36ab06b66780da7923bc68bba45b091b9 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 6 Jan 2024 09:22:25 -0800 Subject: [PATCH 03/11] Fix: unique element key generation in quicklaunch and services (#2586) --- src/components/quicklaunch.jsx | 2 +- src/components/services/list.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index a356fdee..7fb1460a 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -199,7 +199,7 @@ export default function QuickLaunch({ {results.length > 0 && ( <ul className="max-h-[60vh] overflow-y-auto m-2"> {results.map((r, i) => ( - <li key={r.container ?? r.app ?? `${r.name}-${r.href}`}> + <li key={[r.name, r.container, r.app, r.href].filter((s) => s).join("-")}> <button type="button" data-index={i} diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx index 85436178..f3fd6e2a 100644 --- a/src/components/services/list.jsx +++ b/src/components/services/list.jsx @@ -14,7 +14,7 @@ export default function List({ group, services, layout, useEqualHeights }) { > {services.map((service) => ( <Item - key={service.container ?? service.app ?? service.name} + key={[service.container, service.app, service.name].filter((s) => s).join("-")} service={service} group={group} useEqualHeights={layout?.useEqualHeights ?? useEqualHeights} From 1103df2b64d0d9134dca29e16aa79875094f9785 Mon Sep 17 00:00:00 2001 From: Metin Yazici <me@strboul.com> Date: Sun, 7 Jan 2024 18:17:07 +0100 Subject: [PATCH 04/11] Feature: support multiple checks for healthchecks widget (#2580) * Change healthchecks online/offline with the original up/down * Add group statistics to healthcheck widget * Update healthchecks docs --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/healthchecks.md | 11 ++++--- public/locales/en/common.json | 4 +-- src/utils/config/service-helpers.js | 6 ++++ src/widgets/healthchecks/component.jsx | 40 ++++++++++++++++++++++---- src/widgets/healthchecks/widget.js | 3 +- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/docs/widgets/services/healthchecks.md b/docs/widgets/services/healthchecks.md index ae8f1e26..b438e153 100644 --- a/docs/widgets/services/healthchecks.md +++ b/docs/widgets/services/healthchecks.md @@ -3,18 +3,21 @@ title: Health checks description: Health checks Widget Configuration --- -To use the Health Checks widget, you first need to generate an API key. To do this, follow these steps: +Specify a single check by including the `uuid` field or show the total 'up' and 'down' for all +checks by leaving off the `uuid` field. -1. Go to Settings in your check dashboard. +To use the Health Checks widget, you first need to generate an API key. + +1. In your project, go to project Settings on the navigation bar. 2. Click on API key (read-only) and then click _Create_. 3. Copy the API key that is generated for you. -Allowed fields: `["status", "last_ping"]`. +Allowed fields: `["status", "last_ping"]` for single checks, `["up", "down"]` for total stats. ```yaml widget: type: healthchecks url: http://healthchecks.host.or.ip:port key: <YOUR_API_KEY> - uuid: <YOUR_CHECK_UUID> + uuid: <CHECK_UUID> # optional, if not included total statistics for all checks is shown ``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index aa4ef1e5..bf39f1d9 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -491,9 +491,9 @@ }, "healthchecks": { "new": "New", - "up": "Online", + "up": "Up", "grace": "In Grace Period", - "down": "Offline", + "down": "Down", "paused": "Paused", "status": "Status", "last_ping": "Last Ping", diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 1c5ee894..d73c1b5a 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -397,6 +397,9 @@ export function cleanServiceGroups(groups) { // glances, customapi, iframe refreshInterval, + // healthchecks + uuid, + // iframe allowFullscreen, allowPolicy, @@ -536,6 +539,9 @@ export function cleanServiceGroups(groups) { if (previousDays) cleanedService.widget.previousDays = previousDays; if (showTime) cleanedService.widget.showTime = showTime; } + if (type === "healthchecks") { + if (uuid !== undefined) cleanedService.widget.uuid = uuid; + } } return cleanedService; diff --git a/src/widgets/healthchecks/component.jsx b/src/widgets/healthchecks/component.jsx index 12ec726e..bcb8d740 100644 --- a/src/widgets/healthchecks/component.jsx +++ b/src/widgets/healthchecks/component.jsx @@ -27,6 +27,23 @@ function formatDate(dateString) { return new Intl.DateTimeFormat(i18n.language, dateOptions).format(date); } +function countStatus(data) { + let upCount = 0; + let downCount = 0; + + if (data.checks) { + data.checks.forEach((check) => { + if (check.status === "up") { + upCount += 1; + } else if (check.status === "down") { + downCount += 1; + } + }); + } + + return { upCount, downCount }; +} + export default function Component({ service }) { const { t } = useTranslation(); const { widget } = service; @@ -46,13 +63,26 @@ export default function Component({ service }) { ); } + const hasUuid = widget?.uuid; + + const { upCount, downCount } = countStatus(data); + return ( <Container service={service}> - <Block label="healthchecks.status" value={t(`healthchecks.${data.status}`)} /> - <Block - label="healthchecks.last_ping" - value={data.last_ping ? formatDate(data.last_ping) : t("healthchecks.never")} - /> + {hasUuid ? ( + <> + <Block label="healthchecks.status" value={t(`healthchecks.${data.status}`)} /> + <Block + label="healthchecks.last_ping" + value={data.last_ping ? formatDate(data.last_ping) : t("healthchecks.never")} + /> + </> + ) : ( + <> + <Block label="healthchecks.up" value={upCount} /> + <Block label="healthchecks.down" value={downCount} /> + </> + )} </Container> ); } diff --git a/src/widgets/healthchecks/widget.js b/src/widgets/healthchecks/widget.js index 02ae9acf..50324dd5 100644 --- a/src/widgets/healthchecks/widget.js +++ b/src/widgets/healthchecks/widget.js @@ -1,13 +1,12 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { - api: "{url}/api/v2/{endpoint}/{uuid}", + api: "{url}/api/v3/{endpoint}/{uuid}", proxyHandler: credentialedProxyHandler, mappings: { checks: { endpoint: "checks", - validate: ["status", "last_ping"], }, }, }; From 8f121d675c057ce455690201574641300c7a4984 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:16:48 -0800 Subject: [PATCH 05/11] Fix custom API docker labels example --- docs/configs/docker.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configs/docker.md b/docs/configs/docker.md index 2eaf2683..4d3026db 100644 --- a/docs/configs/docker.md +++ b/docs/configs/docker.md @@ -164,10 +164,10 @@ labels: - homepage.description=Media server - homepage.widget.type=customapi - homepage.widget.url=http://argus.service/api/v1/service/summary/emby - - homepage.widget.field[0].label=Deployed Version - - homepage.widget.field[0].field.status=deployed_version - - homepage.widget.field[1].label=Latest Version - - homepage.widget.field[1].field.status=latest_version + - homepage.widget.mappings[0].label=Deployed Version + - homepage.widget.mappings[0].field.status=deployed_version + - homepage.widget.mappings[1].label=Latest Version + - homepage.widget.mappings[1].field.status=latest_version ``` ## Docker Swarm From 66a1368aa3529e110a4853a8fe3de1845b5fa08a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:24:38 -0800 Subject: [PATCH 06/11] Fix: sort ical events in monthly view (#2604) --- src/widgets/calendar/monthly.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/calendar/monthly.jsx b/src/widgets/calendar/monthly.jsx index 8a208bc1..62f869ed 100644 --- a/src/widgets/calendar/monthly.jsx +++ b/src/widgets/calendar/monthly.jsx @@ -111,6 +111,7 @@ export default function Monthly({ service, colorVariants, events, showDate, setS } const eventsArray = Object.keys(events).map((eventKey) => events[eventKey]); + eventsArray.sort((a, b) => a.date - b.date); return ( <div className="w-full text-center"> From 9984e7894fa04fe0ddb4b49850fe4b4ba2cf99bb Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:26:40 -0800 Subject: [PATCH 07/11] Fix lint error for service anchors --- src/components/services/item.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 89319691..0d0c1b5b 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -49,6 +49,7 @@ export default function Item({ service, group, useEqualHeights }) { target={service.target ?? settings.target ?? "_blank"} rel="noreferrer" className="flex-shrink-0 flex items-center justify-center w-12 service-icon" + aria-label={service.icon} > <ResolvedIcon icon={service.icon} /> </a> From 674d7f2e01f5fbf345d4744c16072002a4937a4a Mon Sep 17 00:00:00 2001 From: Denis Papec <denis.papec@gmail.com> Date: Sun, 14 Jan 2024 21:49:28 +0000 Subject: [PATCH 08/11] Fix for events repeating on mothly basis and old events that are shown as occuring today (#2624) --- src/widgets/calendar/integrations/ical.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/widgets/calendar/integrations/ical.jsx b/src/widgets/calendar/integrations/ical.jsx index f5063331..c14aa347 100644 --- a/src/widgets/calendar/integrations/ical.jsx +++ b/src/widgets/calendar/integrations/ical.jsx @@ -55,8 +55,9 @@ export default function Integration({ config, params, setEvents, hideErrors }) { } }; - if (event?.recurrenceRule?.options) { - const rule = new RRule(event.recurrenceRule.options); + const recurrenceOptions = event?.recurrenceRule?.origOptions; + if (recurrenceOptions && Object.keys(recurrenceOptions).length !== 0) { + const rule = new RRule(recurrenceOptions); const recurringEvents = rule.between(startDate.toJSDate(), endDate.toJSDate()); recurringEvents.forEach((date, i) => eventToAdd(date, i, "recurring")); From 1f2081af5d7642f6eeec92210ded14e3b9e1bc0d Mon Sep 17 00:00:00 2001 From: Denis Papec <denis.papec@gmail.com> Date: Mon, 15 Jan 2024 02:01:10 +0000 Subject: [PATCH 09/11] Add option to specify a timezone for events (#2623) * Add option to specify a timezone for events * Amend message, update docs --- docs/widgets/services/calendar.md | 1 + src/widgets/calendar/agenda.jsx | 10 ++++++---- src/widgets/calendar/event.jsx | 4 ++++ src/widgets/calendar/integrations/ical.jsx | 12 ++++++++---- src/widgets/calendar/monthly.jsx | 10 ++++------ 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/widgets/services/calendar.md b/docs/widgets/services/calendar.md index f9ed6284..74751f6e 100644 --- a/docs/widgets/services/calendar.md +++ b/docs/widgets/services/calendar.md @@ -27,6 +27,7 @@ widget: url: https://domain.url/with/link/to.ics # URL with calendar events name: My Events # required - name for these calendar events color: zinc # optional - defaults to pre-defined color for the service (zinc for ical) + timezone: America/Los_Angeles # optional - force timezone for events (if it's the same - no change, if missing or different in ical - will be converted to this timezone) params: # optional - additional params for the service showName: true # optional - show name before event title in event line - defaults to false ``` diff --git a/src/widgets/calendar/agenda.jsx b/src/widgets/calendar/agenda.jsx index 90359269..9313cb8e 100644 --- a/src/widgets/calendar/agenda.jsx +++ b/src/widgets/calendar/agenda.jsx @@ -2,7 +2,7 @@ import { DateTime } from "luxon"; import classNames from "classnames"; import { useTranslation } from "next-i18next"; -import Event from "./event"; +import Event, { compareDateTimezoneAware } from "./event"; export default function Agenda({ service, colorVariants, events, showDate }) { const { widget } = service; @@ -15,8 +15,10 @@ export default function Agenda({ service, colorVariants, events, showDate }) { const eventsArray = Object.keys(events) .filter( (eventKey) => - showDate.minus({ days: widget?.previousDays ?? 0 }).startOf("day").ts <= - events[eventKey].date?.startOf("day").ts, + showDate + .setZone(events[eventKey].date.zoneName) + .minus({ days: widget?.previousDays ?? 0 }) + .startOf("day").ts <= events[eventKey].date?.startOf("day").ts, ) .map((eventKey) => events[eventKey]) .sort((a, b) => a.date - b.date) @@ -56,7 +58,7 @@ export default function Agenda({ service, colorVariants, events, showDate }) { event={event} colorVariants={colorVariants} showDate={j === 0} - showTime={widget?.showTime && event.date.startOf("day").ts === showDate.startOf("day").ts} + showTime={widget?.showTime && compareDateTimezoneAware(showDate, event)} /> ))} </div> diff --git a/src/widgets/calendar/event.jsx b/src/widgets/calendar/event.jsx index 5d3699d7..7d348285 100644 --- a/src/widgets/calendar/event.jsx +++ b/src/widgets/calendar/event.jsx @@ -39,3 +39,7 @@ export default function Event({ event, colorVariants, showDate = false, showTime </div> ); } + +export function compareDateTimezoneAware(date, event) { + return date.setZone(event.date.zoneName).startOf("day").valueOf() === event.date.startOf("day").valueOf(); +} diff --git a/src/widgets/calendar/integrations/ical.jsx b/src/widgets/calendar/integrations/ical.jsx index c14aa347..4c4ec9ca 100644 --- a/src/widgets/calendar/integrations/ical.jsx +++ b/src/widgets/calendar/integrations/ical.jsx @@ -23,8 +23,9 @@ export default function Integration({ config, params, setEvents, hideErrors }) { } } - const startDate = DateTime.fromISO(params.start); - const endDate = DateTime.fromISO(params.end); + const zone = config?.timezone || null; + const startDate = DateTime.fromISO(params.start, { zone }); + const endDate = DateTime.fromISO(params.end, { zone }); if (icalError || !parsedIcal || !startDate.isValid || !endDate.isValid) { return; @@ -43,12 +44,15 @@ export default function Integration({ config, params, setEvents, hideErrors }) { const duration = event.dtend.value - event.dtstart.value; const days = duration / (1000 * 60 * 60 * 24); + const now = DateTime.now().setZone(zone); + const eventDate = DateTime.fromJSDate(date, { zone }); + for (let j = 0; j < days; j += 1) { eventsToAdd[`${event?.uid?.value}${i}${j}${type}`] = { title, - date: DateTime.fromJSDate(date).plus({ days: j }), + date: eventDate.plus({ days: j }), color: config?.color ?? "zinc", - isCompleted: DateTime.fromJSDate(date) < DateTime.now(), + isCompleted: eventDate < now, additional: event.location?.value, type: "ical", }; diff --git a/src/widgets/calendar/monthly.jsx b/src/widgets/calendar/monthly.jsx index 62f869ed..ddb9cd87 100644 --- a/src/widgets/calendar/monthly.jsx +++ b/src/widgets/calendar/monthly.jsx @@ -3,7 +3,7 @@ import { DateTime, Info } from "luxon"; import classNames from "classnames"; import { useTranslation } from "next-i18next"; -import Event from "./event"; +import Event, { compareDateTimezoneAware } from "./event"; const cellStyle = "relative w-10 flex items-center justify-center flex-col"; const monthButton = "pl-6 pr-6 ml-2 mr-2 hover:bg-theme-100/20 dark:hover:bg-white/5 rounded-md cursor-pointer"; @@ -12,9 +12,7 @@ export function Day({ weekNumber, weekday, events, colorVariants, showDate, setS const currentDate = DateTime.now(); const cellDate = showDate.set({ weekday, weekNumber }).startOf("day"); - const filteredEvents = events?.filter( - (event) => event.date?.startOf("day").toUnixInteger() === cellDate.toUnixInteger(), - ); + const filteredEvents = events?.filter((event) => compareDateTimezoneAware(cellDate, event)); const dayStyles = (displayDate) => { let style = "h-9 "; @@ -173,7 +171,7 @@ export default function Monthly({ service, colorVariants, events, showDate, setS <div className="flex flex-col"> {eventsArray - ?.filter((event) => showDate.startOf("day").ts === event.date?.startOf("day").ts) + ?.filter((event) => compareDateTimezoneAware(showDate, event)) .slice(0, widget?.maxEvents ?? 10) .map((event) => ( <Event @@ -181,7 +179,7 @@ export default function Monthly({ service, colorVariants, events, showDate, setS event={event} colorVariants={colorVariants} showDateColumn={widget?.showTime ?? false} - showTime={widget?.showTime && event.date.startOf("day").ts === showDate.startOf("day").ts} + showTime={widget?.showTime && compareDateTimezoneAware(showDate, event)} /> ))} </div> From d61d0eb88ff04d60ef768de03bc05dac0d4d0731 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 15 Jan 2024 06:30:46 -0800 Subject: [PATCH 10/11] Fix configured service weight = 0 (#2628) --- src/utils/config/service-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index d73c1b5a..b0854b08 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -38,7 +38,7 @@ export async function servicesFromConfig() { // add default weight to services based on their position in the configuration servicesArray.forEach((group, groupIndex) => { group.services.forEach((service, serviceIndex) => { - if (!service.weight) { + if (service.weight === undefined) { servicesArray[groupIndex].services[serviceIndex].weight = (serviceIndex + 1) * 100; } }); From 1f905bc241daaa0f3f5420b8a54a25a1977e1a2e Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:07:20 -0800 Subject: [PATCH 11/11] Fix: constrain usage bar to 0-100 (#2650) --- src/components/widgets/resources/usage-bar.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/widgets/resources/usage-bar.jsx b/src/components/widgets/resources/usage-bar.jsx index 8a22339a..82278e9d 100644 --- a/src/components/widgets/resources/usage-bar.jsx +++ b/src/components/widgets/resources/usage-bar.jsx @@ -1,10 +1,11 @@ export default function UsageBar({ percent, additionalClassNames = "" }) { + const normalized = Math.min(100, Math.max(0, percent)); return ( <div className={`mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-theme-200/20 ${additionalClassNames}`}> <div className="bg-theme-800/70 h-1 rounded-full dark:bg-theme-200/50 transition-all duration-1000" style={{ - width: `${percent}%`, + width: `${normalized}%`, }} /> </div>