merge: integrate dev branch, resolve package.json and lockfile conflicts

Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]andajnart committed 2026-03-13 09:20:05 +00:00
1 parent 1e438ec3c3
commit 6cbf72462a
117 files changed
+4495 -1648

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.55.0
- 1.54.0
- 1.53.0
- 1.52.0
@@ -65,7 +65,7 @@ jobs:
- uses: actions/setup-node@v6
if: env.SKIP_RELEASE == 'false'
with:
node-version: 24.13.1
node-version: 24.14.0
cache: "pnpm"
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
@@ -128,7 +128,7 @@ jobs:
images: "${{ env.GHCR_REPO }}"
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -167,7 +167,7 @@ jobs:
images: "${{ env.GHCR_REPO }}"
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -238,7 +238,7 @@ jobs:
permission-contents: write # required to publish the release
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
+1 -1
View File
@@ -64,7 +64,7 @@ jobs:
- name: Create Pull Request
id: create-pull-request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
commit-message: "chore: update bug report template"
+1 -1
View File
@@ -1 +1 @@
24.13.1
24.14.0
+20
View File
@@ -1,3 +1,23 @@
## [1.55.0](https://github.com/homarr-labs/homarr/compare/v1.54.0...v1.55.0) (2026-03-06)
### Features
* add seerr integration ([#5139](https://github.com/homarr-labs/homarr/issues/5139)) ([0e29a2f](https://github.com/homarr-labs/homarr/commit/0e29a2fce172cbfda083b899c01b92bd8b6210b7))
### Bug Fixes
* **deps:** update dependency fast-xml-parser to ^5.4.1 ([#5150](https://github.com/homarr-labs/homarr/issues/5150)) ([eb2f85f](https://github.com/homarr-labs/homarr/commit/eb2f85f6f8c9d7d90c1aeec079225678dfef4dd0))
* **deps:** update dependency ioredis to v5.10.0 ([#5169](https://github.com/homarr-labs/homarr/issues/5169)) ([1c06e56](https://github.com/homarr-labs/homarr/commit/1c06e562c33bd9f1387cb05a3dece7beedfe43c2))
* **deps:** update dependency ldapts to v8.1.7 ([#5168](https://github.com/homarr-labs/homarr/issues/5168)) ([ec45aac](https://github.com/homarr-labs/homarr/commit/ec45aacc63766c51a99ead6130bade442124e70d))
* **deps:** update dependency mysql2 to v3.17.5 ([#5140](https://github.com/homarr-labs/homarr/issues/5140)) ([66ad67e](https://github.com/homarr-labs/homarr/commit/66ad67eced587fd49a470395298477dc09cdd225))
* **deps:** update dependency mysql2 to v3.18.0 ([#5141](https://github.com/homarr-labs/homarr/issues/5141)) ([52792af](https://github.com/homarr-labs/homarr/commit/52792af186a6eb3519aafdee5a7c0dd03a86a461))
* **deps:** update dependency mysql2 to v3.18.1 ([#5152](https://github.com/homarr-labs/homarr/issues/5152)) ([8bf2a43](https://github.com/homarr-labs/homarr/commit/8bf2a43e065671c7c119ef1da5aa09d284db0ede))
* **deps:** update dependency mysql2 to v3.18.2 ([#5164](https://github.com/homarr-labs/homarr/issues/5164)) ([4717a57](https://github.com/homarr-labs/homarr/commit/4717a5724d4a6286c0f2bb92cf1e1fe17903a13a))
* **deps:** update dependency pg to ^8.19.0 ([#5153](https://github.com/homarr-labs/homarr/issues/5153)) ([0b0fb1c](https://github.com/homarr-labs/homarr/commit/0b0fb1c5798357d7bc847dfe577b26529be8aa5c))
* **deps:** update dependency swagger-ui-react to ^5.32.0 ([#5167](https://github.com/homarr-labs/homarr/issues/5167)) ([ff11599](https://github.com/homarr-labs/homarr/commit/ff1159943c967b6fea44894765c2210c3cdd44cc))
* **deps:** update dependency typescript-eslint to ^8.56.1 ([#5142](https://github.com/homarr-labs/homarr/issues/5142)) ([ac3b4e7](https://github.com/homarr-labs/homarr/commit/ac3b4e7de0460cab7723dcc1a5a8e14072a37d25))
* **deps:** update trpc monorepo to ^11.11.0 ([#5174](https://github.com/homarr-labs/homarr/issues/5174)) ([bc2a532](https://github.com/homarr-labs/homarr/commit/bc2a532a03d8cb76270f52b766f1a7f46d704096))
## [1.54.0](https://github.com/homarr-labs/homarr/compare/v1.53.2...v1.54.0) (2026-02-27)
### Features
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24.13.1-alpine AS base
FROM node:24.14.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
+14 -14
View File
@@ -54,21 +54,21 @@
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.3.15",
"@mantine/core": "^8.3.15",
"@mantine/dropzone": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@mantine/modals": "^8.3.15",
"@mantine/tiptap": "^8.3.15",
"@mantine/colors-generator": "^8.3.16",
"@mantine/core": "^8.3.16",
"@mantine/dropzone": "^8.3.16",
"@mantine/hooks": "^8.3.16",
"@mantine/modals": "^8.3.16",
"@mantine/tiptap": "^8.3.16",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.37.1",
"@tabler/icons-react": "^3.40.0",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-query-next-experimental": "^5.91.0",
"@trpc/client": "^11.10.0",
"@trpc/next": "^11.10.0",
"@trpc/react-query": "^11.10.0",
"@trpc/server": "^11.10.0",
"@trpc/client": "^11.12.0",
"@trpc/next": "^11.12.0",
"@trpc/react-query": "^11.12.0",
"@trpc/server": "^11.12.0",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "^6.0.0",
@@ -92,7 +92,7 @@
"react-simple-code-editor": "^0.14.1",
"sass": "^1.97.3",
"superjson": "2.2.6",
"swagger-ui-react": "^5.31.2",
"swagger-ui-react": "^5.32.0",
"use-deep-compare-effect": "^1.8.1",
"ws": "^8.19.0",
"zod": "^4.2.1"
@@ -102,7 +102,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.2",
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"@types/prismjs": "^1.26.6",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
@@ -110,7 +110,7 @@
"@types/ws": "^8.18.1",
"concurrently": "^9.2.1",
"esbuild": "^0.27.3",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"node-loader": "^2.1.0",
"prettier": "^3.8.1",
"tsx": "4.20.4",
@@ -1,9 +1,10 @@
import { useMemo, useState } from "react";
import { Button, Card, Center, Grid, Input, Stack, Text } from "@mantine/core";
import { Avatar, Button, Card, Center, Grid, Group, Input, Stack, Text, Tooltip } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { objectEntries } from "@homarr/common";
import type { WidgetKind } from "@homarr/definitions";
import { getIconUrl, getIntegrationName } from "@homarr/definitions";
import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";
@@ -21,6 +22,10 @@ export const ItemSelectModal = createModal<void>(({ actions }) => {
objectEntries(widgetImports)
.map(([kind, value]) => ({
kind,
supportedIntegrations:
"supportedIntegrations" in value.definition
? value.definition.supportedIntegrations.filter((integration) => integration !== "mock")
: [],
icon: value.definition.icon,
name: t(`widget.${kind}.name`),
description: t(`widget.${kind}.description`),
@@ -74,6 +79,7 @@ const WidgetItem = ({
}: {
item: {
kind: WidgetKind;
supportedIntegrations: IntegrationKind[];
name: string;
description: string;
icon: TablerIcon;
@@ -97,7 +103,8 @@ const WidgetItem = ({
{item.description}
</Text>
</Stack>
<Button onClick={onSelect} variant="light" size="xs" mt="auto" radius="md" fullWidth>
<SupportedIntegrations mt="auto" integrations={item.supportedIntegrations} />
<Button onClick={onSelect} variant="light" size="xs" radius="md" fullWidth>
{t(`item.create.addToBoard`)}
</Button>
</Stack>
@@ -105,3 +112,48 @@ const WidgetItem = ({
</Grid.Col>
);
};
interface SupportedIntegrationsProps {
integrations: IntegrationKind[];
mt: string;
}
const SupportedIntegrations = ({ integrations, mt }: SupportedIntegrationsProps) => {
if (integrations.length === 0) {
return null;
}
// When there are 8 or more integrations, we show 6 and a "+X" avatar. Otherwise, we show all integrations.
const countToShow = integrations.length >= 8 ? 6 : 7;
const moreCount = integrations.length - countToShow;
return (
<Center mt={mt}>
<Tooltip.Group openDelay={300} closeDelay={100}>
<Group gap={2}>
{integrations.slice(0, countToShow).map((integration) => (
<Tooltip key={integration} label={getIntegrationName(integration)} withArrow>
<Avatar src={getIconUrl(integration)} size="xs" radius="xl" />
</Tooltip>
))}
{moreCount > 0 && (
<Tooltip
withArrow
label={
<>
{integrations.slice(countToShow).map((integration) => (
<div key={integration}>{getIntegrationName(integration)}</div>
))}
</>
}
>
<Avatar radius="xl" size="xs">
+{moreCount}
</Avatar>
</Tooltip>
)}
</Group>
</Tooltip.Group>
</Center>
);
};
+3 -3
View File
@@ -39,7 +39,7 @@
"@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.19",
"dotenv": "^17.3.1",
"fastify": "^5.7.4",
"fastify": "^5.8.2",
"superjson": "2.2.6",
"undici": "7.18.2"
},
@@ -47,10 +47,10 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"dotenv-cli": "^11.0.0",
"esbuild": "^0.27.3",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"prettier": "^3.8.1",
"tsx": "4.20.4",
"typescript": "^5.9.3"
+1 -1
View File
@@ -35,7 +35,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1",
"esbuild": "^0.27.3",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24.13.1-trixie AS base
FROM node:24.14.0-trixie AS base
WORKDIR /app
COPY package.json .
COPY pnpm-lock.yaml .
+3 -3
View File
@@ -4,10 +4,10 @@
"dependencies": {
"better-sqlite3": "^12.6.2"
},
"packageManager": "pnpm@10.30.1",
"packageManager": "pnpm@10.31.0",
"engines": {
"node": ">=24.13.1",
"pnpm": ">=10.30.1"
"node": ">=24.14.0",
"pnpm": ">=10.31.0"
},
"pnpm": {
"onlyBuiltDependencies": [
+26 -12
View File
@@ -155,6 +155,13 @@
</a>
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/immich" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/immich.svg" alt="Immich" width="90" height="90" />
<br/>
<p align="center">Immich</p>
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
<br/>
@@ -195,15 +202,15 @@
<br/>
<p align="center">NPM</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/ntfy" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg" alt="ntfy" width="90" height="90" />
<br/>
<p align="center">ntfy</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/nzbget" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg" alt="NZBGet" width="90" height="90" />
<br/>
@@ -244,15 +251,15 @@
<br/>
<p align="center">Plex</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/prowlarr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg" alt="Prowlarr" width="90" height="90" />
<br/>
<p align="center">Prowlarr</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/proxmox" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/proxmox.svg" alt="Proxmox" width="90" height="90" />
<br/>
@@ -293,6 +300,13 @@
<br/>
<p align="center">SABnzbd</p>
</a>
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/seerr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/seerr.svg" alt="Seerr" width="90" height="90" />
<br/>
<p align="center">Seerr</p>
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/slskd" target="_blank" rel="noreferrer noopener">
@@ -300,8 +314,8 @@
<br/>
<p align="center">Slskd</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/sonarr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg" alt="Sonarr" width="90" height="90" />
<br/>
@@ -335,8 +349,8 @@
<br/>
<p align="center">Unifi<br/>Controller</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/unraid" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg" alt="Unraid" width="90" height="90" />
<br/>
+2 -2
View File
@@ -15,7 +15,7 @@ describe("Health checks", () => {
// Assert
expect(readyResponse.status).toBe(200);
expect(liveResponse.status).toBe(200);
}, 20_000);
}, 60_000);
test("ready and live should return 200 OK with external redis", async () => {
// Arrange
@@ -42,5 +42,5 @@ describe("Health checks", () => {
liveResponse.status,
`Expected live to return OK statusCode=${liveResponse.status} content=${await liveResponse.text()}`,
).toBe(200);
}, 20_000);
}, 60_000);
});
+1 -1
View File
@@ -12,5 +12,5 @@ describe("Home", () => {
// Assert
expect(homeResponse.status).toBe(200);
}, 20_000);
}, 60_000);
});
+5
View File
@@ -18,6 +18,11 @@ export const createHomarrContainer = (
.withExposedPorts(7575)
.withEnvironment({
...options.environment,
// We disable external connections due to the following reasons:
// - No icons have to be downloaded (11k)
// - No analytics data is sent
// - It is just faster and makes the tests more reliable (better performance)
NO_EXTERNAL_CONNECTION: true.toString(),
SECRET_ENCRYPTION_KEY: "0".repeat(64),
})
.withBindMounts(
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.54.0",
"version": "1.55.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -42,29 +42,29 @@
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.6",
"@semantic-release/npm": "^13.1.4",
"@semantic-release/npm": "^13.1.5",
"@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.12.0",
"@turbo/gen": "^2.8.9",
"@turbo/gen": "^2.8.14",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
"@vitest/ui": "^4.0.18",
"conventional-changelog-conventionalcommits": "^9.1.0",
"conventional-changelog-conventionalcommits": "^9.3.0",
"cross-env": "^10.1.0",
"jsdom": "^28.1.0",
"json5": "^2.2.3",
"prettier": "^3.8.1",
"semantic-release": "^25.0.3",
"testcontainers": "^11.12.0",
"turbo": "^2.8.9",
"turbo": "^2.8.14",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18"
},
"packageManager": "pnpm@10.30.1",
"packageManager": "pnpm@10.31.0",
"engines": {
"node": ">=24.13.1",
"pnpm": ">=10.30.1"
"node": ">=24.14.0",
"pnpm": ">=10.31.0"
},
"pnpm": {
"onlyBuiltDependencies": [
@@ -81,12 +81,12 @@
"overrides": {
"@babel/helpers@<7.26.10": ">=7.28.6",
"@babel/runtime@<7.26.10": ">=7.28.6",
"axios@>=1.0.0 <1.8.2": ">=1.13.5",
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.3",
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"axios@>=1.0.0 <1.8.2": ">=1.13.6",
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.4",
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.4",
"esbuild@<=0.24.2": ">=0.27.3",
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
"hono@<4.6.5": ">=4.11.9",
"hono@<4.6.5": ">=4.12.5",
"linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0",
@@ -94,8 +94,8 @@
"react-is": "^19.2.4",
"rollup@>=4.0.0 <4.22.4": ">=4.59.0",
"sha.js@<=2.4.11": ">=2.4.12",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2",
"tmp@<=0.2.3": ">=0.2.5",
"vite@>=5.0.0 <=5.4.18": ">=7.3.1"
},
+1 -1
View File
@@ -35,7 +35,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -42,10 +42,10 @@
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.4.0",
"@tanstack/react-query": "^5.90.21",
"@trpc/client": "^11.10.0",
"@trpc/react-query": "^11.10.0",
"@trpc/server": "^11.10.0",
"@trpc/tanstack-react-query": "^11.10.0",
"@trpc/client": "^11.12.0",
"@trpc/react-query": "^11.12.0",
"@trpc/server": "^11.12.0",
"@trpc/tanstack-react-query": "^11.12.0",
"lodash.clonedeep": "^4.5.0",
"next": "16.1.6",
"react": "19.2.4",
@@ -58,7 +58,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
}
+42
View File
@@ -0,0 +1,42 @@
import { z } from "zod/v4";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import {
immichAlbumRequestHandler,
immichAlbumsRequestHandler,
immichStatsRequestHandler,
} from "@homarr/request-handler/immich";
import type { IntegrationAction } from "../../middlewares/integration";
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, publicProcedure } from "../../trpc";
const createImmichIntegrationMiddleware = (action: IntegrationAction) =>
createOneIntegrationMiddleware(action, ...getIntegrationKindsByCategory("photoService"));
export const immichRouter = createTRPCRouter({
getServerStats: publicProcedure.concat(createImmichIntegrationMiddleware("query")).query(async ({ ctx }) => {
const innerHandler = immichStatsRequestHandler.handler(ctx.integration, {});
const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data.data;
}),
getAlbum: publicProcedure
.concat(createImmichIntegrationMiddleware("query"))
.input(
z.object({
albumId: z.string(),
}),
)
.query(async ({ ctx, input }) => {
const innerHandler = immichAlbumRequestHandler.handler(ctx.integration, { albumId: input.albumId });
const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data.data;
}),
getAlbums: publicProcedure.concat(createImmichIntegrationMiddleware("query")).query(async ({ ctx }) => {
const innerHandler = immichAlbumsRequestHandler.handler(ctx.integration, {});
const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data.data;
}),
});
+1
View File
@@ -4,6 +4,7 @@ import { createTRPCRouter } from "../../trpc";
export const widgetRouter = createTRPCRouter({
coolify: lazy(() => import("./coolify").then((mod) => mod.coolifyRouter)),
immich: lazy(() => import("./immich").then((mod) => mod.immichRouter)),
notebook: lazy(() => import("./notebook").then((mod) => mod.notebookRouter)),
weather: lazy(() => import("./weather").then((mod) => mod.weatherRouter)),
app: lazy(() => import("./app").then((mod) => mod.appRouter)),
+2 -2
View File
@@ -33,7 +33,7 @@
"@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^6.0.0",
"cookies": "^0.9.1",
"ldapts": "8.1.6",
"ldapts": "8.1.7",
"next": "16.1.6",
"next-auth": "5.0.0-beta.30",
"react": "19.2.4",
@@ -46,7 +46,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"@types/cookies": "0.9.2",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+4 -2
View File
@@ -19,7 +19,9 @@
"clean": "rm -rf .turbo node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
"run": "pnpm with-env node cli.cjs",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e .env --"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
@@ -35,7 +37,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"esbuild": "^0.27.3",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
@@ -0,0 +1,39 @@
import { command, string } from "@drizzle-team/brocli";
import { db, eq } from "@homarr/db";
import { integrations } from "@homarr/db/schema";
export const integrationsDelete = command({
name: "delete",
desc: "Delete an integration by id or name",
options: {
id: string("id").alias("i").desc("ID of the integration"),
name: string("name").alias("n").desc("Name of the integration"),
},
// eslint-disable-next-line no-restricted-syntax
handler: async (options) => {
if (!options.id && !options.name) {
console.error("Either --id or --name must be provided");
return;
}
let entry;
if (options.id) {
entry = await db.query.integrations.findFirst({
where: eq(integrations.id, options.id),
});
} else if (options.name) {
entry = await db.query.integrations.findFirst({
where: eq(integrations.name, options.name),
});
}
if (!entry) {
console.error("Integration not found");
return;
}
await db.delete(integrations).where(eq(integrations.id, entry.id));
console.log(`Integration ${entry.name} (${entry.id}) deleted`);
},
});
@@ -0,0 +1,21 @@
import { command } from "@drizzle-team/brocli";
import { db } from "@homarr/db";
export const integrationsList = command({
name: "list",
desc: "List all integrations (id and name)",
// eslint-disable-next-line no-restricted-syntax
handler: async () => {
const all = await db.query.integrations.findMany();
if (all.length === 0) {
console.log("No integrations found");
return;
}
console.log("ID\t\t\t\tName");
for (const i of all) {
console.log(`${i.id}\t${i.name}`);
}
},
});
+10
View File
@@ -0,0 +1,10 @@
import { command } from "@drizzle-team/brocli";
import { integrationsDelete } from "./integrations-delete";
import { integrationsList } from "./integrations-list";
export const integrationsRoot = command({
name: "integrations",
desc: "Group of commands to manage integrations",
subcommands: [integrationsList, integrationsDelete],
});
+39
View File
@@ -0,0 +1,39 @@
import { command, string } from "@drizzle-team/brocli";
import { db, eq } from "@homarr/db";
import { users } from "@homarr/db/schema";
export const usersDelete = command({
name: "delete",
desc: "Delete a user by id or username",
options: {
id: string("id").alias("i").desc("ID of the user"),
username: string("username").alias("u").desc("Name of the user"),
},
// eslint-disable-next-line no-restricted-syntax
handler: async (options) => {
if (!options.id && !options.username) {
console.error("Either --id or --username must be provided");
return;
}
let user;
if (options.id) {
user = await db.query.users.findFirst({
where: eq(users.id, options.id),
});
} else if (options.username) {
user = await db.query.users.findFirst({
where: eq(users.name, options.username),
});
}
if (!user) {
console.error("User not found");
return;
}
await db.delete(users).where(eq(users.id, user.id));
console.log(`User ${user.name ?? user.id} (${user.id}) deleted`);
},
});
+22
View File
@@ -0,0 +1,22 @@
import { command } from "@drizzle-team/brocli";
import { db } from "@homarr/db";
export const usersList = command({
name: "list",
desc: "List all users (id, username, email)",
// eslint-disable-next-line no-restricted-syntax
handler: async () => {
const allUsers = await db.query.users.findMany();
if (allUsers.length === 0) {
console.log("No users found");
return;
}
console.log("ID\t\t\t\tUsername\tEmail");
for (const user of allUsers) {
console.log(`${user.id}\t${user.name ?? ""}\t${user.email ?? ""}`);
}
},
});
@@ -0,0 +1,50 @@
import { command, string } from "@drizzle-team/brocli";
import { hashPasswordAsync } from "@homarr/auth";
import { db, eq } from "@homarr/db";
import { sessions, users } from "@homarr/db/schema";
export const usersUpdatePassword = command({
name: "update-password",
desc: "Update the password for a user (credentials provider only)",
options: {
id: string("id").alias("i").desc("ID of the user"),
username: string("username").alias("u").desc("Name of the user"),
password: string("password").alias("p").required().desc("New password"),
},
// eslint-disable-next-line no-restricted-syntax
handler: async (options) => {
if (!process.env.AUTH_PROVIDERS?.toLowerCase().includes("credentials")) {
console.error("Credentials provider is not enabled");
return;
}
if (!options.id && !options.username) {
console.error("Either --id or --username must be provided");
return;
}
const user = await db.query.users.findFirst({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
where: options.id ? eq(users.id, options.id) : eq(users.name, options.username!),
});
if (!user?.salt) {
console.error("User not found or has no credentials record");
return;
}
await db
.update(users)
.set({
password: await hashPasswordAsync(options.password, user.salt),
})
.where(eq(users.id, user.id));
// invalidate existing sessions
await db.delete(sessions).where(eq(sessions.userId, user.id));
console.log(`All sessions for user ${user.name ?? user.id} have been deleted`);
console.log("Password updated successfully");
},
});
+11
View File
@@ -0,0 +1,11 @@
import { command } from "@drizzle-team/brocli";
import { usersDelete } from "./users-delete";
import { usersList } from "./users-list";
import { usersUpdatePassword } from "./users-update-password";
export const usersRoot = command({
name: "users",
desc: "Group of commands to manage users",
subcommands: [usersList, usersDelete, usersUpdatePassword],
});
+3 -1
View File
@@ -1,10 +1,12 @@
import { run } from "@drizzle-team/brocli";
import { fixUsernames } from "./commands/fix-usernames";
import { integrationsRoot } from "./commands/integrations";
import { recreateAdmin } from "./commands/recreate-admin";
import { resetPassword } from "./commands/reset-password";
import { usersRoot } from "./commands/users";
const commands = [resetPassword, fixUsernames, recreateAdmin];
const commands = [usersRoot, integrationsRoot, resetPassword, fixUsernames, recreateAdmin];
void run(commands, {
name: "homarr-cli",
+1 -1
View File
@@ -42,7 +42,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -41,9 +41,9 @@
"better-sqlite3": "^12.6.2",
"dns-caching": "^0.2.9",
"drizzle-orm": "^0.45.1",
"ioredis": "5.9.3",
"mysql2": "3.17.4",
"pg": "^8.18.0",
"ioredis": "5.10.0",
"mysql2": "3.19.0",
"pg": "^8.20.0",
"superjson": "2.2.6",
"winston": "3.19.0",
"zod": "^4.2.1"
@@ -53,8 +53,8 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"eslint": "^9.39.3",
"@types/pg": "^8.18.0",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -29,9 +29,9 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0",
"@tanstack/react-query": "^5.90.21",
"@trpc/client": "^11.10.0",
"@trpc/server": "^11.10.0",
"@trpc/tanstack-react-query": "^11.10.0",
"@trpc/client": "^11.12.0",
"@trpc/server": "^11.12.0",
"@trpc/tanstack-react-query": "^11.12.0",
"node-cron": "^4.2.1",
"react": "19.2.4",
"zod": "^4.2.1"
@@ -42,7 +42,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"@types/react": "19.2.14",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -34,7 +34,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -47,7 +47,7 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@mantine/core": "^8.3.16",
"@paralleldrive/cuid2": "^3.3.0",
"@testcontainers/mysql": "^11.12.0",
"@testcontainers/postgresql": "^11.12.0",
@@ -56,8 +56,8 @@
"drizzle-kit": "^0.31.9",
"drizzle-orm": "^0.45.1",
"drizzle-zod": "^0.8.3",
"mysql2": "3.17.4",
"pg": "^8.18.0",
"mysql2": "3.19.0",
"pg": "^8.20.0",
"superjson": "2.2.6"
},
"devDependencies": {
@@ -65,10 +65,10 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"@types/pg": "^8.18.0",
"dotenv-cli": "^11.0.0",
"esbuild": "^0.27.3",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"prettier": "^3.8.1",
"tsx": "4.20.4",
"typescript": "^5.9.3"
+2 -2
View File
@@ -24,14 +24,14 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"fast-xml-parser": "^5.3.7",
"fast-xml-parser": "^5.4.2",
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"tsx": "4.20.4",
"typescript": "^5.9.3"
}
@@ -185,6 +185,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/radarr"
| "/docs/integrations/readarr"
| "/docs/integrations/sabnzbd"
| "/docs/integrations/seerr"
| "/docs/integrations/slskd"
| "/docs/integrations/sonarr"
| "/docs/integrations/tdarr"
+16
View File
@@ -143,6 +143,13 @@ export const integrationDefs = {
category: ["mediaSearch", "mediaRequest", "search"],
documentationUrl: createDocumentationLink("/docs/integrations/jellyseerr"),
},
seerr: {
name: "Seerr",
secretKinds: [["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/seerr.svg",
category: ["mediaSearch", "mediaRequest", "search"],
documentationUrl: createDocumentationLink("/docs/integrations/seerr"),
},
overseerr: {
name: "Overseerr",
secretKinds: [["apiKey"]],
@@ -327,6 +334,14 @@ export const integrationDefs = {
// @ts-expect-error - docs page will be created when integration is merged
documentationUrl: createDocumentationLink("/docs/integrations/coolify"),
},
immich: {
name: "Immich",
secretKinds: [["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/immich.svg",
category: ["photoService"],
// @ts-expect-error TS2345
documentationUrl: createDocumentationLink("/docs/integrations/immich"),
},
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
mock: {
name: "Mock",
@@ -413,6 +428,7 @@ export const integrationCategories = [
"releasesProvider",
"notifications",
"firewall",
"photoService",
] as const;
export type IntegrationCategory = (typeof integrationCategories)[number];
+2
View File
@@ -31,5 +31,7 @@ export const widgetKinds = [
"systemResources",
"coolify",
"systemDisks",
"immich-serverStats",
"immich-albumCarousel",
] as const;
export type WidgetKind = (typeof widgetKinds)[number];
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/dockerode": "^4.0.1",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -26,7 +26,7 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.15",
"@mantine/form": "^8.3.16",
"mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.2.1"
},
@@ -34,7 +34,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -30,7 +30,7 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@mantine/core": "^8.3.16",
"react": "19.2.4",
"zod": "^4.2.1"
},
@@ -38,7 +38,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+20 -24
View File
@@ -1,6 +1,7 @@
import bcrypt from "bcrypt";
import { createHmac, hkdfSync } from "node:crypto";
import { createId } from "@homarr/common";
import { env } from "@homarr/common/env";
import { decryptSecret, encryptSecret } from "@homarr/common/server";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { createLogger } from "@homarr/core/infrastructure/logs";
@@ -9,29 +10,22 @@ import { createGetSetChannel } from "@homarr/redis";
const logger = createLogger({ module: "imageProxy" });
const createHashChannel = (hash: `${string}.${string}`) => createGetSetChannel<string>(`image-proxy:hash:${hash}`);
const createHmacChannel = (hmac: `${string}.${string}`) => createGetSetChannel<string>(`image-proxy:hmac:${hmac}`);
const createUrlByIdChannel = (id: string) =>
createGetSetChannel<{
url: `${string}.${string}`;
headers: `${string}.${string}`;
}>(`image-proxy:url:${id}`);
const saltChannel = createGetSetChannel<string>("image-proxy:salt");
export class ImageProxy {
private static salt: string | null = null;
private async getOrCreateSaltAsync(): Promise<string> {
if (ImageProxy.salt) return ImageProxy.salt;
const existingSalt = await saltChannel.getAsync();
if (existingSalt) {
ImageProxy.salt = existingSalt;
return existingSalt;
}
private static hmacKey: Buffer<ArrayBuffer> | null = null;
const salt = await bcrypt.genSalt(10);
logger.debug("Generated new salt for image proxy", { salt });
ImageProxy.salt = salt;
await saltChannel.setAsync(salt);
return salt;
/**
* Generate secure key for image-proxy from encryption key
*/
private static getHmacKey(): Buffer<ArrayBuffer> {
this.hmacKey ??= Buffer.from(hkdfSync("sha256", env.SECRET_ENCRYPTION_KEY, "", "image-proxy", 32));
return this.hmacKey;
}
public async createImageAsync(url: string, headers?: Record<string, string>): Promise<string> {
@@ -106,20 +100,18 @@ export class ImageProxy {
}
private async getExistingIdAsync(url: string, headers: Record<string, string> | undefined): Promise<string | null> {
const salt = await this.getOrCreateSaltAsync();
const urlHash = await bcrypt.hash(url, salt);
const headerHash = await bcrypt.hash(JSON.stringify(headers ?? null), salt);
const urlHash = this.hashSecret(url);
const headerHash = this.hashSecret(JSON.stringify(headers ?? null));
const channel = createHashChannel(`${urlHash}.${headerHash}`);
const channel = createHmacChannel(`${urlHash}.${headerHash}`);
return await channel.getAsync();
}
private async storeImageAsync(id: string, url: string, headers: Record<string, string> | undefined): Promise<void> {
const salt = await this.getOrCreateSaltAsync();
const urlHash = await bcrypt.hash(url, salt);
const headerHash = await bcrypt.hash(JSON.stringify(headers ?? null), salt);
const urlHash = this.hashSecret(url);
const headerHash = this.hashSecret(JSON.stringify(headers ?? null));
const hashChannel = createHashChannel(`${urlHash}.${headerHash}`);
const hashChannel = createHmacChannel(`${urlHash}.${headerHash}`);
const urlHeaderChannel = createUrlByIdChannel(id);
await urlHeaderChannel.setAsync({
url: encryptSecret(url),
@@ -134,6 +126,10 @@ export class ImageProxy {
});
}
private hashSecret(value: string): string {
return createHmac("sha256", ImageProxy.getHmacKey()).update(value).digest("hex");
}
private redactUrl(url: string): string {
const urlObject = new URL(url);
+2 -1
View File
@@ -38,6 +38,7 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@immich/sdk": "^2.5.6",
"@jellyfin/sdk": "^0.13.0",
"@octokit/auth-app": "^8.2.0",
"ical.js": "^2.2.1",
@@ -56,7 +57,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-unifi": "^2.5.1",
"@types/xml2js": "^0.4.14",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
@@ -19,6 +19,7 @@ import { GitlabIntegration } from "../gitlab/gitlab-integration";
import { GlancesIntegration } from "../glances/glances-integration";
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
import { ICalIntegration } from "../ical/ical-integration";
import { ImmichIntegration } from "../immich/immich-integration";
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
@@ -39,6 +40,7 @@ import { PlexIntegration } from "../plex/plex-integration";
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
import { QuayIntegration } from "../quay/quay-integration";
import { SeerrIntegration } from "../seerr/seerr-integration";
import { TrueNasIntegration } from "../truenas/truenas-integration";
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
import { UnraidIntegration } from "../unraid/unraid-integration";
@@ -82,6 +84,7 @@ export const integrationCreators = {
slskd: SlskdIntegration,
aria2: Aria2Integration,
jellyseerr: JellyseerrIntegration,
seerr: SeerrIntegration,
overseerr: OverseerrIntegration,
prowlarr: ProwlarrIntegration,
openmediavault: OpenMediaVaultIntegration,
@@ -109,6 +112,7 @@ export const integrationCreators = {
unraid: UnraidIntegration,
coolify: CoolifyIntegration,
glances: GlancesIntegration,
immich: ImmichIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
@@ -0,0 +1,111 @@
import { getAlbumInfo, getAllAlbums, getMyUser, getServerStatistics, init, searchUsers } from "@immich/sdk";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ImageProxy } from "@homarr/image-proxy";
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service";
export interface ImmichServerStats {
userCount: number;
photoCount: number;
videoCount: number;
totalLibraryUsageInBytes: number;
}
export interface ImmichAlbum {
id: string;
albumName: string;
assets: ImmichAsset[];
}
export interface ImmichAsset {
id: string;
deviceAssetId: string;
originalPath: string;
thumbhash: string | null;
fileModifiedAt: string;
fileCreatedAt: string;
updatedAt: string;
type: "IMAGE" | "VIDEO" | "AUDIO" | "OTHER";
publicLink: string;
}
const logger = createLogger({ module: "immich-integration" });
export class ImmichIntegration extends Integration {
constructor(integration: IntegrationInput) {
super(integration);
}
public async getServerStatsAsync(): Promise<ImmichServerStats> {
this.initClient();
const [statistics, users] = await Promise.all([getServerStatistics(), searchUsers()]);
return {
photoCount: statistics.photos,
userCount: users.length,
totalLibraryUsageInBytes: statistics.usage,
videoCount: statistics.videos,
};
}
public async getAlbumAsync(albumId: string): Promise<ImmichAlbum> {
this.initClient();
const album = await getAlbumInfo({ id: albumId });
const imageProxy = new ImageProxy();
return {
albumName: album.albumName,
assets: await Promise.all(
album.assets.map(async (asset) => {
const publicLink = await imageProxy.createImageAsync(
this.url(`/api/assets/${asset.id}/original`).toString(),
{
"x-api-key": this.getSecretValue("apiKey"),
},
);
return {
id: asset.id,
type: asset.type,
deviceAssetId: asset.deviceAssetId,
thumbhash: asset.thumbhash,
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
originalPath: asset.originalPath,
updatedAt: asset.updatedAt,
publicLink,
};
}),
),
id: album.id,
};
}
public async getAlbumsAsync(): Promise<
{
id: string;
albumName: string;
assetCount: number;
}[]
> {
this.initClient();
const albums = await getAllAlbums({});
return albums.map((album) => ({ id: album.id, albumName: album.albumName, assetCount: album.assetCount }));
}
protected async testingAsync(_: IntegrationTestingInput): Promise<TestingResult> {
this.initClient();
const user = await getMyUser();
logger.debug(`Logged in as ${user.name} (${user.id})`);
return { success: true };
}
/**
* Sets the credentials and prepares the client for calls.
* Must be called before any other functions.
* @private
*/
private initClient() {
init({ baseUrl: this.url("/api").toString(), apiKey: this.getSecretValue("apiKey") });
}
}
+3
View File
@@ -19,6 +19,7 @@ export { NTFYIntegration } from "./ntfy/ntfy-integration";
export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration";
export { GlancesIntegration } from "./glances/glances-integration";
export { OverseerrIntegration } from "./overseerr/overseerr-integration";
export { SeerrIntegration } from "./seerr/seerr-integration";
export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export { PlexIntegration } from "./plex/plex-integration";
@@ -28,6 +29,7 @@ export { UnraidIntegration } from "./unraid/unraid-integration";
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
export { ICalIntegration } from "./ical/ical-integration";
export { CoolifyIntegration } from "./coolify/coolify-integration";
export { ImmichIntegration } from "./immich/immich-integration";
// Types
export type { IntegrationInput } from "./base/integration";
@@ -53,6 +55,7 @@ export type {
} from "./interfaces/media-transcoding/media-transcoding-types";
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
export type { Notification } from "./interfaces/notifications/notification-types";
export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration";
// Schemas
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";
@@ -0,0 +1,4 @@
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
// Seerr is a fork of Overseerr, the API is compatible and no additional logic is required.
export class SeerrIntegration extends OverseerrIntegration {}
+3 -3
View File
@@ -33,8 +33,8 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@tabler/icons-react": "^3.37.1",
"@mantine/core": "^8.3.16",
"@tabler/icons-react": "^3.40.0",
"dayjs": "^1.11.19",
"next": "16.1.6",
"react": "19.2.4",
@@ -45,7 +45,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+3 -3
View File
@@ -24,15 +24,15 @@
"dependencies": {
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@mantine/core": "^8.3.16",
"@mantine/hooks": "^8.3.16",
"react": "19.2.4"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+3 -3
View File
@@ -24,14 +24,14 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/ui": "workspace:^0.1.0",
"@mantine/notifications": "^8.3.15",
"@tabler/icons-react": "^3.37.1"
"@mantine/notifications": "^8.3.16",
"@tabler/icons-react": "^3.40.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+3 -3
View File
@@ -37,8 +37,8 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@mantine/core": "^8.3.16",
"@mantine/hooks": "^8.3.16",
"adm-zip": "0.5.16",
"next": "16.1.6",
"react": "19.2.4",
@@ -52,7 +52,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/adm-zip": "0.5.7",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -26,14 +26,14 @@
"@homarr/core": "workspace:^",
"@homarr/db": "workspace:^",
"@homarr/definitions": "workspace:^",
"ioredis": "5.9.3",
"ioredis": "5.10.0",
"superjson": "2.2.6"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -40,7 +40,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+50
View File
@@ -0,0 +1,50 @@
import dayjs from "dayjs";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import { createIntegrationAsync } from "@homarr/integrations";
import type { ImmichAlbum, ImmichServerStats } from "@homarr/integrations";
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
export const immichStatsRequestHandler = createCachedIntegrationRequestHandler<
ImmichServerStats,
IntegrationKindByCategory<"photoService">,
Record<string, never>
>({
async requestAsync(integration) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getServerStatsAsync();
},
cacheDuration: dayjs.duration(15, "minute"),
queryKey: "immich-server-stats",
});
export const immichAlbumsRequestHandler = createCachedIntegrationRequestHandler<
{
id: string;
albumName: string;
assetCount: number;
}[],
IntegrationKindByCategory<"photoService">,
Record<string, never>
>({
async requestAsync(integration) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getAlbumsAsync();
},
cacheDuration: dayjs.duration(15, "minute"),
queryKey: "immich-albums",
});
export const immichAlbumRequestHandler = createCachedIntegrationRequestHandler<
ImmichAlbum,
IntegrationKindByCategory<"photoService">,
{ albumId: string }
>({
async requestAsync(integration, input) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getAlbumAsync(input.albumId);
},
cacheDuration: dayjs.duration(15, "minute"),
queryKey: "immich-album",
});
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -25,7 +25,7 @@
"dependencies": {
"@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.3.15",
"@mantine/dates": "^8.3.16",
"next": "16.1.6",
"react": "19.2.4",
"react-dom": "19.2.4"
@@ -34,7 +34,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -33,10 +33,10 @@
"@homarr/settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@mantine/spotlight": "^8.3.15",
"@tabler/icons-react": "^3.37.1",
"@mantine/core": "^8.3.16",
"@mantine/hooks": "^8.3.16",
"@mantine/spotlight": "^8.3.16",
"@tabler/icons-react": "^3.40.0",
"jotai": "^2.18.0",
"next": "16.1.6",
"react": "19.2.4",
@@ -47,7 +47,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+1 -1
View File
@@ -41,7 +41,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.39.3",
"eslint": "^9.39.4",
"typescript": "^5.9.3"
}
}
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "下载客户端",
"description": "允许您查看和管理来自Torrent 和 Usenet 客户端的下载。",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "crwdns3880:0crwdne3880:0",
"description": "crwdns3882:0crwdne3882:0",
"option": {
"showUsers": {
"label": "crwdns3884:0crwdne3884:0",
"description": "crwdns3886:0crwdne3886:0"
},
"showPhotos": {
"label": "crwdns3888:0crwdne3888:0",
"description": "crwdns3890:0crwdne3890:0"
},
"showVideos": {
"label": "crwdns3892:0crwdne3892:0",
"description": "crwdns3894:0crwdne3894:0"
},
"showStorage": {
"label": "crwdns3896:0crwdne3896:0",
"description": "crwdns3898:0crwdne3898:0"
}
},
"users": "crwdns3900:0crwdne3900:0",
"photos": "crwdns3902:0crwdne3902:0",
"videos": "crwdns3904:0crwdne3904:0",
"storage": "crwdns3906:0crwdne3906:0"
},
"immich-albumCarousel": {
"name": "crwdns3908:0crwdne3908:0",
"option": {
"albumId": {
"label": "crwdns3910:0crwdne3910:0",
"description": "crwdns3912:0crwdne3912:0"
},
"rotationIntervalSeconds": {
"label": "crwdns3914:0crwdne3914:0",
"description": "crwdns3916:0crwdne3916:0"
},
"showPhotoInfo": {
"label": "crwdns3918:0crwdne3918:0",
"description": "crwdns3920:0crwdne3920:0"
}
},
"description": "crwdns3922:0crwdne3922:0",
"noAlbumSelected": "crwdns3924:0crwdne3924:0",
"noPhotos": "crwdns3926:0crwdne3926:0"
},
"downloads": {
"name": "crwdns1170:0crwdne1170:0",
"description": "crwdns1172:0crwdne1172:0",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Download Klient",
"description": "Giver dig mulighed for at se og administrere dine downloads fra både Torrent og Usenet klienter.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "Ermöglicht es Ihnen, Ihre Downloads sowohl von Torrent- als auch von Usenet-Clients anzusehen und zu verwalten.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Download Client",
"description": "Ermöglicht es Ihnen, Ihre Downloads sowohl von Torrent- als auch von Usenet-Clients anzusehen und zu verwalten.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+195 -149
View File
@@ -1366,46 +1366,46 @@
"label": "Vertical"
},
"grid": {
"label": ""
"label": "Grid"
},
"gridHorizontal": {
"label": ""
"label": "Grid horizontal"
}
}
},
"hideTitle": {
"label": ""
"label": "Hide title"
},
"hideIcon": {
"label": ""
"label": "Hide icons"
},
"hideHostname": {
"label": ""
"label": "Hide hostnames"
},
"openNewTab": {
"label": ""
"label": "Open in new tab"
},
"items": {
"label": "",
"add": ""
"label": "Bookmarks",
"add": "Add bookmark"
}
}
},
"dnsHoleSummary": {
"name": "",
"description": "",
"name": "DNS Hole Summary",
"description": "Displays the summary of your DNS Hole",
"option": {
"layout": {
"label": "",
"label": "Layout",
"option": {
"row": {
"label": ""
"label": "Horizontal"
},
"column": {
"label": ""
"label": "Vertical"
},
"grid": {
"label": ""
"label": "Grid"
}
}
},
@@ -1414,19 +1414,19 @@
}
},
"error": {
"internalServerError": "",
"integrationsDisconnected": ""
"internalServerError": "Failed to fetch DNS Hole Summary",
"integrationsDisconnected": "No data available, all integrations disconnected"
},
"data": {
"adsBlockedToday": "",
"adsBlockedTodayPercentage": "",
"dnsQueriesToday": "",
"domainsBeingBlocked": ""
"adsBlockedToday": "Blocked today",
"adsBlockedTodayPercentage": "Blocked today",
"dnsQueriesToday": "Queries today",
"domainsBeingBlocked": "Domains on blocklist"
},
"domainsTooltip": ""
"domainsTooltip": "Due to multiple integrations Homarr can't calculate the exact number of domains being blocked"
},
"dnsHoleControls": {
"name": "",
"name": "DNS Hole Controls",
"description": "Control PiHole or AdGuard from your dashboard",
"option": {
"layout": {
@@ -1469,224 +1469,224 @@
"description": "Displays the current date and time.",
"option": {
"customTitleToggle": {
"label": "",
"description": ""
"label": "Custom Title/City display",
"description": "Show off a custom title or the name of the city/country on top of the clock."
},
"customTitle": {
"label": ""
"label": "Title"
},
"is24HourFormat": {
"label": "",
"description": ""
"label": "24-hour format",
"description": "Use 24-hour format instead of 12-hour format"
},
"showSeconds": {
"label": ""
"label": "Display seconds"
},
"useCustomTimezone": {
"label": " "
},
"timezone": {
"label": "",
"description": ""
"label": "Time zone",
"description": "Choose the time zone following the IANA standard"
},
"showDate": {
"label": ""
"label": "Show the date"
},
"dateFormat": {
"label": "",
"description": ""
"label": "Date Format",
"description": "How the date should look like"
},
"customTimeFormat": {
"label": "",
"description": ""
"label": "Custom time format",
"description": "Use ISO 8601 to format time (this will override other options)"
},
"customDateFormat": {
"label": "",
"description": ""
"label": "Custom date format",
"description": "Use ISO 8601 to format date (this will override other options)"
}
}
},
"minecraftServerStatus": {
"name": "",
"description": "",
"name": "Minecraft Server Status",
"description": "Displays the status of a Minecraft server",
"option": {
"title": {
"label": ""
"label": "Title"
},
"domain": {
"label": ""
"label": "Server address"
},
"isBedrockServer": {
"label": ""
"label": "Bedrock server"
}
},
"status": {
"online": "",
"offline": ""
"online": "Online",
"offline": "Offline"
}
},
"notebook": {
"name": "",
"description": "",
"name": "Notebook",
"description": "A simple notebook widget that supports markdown",
"option": {
"showToolbar": {
"label": ""
"label": "Show the toolbar to help you write markdown"
},
"allowReadOnlyCheck": {
"label": ""
"label": "Allow check in read only mode"
},
"content": {
"label": ""
"label": "The content of the notebook"
}
},
"controls": {
"bold": "",
"italic": "",
"strikethrough": "",
"underline": "",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"underline": "Underline",
"colorText": "Colour text",
"colorHighlight": "Coloured highlight text",
"code": "",
"clear": "",
"heading": "",
"align": "",
"blockquote": "",
"horizontalLine": "",
"bulletList": "",
"orderedList": "",
"checkList": "",
"increaseIndent": "",
"decreaseIndent": "",
"link": "",
"unlink": "",
"image": "",
"addTable": "",
"deleteTable": "",
"code": "Code",
"clear": "Clear formatting",
"heading": "Heading {level}",
"align": "Align text: {position}",
"blockquote": "Blockquote",
"horizontalLine": "Horizontal line",
"bulletList": "Bullet list",
"orderedList": "Ordered list",
"checkList": "Check list",
"increaseIndent": "Increase Indent",
"decreaseIndent": "Decrease Indent",
"link": "Link",
"unlink": "Remove link",
"image": "Embed Image",
"addTable": "Add table",
"deleteTable": "Delete Table",
"colorCell": "Colour cell",
"mergeCell": "",
"addColumnLeft": "",
"addColumnRight": "",
"deleteColumn": "",
"addRowTop": "",
"addRowBelow": "",
"deleteRow": ""
"mergeCell": "Toggle cell merging",
"addColumnLeft": "Add column before",
"addColumnRight": "Add column after",
"deleteColumn": "Delete column",
"addRowTop": "Add row before",
"addRowBelow": "Add row after",
"deleteRow": "Delete row"
},
"align": {
"left": "",
"center": "",
"right": ""
"left": "Left",
"center": "Centre",
"right": "Right"
},
"popover": {
"clearColor": "Clear colour",
"source": "",
"widthPlaceholder": "",
"columns": "",
"rows": "",
"width": "",
"height": ""
"source": "Source",
"widthPlaceholder": "Value in % or pixels",
"columns": "Columns",
"rows": "Rows",
"width": "Width",
"height": "Height"
},
"placeholder": "",
"placeholder": "Start writing your notes",
"dismiss": {
"title": "",
"message": "",
"title": "Dismiss changes?",
"message": "You have unsaved changes in your notebook. Are you sure you want to discard them?",
"action": {
"discard": "",
"keepEditing": ""
"discard": "Discard changes",
"keepEditing": "Keep editing"
}
}
},
"iframe": {
"name": "",
"description": "",
"name": "iFrame",
"description": "Embed any content from the Internet. Some websites may restrict access.",
"option": {
"embedUrl": {
"label": ""
"label": "Embed URL"
},
"allowFullScreen": {
"label": ""
"label": "Allow full screen"
},
"allowTransparency": {
"label": ""
"label": "Allow transparency"
},
"allowScrolling": {
"label": ""
"label": "Allow scrolling"
},
"allowPayment": {
"label": ""
"label": "Allow payment"
},
"allowAutoPlay": {
"label": ""
"label": "Allow auto play"
},
"allowMicrophone": {
"label": ""
"label": "Allow microphone"
},
"allowCamera": {
"label": ""
"label": "Allow camera"
},
"allowGeolocation": {
"label": ""
"label": "Allow geolocation"
}
},
"error": {
"noUrl": "",
"unsupportedProtocol": "",
"noBrowerSupport": ""
"noUrl": "No iFrame URL provided",
"unsupportedProtocol": "The URL provided is using an unsupported protocol. Please use one of ({supportedProtocols})",
"noBrowerSupport": "Your Browser does not support iframes. Please update your browser."
}
},
"smartHome-entityState": {
"name": "",
"description": "",
"name": "Entity State",
"description": "Display the state of an entity and toggle it optionally",
"option": {
"entityId": {
"label": ""
"label": "Entity ID"
},
"displayName": {
"label": ""
"label": "Display-name"
},
"entityUnit": {
"label": ""
"label": "Entity Unit"
},
"clickable": {
"label": ""
"label": "Clickable"
}
}
},
"smartHome-executeAutomation": {
"name": "",
"description": "",
"name": "Execute Automation",
"description": "Trigger an automation with one click",
"option": {
"displayName": {
"label": ""
"label": "Display name"
},
"automationId": {
"label": ""
"label": "Automation ID"
}
},
"spotlightAction": {
"run": ""
"run": "Run {name}"
}
},
"stockPrice": {
"name": "",
"description": "",
"name": "Stock Price",
"description": "Displays the current stock price of a company",
"option": {
"stock": {
"label": ""
"label": "Stock symbol"
},
"timeRange": {
"label": "",
"label": "Time Range",
"option": {
"1d": {
"label": ""
"label": "1 Day"
},
"5d": {
"label": ""
"label": "5 Day"
},
"1mo": {
"label": ""
"label": "1 Month"
},
"3mo": {
"label": ""
"label": "3 Months"
},
"6mo": {
"label": ""
@@ -1816,9 +1816,9 @@
"fog": "",
"drizzle": "",
"freezingDrizzle": "",
"rain": "",
"freezingRain": "",
"snowFall": "",
"rain": "Rain",
"freezingRain": "Freezing rain",
"snowFall": "Snow fall",
"snowGrains": "",
"rainShowers": "",
"snowShowers": "",
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
@@ -4311,7 +4357,7 @@
}
},
"local": {
"title": ""
"title": "Local results"
}
}
},
@@ -4364,50 +4410,50 @@
"label": "Global settings"
},
"about": {
"label": ""
"label": "About"
},
"homeBoard": {
"label": ""
"label": "Home board"
},
"preferences": {
"label": ""
"label": "Your preferences"
}
}
}
}
},
"userGroup": {
"help": "",
"help": "Search for users or groups",
"group": {
"user": {
"title": "",
"title": "Users",
"children": {
"action": {
"detail": {
"label": ""
"label": "Show user details"
}
},
"detail": {
"title": ""
"title": "Select an action for the user"
}
}
},
"group": {
"title": "",
"title": "Groups",
"children": {
"action": {
"detail": {
"label": ""
"label": "Show group details"
},
"manageMember": {
"label": ""
"label": "Manage members"
},
"managePermission": {
"label": ""
"label": "Manage permissions"
}
},
"detail": {
"title": ""
"title": "Select an action for the group"
}
}
}
@@ -4415,32 +4461,32 @@
}
},
"engine": {
"search": "",
"search": "Find a search engine",
"field": {
"name": {
"label": ""
"label": "Name"
},
"short": {
"label": ""
"label": "Short"
},
"urlTemplate": {
"label": ""
"label": "URL search template"
},
"description": {
"label": ""
"label": "Description"
}
},
"page": {
"list": {
"title": "",
"title": "Search engines",
"noResults": {
"title": "",
"action": ""
"title": "There are no search engines yet",
"action": "Create your first search engine"
},
"interactive": ""
"interactive": "Interactive, uses an integration"
},
"create": {
"title": "",
"title": "New search engine",
"notification": {
"success": {
"title": "Search engine created",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "Immich Server Stats",
"description": "Information about your Immich instance",
"option": {
"showUsers": {
"label": "Show users",
"description": "Show count of users on your instance"
},
"showPhotos": {
"label": "Show photos",
"description": "Show count of photos on your instance"
},
"showVideos": {
"label": "Show videos",
"description": "Show count of videos on your instance"
},
"showStorage": {
"label": "Show storage",
"description": "Show the total amount of storage used"
}
},
"users": "Users",
"photos": "Photos",
"videos": "Videos",
"storage": "Used Storage"
},
"immich-albumCarousel": {
"name": "Immich Album",
"option": {
"albumId": {
"label": "Album ID",
"description": "This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
},
"rotationIntervalSeconds": {
"label": "Interval in seconds",
"description": "Rotate between pictures in the album every X seconds. Must be at least 1. Must be below 3600."
},
"showPhotoInfo": {
"label": "Show photo info",
"description": "Display date of the picture and the current index at the bottom of the slideshow"
}
},
"description": "Slideshow of pictures in your Immich album",
"noAlbumSelected": "No album selected, edit widget and choose an album",
"noPhotos": "No photos in this album"
},
"downloads": {
"name": "Download Client",
"description": "Allows you to view and manage your Downloads from both Torrent and Usenet clients.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Cliente de descarga",
"description": "Te permite ver y gestionar tus descargas tanto desde los clientes de Torrent como desde Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Client de téléchargement",
"description": "Vous permet de visualiser et de gérer vos téléchargements à partir de clients Torrent et Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "הורדת לקוח",
"description": "מאפשר לך להציג ולנהל את ההורדות שלך הן מלקוחות Torrent והן מלקוחות Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "Statistiche Server Immich",
"description": "Informazioni sulla tua istanza Immich",
"option": {
"showUsers": {
"label": "Mostra utenti",
"description": "Mostra il conteggio degli utenti sulla tua istanza"
},
"showPhotos": {
"label": "Mostra foto",
"description": "Mostra il conteggio delle foto sulla tua istanza"
},
"showVideos": {
"label": "Mostra video",
"description": "Mostra il conteggio dei video sulla tua istanza"
},
"showStorage": {
"label": "Mostra spazio di archiviazione",
"description": "Visualizza la quantità totale di memoria utilizzata"
}
},
"users": "Utenti",
"photos": "Foto",
"videos": "Video",
"storage": "Spazio utilizzato"
},
"immich-albumCarousel": {
"name": "Album Immich",
"option": {
"albumId": {
"label": "ID Album",
"description": "Si tratta di un ID univoco che si riferisce al tuo album Immich. Puoi trovare questo id aprendo un album e copiando la stringa univoca dall'URL, ad esempio 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
},
"rotationIntervalSeconds": {
"label": "Intervallo in secondi",
"description": "Ruota le immagini nell'album ogni X secondi. Deve essere almeno 1. Deve essere inferiore a 3600."
},
"showPhotoInfo": {
"label": "Mostra informazioni foto",
"description": "Visualizza la data dell'immagine e l'indice corrente nella parte inferiore della presentazione"
}
},
"description": "Presentazione delle immagini nel tuo album Immich",
"noAlbumSelected": "Nessun album selezionato, modifica il widget e scegli un album",
"noPhotos": "Nessuna foto in questo album"
},
"downloads": {
"name": "Scarica Client",
"description": "Ti consente di visualizzare e gestire i tuoi download dai client Torrent e Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "クライアントをダウンロード",
"description": "Torrent クライアントと Usenet クライアントの両方から、ダウンロードを表示および管理できます。",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Client downloaden",
"description": "Geeft je de mogelijkheid om downloads van torrent- en usenet-clients te bekijken en beheren.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "Immich-serverstatistikk",
"description": "Informasjon om din Immich-instans",
"option": {
"showUsers": {
"label": "Vis brukere",
"description": "Vis antall brukere på din instans"
},
"showPhotos": {
"label": "Vis bilder",
"description": "Vis antall bilder på din instans"
},
"showVideos": {
"label": "Vis videoer",
"description": "Vis antall videoer på din instans"
},
"showStorage": {
"label": "Vis lagring",
"description": "Vis total mengde lagringsplass brukt"
}
},
"users": "Brukere",
"photos": "Bilder",
"videos": "Videoer",
"storage": "Brukt lagringsplass"
},
"immich-albumCarousel": {
"name": "Immich Album",
"option": {
"albumId": {
"label": "Album ID",
"description": "Dette er en unik ID som refererer til ditt Immich-album. Du kan finne denne ID-en ved å åpne et album og kopiere den unike strengen fra URL-en, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
},
"rotationIntervalSeconds": {
"label": "Intervall i sekunder",
"description": "Roter mellom bilder i albumet hvert X. sekund. Må være minst 1. Må være under 3600."
},
"showPhotoInfo": {
"label": "Vis bildeinformasjon",
"description": "Vis datoen for bildet og gjeldende indeks nederst i lysbildeserien"
}
},
"description": "Lysbildefremvisning av bilder i ditt Immich-album",
"noAlbumSelected": "Ingen album valgt, rediger widgeten og velg et album",
"noPhotos": "Ingen bilder i dette albumet"
},
"downloads": {
"name": "Nedlastings-klient",
"description": "Lar deg se og behandle nedlastinger fra både Torrent og Usenet klienter.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Klient pobierania",
"description": "Pozwala na oglądanie i zarządzanie pobieraniem zarówno przez klientów Torrent, jak i Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Cliente de Download",
"description": "Permite visualizar e gerenciar seus Downloads de clientes Torrent e Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "Статистика сервера Immich",
"description": "Информация о вашем экземпляре Immich",
"option": {
"showUsers": {
"label": "Показать пользователей",
"description": "Показать количество пользователей на вашем экземпляре"
},
"showPhotos": {
"label": "Показать фото",
"description": "Показать количество фотографий на вашем экземпляре"
},
"showVideos": {
"label": "Показать видео",
"description": "Показать количество видео на вашем экземпляре"
},
"showStorage": {
"label": "Показать хранилище",
"description": "Показать общий объём используемого хранилища"
}
},
"users": "Пользователи",
"photos": "Фото",
"videos": "Видео",
"storage": "Занято"
},
"immich-albumCarousel": {
"name": "Альбом Immich",
"option": {
"albumId": {
"label": "ID альбома",
"description": "Уникальный идентификатор вашего альбома Immich. Его можно найти, открыв альбом и скопировав уникальную строку из URL, например 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
},
"rotationIntervalSeconds": {
"label": "Интервал в секундах",
"description": "Переключать фотографии в альбоме каждые X секунд. Минимум 1. Максимум 3600."
},
"showPhotoInfo": {
"label": "Показать информацию о фото",
"description": "Отображать дату снимка и текущий номер в нижней части слайд-шоу"
}
},
"description": "Слайд-шоу фотографий из вашего альбома Immich",
"noAlbumSelected": "Альбом не выбран — отредактируйте виджет и выберите альбом",
"noPhotos": "В этом альбоме нет фотографий"
},
"downloads": {
"name": "Загрузки",
"description": "Позволяет просматривать и управлять загрузками как из торрент-клиентов, так и из Usenet-клиентов.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Download klient",
"description": "Umožňuje vám zobraziť a spravovať stiahnuté súbory z klientov Torrent aj Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
+53 -7
View File
@@ -649,14 +649,14 @@
"app": {
"option": {
"existing": {
"title": "",
"label": ""
"title": "Mevcut",
"label": "Mevcut uygulamayı seçin"
},
"new": {
"title": "",
"title": "Yeni",
"url": {
"label": "",
"description": ""
"label": "Uygulama url si",
"description": "Kontrol panelinden erişildiğinde uygulamanın açılacağı URL"
}
}
}
@@ -676,8 +676,8 @@
},
"app": {
"action": {
"add": "",
"remove": "",
"add": "Bir uygulamayı bağla",
"remove": "Bağlantıyı kaldır",
"select": ""
}
}
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "İndirme İstemcisi",
"description": "Hem Torrent hem de Usenet istemcilerinden indirdiklerinizi görüntülemenizi ve yönetmenizi sağlar.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "Завантаження",
"description": "Дозволяє переглядати та керувати завантаженнями від клієнтів Torrent та Usenet.",
+46
View File
@@ -2091,6 +2091,52 @@
}
}
},
"immich-serverStats": {
"name": "",
"description": "",
"option": {
"showUsers": {
"label": "",
"description": ""
},
"showPhotos": {
"label": "",
"description": ""
},
"showVideos": {
"label": "",
"description": ""
},
"showStorage": {
"label": "",
"description": ""
}
},
"users": "",
"photos": "",
"videos": "",
"storage": ""
},
"immich-albumCarousel": {
"name": "",
"option": {
"albumId": {
"label": "",
"description": ""
},
"rotationIntervalSeconds": {
"label": "",
"description": ""
},
"showPhotoInfo": {
"label": "",
"description": ""
}
},
"description": "",
"noAlbumSelected": "",
"noPhotos": ""
},
"downloads": {
"name": "",
"description": "",
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more