chore(release): automatic release v1.26.0

This commit is contained in:
homarr-releases[bot]
2025-06-27 19:14:04 +00:00
committed by GitHub
84 changed files with 2570 additions and 1573 deletions

View File

@@ -31,6 +31,7 @@ body:
label: Version label: Version
description: What version of Homarr are you running? description: What version of Homarr are you running?
options: options:
- 1.25.0
- 1.24.0 - 1.24.0
- 1.23.0 - 1.23.0
- 1.22.0 - 1.22.0

View File

@@ -1,14 +0,0 @@
# https://github.com/webiny/action-conventional-commits?tab=readme-ov-file
name: "[Conventions] Semantic Commits"
on:
pull_request:
branches: [ dev ]
jobs:
build:
name: Conventional Commits
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: webiny/action-conventional-commits@v1.3.0

2
.nvmrc
View File

@@ -1 +1 @@
22.16.0 22.17.0

View File

@@ -48,28 +48,28 @@
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.1.1", "@mantine/colors-generator": "^8.1.2",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/dropzone": "^8.1.1", "@mantine/dropzone": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"@mantine/modals": "^8.1.1", "@mantine/modals": "^8.1.2",
"@mantine/tiptap": "^8.1.1", "@mantine/tiptap": "^8.1.2",
"@million/lint": "1.0.14", "@million/lint": "1.0.14",
"@tabler/icons-react": "^3.34.0", "@tabler/icons-react": "^3.34.0",
"@tanstack/react-query": "^5.80.10", "@tanstack/react-query": "^5.81.4",
"@tanstack/react-query-devtools": "^5.80.10", "@tanstack/react-query-devtools": "^5.81.4",
"@tanstack/react-query-next-experimental": "^5.80.10", "@tanstack/react-query-next-experimental": "^5.81.4",
"@trpc/client": "^11.4.2", "@trpc/client": "^11.4.3",
"@trpc/next": "^11.4.2", "@trpc/next": "^11.4.3",
"@trpc/react-query": "^11.4.2", "@trpc/react-query": "^11.4.3",
"@trpc/server": "^11.4.2", "@trpc/server": "^11.4.3",
"@xterm/addon-canvas": "^0.7.0", "@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"chroma-js": "^3.1.2", "chroma-js": "^3.1.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dotenv": "^16.5.0", "dotenv": "^16.6.0",
"flag-icons": "^7.5.0", "flag-icons": "^7.5.0",
"glob": "^11.0.3", "glob": "^11.0.3",
"jotai": "^2.12.5", "jotai": "^2.12.5",
@@ -83,7 +83,7 @@
"react-simple-code-editor": "^0.14.1", "react-simple-code-editor": "^0.14.1",
"sass": "^1.89.2", "sass": "^1.89.2",
"superjson": "2.2.2", "superjson": "2.2.2",
"swagger-ui-react": "^5.25.2", "swagger-ui-react": "^5.25.3",
"use-deep-compare-effect": "^1.8.1", "use-deep-compare-effect": "^1.8.1",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },
@@ -92,15 +92,15 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1", "@types/chroma-js": "3.1.1",
"@types/node": "^22.15.32", "@types/node": "^22.15.33",
"@types/prismjs": "^1.26.5", "@types/prismjs": "^1.26.5",
"@types/react": "19.1.8", "@types/react": "19.1.8",
"@types/react-dom": "19.1.6", "@types/react-dom": "19.1.6",
"@types/swagger-ui-react": "^5.18.0", "@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.1.2", "concurrently": "^9.2.0",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"node-loader": "^2.1.0", "node-loader": "^2.1.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
} }

View File

@@ -1,4 +1,4 @@
import { IconGrid3x3, IconKey, IconPassword, IconServer, IconUser } from "@tabler/icons-react"; import { IconGrid3x3, IconKey, IconMessage, IconPassword, IconServer, IconUser } from "@tabler/icons-react";
import type { IntegrationSecretKind } from "@homarr/definitions"; import type { IntegrationSecretKind } from "@homarr/definitions";
import type { TablerIcon } from "@homarr/ui"; import type { TablerIcon } from "@homarr/ui";
@@ -9,4 +9,5 @@ export const integrationSecretIcons = {
password: IconPassword, password: IconPassword,
realm: IconServer, realm: IconServer,
tokenId: IconGrid3x3, tokenId: IconGrid3x3,
topic: IconMessage,
} satisfies Record<IntegrationSecretKind, TablerIcon>; } satisfies Record<IntegrationSecretKind, TablerIcon>;

View File

@@ -36,19 +36,19 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dotenv": "^16.5.0", "dotenv": "^16.6.0",
"superjson": "2.2.2", "superjson": "2.2.2",
"undici": "7.10.0" "undici": "7.11.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.15.32", "@types/node": "^22.15.33",
"dotenv-cli": "^8.0.0", "dotenv-cli": "^8.0.0",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"tsx": "4.20.3", "tsx": "4.20.3",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }

View File

@@ -25,7 +25,7 @@
"@homarr/log": "workspace:^", "@homarr/log": "workspace:^",
"@homarr/redis": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.5.0", "dotenv": "^16.6.0",
"tsx": "4.20.3", "tsx": "4.20.3",
"ws": "^8.18.2" "ws": "^8.18.2"
}, },
@@ -36,7 +36,7 @@
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
} }

View File

@@ -36,16 +36,16 @@
"@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^11.0.3", "@semantic-release/github": "^11.0.3",
"@semantic-release/npm": "^12.0.1", "@semantic-release/npm": "^12.0.2",
"@semantic-release/release-notes-generator": "^14.0.3", "@semantic-release/release-notes-generator": "^14.0.3",
"@turbo/gen": "^2.5.4", "@turbo/gen": "^2.5.4",
"@vitejs/plugin-react": "^4.5.2", "@vitejs/plugin-react": "^4.6.0",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"conventional-changelog-conventionalcommits": "^9.0.0", "conventional-changelog-conventionalcommits": "^9.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"semantic-release": "^24.2.5", "semantic-release": "^24.2.5",
"testcontainers": "^11.0.3", "testcontainers": "^11.0.3",
"turbo": "^2.5.4", "turbo": "^2.5.4",
@@ -53,9 +53,9 @@
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"packageManager": "pnpm@10.12.1", "packageManager": "pnpm@10.12.4",
"engines": { "engines": {
"node": ">=22.16.0" "node": ">=22.17.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
@@ -70,7 +70,7 @@
"tree-sitter-json" "tree-sitter-json"
], ],
"overrides": { "overrides": {
"proxmox-api>undici": "7.10.0" "proxmox-api>undici": "7.11.0"
}, },
"allowUnusedPatches": true, "allowUnusedPatches": true,
"ignoredBuiltDependencies": [ "ignoredBuiltDependencies": [

View File

@@ -41,11 +41,11 @@
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.3.0", "@kubernetes/client-node": "^1.3.0",
"@tanstack/react-query": "^5.80.10", "@tanstack/react-query": "^5.81.4",
"@trpc/client": "^11.4.2", "@trpc/client": "^11.4.3",
"@trpc/react-query": "^11.4.2", "@trpc/react-query": "^11.4.3",
"@trpc/server": "^11.4.2", "@trpc/server": "^11.4.3",
"@trpc/tanstack-react-query": "^11.4.2", "@trpc/tanstack-react-query": "^11.4.3",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"next": "15.3.4", "next": "15.3.4",
"react": "19.1.0", "react": "19.1.0",
@@ -59,7 +59,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
} }

