mirror of
				https://github.com/karl0ss/homepage.git
				synced 2025-10-31 14:34:00 +00:00 
			
		
		
		
	Feature: Vikunja service widget (#4118)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									e6c7692677
								
							
						
					
					
						commit
						20048ff567
					
				| @ -128,6 +128,7 @@ You can also find a list of all available service widgets in the sidebar navigat | |||||||
| - [Uptime Kuma](uptime-kuma.md) | - [Uptime Kuma](uptime-kuma.md) | ||||||
| - [UptimeRobot](uptimerobot.md) | - [UptimeRobot](uptimerobot.md) | ||||||
| - [UrBackup](urbackup.md) | - [UrBackup](urbackup.md) | ||||||
|  | - [Vikunja](vikunja.md) | ||||||
| - [Watchtower](watchtower.md) | - [Watchtower](watchtower.md) | ||||||
| - [WGEasy](wgeasy.md) | - [WGEasy](wgeasy.md) | ||||||
| - [WhatsUpDocker](whatsupdocker.md) | - [WhatsUpDocker](whatsupdocker.md) | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								docs/widgets/services/vikunja.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								docs/widgets/services/vikunja.md
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
|  | ``` | ||||||
| @ -151,6 +151,7 @@ nav: | |||||||
|           - widgets/services/uptime-kuma.md |           - widgets/services/uptime-kuma.md | ||||||
|           - widgets/services/uptimerobot.md |           - widgets/services/uptimerobot.md | ||||||
|           - widgets/services/urbackup.md |           - widgets/services/urbackup.md | ||||||
|  |           - widgets/services/vikunja.md | ||||||
|           - widgets/services/watchtower.md |           - widgets/services/watchtower.md | ||||||
|           - widgets/services/wgeasy.md |           - widgets/services/wgeasy.md | ||||||
|           - widgets/services/whatsupdocker.md |           - widgets/services/whatsupdocker.md | ||||||
|  | |||||||
| @ -953,5 +953,11 @@ | |||||||
|       "reminders": "Reminders", |       "reminders": "Reminders", | ||||||
|       "nextReminder": "Next Reminder", |       "nextReminder": "Next Reminder", | ||||||
|       "none": "None" |       "none": "None" | ||||||
|  |     }, | ||||||
|  |     "vikunja": { | ||||||
|  |       "projects": "Active Projects", | ||||||
|  |       "tasks7d": "Tasks Due This Week", | ||||||
|  |       "tasksOverdue": "Overdue Tasks", | ||||||
|  |       "tasksInProgress": "Tasks In Progress" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -478,6 +478,9 @@ export function cleanServiceGroups(groups) { | |||||||
|           // unifi
 |           // unifi
 | ||||||
|           site, |           site, | ||||||
| 
 | 
 | ||||||
|  |           // vikunja
 | ||||||
|  |           enableTaskList, | ||||||
|  | 
 | ||||||
|           // wgeasy
 |           // wgeasy
 | ||||||
|           threshold, |           threshold, | ||||||
| 
 | 
 | ||||||
| @ -633,6 +636,9 @@ export function cleanServiceGroups(groups) { | |||||||
|         if (type === "lubelogger") { |         if (type === "lubelogger") { | ||||||
|           if (vehicleID !== undefined) cleanedService.widget.vehicleID = parseInt(vehicleID, 10); |           if (vehicleID !== undefined) cleanedService.widget.vehicleID = parseInt(vehicleID, 10); | ||||||
|         } |         } | ||||||
|  |         if (type === "vikunja") { | ||||||
|  |           if (enableTaskList !== undefined) cleanedService.widget.enableTaskList = !!enableTaskList; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return cleanedService; |       return cleanedService; | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ export default async function credentialedProxyHandler(req, res, map) { | |||||||
|           "tailscale", |           "tailscale", | ||||||
|           "tandoor", |           "tandoor", | ||||||
|           "pterodactyl", |           "pterodactyl", | ||||||
|  |           "vikunja", | ||||||
|         ].includes(widget.type) |         ].includes(widget.type) | ||||||
|       ) { |       ) { | ||||||
|         headers.Authorization = `Bearer ${widget.key}`; |         headers.Authorization = `Bearer ${widget.key}`; | ||||||
|  | |||||||
| @ -125,6 +125,7 @@ const components = { | |||||||
|   uptimekuma: dynamic(() => import("./uptimekuma/component")), |   uptimekuma: dynamic(() => import("./uptimekuma/component")), | ||||||
|   uptimerobot: dynamic(() => import("./uptimerobot/component")), |   uptimerobot: dynamic(() => import("./uptimerobot/component")), | ||||||
|   urbackup: dynamic(() => import("./urbackup/component")), |   urbackup: dynamic(() => import("./urbackup/component")), | ||||||
|  |   vikunja: dynamic(() => import("./vikunja/component")), | ||||||
|   watchtower: dynamic(() => import("./watchtower/component")), |   watchtower: dynamic(() => import("./watchtower/component")), | ||||||
|   wgeasy: dynamic(() => import("./wgeasy/component")), |   wgeasy: dynamic(() => import("./wgeasy/component")), | ||||||
|   whatsupdocker: dynamic(() => import("./whatsupdocker/component")), |   whatsupdocker: dynamic(() => import("./whatsupdocker/component")), | ||||||
|  | |||||||
							
								
								
									
										68
									
								
								src/widgets/vikunja/component.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/widgets/vikunja/component.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -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 <Container service={service} error={projectsError ?? tasksError} />; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!projectsData || !tasksData) { | ||||||
|  |     return ( | ||||||
|  |       <Container service={service}> | ||||||
|  |         <Block label="vikunja.projects" /> | ||||||
|  |         <Block label="vikunja.tasks7d" /> | ||||||
|  |         <Block label="vikunja.tasksOverdue" /> | ||||||
|  |         <Block label="vikunja.tasksInProgress" /> | ||||||
|  |       </Container> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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 ( | ||||||
|  |     <> | ||||||
|  |       <Container service={service}> | ||||||
|  |         <Block label="vikunja.projects" value={t("common.number", { value: projects.length })} /> | ||||||
|  |         <Block label="vikunja.tasks7d" value={t("common.number", { value: tasks7d.length })} /> | ||||||
|  |         <Block label="vikunja.tasksOverdue" value={t("common.number", { value: tasksOverdue.length })} /> | ||||||
|  |         <Block label="vikunja.tasksInProgress" value={t("common.number", { value: tasksInProgress.length })} /> | ||||||
|  |       </Container> | ||||||
|  |       {widget.enableTaskList && | ||||||
|  |         tasksData.slice(0, 5).map((task) => ( | ||||||
|  |           <div | ||||||
|  |             key={task.id} | ||||||
|  |             className="text-theme-700 dark:text-theme-200 relative h-5 rounded-md bg-theme-200/50 dark:bg-theme-900/20 m-1 px-1 flex" | ||||||
|  |           > | ||||||
|  |             <div className="text-xs z-10 self-center ml-2 relative h-4 grow mr-2"> | ||||||
|  |               <div className="absolute w-full h-4 whitespace-nowrap text-ellipsis overflow-hidden text-left"> | ||||||
|  |                 {task.title} | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |             {!task.dueDateIsDefault && ( | ||||||
|  |               <div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10 text-ellipsis overflow-hidden whitespace-nowrap"> | ||||||
|  |                 {t("common.relativeDate", { | ||||||
|  |                   value: task.dueDate, | ||||||
|  |                   formatParams: { value: { style: "narrow", numeric: "auto" } }, | ||||||
|  |                 })} | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |         ))} | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								src/widgets/vikunja/widget.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/widgets/vikunja/widget.js
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
| @ -115,6 +115,7 @@ import unifi from "./unifi/widget"; | |||||||
| import unmanic from "./unmanic/widget"; | import unmanic from "./unmanic/widget"; | ||||||
| import uptimekuma from "./uptimekuma/widget"; | import uptimekuma from "./uptimekuma/widget"; | ||||||
| import uptimerobot from "./uptimerobot/widget"; | import uptimerobot from "./uptimerobot/widget"; | ||||||
|  | import vikunja from "./vikunja/widget"; | ||||||
| import watchtower from "./watchtower/widget"; | import watchtower from "./watchtower/widget"; | ||||||
| import wgeasy from "./wgeasy/widget"; | import wgeasy from "./wgeasy/widget"; | ||||||
| import whatsupdocker from "./whatsupdocker/widget"; | import whatsupdocker from "./whatsupdocker/widget"; | ||||||
| @ -246,6 +247,7 @@ const widgets = { | |||||||
|   uptimekuma, |   uptimekuma, | ||||||
|   uptimerobot, |   uptimerobot, | ||||||
|   urbackup, |   urbackup, | ||||||
|  |   vikunja, | ||||||
|   watchtower, |   watchtower, | ||||||
|   wgeasy, |   wgeasy, | ||||||
|   whatsupdocker, |   whatsupdocker, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 vhsdream
						vhsdream