From cae304b7eb57836e6e1d86e5b40015ad28854b77 Mon Sep 17 00:00:00 2001 From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Date: Sun, 2 Feb 2025 11:40:21 +0800 Subject: [PATCH] Feature: Firefly widget (#4683) Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/firefly.md | 17 ++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 4 ++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/components.js | 1 + src/widgets/firefly/component.jsx | 71 ++++++++++++++++++++++++ src/widgets/firefly/widget.js | 19 +++++++ src/widgets/widgets.js | 2 + 9 files changed, 117 insertions(+) create mode 100644 docs/widgets/services/firefly.md create mode 100644 src/widgets/firefly/component.jsx create mode 100644 src/widgets/firefly/widget.js diff --git a/docs/widgets/services/firefly.md b/docs/widgets/services/firefly.md new file mode 100644 index 000000000..38793ce29 --- /dev/null +++ b/docs/widgets/services/firefly.md @@ -0,0 +1,17 @@ +--- +title: Firefly III +description: Firefly III Widget Configuration +--- + +Learn more about [Firefly III](https://www.firefly-iii.org/). + +Find your API key under `Options > Profile > OAuth > Personal Access Tokens`. + +Allowed fields: `["networth" ,"budget"]`. + +```yaml +widget: + type: firefly + url: https://firefly.host.or.ip + key: personalaccesstoken.personalaccesstoken.personalaccesstoken +``` diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 894a31f6e..2e3965820 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -33,6 +33,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [ESPHome](esphome.md) - [EVCC](evcc.md) - [Fileflows](fileflows.md) +- [Firefly III](firefly.md) - [Flood](flood.md) - [FreshRSS](freshrss.md) - [Frigate](frigate.md) diff --git a/mkdocs.yml b/mkdocs.yml index fa2188ad5..958c3398c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,6 +56,7 @@ nav: - widgets/services/esphome.md - widgets/services/evcc.md - widgets/services/fileflows.md + - widgets/services/firefly.md - widgets/services/flood.md - widgets/services/freshrss.md - widgets/services/frigate.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 1d7380894..0674d6be3 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -702,6 +702,10 @@ "processed": "Processed", "time": "Time" }, + "firefly": { + "networth": "Net Worth", + "budget": "Budget" + }, "grafana": { "dashboards": "Dashboards", "datasources": "Data Sources", diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 01fb313b1..9a333d6fb 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -48,6 +48,7 @@ export default async function credentialedProxyHandler(req, res, map) { "tandoor", "pterodactyl", "vikunja", + "firefly", ].includes(widget.type) ) { headers.Authorization = `Bearer ${widget.key}`; diff --git a/src/widgets/components.js b/src/widgets/components.js index 19f41d4ae..67d17d50b 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -30,6 +30,7 @@ const components = { esphome: dynamic(() => import("./esphome/component")), evcc: dynamic(() => import("./evcc/component")), fileflows: dynamic(() => import("./fileflows/component")), + firefly: dynamic(() => import("./firefly/component")), flood: dynamic(() => import("./flood/component")), freshrss: dynamic(() => import("./freshrss/component")), frigate: dynamic(() => import("./frigate/component")), diff --git a/src/widgets/firefly/component.jsx b/src/widgets/firefly/component.jsx new file mode 100644 index 000000000..30e495c16 --- /dev/null +++ b/src/widgets/firefly/component.jsx @@ -0,0 +1,71 @@ +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 startOfMonth = new Date(); + startOfMonth.setDate(1); + startOfMonth.setHours(0, 0, 0, 0); + const startOfMonthFormatted = startOfMonth.toISOString().split("T")[0]; + + const endOfMonth = new Date(startOfMonth); + endOfMonth.setMonth(endOfMonth.getMonth() + 1); + endOfMonth.setDate(0); + endOfMonth.setHours(23, 59, 59, 999); + const endOfMonthFormatted = endOfMonth.toISOString().split("T")[0]; + + const { data: summaryData, error: summaryError } = useWidgetAPI(widget, "summary", { + start: startOfMonthFormatted, + end: endOfMonthFormatted, + }); + + const { data: budgetData, error: budgetError } = useWidgetAPI(widget, "budgets", { + start: startOfMonthFormatted, + end: endOfMonthFormatted, + }); + + if (summaryError || budgetError) { + return ; + } + + if (!summaryData || !budgetData) { + return ( + + + + + ); + } + + const netWorth = Object.keys(summaryData) + .filter((key) => key.includes("net-worth-in")) + .map((key) => summaryData[key]); + + let budgetValue = null; + + if (budgetData.data?.length && budgetData.data[0].type === "available_budgets") { + const budgetAmount = parseFloat(budgetData.data[0].attributes.amount); + const budgetSpent = -parseFloat(budgetData.data[0].attributes.spent_in_budgets[0]?.sum ?? "0"); + const budgetCurrency = budgetData.data[0].attributes.currency_symbol; + + budgetValue = `${budgetCurrency} ${t("common.number", { + value: budgetSpent, + minimumFractionDigits: 2, + })} / ${budgetCurrency} ${t("common.number", { + value: budgetAmount, + minimumFractionDigits: 2, + })}`; + } + + return ( + + + + + ); +} diff --git a/src/widgets/firefly/widget.js b/src/widgets/firefly/widget.js new file mode 100644 index 000000000..cd23504db --- /dev/null +++ b/src/widgets/firefly/widget.js @@ -0,0 +1,19 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + summary: { + endpoint: "v1/summary/basic", + params: ["start", "end"], + }, + budgets: { + endpoint: "v1/available-budgets", + params: ["start", "end"], + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 9d4bb935d..b78f5b9c2 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -24,6 +24,7 @@ import emby from "./emby/widget"; import esphome from "./esphome/widget"; import evcc from "./evcc/widget"; import fileflows from "./fileflows/widget"; +import firefly from "./firefly/widget"; import flood from "./flood/widget"; import freshrss from "./freshrss/widget"; import frigate from "./frigate/widget"; @@ -157,6 +158,7 @@ const widgets = { esphome, evcc, fileflows, + firefly, flood, freshrss, frigate,