View File

@@ -11,6 +11,7 @@ import { mediaTranscodingRouter } from "./media-transcoding";
import { minecraftRouter } from "./minecraft"; import { minecraftRouter } from "./minecraft";
import { networkControllerRouter } from "./network-controller"; import { networkControllerRouter } from "./network-controller";
import { notebookRouter } from "./notebook"; import { notebookRouter } from "./notebook";
import { notificationsRouter } from "./notifications";
import { optionsRouter } from "./options"; import { optionsRouter } from "./options";
import { releasesRouter } from "./releases"; import { releasesRouter } from "./releases";
import { rssFeedRouter } from "./rssFeed"; import { rssFeedRouter } from "./rssFeed";
@@ -37,4 +38,5 @@ export const widgetRouter = createTRPCRouter({
options: optionsRouter, options: optionsRouter,
releases: releasesRouter, releases: releasesRouter,
networkController: networkControllerRouter, networkController: networkControllerRouter,
notifications: notificationsRouter,
}); });

View File

@@ -0,0 +1,64 @@
import { observable } from "@trpc/server/observable";
import type { Modify } from "@homarr/common/types";
import type { Integration } from "@homarr/db/schema";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import type { Notification } from "@homarr/integrations";
import { notificationsRequestHandler } from "@homarr/request-handler/notifications";
import type { IntegrationAction } from "../../middlewares/integration";
import { createManyIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, publicProcedure } from "../../trpc";
const createNotificationsIntegrationMiddleware = (action: IntegrationAction) =>
createManyIntegrationMiddleware(action, ...getIntegrationKindsByCategory("notifications"));
export const notificationsRouter = createTRPCRouter({
getNotifications: publicProcedure
.unstable_concat(createNotificationsIntegrationMiddleware("query"))
.query(async ({ ctx }) => {
return await Promise.all(
ctx.integrations.map(async (integration) => {
const innerHandler = notificationsRequestHandler.handler(integration, {});
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return {
integration: {
id: integration.id,
name: integration.name,
kind: integration.kind,
updatedAt: timestamp,
},
data,
};
}),
);
}),
subscribeNotifications: publicProcedure
.unstable_concat(createNotificationsIntegrationMiddleware("query"))
.subscription(({ ctx }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"notifications"> }>;
data: Notification[];
}>((emit) => {
const unsubscribes: (() => void)[] = [];
for (const integrationWithSecrets of ctx.integrations) {
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
const innerHandler = notificationsRequestHandler.handler(integrationWithSecrets, {});
const unsubscribe = innerHandler.subscribe((data) => {
emit.next({
integration,
data,
});
});
unsubscribes.push(unsubscribe);
}
return () => {
unsubscribes.forEach((unsubscribe) => {
unsubscribe();
});
};
});
}),
});

View File

@@ -23,8 +23,8 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@auth/core": "^0.39.1", "@auth/core": "^0.40.0",
"@auth/drizzle-adapter": "^1.9.1", "@auth/drizzle-adapter": "^1.10.0",
"@homarr/certificates": "workspace:^0.1.0", "@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
@@ -34,9 +34,9 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"cookies": "^0.9.1", "cookies": "^0.9.1",
"ldapts": "8.0.1", "ldapts": "8.0.2",
"next": "15.3.4", "next": "15.3.4",
"next-auth": "5.0.0-beta.28", "next-auth": "5.0.0-beta.29",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"zod": "^3.25.67" "zod": "^3.25.67"
@@ -48,7 +48,7 @@
"@types/bcrypt": "5.0.2", "@types/bcrypt": "5.0.2",
"@types/cookies": "0.9.1", "@types/cookies": "0.9.1",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
} }

View File

@@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"undici": "7.10.0" "undici": "7.11.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",

View File

@@ -28,7 +28,7 @@
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.5.0" "dotenv": "^16.6.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",

View File

@@ -33,7 +33,7 @@
"next": "15.3.4", "next": "15.3.4",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"undici": "7.10.0", "undici": "7.11.0",
"zod": "^3.25.67", "zod": "^3.25.67",
"zod-validation-error": "^3.5.2" "zod-validation-error": "^3.5.2"
}, },

View File

@@ -10,11 +10,11 @@ const calculateTimeAgo = (timestamp: Date) => {
return dayjs().to(timestamp); return dayjs().to(timestamp);
}; };
export const useTimeAgo = (timestamp: Date) => { export const useTimeAgo = (timestamp: Date, updateFrequency = 1000) => {
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp)); const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp));
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), 1000); // update every second const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), updateFrequency);
return () => clearInterval(intervalId); // clear interval on hook unmount return () => clearInterval(intervalId); // clear interval on hook unmount
}, [timestamp]); }, [timestamp]);

View File

