Compare commits
52
Commits
No files matched your search
@@ -33,6 +33,7 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.44.0
|
||||
- 1.43.1
|
||||
- 1.43.0
|
||||
- 1.42.1
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
name: "[Quality] Code Analysis"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
name: "[Crowdin] Upload translations"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 24.11.0
|
||||
node-version: 24.11.1
|
||||
cache: "pnpm"
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
|
||||
@@ -9,6 +9,8 @@ env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
update-contributors:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
## [1.44.0](https://github.com/homarr-labs/homarr/compare/v1.43.3...v1.44.0) (2025-11-21)
|
||||
|
||||
### Features
|
||||
|
||||
* **pi-hole:** support no-auth ([#4480](https://github.com/homarr-labs/homarr/issues/4480)) ([c89e35e](https://github.com/homarr-labs/homarr/commit/c89e35e13c794bbdf98748c00644b86f7f5dbe68))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @tanstack/react-query to ^5.90.8 ([#4494](https://github.com/homarr-labs/homarr/issues/4494)) ([a5b6203](https://github.com/homarr-labs/homarr/commit/a5b6203be6ac3c2308614f10ef6a17590917e2c9))
|
||||
* **deps:** update dependency @tanstack/react-query to ^5.90.9 ([#4508](https://github.com/homarr-labs/homarr/issues/4508)) ([b3db5d5](https://github.com/homarr-labs/homarr/commit/b3db5d5f7bc2d146f0b2c230a52e7bd4efad3bcb))
|
||||
* **deps:** update dependency @tanstack/react-query-next-experimental to ^5.91.0 ([#4505](https://github.com/homarr-labs/homarr/issues/4505)) ([8ea6cff](https://github.com/homarr-labs/homarr/commit/8ea6cffb537ca54a413c583ab8b8fd24a52185be))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.7 ([#4507](https://github.com/homarr-labs/homarr/issues/4507)) ([b4f9fb8](https://github.com/homarr-labs/homarr/commit/b4f9fb8b46a8f74e2d7896dfe98e80618cfbcde4))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.2 ([#4510](https://github.com/homarr-labs/homarr/issues/4510)) ([5e57a49](https://github.com/homarr-labs/homarr/commit/5e57a4906bcedc53d220e983827293f4a699c425))
|
||||
* **deps:** update dependency next-intl to v4.5.1 ([#4484](https://github.com/homarr-labs/homarr/issues/4484)) ([06942ec](https://github.com/homarr-labs/homarr/commit/06942ecbcd245f5540f2960f437ca2955a5ed0c3))
|
||||
* **deps:** update dependency next-intl to v4.5.3 ([#4503](https://github.com/homarr-labs/homarr/issues/4503)) ([0642773](https://github.com/homarr-labs/homarr/commit/064277373cacd3309d8da3779d5451d195da7f81))
|
||||
* **deps:** update dependency sass to ^1.94.0 ([#4482](https://github.com/homarr-labs/homarr/issues/4482)) ([19ecede](https://github.com/homarr-labs/homarr/commit/19ecedea4370924d47545ca8d9d46a0b7c016cc9))
|
||||
* **deps:** update mantine monorepo to ^8.3.8 ([#4516](https://github.com/homarr-labs/homarr/issues/4516)) ([ded4e72](https://github.com/homarr-labs/homarr/commit/ded4e72e5407b0076495a06111f3b04445f27276))
|
||||
* **deps:** update nextjs monorepo to v16.0.3 ([#4504](https://github.com/homarr-labs/homarr/issues/4504)) ([7d0c1a1](https://github.com/homarr-labs/homarr/commit/7d0c1a14fdf1731ff44e3d00793cf4cd1c3e9b1f))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.8.1 ([#4486](https://github.com/homarr-labs/homarr/issues/4486)) ([64657d8](https://github.com/homarr-labs/homarr/commit/64657d831cbe28a59708b280b07889b9f0232743))
|
||||
* **deps:** update turbo monorepo to ^2.6.1 ([#4483](https://github.com/homarr-labs/homarr/issues/4483)) ([cd124d6](https://github.com/homarr-labs/homarr/commit/cd124d6e8392eef5df0092481ec3cc26599f2902))
|
||||
* **iframe:** allow clicking links within ([#4521](https://github.com/homarr-labs/homarr/issues/4521)) ([5a418d7](https://github.com/homarr-labs/homarr/commit/5a418d73bdb07cf3d3270983b6881f2de8d58ad7))
|
||||
* **media-requests:** incorrect availability mapping ([#4520](https://github.com/homarr-labs/homarr/issues/4520)) ([d9cc35b](https://github.com/homarr-labs/homarr/commit/d9cc35b9859a14f44c4e4b13c6518287f8d18e54))
|
||||
|
||||
## [1.43.3](https://github.com/homarr-labs/homarr/compare/v1.43.2...v1.43.3) (2025-11-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:24.11.0-alpine AS base
|
||||
FROM node:24.11.1-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
+14
-14
@@ -50,17 +50,17 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.3.7",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/dropzone": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/modals": "^8.3.7",
|
||||
"@mantine/tiptap": "^8.3.7",
|
||||
"@mantine/colors-generator": "^8.3.8",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/dropzone": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@mantine/modals": "^8.3.8",
|
||||
"@mantine/tiptap": "^8.3.8",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.90.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||
"@trpc/client": "^11.7.1",
|
||||
"@trpc/next": "^11.7.1",
|
||||
"@trpc/react-query": "^11.7.1",
|
||||
@@ -74,18 +74,18 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.2.3",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^11.0.3",
|
||||
"glob": "^12.0.0",
|
||||
"isomorphic-dompurify": "^2.32.0",
|
||||
"jotai": "^2.15.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.93.3",
|
||||
"sass": "^1.94.1",
|
||||
"superjson": "2.2.5",
|
||||
"swagger-ui-react": "^5.30.2",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
@@ -96,10 +96,10 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.2",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
"@types/react": "19.2.6",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.39.1",
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.26.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.4",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.26.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
+17
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.43.3",
|
||||
"version": "1.44.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -40,27 +40,27 @@
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^12.0.2",
|
||||
"@semantic-release/npm": "^13.1.1",
|
||||
"@semantic-release/npm": "^13.1.2",
|
||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||
"@testcontainers/redis": "^11.8.0",
|
||||
"@turbo/gen": "^2.6.0",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"@vitest/coverage-v8": "^4.0.8",
|
||||
"@vitest/ui": "^4.0.8",
|
||||
"@testcontainers/redis": "^11.8.1",
|
||||
"@turbo/gen": "^2.6.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"@vitest/coverage-v8": "^4.0.10",
|
||||
"@vitest/ui": "^4.0.10",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"jsdom": "^27.1.0",
|
||||
"jsdom": "^27.2.0",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^25.0.2",
|
||||
"testcontainers": "^11.8.0",
|
||||
"turbo": "^2.6.0",
|
||||
"testcontainers": "^11.8.1",
|
||||
"turbo": "^2.6.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^4.0.8"
|
||||
"vitest": "^4.0.10"
|
||||
},
|
||||
"packageManager": "pnpm@10.20.0",
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"engines": {
|
||||
"node": ">=24.11.0"
|
||||
"node": ">=24.11.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -80,15 +80,15 @@
|
||||
"axios@>=1.0.0 <1.8.2": ">=1.13.2",
|
||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
|
||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||
"esbuild@<=0.24.2": ">=0.26.0",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
||||
"hono@<4.6.5": ">=4.10.4",
|
||||
"esbuild@<=0.24.2": ">=0.27.0",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||
"hono@<4.6.5": ">=4.10.6",
|
||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
||||
"prismjs@<1.30.0": ">=1.30.0",
|
||||
"proxmox-api>undici": "7.16.0",
|
||||
"react-is": "^19.2.0",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.53.1",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.53.2",
|
||||
"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",
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"@trpc/client": "^11.7.1",
|
||||
"@trpc/react-query": "^11.7.1",
|
||||
"@trpc/server": "^11.7.1",
|
||||
"@trpc/tanstack-react-query": "^11.7.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"superjson": "2.2.5",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { mediaRequestStatusConfiguration } from "@homarr/integrations/types";
|
||||
import type { MediaRequest } from "@homarr/integrations/types";
|
||||
import { mediaRequestListRequestHandler } from "@homarr/request-handler/media-request-list";
|
||||
import { mediaRequestStatsRequestHandler } from "@homarr/request-handler/media-request-stats";
|
||||
@@ -35,7 +36,10 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
return dataB.createdAt.getTime() - dataA.createdAt.getTime();
|
||||
}
|
||||
|
||||
return dataA.status - dataB.status;
|
||||
return (
|
||||
mediaRequestStatusConfiguration[dataA.status].position -
|
||||
mediaRequestStatusConfiguration[dataB.status].position
|
||||
);
|
||||
});
|
||||
}),
|
||||
subscribeToLatestRequests: publicProcedure
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.9",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.26.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"dns-caching": "^0.2.7",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"octokit": "^5.0.5",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"undici": "7.16.0",
|
||||
"zod": "^4.1.12",
|
||||
"zod-validation-error": "^4.0.2"
|
||||
"zod-validation-error": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"@trpc/client": "^11.7.1",
|
||||
"@trpc/server": "^11.7.1",
|
||||
"@trpc/tanstack-react-query": "^11.7.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react": "19.2.6",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"@testcontainers/mysql": "^11.8.0",
|
||||
"@testcontainers/postgresql": "^11.8.0",
|
||||
"@testcontainers/mysql": "^11.8.1",
|
||||
"@testcontainers/postgresql": "^11.8.1",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-kit": "^0.31.6",
|
||||
"drizzle-kit": "^0.31.7",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.15.3",
|
||||
@@ -69,7 +69,7 @@
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.15.6",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.26.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.4",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.3.1",
|
||||
"fast-xml-parser": "^5.3.2",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -145,7 +145,7 @@ export const integrationDefs = {
|
||||
},
|
||||
piHole: {
|
||||
name: "Pi-hole",
|
||||
secretKinds: [["apiKey"]],
|
||||
secretKinds: [["apiKey"], []],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg",
|
||||
category: ["dnsHole"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/pi-hole"),
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.45",
|
||||
"@types/dockerode": "^3.3.46",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -8,3 +8,4 @@ export type { Docker };
|
||||
export const containerStates = ["created", "running", "paused", "restarting", "exited", "removing", "dead"] as const;
|
||||
|
||||
export type ContainerState = (typeof containerStates)[number];
|
||||
export * from "./labels";
|
||||
@@ -0,0 +1,4 @@
|
||||
export const dockerLabels = {
|
||||
// Label to hide a container from Homarrs docker integration
|
||||
hide: "homarr.hide",
|
||||
} as const;
|
||||
@@ -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.7",
|
||||
"@mantine/form": "^8.3.8",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@@ -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.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"react": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ export type {
|
||||
FirewallMemorySummary,
|
||||
} from "./interfaces/firewall-summary/firewall-summary-types";
|
||||
export type { SystemHealthMonitoring } from "./interfaces/health-monitoring/health-monitoring-types";
|
||||
export { MediaRequestStatus } from "./interfaces/media-requests/media-request-types";
|
||||
export { UpstreamMediaRequestStatus } from "./interfaces/media-requests/media-request-types";
|
||||
export type { MediaRequestList, MediaRequestStats } from "./interfaces/media-requests/media-request-types";
|
||||
export type { StreamSession } from "./interfaces/media-server/media-server-types";
|
||||
export type {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { objectKeys } from "@homarr/common";
|
||||
|
||||
interface SerieSeason {
|
||||
id: number;
|
||||
seasonNumber: number;
|
||||
@@ -34,6 +36,64 @@ export interface MediaRequest {
|
||||
requestedBy?: Omit<RequestUser, "requestCount">;
|
||||
}
|
||||
|
||||
export const mediaAvailabilityConfiguration = {
|
||||
available: {
|
||||
color: "green",
|
||||
},
|
||||
partiallyAvailable: {
|
||||
color: "yellow",
|
||||
},
|
||||
processing: {
|
||||
color: "blue",
|
||||
},
|
||||
requested: {
|
||||
color: "violet",
|
||||
},
|
||||
pending: {
|
||||
color: "violet",
|
||||
},
|
||||
unknown: {
|
||||
color: "orange",
|
||||
},
|
||||
deleted: {
|
||||
color: "red",
|
||||
},
|
||||
blacklisted: {
|
||||
color: "gray",
|
||||
},
|
||||
} satisfies Record<string, { color: string }>;
|
||||
|
||||
export const mediaAvailabilities = objectKeys(mediaAvailabilityConfiguration);
|
||||
|
||||
export type MediaAvailability = (typeof mediaAvailabilities)[number];
|
||||
|
||||
export const mediaRequestStatusConfiguration = {
|
||||
pending: {
|
||||
color: "blue",
|
||||
position: 1,
|
||||
},
|
||||
approved: {
|
||||
color: "green",
|
||||
position: 2,
|
||||
},
|
||||
declined: {
|
||||
color: "red",
|
||||
position: 3,
|
||||
},
|
||||
failed: {
|
||||
color: "red",
|
||||
position: 4,
|
||||
},
|
||||
completed: {
|
||||
color: "green",
|
||||
position: 5,
|
||||
},
|
||||
} satisfies Record<string, { color: string; position: number }>;
|
||||
|
||||
export const mediaRequestStatuses = objectKeys(mediaRequestStatusConfiguration);
|
||||
|
||||
export type MediaRequestStatus = (typeof mediaRequestStatuses)[number];
|
||||
|
||||
export interface MediaRequestList {
|
||||
integration: {
|
||||
id: string;
|
||||
@@ -66,7 +126,7 @@ export interface MediaRequestStats {
|
||||
}
|
||||
|
||||
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L1
|
||||
export enum MediaRequestStatus {
|
||||
export enum UpstreamMediaRequestStatus {
|
||||
PendingApproval = 1,
|
||||
Approved = 2,
|
||||
Declined = 3,
|
||||
@@ -75,12 +135,12 @@ export enum MediaRequestStatus {
|
||||
}
|
||||
|
||||
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L14
|
||||
export enum MediaAvailability {
|
||||
export enum UpstreamMediaAvailability {
|
||||
Unknown = 1,
|
||||
Pending = 2,
|
||||
Processing = 3,
|
||||
PartiallyAvailable = 4,
|
||||
Available = 5,
|
||||
Blacklisted = 6,
|
||||
Deleted = 7,
|
||||
JellyseerrBlacklistedOrOverseerrDeleted = 6,
|
||||
JellyseerrDeleted = 7,
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
import type { MediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { UpstreamMediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
|
||||
|
||||
export class JellyseerrIntegration extends OverseerrIntegration {}
|
||||
export class JellyseerrIntegration extends OverseerrIntegration {
|
||||
protected override mapAvailability(availability: UpstreamMediaAvailability, inProgress: boolean): MediaAvailability {
|
||||
// Availability statuses are not exactly the same between Jellyseerr and Overseerr (Jellyseerr has "blacklisted" additionally (deleted is the same value in overseerr))
|
||||
if (availability === UpstreamMediaAvailability.JellyseerrBlacklistedOrOverseerrDeleted) return "blacklisted";
|
||||
if (availability === UpstreamMediaAvailability.JellyseerrDeleted) return "deleted";
|
||||
return super.mapAvailability(availability, inProgress);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import type { IMediaRequestIntegration } from "../../interfaces/media-requests/media-request-integration";
|
||||
import type { MediaInformation, MediaRequest, RequestStats, RequestUser } from "../../types";
|
||||
import { MediaAvailability, MediaRequestStatus } from "../../types";
|
||||
import type {
|
||||
MediaAvailability,
|
||||
MediaInformation,
|
||||
MediaRequest,
|
||||
MediaRequestStatus,
|
||||
RequestStats,
|
||||
RequestUser,
|
||||
} from "../../types";
|
||||
import { mediaAvailabilities, mediaRequestStatuses } from "../../types";
|
||||
|
||||
export class MediaRequestMockService implements IMediaRequestIntegration {
|
||||
public async getSeriesInformationAsync(mediaType: "movie" | "tv", id: number): Promise<MediaInformation> {
|
||||
@@ -86,12 +91,10 @@ export class MediaRequestMockService implements IMediaRequestIntegration {
|
||||
}
|
||||
|
||||
private static randomAvailability(): MediaAvailability {
|
||||
const values = objectEntries(MediaAvailability).filter(([key]) => typeof key === "number");
|
||||
return values[Math.floor(Math.random() * values.length)]?.[1] ?? MediaAvailability.Available;
|
||||
return mediaAvailabilities.at(Math.floor(Math.random() * mediaAvailabilities.length)) ?? "unknown";
|
||||
}
|
||||
|
||||
private static randomStatus(): MediaRequestStatus {
|
||||
const values = objectEntries(MediaRequestStatus).filter(([key]) => typeof key === "number");
|
||||
return values[Math.floor(Math.random() * values.length)]?.[1] ?? MediaRequestStatus.PendingApproval;
|
||||
return mediaRequestStatuses.at(Math.floor(Math.random() * mediaRequestStatuses.length)) ?? "pending";
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,17 @@ import type { ISearchableIntegration } from "../base/searchable-integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { IMediaRequestIntegration } from "../interfaces/media-requests/media-request-integration";
|
||||
import type { MediaRequest, RequestStats, RequestUser } from "../interfaces/media-requests/media-request-types";
|
||||
import { MediaAvailability, MediaRequestStatus } from "../interfaces/media-requests/media-request-types";
|
||||
import type {
|
||||
MediaAvailability,
|
||||
MediaRequest,
|
||||
MediaRequestStatus,
|
||||
RequestStats,
|
||||
RequestUser,
|
||||
} from "../interfaces/media-requests/media-request-types";
|
||||
import {
|
||||
UpstreamMediaAvailability,
|
||||
UpstreamMediaRequestStatus,
|
||||
} from "../interfaces/media-requests/media-request-types";
|
||||
|
||||
interface OverseerrSearchResult {
|
||||
id: number;
|
||||
@@ -128,7 +137,7 @@ export class OverseerrIntegration
|
||||
|
||||
if (pendingResults.length > 0 && allResults.length > 0) {
|
||||
requests = pendingResults.concat(
|
||||
allResults.filter(({ status }) => status !== MediaRequestStatus.PendingApproval),
|
||||
allResults.filter(({ status }) => status !== UpstreamMediaRequestStatus.PendingApproval),
|
||||
);
|
||||
} else if (pendingResults.length > 0) requests = pendingResults;
|
||||
else if (allResults.length > 0) requests = allResults;
|
||||
@@ -137,11 +146,15 @@ export class OverseerrIntegration
|
||||
return await Promise.all(
|
||||
requests.map(async (request): Promise<MediaRequest> => {
|
||||
const information = await this.getItemInformationAsync(request.media.tmdbId, request.type);
|
||||
|
||||
// See https://github.com/seerr-team/seerr/blob/af083a3cd5c3e3d5d7917fdf4fdd67fe3f39c46b/src/components/StatusBadge/index.tsx#L40
|
||||
const inProgress = (request.media.downloadStatus ?? []).length >= 1;
|
||||
|
||||
return {
|
||||
id: request.id,
|
||||
name: information.name,
|
||||
status: request.status,
|
||||
availability: request.media.status,
|
||||
status: this.mapRequestStatus(request.status),
|
||||
availability: this.mapAvailability(request.media.status, inProgress),
|
||||
backdropImageUrl: `https://image.tmdb.org/t/p/original/${information.backdropPath}`,
|
||||
posterImagePath: `https://image.tmdb.org/t/p/w600_and_h900_bestv2/${information.posterPath}`,
|
||||
href: this.externalUrl(`/${request.type}/${request.media.tmdbId}`).toString(),
|
||||
@@ -161,6 +174,42 @@ export class OverseerrIntegration
|
||||
);
|
||||
}
|
||||
|
||||
protected mapRequestStatus(status: UpstreamMediaRequestStatus): MediaRequestStatus {
|
||||
switch (status) {
|
||||
case UpstreamMediaRequestStatus.PendingApproval:
|
||||
return "pending";
|
||||
case UpstreamMediaRequestStatus.Approved:
|
||||
return "approved";
|
||||
case UpstreamMediaRequestStatus.Declined:
|
||||
return "declined";
|
||||
case UpstreamMediaRequestStatus.Failed:
|
||||
return "failed";
|
||||
case UpstreamMediaRequestStatus.Completed:
|
||||
return "completed";
|
||||
default:
|
||||
return "failed";
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/seerr-team/seerr/blob/af083a3cd5c3e3d5d7917fdf4fdd67fe3f39c46b/src/components/StatusBadge/index.tsx#L153-L387
|
||||
protected mapAvailability(availability: UpstreamMediaAvailability, inProgress: boolean): MediaAvailability {
|
||||
switch (availability) {
|
||||
case UpstreamMediaAvailability.Available:
|
||||
return inProgress ? "processing" : "available";
|
||||
case UpstreamMediaAvailability.PartiallyAvailable:
|
||||
return inProgress ? "processing" : "partiallyAvailable";
|
||||
case UpstreamMediaAvailability.Processing:
|
||||
return inProgress ? "processing" : "requested";
|
||||
case UpstreamMediaAvailability.Pending:
|
||||
return "pending";
|
||||
case UpstreamMediaAvailability.JellyseerrBlacklistedOrOverseerrDeleted:
|
||||
return "deleted";
|
||||
case UpstreamMediaAvailability.Unknown:
|
||||
default:
|
||||
return inProgress ? "processing" : "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public async getStatsAsync(): Promise<RequestStats> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/request/count"), {
|
||||
headers: {
|
||||
@@ -339,11 +388,12 @@ const getRequestsSchema = z.object({
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
status: z.nativeEnum(MediaRequestStatus),
|
||||
status: z.enum(UpstreamMediaRequestStatus),
|
||||
createdAt: z.string().transform((value) => new Date(value)),
|
||||
media: z.object({
|
||||
status: z.nativeEnum(MediaAvailability),
|
||||
status: z.enum(UpstreamMediaAvailability),
|
||||
tmdbId: z.number(),
|
||||
downloadStatus: z.array(z.unknown()).optional(),
|
||||
}),
|
||||
type: z.enum(["movie", "tv"]),
|
||||
requestedBy: z
|
||||
|
||||
@@ -17,7 +17,7 @@ import { dnsBlockingGetSchema, sessionResponseSchema, statsSummaryGetSchema } fr
|
||||
const localLogger = logger.child({ module: "PiHoleIntegrationV6" });
|
||||
|
||||
export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIntegration {
|
||||
private readonly sessionStore: SessionStore<string>;
|
||||
private readonly sessionStore: SessionStore<{ sid: string | null }>;
|
||||
|
||||
constructor(integration: IntegrationInput) {
|
||||
super(integration);
|
||||
@@ -28,7 +28,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
const response = await this.withAuthAsync(async (sessionId) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), {
|
||||
headers: {
|
||||
sid: sessionId,
|
||||
sid: sessionId ?? undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -46,7 +46,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
const response = await this.withAuthAsync(async (sessionId) => {
|
||||
return fetchWithTrustedCertificatesAsync(this.url("/api/stats/summary"), {
|
||||
headers: {
|
||||
sid: sessionId,
|
||||
sid: sessionId ?? undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -85,7 +85,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
const response = await this.withAuthAsync(async (sessionId) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), {
|
||||
headers: {
|
||||
sid: sessionId,
|
||||
sid: sessionId ?? undefined,
|
||||
},
|
||||
body: JSON.stringify({ blocking: true }),
|
||||
method: "POST",
|
||||
@@ -101,7 +101,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
const response = await this.withAuthAsync(async (sessionId) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), {
|
||||
headers: {
|
||||
sid: sessionId,
|
||||
sid: sessionId ?? undefined,
|
||||
},
|
||||
body: JSON.stringify({ blocking: false, timer: duration }),
|
||||
method: "POST",
|
||||
@@ -118,12 +118,16 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
* @param callback
|
||||
* @returns
|
||||
*/
|
||||
private async withAuthAsync(callback: (sessionId: string) => Promise<UndiciResponse>) {
|
||||
private async withAuthAsync(callback: (sessionId: string | null) => Promise<UndiciResponse>) {
|
||||
if (!super.hasSecretValue("apiKey")) {
|
||||
return await callback(null);
|
||||
}
|
||||
|
||||
const storedSession = await this.sessionStore.getAsync();
|
||||
|
||||
if (storedSession) {
|
||||
localLogger.debug("Using stored session for request", { integrationId: this.integration.id });
|
||||
const response = await callback(storedSession);
|
||||
const response = await callback(storedSession.sid);
|
||||
if (response.status !== 401) {
|
||||
return response;
|
||||
}
|
||||
@@ -132,7 +136,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
}
|
||||
|
||||
const sessionId = await this.getSessionAsync();
|
||||
await this.sessionStore.setAsync(sessionId);
|
||||
await this.sessionStore.setAsync({ sid: sessionId });
|
||||
const response = await callback(sessionId);
|
||||
return response;
|
||||
}
|
||||
@@ -141,11 +145,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
* Get a session id from the Pi-hole server
|
||||
* @returns The session id
|
||||
*/
|
||||
private async getSessionAsync(fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync): Promise<string> {
|
||||
const apiKey = super.getSecretValue("apiKey");
|
||||
private async getSessionAsync(
|
||||
fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync,
|
||||
): Promise<string | null> {
|
||||
const apiKey = super.hasSecretValue("apiKey") ? super.getSecretValue("apiKey") : null;
|
||||
const response = await fetchAsync(this.url("/api/auth"), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ password: apiKey }),
|
||||
body: JSON.stringify({ password: apiKey ?? "" }),
|
||||
headers: {
|
||||
"User-Agent": "Homarr",
|
||||
},
|
||||
@@ -156,8 +162,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
const data = await response.json();
|
||||
const result = await sessionResponseSchema.parseAsync(data);
|
||||
|
||||
if (!result.session.sid) {
|
||||
throw new ResponseError({ status: 401, url: response.url });
|
||||
if (!result.session.valid) {
|
||||
throw new ResponseError(
|
||||
{ status: 401, url: response.url },
|
||||
{
|
||||
cause: result.session.message ? new Error(result.session.message) : undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
localLogger.info("Received session id successfully", { integrationId: this.integration.id });
|
||||
@@ -170,9 +181,14 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
* @param sessionId The session id to remove
|
||||
*/
|
||||
private async clearSessionAsync(
|
||||
sessionId: string,
|
||||
sessionId: string | null,
|
||||
fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync,
|
||||
) {
|
||||
if (!sessionId) {
|
||||
localLogger.debug("No session id to clear");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetchAsync(this.url("/api/auth"), {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod/v4";
|
||||
|
||||
export const sessionResponseSchema = z.object({
|
||||
session: z.object({
|
||||
valid: z.boolean(),
|
||||
sid: z.string().nullable(),
|
||||
message: z.string().nullable(),
|
||||
}),
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"react": "19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.3.7",
|
||||
"@mantine/notifications": "^8.3.8",
|
||||
"@tabler/icons-react": "^3.35.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"superjson": "2.2.5",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { ContainerInfo, ContainerStats } from "dockerode";
|
||||
|
||||
import { db, like, or } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
import type { ContainerState } from "@homarr/docker";
|
||||
import { dockerLabels, DockerSingleton } from "@homarr/docker";
|
||||
|
||||
import type { ContainerState } from "../../docker/src";
|
||||
import { DockerSingleton } from "../../docker/src";
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
export const dockerContainersRequestHandler = createCachedWidgetRequestHandler({
|
||||
@@ -27,13 +27,11 @@ async function getContainersWithStatsAsync() {
|
||||
const containers = await Promise.all(
|
||||
dockerInstances.map(async ({ instance, host }) => {
|
||||
const instanceContainers = await instance.listContainers({ all: true });
|
||||
return instanceContainers.map((container) => ({
|
||||
...container,
|
||||
instance: host,
|
||||
}));
|
||||
return instanceContainers
|
||||
.filter((container) => dockerLabels.hide in container.Labels === false)
|
||||
.map((container) => ({ ...container, instance: host }));
|
||||
}),
|
||||
).then((res) => res.flat());
|
||||
|
||||
const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
|
||||
|
||||
const dbIcons =
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.3.7",
|
||||
"next": "16.0.1",
|
||||
"@mantine/dates": "^8.3.8",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0"
|
||||
},
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/spotlight": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@mantine/spotlight": "^8.3.8",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"jotai": "^2.15.1",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.1",
|
||||
"next-intl": "4.5.0",
|
||||
"next": "16.0.3",
|
||||
"next-intl": "4.5.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0"
|
||||
},
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -649,14 +649,14 @@
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
"title": "现有",
|
||||
"label": "选择现有应用"
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"title": "新建",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "应用网址",
|
||||
"description": "当从面板访问时应用将打开的网址"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,9 +676,9 @@
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
"add": "链接应用",
|
||||
"remove": "取消连接",
|
||||
"select": "选择要链接的应用"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -709,7 +709,7 @@
|
||||
"description": "集成“{kind}”可以与搜索引擎一起使用。勾选此项可自动配置搜索引擎。"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
"sectionTitle": "关联的应用"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "创建应用",
|
||||
@@ -1051,7 +1051,7 @@
|
||||
"add": "添加",
|
||||
"apply": "应用",
|
||||
"backToOverview": "返回概览",
|
||||
"change": "",
|
||||
"change": "更改",
|
||||
"create": "创建",
|
||||
"createAnother": "创建并重新开始",
|
||||
"edit": "编辑",
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "未知",
|
||||
"pending": "等待处理",
|
||||
"processing": "处理中",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "部分",
|
||||
"available": "可用",
|
||||
"blacklisted": "已列入黑名单",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "crwdns1314:0crwdne1314:0",
|
||||
"pending": "crwdns1316:0crwdne1316:0",
|
||||
"processing": "crwdns1318:0crwdne1318:0",
|
||||
"requested": "crwdns3806:0crwdne3806:0",
|
||||
"partiallyAvailable": "crwdns1320:0crwdne1320:0",
|
||||
"available": "crwdns1322:0crwdne1322:0",
|
||||
"blacklisted": "crwdns3468:0crwdne3468:0",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Neznámý",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Částečně dostupné",
|
||||
"available": "K dispozici",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Ukendt",
|
||||
"pending": "Afventende",
|
||||
"processing": "Behandler",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Delvis",
|
||||
"available": "Tilgængelig",
|
||||
"blacklisted": "Blacklistet",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Unbekannt",
|
||||
"pending": "Ausstehend",
|
||||
"processing": "In Bearbeitung",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Teilweise",
|
||||
"available": "Verfügbar",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Unbekannt",
|
||||
"pending": "Ausstehend",
|
||||
"processing": "In Bearbeitung",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Teilweise",
|
||||
"available": "Verfügbar",
|
||||
"blacklisted": "Gesperrt",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Άγνωστο",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Μερικώς",
|
||||
"available": "Διαθέσιμο",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Unknown",
|
||||
"pending": "Pending",
|
||||
"processing": "Processing",
|
||||
"requested": "Requested",
|
||||
"partiallyAvailable": "Partial",
|
||||
"available": "Available",
|
||||
"blacklisted": "Blacklisted",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Desconocido",
|
||||
"pending": "Pendiente",
|
||||
"processing": "Procesando",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Parcial",
|
||||
"available": "Disponible",
|
||||
"blacklisted": "Lista negra",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Inconnu",
|
||||
"pending": "En attente",
|
||||
"processing": "Traitement en cours",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Partiel",
|
||||
"available": "Disponible",
|
||||
"blacklisted": "Sur la liste noire",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "לא ידוע",
|
||||
"pending": "בהמתנה",
|
||||
"processing": "מעבד",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "חלקי",
|
||||
"available": "זמין",
|
||||
"blacklisted": "ברשימה השחורה",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Nepoznato",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Ismeretlen",
|
||||
"pending": "",
|
||||
"processing": "Feldolgozás",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Részleges",
|
||||
"available": "Elérhető",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
"error": {
|
||||
"common": {
|
||||
"cause": {
|
||||
"title": ""
|
||||
"title": "Causa con più dettagli"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
@@ -738,7 +738,7 @@
|
||||
"description": "Si è verificato un errore sconosciuto, apri la causa sottostante per vedere maggiori dettagli"
|
||||
},
|
||||
"parse": {
|
||||
"title": "",
|
||||
"title": "Errore di analisi",
|
||||
"description": "La risposta non può essere analizzata. Si prega di verificare che l'URL stia puntando all'URL di base del servizio."
|
||||
},
|
||||
"authorization": {
|
||||
@@ -959,8 +959,8 @@
|
||||
"newLabel": "Nuovo ID token"
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Dominio",
|
||||
"newLabel": "Nuovo dominio"
|
||||
},
|
||||
"personalAccessToken": {
|
||||
"label": "Token d'Accesso Personale",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"backToOverview": "Torna alla panoramica",
|
||||
"change": "Cambia",
|
||||
"create": "Crea",
|
||||
"createAnother": "",
|
||||
"createAnother": "Crea e ricomincia",
|
||||
"edit": "Modifica",
|
||||
"import": "Importa",
|
||||
"insert": "Inserisci",
|
||||
@@ -1389,8 +1389,8 @@
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Riepilogo del filtraggio DNS",
|
||||
"description": "Mostra il riepilogo del tuo filtro DNS",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Layout",
|
||||
@@ -1411,7 +1411,7 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"internalServerError": "Impossibile recuperare il riepilogo del filtro DNS",
|
||||
"integrationsDisconnected": "Nessun dato disponibile, tutte le integrazioni disconnesse"
|
||||
},
|
||||
"data": {
|
||||
@@ -1423,7 +1423,7 @@
|
||||
"domainsTooltip": "A causa di più integrazioni, Homarr non può calcolare il numero esatto di domini bloccati"
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"name": "",
|
||||
"name": "Controlli del filtro DNS",
|
||||
"description": "Controlla PiHole o AdGuard dalla tua dashboard",
|
||||
"option": {
|
||||
"layout": {
|
||||
@@ -1445,7 +1445,7 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "Impossibile controllare il DNS Hole"
|
||||
},
|
||||
"controls": {
|
||||
"enableAll": "Abilita tutto",
|
||||
@@ -1514,7 +1514,7 @@
|
||||
"label": "Indirizzo del server"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
"label": "Server Bedrock"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
@@ -1625,7 +1625,7 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"noUrl": "",
|
||||
"noUrl": "Nessun URL iFrame fornito",
|
||||
"unsupportedProtocol": "L'URL fornito sta usando un protocollo non supportato. Si prega di utilizzare uno di ({supportedProtocols})",
|
||||
"noBrowerSupport": "Il tuo browser non supporta iframes. Aggiorna il tuo browser."
|
||||
}
|
||||
@@ -1664,7 +1664,7 @@
|
||||
}
|
||||
},
|
||||
"stockPrice": {
|
||||
"name": "",
|
||||
"name": "Prezzo delle azioni",
|
||||
"description": "Visualizza il prezzo attuale delle azioni di una società",
|
||||
"option": {
|
||||
"stock": {
|
||||
@@ -1835,7 +1835,7 @@
|
||||
"title": "Index manager",
|
||||
"testAll": "Prova tutto",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "Impossibile recuperare lo stato degli indicizzatori"
|
||||
}
|
||||
},
|
||||
"healthMonitoring": {
|
||||
@@ -2020,7 +2020,7 @@
|
||||
"name": "Nome",
|
||||
"id": "Id",
|
||||
"metadata": {
|
||||
"title": "",
|
||||
"title": "Statistiche per nerd",
|
||||
"video": {
|
||||
"title": "Video",
|
||||
"resolution": "Risoluzione"
|
||||
@@ -2034,7 +2034,7 @@
|
||||
"title": "Transcodifica",
|
||||
"container": "Container",
|
||||
"resolution": "Risoluzione",
|
||||
"target": ""
|
||||
"target": "Codec di destinazione"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2098,7 +2098,7 @@
|
||||
"detailsTitle": "Categorie (Informazioni aggiuntive)"
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "",
|
||||
"columnTitle": "Giù",
|
||||
"detailsTitle": "Velocità Di Download"
|
||||
},
|
||||
"index": {
|
||||
@@ -2112,22 +2112,22 @@
|
||||
"columnTitle": "Integrazione"
|
||||
},
|
||||
"name": {
|
||||
"columnTitle": ""
|
||||
"columnTitle": "Nome del job"
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Avanzamento",
|
||||
"detailsTitle": ""
|
||||
"detailsTitle": "Avanzamento del download"
|
||||
},
|
||||
"ratio": {
|
||||
"columnTitle": "Rapporto",
|
||||
"detailsTitle": "Rapporto torrent (ricevuti/inviati)"
|
||||
},
|
||||
"received": {
|
||||
"columnTitle": "",
|
||||
"columnTitle": "Totale fuori servizio",
|
||||
"detailsTitle": "Totale scaricati"
|
||||
},
|
||||
"sent": {
|
||||
"columnTitle": "",
|
||||
"columnTitle": "Totale operativo",
|
||||
"detailsTitle": "Totale Caricati"
|
||||
},
|
||||
"size": {
|
||||
@@ -2136,7 +2136,7 @@
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "Stato",
|
||||
"detailsTitle": ""
|
||||
"detailsTitle": "Stato job"
|
||||
},
|
||||
"time": {
|
||||
"columnTitle": "Ora di fine",
|
||||
@@ -2158,7 +2158,7 @@
|
||||
"completed": "Completato",
|
||||
"failed": "Non riuscito",
|
||||
"processing": "In lavorazione",
|
||||
"leeching": "",
|
||||
"leeching": "Leeching",
|
||||
"stalled": "In stallo",
|
||||
"unknown": "Sconosciuto",
|
||||
"seeding": "Seeding"
|
||||
@@ -2205,7 +2205,7 @@
|
||||
"label": "Mostra suggerimento descrizione"
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
"label": "Mostra il badge del tipo di media"
|
||||
},
|
||||
"showSource": {
|
||||
"label": "Mostra sorgente integrazione"
|
||||
@@ -2216,7 +2216,7 @@
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"name": "Elenco Richieste Media",
|
||||
"description": "Visualizza un elenco di tutte le richieste media dalla tua istanza Overseerr o Jellyseerr",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
@@ -2232,9 +2232,10 @@
|
||||
"unknown": "Sconosciuto",
|
||||
"pending": "In sospeso",
|
||||
"processing": "In lavorazione",
|
||||
"requested": "Richiesto",
|
||||
"partiallyAvailable": "Parziale",
|
||||
"available": "Disponibile",
|
||||
"blacklisted": "",
|
||||
"blacklisted": "In blacklist",
|
||||
"deleted": "Eliminato"
|
||||
},
|
||||
"status": {
|
||||
@@ -2247,7 +2248,7 @@
|
||||
"toBeDetermined": "TBD"
|
||||
},
|
||||
"mediaRequests-requestStats": {
|
||||
"name": "",
|
||||
"name": "Statistiche Richiesta Media",
|
||||
"description": "Statistiche sulle richieste dei media",
|
||||
"option": {},
|
||||
"titles": {
|
||||
@@ -2338,7 +2339,7 @@
|
||||
"label": "Abilita RTL"
|
||||
},
|
||||
"textLinesClamp": {
|
||||
"label": ""
|
||||
"label": "Limita le righe della descrizione"
|
||||
},
|
||||
"maximumAmountPosts": {
|
||||
"label": "Limite di post"
|
||||
@@ -2353,11 +2354,11 @@
|
||||
"description": "Visualizza un elenco della versione corrente dei repository dati con la versione regex specificata.",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"label": "Nuova versione entro",
|
||||
"description": "Esempio di utilizzo: 1w (1 settimana), 10M (10 mesi). Tipi di unità accettati h (ore), d (giorni), w (settimane), M (mesi), y (anni). Lasciare vuoto per non evidenziare le nuove versioni."
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"label": "Rilascio obsoleto entro",
|
||||
"description": "Esempio di utilizzo: 1w (1 settimana), 10M (10 mesi). Tipi di unità accettati h (ore), d (giorni), w (settimane), M (mesi), y (anni). Lasciare vuoto per non evidenziare le release obsolete."
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
@@ -2702,7 +2703,7 @@
|
||||
"label": "Titolo della pagina"
|
||||
},
|
||||
"metaTitle": {
|
||||
"label": ""
|
||||
"label": "Meta title"
|
||||
},
|
||||
"logoImageUrl": {
|
||||
"label": "URL immagine del logo"
|
||||
@@ -2948,7 +2949,7 @@
|
||||
"label": "Numero di colonne"
|
||||
},
|
||||
"breakpoint": {
|
||||
"label": "",
|
||||
"label": "Breakpoint",
|
||||
"description": "Il layout verrà utilizzato su tutti gli schermi più grandi di questo breakpoint fino al breakpoint più grande successivo."
|
||||
}
|
||||
}
|
||||
@@ -3322,7 +3323,7 @@
|
||||
"label": "Analytics"
|
||||
},
|
||||
"smartHomeEntityState": {
|
||||
"label": ""
|
||||
"label": "Stato dell'entità Smart Home"
|
||||
},
|
||||
"ping": {
|
||||
"label": "Ping"
|
||||
@@ -3337,10 +3338,10 @@
|
||||
"label": "Scaricamenti"
|
||||
},
|
||||
"mediaRequestStats": {
|
||||
"label": ""
|
||||
"label": "Statistiche richieste media"
|
||||
},
|
||||
"mediaRequestList": {
|
||||
"label": ""
|
||||
"label": "Elenco richieste media"
|
||||
},
|
||||
"rssFeeds": {
|
||||
"label": "Feeds RSS"
|
||||
@@ -3349,10 +3350,10 @@
|
||||
"label": "Gestione Indicizzatori"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"label": ""
|
||||
"label": "Monitoraggio integrità del sistema"
|
||||
},
|
||||
"dnsHole": {
|
||||
"label": ""
|
||||
"label": "Dati di filtraggio DNS"
|
||||
},
|
||||
"updateChecker": {
|
||||
"label": "Controllo aggiornamenti"
|
||||
@@ -3435,10 +3436,10 @@
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Attività attivata con successo"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Impossibile attivare l'attività"
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
@@ -3453,7 +3454,7 @@
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Impossibile attivare lo stato dell'attività"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3517,12 +3518,12 @@
|
||||
"action": "Azione"
|
||||
},
|
||||
"action": {
|
||||
"toggleBoardEdit": "",
|
||||
"toggleColorScheme": "",
|
||||
"saveNotebook": "",
|
||||
"toggleBoardEdit": "Attiva la modalità di modifica della board",
|
||||
"toggleColorScheme": "Attiva tema chiaro/scuro",
|
||||
"saveNotebook": "Salva notebook (solo nel widget notebook)",
|
||||
"openSpotlight": "Apri ricerca"
|
||||
},
|
||||
"note": ""
|
||||
"note": "Suggerimento: 'Mod' si riferisce sia al tasto Ctrl che al tasto ⌘ su macOS"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3534,7 +3535,7 @@
|
||||
"updated": "Aggiornato {when}",
|
||||
"search": "Cerca {count} container",
|
||||
"selected": "{selectCount} di {totalCount} containers selezionati",
|
||||
"footer": ""
|
||||
"footer": "Totale container {count}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -3672,11 +3673,11 @@
|
||||
"resources": {
|
||||
"title": "Risorse",
|
||||
"nodes": "Nodi",
|
||||
"namespaces": "",
|
||||
"namespaces": "Spazi dei nomi",
|
||||
"ingresses": "Ingressi",
|
||||
"services": "Servizi",
|
||||
"pods": "Pods",
|
||||
"configmaps": "",
|
||||
"configmaps": "ConfigMaps",
|
||||
"secrets": "Secrets",
|
||||
"volumes": "Volumi"
|
||||
}
|
||||
@@ -3721,7 +3722,7 @@
|
||||
}
|
||||
},
|
||||
"namespaces": {
|
||||
"label": "",
|
||||
"label": "Spazi dei nomi",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nome"
|
||||
@@ -3730,7 +3731,7 @@
|
||||
"label": "Stato",
|
||||
"option": {
|
||||
"active": "Attivo",
|
||||
"terminating": ""
|
||||
"terminating": "Terminare"
|
||||
}
|
||||
},
|
||||
"creationTimestamp": {
|
||||
@@ -3738,7 +3739,7 @@
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Cerca {count} spazi dei nomi"
|
||||
}
|
||||
},
|
||||
"ingresses": {
|
||||
@@ -3748,7 +3749,7 @@
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "Spazio dei nomi"
|
||||
},
|
||||
"className": {
|
||||
"label": "Nome classe"
|
||||
@@ -3761,7 +3762,7 @@
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Cerca {count} ingresses"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
@@ -3771,7 +3772,7 @@
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "Spazio del nome"
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipo"
|
||||
@@ -3800,7 +3801,7 @@
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "Spazio del nome"
|
||||
},
|
||||
"image": {
|
||||
"label": "Immagine"
|
||||
@@ -3826,7 +3827,7 @@
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "spazio del nome"
|
||||
},
|
||||
"type": {
|
||||
"label": "tipo"
|
||||
@@ -3836,24 +3837,24 @@
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Cerca {count} segreti"
|
||||
}
|
||||
},
|
||||
"configmaps": {
|
||||
"label": "",
|
||||
"label": "ConfigMaps",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "namespace"
|
||||
},
|
||||
"creationTimestamp": {
|
||||
"label": "Creato"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Cerca {count} ConfigMaps"
|
||||
}
|
||||
},
|
||||
"volumes": {
|
||||
@@ -3863,7 +3864,7 @@
|
||||
"label": "Nome"
|
||||
},
|
||||
"namespace": {
|
||||
"label": ""
|
||||
"label": "Namespace"
|
||||
},
|
||||
"accessModes": {
|
||||
"label": "Modalità Di Accesso"
|
||||
@@ -3872,7 +3873,7 @@
|
||||
"label": "Spazio di archiviazione"
|
||||
},
|
||||
"storageClassName": {
|
||||
"label": ""
|
||||
"label": "Nome Classe Archivio"
|
||||
},
|
||||
"volumeMode": {
|
||||
"label": "Modalità Volume"
|
||||
@@ -3888,7 +3889,7 @@
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Cerca {count} volumi"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3989,7 +3990,7 @@
|
||||
"label": "Nodi"
|
||||
},
|
||||
"namespaces": {
|
||||
"label": ""
|
||||
"label": "Namespaces"
|
||||
},
|
||||
"ingresses": {
|
||||
"label": "Ingressi"
|
||||
@@ -4001,7 +4002,7 @@
|
||||
"label": "pods"
|
||||
},
|
||||
"configmaps": {
|
||||
"label": ""
|
||||
"label": "ConfigMaps"
|
||||
},
|
||||
"secrets": {
|
||||
"label": "Secrets"
|
||||
@@ -4092,7 +4093,7 @@
|
||||
"option": {
|
||||
"colorScheme": {
|
||||
"light": "Passa al tema chiaro",
|
||||
"dark": "Passa alla modalità scura"
|
||||
"dark": "Passa al tema scuro"
|
||||
},
|
||||
"language": {
|
||||
"label": "Cambia lingua",
|
||||
|
||||
@@ -649,14 +649,14 @@
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
"title": "既存",
|
||||
"label": "既存のアプリを選択"
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"title": "新規作成",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "アプリURL",
|
||||
"description": "ダッシュボードからアクセスしたときにアプリが開くURL"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,9 +676,9 @@
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
"add": "アプリにリンクする",
|
||||
"remove": "リンクを解除する",
|
||||
"select": "リンクするアプリを選択する"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -709,7 +709,7 @@
|
||||
"description": "連携機能 \"{kind}\" は、検索エンジンで使用できます。検索エンジンを自動的に設定するには、これにチェックを入れてください。"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
"sectionTitle": "リンクされたアプリ"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "アプリの作成",
|
||||
@@ -1051,7 +1051,7 @@
|
||||
"add": "追加",
|
||||
"apply": "適用",
|
||||
"backToOverview": "概要に戻る",
|
||||
"change": "",
|
||||
"change": "変更する",
|
||||
"create": "作成",
|
||||
"createAnother": "作成・新規入力",
|
||||
"edit": "編集",
|
||||
@@ -1174,8 +1174,8 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kilometersPerHour": "",
|
||||
"milesPerHour": ""
|
||||
"kilometersPerHour": "km/h",
|
||||
"milesPerHour": "mph"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1190,7 +1190,7 @@
|
||||
"label": "タイトル"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": ""
|
||||
"label": "カスタム css クラス"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "境界線の色"
|
||||
@@ -1780,7 +1780,7 @@
|
||||
"description": "現在の天気のみ表示"
|
||||
},
|
||||
"useImperialSpeed": {
|
||||
"label": ""
|
||||
"label": "風速にmphを使用する"
|
||||
},
|
||||
"location": {
|
||||
"label": "天候の場所"
|
||||
@@ -1800,12 +1800,12 @@
|
||||
"description": "日付がどのように見えるか"
|
||||
}
|
||||
},
|
||||
"currentWindSpeed": "時速 {currentWindSpeed} km",
|
||||
"currentWindSpeed": "時速 {currentWindSpeed} {unit}",
|
||||
"dailyForecast": {
|
||||
"sunrise": "日出",
|
||||
"sunset": "日没",
|
||||
"maxWindSpeed": "最大風速: {maxWindSpeed} km/h",
|
||||
"maxWindGusts": "最大瞬間風速: {maxWindGusts} km/h"
|
||||
"maxWindSpeed": "最大風速: {maxWindSpeed} {unit}",
|
||||
"maxWindGusts": "最大瞬間風速: {maxWindGusts} {unit}"
|
||||
},
|
||||
"kind": {
|
||||
"clear": "晴れ",
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "不明",
|
||||
"pending": "保留中",
|
||||
"processing": "処理中",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "一部",
|
||||
"available": "利用可能",
|
||||
"blacklisted": "ブラックリスト",
|
||||
@@ -3013,8 +3014,8 @@
|
||||
"integration": "連携機能",
|
||||
"app": "アプリ",
|
||||
"group": "グループ",
|
||||
"searchEngine": "",
|
||||
"media": ""
|
||||
"searchEngine": "検索エンジン",
|
||||
"media": "メディア"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "ボード",
|
||||
@@ -3023,8 +3024,8 @@
|
||||
"authorization": "認可"
|
||||
},
|
||||
"heroBanner": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "おかえりなさい",
|
||||
"subtitle": "{app} ボード"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "알 수 없음",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Nežinoma",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Dalis",
|
||||
"available": "Galima",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Nezināms",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Daļējs",
|
||||
"available": "Pieejams",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Onbekend",
|
||||
"pending": "In afwachting",
|
||||
"processing": "Verwerken",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Gedeeltelijk",
|
||||
"available": "Beschikbaar",
|
||||
"blacklisted": "Geblacklist",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Ukjent",
|
||||
"pending": "Pågår",
|
||||
"processing": "Prosesserer",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Delvis",
|
||||
"available": "Tilgjengelig",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Nieznany",
|
||||
"pending": "Oczekujący",
|
||||
"processing": "Przetwarzanie",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Częściowy",
|
||||
"available": "Dostępne",
|
||||
"blacklisted": "Zablokowany",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Desconhecido",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Parcial",
|
||||
"available": "Disponível",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Necunoscut",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Parțial",
|
||||
"available": "Disponibil",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Неизвестно",
|
||||
"pending": "В ожидании",
|
||||
"processing": "Обработка",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Частично доступно",
|
||||
"available": "Доступно",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Neznámy",
|
||||
"pending": "Čakajúce",
|
||||
"processing": "Spracovanie",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Čiastočný",
|
||||
"available": "K dispozícii",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Neznano",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Okänd",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Delmängd",
|
||||
"available": "Tillgänglig",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Bilinmeyen",
|
||||
"pending": "Bekleyen",
|
||||
"processing": "İşlemde",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Kısmi",
|
||||
"available": "Mevcut",
|
||||
"blacklisted": "Engellenenler",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Невідомо",
|
||||
"pending": "В очікуванні",
|
||||
"processing": "В обробці",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Частково доступно",
|
||||
"available": "Доступно",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "Không rõ",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "Một phần",
|
||||
"available": "Khả dụng",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -2232,6 +2232,7 @@
|
||||
"unknown": "未知",
|
||||
"pending": "等待處理中",
|
||||
"processing": "處理中",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "部分",
|
||||
"available": "待定",
|
||||
"blacklisted": "",
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/dates": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/dates": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"svgson": "^5.3.1"
|
||||
|
||||
@@ -48,9 +48,9 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^8.3.7",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/charts": "^8.3.8",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tiptap/extension-color": "2.27.1",
|
||||
"@tiptap/extension-highlight": "2.27.1",
|
||||
@@ -72,7 +72,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.1",
|
||||
"next": "16.0.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
|
||||
@@ -85,7 +85,13 @@ const getAllowedPermissions = (
|
||||
const getSandboxFlags = (
|
||||
permissions: Omit<WidgetComponentProps<"iframe">["options"], "embedUrl" | "allowScrolling">,
|
||||
) => {
|
||||
const baseSandbox = ["allow-scripts", "allow-same-origin", "allow-forms", "allow-popups"];
|
||||
const baseSandbox = [
|
||||
"allow-scripts",
|
||||
"allow-same-origin",
|
||||
"allow-forms",
|
||||
"allow-popups",
|
||||
"allow-top-navigation-by-user-activation",
|
||||
];
|
||||
|
||||
if (permissions.allowFullScreen) {
|
||||
baseSandbox.push("allow-presentation");
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { ActionIcon, Anchor, Avatar, Badge, Card, Group, Image, ScrollArea, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { IconThumbDown, IconThumbUp } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { MediaAvailability, MediaRequestStatus } from "@homarr/integrations/types";
|
||||
import type { ScopedTranslationFunction } from "@homarr/translation";
|
||||
import type { MediaRequestStatus } from "@homarr/integrations/types";
|
||||
import { mediaAvailabilityConfiguration, mediaRequestStatusConfiguration } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
@@ -18,7 +19,6 @@ export default function MediaServerWidget({
|
||||
options,
|
||||
width,
|
||||
}: WidgetComponentProps<"mediaRequests-requestList">) {
|
||||
const t = useScopedI18n("widget.mediaRequests-requestList");
|
||||
const [mediaRequests] = clientApi.widget.mediaRequests.getLatestRequests.useSuspenseQuery(
|
||||
{
|
||||
integrationIds,
|
||||
@@ -48,20 +48,18 @@ export default function MediaServerWidget({
|
||||
return dataB.createdAt.getTime() - dataA.createdAt.getTime();
|
||||
}
|
||||
|
||||
return dataA.status - dataB.status;
|
||||
return (
|
||||
mediaRequestStatusConfiguration[dataA.status].position -
|
||||
mediaRequestStatusConfiguration[dataB.status].position
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();
|
||||
const board = useRequiredBoard();
|
||||
|
||||
if (mediaRequests.length === 0) throw new NoIntegrationDataError();
|
||||
|
||||
const isTiny = width < 256;
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="mediaRequests-list-scrollArea"
|
||||
@@ -70,193 +68,188 @@ export default function MediaServerWidget({
|
||||
>
|
||||
<Stack className="mediaRequests-list-list" gap="xs" p="sm">
|
||||
{mediaRequests.map((mediaRequest) => (
|
||||
<Card
|
||||
className={`mediaRequests-list-item-wrapper mediaRequests-list-item-${mediaRequest.type} mediaRequests-list-item-${mediaRequest.status}`}
|
||||
<MediaRequestCard
|
||||
key={`${mediaRequest.integrationId}-${mediaRequest.id}`}
|
||||
radius={board.itemRadius}
|
||||
p="xs"
|
||||
withBorder
|
||||
>
|
||||
<Image
|
||||
className="mediaRequests-list-item-background"
|
||||
src={mediaRequest.backdropImageUrl}
|
||||
pos="absolute"
|
||||
w="100%"
|
||||
h="100%"
|
||||
opacity={0.2}
|
||||
top={0}
|
||||
left={0}
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<Group
|
||||
className="mediaRequests-list-item-contents"
|
||||
h="100%"
|
||||
style={{ zIndex: 1 }}
|
||||
justify="space-between"
|
||||
wrap="nowrap"
|
||||
gap={0}
|
||||
>
|
||||
<Group className="mediaRequests-list-item-left-side" h="100%" gap="md" wrap="nowrap" flex={1}>
|
||||
{!isTiny && (
|
||||
<Image
|
||||
className="mediaRequests-list-item-poster"
|
||||
src={mediaRequest.posterImagePath}
|
||||
h={40}
|
||||
w="auto"
|
||||
radius={"md"}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack gap={0} w="100%">
|
||||
<Group justify="space-between" gap="xs" className="mediaRequests-list-item-top-group">
|
||||
<Group gap="xs">
|
||||
<Text className="mediaRequests-list-item-media-year" size="xs">
|
||||
{mediaRequest.airDate?.getFullYear() ?? t("toBeDetermined")}
|
||||
</Text>
|
||||
{!isTiny && (
|
||||
<Badge
|
||||
className="mediaRequests-list-item-media-status"
|
||||
color={getAvailabilityProperties(mediaRequest.availability, t).color}
|
||||
variant="light"
|
||||
size="xs"
|
||||
>
|
||||
{getAvailabilityProperties(mediaRequest.availability, t).label}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Group className="mediaRequests-list-item-request-user" gap={4} wrap="nowrap">
|
||||
<Avatar
|
||||
className="mediaRequests-list-item-request-user-avatar"
|
||||
src={mediaRequest.requestedBy?.avatar}
|
||||
size="xs"
|
||||
/>
|
||||
<Anchor
|
||||
className="mediaRequests-list-item-request-user-name"
|
||||
href={mediaRequest.requestedBy?.link}
|
||||
c="var(--mantine-color-text)"
|
||||
target={options.linksTargetNewTab ? "_blank" : "_self"}
|
||||
fz="xs"
|
||||
lineClamp={1}
|
||||
style={{ wordBreak: "break-all" }}
|
||||
>
|
||||
{(mediaRequest.requestedBy?.displayName ?? "") || "unknown"}
|
||||
</Anchor>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group gap="xs" justify="space-between" className="mediaRequests-list-item-bottom-group">
|
||||
<Anchor
|
||||
className="mediaRequests-list-item-info-second-line mediaRequests-list-item-media-title"
|
||||
href={mediaRequest.href}
|
||||
c="var(--mantine-color-text)"
|
||||
target={options.linksTargetNewTab ? "_blank" : "_self"}
|
||||
fz={isTiny ? "xs" : "sm"}
|
||||
fw={"bold"}
|
||||
title={mediaRequest.name}
|
||||
lineClamp={1}
|
||||
>
|
||||
{mediaRequest.name || "unknown"}
|
||||
</Anchor>
|
||||
{mediaRequest.status === MediaRequestStatus.PendingApproval ? (
|
||||
<Group className="mediaRequests-list-item-pending-buttons" gap="sm">
|
||||
<Tooltip label={t("pending.approve")}>
|
||||
<ActionIcon
|
||||
className="mediaRequests-list-item-pending-button-approve"
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
radius="md"
|
||||
onClick={() => {
|
||||
mutateRequestAnswer({
|
||||
integrationId: mediaRequest.integrationId,
|
||||
requestId: mediaRequest.id,
|
||||
answer: "approve",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconThumbUp size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("pending.decline")}>
|
||||
<ActionIcon
|
||||
className="mediaRequests-list-item-pending-button-decline"
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
radius="md"
|
||||
onClick={() => {
|
||||
mutateRequestAnswer({
|
||||
integrationId: mediaRequest.integrationId,
|
||||
requestId: mediaRequest.id,
|
||||
answer: "decline",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconThumbDown size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
) : (
|
||||
<StatusBadge status={mediaRequest.status} />
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
request={mediaRequest}
|
||||
isTiny={width <= 256}
|
||||
options={options}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
const statusMapping = {
|
||||
[MediaRequestStatus.PendingApproval]: { color: "blue", label: (t) => t("pending") },
|
||||
[MediaRequestStatus.Approved]: { color: "green", label: (t) => t("approved") },
|
||||
[MediaRequestStatus.Declined]: { color: "red", label: (t) => t("declined") },
|
||||
[MediaRequestStatus.Failed]: { color: "red", label: (t) => t("failed") },
|
||||
[MediaRequestStatus.Completed]: { color: "green", label: (t) => t("completed") },
|
||||
} satisfies Record<
|
||||
MediaRequestStatus,
|
||||
{
|
||||
color: string;
|
||||
label: (t: ScopedTranslationFunction<"widget.mediaRequests-requestList.status">) => string;
|
||||
}
|
||||
>;
|
||||
interface MediaRequestCardProps {
|
||||
request: RouterOutputs["widget"]["mediaRequests"]["getLatestRequests"][number];
|
||||
isTiny: boolean;
|
||||
options: WidgetComponentProps<"mediaRequests-requestList">["options"];
|
||||
}
|
||||
|
||||
const MediaRequestCard = ({ request, isTiny, options }: MediaRequestCardProps) => {
|
||||
const board = useRequiredBoard();
|
||||
const t = useScopedI18n("widget.mediaRequests-requestList");
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`mediaRequests-list-item-wrapper mediaRequests-list-item-${request.type} mediaRequests-list-item-${request.status}`}
|
||||
radius={board.itemRadius}
|
||||
p="xs"
|
||||
withBorder
|
||||
>
|
||||
<Image
|
||||
className="mediaRequests-list-item-background"
|
||||
src={request.backdropImageUrl}
|
||||
pos="absolute"
|
||||
w="100%"
|
||||
h="100%"
|
||||
opacity={0.2}
|
||||
top={0}
|
||||
left={0}
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<Group
|
||||
className="mediaRequests-list-item-contents"
|
||||
h="100%"
|
||||
style={{ zIndex: 1 }}
|
||||
justify="space-between"
|
||||
wrap="nowrap"
|
||||
gap={0}
|
||||
>
|
||||
<Group className="mediaRequests-list-item-left-side" h="100%" gap="md" wrap="nowrap" flex={1}>
|
||||
{!isTiny && (
|
||||
<Image
|
||||
className="mediaRequests-list-item-poster"
|
||||
src={request.posterImagePath}
|
||||
h={40}
|
||||
w="auto"
|
||||
radius={"md"}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack gap={0} w="100%">
|
||||
<Group justify="space-between" gap="xs" className="mediaRequests-list-item-top-group">
|
||||
<Group gap="xs">
|
||||
<Text className="mediaRequests-list-item-media-year" size="xs">
|
||||
{request.airDate?.getFullYear() ?? t("toBeDetermined")}
|
||||
</Text>
|
||||
{!isTiny && (
|
||||
<Badge
|
||||
className="mediaRequests-list-item-media-status"
|
||||
color={mediaAvailabilityConfiguration[request.availability].color}
|
||||
variant="light"
|
||||
size="xs"
|
||||
>
|
||||
{t(`availability.${request.availability}`)}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Group className="mediaRequests-list-item-request-user" gap={4} wrap="nowrap">
|
||||
<Avatar
|
||||
className="mediaRequests-list-item-request-user-avatar"
|
||||
src={request.requestedBy?.avatar}
|
||||
size="xs"
|
||||
/>
|
||||
<Anchor
|
||||
className="mediaRequests-list-item-request-user-name"
|
||||
href={request.requestedBy?.link}
|
||||
c="var(--mantine-color-text)"
|
||||
target={options.linksTargetNewTab ? "_blank" : "_self"}
|
||||
fz="xs"
|
||||
lineClamp={1}
|
||||
style={{ wordBreak: "break-all" }}
|
||||
>
|
||||
{(request.requestedBy?.displayName ?? "") || "unknown"}
|
||||
</Anchor>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group gap="xs" justify="space-between" className="mediaRequests-list-item-bottom-group">
|
||||
<Anchor
|
||||
className="mediaRequests-list-item-info-second-line mediaRequests-list-item-media-title"
|
||||
href={request.href}
|
||||
c="var(--mantine-color-text)"
|
||||
target={options.linksTargetNewTab ? "_blank" : "_self"}
|
||||
fz={isTiny ? "xs" : "sm"}
|
||||
fw={"bold"}
|
||||
title={request.name}
|
||||
lineClamp={1}
|
||||
>
|
||||
{request.name || "unknown"}
|
||||
</Anchor>
|
||||
{request.status === "pending" ? (
|
||||
<DecisionButtons requestId={request.id} integrationId={request.integrationId} />
|
||||
) : (
|
||||
<StatusBadge status={request.status} />
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface DecisionButtonsProps {
|
||||
requestId: number;
|
||||
integrationId: string;
|
||||
}
|
||||
|
||||
const DecisionButtons = ({ requestId, integrationId }: DecisionButtonsProps) => {
|
||||
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();
|
||||
const t = useScopedI18n("widget.mediaRequests-requestList");
|
||||
const handleDecision = (answer: RouterInputs["widget"]["mediaRequests"]["answerRequest"]["answer"]) => {
|
||||
mutateRequestAnswer({
|
||||
integrationId,
|
||||
requestId,
|
||||
answer,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Group className="mediaRequests-list-item-pending-buttons" gap="sm">
|
||||
<Tooltip label={t("pending.approve")}>
|
||||
<ActionIcon
|
||||
className="mediaRequests-list-item-pending-button-approve"
|
||||
variant="light"
|
||||
color="green"
|
||||
size="xs"
|
||||
radius="md"
|
||||
onClick={() => {
|
||||
handleDecision("approve");
|
||||
}}
|
||||
>
|
||||
<IconThumbUp size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("pending.decline")}>
|
||||
<ActionIcon
|
||||
className="mediaRequests-list-item-pending-button-decline"
|
||||
variant="light"
|
||||
color="red"
|
||||
size="xs"
|
||||
radius="md"
|
||||
onClick={() => {
|
||||
handleDecision("decline");
|
||||
}}
|
||||
>
|
||||
<IconThumbDown size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
interface StatusBadgeProps {
|
||||
status: MediaRequestStatus;
|
||||
}
|
||||
|
||||
const StatusBadge = ({ status }: StatusBadgeProps) => {
|
||||
const { color, label } = statusMapping[status];
|
||||
const tStatus = useScopedI18n("widget.mediaRequests-requestList.status");
|
||||
|
||||
return (
|
||||
<Badge size="xs" color={color} variant="light">
|
||||
{label(tStatus)}
|
||||
<Badge size="xs" color={mediaRequestStatusConfiguration[status].color} variant="light">
|
||||
{tStatus(status)}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
function getAvailabilityProperties(
|
||||
mediaRequestAvailability: MediaAvailability,
|
||||
t: ScopedTranslationFunction<"widget.mediaRequests-requestList">,
|
||||
) {
|
||||
switch (mediaRequestAvailability) {
|
||||
case MediaAvailability.Available:
|
||||
return { color: "green", label: t("availability.available") };
|
||||
case MediaAvailability.PartiallyAvailable:
|
||||
return { color: "yellow", label: t("availability.partiallyAvailable") };
|
||||
case MediaAvailability.Processing:
|
||||
return { color: "blue", label: t("availability.processing") };
|
||||
case MediaAvailability.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:
|
||||
return { color: "orange", label: t("availability.unknown") };
|
||||
}
|
||||
}
|
||||
Generated
+956
-836
File diff suppressed because it is too large.
Load diff
@@ -17,9 +17,9 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "16.0.1",
|
||||
"@next/eslint-plugin-next": "16.0.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-config-turbo": "^2.6.0",
|
||||
"eslint-config-turbo": "^2.6.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.11.0
|
||||
node-version: 24.11.1
|
||||
cache: "pnpm"
|
||||
|
||||
- shell: bash
|
||||
|
||||
Reference in new issue
Block a user