mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-07 09:35:54 -08:00
Initial commit of Vikunja widget
This commit is contained in:
19
docs/widgets/services/vikunja.md
Normal file
19
docs/widgets/services/vikunja.md
Normal 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
|
||||
```
|
||||
@@ -953,5 +953,9 @@
|
||||
"reminders": "Reminders",
|
||||
"nextReminder": "Next Reminder",
|
||||
"none": "None"
|
||||
},
|
||||
"vikunja": {
|
||||
"projects": "Total Active Projects",
|
||||
"tasks": "Tasks Due This Week"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
"tailscale",
|
||||
"tandoor",
|
||||
"pterodactyl",
|
||||
"vikunja",
|
||||
].includes(widget.type)
|
||||
) {
|
||||
headers.Authorization = `Bearer ${widget.key}`;
|
||||
|
||||
@@ -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")),
|
||||
|
||||
37
src/widgets/vikunja/component.jsx
Normal file
37
src/widgets/vikunja/component.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
49
src/widgets/vikunja/proxy.js
Normal file
49
src/widgets/vikunja/proxy.js
Normal 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 });
|
||||
}
|
||||
}
|
||||
26
src/widgets/vikunja/widget.js
Normal file
26
src/widgets/vikunja/widget.js
Normal 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;
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user