Initial commit of Vikunja widget

This commit is contained in:
vhsdream
2024-10-04 16:35:19 -04:00
parent b1ca6b8e1a
commit 60c6123dc8
8 changed files with 139 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
---
title: Vikunja
description: Vikunja Widget Configuration
---
Learn more about [Vikunja](https://vikunja.io).
Allowed fields: `["projects", "tasks"]`.
"Projects" lists the number of non-archived Projects the user has access to.
"Tasks" lists the number of tasks due within the next 7 days.
```yaml
widget:
type: vikunja
url: http[s]://vikunja.host.or.ip[:port]
key: vikunjaapikey
```

View File

@@ -953,5 +953,9 @@
"reminders": "Reminders",
"nextReminder": "Next Reminder",
"none": "None"
},
"vikunja": {
"projects": "Total Active Projects",
"tasks": "Tasks Due This Week"
}
}

View File

@@ -44,6 +44,7 @@ export default async function credentialedProxyHandler(req, res, map) {
"tailscale",
"tandoor",
"pterodactyl",
"vikunja",
].includes(widget.type)
) {
headers.Authorization = `Bearer ${widget.key}`;

View File

@@ -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")),

View File

@@ -0,0 +1,37 @@
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", {
filter: "done=false&&due_date<=now+7d",
});
if (projectsError || tasksError) {
const vikunjaError = projectsError ?? tasksError;
return <Container service={service} error={vikunjaError} />;
}
if (!projectsData || !tasksData) {
return (
<Container service={service}>
<Block label="vikunja.projects" />
<Block label="vikunja.tasks" />
</Container>
);
}
return (
<Container service={service}>
<Block label="vikunja.projects" value={t("common.number", { value: projectsData.projects })} />
<Block label="vikunja.tasks" value={t("common.number", { value: tasksData.tasks })} />
</Container>
);
}

View File

@@ -0,0 +1,49 @@
import { httpProxy } from "utils/proxy/http";
import { formatApiCall } from "utils/proxy/api-helpers";
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import widgets from "widgets/widgets";
const proxyName = "vikunjaProxyHandler";
const logger = createLogger(proxyName);
export default async function vikunjaProxyHandler(req, res) {
const { group, service, endpoint } = 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);
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" });
}
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
try {
const params = {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${widget.token}`,
},
};
logger.debug("Calling Vikunja API endpoint: %s", endpoint);
const [status, , data] = await httpProxy(url, params);
if (status !== 200) {
logger.error("Error calling Vikunja API: %d. Data: %s", status, data);
return res.status(status).json({ error: "Vikunja API Error", data });
}
return res.status(status).send(data);
} catch (error) {
logger.error("Exception calling Vikunja API: %s", error.message);
return res.status(500).json({ error: "Vikunja API Error", message: error.message });
}
}

View File

@@ -0,0 +1,26 @@
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
import { jsonArrayFilter } from "utils/proxy/api-helpers";
const widget = {
api: `{url}/api/v1/{endpoint}`,
proxyHandler: credentialedProxyHandler,
mappings: {
projects: {
endpoint: "projects",
map: (data) => ({
projects: jsonArrayFilter(data, (item) => !item.isArchived).length,
}),
},
tasks: {
endpoint: "tasks/all",
// to filter by done=false and dueDate <= now+7d or whatever
params: ["filter"],
map: (data) => ({
tasks: jsonArrayFilter(data, (item) => !item.done).length,
}),
},
},
};
export default widget;

View File

@@ -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,