feat: add immich integration and widget (#5165)

* feat: add immich integration and widget

* feat: use sdk
This commit is contained in:
Manuel authored and GitHub committed 2026-03-07 09:39:31 +01:00
1 parent 6aaaf31d8b
commit cdd90b8aa0
18 files changed
+609

No files matched your search

@@ -0,0 +1,20 @@
.statItem {
padding: 12px;
border-radius: var(--mantine-radius-md);
background-color: var(--mantine-color-gray-0);
transition: all 0.2s ease;
&:hover {
background-color: var(--mantine-color-gray-1);
}
}
@media (prefers-color-scheme: dark) {
.statItem {
background-color: var(--mantine-color-dark-6);
&:hover {
background-color: var(--mantine-color-dark-5);
}
}
}
@@ -0,0 +1,73 @@
"use client";
import React from "react";
import { Group, Stack, Text } from "@mantine/core";
import { IconDatabase, IconPhoto, IconUsers, IconVideo } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { humanFileSize } from "@homarr/common";
import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../../definition";
import classes from "./component.module.css";
export default function ImmichServerStatsWidget({
integrationIds,
options,
}: WidgetComponentProps<"immich-serverStats">) {
const t = useI18n();
const [stats] = clientApi.widget.immich.getServerStats.useSuspenseQuery({
integrationId: integrationIds[0] ?? "",
});
return (
<Stack gap="md" h="100%" p="md">
{options.showUsers && (
<StatItem icon={<IconUsers size={20} />} label={t("widget.immich-serverStats.users")} value={stats.userCount} />
)}
{options.showPhotos && (
<StatItem
icon={<IconPhoto size={20} />}
label={t("widget.immich-serverStats.photos")}
value={stats.photoCount}
/>
)}
{options.showVideos && (
<StatItem
icon={<IconVideo size={20} />}
label={t("widget.immich-serverStats.videos")}
value={stats.videoCount}
/>
)}
{options.showStorage && (
<StatItem
icon={<IconDatabase size={20} />}
label={t("widget.immich-serverStats.storage")}
value={humanFileSize(stats.totalLibraryUsageInBytes)}
/>
)}
</Stack>
);
}
interface StatItemProps {
icon: React.ReactNode;
label: string;
value: string | number;
}
function StatItem({ icon, label, value }: StatItemProps) {
return (
<Group justify="space-between" align="center" className={classes.statItem}>
<Group gap="sm" align="center">
{icon}
<Text size="sm" fw={500}>
{label}
</Text>
</Group>
<Text size="sm" fw={700} c="var(--mantine-primary-color)">
{value}
</Text>
</Group>
);
}
@@ -0,0 +1,30 @@
import { IconGraphFilled } from "@tabler/icons-react";
import { createWidgetDefinition } from "../../definition";
import { optionsBuilder } from "../../options";
export const { definition, componentLoader } = createWidgetDefinition("immich-serverStats", {
icon: IconGraphFilled,
supportedIntegrations: ["immich"],
integrationsRequired: true,
createOptions() {
return optionsBuilder.from((factory) => ({
showUsers: factory.switch({
defaultValue: true,
withDescription: true,
}),
showPhotos: factory.switch({
defaultValue: true,
withDescription: true,
}),
showVideos: factory.switch({
defaultValue: true,
withDescription: true,
}),
showStorage: factory.switch({
defaultValue: true,
withDescription: true,
}),
}));
},
}).withDynamicImport(() => import("./component"));