diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 3462fef37b3..91f91d38316 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -128,6 +128,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Uptime Kuma](uptime-kuma.md) - [UptimeRobot](uptimerobot.md) - [UrBackup](urbackup.md) +- [Vikunja](vikunja.md) - [Watchtower](watchtower.md) - [WGEasy](wgeasy.md) - [WhatsUpDocker](whatsupdocker.md) diff --git a/docs/widgets/services/vikunja.md b/docs/widgets/services/vikunja.md new file mode 100644 index 00000000000..94b9905571e --- /dev/null +++ b/docs/widgets/services/vikunja.md @@ -0,0 +1,18 @@ +--- +title: Vikunja +description: Vikunja Widget Configuration +--- + +Learn more about [Vikunja](https://vikunja.io). + +Allowed fields: `["projects", "tasks7d", "tasksOverdue", "tasksInProgress"]`. + +A list of the next 5 tasks ordered by due date is disabled by default, but can be enabled with the `enableTaskList` option. + +```yaml +widget: + type: vikunja + url: http[s]://vikunja.host.or.ip[:port] + key: vikunjaapikey + enableTaskList: true # optional, defaults to false +``` diff --git a/mkdocs.yml b/mkdocs.yml index 1b3afd62273..0667e5eaa66 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -151,6 +151,7 @@ nav: - widgets/services/uptime-kuma.md - widgets/services/uptimerobot.md - widgets/services/urbackup.md + - widgets/services/vikunja.md - widgets/services/watchtower.md - widgets/services/wgeasy.md - widgets/services/whatsupdocker.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 3aea07ebba2..384f44befcb 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -953,5 +953,11 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 24ba57e23da..9d55ce43af9 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -478,6 +478,9 @@ export function cleanServiceGroups(groups) { // unifi site, + // vikunja + enableTaskList, + // wgeasy threshold, @@ -633,6 +636,9 @@ export function cleanServiceGroups(groups) { if (type === "lubelogger") { if (vehicleID !== undefined) cleanedService.widget.vehicleID = parseInt(vehicleID, 10); } + if (type === "vikunja") { + if (enableTaskList !== undefined) cleanedService.widget.enableTaskList = !!enableTaskList; + } } return cleanedService; diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 3982207539f..e907912daa7 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -44,6 +44,7 @@ export default async function credentialedProxyHandler(req, res, map) { "tailscale", "tandoor", "pterodactyl", + "vikunja", ].includes(widget.type) ) { headers.Authorization = `Bearer ${widget.key}`; diff --git a/src/widgets/components.js b/src/widgets/components.js index 0a5a815cbd9..62bd479f559 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -125,6 +125,7 @@ const components = { uptimekuma: dynamic(() => import("./uptimekuma/component")), uptimerobot: dynamic(() => import("./uptimerobot/component")), urbackup: dynamic(() => import("./urbackup/component")), + vikunja: dynamic(() => import("./vikunja/component")), watchtower: dynamic(() => import("./watchtower/component")), wgeasy: dynamic(() => import("./wgeasy/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")), diff --git a/src/widgets/vikunja/component.jsx b/src/widgets/vikunja/component.jsx new file mode 100644 index 00000000000..09704338a8d --- /dev/null +++ b/src/widgets/vikunja/component.jsx @@ -0,0 +1,68 @@ +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 { data: projectsData, error: projectsError } = useWidgetAPI(widget, "projects"); + const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "tasks"); + + if (projectsError || tasksError) { + return ; + } + + if (!projectsData || !tasksData) { + return ( + + + + + + + ); + } + + const projects = projectsData.filter((project) => project.id > 0); // saved filters have id < 0 + + const oneWeekFromNow = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); + const tasksWithDueDate = tasksData.filter((task) => !task.dueDateIsDefault); + const tasks7d = tasksWithDueDate.filter((task) => new Date(task.dueDate) <= oneWeekFromNow); + const tasksOverdue = tasksWithDueDate.filter((task) => new Date(task.dueDate) <= new Date(Date.now())); + const tasksInProgress = tasksData.filter((task) => task.inProgress); + + return ( + <> + + + + + + + {widget.enableTaskList && + tasksData.slice(0, 5).map((task) => ( +
+
+
+ {task.title} +
+
+ {!task.dueDateIsDefault && ( +
+ {t("common.relativeDate", { + value: task.dueDate, + formatParams: { value: { style: "narrow", numeric: "auto" } }, + })} +
+ )} +
+ ))} + + ); +} diff --git a/src/widgets/vikunja/widget.js b/src/widgets/vikunja/widget.js new file mode 100644 index 00000000000..9a1920266da --- /dev/null +++ b/src/widgets/vikunja/widget.js @@ -0,0 +1,27 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +import { asJson } from "utils/proxy/api-helpers"; + +const widget = { + api: `{url}/api/v1/{endpoint}`, + proxyHandler: credentialedProxyHandler, + + mappings: { + projects: { + endpoint: "projects", + }, + tasks: { + endpoint: "tasks/all?filter=done%3Dfalse&sort_by=due_date", + map: (data) => + asJson(data).map((task) => ({ + id: task.id, + title: task.title, + priority: task.priority, + dueDate: task.due_date, + dueDateIsDefault: task.due_date === "0001-01-01T00:00:00Z", + inProgress: task.percent_done > 0 && task.percent_done < 1, + })), + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 3334e47e593..faff57eb4da 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -115,6 +115,7 @@ import unifi from "./unifi/widget"; import unmanic from "./unmanic/widget"; import uptimekuma from "./uptimekuma/widget"; import uptimerobot from "./uptimerobot/widget"; +import vikunja from "./vikunja/widget"; import watchtower from "./watchtower/widget"; import wgeasy from "./wgeasy/widget"; import whatsupdocker from "./whatsupdocker/widget"; @@ -246,6 +247,7 @@ const widgets = { uptimekuma, uptimerobot, urbackup, + vikunja, watchtower, wgeasy, whatsupdocker,