Feature: wallos service widget (#5562)

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
Matan Heimann 2025-07-24 07:07:26 +01:00 committed by GitHub
parent 612d05fd4d
commit d581d70d1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 154 additions and 0 deletions

View File

@ -0,0 +1,23 @@
---
title: Wallos
description: Wallos Widget Configuration
---
Learn more about [Wallos](https://github.com/ellite/wallos).
If you're using more than one currency to record subscriptions then you should also have your "Fixer API" key set-up (`Settings > Fixer API Key`).
> **Please Note:** The monthly cost displayed is the total cost of subscriptions in that month, **not** the _"monthly"_ average cost.
Get your API key under `Profile > API Key`.
Allowed fields: `["activeSubscriptions", "nextRenewingSubscription", "previousMonthlyCost", "thisMonthlyCost", "nextMonthlyCost"]`.
Default fields: `["activeSubscriptions", "nextRenewingSubscription", "thisMonthlyCost", "nextMonthlyCost"]`.
```yaml
widget:
type: wallos
url: http://wallos.host.or.ip
key: apikeyapikeyapikeyapikeyapikey
```

View File

@ -1071,5 +1071,12 @@
"servers": "Servers",
"stacks": "Stacks",
"containers": "Containers"
},
"wallos": {
"activeSubscriptions": "Subscriptions",
"thisMonthlyCost": "This Month",
"nextMonthlyCost": "Next Month",
"previousMonthlyCost": "Prev. Month",
"nextRenewingSubscription": "Next Payment"
}
}

View File

@ -142,6 +142,7 @@ const components = {
uptimerobot: dynamic(() => import("./uptimerobot/component")),
urbackup: dynamic(() => import("./urbackup/component")),
vikunja: dynamic(() => import("./vikunja/component")),
wallos: dynamic(() => import("./wallos/component")),
watchtower: dynamic(() => import("./watchtower/component")),
wgeasy: dynamic(() => import("./wgeasy/component")),
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),

View File

@ -0,0 +1,100 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next";
import useWidgetAPI from "utils/proxy/use-widget-api";
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service }) {
const todayDate = new Date();
const { t } = useTranslation();
const { widget } = service;
if (!widget.fields) {
widget.fields = ["activeSubscriptions", "nextRenewingSubscription", "thisMonthlyCost", "nextMonthlyCost"];
} else if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
}
const subscriptionsEndPoint =
widget.fields.includes("activeSubscriptions") || widget.fields.includes("nextRenewingSubscription")
? "get_subscriptions"
: "";
const { data: subscriptionsData, error: subscriptionsError } = useWidgetAPI(widget, subscriptionsEndPoint, {
state: 0,
sort: "next_payment",
});
const subscriptionsThisMonthlyEndpoint = widget.fields.includes("thisMonthlyCost") ? "get_monthly_cost" : "";
const { data: subscriptionsThisMonthlyCostData, error: subscriptionsThisMonthlyCostError } = useWidgetAPI(
widget,
subscriptionsThisMonthlyEndpoint,
{
month: todayDate.getMonth(),
year: todayDate.getFullYear(),
},
);
const subscriptionsNextMonthlyEndpoint = widget.fields.includes("nextMonthlyCost") ? "get_monthly_cost" : "";
const { data: subscriptionsNextMonthlyCostData, error: subscriptionsNextMonthlyCostError } = useWidgetAPI(
widget,
subscriptionsNextMonthlyEndpoint,
{
month: todayDate.getMonth() + 1,
year: todayDate.getFullYear(),
},
);
const subscriptionsPreviousMonthlyEndpoint = widget.fields.includes("previousMonthlyCost") ? "get_monthly_cost" : "";
const { data: subscriptionsPreviousMonthlyCostData, error: subscriptionsPreviousMonthlyCostError } = useWidgetAPI(
widget,
subscriptionsPreviousMonthlyEndpoint,
{
month: todayDate.getMonth() - 1,
year: todayDate.getFullYear(),
},
);
if (
subscriptionsError ||
subscriptionsThisMonthlyCostError ||
subscriptionsNextMonthlyCostError ||
subscriptionsPreviousMonthlyCostError
) {
const finalError =
subscriptionsError ??
subscriptionsThisMonthlyCostError ??
subscriptionsNextMonthlyCostError ??
subscriptionsPreviousMonthlyCostError;
return <Container service={service} error={finalError} />;
}
if (
(!subscriptionsData &&
(widget.fields.includes("activeSubscriptions") || widget.fields.includes("nextRenewingSubscription"))) ||
(!subscriptionsThisMonthlyCostData && widget.fields.includes("thisMonthlyCost")) ||
(!subscriptionsNextMonthlyCostData && widget.fields.includes("nextMonthlyCost")) ||
(!subscriptionsPreviousMonthlyCostData && widget.fields.includes("previousMonthlyCost"))
) {
return (
<Container service={service}>
<Block label="wallos.activeSubscriptions" />
<Block label="wallos.nextRenewingSubscription" />
<Block label="wallos.previousMonthlyCost" />
<Block label="wallos.thisMonthlyCost" />
<Block label="wallos.nextMonthlyCost" />
</Container>
);
}
return (
<Container service={service}>
<Block
label="wallos.activeSubscriptions"
value={t("common.number", { value: subscriptionsData?.subscriptions?.length })}
/>
<Block label="wallos.nextRenewingSubscription" value={subscriptionsData?.subscriptions[0]?.name} />
<Block label="wallos.previousMonthlyCost" value={subscriptionsPreviousMonthlyCostData?.localized_monthly_cost} />
<Block label="wallos.thisMonthlyCost" value={subscriptionsThisMonthlyCostData?.localized_monthly_cost} />
<Block label="wallos.nextMonthlyCost" value={subscriptionsNextMonthlyCostData?.localized_monthly_cost} />
</Container>
);
}

View File

@ -0,0 +1,21 @@
import genericProxyHandler from "utils/proxy/handlers/generic";
const widget = {
api: "{url}/api/{endpoint}?api_key={key}",
proxyHandler: genericProxyHandler,
mappings: {
get_monthly_cost: {
endpoint: "subscriptions/get_monthly_cost.php",
validate: ["localized_monthly_cost", "currency_symbol"],
params: ["month", "year"],
},
get_subscriptions: {
endpoint: "subscriptions/get_subscriptions.php",
validate: ["subscriptions"],
params: ["state", "sort"],
},
},
};
export default widget;

View File

@ -133,6 +133,7 @@ import uptimekuma from "./uptimekuma/widget";
import uptimerobot from "./uptimerobot/widget";
import urbackup from "./urbackup/widget";
import vikunja from "./vikunja/widget";
import wallos from "./wallos/widget";
import watchtower from "./watchtower/widget";
import wgeasy from "./wgeasy/widget";
import whatsupdocker from "./whatsupdocker/widget";
@ -279,6 +280,7 @@ const widgets = {
uptimerobot,
urbackup,
vikunja,
wallos,
watchtower,
wgeasy,
whatsupdocker,