@@ -25,6 +25,7 @@ export const cronJobs = {
minecraftServerStatus: { preventManualExecution: false }, minecraftServerStatus: { preventManualExecution: false },
networkController: { preventManualExecution: false }, networkController: { preventManualExecution: false },
dockerContainers: { preventManualExecution: false }, dockerContainers: { preventManualExecution: false },
refreshNotifications: { preventManualExecution: false },
} satisfies Record<JobGroupKeys, { preventManualExecution?: boolean }>; } satisfies Record<JobGroupKeys, { preventManualExecution?: boolean }>;
/** /**

View File

@@ -11,6 +11,7 @@ import { mediaRequestListJob, mediaRequestStatsJob } from "./jobs/integrations/m
import { mediaServerJob } from "./jobs/integrations/media-server"; import { mediaServerJob } from "./jobs/integrations/media-server";
import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding"; import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding";
import { networkControllerJob } from "./jobs/integrations/network-controller"; import { networkControllerJob } from "./jobs/integrations/network-controller";
import { refreshNotificationsJob } from "./jobs/integrations/notifications";
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status"; import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
import { pingJob } from "./jobs/ping"; import { pingJob } from "./jobs/ping";
import { rssFeedsJob } from "./jobs/rss-feeds"; import { rssFeedsJob } from "./jobs/rss-feeds";
@@ -38,6 +39,7 @@ export const jobGroup = createCronJobGroup({
minecraftServerStatus: minecraftServerStatusJob, minecraftServerStatus: minecraftServerStatusJob,
dockerContainers: dockerContainersJob, dockerContainers: dockerContainersJob,
networkController: networkControllerJob, networkController: networkControllerJob,
refreshNotifications: refreshNotificationsJob,
}); });
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number]; export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];

View File

@@ -0,0 +1,14 @@
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler";
import { notificationsRequestHandler } from "@homarr/request-handler/notifications";
import { createCronJob } from "../../lib";
export const refreshNotificationsJob = createCronJob("refreshNotifications", EVERY_5_MINUTES).withCallback(
createRequestIntegrationJobHandler(notificationsRequestHandler.handler, {
widgetKinds: ["notifications"],
getInput: {
notifications: (options) => options,
},
}),
);

View File

@@ -38,18 +38,18 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@auth/core": "^0.39.1", "@auth/core": "^0.40.0",
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0",
"@homarr/env": "workspace:^0.1.0", "@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@testcontainers/mysql": "^11.0.3", "@testcontainers/mysql": "^11.0.3",
"better-sqlite3": "^11.10.0", "better-sqlite3": "^12.1.1",
"dotenv": "^16.5.0", "dotenv": "^16.6.0",
"drizzle-kit": "^0.31.1", "drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.2", "drizzle-orm": "^0.44.2",
"drizzle-zod": "^0.7.1", "drizzle-zod": "^0.7.1",
"mysql2": "3.14.1" "mysql2": "3.14.1"
@@ -62,7 +62,7 @@
"dotenv-cli": "^8.0.0", "dotenv-cli": "^8.0.0",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"tsx": "4.20.3", "tsx": "4.20.3",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }

View File

@@ -7,6 +7,7 @@ export const integrationSecretKindObject = {
password: { isPublic: false }, password: { isPublic: false },
tokenId: { isPublic: true }, tokenId: { isPublic: true },
realm: { isPublic: true }, realm: { isPublic: true },
topic: { isPublic: true },
} satisfies Record<string, { isPublic: boolean }>; } satisfies Record<string, { isPublic: boolean }>;
export const integrationSecretKinds = objectKeys(integrationSecretKindObject); export const integrationSecretKinds = objectKeys(integrationSecretKindObject);
@@ -169,6 +170,12 @@ export const integrationDefs = {
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png",
category: ["networkController"], category: ["networkController"],
}, },
ntfy: {
name: "ntfy",
secretKinds: [["topic"], ["topic", "apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg",
category: ["notifications"],
},
} as const satisfies Record<string, integrationDefinition>; } as const satisfies Record<string, integrationDefinition>;
export const integrationKinds = objectKeys(integrationDefs) as AtLeastOneOf<IntegrationKind>; export const integrationKinds = objectKeys(integrationDefs) as AtLeastOneOf<IntegrationKind>;
@@ -223,4 +230,5 @@ export type IntegrationCategory =
| "healthMonitoring" | "healthMonitoring"
| "search" | "search"
| "mediaTranscoding" | "mediaTranscoding"
| "networkController"; | "networkController"
| "notifications";

View File

@@ -25,5 +25,6 @@ export const widgetKinds = [
"healthMonitoring", "healthMonitoring",
"releases", "releases",
"dockerContainers", "dockerContainers",
"notifications",
] as const; ] as const;
export type WidgetKind = (typeof widgetKinds)[number]; export type WidgetKind = (typeof widgetKinds)[number];

View File

@@ -26,7 +26,7 @@
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.1.1", "@mantine/form": "^8.1.2",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -29,7 +29,7 @@
"@homarr/notifications": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"react": "19.1.0", "react": "19.1.0",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },

View File

@@ -38,11 +38,11 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@jellyfin/sdk": "^0.11.0", "@jellyfin/sdk": "^0.11.0",
"maria2": "^0.4.0", "maria2": "^0.4.1",
"node-ical": "^0.20.1", "node-ical": "^0.20.1",
"proxmox-api": "1.1.1", "proxmox-api": "1.1.1",
"tsdav": "^2.1.5", "tsdav": "^2.1.5",
"undici": "7.10.0", "undici": "7.11.0",
"xml2js": "^0.6.2", "xml2js": "^0.6.2",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },

View File

@@ -21,6 +21,7 @@ import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integrati
import { SonarrIntegration } from "../media-organizer/sonarr/sonarr-integration"; import { SonarrIntegration } from "../media-organizer/sonarr/sonarr-integration";
import { TdarrIntegration } from "../media-transcoding/tdarr-integration"; import { TdarrIntegration } from "../media-transcoding/tdarr-integration";
import { NextcloudIntegration } from "../nextcloud/nextcloud.integration"; import { NextcloudIntegration } from "../nextcloud/nextcloud.integration";
import { NTFYIntegration } from "../ntfy/ntfy-integration";
import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration"; import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration";
import { OverseerrIntegration } from "../overseerr/overseerr-integration"; import { OverseerrIntegration } from "../overseerr/overseerr-integration";
import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-factory"; import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-factory";
@@ -92,6 +93,7 @@ export const integrationCreators = {
emby: EmbyIntegration, emby: EmbyIntegration,
nextcloud: NextcloudIntegration, nextcloud: NextcloudIntegration,
unifiController: UnifiControllerIntegration, unifiController: UnifiControllerIntegration,
ntfy: NTFYIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>; } satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = { type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {

View File

@@ -1,26 +1,27 @@
// General integrations // General integrations
export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration"; export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration";
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
export { DelugeIntegration } from "./download-client/deluge/deluge-integration"; export { DelugeIntegration } from "./download-client/deluge/deluge-integration";
export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration"; export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
export { QBitTorrentIntegration } from "./download-client/qbittorrent/qbittorrent-integration"; export { QBitTorrentIntegration } from "./download-client/qbittorrent/qbittorrent-integration";
export { SabnzbdIntegration } from "./download-client/sabnzbd/sabnzbd-integration"; export { SabnzbdIntegration } from "./download-client/sabnzbd/sabnzbd-integration";
export { TransmissionIntegration } from "./download-client/transmission/transmission-integration"; export { TransmissionIntegration } from "./download-client/transmission/transmission-integration";
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration"; export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration";
export { DownloadClientIntegration } from "./interfaces/downloads/download-client-integration"; export { DownloadClientIntegration } from "./interfaces/downloads/download-client-integration";
export { JellyfinIntegration } from "./jellyfin/jellyfin-integration"; export { JellyfinIntegration } from "./jellyfin/jellyfin-integration";
export { JellyseerrIntegration } from "./jellyseerr/jellyseerr-integration"; export { JellyseerrIntegration } from "./jellyseerr/jellyseerr-integration";
export { LidarrIntegration } from "./media-organizer/lidarr/lidarr-integration";
export { RadarrIntegration } from "./media-organizer/radarr/radarr-integration"; export { RadarrIntegration } from "./media-organizer/radarr/radarr-integration";
export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integration";
export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration"; export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration";
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
export { NTFYIntegration } from "./ntfy/ntfy-integration";
export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration"; export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration";
export { OverseerrIntegration } from "./overseerr/overseerr-integration"; export { OverseerrIntegration } from "./overseerr/overseerr-integration";
export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5"; export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6"; export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export { PlexIntegration } from "./plex/plex-integration"; export { PlexIntegration } from "./plex/plex-integration";
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration"; export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
export { LidarrIntegration } from "./media-organizer/lidarr/lidarr-integration";
export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integration";
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
// Types // Types
export type { IntegrationInput } from "./base/integration"; export type { IntegrationInput } from "./base/integration";
@@ -34,6 +35,7 @@ export type { StreamSession } from "./interfaces/media-server/session";
export type { TdarrQueue } from "./interfaces/media-transcoding/queue"; export type { TdarrQueue } from "./interfaces/media-transcoding/queue";
export type { TdarrPieSegment, TdarrStatistics } from "./interfaces/media-transcoding/statistics"; export type { TdarrPieSegment, TdarrStatistics } from "./interfaces/media-transcoding/statistics";
export type { TdarrWorker } from "./interfaces/media-transcoding/workers"; export type { TdarrWorker } from "./interfaces/media-transcoding/workers";
export type { Notification } from "./interfaces/notifications/notification";
// Schemas // Schemas
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items"; export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";

View File

@@ -43,6 +43,7 @@ export interface MediaRequestStats {
users: RequestUser[]; users: RequestUser[];
} }
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L1
export enum MediaRequestStatus { export enum MediaRequestStatus {
PendingApproval = 1, PendingApproval = 1,
Approved = 2, Approved = 2,
@@ -51,6 +52,7 @@ export enum MediaRequestStatus {
Completed = 5, Completed = 5,
} }
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L14
export enum MediaAvailability { export enum MediaAvailability {
Unknown = 1, Unknown = 1,
Pending = 2, Pending = 2,
@@ -58,4 +60,5 @@ export enum MediaAvailability {
PartiallyAvailable = 4, PartiallyAvailable = 4,
Available = 5, Available = 5,
Blacklisted = 6, Blacklisted = 6,
Deleted = 7,
} }

View File

@@ -0,0 +1,6 @@
export interface Notification {
id: string;
time: Date;
title: string;
body: string;
}

View File

@@ -0,0 +1,6 @@
import { Integration } from "../../base/integration";
import type { Notification } from "./notification";
export abstract class NotificationsIntegration extends Integration {
public abstract getNotificationsAsync(): Promise<Notification[]>;
}

View File

@@ -0,0 +1,65 @@
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import type { IntegrationTestingInput } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { Notification } from "../interfaces/notifications/notification";
import { NotificationsIntegration } from "../interfaces/notifications/notifications-integration";
import { ntfyNotificationSchema } from "./ntfy-schema";
export class NTFYIntegration extends NotificationsIntegration {
public async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
await input.fetchAsync(this.url("/v1/account"), { headers: this.getHeaders() });
return { success: true };
}
private getTopicURL() {
return this.url(`/${encodeURIComponent(super.getSecretValue("topic"))}/json`, { poll: 1 });
}
private getHeaders() {
return this.hasSecretValue("apiKey") ? { Authorization: `Bearer ${super.getSecretValue("apiKey")}` } : {};
}
public async getNotificationsAsync() {
const url = this.getTopicURL();
const notifications = await Promise.all(
(
await fetchWithTrustedCertificatesAsync(url, { headers: this.getHeaders() })
.then((response) => {
if (!response.ok) throw new ResponseError(response);
return response.text();
})
.catch((error) => {
if (error instanceof Error) throw error;
else {
throw new Error("Error communicating with ntfy");
}
})
)
// response is provided as individual lines of JSON
.split("\n")
.map(async (line) => {
// ignore empty lines
if (line.length === 0) return null;
const json = JSON.parse(line) as unknown;
const parsed = await ntfyNotificationSchema.parseAsync(json);
if (parsed.event === "message") return parsed;
// ignore non-event messages
else return null;
}),
);
return notifications
.filter((notification) => notification !== null)
.map((notification): Notification => {
const topicURL = this.url(`/${notification.topic}`);
return {
id: notification.id,
time: new Date(notification.time * 1000),
title: notification.title ?? topicURL.hostname + topicURL.pathname,
body: notification.message,
};
});
}
}

View File

@@ -0,0 +1,12 @@
import { z } from "zod";
// There are more properties, see: https://docs.ntfy.sh/subscribe/api/#json-message-format
// Not all properties are required for this use case.
export const ntfyNotificationSchema = z.object({
id: z.string(),
time: z.number(),
event: z.string(), // we only care about "message"
topic: z.string(),
title: z.optional(z.string()),
message: z.string(),
});

View File

@@ -33,7 +33,7 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@tabler/icons-react": "^3.34.0", "@tabler/icons-react": "^3.34.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"next": "15.3.4", "next": "15.3.4",

View File

@@ -24,8 +24,8 @@
"dependencies": { "dependencies": {
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"react": "19.1.0" "react": "19.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -24,7 +24,7 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/notifications": "^8.1.1", "@mantine/notifications": "^8.1.2",
"@tabler/icons-react": "^3.34.0" "@tabler/icons-react": "^3.34.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -37,8 +37,8 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"adm-zip": "0.5.16", "adm-zip": "0.5.16",
"next": "15.3.4", "next": "15.3.4",
"react": "19.1.0", "react": "19.1.0",

View File

@@ -0,0 +1,20 @@
import dayjs from "dayjs";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import type { Notification } from "@homarr/integrations";
import { createIntegrationAsync } from "@homarr/integrations";
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
export const notificationsRequestHandler = createCachedIntegrationRequestHandler<
Notification[],
IntegrationKindByCategory<"notifications">,
Record<string, never>
>({
async requestAsync(integration) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getNotificationsAsync();
},
cacheDuration: dayjs.duration(5, "minutes"),
queryKey: "notificationsJobStatus",
});

View File

@@ -26,7 +26,7 @@
"@homarr/api": "workspace:^0.1.0", "@homarr/api": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.1.1", "@mantine/dates": "^8.1.2",
"next": "15.3.4", "next": "15.3.4",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0"

View File

@@ -33,9 +33,9 @@
"@homarr/settings": "workspace:^0.1.0", "@homarr/settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"@mantine/spotlight": "^8.1.1", "@mantine/spotlight": "^8.1.2",
"@tabler/icons-react": "^3.34.0", "@tabler/icons-react": "^3.34.0",
"jotai": "^2.12.5", "jotai": "^2.12.5",
"next": "15.3.4", "next": "15.3.4",

View File

@@ -33,7 +33,7 @@
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "15.3.4", "next": "15.3.4",
"next-intl": "4.1.0", "next-intl": "4.3.1",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0"
}, },

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "领域", "label": "领域",
"newLabel": "新领域" "newLabel": "新领域"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "等待处理", "pending": "等待处理",
"processing": "处理中", "processing": "处理中",
"partiallyAvailable": "部分", "partiallyAvailable": "部分",
"available": "可用" "available": "可用",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "待处理", "pending": "待处理",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "获取网络控制器概述失败" "internalServerError": "获取网络控制器概述失败"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "网络控制器" "label": "网络控制器"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "Docker 容器" "label": "Docker 容器"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Částečně dostupné", "partiallyAvailable": "Částečně dostupné",
"available": "K dispozici" "available": "K dispozici",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Realm", "label": "Realm",
"newLabel": "Nyt realm" "newLabel": "Nyt realm"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Afventende", "pending": "Afventende",
"processing": "Behandler", "processing": "Behandler",
"partiallyAvailable": "Delvis", "partiallyAvailable": "Delvis",
"available": "Tilgængelig" "available": "Tilgængelig",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "Afventende", "pending": "Afventende",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Kunne ikke hente Netværkskontroloversigt" "internalServerError": "Kunne ikke hente Netværkskontroloversigt"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "Netværkskontroller" "label": "Netværkskontroller"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "Docker containers" "label": "Docker containers"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Bereich", "label": "Bereich",
"newLabel": "Neuer Bereich" "newLabel": "Neuer Bereich"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Ausstehend", "pending": "Ausstehend",
"processing": "In Bearbeitung", "processing": "In Bearbeitung",
"partiallyAvailable": "Teilweise", "partiallyAvailable": "Teilweise",
"available": "Verfügbar" "available": "Verfügbar",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "Ausstehend", "pending": "Ausstehend",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Bereich", "label": "Bereich",
"newLabel": "Neuer Bereich" "newLabel": "Neuer Bereich"
},
"topic": {
"label": "Thema",
"newLabel": "Neues Thema erstellen"
} }
} }
}, },
@@ -1758,7 +1762,7 @@
"label": "Speicher-Info anzeigen" "label": "Speicher-Info anzeigen"
}, },
"showUptime": { "showUptime": {
"label": "" "label": "Laufzeit anzeigen"
}, },
"fileSystem": { "fileSystem": {
"label": "Dateisystem Info anzeigen" "label": "Dateisystem Info anzeigen"
@@ -1767,7 +1771,7 @@
"label": "Standard Tab" "label": "Standard Tab"
}, },
"visibleClusterSections": { "visibleClusterSections": {
"label": "" "label": "Sichtbare Cluster-Abschnitte"
}, },
"sectionIndicatorRequirement": { "sectionIndicatorRequirement": {
"label": "Anforderung der Sektionsindikatoren" "label": "Anforderung der Sektionsindikatoren"
@@ -1839,11 +1843,11 @@
} }
}, },
"dockerContainers": { "dockerContainers": {
"name": "", "name": "Docker Statistiken",
"description": "", "description": "Statistiken Ihrer Container (Dieses Widget kann nur mit Administratorrechten hinzugefügt werden)",
"option": {}, "option": {},
"error": { "error": {
"internalServerError": "" "internalServerError": "Fehler beim Abrufen der Container Statistiken"
} }
}, },
"common": { "common": {
@@ -1961,8 +1965,8 @@
"label": "Filter zur Berechnung des Verhältnisses verwenden" "label": "Filter zur Berechnung des Verhältnisses verwenden"
}, },
"limitPerIntegration": { "limitPerIntegration": {
"label": "", "label": "Elemente pro Integration begrenzen",
"description": "" "description": "Dies begrenzt die Anzahl der Elemente pro Integration, jedoch nicht global"
} }
}, },
"errors": { "errors": {
@@ -2088,7 +2092,9 @@
"pending": "Ausstehend", "pending": "Ausstehend",
"processing": "In Bearbeitung", "processing": "In Bearbeitung",
"partiallyAvailable": "Teilweise", "partiallyAvailable": "Teilweise",
"available": "Verfügbar" "available": "Verfügbar",
"blacklisted": "Gesperrt",
"deleted": "Gelöscht"
}, },
"status": { "status": {
"pending": "Ausstehend", "pending": "Ausstehend",
@@ -2227,13 +2233,13 @@
"label": "Repository hinzufügen" "label": "Repository hinzufügen"
}, },
"importRepositories": { "importRepositories": {
"label": "", "label": "Von Docker importieren",
"loading": "", "loading": "Lade Docker Images",
"noImagesFound": "", "noImagesFound": "Keine Docker Images gefunden",
"listFoundImages": "", "listFoundImages": "Liste der gefundenen Images",
"listAlreadyImportedImages": "", "listAlreadyImportedImages": "Liste der bereits importierten Images",
"allImagesAlreadyImported": "", "allImagesAlreadyImported": "Alle Images wurden bereits importiert",
"onlyAdminCanImport": "" "onlyAdminCanImport": "Nur Administratoren können vom Docker importieren"
}, },
"provider": { "provider": {
"label": "Anbieter" "label": "Anbieter"
@@ -2276,7 +2282,7 @@
} }
}, },
"importForm": { "importForm": {
"title": "" "title": "Von Docker importieren"
}, },
"example": { "example": {
"label": "Beispiel" "label": "Beispiel"
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Fehler beim Abrufen der Netzwerk Controller Zusammenfassung" "internalServerError": "Fehler beim Abrufen der Netzwerk Controller Zusammenfassung"
} }
},
"notifications": {
"name": "Benachrichtigungen",
"description": "Benachrichtigungshistorie von einer Integration anzeigen",
"noItems": "Keine Benachrichtigungen zum Anzeigen.",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,8 +3134,11 @@
"networkController": { "networkController": {
"label": "Netzwerk Controller" "label": "Netzwerk Controller"
}, },
"refreshNotifications": {
"label": "Benachrichtigungs Updater"
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": "Docker Container"
} }
} }
}, },
@@ -3189,7 +3204,7 @@
"updated": "Aktualisiert {when}", "updated": "Aktualisiert {when}",
"search": "{count} Container durchsuchen", "search": "{count} Container durchsuchen",
"selected": "{selectCount} von {totalCount} ausgewählten Containern", "selected": "{selectCount} von {totalCount} ausgewählten Containern",
"footer": "" "footer": "Insgesamt {count} Container"
}, },
"field": { "field": {
"name": { "name": {
@@ -3209,10 +3224,10 @@
}, },
"stats": { "stats": {
"cpu": { "cpu": {
"label": "" "label": "CPU"
}, },
"memory": { "memory": {
"label": "" "label": "Speicher"
} }
}, },
"containerImage": { "containerImage": {
@@ -3223,7 +3238,7 @@
} }
}, },
"action": { "action": {
"title": "", "title": "Aktionen",
"start": { "start": {
"label": "Starten", "label": "Starten",
"notification": { "notification": {

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Μερικώς", "partiallyAvailable": "Μερικώς",
"available": "Διαθέσιμο" "available": "Διαθέσιμο",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Realm", "label": "Realm",
"newLabel": "New realm" "newLabel": "New realm"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Realm", "label": "Realm",
"newLabel": "New realm" "newLabel": "New realm"
},
"topic": {
"label": "Topic",
"newLabel": "New topic"
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Pending", "pending": "Pending",
"processing": "Processing", "processing": "Processing",
"partiallyAvailable": "Partial", "partiallyAvailable": "Partial",
"available": "Available" "available": "Available",
"blacklisted": "Blacklisted",
"deleted": "Deleted"
}, },
"status": { "status": {
"pending": "Pending", "pending": "Pending",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Failed to fetch Network Controller Summary" "internalServerError": "Failed to fetch Network Controller Summary"
} }
},
"notifications": {
"name": "Notifications",
"description": "Display notification history from an integration",
"noItems": "No notifications to display.",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "Network Controller" "label": "Network Controller"
}, },
"refreshNotifications": {
"label": "Notification Updater"
},
"dockerContainers": { "dockerContainers": {
"label": "Docker containers" "label": "Docker containers"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Parcial", "partiallyAvailable": "Parcial",
"available": "Disponible" "available": "Disponible",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Domaine", "label": "Domaine",
"newLabel": "Nouveau domaine" "newLabel": "Nouveau domaine"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "En attente", "pending": "En attente",
"processing": "Traitement en cours", "processing": "Traitement en cours",
"partiallyAvailable": "Partiel", "partiallyAvailable": "Partiel",
"available": "Disponible" "available": "Disponible",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "En attente", "pending": "En attente",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "תחום", "label": "תחום",
"newLabel": "תחום חדש" "newLabel": "תחום חדש"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "בהמתנה", "pending": "בהמתנה",
"processing": "מעבד", "processing": "מעבד",
"partiallyAvailable": "חלקי", "partiallyAvailable": "חלקי",
"available": "זמין" "available": "זמין",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "ממתין", "pending": "ממתין",
@@ -2227,13 +2233,13 @@
"label": "הוסף מאגר" "label": "הוסף מאגר"
}, },
"importRepositories": { "importRepositories": {
"label": "", "label": "ייבוא מדוקר",
"loading": "", "loading": "טוען תמונות דוקר",
"noImagesFound": "", "noImagesFound": "לא נמצאו תמונות דוקר",
"listFoundImages": "", "listFoundImages": "רשימת התמונות שנמצאו",
"listAlreadyImportedImages": "", "listAlreadyImportedImages": "רשימת התמונות שכבר יובאו",
"allImagesAlreadyImported": "", "allImagesAlreadyImported": "כל התמונות כבר יובאו",
"onlyAdminCanImport": "" "onlyAdminCanImport": "רק מנהלים יכולים לייבא מדוקר"
}, },
"provider": { "provider": {
"label": "ספק" "label": "ספק"
@@ -2276,7 +2282,7 @@
} }
}, },
"importForm": { "importForm": {
"title": "" "title": "ייבוא מדוקר"
}, },
"example": { "example": {
"label": "דוגמא" "label": "דוגמא"
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "אחזור תקציר בקר הרשת נכשל" "internalServerError": "אחזור תקציר בקר הרשת נכשל"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "בקר רשת" "label": "בקר רשת"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "מכולות דוקר" "label": "מכולות דוקר"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "Feldolgozás", "processing": "Feldolgozás",
"partiallyAvailable": "Részleges", "partiallyAvailable": "Részleges",
"available": "Elérhető" "available": "Elérhető",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Parziale", "partiallyAvailable": "Parziale",
"available": "Disponibile" "available": "Disponibile",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

File diff suppressed because it is too large Load Diff

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Dalis", "partiallyAvailable": "Dalis",
"available": "Galima" "available": "Galima",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Daļējs", "partiallyAvailable": "Daļējs",
"available": "Pieejams" "available": "Pieejams",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Realm", "label": "Realm",
"newLabel": "Nieuwe Realm" "newLabel": "Nieuwe Realm"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "In afwachting", "pending": "In afwachting",
"processing": "Verwerken", "processing": "Verwerken",
"partiallyAvailable": "Gedeeltelijk", "partiallyAvailable": "Gedeeltelijk",
"available": "Beschikbaar" "available": "Beschikbaar",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "In afwachting", "pending": "In afwachting",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Netwerkcontroller samenvatting ophalen mislukt" "internalServerError": "Netwerkcontroller samenvatting ophalen mislukt"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "Netwerkcontroller" "label": "Netwerkcontroller"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Område", "label": "Område",
"newLabel": "Nytt område" "newLabel": "Nytt område"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Pågår", "pending": "Pågår",
"processing": "Prosesserer", "processing": "Prosesserer",
"partiallyAvailable": "Delvis", "partiallyAvailable": "Delvis",
"available": "Tilgjengelig" "available": "Tilgjengelig",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "Venter", "pending": "Venter",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Oczekujący", "pending": "Oczekujący",
"processing": "Przetwarzanie", "processing": "Przetwarzanie",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "Dostępne" "available": "Dostępne",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Parcial", "partiallyAvailable": "Parcial",
"available": "Disponível" "available": "Disponível",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Parțial", "partiallyAvailable": "Parțial",
"available": "Disponibil" "available": "Disponibil",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Область", "label": "Область",
"newLabel": "Новая область" "newLabel": "Новая область"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "В ожидании", "pending": "В ожидании",
"processing": "Обработка", "processing": "Обработка",
"partiallyAvailable": "Частично доступно", "partiallyAvailable": "Частично доступно",
"available": "Доступно" "available": "Доступно",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "В ожидании", "pending": "В ожидании",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Ríša", "label": "Ríša",
"newLabel": "Nová ríša" "newLabel": "Nová ríša"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Čakajúce", "pending": "Čakajúce",
"processing": "Spracovanie", "processing": "Spracovanie",
"partiallyAvailable": "Čiastočný", "partiallyAvailable": "Čiastočný",
"available": "K dispozícii" "available": "K dispozícii",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "Čakajúce", "pending": "Čakajúce",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Nepodarilo sa načítať súhrn sieťového ovládača" "internalServerError": "Nepodarilo sa načítať súhrn sieťového ovládača"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "Sieťový ovládač" "label": "Sieťový ovládač"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "Docker kontajnery" "label": "Docker kontajnery"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "", "partiallyAvailable": "",
"available": "" "available": "",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Delmängd", "partiallyAvailable": "Delmängd",
"available": "Tillgänglig" "available": "Tillgänglig",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "Erişim Alanı", "label": "Erişim Alanı",
"newLabel": "Yeni erişim alanı" "newLabel": "Yeni erişim alanı"
},
"topic": {
"label": "Konu",
"newLabel": "Yeni konu"
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "Bekleyen", "pending": "Bekleyen",
"processing": "İşlemde", "processing": "İşlemde",
"partiallyAvailable": "Kısmi", "partiallyAvailable": "Kısmi",
"available": "Mevcut" "available": "Mevcut",
"blacklisted": "Engellenenler",
"deleted": "Silinen"
}, },
"status": { "status": {
"pending": "Bekleyen", "pending": "Bekleyen",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "Ağ Denetleyicisi Özeti alınamadı" "internalServerError": "Ağ Denetleyicisi Özeti alınamadı"
} }
},
"notifications": {
"name": "Bildirimler",
"description": "Entegrasyonların bildirim geçmişini görüntüle",
"noItems": "Görüntülenecek bildirim yok.",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "Ağ Denetleyicisi" "label": "Ağ Denetleyicisi"
}, },
"refreshNotifications": {
"label": "Bildirim Güncelleyici"
},
"dockerContainers": { "dockerContainers": {
"label": "Docker konteynerleri" "label": "Docker konteynerleri"
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "В очікуванні", "pending": "В очікуванні",
"processing": "В обробці", "processing": "В обробці",
"partiallyAvailable": "Частково доступно", "partiallyAvailable": "Частково доступно",
"available": "Доступно" "available": "Доступно",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "", "label": "",
"newLabel": "" "newLabel": ""
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "", "pending": "",
"processing": "", "processing": "",
"partiallyAvailable": "Một phần", "partiallyAvailable": "Một phần",
"available": "Khả dụng" "available": "Khả dụng",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "", "pending": "",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "" "internalServerError": ""
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "" "label": ""
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -936,6 +936,10 @@
"realm": { "realm": {
"label": "領域", "label": "領域",
"newLabel": "新領域" "newLabel": "新領域"
},
"topic": {
"label": "",
"newLabel": ""
} }
} }
}, },
@@ -2088,7 +2092,9 @@
"pending": "等待處理中", "pending": "等待處理中",
"processing": "處理中", "processing": "處理中",
"partiallyAvailable": "部分", "partiallyAvailable": "部分",
"available": "待定" "available": "待定",
"blacklisted": "",
"deleted": ""
}, },
"status": { "status": {
"pending": "待處理", "pending": "待處理",
@@ -2357,6 +2363,12 @@
"error": { "error": {
"internalServerError": "無法獲取網路控制總覽" "internalServerError": "無法獲取網路控制總覽"
} }
},
"notifications": {
"name": "",
"description": "",
"noItems": "",
"option": {}
} }
}, },
"widgetPreview": { "widgetPreview": {
@@ -3122,6 +3134,9 @@
"networkController": { "networkController": {
"label": "網路控制" "label": "網路控制"
}, },
"refreshNotifications": {
"label": ""
},
"dockerContainers": { "dockerContainers": {
"label": "" "label": ""
} }

View File

@@ -29,9 +29,9 @@
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/dates": "^8.1.1", "@mantine/dates": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"@tabler/icons-react": "^3.34.0", "@tabler/icons-react": "^3.34.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "15.3.4", "next": "15.3.4",

View File

@@ -48,25 +48,25 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/charts": "^8.1.1", "@mantine/charts": "^8.1.2",
"@mantine/core": "^8.1.1", "@mantine/core": "^8.1.2",
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.2",
"@tabler/icons-react": "^3.34.0", "@tabler/icons-react": "^3.34.0",
"@tiptap/extension-color": "2.22.0", "@tiptap/extension-color": "2.23.0",
"@tiptap/extension-highlight": "2.22.0", "@tiptap/extension-highlight": "2.23.0",
"@tiptap/extension-image": "2.22.0", "@tiptap/extension-image": "2.23.0",
"@tiptap/extension-link": "^2.22.0", "@tiptap/extension-link": "^2.23.0",
"@tiptap/extension-table": "2.22.0", "@tiptap/extension-table": "2.23.0",
"@tiptap/extension-table-cell": "2.22.0", "@tiptap/extension-table-cell": "2.23.0",
"@tiptap/extension-table-header": "2.22.0", "@tiptap/extension-table-header": "2.23.0",
"@tiptap/extension-table-row": "2.22.0", "@tiptap/extension-table-row": "2.23.0",
"@tiptap/extension-task-item": "2.22.0", "@tiptap/extension-task-item": "2.23.0",
"@tiptap/extension-task-list": "2.22.0", "@tiptap/extension-task-list": "2.23.0",
"@tiptap/extension-text-align": "2.22.0", "@tiptap/extension-text-align": "2.23.0",
"@tiptap/extension-text-style": "2.22.0", "@tiptap/extension-text-style": "2.23.0",
"@tiptap/extension-underline": "2.22.0", "@tiptap/extension-underline": "2.23.0",
"@tiptap/react": "^2.22.0", "@tiptap/react": "^2.23.0",
"@tiptap/starter-kit": "^2.22.0", "@tiptap/starter-kit": "^2.23.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
@@ -74,7 +74,7 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"recharts": "^2.15.3", "recharts": "^2.15.4",
"video.js": "^8.23.3", "video.js": "^8.23.3",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },

View File

@@ -28,6 +28,7 @@ import * as minecraftServerStatus from "./minecraft/server-status";
import * as networkControllerStatus from "./network-controller/network-status"; import * as networkControllerStatus from "./network-controller/network-status";
import * as networkControllerSummary from "./network-controller/summary"; import * as networkControllerSummary from "./network-controller/summary";
import * as notebook from "./notebook"; import * as notebook from "./notebook";
import * as notifications from "./notifications";
import type { WidgetOptionDefinition } from "./options"; import type { WidgetOptionDefinition } from "./options";
import * as releases from "./releases"; import * as releases from "./releases";
import * as rssFeed from "./rssFeed"; import * as rssFeed from "./rssFeed";
@@ -67,6 +68,7 @@ export const widgetImports = {
minecraftServerStatus, minecraftServerStatus,
dockerContainers, dockerContainers,
releases, releases,
notifications,
} satisfies WidgetImportRecord; } satisfies WidgetImportRecord;
export type WidgetImports = typeof widgetImports; export type WidgetImports = typeof widgetImports;

View File

@@ -252,7 +252,11 @@ function getAvailabilityProperties(
return { color: "blue", label: t("availability.processing") }; return { color: "blue", label: t("availability.processing") };
case MediaAvailability.Pending: case MediaAvailability.Pending:
return { color: "violet", label: t("availability.pending") }; return { color: "violet", label: t("availability.pending") };
case MediaAvailability.Blacklisted:
return { color: "gray", label: t("availability.blacklisted") };
case MediaAvailability.Deleted:
return { color: "red", label: t("availability.deleted") };
default: default:
return { color: "red", label: t("availability.unknown") }; return { color: "orange", label: t("availability.unknown") };
} }
} }

View File

@@ -0,0 +1,106 @@
"use client";
import { useMemo } from "react";
import { Card, Flex, Group, ScrollArea, Stack, Text } from "@mantine/core";
import { IconClock } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { useTimeAgo } from "@homarr/common";
import { useScopedI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
export default function NotificationsWidget({ options, integrationIds }: WidgetComponentProps<"notifications">) {
const [notificationIntegrations] = clientApi.widget.notifications.getNotifications.useSuspenseQuery(
{
...options,
integrationIds,
},
{
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: false,
},
);
const utils = clientApi.useUtils();
clientApi.widget.notifications.subscribeNotifications.useSubscription(
{
...options,
integrationIds,
},
{
onData: (data) => {
utils.widget.notifications.getNotifications.setData({ ...options, integrationIds }, (prevData) => {
return prevData?.map((item) => {
if (item.integration.id !== data.integration.id) return item;
return {
data: data.data,
integration: {
...data.integration,
updatedAt: new Date(),
},
};
});
});
},
},
);
const t = useScopedI18n("widget.notifications");
const board = useRequiredBoard();
const sortedNotifications = useMemo(
() =>
notificationIntegrations
.flatMap((integration) => integration.data)
.sort((entryA, entryB) => entryB.time.getTime() - entryA.time.getTime()),
[notificationIntegrations],
);
return (
<ScrollArea className="scroll-area-w100" w="100%" p="sm">
<Stack w={"100%"} gap="sm">
{sortedNotifications.length > 0 ? (
sortedNotifications.map((notification) => (
<Card key={notification.id} withBorder radius={board.itemRadius} w="100%" p="sm">
<Flex gap="sm" direction="column" w="100%">
{notification.title && (
<Text fz="sm" lh="sm" lineClamp={2}>
{notification.title}
</Text>
)}
<Text c="dimmed" size="sm" lineClamp={4} style={{ whiteSpace: "pre-line" }}>
{notification.body}
</Text>
<InfoDisplay date={notification.time} />
</Flex>
</Card>
))
) : (
<Text size="sm" c="dimmed">
{t("noItems")}
</Text>
)}
</Stack>
</ScrollArea>
);
}
const InfoDisplay = ({ date }: { date: Date }) => {
const timeAgo = useTimeAgo(date, 30000); // update every 30sec
return (
<Group gap={5} align={"center"}>
<IconClock size={"1rem"} color={"var(--mantine-color-dimmed)"} />
<Text size="sm" c="dimmed">
{timeAgo}
</Text>
</Group>
);
};

View File

@@ -0,0 +1,14 @@
import { IconMessage } from "@tabler/icons-react";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
export const { componentLoader, definition } = createWidgetDefinition("notifications", {
icon: IconMessage,
createOptions() {
return optionsBuilder.from(() => ({}));
},
supportedIntegrations: getIntegrationKindsByCategory("notifications"),
}).withDynamicImport(() => import("./component"));

1790
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,11 +20,11 @@
"@next/eslint-plugin-next": "15.3.4", "@next/eslint-plugin-next": "15.3.4",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-config-turbo": "^2.5.4", "eslint-config-turbo": "^2.5.4",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"typescript-eslint": "^8.34.1" "typescript-eslint": "^8.35.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",

View File

@@ -11,11 +11,11 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.2", "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
"prettier": "^3.5.3" "prettier": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"prettier-plugin-packagejson": "^2.5.15", "prettier-plugin-packagejson": "^2.5.16",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
} }