Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
024ec9235d | ||
|
|
58fdea0245 | ||
|
|
4ead238462 | ||
|
|
6305c74f90 | ||
|
|
0780080681 | ||
|
|
aa15a705d7 | ||
|
|
cc70175125 | ||
|
|
bc5e25e9cd | ||
|
|
d43557e165 | ||
|
|
b6c96f73a5 | ||
|
|
ee8831affe | ||
|
|
6e21863d04 | ||
|
|
ca39ca66f0 | ||
|
|
cc7d53e84e | ||
|
|
99c409cbb7 | ||
|
|
1fcf9d0146 | ||
|
|
a234fc844e | ||
|
|
168b587c18 | ||
|
|
dc12a82805 | ||
|
|
f5076454cd | ||
|
|
aeb681a858 | ||
|
|
56b57ad171 | ||
|
|
91e1cef611 | ||
|
|
e53b06e8c2 | ||
|
|
0ebf4bc55e | ||
|
|
b8a155dd08 | ||
|
|
63786eed06 | ||
|
|
598f0047eb | ||
|
|
eacda4c397 | ||
|
|
5cb726f793 | ||
|
|
e8d29420e0 | ||
|
|
23c7d0bbf0 | ||
|
|
75aefcb0d5 | ||
|
|
b52a8a7935 | ||
|
|
6d90209e00 | ||
|
|
8f0945f51a | ||
|
|
5b04b93f23 | ||
|
|
c8228b920c | ||
|
|
abf7942fb7 | ||
|
|
50746eb3e4 | ||
|
|
76abfca11b | ||
|
|
fcab6a347d | ||
|
|
c91aae53ac | ||
|
|
4943e7b851 |
No files matched your search
+2
-1
@@ -6,4 +6,5 @@ README.md
|
||||
.next
|
||||
.git
|
||||
dev
|
||||
.build
|
||||
.build
|
||||
e2e
|
||||
+8
-5
@@ -4,6 +4,14 @@
|
||||
# This file will be committed to version control, so make sure not to have any secrets in it.
|
||||
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
|
||||
|
||||
# The below secret is not used anywhere but required for Auth.js (Would encrypt JWTs and Mail hashes, both not used)
|
||||
AUTH_SECRET="supersecret"
|
||||
|
||||
# The below secret is used to encrypt integration secrets in the database.
|
||||
# It should be a 32-byte string, generated by running `openssl rand -hex 32` on Unix
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
DB_DRIVER='better-sqlite3'
|
||||
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
@@ -20,11 +28,6 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
|
||||
# You can generate the secret via 'openssl rand -base64 32' on Unix
|
||||
# @see https://next-auth.js.org/configuration/options#secret
|
||||
AUTH_SECRET='supersecret'
|
||||
|
||||
TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
# Configure logging to use winston logger
|
||||
|
||||
@@ -91,6 +91,8 @@ jobs:
|
||||
network: host
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Install playwright browsers
|
||||
run: pnpm exec playwright install chromium
|
||||
- name: Run E2E Tests
|
||||
shell: bash
|
||||
run: pnpm test:e2e
|
||||
|
||||
@@ -13,11 +13,6 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
description: Send notifications
|
||||
push-image:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: Push Docker Image
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -112,7 +107,6 @@ jobs:
|
||||
NEXT_VERSION: ${{ needs.release.outputs.version }}
|
||||
DEPLOY_LATEST: ${{ github.ref_name == 'main' }}
|
||||
DEPLOY_BETA: ${{ github.ref_name == 'beta' }}
|
||||
PUSH_IMAGE: ${{ github.event_name != 'workflow_dispatch' || github.events.inputs.push-image == true }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -143,13 +137,13 @@ jobs:
|
||||
${{ env.DEPLOY_LATEST == 'true' && 'type=raw,value=latest' || null }}
|
||||
${{ env.DEPLOY_BETA == 'true' && 'type=raw,value=beta' || null }}
|
||||
type=raw,value=${{ env.NEXT_VERSION }}
|
||||
- name: Build and maybe push
|
||||
- name: Build and push
|
||||
id: buildPushAction
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
push: ${{ env.PUSH_IMAGE }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
network: host
|
||||
@@ -160,4 +154,4 @@ jobs:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: "Deployment of image has completed for branch ${{ github.ref_name }}. Image ID is '${{ steps.buildPushAction.outputs.imageid }}'. ${{ env.PUSH_IMAGE == 'true' && '' || 'This was a dry run' }}"
|
||||
args: "Deployment of image has completed for branch ${{ github.ref_name }}. Image ID is '${{ steps.buildPushAction.outputs.imageid }}'."
|
||||
@@ -58,6 +58,8 @@ apps/websocket/wssServer.cjs
|
||||
apps/nextjs/.million/
|
||||
packages/cli/cli.cjs
|
||||
|
||||
# e2e mounts
|
||||
e2e/shared/tmp
|
||||
|
||||
#personal backgrounds
|
||||
apps/nextjs/public/images/background.png
|
||||
Vendored
+1
-1
@@ -39,5 +39,5 @@
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/translation/src/lang",
|
||||
],
|
||||
"i18n-ally.keystyle": "auto",
|
||||
"i18n-ally.keystyle": "nested",
|
||||
}
|
||||
@@ -1,3 +1,36 @@
|
||||
## [1.0.0-beta.11](https://github.com/homarr-labs/homarr/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2025-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
* remove import beta badge ([#1829](https://github.com/homarr-labs/homarr/issues/1829)) ([6305c74](https://github.com/homarr-labs/homarr/commit/6305c74f90d943722c915ffbf54b5fd7e68a7dca))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency zod-form-data to ^2.0.5 ([#1822](https://github.com/homarr-labs/homarr/issues/1822)) ([a234fc8](https://github.com/homarr-labs/homarr/commit/a234fc844e5edcef9935efb0725b6ccceadfe69d))
|
||||
* **deps:** update tanstack-query monorepo to ^5.62.12 ([#1843](https://github.com/homarr-labs/homarr/issues/1843)) ([bc5e25e](https://github.com/homarr-labs/homarr/commit/bc5e25e9cd3269a3d8cc8ef521ae75aff50088be))
|
||||
* **icons:** picker does not allow multiple words ([#1827](https://github.com/homarr-labs/homarr/issues/1827)) ([6e21863](https://github.com/homarr-labs/homarr/commit/6e21863d04f7d0598e7e7ca3fb707f9beb09d7ef))
|
||||
* login page in light mode is always black ([#1821](https://github.com/homarr-labs/homarr/issues/1821)) ([cc7d53e](https://github.com/homarr-labs/homarr/commit/cc7d53e84ec8c2dd3698a178ca6f35099194b5d7))
|
||||
* prowlarr integration indexers status changed from id to indexerId ([#1837](https://github.com/homarr-labs/homarr/issues/1837)) ([ee8831a](https://github.com/homarr-labs/homarr/commit/ee8831affe34a33b80a0993aa538c2b16c035f11))
|
||||
|
||||
## [1.0.0-beta.10](https://github.com/homarr-labs/homarr/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2024-12-31)
|
||||
|
||||
### Features
|
||||
|
||||
* add request media ([#1811](https://github.com/homarr-labs/homarr/issues/1811)) ([91e1cef](https://github.com/homarr-labs/homarr/commit/91e1cef611a47d57b42c91c0c6d49fcf2e6a10bb))
|
||||
* **docker:** add support for pgid and puid env variables ([#1759](https://github.com/homarr-labs/homarr/issues/1759)) ([aeb681a](https://github.com/homarr-labs/homarr/commit/aeb681a85839e31284e8f1566adc4191a68ea385))
|
||||
* **integration:** add search engine creation ([#1816](https://github.com/homarr-labs/homarr/issues/1816)) ([f507645](https://github.com/homarr-labs/homarr/commit/f5076454cd644a99439b697f24bb0b1f871b2205))
|
||||
* **widget:** add minecraft server status widget ([#1801](https://github.com/homarr-labs/homarr/issues/1801)) ([0ebf4bc](https://github.com/homarr-labs/homarr/commit/0ebf4bc55e62caf5917340496abb350d564df255))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency octokit to ^4.0.3 ([#1806](https://github.com/homarr-labs/homarr/issues/1806)) ([75aefcb](https://github.com/homarr-labs/homarr/commit/75aefcb0d5210a53e8a00b675dbbce847aa8add4))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.1.1 ([#1788](https://github.com/homarr-labs/homarr/issues/1788)) ([4943e7b](https://github.com/homarr-labs/homarr/commit/4943e7b851cd29291b5e8f2f9d0e084b4deeffc0))
|
||||
* **deps:** update dependency typescript-eslint to ^8.19.0 ([#1814](https://github.com/homarr-labs/homarr/issues/1814)) ([598f004](https://github.com/homarr-labs/homarr/commit/598f0047eb13d6d0d6b68c9032a7a6648f4b6aa3))
|
||||
* **deps:** update tiptap monorepo to v2.11.0 ([#1813](https://github.com/homarr-labs/homarr/issues/1813)) ([eacda4c](https://github.com/homarr-labs/homarr/commit/eacda4c397cb384a35e9eec0305f9b26f2a22a4a))
|
||||
* **docker:** replace anonymous docker volume with env variable for encrypting secrets ([#1809](https://github.com/homarr-labs/homarr/issues/1809)) ([56b57ad](https://github.com/homarr-labs/homarr/commit/56b57ad171da170c2d8edd6112092cef0b586852))
|
||||
* **health-monitoring:** add months to uptime, fix status update ([#1792](https://github.com/homarr-labs/homarr/issues/1792)) ([c91aae5](https://github.com/homarr-labs/homarr/commit/c91aae53acf27eab96a9150281f98a2156e9e2af))
|
||||
* **media-server:** redesign widget ([#1775](https://github.com/homarr-labs/homarr/issues/1775)) ([23c7d0b](https://github.com/homarr-labs/homarr/commit/23c7d0bbf01215b680f346e7eef8072d8b9db09e))
|
||||
|
||||
## [1.0.0-beta.9](https://github.com/homarr-labs/homarr/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2024-12-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+20
-30
@@ -25,51 +25,41 @@ RUN corepack enable pnpm && pnpm build
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# gettext is required for envsubst
|
||||
RUN apk add --no-cache redis nginx bash gettext su-exec
|
||||
# gettext is required for envsubst, openssl for generating AUTH_SECRET, su-exec for running application as non-root
|
||||
RUN apk add --no-cache redis nginx bash gettext su-exec openssl
|
||||
RUN mkdir /appdata
|
||||
VOLUME /appdata
|
||||
RUN mkdir /secrets
|
||||
VOLUME /secrets
|
||||
|
||||
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Enable homarr cli
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
|
||||
COPY --from=builder /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
|
||||
RUN echo $'#!/bin/bash\ncd /app/apps/cli && node ./cli.cjs "$@"' > /usr/bin/homarr
|
||||
RUN chmod +x /usr/bin/homarr
|
||||
|
||||
# Don't run production as root
|
||||
RUN chown -R nextjs:nodejs /secrets
|
||||
RUN mkdir -p /var/cache/nginx && chown -R nextjs:nodejs /var/cache/nginx && \
|
||||
mkdir -p /var/log/nginx && chown -R nextjs:nodejs /var/log/nginx && \
|
||||
mkdir -p /var/lib/nginx && chown -R nextjs:nodejs /var/lib/nginx && \
|
||||
touch /run/nginx/nginx.pid && chown -R nextjs:nodejs /run/nginx/nginx.pid && \
|
||||
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs && chown -R nextjs:nodejs /etc/nginx
|
||||
RUN mkdir -p /var/cache/nginx && \
|
||||
mkdir -p /var/log/nginx && \
|
||||
mkdir -p /var/lib/nginx && \
|
||||
touch /run/nginx/nginx.pid && \
|
||||
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs
|
||||
|
||||
COPY --from=builder /app/apps/nextjs/next.config.mjs .
|
||||
COPY --from=builder /app/apps/nextjs/package.json .
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
|
||||
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
|
||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/packages/db/migrations ./db/migrations
|
||||
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/nextjs/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/apps/nextjs/public ./apps/nextjs/public
|
||||
COPY --chown=nextjs:nodejs scripts/run.sh ./run.sh
|
||||
COPY scripts/entrypoint.sh ./entrypoint.sh
|
||||
RUN chmod +x ./entrypoint.sh
|
||||
COPY --chown=nextjs:nodejs scripts/generateRandomSecureKey.js ./generateRandomSecureKey.js
|
||||
COPY --chown=nextjs:nodejs packages/redis/redis.conf /app/redis.conf
|
||||
COPY --chown=nextjs:nodejs nginx.conf /etc/nginx/templates/nginx.conf
|
||||
COPY --from=builder /app/apps/nextjs/.next/standalone ./
|
||||
COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
|
||||
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
|
||||
COPY scripts/run.sh ./run.sh
|
||||
COPY --chmod=777 scripts/entrypoint.sh ./entrypoint.sh
|
||||
COPY packages/redis/redis.conf /app/redis.conf
|
||||
COPY nginx.conf /etc/nginx/templates/nginx.conf
|
||||
|
||||
|
||||
ENV DB_URL='/appdata/db/db.sqlite'
|
||||
@@ -78,4 +68,4 @@ ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -1,6 +1,7 @@
|
||||
// Importing env files here to validate on build
|
||||
import "@homarr/auth/env.mjs";
|
||||
import "@homarr/db/env.mjs";
|
||||
import "@homarr/common/env.mjs";
|
||||
|
||||
import MillionLint from "@million/lint";
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
"@million/lint": "1.0.14",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@tabler/icons-react": "^3.26.0",
|
||||
"@tanstack/react-query": "^5.62.11",
|
||||
"@tanstack/react-query-devtools": "^5.62.11",
|
||||
"@tanstack/react-query-next-experimental": "5.62.11",
|
||||
"@tanstack/react-query": "^5.62.12",
|
||||
"@tanstack/react-query-devtools": "^5.62.12",
|
||||
"@tanstack/react-query-next-experimental": "5.62.12",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
"@trpc/react-query": "next",
|
||||
@@ -82,12 +82,12 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "2.4.5",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"concurrently": "^9.1.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.17.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function InviteUsagePage({ params, searchParams }: InviteUs
|
||||
{t("subtitle")}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Card bg="dark.8" w={64 * 6} maw="90vw">
|
||||
<Card withBorder w={64 * 6} maw="90vw">
|
||||
<RegistrationForm invite={invite} />
|
||||
</Card>
|
||||
<Text size="xs" c="gray.5" ta="center">
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function Login({ searchParams }: LoginProps) {
|
||||
{t("subtitle")}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Card bg="dark.8" w={64 * 6} maw="90vw">
|
||||
<Card withBorder w={64 * 6} maw="90vw">
|
||||
<LoginForm
|
||||
providers={env.AUTH_PROVIDERS}
|
||||
oidcClientName={env.AUTH_OIDC_CLIENT_NAME}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Affix, Button, Group, Menu } from "@mantine/core";
|
||||
import { Affix, Button, Menu } from "@mantine/core";
|
||||
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
|
||||
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddBoardModal, ImportBoardModal } from "@homarr/modals-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { BetaBadge } from "@homarr/ui";
|
||||
|
||||
export const CreateBoardButton = () => {
|
||||
const t = useI18n();
|
||||
@@ -26,10 +25,7 @@ export const CreateBoardButton = () => {
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={openImportModal} leftSection={<IconFileImport size="1rem" />}>
|
||||
<Group>
|
||||
{t("board.action.oldImport.label")}
|
||||
<BetaBadge size="xs" />
|
||||
</Group>
|
||||
{t("board.action.oldImport.label")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Alert, Button, Fieldset, Group, SegmentedControl, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { Alert, Button, Checkbox, Fieldset, Group, SegmentedControl, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||
import { getAllSecretKindOptions, getIntegrationName } from "@homarr/definitions";
|
||||
import { getAllSecretKindOptions, getIntegrationName, integrationDefs } from "@homarr/definitions";
|
||||
import type { UseFormReturnType } from "@homarr/form";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
||||
@@ -38,6 +38,7 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
kind,
|
||||
value: "",
|
||||
})),
|
||||
attemptSearchEngineCreation: true,
|
||||
},
|
||||
});
|
||||
const { mutateAsync, isPending } = clientApi.integration.create.useMutation();
|
||||
@@ -78,6 +79,8 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
);
|
||||
};
|
||||
|
||||
const supportsSearchEngine = integrationDefs[searchParams.kind].category.flat().includes("search");
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit((value) => void handleSubmitAsync(value))}>
|
||||
<Stack>
|
||||
@@ -104,6 +107,16 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
|
||||
{supportsSearchEngine && (
|
||||
<Checkbox
|
||||
label={t("integration.field.attemptSearchEngineCreation.label")}
|
||||
description={t("integration.field.attemptSearchEngineCreation.description", {
|
||||
kind: getIntegrationName(searchParams.kind),
|
||||
})}
|
||||
{...form.getInputProps("attemptSearchEngineCreation", { type: "checkbox" })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Group justify="end" align="center">
|
||||
<Button variant="default" component={Link} href="/manage/integrations">
|
||||
{t("common.action.backToOverview")}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.10.5",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.17.0",
|
||||
"prettier": "^3.4.2",
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { chromium } from "playwright";
|
||||
import { GenericContainer } from "testcontainers";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { OnboardingActions } from "./shared/actions/onboarding-actions";
|
||||
import { createHomarrContainer, withLogs } from "./shared/create-homarr-container";
|
||||
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
||||
|
||||
const defaultCredentials = {
|
||||
username: "admin",
|
||||
password: "password",
|
||||
email: "admin@homarr.dev",
|
||||
group: "lldap_admin",
|
||||
};
|
||||
|
||||
const ldapBase = "dc=example,dc=com";
|
||||
|
||||
describe("LLDAP authorization", () => {
|
||||
test("Authorize with LLDAP successfully", async () => {
|
||||
// Arrange
|
||||
const lldapContainer = await createLldapContainer().start();
|
||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||
const homarrContainer = await createHomarrContainer({
|
||||
environment: {
|
||||
AUTH_PROVIDERS: "ldap",
|
||||
AUTH_LDAP_URI: `ldap://host.docker.internal:${lldapContainer.getMappedPort(3890)}`,
|
||||
AUTH_LDAP_BASE: ldapBase,
|
||||
AUTH_LDAP_BIND_DN: `uid=${defaultCredentials.username},ou=People,${ldapBase}`,
|
||||
AUTH_LDAP_BIND_PASSWORD: defaultCredentials.password,
|
||||
},
|
||||
mounts: {
|
||||
"/appdata": localMountPath,
|
||||
},
|
||||
}).start();
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
const onboardingActions = new OnboardingActions(page, db);
|
||||
await onboardingActions.skipOnboardingAsync({
|
||||
group: defaultCredentials.group,
|
||||
});
|
||||
|
||||
// Act
|
||||
await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}/auth/login`);
|
||||
await page.getByLabel("Username").fill(defaultCredentials.username);
|
||||
await page.getByLabel("Password").fill(defaultCredentials.password);
|
||||
await page.locator("css=button[type='submit']").click();
|
||||
|
||||
// Assert
|
||||
await page.waitForURL(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
|
||||
const users = await db.query.users.findMany({
|
||||
with: {
|
||||
groups: {
|
||||
with: {
|
||||
group: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(users).toHaveLength(1);
|
||||
const user = users[0]!;
|
||||
expect(user).toEqual(
|
||||
expect.objectContaining({
|
||||
name: defaultCredentials.username,
|
||||
email: defaultCredentials.email,
|
||||
provider: "ldap",
|
||||
}),
|
||||
);
|
||||
|
||||
const groups = user.groups.map((g) => g.group.name);
|
||||
expect(groups).toContain(defaultCredentials.group);
|
||||
|
||||
// Cleanup
|
||||
await browser.close();
|
||||
await homarrContainer.stop();
|
||||
await lldapContainer.stop();
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
const createLldapContainer = () => {
|
||||
return withLogs(
|
||||
new GenericContainer("lldap/lldap:stable").withExposedPorts(3890).withEnvironment({
|
||||
LLDAP_JWT_SECRET: "REPLACE_WITH_RANDOM",
|
||||
LLDAP_KEY_SEED: "REPLACE_WITH_RANDOM",
|
||||
LLDAP_LDAP_BASE_DN: ldapBase,
|
||||
LLDAP_LDAP_USER_PASS: defaultCredentials.password,
|
||||
LLDAP_LDAP_USER_EMAIL: defaultCredentials.email,
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { chromium } from "playwright";
|
||||
import { describe, test } from "vitest";
|
||||
|
||||
import { OnboardingActions } from "./shared/actions/onboarding-actions";
|
||||
import { OnboardingAssertions } from "./shared/assertions/onboarding-assertions";
|
||||
import { createHomarrContainer } from "./shared/create-homarr-container";
|
||||
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
||||
|
||||
describe("Onboarding", () => {
|
||||
test("Credentials onboarding should be successful", async () => {
|
||||
// Arrange
|
||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||
const homarrContainer = await createHomarrContainer({
|
||||
mounts: {
|
||||
"/appdata": localMountPath,
|
||||
},
|
||||
}).start();
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const actions = new OnboardingActions(page, db);
|
||||
const assertions = new OnboardingAssertions(page, db);
|
||||
|
||||
// Act
|
||||
await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
|
||||
await actions.startOnboardingAsync("scratch");
|
||||
await actions.processUserStepAsync({
|
||||
username: "admin",
|
||||
password: "Comp(exP4sswOrd",
|
||||
confirmPassword: "Comp(exP4sswOrd",
|
||||
});
|
||||
await actions.processSettingsStepAsync();
|
||||
|
||||
// Assert
|
||||
await assertions.assertFinishStepVisibleAsync();
|
||||
await assertions.assertUserAndAdminGroupInsertedAsync("admin");
|
||||
await assertions.assertDbOnboardingStepAsync("finish");
|
||||
|
||||
// Cleanup
|
||||
await browser.close();
|
||||
await homarrContainer.stop();
|
||||
}, 60_000);
|
||||
|
||||
test("External provider onboarding setup should be successful", async () => {
|
||||
// Arrange
|
||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||
const homarrContainer = await createHomarrContainer({
|
||||
environment: {
|
||||
AUTH_PROVIDERS: "ldap",
|
||||
AUTH_LDAP_URI: "ldap://host.docker.internal:3890",
|
||||
AUTH_LDAP_BASE: "",
|
||||
AUTH_LDAP_BIND_DN: "",
|
||||
AUTH_LDAP_BIND_PASSWORD: "",
|
||||
},
|
||||
mounts: {
|
||||
"/appdata": localMountPath,
|
||||
},
|
||||
}).start();
|
||||
const externalGroupName = "oidc-admins";
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const actions = new OnboardingActions(page, db);
|
||||
const assertions = new OnboardingAssertions(page, db);
|
||||
|
||||
// Act
|
||||
await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
|
||||
await actions.startOnboardingAsync("scratch");
|
||||
await actions.processExternalGroupStepAsync({
|
||||
name: externalGroupName,
|
||||
});
|
||||
await actions.processSettingsStepAsync();
|
||||
|
||||
// Assert
|
||||
await assertions.assertFinishStepVisibleAsync();
|
||||
await assertions.assertExternalGroupInsertedAsync(externalGroupName);
|
||||
await assertions.assertDbOnboardingStepAsync("finish");
|
||||
|
||||
// Cleanup
|
||||
await browser.close();
|
||||
await homarrContainer.stop();
|
||||
}, 60_000);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import type { Page } from "playwright";
|
||||
|
||||
import * as sqliteSchema from "../../../packages/db/schema/sqlite";
|
||||
import type { SqliteDatabase } from "../e2e-db";
|
||||
|
||||
export class OnboardingActions {
|
||||
private readonly page: Page;
|
||||
private readonly db: SqliteDatabase;
|
||||
|
||||
constructor(page: Page, db: SqliteDatabase) {
|
||||
this.page = page;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async skipOnboardingAsync(input?: { group?: string }) {
|
||||
await this.db.update(sqliteSchema.onboarding).set({
|
||||
step: "finish",
|
||||
});
|
||||
|
||||
if (input?.group) {
|
||||
await this.db.insert(sqliteSchema.groups).values({
|
||||
id: createId(),
|
||||
name: input.group,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async startOnboardingAsync(type: "scratch" | "before 1.0") {
|
||||
await this.page.locator("button", { hasText: type }).click();
|
||||
}
|
||||
|
||||
public async processUserStepAsync(input: { username: string; password: string; confirmPassword: string }) {
|
||||
await this.page.waitForSelector("text=administrator user");
|
||||
|
||||
await this.page.getByLabel("Username").fill(input.username);
|
||||
await this.page.getByLabel("Password", { exact: true }).fill(input.password);
|
||||
await this.page.getByLabel("Confirm password").fill(input.confirmPassword);
|
||||
|
||||
await this.page.locator("css=button[type='submit']").click();
|
||||
}
|
||||
|
||||
public async processExternalGroupStepAsync(input: { name: string }) {
|
||||
await this.page.waitForSelector("text=external provider");
|
||||
await this.page.locator("input").fill(input.name);
|
||||
await this.page.locator("css=button[type='submit']").click();
|
||||
}
|
||||
|
||||
public async processSettingsStepAsync() {
|
||||
await this.page.waitForSelector("text=Analytics");
|
||||
await this.page.locator("css=button[type='submit']").click();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { Page } from "playwright";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import * as sqliteSchema from "../../../packages/db/schema/sqlite";
|
||||
import { OnboardingStep } from "../../../packages/definitions/src";
|
||||
import { credentialsAdminGroup } from "../../../packages/definitions/src/group";
|
||||
import type { SqliteDatabase } from "../e2e-db";
|
||||
|
||||
export class OnboardingAssertions {
|
||||
private readonly page: Page;
|
||||
private readonly db: SqliteDatabase;
|
||||
|
||||
constructor(page: Page, db: SqliteDatabase) {
|
||||
this.page = page;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async assertDbOnboardingStepAsync(expectedStep: OnboardingStep) {
|
||||
const onboarding = await this.db.query.onboarding.findFirst();
|
||||
expect(onboarding?.step).toEqual(expectedStep);
|
||||
}
|
||||
|
||||
public async assertUserAndAdminGroupInsertedAsync(expectedUsername: string) {
|
||||
const users = await this.db.query.users.findMany({
|
||||
with: {
|
||||
groups: {
|
||||
with: {
|
||||
group: {
|
||||
with: {
|
||||
permissions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const user = users.find((u) => u.name === expectedUsername);
|
||||
expect(user).toBeDefined();
|
||||
|
||||
const adminGroup = user!.groups.find((g) => g.group.name === credentialsAdminGroup);
|
||||
expect(adminGroup).toBeDefined();
|
||||
expect(adminGroup!.group.permissions).toEqual([expect.objectContaining({ permission: "admin" })]);
|
||||
}
|
||||
|
||||
public async assertExternalGroupInsertedAsync(expectedGroupName: string) {
|
||||
const group = await this.db.query.groups.findFirst({
|
||||
where: eq(sqliteSchema.groups.name, expectedGroupName),
|
||||
with: {
|
||||
permissions: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(group).toBeDefined();
|
||||
expect(group!.permissions).toEqual([expect.objectContaining({ permission: "admin" })]);
|
||||
}
|
||||
|
||||
public async assertFinishStepVisibleAsync() {
|
||||
await this.page.waitForSelector("text=completed the setup", { timeout: 5000 });
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,51 @@
|
||||
import { GenericContainer, Wait } from "testcontainers";
|
||||
import { Environment } from "testcontainers/build/types";
|
||||
|
||||
export const createHomarrContainer = () => {
|
||||
export const createHomarrContainer = (
|
||||
options: {
|
||||
environment?: Environment;
|
||||
mounts?: {
|
||||
"/appdata"?: string;
|
||||
"/var/run/docker.sock"?: string;
|
||||
};
|
||||
} = {},
|
||||
) => {
|
||||
if (!process.env.CI) {
|
||||
throw new Error("This test should only be run in CI or with a homarr image named 'homarr-e2e'");
|
||||
}
|
||||
|
||||
return new GenericContainer("homarr-e2e")
|
||||
const container = new GenericContainer("homarr-e2e")
|
||||
.withExposedPorts(7575)
|
||||
.withWaitStrategy(Wait.forHttp("/api/health/ready", 7575));
|
||||
.withEnvironment({
|
||||
...options.environment,
|
||||
SECRET_ENCRYPTION_KEY: "0".repeat(64),
|
||||
})
|
||||
.withBindMounts(
|
||||
Object.entries(options.mounts ?? {})
|
||||
.filter((item) => item?.[0] !== undefined)
|
||||
.map(([container, local]) => ({
|
||||
source: local,
|
||||
target: container,
|
||||
})),
|
||||
)
|
||||
.withWaitStrategy(Wait.forHttp("/api/health/ready", 7575))
|
||||
.withExtraHosts([
|
||||
{
|
||||
// This enabled the usage of host.docker.internal as hostname in the container
|
||||
host: "host.docker.internal",
|
||||
ipAddress: "host-gateway",
|
||||
},
|
||||
]);
|
||||
|
||||
return withLogs(container);
|
||||
};
|
||||
|
||||
export const withLogs = (container: GenericContainer) => {
|
||||
container.withLogConsumer((stream) =>
|
||||
stream
|
||||
.on("data", (line) => console.log(line))
|
||||
.on("err", (line) => console.error(line))
|
||||
.on("end", () => console.log("Stream closed")),
|
||||
);
|
||||
return container;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { mkdir } from "fs/promises";
|
||||
import path from "path";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import Database from "better-sqlite3";
|
||||
import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
|
||||
import * as sqliteSchema from "../../packages/db/schema/sqlite";
|
||||
|
||||
export const createSqliteDbFileAsync = async () => {
|
||||
const localMountPath = path.join(__dirname, "tmp", createId());
|
||||
await mkdir(path.join(localMountPath, "db"), { recursive: true });
|
||||
|
||||
const localDbUrl = path.join(localMountPath, "db", "db.sqlite");
|
||||
|
||||
const connection = new Database(localDbUrl);
|
||||
const db = drizzle(connection, {
|
||||
schema: sqliteSchema,
|
||||
casing: "snake_case",
|
||||
});
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: path.join(__dirname, "..", "..", "packages", "db", "migrations", "sqlite"),
|
||||
});
|
||||
|
||||
return {
|
||||
db,
|
||||
localMountPath,
|
||||
};
|
||||
};
|
||||
|
||||
export type SqliteDatabase = BetterSQLite3Database<typeof sqliteSchema>;
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.0.0-beta.9",
|
||||
"version": "1.0.0-beta.11",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -32,11 +32,11 @@
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^13.0.0",
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^11.0.1",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/release-notes-generator": "^14.0.2",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.3.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
@@ -45,14 +45,14 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^25.0.1",
|
||||
"prettier": "^3.4.2",
|
||||
"semantic-release": "^24.2.0",
|
||||
"semantic-release": "^24.2.1",
|
||||
"testcontainers": "^10.16.0",
|
||||
"turbo": "^2.3.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.1",
|
||||
"packageManager": "pnpm@9.15.2",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
|
||||
@@ -41,16 +41,17 @@
|
||||
"@trpc/react-query": "next",
|
||||
"@trpc/server": "next",
|
||||
"dockerode": "^4.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "^14.2.22",
|
||||
"react": "^19.0.0",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.1.0"
|
||||
"trpc-to-openapi": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.32",
|
||||
"@types/dockerode": "^3.3.33",
|
||||
"eslint": "^9.17.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.2"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { count, like } from "@homarr/db";
|
||||
import { and, count, like } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
@@ -15,7 +15,11 @@ export const iconsRouter = createTRPCRouter({
|
||||
name: true,
|
||||
url: true,
|
||||
},
|
||||
where: (input.searchText?.length ?? 0) > 0 ? like(icons.name, `%${input.searchText}%`) : undefined,
|
||||
where:
|
||||
(input.searchText?.length ?? 0) > 0
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
and(...input.searchText!.split(" ").map((keyword) => like(icons.name, `%${keyword}%`)))
|
||||
: undefined,
|
||||
limit: input.limitPerGroup,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
integrations,
|
||||
integrationSecrets,
|
||||
integrationUserPermissions,
|
||||
searchEngines,
|
||||
} from "@homarr/db/schema";
|
||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||
import {
|
||||
getIconUrl,
|
||||
getIntegrationKindsByCategory,
|
||||
getPermissionsWithParents,
|
||||
integrationDefs,
|
||||
@@ -192,6 +194,18 @@ export const integrationRouter = createTRPCRouter({
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (input.attemptSearchEngineCreation) {
|
||||
const icon = getIconUrl(input.kind);
|
||||
await ctx.db.insert(searchEngines).values({
|
||||
id: createId(),
|
||||
name: input.name,
|
||||
integrationId,
|
||||
type: "fromIntegration",
|
||||
iconUrl: icon,
|
||||
short: await getNextValidShortNameForSearchEngineAsync(ctx.db, input.name),
|
||||
});
|
||||
}
|
||||
}),
|
||||
update: protectedProcedure.input(validation.integration.update).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
@@ -411,6 +425,36 @@ interface AddSecretInput {
|
||||
value: string;
|
||||
kind: IntegrationSecretKind;
|
||||
}
|
||||
|
||||
const getNextValidShortNameForSearchEngineAsync = async (db: Database, integrationName: string) => {
|
||||
const searchEngines = await db.query.searchEngines.findMany({
|
||||
columns: {
|
||||
short: true,
|
||||
},
|
||||
});
|
||||
|
||||
const usedShortNames = searchEngines.flatMap((searchEngine) => searchEngine.short.toLowerCase());
|
||||
const nameByIntegrationName = integrationName.slice(0, 1).toLowerCase();
|
||||
|
||||
if (!usedShortNames.includes(nameByIntegrationName)) {
|
||||
return nameByIntegrationName;
|
||||
}
|
||||
|
||||
// 8 is max length constraint
|
||||
for (let i = 2; i < 9999999; i++) {
|
||||
const generatedName = `${nameByIntegrationName}${i}`;
|
||||
if (usedShortNames.includes(generatedName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return generatedName;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Unable to automatically generate a short name. All possible variations were exhausted. Please disable the automatic creation and choose one later yourself.",
|
||||
);
|
||||
};
|
||||
|
||||
const addSecretAsync = async (db: Database, input: AddSecretInput) => {
|
||||
await db.insert(integrationSecrets).values({
|
||||
kind: input.kind,
|
||||
|
||||
@@ -2,8 +2,10 @@ import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { createId, eq, like, sql } from "@homarr/db";
|
||||
import { searchEngines } from "@homarr/db/schema";
|
||||
import { integrationCreator } from "@homarr/integrations";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc";
|
||||
|
||||
export const searchEngineRouter = createTRPCRouter({
|
||||
@@ -56,9 +58,32 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.searchEngines.findMany({
|
||||
where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),
|
||||
with: {
|
||||
integration: {
|
||||
columns: {
|
||||
kind: true,
|
||||
url: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
limit: input.limit,
|
||||
});
|
||||
}),
|
||||
getMediaRequestOptions: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
||||
.input(validation.common.mediaRequestOptions)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const integration = integrationCreator(ctx.integration);
|
||||
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
|
||||
}),
|
||||
requestMedia: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
||||
.input(validation.common.requestMedia)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const integration = integrationCreator(ctx.integration);
|
||||
return await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons);
|
||||
}),
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("search-engine-create")
|
||||
.input(validation.searchEngine.manage)
|
||||
|
||||
@@ -179,6 +179,7 @@ describe("create should create a new integration", () => {
|
||||
kind: "jellyfin" as const,
|
||||
url: "http://jellyfin.local",
|
||||
secrets: [{ kind: "apiKey" as const, value: "1234567890" }],
|
||||
attemptSearchEngineCreation: false,
|
||||
};
|
||||
|
||||
const fakeNow = new Date("2023-07-01T00:00:00Z");
|
||||
@@ -201,6 +202,48 @@ describe("create should create a new integration", () => {
|
||||
expect(dbSecret!.updatedAt).toEqual(fakeNow);
|
||||
});
|
||||
|
||||
test("with create integration access should create a new integration when creating search engine", async () => {
|
||||
const db = createDb();
|
||||
const caller = integrationRouter.createCaller({
|
||||
db,
|
||||
session: defaultSessionWithPermissions(["integration-create"]),
|
||||
});
|
||||
const input = {
|
||||
name: "Jellyseerr",
|
||||
kind: "jellyseerr" as const,
|
||||
url: "http://jellyseerr.local",
|
||||
secrets: [{ kind: "apiKey" as const, value: "1234567890" }],
|
||||
attemptSearchEngineCreation: true,
|
||||
};
|
||||
|
||||
const fakeNow = new Date("2023-07-01T00:00:00Z");
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(fakeNow);
|
||||
await caller.create(input);
|
||||
vi.useRealTimers();
|
||||
|
||||
const dbIntegration = await db.query.integrations.findFirst();
|
||||
const dbSecret = await db.query.integrationSecrets.findFirst();
|
||||
const dbSearchEngine = await db.query.searchEngines.findFirst();
|
||||
expect(dbIntegration).toBeDefined();
|
||||
expect(dbIntegration!.name).toBe(input.name);
|
||||
expect(dbIntegration!.kind).toBe(input.kind);
|
||||
expect(dbIntegration!.url).toBe(input.url);
|
||||
|
||||
expect(dbSecret!.integrationId).toBe(dbIntegration!.id);
|
||||
expect(dbSecret).toBeDefined();
|
||||
expect(dbSecret!.kind).toBe(input.secrets[0]!.kind);
|
||||
expect(dbSecret!.value).toMatch(/^[a-f0-9]+.[a-f0-9]+$/);
|
||||
expect(dbSecret!.updatedAt).toEqual(fakeNow);
|
||||
|
||||
expect(dbSearchEngine!.integrationId).toBe(dbIntegration!.id);
|
||||
expect(dbSearchEngine!.short).toBe("j");
|
||||
expect(dbSearchEngine!.name).toBe(input.name);
|
||||
expect(dbSearchEngine!.iconUrl).toBe(
|
||||
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyseerr.png",
|
||||
);
|
||||
});
|
||||
|
||||
test("without create integration access should throw permission error", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
@@ -213,6 +256,7 @@ describe("create should create a new integration", () => {
|
||||
kind: "jellyfin" as const,
|
||||
url: "http://jellyfin.local",
|
||||
secrets: [{ kind: "apiKey" as const, value: "1234567890" }],
|
||||
attemptSearchEngineCreation: false,
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
@@ -8,6 +8,7 @@ import { indexerManagerRouter } from "./indexer-manager";
|
||||
import { mediaRequestsRouter } from "./media-requests";
|
||||
import { mediaServerRouter } from "./media-server";
|
||||
import { mediaTranscodingRouter } from "./media-transcoding";
|
||||
import { minecraftRouter } from "./minecraft";
|
||||
import { notebookRouter } from "./notebook";
|
||||
import { rssFeedRouter } from "./rssFeed";
|
||||
import { smartHomeRouter } from "./smart-home";
|
||||
@@ -27,4 +28,5 @@ export const widgetRouter = createTRPCRouter({
|
||||
indexerManager: indexerManagerRouter,
|
||||
healthMonitoring: healthMonitoringRouter,
|
||||
mediaTranscoding: mediaTranscodingRouter,
|
||||
minecraft: minecraftRouter,
|
||||
});
|
||||
@@ -21,6 +21,7 @@ export const mediaServerRouter = createTRPCRouter({
|
||||
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return {
|
||||
integrationId: integration.id,
|
||||
integrationKind: integration.kind,
|
||||
sessions: data,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { MinecraftServerStatus } from "@homarr/request-handler/minecraft-server-status";
|
||||
import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const serverStatusInputSchema = z.object({
|
||||
domain: z.string().nonempty(),
|
||||
isBedrockServer: z.boolean(),
|
||||
});
|
||||
export const minecraftRouter = createTRPCRouter({
|
||||
getServerStatus: publicProcedure.input(serverStatusInputSchema).query(async ({ input }) => {
|
||||
const innerHandler = minecraftServerStatusRequestHandler.handler({
|
||||
isBedrockServer: input.isBedrockServer,
|
||||
domain: input.domain,
|
||||
});
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
}),
|
||||
subscribeServerStatus: publicProcedure.input(serverStatusInputSchema).subscription(({ input }) => {
|
||||
return observable<MinecraftServerStatus>((emit) => {
|
||||
const innerHandler = minecraftServerStatusRequestHandler.handler({
|
||||
isBedrockServer: input.isBedrockServer,
|
||||
domain: input.domain,
|
||||
});
|
||||
const unsubscribe = innerHandler.subscribe((data) => {
|
||||
emit.next(data);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -64,7 +64,6 @@ export const env = createEnv({
|
||||
server: {
|
||||
AUTH_LOGOUT_REDIRECT_URL: z.string().url().optional(),
|
||||
AUTH_SESSION_EXPIRY_TIME: createDurationSchema("30d"),
|
||||
AUTH_SECRET: process.env.NODE_ENV === "production" ? z.string().min(1) : z.string().min(1).optional(),
|
||||
AUTH_PROVIDERS: authProvidersSchema,
|
||||
...(authProviders.includes("oidc")
|
||||
? {
|
||||
@@ -98,7 +97,6 @@ export const env = createEnv({
|
||||
runtimeEnv: {
|
||||
AUTH_LOGOUT_REDIRECT_URL: process.env.AUTH_LOGOUT_REDIRECT_URL,
|
||||
AUTH_SESSION_EXPIRY_TIME: process.env.AUTH_SESSION_EXPIRY_TIME,
|
||||
AUTH_SECRET: process.env.AUTH_SECRET,
|
||||
AUTH_PROVIDERS: process.env.AUTH_PROVIDERS,
|
||||
AUTH_LDAP_BASE: process.env.AUTH_LDAP_BASE,
|
||||
AUTH_LDAP_BIND_DN: process.env.AUTH_LDAP_BIND_DN,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
SECRET_ENCRYPTION_KEY: z
|
||||
.string({
|
||||
required_error: `SECRET_ENCRYPTION_KEY is required${errorSuffix}`,
|
||||
})
|
||||
.min(64, {
|
||||
message: `SECRET_ENCRYPTION_KEY has to be 64 characters${errorSuffix}`,
|
||||
})
|
||||
.max(64, {
|
||||
message: `SECRET_ENCRYPTION_KEY has to be 64 characters${errorSuffix}`,
|
||||
})
|
||||
.regex(/^[0-9a-fA-F]{64}$/, {
|
||||
message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`,
|
||||
}),
|
||||
},
|
||||
runtimeEnv: {
|
||||
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
|
||||
},
|
||||
skipValidation:
|
||||
Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint",
|
||||
});
|
||||
@@ -8,7 +8,8 @@
|
||||
".": "./index.ts",
|
||||
"./types": "./src/types.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./client": "./src/client.ts"
|
||||
"./client": "./src/client.ts",
|
||||
"./env.mjs": "./env.mjs"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { env } from "../env.mjs";
|
||||
|
||||
const algorithm = "aes-256-cbc"; //Using AES encryption
|
||||
const fallbackKey = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
const encryptionKey = process.env.ENCRYPTION_KEY ?? fallbackKey; // Fallback to a default key for local development
|
||||
if (encryptionKey === fallbackKey) {
|
||||
logger.warn("Using a fallback encryption key, stored secrets are not secure");
|
||||
|
||||
// We never want to use the fallback key in production
|
||||
if (process.env.NODE_ENV === "production" && process.env.CI !== "true") {
|
||||
throw new Error("Encryption key is not set");
|
||||
}
|
||||
}
|
||||
|
||||
const key = Buffer.from(encryptionKey, "hex");
|
||||
// We fallback to a key of 0s if the key was not provided because env validation was skipped
|
||||
// This should only be the case in CI
|
||||
const key = Buffer.from(env.SECRET_ENCRYPTION_KEY || "0".repeat(64), "hex");
|
||||
|
||||
export function encryptSecret(text: string): `${string}.${string}` {
|
||||
const initializationVector = crypto.randomBytes(16);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { mediaOrganizerJob } from "./jobs/integrations/media-organizer";
|
||||
import { mediaRequestListJob, mediaRequestStatsJob } from "./jobs/integrations/media-requests";
|
||||
import { mediaServerJob } from "./jobs/integrations/media-server";
|
||||
import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding";
|
||||
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
|
||||
import { pingJob } from "./jobs/ping";
|
||||
import type { RssFeed } from "./jobs/rss-feeds";
|
||||
import { rssFeedsJob } from "./jobs/rss-feeds";
|
||||
@@ -33,6 +34,7 @@ export const jobGroup = createCronJobGroup({
|
||||
sessionCleanup: sessionCleanupJob,
|
||||
updateChecker: updateCheckerJob,
|
||||
mediaTranscoding: mediaTranscodingJob,
|
||||
minecraftServerStatus: minecraftServerStatusJob,
|
||||
});
|
||||
|
||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../widgets/src";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const minecraftServerStatusJob = createCronJob("minecraftServerStatus", EVERY_5_MINUTES).withCallback(
|
||||
async () => {
|
||||
const dbItems = await db.query.items.findMany({
|
||||
where: eq(items.kind, "minecraftServerStatus"),
|
||||
});
|
||||
|
||||
await Promise.allSettled(
|
||||
dbItems.map(async (item) => {
|
||||
const options = SuperJSON.parse<WidgetComponentProps<"minecraftServerStatus">["options"]>(item.options);
|
||||
const innerHandler = minecraftServerStatusRequestHandler.handler(options);
|
||||
await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `search_engine` ADD CONSTRAINT `search_engine_short_unique` UNIQUE(`short`);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -127,6 +127,13 @@
|
||||
"when": 1733777544067,
|
||||
"tag": "0017_tired_penance",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "5",
|
||||
"when": 1735593853768,
|
||||
"tag": "0018_mighty_shaman",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CREATE UNIQUE INDEX `search_engine_short_unique` ON `search_engine` (`short`);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -127,6 +127,13 @@
|
||||
"when": 1733777395703,
|
||||
"tag": "0017_small_rumiko_fujikawa",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "6",
|
||||
"when": 1735593831501,
|
||||
"tag": "0018_cheerful_tattoo",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -389,7 +389,7 @@ export const searchEngines = mysqlTable("search_engine", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
iconUrl: text().notNull(),
|
||||
name: varchar({ length: 64 }).notNull(),
|
||||
short: varchar({ length: 8 }).notNull(),
|
||||
short: varchar({ length: 8 }).unique().notNull(),
|
||||
description: text(),
|
||||
urlTemplate: text(),
|
||||
type: varchar({ length: 64 }).$type<SearchEngineType>().notNull().default("generic"),
|
||||
|
||||
@@ -375,7 +375,7 @@ export const searchEngines = sqliteTable("search_engine", {
|
||||
id: text().notNull().primaryKey(),
|
||||
iconUrl: text().notNull(),
|
||||
name: text().notNull(),
|
||||
short: text().notNull(),
|
||||
short: text().unique().notNull(),
|
||||
description: text(),
|
||||
urlTemplate: text(),
|
||||
type: text().$type<SearchEngineType>().notNull().default("generic"),
|
||||
|
||||
@@ -9,6 +9,7 @@ export type HomarrDocumentationPath =
|
||||
| "/blog/2023/12/22/updated-documentation"
|
||||
| "/blog/2024/09/23/version-1.0"
|
||||
| "/blog/2024/12/17/open-beta-1.0"
|
||||
| "/blog/2024/12/31/migrate-secret-enryption-key"
|
||||
| "/blog/archive"
|
||||
| "/blog/authors"
|
||||
| "/blog/authors/ajnart"
|
||||
@@ -90,6 +91,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/lists"
|
||||
| "/docs/tags/management"
|
||||
| "/docs/tags/media"
|
||||
| "/docs/tags/minecraft"
|
||||
| "/docs/tags/monitoring"
|
||||
| "/docs/tags/news"
|
||||
| "/docs/tags/notebook"
|
||||
@@ -99,11 +101,13 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/open-media-vault"
|
||||
| "/docs/tags/overseerr"
|
||||
| "/docs/tags/permissions"
|
||||
| "/docs/tags/pgid"
|
||||
| "/docs/tags/pi-hole"
|
||||
| "/docs/tags/ping"
|
||||
| "/docs/tags/programming"
|
||||
| "/docs/tags/proxmox"
|
||||
| "/docs/tags/proxy"
|
||||
| "/docs/tags/puid"
|
||||
| "/docs/tags/roles"
|
||||
| "/docs/tags/rss"
|
||||
| "/docs/tags/search"
|
||||
@@ -134,6 +138,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/advanced/icons"
|
||||
| "/docs/advanced/keyboard-shortcuts"
|
||||
| "/docs/advanced/proxy"
|
||||
| "/docs/advanced/running-as-different-user"
|
||||
| "/docs/advanced/single-sign-on"
|
||||
| "/docs/category/advanced"
|
||||
| "/docs/category/community"
|
||||
@@ -190,6 +195,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/widgets/indexer-manager"
|
||||
| "/docs/widgets/media-requests"
|
||||
| "/docs/widgets/media-server"
|
||||
| "/docs/widgets/minecraft-server-status"
|
||||
| "/docs/widgets/notebook"
|
||||
| "/docs/widgets/rss"
|
||||
| "/docs/widgets/video"
|
||||
|
||||
@@ -15,6 +15,7 @@ export const widgetKinds = [
|
||||
"mediaRequests-requestList",
|
||||
"mediaRequests-requestStats",
|
||||
"mediaTranscoding",
|
||||
"minecraftServerStatus",
|
||||
"rssFeed",
|
||||
"bookmarks",
|
||||
"indexerManager",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./src/icons-fetcher";
|
||||
export * from "./src/types";
|
||||
export * from "./src/auto-icon-searcher";
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Database } from "@homarr/db";
|
||||
import { like } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
|
||||
export const getIconForNameAsync = async (db: Database, name: string) => {
|
||||
return await db.query.icons.findFirst({
|
||||
where: like(icons.name, `%${name}%`),
|
||||
});
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface ISearchableIntegration {
|
||||
searchAsync(query: string): Promise<{ image?: string; name: string; link: string }[]>;
|
||||
export interface ISearchableIntegration<TResult extends { image?: string; name: string; link: string }> {
|
||||
searchAsync(query: string): Promise<TResult[]>;
|
||||
}
|
||||
@@ -6,11 +6,20 @@ import type { ISearchableIntegration } from "../base/searchable-integration";
|
||||
import type { MediaRequest, RequestStats, RequestUser } from "../interfaces/media-requests/media-request";
|
||||
import { MediaAvailability, MediaRequestStatus } from "../interfaces/media-requests/media-request";
|
||||
|
||||
interface OverseerrSearchResult {
|
||||
id: number;
|
||||
name: string;
|
||||
link: string;
|
||||
image?: string;
|
||||
text?: string;
|
||||
type: Exclude<z.infer<typeof searchSchema>["results"], undefined>[number]["mediaType"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overseerr Integration. See https://api-docs.overseerr.dev
|
||||
*/
|
||||
export class OverseerrIntegration extends Integration implements ISearchableIntegration {
|
||||
public async searchAsync(query: string): Promise<{ image?: string; name: string; link: string; text?: string }[]> {
|
||||
export class OverseerrIntegration extends Integration implements ISearchableIntegration<OverseerrSearchResult> {
|
||||
public async searchAsync(query: string) {
|
||||
const response = await fetch(this.url("/api/v1/search", { query }), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
@@ -23,13 +32,53 @@ export class OverseerrIntegration extends Integration implements ISearchableInte
|
||||
}
|
||||
|
||||
return schemaData.results.map((result) => ({
|
||||
id: result.id,
|
||||
name: "name" in result ? result.name : result.title,
|
||||
link: this.url(`/${result.mediaType}/${result.id}`).toString(),
|
||||
image: constructSearchResultImage(result),
|
||||
text: "overview" in result ? result.overview : undefined,
|
||||
type: result.mediaType,
|
||||
inLibrary: result.mediaInfo !== undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
public async getSeriesInformationAsync(mediaType: "movie" | "tv", id: number) {
|
||||
const url = mediaType === "tv" ? this.url(`/api/v1/tv/${id}`) : this.url(`/api/v1/movie/${id}`);
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
});
|
||||
return await mediaInformationSchema.parseAsync(await response.json());
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a media. See https://api-docs.overseerr.dev/#/request/post_request
|
||||
* @param mediaType The media type to request. Can be "movie" or "tv".
|
||||
* @param id The Overseerr ID of the media to request.
|
||||
* @param seasons A list of the seasons that should be requested.
|
||||
*/
|
||||
public async requestMediaAsync(mediaType: "movie" | "tv", id: number, seasons?: number[]): Promise<void> {
|
||||
const url = this.url("/api/v1/request");
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
mediaType,
|
||||
mediaId: id,
|
||||
seasons,
|
||||
}),
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Status code ${response.status} does not match the expected status code. The request was likely not created. Response: ${await response.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async testConnectionAsync(): Promise<void> {
|
||||
const response = await fetch(this.url("/api/v1/auth/me"), {
|
||||
headers: {
|
||||
@@ -220,6 +269,27 @@ interface MovieInformation {
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
const mediaInformationSchema = z.union([
|
||||
z.object({
|
||||
id: z.number(),
|
||||
overview: z.string(),
|
||||
seasons: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
name: z.string().min(0),
|
||||
episodeCount: z.number().min(0),
|
||||
}),
|
||||
),
|
||||
numberOfSeasons: z.number(),
|
||||
posterPath: z.string().startsWith("/"),
|
||||
}),
|
||||
z.object({
|
||||
id: z.number(),
|
||||
overview: z.string(),
|
||||
posterPath: z.string().startsWith("/"),
|
||||
}),
|
||||
]);
|
||||
|
||||
const searchSchema = z.object({
|
||||
results: z
|
||||
.array(
|
||||
@@ -230,6 +300,7 @@ const searchSchema = z.object({
|
||||
name: z.string(),
|
||||
posterPath: z.string().startsWith("/").endsWith(".jpg").nullable(),
|
||||
overview: z.string(),
|
||||
mediaInfo: z.object({}).optional(),
|
||||
}),
|
||||
z.object({
|
||||
id: z.number(),
|
||||
@@ -237,12 +308,14 @@ const searchSchema = z.object({
|
||||
title: z.string(),
|
||||
posterPath: z.string().startsWith("/").endsWith(".jpg").nullable(),
|
||||
overview: z.string(),
|
||||
mediaInfo: z.object({}).optional(),
|
||||
}),
|
||||
z.object({
|
||||
id: z.number(),
|
||||
mediaType: z.literal("person"),
|
||||
name: z.string(),
|
||||
profilePath: z.string().startsWith("/").endsWith(".jpg").nullable(),
|
||||
mediaInfo: z.object({}).optional(),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ export class ProwlarrIntegration extends Integration {
|
||||
);
|
||||
}
|
||||
|
||||
const inactiveIndexerIds = new Set(statusResult.data.map((status: { id: number }) => status.id));
|
||||
const inactiveIndexerIds = new Set(statusResult.data.map((status: { indexerId: number }) => status.indexerId));
|
||||
|
||||
const indexers: Indexer[] = indexersResult.data.map((indexer) => ({
|
||||
id: indexer.id,
|
||||
|
||||
@@ -9,6 +9,6 @@ export const indexerResponseSchema = z.object({
|
||||
|
||||
export const statusResponseSchema = z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
indexerId: z.number(),
|
||||
}),
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./boards";
|
||||
export * from "./invites";
|
||||
export * from "./groups";
|
||||
export * from "./search-engines";
|
||||
@@ -0,0 +1 @@
|
||||
export { RequestMediaModal } from "./request-media-modal";
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useMemo } from "react";
|
||||
import { Button, Group, Image, LoadingOverlay, Stack, Text } from "@mantine/core";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { MRT_Table } from "mantine-react-table";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createModal } from "@homarr/modals";
|
||||
import { showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
||||
|
||||
interface RequestMediaModalProps {
|
||||
integrationId: string;
|
||||
mediaId: number;
|
||||
mediaType: "movie" | "tv";
|
||||
}
|
||||
|
||||
export const RequestMediaModal = createModal<RequestMediaModalProps>(({ actions, innerProps }) => {
|
||||
const { data, isPending: isPendingQuery } = clientApi.searchEngine.getMediaRequestOptions.useQuery({
|
||||
integrationId: innerProps.integrationId,
|
||||
mediaId: innerProps.mediaId,
|
||||
mediaType: innerProps.mediaType,
|
||||
});
|
||||
|
||||
const { mutate, isPending: isPendingMutation } = clientApi.searchEngine.requestMedia.useMutation({
|
||||
onSuccess() {
|
||||
actions.closeModal();
|
||||
showSuccessNotification({
|
||||
message: t("common.notification.create.success"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const isPending = isPendingQuery || isPendingMutation;
|
||||
const t = useI18n();
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<Season>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: t("search.engine.media.request.modal.table.header.season"),
|
||||
},
|
||||
{
|
||||
accessorKey: "episodeCount",
|
||||
header: t("search.engine.media.request.modal.table.header.episodes"),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const table = useTranslatedMantineReactTable({
|
||||
columns,
|
||||
data: data && "seasons" in data ? data.seasons : [],
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enablePagination: false,
|
||||
enableSorting: false,
|
||||
enableSelectAll: true,
|
||||
enableRowSelection: true,
|
||||
mantineTableProps: {
|
||||
highlightOnHover: false,
|
||||
striped: "odd",
|
||||
withColumnBorders: true,
|
||||
withRowBorders: true,
|
||||
withTableBorder: true,
|
||||
},
|
||||
initialState: {
|
||||
density: "xs",
|
||||
},
|
||||
});
|
||||
|
||||
const anySelected = Object.keys(table.getState().rowSelection).length > 0;
|
||||
|
||||
const handleMutate = () => {
|
||||
const selectedSeasons = table.getSelectedRowModel().rows.flatMap((row) => row.original.id);
|
||||
mutate({
|
||||
integrationId: innerProps.integrationId,
|
||||
mediaId: innerProps.mediaId,
|
||||
mediaType: innerProps.mediaType,
|
||||
seasons: selectedSeasons,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
||||
{data && (
|
||||
<Group wrap="nowrap" align="start">
|
||||
<Image
|
||||
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
||||
alt="poster"
|
||||
w={100}
|
||||
radius="md"
|
||||
/>
|
||||
<Text c="dimmed" style={{ flex: "1" }}>
|
||||
{data.overview}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{innerProps.mediaType === "tv" && <MRT_Table table={table} />}
|
||||
<Group justify="end">
|
||||
<Button onClick={actions.closeModal} variant="light">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleMutate} disabled={!anySelected && innerProps.mediaType === "tv"}>
|
||||
{t("search.engine.media.request.modal.button.send")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}).withOptions({
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
interface Season {
|
||||
id: number;
|
||||
name: string;
|
||||
episodeCount: number;
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"react": "^19.0.0",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^3.24.1",
|
||||
"zod-form-data": "^2.0.4"
|
||||
"zod-form-data": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
createItemAndIntegrationChannel,
|
||||
createItemChannel,
|
||||
createIntegrationOptionsChannel,
|
||||
createWidgetOptionsChannel,
|
||||
createChannelWithLatestAndEvents,
|
||||
handshakeAsync,
|
||||
createSubPubChannel,
|
||||
|
||||
@@ -183,6 +183,16 @@ export const createIntegrationOptionsChannel = <TData>(
|
||||
return createChannelWithLatestAndEvents<TData>(channelName);
|
||||
};
|
||||
|
||||
export const createWidgetOptionsChannel = <TData>(
|
||||
widgetKind: WidgetKind,
|
||||
queryKey: string,
|
||||
options: Record<string, unknown>,
|
||||
) => {
|
||||
const optionsKey = hashObjectBase64(options);
|
||||
const channelName = `widget:${widgetKind}:${queryKey}:options:${optionsKey}`;
|
||||
return createChannelWithLatestAndEvents<TData>(channelName);
|
||||
};
|
||||
|
||||
export const createItemChannel = <TData>(itemId: string) => {
|
||||
return createChannelWithLatestAndEvents<TData>(`item:${itemId}`);
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"octokit": "^4.0.2",
|
||||
"octokit": "^4.0.3",
|
||||
"pretty-print-error": "^1.1.2",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Duration } from "dayjs/plugin/duration";
|
||||
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { createWidgetOptionsChannel } from "@homarr/redis";
|
||||
|
||||
import { createCachedRequestHandler } from "./cached-request-handler";
|
||||
|
||||
interface Options<TData, TKind extends WidgetKind, TInput extends Record<string, unknown>> {
|
||||
// Unique key for this request handler
|
||||
queryKey: string;
|
||||
requestAsync: (input: TInput) => Promise<TData>;
|
||||
cacheDuration: Duration;
|
||||
widgetKind: TKind;
|
||||
}
|
||||
|
||||
export const createCachedWidgetRequestHandler = <
|
||||
TData,
|
||||
TKind extends WidgetKind,
|
||||
TInput extends Record<string, unknown>,
|
||||
>(
|
||||
requestHandlerOptions: Options<TData, TKind, TInput>,
|
||||
) => {
|
||||
return {
|
||||
handler: (widgetOptions: TInput) =>
|
||||
createCachedRequestHandler({
|
||||
queryKey: requestHandlerOptions.queryKey,
|
||||
requestAsync: async (input: TInput) => {
|
||||
return await requestHandlerOptions.requestAsync(input);
|
||||
},
|
||||
cacheDuration: requestHandlerOptions.cacheDuration,
|
||||
createRedisChannel(input, options) {
|
||||
return createWidgetOptionsChannel<TData>(requestHandlerOptions.widgetKind, options.queryKey, input);
|
||||
},
|
||||
}).handler(widgetOptions),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "minecraftServerStatusApiResult",
|
||||
widgetKind: "minecraftServerStatus",
|
||||
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
|
||||
const path = `/3/${input.isBedrockServer ? "bedrock/" : ""}${input.domain}`;
|
||||
|
||||
const response = await fetchWithTimeout(`https://api.mcsrvstat.us${path}`);
|
||||
return responseSchema.parse(await response.json());
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
});
|
||||
|
||||
const responseSchema = z
|
||||
.object({
|
||||
online: z.literal(false),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
online: z.literal(true),
|
||||
players: z.object({
|
||||
online: z.number(),
|
||||
max: z.number(),
|
||||
}),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
export type MinecraftServerStatus = z.infer<typeof responseSchema>;
|
||||
@@ -4,14 +4,25 @@ import { ChildrenActionItem } from "./items/children-action-item";
|
||||
interface SpotlightChildrenActionsProps {
|
||||
childrenOptions: inferSearchInteractionOptions<"children">;
|
||||
query: string;
|
||||
setChildrenOptions: (options: inferSearchInteractionOptions<"children">) => void;
|
||||
}
|
||||
|
||||
export const SpotlightChildrenActions = ({ childrenOptions, query }: SpotlightChildrenActionsProps) => {
|
||||
export const SpotlightChildrenActions = ({
|
||||
childrenOptions,
|
||||
query,
|
||||
setChildrenOptions,
|
||||
}: SpotlightChildrenActionsProps) => {
|
||||
const actions = childrenOptions.useActions(childrenOptions.option, query);
|
||||
|
||||
return actions
|
||||
.filter((action) => (typeof action.hide === "function" ? !action.hide(childrenOptions.option) : !action.hide))
|
||||
.map((action) => (
|
||||
<ChildrenActionItem key={action.key} childrenOptions={childrenOptions} query={query} action={action} />
|
||||
<ChildrenActionItem
|
||||
key={action.key}
|
||||
childrenOptions={childrenOptions}
|
||||
query={query}
|
||||
action={action}
|
||||
setChildrenOptions={setChildrenOptions}
|
||||
/>
|
||||
));
|
||||
};
|
||||
@@ -8,9 +8,10 @@ interface ChildrenActionItemProps {
|
||||
childrenOptions: inferSearchInteractionOptions<"children">;
|
||||
query: string;
|
||||
action: ReturnType<inferSearchInteractionOptions<"children">["useActions"]>[number];
|
||||
setChildrenOptions: (options: inferSearchInteractionOptions<"children">) => void;
|
||||
}
|
||||
|
||||
export const ChildrenActionItem = ({ childrenOptions, action, query }: ChildrenActionItemProps) => {
|
||||
export const ChildrenActionItem = ({ childrenOptions, action, query, setChildrenOptions }: ChildrenActionItemProps) => {
|
||||
const interaction = action.useInteraction(childrenOptions.option, query);
|
||||
|
||||
const renderRoot =
|
||||
@@ -20,10 +21,20 @@ export const ChildrenActionItem = ({ childrenOptions, action, query }: ChildrenA
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onClick = interaction.type === "javaScript" ? interaction.onSelect : undefined;
|
||||
const onClick =
|
||||
interaction.type === "javaScript"
|
||||
? interaction.onSelect
|
||||
: interaction.type === "children"
|
||||
? () => setChildrenOptions(interaction)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Spotlight.Action renderRoot={renderRoot} onClick={onClick} className={classes.spotlightAction}>
|
||||
<Spotlight.Action
|
||||
renderRoot={renderRoot}
|
||||
onClick={onClick}
|
||||
closeSpotlightOnTrigger={interaction.type !== "children"}
|
||||
className={classes.spotlightAction}
|
||||
>
|
||||
<action.Component {...childrenOptions.option} />
|
||||
</Spotlight.Action>
|
||||
);
|
||||
|
||||
@@ -140,7 +140,11 @@ const SpotlightWithActiveMode = ({ modeState, activeMode, defaultMode }: Spotlig
|
||||
|
||||
<MantineSpotlight.ActionsList>
|
||||
{childrenOptions ? (
|
||||
<SpotlightChildrenActions childrenOptions={childrenOptions} query={query} />
|
||||
<SpotlightChildrenActions
|
||||
childrenOptions={childrenOptions}
|
||||
query={query}
|
||||
setChildrenOptions={setChildrenOptions}
|
||||
/>
|
||||
) : (
|
||||
<SpotlightActionGroups
|
||||
setMode={(mode) => {
|
||||
|
||||
@@ -10,7 +10,10 @@ export interface CreateChildrenOptionsProps<TParentOptions extends Record<string
|
||||
export interface ChildrenAction<TParentOptions extends Record<string, unknown>> {
|
||||
key: string;
|
||||
Component: (option: TParentOptions) => JSX.Element;
|
||||
useInteraction: (option: TParentOptions, query: string) => inferSearchInteractionDefinition<"link" | "javaScript">;
|
||||
useInteraction: (
|
||||
option: TParentOptions,
|
||||
query: string,
|
||||
) => inferSearchInteractionDefinition<"link" | "javaScript" | "children">;
|
||||
hide?: boolean | ((option: TParentOptions) => boolean);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Group, Image, Kbd, Stack, Text } from "@mantine/core";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconDownload, IconSearch } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { getIntegrationKindsByCategory, getIntegrationName } from "@homarr/definitions";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { RequestMediaModal } from "@homarr/modals-collection";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { createChildrenOptions } from "../../lib/children";
|
||||
@@ -11,6 +15,100 @@ import { interaction } from "../../lib/interaction";
|
||||
|
||||
type SearchEngine = RouterOutputs["searchEngine"]["search"][number];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
type MediaRequestChildrenProps = {
|
||||
result: {
|
||||
id: number;
|
||||
image?: string;
|
||||
name: string;
|
||||
link: string;
|
||||
text?: string;
|
||||
type: "tv" | "movie";
|
||||
inLibrary: boolean;
|
||||
};
|
||||
integration: {
|
||||
kind: IntegrationKind;
|
||||
url: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
const mediaRequestsChildrenOptions = createChildrenOptions<MediaRequestChildrenProps>({
|
||||
useActions() {
|
||||
const { openModal } = useModalAction(RequestMediaModal);
|
||||
return [
|
||||
{
|
||||
key: "request",
|
||||
hide: (option) => option.result.inLibrary,
|
||||
Component(option) {
|
||||
const t = useScopedI18n("search.mode.media");
|
||||
return (
|
||||
<Group mx="md" my="sm" wrap="nowrap">
|
||||
<IconDownload stroke={1.5} />
|
||||
{option.result.type === "tv" && <Text>{t("requestSeries")}</Text>}
|
||||
{option.result.type === "movie" && <Text>{t("requestMovie")}</Text>}
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
useInteraction: interaction.javaScript((option) => ({
|
||||
onSelect() {
|
||||
openModal(
|
||||
{
|
||||
integrationId: option.integration.id,
|
||||
mediaId: option.result.id,
|
||||
mediaType: option.result.type,
|
||||
},
|
||||
{
|
||||
title(t) {
|
||||
return t("search.engine.media.request.modal.title", { name: option.result.name });
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: "open",
|
||||
Component({ integration }) {
|
||||
const tChildren = useScopedI18n("search.mode.media");
|
||||
return (
|
||||
<Group mx="md" my="sm" wrap="nowrap">
|
||||
<IconSearch stroke={1.5} />
|
||||
<Text>{tChildren("openIn", { kind: getIntegrationName(integration.kind) })}</Text>
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
useInteraction({ result }) {
|
||||
return {
|
||||
type: "link",
|
||||
href: result.link,
|
||||
newTab: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
DetailComponent({ options }) {
|
||||
return (
|
||||
<Group mx="md" my="sm" wrap="nowrap">
|
||||
{options.result.image ? (
|
||||
<Image src={options.result.image} w={35} h={50} fit="cover" radius={"md"} />
|
||||
) : (
|
||||
<IconSearch stroke={1.5} size={35} />
|
||||
)}
|
||||
<Stack gap={2}>
|
||||
<Text>{options.result.name}</Text>
|
||||
{options.result.text && (
|
||||
<Text c="dimmed" size="sm" lineClamp={2}>
|
||||
{options.result.text}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>({
|
||||
useActions: (searchEngine, query) => {
|
||||
const { data } = clientApi.integration.searchInIntegration.useQuery(
|
||||
@@ -64,10 +162,48 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
useInteraction: interaction.link(() => ({
|
||||
href: searchResult.link,
|
||||
newTab: true,
|
||||
})),
|
||||
useInteraction(searchEngine) {
|
||||
if (searchEngine.type !== "fromIntegration") {
|
||||
throw new Error("Invalid search engine type");
|
||||
}
|
||||
|
||||
if (!searchEngine.integration) {
|
||||
throw new Error("Invalid search engine integration");
|
||||
}
|
||||
|
||||
if (
|
||||
getIntegrationKindsByCategory("mediaRequest").some(
|
||||
(categoryKind) => categoryKind === searchEngine.integration?.kind,
|
||||
) &&
|
||||
"type" in searchResult
|
||||
) {
|
||||
const type = searchResult.type;
|
||||
if (type === "person") {
|
||||
return {
|
||||
type: "link",
|
||||
href: searchResult.link,
|
||||
newTab: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "children",
|
||||
...mediaRequestsChildrenOptions({
|
||||
result: {
|
||||
...searchResult,
|
||||
type,
|
||||
},
|
||||
integration: searchEngine.integration,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "link",
|
||||
href: searchResult.link,
|
||||
newTab: true,
|
||||
};
|
||||
},
|
||||
}));
|
||||
},
|
||||
DetailComponent({ options }) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1381,7 +1381,7 @@
|
||||
"memory": "crwdns5734:0{memory}crwdne5734:0",
|
||||
"memoryAvailable": "crwdns5736:0{memoryAvailable}crwdnd5736:0{percent}crwdne5736:0",
|
||||
"version": "crwdns5738:0{version}crwdne5738:0",
|
||||
"uptime": "crwdns5740:0{days}crwdnd5740:0{hours}crwdnd5740:0{minutes}crwdne5740:0",
|
||||
"uptime": "crwdns7008:0{months}crwdnd7008:0{days}crwdnd7008:0{hours}crwdnd7008:0{minutes}crwdne7008:0",
|
||||
"loadAverage": "crwdns5742:0crwdne5742:0",
|
||||
"minute": "crwdns5744:0crwdne5744:0",
|
||||
"minutes": "crwdns5746:0{count}crwdne5746:0",
|
||||
@@ -1457,7 +1457,12 @@
|
||||
"mediaServer": {
|
||||
"name": "crwdns5810:0crwdne5810:0",
|
||||
"description": "crwdns5812:0crwdne5812:0",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "crwdns7010:0crwdne7010:0",
|
||||
"name": "crwdns7012:0crwdne7012:0",
|
||||
"id": "crwdns7014:0crwdne7014:0"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "crwdns5814:0crwdne5814:0",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,130 +2,130 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Velkommen til Homarr",
|
||||
"subtitle": "Lad os komme i gang med at konfigurere din Homarr instans.",
|
||||
"description": "For at komme i gang, skal du vælge, hvordan du vil konfigurere din Homarr instans.",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"importOldmarr": ""
|
||||
"scratch": "Start fra bunden",
|
||||
"importOldmarr": "Importer fra Homarr før 1.0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Importer data",
|
||||
"subtitle": "Du kan importere data fra en eksisterende Homarr instans.",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Træk zip-filen her eller klik for at gennemse",
|
||||
"description": "Den uploadede zip vil blive behandlet, og du vil kunne vælge, hvad du vil importere"
|
||||
},
|
||||
"fileInfo": {
|
||||
"action": {
|
||||
"change": ""
|
||||
"change": "Ændre fil"
|
||||
}
|
||||
},
|
||||
"importSettings": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Importer indstillinger",
|
||||
"description": "Konfigurer importadfærden"
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Fundet {count} tavler",
|
||||
"description": "Vælg alle tavler med deres størrelser du vil importere",
|
||||
"action": {
|
||||
"selectAll": "",
|
||||
"unselectAll": ""
|
||||
"selectAll": "Vælg alle",
|
||||
"unselectAll": "Fravælg alle"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Importér sammendrag",
|
||||
"description": "I nedenstående oversigt kan du se hvad der vil blive importeret",
|
||||
"action": {
|
||||
"import": ""
|
||||
"import": "Bekræft import og fortsæt"
|
||||
},
|
||||
"entities": {
|
||||
"apps": "",
|
||||
"boards": "",
|
||||
"integrations": "",
|
||||
"credentialUsers": ""
|
||||
"apps": "Apps",
|
||||
"boards": "Tavler",
|
||||
"integrations": "Integrationer",
|
||||
"credentialUsers": "Brugere, der er legitimerede"
|
||||
}
|
||||
},
|
||||
"tokenModal": {
|
||||
"title": "",
|
||||
"title": "Angiv import token",
|
||||
"field": {
|
||||
"token": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Token",
|
||||
"description": "Indtast den viste import token fra din tidligere homarr instans"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Ugyldig token",
|
||||
"message": "Den indtastede token er ugyldig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Admin bruger",
|
||||
"subtitle": "Angiv legitimationsoplysninger for din administrator bruger.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Bruger oprettet",
|
||||
"message": "Brugeren blev oprettet"
|
||||
},
|
||||
"error": {
|
||||
"title": ""
|
||||
"title": "Brugeroprettelse mislykkedes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Ekstern gruppe",
|
||||
"subtitle": "Angiv den gruppe, der skal bruges til eksterne brugere.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Gruppenavn",
|
||||
"description": "Navn skal matche admin gruppe fra ekstern udbyder"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Indstillinger",
|
||||
"subtitle": ""
|
||||
"subtitle": "Konfigurer serverindstillinger."
|
||||
},
|
||||
"finish": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Afslut opsætningen",
|
||||
"subtitle": "Du er klar!",
|
||||
"description": "Du har fuldført opsætningsprocessen. Du kan nu begynde at bruge Homarr. Vælg din næste handling:",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": ""
|
||||
"goToBoard": "Gå til {name} tavle",
|
||||
"createBoard": "Opret din første tavle",
|
||||
"inviteUser": "Inviter andre brugere",
|
||||
"docs": "Læs dokumentationen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backToStart": ""
|
||||
"backToStart": "Tilbage til start"
|
||||
},
|
||||
"user": {
|
||||
"title": "Brugere",
|
||||
"name": "Bruger",
|
||||
"page": {
|
||||
"login": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Log ind på din konto",
|
||||
"subtitle": "Velkommen tilbage! Indtast venligst dine legitimationsoplysninger"
|
||||
},
|
||||
"invite": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": ""
|
||||
"title": "Tilmeld Homarr",
|
||||
"subtitle": "Velkommen til Homarr! Opret venligst din konto",
|
||||
"description": "Du blev inviteret af {username}"
|
||||
},
|
||||
"init": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Ny Homarr installation",
|
||||
"subtitle": "Opret venligst den første administrator bruger"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "E-mail",
|
||||
"verified": ""
|
||||
"verified": "Verificeret"
|
||||
},
|
||||
"username": {
|
||||
"label": "Brugernavn"
|
||||
@@ -133,46 +133,46 @@
|
||||
"password": {
|
||||
"label": "Adgangskode",
|
||||
"requirement": {
|
||||
"length": "",
|
||||
"length": "Inkluderer mindst 8 tegn",
|
||||
"lowercase": "Inkluderer små bogstaver",
|
||||
"uppercase": "Inkluderer store bogstaver",
|
||||
"number": "Inkluderer nummer",
|
||||
"special": ""
|
||||
"special": "Inkluderer specielt symbol"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Bekræft kodeord"
|
||||
},
|
||||
"previousPassword": {
|
||||
"label": ""
|
||||
"label": "Tidligere adgangskode"
|
||||
},
|
||||
"homeBoard": {
|
||||
"label": ""
|
||||
"label": "Hjemme tavle"
|
||||
},
|
||||
"pingIconsEnabled": {
|
||||
"label": ""
|
||||
"label": "Brug ikoner til pings"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"usernameTaken": ""
|
||||
"usernameTaken": "Brugernavn allerede taget"
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Log ind",
|
||||
"labelWith": "",
|
||||
"labelWith": "Log ind med {provider}",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Login lykkedes",
|
||||
"message": "Du er nu logget ind"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Login mislykkedes",
|
||||
"message": "Dit login mislykkedes"
|
||||
}
|
||||
},
|
||||
"forgotPassword": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Glemt din adgangskode?",
|
||||
"description": "En administrator kan bruge følgende kommando til at nulstille din adgangskode:"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
@@ -180,81 +180,81 @@
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Konto oprettet",
|
||||
"message": ""
|
||||
"message": "Log venligst på for at fortsætte"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Oprettelse af konto mislykkedes",
|
||||
"message": "Din konto kunne ikke oprettes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Opret bruger",
|
||||
"changePassword": {
|
||||
"label": "",
|
||||
"label": "Skift adgangskode",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Adgangskoden er blevet ændret"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Adgangskoden kunne ikke ændres"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changeHomeBoard": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Hjemmetavle blev ændret"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Kan ikke ændre hjemmetavle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changeFirstDayOfWeek": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Første ugedag ændret med succes"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Kan ikke ændre den første ugedag"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changePingIconsEnabled": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Ping ikoner skiftet"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Kan ikke skifte ping-ikoner"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manageAvatar": {
|
||||
"changeImage": {
|
||||
"label": "",
|
||||
"label": "Skift billede",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Billedet blev ændret"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Kan ikke ændre billede"
|
||||
},
|
||||
"toLarge": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Billedet er for stort",
|
||||
"message": "Maks billedstørrelse er {size}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "Fjern billede",
|
||||
"confirm": "Er du sikker på du vil fjerne billedet?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Billedet blev fjernet"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Kan ikke fjerne billede"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,15 +262,15 @@
|
||||
"editProfile": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Profilen blev opdateret"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Ikke i stand til at opdatere profil"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "",
|
||||
"label": "Slet brugeren permanent",
|
||||
"description": "",
|
||||
"confirm": ""
|
||||
},
|
||||
@@ -293,23 +293,23 @@
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "",
|
||||
"title": "Admin",
|
||||
"item": {
|
||||
"admin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Administrator",
|
||||
"description": "Medlemmer med denne tilladelse har fuld adgang til alle funktioner og indstillinger"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"title": "",
|
||||
"title": "Apps",
|
||||
"item": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Opret apps",
|
||||
"description": "Tillad medlemmer at oprette apps"
|
||||
},
|
||||
"use-all": {
|
||||
"label": "",
|
||||
"label": "Brug alle apps",
|
||||
"description": ""
|
||||
},
|
||||
"modify-all": {
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notesbog",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Fejl"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "URL"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notizbuch",
|
||||
"description": "Ein einfaches Notizbuch Widget, das Markdown unterstützt",
|
||||
@@ -1381,7 +1404,7 @@
|
||||
"memory": "Speicher: {memory}GiB",
|
||||
"memoryAvailable": "Verfügbar: {memoryAvailable} GiB ({percent}%)",
|
||||
"version": "",
|
||||
"uptime": "Betriebszeit: {days} Tage, {hours} Stunden, {minutes} Minuten",
|
||||
"uptime": "Laufzeit: {months} Monate, {days} Tage, {hours} Stunden, {minutes} Minuten",
|
||||
"loadAverage": "Durchschnittliche Last:",
|
||||
"minute": "1 Minute",
|
||||
"minutes": "{count} Minuten",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Fehler"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Σημειωματάριο",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Σφάλμα"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "Create Search Engine",
|
||||
"description": "Integration \"{kind}\" can be used with the search engines. Check this to automatically configure the search engine."
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "Minecraft Server Status",
|
||||
"description": "Displays the status of a Minecraft server",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": "Title"
|
||||
},
|
||||
"domain": {
|
||||
"label": "Server address"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": "Bedrock server"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "Online",
|
||||
"offline": "Offline"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notebook",
|
||||
"description": "A simple notebook widget that supports markdown",
|
||||
@@ -1381,7 +1404,7 @@
|
||||
"memory": "Memory: {memory}GiB",
|
||||
"memoryAvailable": "Available: {memoryAvailable}GiB ({percent}%)",
|
||||
"version": "Version: {version}",
|
||||
"uptime": "Uptime: {days} Days, {hours} Hours, {minutes} Minutes",
|
||||
"uptime": "Uptime: {months} Months, {days} Days, {hours} Hours, {minutes} Minutes",
|
||||
"loadAverage": "Load average:",
|
||||
"minute": "1 minute",
|
||||
"minutes": "{count} minutes",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "Current media server streams",
|
||||
"description": "Show the current streams on your media servers",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "User",
|
||||
"name": "Name",
|
||||
"id": "Id"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "Download Client",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Error"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": "Minecraft server status"
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Icons Updater"
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "Request movie",
|
||||
"requestSeries": "Request series",
|
||||
"openIn": "Open in {kind}"
|
||||
},
|
||||
"external": {
|
||||
"help": "Use an external search engine",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "Request \"{name}\"",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "Season",
|
||||
"episodes": "Episodes"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": "Send request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"title": "Bienvenido a Homarr",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"importOldmarr": ""
|
||||
"scratch": "Empezar de cero",
|
||||
"importOldmarr": "Importar de Homarr antes de 1.0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"title": "Importar datos",
|
||||
"subtitle": "",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Arrastre el archivo zip aquí o haga clic para navegar",
|
||||
"description": "El zip subido será procesado y podrás seleccionar lo que quieres importar"
|
||||
},
|
||||
"fileInfo": {
|
||||
"action": {
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"importSettings": {
|
||||
"title": "",
|
||||
"title": "Importar ajustes",
|
||||
"description": ""
|
||||
},
|
||||
"boardSelection": {
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Bloc de notas",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": ""
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": ""
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Bloc-notes",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Erreur"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Bilježnica",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Pogreška"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Jegyzettömb",
|
||||
"description": "Egy egyszerű notebook widget, amely támogatja a leértékelést",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Hiba"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Blocco note",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Errore"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"title": "Homarrへようこそ",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"scratch": "ゼロからスタート",
|
||||
"importOldmarr": ""
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"title": "データのインポート",
|
||||
"subtitle": "",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "メモ帳",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "エラー"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "노트북",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "오류"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Užrašai",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": ""
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"title": "Importēt datus",
|
||||
"subtitle": "",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Piezīmes",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Kļūda"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notisbok",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Feil"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Notatnik",
|
||||
"description": "",
|
||||
@@ -1254,7 +1277,7 @@
|
||||
"description": "",
|
||||
"option": {
|
||||
"entityId": {
|
||||
"label": "ID encji"
|
||||
"label": "ID obiektu"
|
||||
},
|
||||
"displayName": {
|
||||
"label": ""
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Błąd"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Caderno de anotações",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Erro"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Agendă",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Eroare"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "URL-адрес"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Заметки",
|
||||
"description": "Простой виджет заметок с поддержкой markdown",
|
||||
@@ -1381,7 +1404,7 @@
|
||||
"memory": "Память: {memory}GiB",
|
||||
"memoryAvailable": "Доступно: {memoryAvailable}GiB ({percent}%)",
|
||||
"version": "Версия: {version}",
|
||||
"uptime": "Время работы: {days} дн., {hours} ч., {minutes} мин.",
|
||||
"uptime": "Время работы: {months} мес., {days} дн., {hours} ч., {minutes} мин.",
|
||||
"loadAverage": "Средняя нагрузка:",
|
||||
"minute": "1 минута",
|
||||
"minutes": "{count} минут",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "Активные просмотры",
|
||||
"description": "Отображает текущие сеансы воспроизведения на медиасерверах",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "Загрузки",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Ошибка"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Обновление иконок"
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "Использовать внешнюю поисковую систему",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "Vitajte na stránke Homarr",
|
||||
"subtitle": "Začnime s nastavením inštancie Homarr.",
|
||||
"description": "Ak chcete začať, vyberte, ako chcete nastaviť inštanciu Homarr.",
|
||||
"title": "Vitajte na Homár",
|
||||
"subtitle": "Začnime s nastavením inštancie Homár.",
|
||||
"description": "Ak chcete začať, vyberte, ako chcete nastaviť inštanciu Homár.",
|
||||
"action": {
|
||||
"scratch": "Začať odznova",
|
||||
"importOldmarr": "Import z Homarru pred 1.0"
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "Vytvorte vyhľadávače",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "Minecraft Server Stav",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": "Názov"
|
||||
},
|
||||
"domain": {
|
||||
"label": "Adresa servera"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": "Bedrock server"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "Online",
|
||||
"offline": "Odpojené"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Poznámkový blok",
|
||||
"description": "Jednoduchý widget poznámkového bloku, ktorý podporuje markdown",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "Aktuálne streamy mediálneho servera",
|
||||
"description": "Zobrazte aktuálne streamy na vašich mediálnych serveroch",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "Používateľ",
|
||||
"name": "Názov",
|
||||
"id": "Id"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "Download klient",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Chyba"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": "Minecraft Server Stav"
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Aktualizátor ikon"
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "Používanie externého vyhľadávača",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Beležnica",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Napaka"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Anteckningsbok",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Fel"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
"label": "Eski Şifre"
|
||||
},
|
||||
"homeBoard": {
|
||||
"label": "Varsayılan Panel"
|
||||
"label": "Ana panel"
|
||||
},
|
||||
"pingIconsEnabled": {
|
||||
"label": "Pingler İçin Simgeler Kullanın"
|
||||
@@ -203,10 +203,10 @@
|
||||
"changeHomeBoard": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": "Varsayılan panel başarıyla değiştirildi"
|
||||
"message": "Öntanımlı panel başarıyla değiştirildi"
|
||||
},
|
||||
"error": {
|
||||
"message": "Varsayılan panel değiştirilemiyor"
|
||||
"message": "Öntanımlı panel değiştirilemiyor"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -498,7 +498,7 @@
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"title": "Yeni uygulama",
|
||||
"title": "Yeni Uygulama",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Oluşturma başarılı",
|
||||
@@ -566,7 +566,7 @@
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"title": "Yeni {name} entegrasyonu",
|
||||
"title": "Yeni {name} Entegrasyonu",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Oluşturma başarılı",
|
||||
@@ -612,10 +612,14 @@
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "Arama Motoru Oluştur",
|
||||
"description": "\"{kind}\" entegrasyonu arama motorlarıyla kullanılabilir. Arama motorunu otomatik olarak yapılandırmak için bunu işaretleyin."
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": "Yeni entegrasyon"
|
||||
"create": "Yeni Entegrasyon"
|
||||
},
|
||||
"testConnection": {
|
||||
"action": {
|
||||
@@ -703,13 +707,13 @@
|
||||
"message": "Gizli anahtarı sıfırlamak istediğinizden emin misiniz?"
|
||||
},
|
||||
"noSecretsRequired": {
|
||||
"segmentTitle": "Hiçbir Gizli Anahtar Bulunamadı",
|
||||
"segmentTitle": "Gizli Anahtar Yok",
|
||||
"text": "Bu entegrasyon için gizli anahtar gerekmiyor"
|
||||
},
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Kullanıcı adı",
|
||||
"newLabel": "Yeni kullanıcı adı"
|
||||
"newLabel": "Yeni Kullanıcı Adı"
|
||||
},
|
||||
"apiKey": {
|
||||
"label": "API Anahtarı",
|
||||
@@ -717,7 +721,7 @@
|
||||
},
|
||||
"password": {
|
||||
"label": "Şifre",
|
||||
"newLabel": "Yeni parola"
|
||||
"newLabel": "Yeni Şifre"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -842,18 +846,18 @@
|
||||
},
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"switchToDarkMode": "Koyu moda geç",
|
||||
"switchToLightMode": "Aydınlık moda geç",
|
||||
"switchToDarkMode": "Koyu Temaya Geç",
|
||||
"switchToLightMode": "Aydınlık Temaya Geç",
|
||||
"management": "Yönetim",
|
||||
"preferences": "Tercihleriniz",
|
||||
"logout": "Oturumu kapat",
|
||||
"login": "Giriş",
|
||||
"homeBoard": "Varsayılan Panel",
|
||||
"homeBoard": "Öntanımlı Panel",
|
||||
"loggedOut": "Oturum kapatıldı",
|
||||
"updateAvailable": "{countUpdates} güncelleme mevcut: {tag}"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Tehlikeli bölge",
|
||||
"dangerZone": "Tehlikeli Bölge",
|
||||
"noResults": "Sonuç bulunamadı",
|
||||
"preview": {
|
||||
"show": "Ara Renkleri Göster",
|
||||
@@ -892,7 +896,7 @@
|
||||
"section": {
|
||||
"dynamic": {
|
||||
"action": {
|
||||
"create": "Yeni dinamik bölüm",
|
||||
"create": "Yeni Dinamik Bölüm",
|
||||
"remove": "Dinamik bölümü kaldır"
|
||||
},
|
||||
"remove": {
|
||||
@@ -907,16 +911,16 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": "Yeni kategori",
|
||||
"edit": "Kategoriyi yeniden adlandır",
|
||||
"create": "Yeni Kategori",
|
||||
"edit": "Kategoriyi Yeniden Adlandır",
|
||||
"remove": "Kategoriyi kaldır",
|
||||
"moveUp": "Yukarı taşı",
|
||||
"moveDown": "Aşağı taşı",
|
||||
"createAbove": "Yukarı yeni kategori",
|
||||
"createBelow": "Aşağı yeni kategori"
|
||||
"createAbove": "Yukarı Yeni Kategori",
|
||||
"createBelow": "Aşağı Yeni Kategori"
|
||||
},
|
||||
"create": {
|
||||
"title": "Yeni kategori",
|
||||
"title": "Yeni Kategori",
|
||||
"submit": "Kategori ekle"
|
||||
},
|
||||
"remove": {
|
||||
@@ -924,12 +928,12 @@
|
||||
"message": "{name} kategorisini kaldırmak istediğinizden emin misiniz?"
|
||||
},
|
||||
"edit": {
|
||||
"title": "Kategoriyi yeniden adlandır",
|
||||
"submit": "Kategoriyi yeniden adlandır"
|
||||
"title": "Kategoriyi Yeniden Adlandır",
|
||||
"submit": "Kategoriyi Yeniden Adlandır"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"create": "Yeni kategori",
|
||||
"create": "Yeni Kategori",
|
||||
"changePosition": "Pozisyonu değiştir"
|
||||
}
|
||||
}
|
||||
@@ -937,10 +941,10 @@
|
||||
},
|
||||
"item": {
|
||||
"action": {
|
||||
"create": "Yeni öğe",
|
||||
"create": "Yeni Öğe",
|
||||
"import": "Öğeleri İçe Aktar",
|
||||
"edit": "Öğeyi Düzenle",
|
||||
"moveResize": "Öğeyi taşı / yeniden boyutlandır",
|
||||
"moveResize": "Öğeyi Taşı / Yeniden Boyutlandır",
|
||||
"duplicate": "Öğeyi Çoğalt",
|
||||
"remove": "Öğeyi kaldır"
|
||||
},
|
||||
@@ -954,7 +958,7 @@
|
||||
"addToBoard": "Panele Ekle"
|
||||
},
|
||||
"moveResize": {
|
||||
"title": "Öğeyi taşı / yeniden boyutlandır",
|
||||
"title": "Öğeyi Taşı / Yeniden Boyutlandır",
|
||||
"field": {
|
||||
"width": {
|
||||
"label": "Genişlik"
|
||||
@@ -981,7 +985,7 @@
|
||||
"label": "Entegrasyonlar"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": "Özel css sınıfları"
|
||||
"label": "Özel CSS Alanı"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -999,7 +1003,7 @@
|
||||
"label": "Uygulama seç"
|
||||
},
|
||||
"openInNewTab": {
|
||||
"label": "Yeni sekmede aç"
|
||||
"label": "Yeni Sekmede Aç"
|
||||
},
|
||||
"showTitle": {
|
||||
"label": "Uygulama adını göster"
|
||||
@@ -1019,7 +1023,7 @@
|
||||
}
|
||||
},
|
||||
"bookmarks": {
|
||||
"name": "Yer imleri",
|
||||
"name": "Yer İmleri",
|
||||
"description": "Birden fazla uygulama bağlantısını görüntüler",
|
||||
"option": {
|
||||
"title": {
|
||||
@@ -1040,7 +1044,7 @@
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
"label": "Yer imleri",
|
||||
"label": "Yer İmleri",
|
||||
"add": "Yer imi ekle"
|
||||
}
|
||||
}
|
||||
@@ -1079,7 +1083,7 @@
|
||||
}
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"name": "DNS çözümleyici kontrolleri",
|
||||
"name": "DNS Çözümleyici Kontrolleri",
|
||||
"description": "Kontrol panelinizden PiHole veya AdGuard'ı kontrol edin",
|
||||
"option": {
|
||||
"layout": {
|
||||
@@ -1118,7 +1122,7 @@
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"name": "Tarih ve saat",
|
||||
"name": "Tarih ve Saat",
|
||||
"description": "Geçerli tarih ve saati görüntüler.",
|
||||
"option": {
|
||||
"customTitleToggle": {
|
||||
@@ -1151,8 +1155,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "Minecraft Sunucu Durumu",
|
||||
"description": "Minecraft sunucusunuzun durumunu görüntüler",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": "Başlık"
|
||||
},
|
||||
"domain": {
|
||||
"label": "Sunucu adresi"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": "Bedrock sunucusu"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "Çevrimiçi",
|
||||
"offline": "Çevrimdışı"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Not defteri",
|
||||
"name": "Not Defteri",
|
||||
"description": "Markdown'ı destekleyen basit bir not defteri bileşeni",
|
||||
"option": {
|
||||
"showToolbar": {
|
||||
@@ -1185,7 +1208,7 @@
|
||||
"decreaseIndent": "Girintiyi Azalt",
|
||||
"link": "Bağlantı",
|
||||
"unlink": "Bağlantıyı kaldır",
|
||||
"image": "Görsel Göm",
|
||||
"image": "Görsel Yerleştir",
|
||||
"addTable": "Tablo ekle",
|
||||
"deleteTable": "Tablo Sil",
|
||||
"colorCell": "Renk Hücresi",
|
||||
@@ -1345,17 +1368,17 @@
|
||||
}
|
||||
},
|
||||
"indexerManager": {
|
||||
"name": "Dizin oluşturucu yöneticisi statüsü",
|
||||
"description": "Dizinleyicilerinizin durumu",
|
||||
"name": "İndeksleyici Yönetim Durumu",
|
||||
"description": "İndeksleyicilerinizin durumu",
|
||||
"option": {
|
||||
"openIndexerSiteInNewTab": {
|
||||
"label": "Dizinleyici sitesini yeni sekmede aç"
|
||||
"label": "İndeksleyici Sitesini Yeni Sekmede Aç"
|
||||
}
|
||||
},
|
||||
"title": "Dizin oluşturucu yöneticisi",
|
||||
"title": "İndeksleyici Yöneticisi",
|
||||
"testAll": "Tümünü test et",
|
||||
"error": {
|
||||
"internalServerError": "Dizinleyicilerin durumu alınamadı"
|
||||
"internalServerError": "İndeksleyicilerin durumu alınamadı"
|
||||
}
|
||||
},
|
||||
"healthMonitoring": {
|
||||
@@ -1455,9 +1478,14 @@
|
||||
}
|
||||
},
|
||||
"mediaServer": {
|
||||
"name": "Güncel medya sunucusu akışları",
|
||||
"name": "Güncel Medya Sunucusu Akışları",
|
||||
"description": "Medya sunucularınızdaki mevcut akışları gösterin",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "Kullanıcı",
|
||||
"name": "İsim",
|
||||
"id": "Kimlik"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "İndirme İstemcisi",
|
||||
@@ -1470,7 +1498,7 @@
|
||||
"label": "Öğelerin sıralanmasını etkinleştir"
|
||||
},
|
||||
"defaultSort": {
|
||||
"label": "Varsayılan olarak sıralama için kullanılan sütun"
|
||||
"label": "Öntanımlı olarak sıralama için kullanılan sütun"
|
||||
},
|
||||
"descendingDefaultSort": {
|
||||
"label": "Ters sıralama"
|
||||
@@ -1644,11 +1672,11 @@
|
||||
}
|
||||
},
|
||||
"mediaTranscoding": {
|
||||
"name": "Medya kod dönüştürme",
|
||||
"name": "Medya Kod Dönüştürme",
|
||||
"description": "Medya kod dönüştürmenizin istatistikleri, geçerli kuyruğu ve çalışan durumu",
|
||||
"option": {
|
||||
"defaultView": {
|
||||
"label": "Varsayılan görünüm"
|
||||
"label": "Varsayılan Görünüm"
|
||||
},
|
||||
"queuePageSize": {
|
||||
"label": "Kuyruk Sayfa Boyutu"
|
||||
@@ -1703,8 +1731,8 @@
|
||||
}
|
||||
},
|
||||
"rssFeed": {
|
||||
"name": "RSS beslemeleri",
|
||||
"description": "Bir veya daha fazla genel RSS, ATOM veya JSON beslemesini izleyin ve görüntüleyin",
|
||||
"name": "RSS Beslemeleri",
|
||||
"description": "Bir veya daha fazla RSS, ATOM veya JSON beslemesini takip edin ve görüntüleyin",
|
||||
"option": {
|
||||
"feedUrls": {
|
||||
"label": "Besleme URL'leri"
|
||||
@@ -1807,22 +1835,22 @@
|
||||
},
|
||||
"field": {
|
||||
"pageTitle": {
|
||||
"label": "Sayfa başlığı"
|
||||
"label": "Sayfa Başlığı"
|
||||
},
|
||||
"metaTitle": {
|
||||
"label": "Meta başlığı"
|
||||
"label": "Meta Başlığı"
|
||||
},
|
||||
"logoImageUrl": {
|
||||
"label": "Logo görsel URL'si"
|
||||
"label": "Logo Görseli URL'si"
|
||||
},
|
||||
"faviconImageUrl": {
|
||||
"label": "Favicon görsel URL'si"
|
||||
"label": "Favicon Görseli URL'si"
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "Arkaplan görsel URL'si"
|
||||
"label": "Arkaplan Resmi URL'si"
|
||||
},
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Arkaplan görsel yerleştirme",
|
||||
"label": "Arka Plan Yerleşimi",
|
||||
"option": {
|
||||
"fixed": {
|
||||
"label": "Sabit",
|
||||
@@ -1830,12 +1858,12 @@
|
||||
},
|
||||
"scroll": {
|
||||
"label": "Kaydır",
|
||||
"description": "Arkaplan farenizle birlikte kayar."
|
||||
"description": "Arkaplan Farenizle Birlikte Kayar."
|
||||
}
|
||||
}
|
||||
},
|
||||
"backgroundImageRepeat": {
|
||||
"label": "Arkaplan görüntüsünü Tekrarla",
|
||||
"label": "Arkaplan Resmini Tekrarla",
|
||||
"option": {
|
||||
"repeat": {
|
||||
"label": "Tekrarla",
|
||||
@@ -1856,7 +1884,7 @@
|
||||
}
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Arkaplan görsel boyutu",
|
||||
"label": "Arkaplan Resim Boyutu",
|
||||
"option": {
|
||||
"cover": {
|
||||
"label": "Kapak",
|
||||
@@ -1879,14 +1907,14 @@
|
||||
},
|
||||
"customCss": {
|
||||
"label": "Bu panel için özel css",
|
||||
"description": "Kontrol panelinizi yalnızca deneyimli kullanıcılar için önerilen CSS kullanarak özelleştirin",
|
||||
"description": "CSS kullanarak panelizi özelleştirin, sadece tecrübeli kullanıcılar için tavsiye edilir",
|
||||
"customClassesAlert": {
|
||||
"title": "Özel sınıflar",
|
||||
"description": "Her öğenin gelişmiş seçeneklerinde panel örneklerinize özel sınıflar ekleyebilir ve bunları yukarıdaki özel CSS'de kullanabilirsiniz."
|
||||
"description": "Düzenlenen her aracın gelişmiş seçenekler sekmesinde bulunan alanı kullanarak panel öğelerinizi CSS ile özelleştirebilir ve ya yukarıdaki özel CSS alanını kullanabilirsiniz."
|
||||
}
|
||||
},
|
||||
"columnCount": {
|
||||
"label": "Sütun sayısı"
|
||||
"label": "Sütun Sayısı"
|
||||
},
|
||||
"name": {
|
||||
"label": "İsim"
|
||||
@@ -1919,7 +1947,7 @@
|
||||
"title": "Özel Css"
|
||||
},
|
||||
"access": {
|
||||
"title": "Erişim kontrolü",
|
||||
"title": "Erişim Kontrolü",
|
||||
"permission": {
|
||||
"item": {
|
||||
"view": {
|
||||
@@ -1935,14 +1963,14 @@
|
||||
}
|
||||
},
|
||||
"dangerZone": {
|
||||
"title": "Tehlikeli bölge",
|
||||
"title": "Tehlikeli Bölge",
|
||||
"action": {
|
||||
"rename": {
|
||||
"label": "Panelin adını değiştir",
|
||||
"label": "Panelin ismini değiştir",
|
||||
"description": "İsmin değiştirilmesi bu panele olan tüm bağlantıların kopmasına neden olacaktır.",
|
||||
"button": "İsmi değiştir",
|
||||
"modal": {
|
||||
"title": "Panelin adını değiştir"
|
||||
"title": "Panelin ismini değiştir"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
@@ -1993,7 +2021,7 @@
|
||||
"notice": "Bağlantıyı kontrol edin veya erişilebilir olması gerektiğini düşünüyorsanız bir yöneticiyle iletişime geçin"
|
||||
},
|
||||
"homeBoard": {
|
||||
"title": "Varsayılan Panel Yapılandırılmadı",
|
||||
"title": "Öntanımlı Panel Yapılandırılmadı",
|
||||
"admin": {
|
||||
"description": "Sunucu için henüz bir ana sayfa paneli belirlemediniz.",
|
||||
"link": "Sunucu geneli için ana panel yapılandırın",
|
||||
@@ -2001,13 +2029,13 @@
|
||||
},
|
||||
"user": {
|
||||
"description": "Henüz bir ev paneli belirlemediniz.",
|
||||
"link": "Varsayılan Paneli Yapılandır",
|
||||
"notice": "Bu Sayfanın Kaybolmasını Sağlamak İçin Tercihlerden Varsayılan Panel Atayın"
|
||||
"link": "Öntanımlı paneli yapılandır",
|
||||
"notice": "Bu sayfanın kaybolmasını sağlamak için tercihlerden öntanımlı panel seçin"
|
||||
},
|
||||
"anonymous": {
|
||||
"description": "Sunucu Yöneticiniz Henüz Bir Varsayılan Panel Atamadı.",
|
||||
"description": "Sunucu yöneticiniz henüz bir öntanımlı panel belirlemedi.",
|
||||
"link": "Herkese açık panelleri görüntüle",
|
||||
"notice": "Bu Sayfanın Kaybolmasını Sağlamak İçin Sunucu Yöneticisinden Varsayılan Panel Atamasını isteyin"
|
||||
"notice": "Bu sayfanın kaybolmasını sağlamak için sunucu yöneticisinden öntanımlı panel ayarlamasını isteyin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2082,7 +2110,7 @@
|
||||
"title": "Panelleriniz",
|
||||
"action": {
|
||||
"new": {
|
||||
"label": "Yeni panel"
|
||||
"label": "Yeni Panel"
|
||||
},
|
||||
"open": {
|
||||
"label": "Panel'i aç"
|
||||
@@ -2091,10 +2119,10 @@
|
||||
"label": "Ayarlar"
|
||||
},
|
||||
"setHomeBoard": {
|
||||
"label": "Varsayılan Panel Olarak Ata",
|
||||
"label": "Öntanımlı panel olarak ayarlayın",
|
||||
"badge": {
|
||||
"label": "Ana Sayfa",
|
||||
"tooltip": "Bu panel sizin varsayılan paneliniz olarak görüntülenecektir"
|
||||
"tooltip": "Bu panel sizin öntanımlı paneliniz olarak görüntülenecektir"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -2130,7 +2158,7 @@
|
||||
"title": "Genel",
|
||||
"item": {
|
||||
"language": "Dil & Bölge",
|
||||
"board": "Varsayılan Panel",
|
||||
"board": "Öntanımlı panel",
|
||||
"firstDayOfWeek": "Haftanın ilk günü",
|
||||
"accessibility": "Erişilebilirlik"
|
||||
}
|
||||
@@ -2151,7 +2179,7 @@
|
||||
},
|
||||
"create": {
|
||||
"metaTitle": "Kullanıcı ekle",
|
||||
"title": "Yeni kullanıcı oluştur",
|
||||
"title": "Yeni Kullanıcı Oluştur",
|
||||
"step": {
|
||||
"personalInformation": {
|
||||
"label": "Kişisel bilgiler"
|
||||
@@ -2183,7 +2211,7 @@
|
||||
"title": "Kullanıcı Davetlerini Yönet",
|
||||
"action": {
|
||||
"new": {
|
||||
"title": "Yeni davet",
|
||||
"title": "Yeni Davet",
|
||||
"description": "Süre sona erdikten sonra davet artık geçerli olmayacak ve daveti alan kişi bir hesap oluşturamayacaktır."
|
||||
},
|
||||
"copy": {
|
||||
@@ -2254,11 +2282,11 @@
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget verileri",
|
||||
"text": "Widget (ve sayıları) yapılandırma verilerini gönder. URL'leri, adları veya başka herhangi bir veriyi içermez."
|
||||
"text": "Araç (ve sayılarını) yapılandırma verilerini gönder. URL'leri, alan adlarını veya başka herhangi bir veriyi içermez."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Entegrasyon verileri",
|
||||
"text": "Entegrasyon (ve sayıları) yapılandırma verilerini gönder. URL'leri, adları veya başka herhangi bir veriyi içermez."
|
||||
"text": "Entegrasyon (ve sayıları) yapılandırma verilerini gönder. URL'leri, alan adlarını veya başka herhangi bir veriyi içermez."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Kullanıcı verileri",
|
||||
@@ -2281,21 +2309,21 @@
|
||||
"text": "Kullanıcının site üzerinde okumak isteyeceği dilin olmaması durumunda Google, arama sonuçlarında bir çeviri bağlantısı gösterecektir"
|
||||
},
|
||||
"noSiteLinksSearchBox": {
|
||||
"title": "Site arama alanını gösterme",
|
||||
"title": "Site Arama Alanını Gösterme",
|
||||
"text": "Google, aranan bağlantıların yanı sıra diğer doğrudan bağlantılardan oluşan bir arama kutusu oluşturur. Bu seçenek etkinleştirildiğinde Google'dan bu alanı devre dışı bırakması istenecektir."
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"title": "Paneller",
|
||||
"homeBoard": {
|
||||
"label": "Genel Varsayılan Panel",
|
||||
"description": "Seçim için yalnızca herkese açık paneller kullanılabilir"
|
||||
"label": "Genel Ana Sayfa Paneli",
|
||||
"description": "Seçim yalnızca herkese açık paneller için kullanılabilir"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Görünüş",
|
||||
"defaultColorScheme": {
|
||||
"label": "Varsayılan renk şeması",
|
||||
"label": "Öntanımlı Renk Düzeni",
|
||||
"options": {
|
||||
"light": "Aydınlık",
|
||||
"dark": "Koyu"
|
||||
@@ -2305,7 +2333,7 @@
|
||||
"culture": {
|
||||
"title": "Kültür",
|
||||
"defaultLocale": {
|
||||
"label": "Varsayılan dil"
|
||||
"label": "Öntanımlı Dil"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2319,8 +2347,11 @@
|
||||
"error": "Hata"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": "Minecraft sunucu durumu"
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Simge Güncelleyici"
|
||||
"label": "Simge Güncelleme"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "Analiz"
|
||||
@@ -2347,10 +2378,10 @@
|
||||
"label": "Medya Talep Listesi"
|
||||
},
|
||||
"rssFeeds": {
|
||||
"label": "RSS beslemeleri"
|
||||
"label": "RSS Beslemeleri"
|
||||
},
|
||||
"indexerManager": {
|
||||
"label": "Dizinleyici Yöneticisi"
|
||||
"label": "İndeksleyici Yöneticisi"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"label": "Sağlık İzleme"
|
||||
@@ -2365,7 +2396,7 @@
|
||||
"label": "Güncelleme denetleyicisi"
|
||||
},
|
||||
"mediaTranscoding": {
|
||||
"label": "Medya kod dönüştürme"
|
||||
"label": "Medya Kod Dönüştürme"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2400,7 +2431,7 @@
|
||||
},
|
||||
"about": {
|
||||
"version": "Sürüm {version}",
|
||||
"text": "Homarr, gönüllüler tarafından sürdürülen topluluk odaklı bir açık kaynak projesidir. Bu kişiler sayesinde Homarr, 2021'den beri büyüyen bir projedir. Ekibimiz, boş zamanlarında Homarr üzerinde birçok farklı ülkeden tamamen uzaktan, hiçbir ücret almadan çalışmaktadır.",
|
||||
"text": "Homarr, gönüllüler tarafından sürdürülen topluluk odaklı bir açık kaynak projesidir. Bu kişiler sayesinde Homarr, 2021'den bugüne büyüyerek ilerleyen bir projedir. Ekibimiz, boş zamanlarında Homarr üzerinde birçok farklı ülkeden tamamen uzaktan, hiçbir ücret almadan çalışmaktadır.",
|
||||
"accordion": {
|
||||
"contributors": {
|
||||
"title": "Katkıda Bulunanlar",
|
||||
@@ -2408,7 +2439,7 @@
|
||||
},
|
||||
"translators": {
|
||||
"title": "Çevirmenler",
|
||||
"subtitle": "{count} dilde birçok kişi çevirilere katkıda bulunuyor"
|
||||
"subtitle": "Birçok kişi Homarr'ın {count} dile çevrilmesine katkı sağlıyor"
|
||||
},
|
||||
"libraries": {
|
||||
"title": "Kütüphaneler",
|
||||
@@ -2419,10 +2450,10 @@
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"title": "Konteyner",
|
||||
"title": "Konteynerler",
|
||||
"table": {
|
||||
"updated": "Güncellendi {when}",
|
||||
"search": "{count} konteyner ara",
|
||||
"updated": "{when} Güncellendi",
|
||||
"search": "{count} konteyner içinde ara",
|
||||
"selected": "{selectCount} / {totalCount} konteyner seçildi"
|
||||
},
|
||||
"field": {
|
||||
@@ -2435,8 +2466,8 @@
|
||||
"created": "Oluşturuldu",
|
||||
"running": "Çalışıyor",
|
||||
"paused": "Duraklatıldı",
|
||||
"restarting": "Yeniden başlatılıyor",
|
||||
"exited": "Çıkıldı",
|
||||
"restarting": "Yeniden Başlatılıyor",
|
||||
"exited": "Durduruldu",
|
||||
"removing": "Kaldırılıyor",
|
||||
"dead": "Ölü"
|
||||
}
|
||||
@@ -2564,7 +2595,7 @@
|
||||
}
|
||||
},
|
||||
"search-engines": {
|
||||
"label": "Arama motorları",
|
||||
"label": "Arama Motorları",
|
||||
"new": {
|
||||
"label": "Yeni"
|
||||
},
|
||||
@@ -2617,7 +2648,7 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Ara",
|
||||
"placeholder": "Ara (\"!\" İle Başlamalı)",
|
||||
"nothingFound": "Hiçbir şey bulunamadı",
|
||||
"error": {
|
||||
"fetch": "Veriler alınırken bir hata oluştu"
|
||||
@@ -2650,7 +2681,7 @@
|
||||
"label": "Panel'i aç"
|
||||
},
|
||||
"homeBoard": {
|
||||
"label": "Varsayılan Panel Olarak Ata"
|
||||
"label": "Öntanımlı panel olarak ata"
|
||||
},
|
||||
"settings": {
|
||||
"label": "Ayarları aç"
|
||||
@@ -2676,8 +2707,8 @@
|
||||
"title": "Genel komutlar",
|
||||
"option": {
|
||||
"colorScheme": {
|
||||
"light": "Aydınlık moda geç",
|
||||
"dark": "Koyu moda geç"
|
||||
"light": "Aydınlık Temaya Geç",
|
||||
"dark": "Koyu Temaya Geç"
|
||||
},
|
||||
"language": {
|
||||
"label": "Dili değiştir",
|
||||
@@ -2688,16 +2719,16 @@
|
||||
}
|
||||
},
|
||||
"newBoard": {
|
||||
"label": "Yeni bir panel oluştur"
|
||||
"label": "Yeni Panel Oluştur"
|
||||
},
|
||||
"importBoard": {
|
||||
"label": "Bir paneli içe aktar"
|
||||
},
|
||||
"newApp": {
|
||||
"label": "Yeni bir uygulama oluştur"
|
||||
"label": "Yeni Uygulama Oluştur"
|
||||
},
|
||||
"newIntegration": {
|
||||
"label": "Yeni bir entegrasyon oluşturun",
|
||||
"label": "Yeni Entegrasyon Oluştur",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": "Oluşturmak istediğiniz entegrasyon türünü seçin"
|
||||
@@ -2705,23 +2736,28 @@
|
||||
}
|
||||
},
|
||||
"newUser": {
|
||||
"label": "Yeni bir kullanıcı oluştur"
|
||||
"label": "Yeni Kullanıcı Oluştur"
|
||||
},
|
||||
"newInvite": {
|
||||
"label": "Yeni bir davet oluştur"
|
||||
"label": "Yeni Davet Oluştur"
|
||||
},
|
||||
"newGroup": {
|
||||
"label": "Yeni bir grup oluştur"
|
||||
"label": "Yeni Grup Oluştur"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "Filmi Talep Et",
|
||||
"requestSeries": "Diziyi Talep Et",
|
||||
"openIn": "{kind} üzerinde göster"
|
||||
},
|
||||
"external": {
|
||||
"help": "Harici bir arama motoru kullanın",
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"title": "Arama motorları",
|
||||
"title": "Arama Motorları",
|
||||
"children": {
|
||||
"action": {
|
||||
"search": {
|
||||
@@ -2729,10 +2765,10 @@
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"title": "Arama motoru için bir eylem seçin"
|
||||
"title": "Arama yapmak istediğiniz terimleri girin"
|
||||
},
|
||||
"searchResults": {
|
||||
"title": "Eylemler için bir arama sonucu seçin"
|
||||
"title": "Etkileşime geçmek için bir sonuç seçin"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
@@ -2840,7 +2876,7 @@
|
||||
"label": "Hakkında"
|
||||
},
|
||||
"homeBoard": {
|
||||
"label": "Varsayılan Panel"
|
||||
"label": "Öntanımlı panel"
|
||||
},
|
||||
"preferences": {
|
||||
"label": "Tercihleriniz"
|
||||
@@ -2913,7 +2949,7 @@
|
||||
"interactive": "Etkileşimli, entegrasyon kullanır"
|
||||
},
|
||||
"create": {
|
||||
"title": "Yeni arama motoru",
|
||||
"title": "Yeni Arama Motoru",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Arama motoru oluşturuldu",
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "Talep Et \"{name}\"",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "Sezon",
|
||||
"episodes": "Bölüm"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": "Talep Gönder"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1457,7 +1457,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "使用者",
|
||||
"name": "名稱",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
|
||||
@@ -2,130 +2,130 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Ласкаво просимо до Homarr",
|
||||
"subtitle": "Почнемо з налаштування вашого Homarr.",
|
||||
"description": "Щоб почати, будь ласка, виберіть спосіб налаштування Вашого Homarr.",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"importOldmarr": ""
|
||||
"scratch": "Почати з нуля",
|
||||
"importOldmarr": "Імпорт з Homarr пре-1.0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Імпорт даних",
|
||||
"subtitle": "Ви можете імпортувати дані з наявного екземпляра Homarr.",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Перетягніть zip-файл сюди або клікніть для пошуку",
|
||||
"description": "Завантажений zip буде оброблений і ви зможете вибрати що ви хочете імпортувати"
|
||||
},
|
||||
"fileInfo": {
|
||||
"action": {
|
||||
"change": ""
|
||||
"change": "Змінити файл"
|
||||
}
|
||||
},
|
||||
"importSettings": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Імпортувати налаштування",
|
||||
"description": "Конфігурація імпорту"
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Знайдено {count} дощок",
|
||||
"description": "Оберіть всі дошки з розміром які ви хочете імпортувати",
|
||||
"action": {
|
||||
"selectAll": "",
|
||||
"unselectAll": ""
|
||||
"selectAll": "Вибрати все",
|
||||
"unselectAll": "Прибрати всі"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Підсумок імпорту",
|
||||
"description": "У наведеному нижче розділі ви можете побачити, що буде імпортовано",
|
||||
"action": {
|
||||
"import": ""
|
||||
"import": "Підтвердить імпорт та продовжуйте"
|
||||
},
|
||||
"entities": {
|
||||
"apps": "Додатки",
|
||||
"boards": "Дошки",
|
||||
"integrations": "",
|
||||
"credentialUsers": ""
|
||||
"integrations": "Інтеграції",
|
||||
"credentialUsers": "Облікові записи"
|
||||
}
|
||||
},
|
||||
"tokenModal": {
|
||||
"title": "",
|
||||
"title": "Введіть токен імпорту",
|
||||
"field": {
|
||||
"token": {
|
||||
"label": "Токен.",
|
||||
"description": ""
|
||||
"description": "Введіть показаний токен імпорту з вашого попереднього екземпляра homarr"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Невірний токен",
|
||||
"message": "Введений вами токен невірний"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Адміністратор",
|
||||
"subtitle": "Вкажіть облікові дані для вашого адміністратора.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Користувача створено",
|
||||
"message": "Користувача успішно створено"
|
||||
},
|
||||
"error": {
|
||||
"title": ""
|
||||
"title": "Не вдалося створити користувача"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Зовнішня група",
|
||||
"subtitle": "Вкажіть групу, яку слід використовувати для зовнішніх користувачів.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Назва групи",
|
||||
"description": "Ім'я має відповідати групі адміністраторів зовнішнього провайдера"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Налаштування",
|
||||
"subtitle": ""
|
||||
"subtitle": "Налаштування сервера."
|
||||
},
|
||||
"finish": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Завершити налаштування",
|
||||
"subtitle": "Все готово!",
|
||||
"description": "Ви завершили процес налаштування. Тепер ви можете почати використовувати Homarr. Виберіть наступну дію:",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": ""
|
||||
"goToBoard": "Перейти на {name} дошку",
|
||||
"createBoard": "Створіть свою першу дошку",
|
||||
"inviteUser": "Запросити інших користувачів",
|
||||
"docs": "Ознайомитися з документацією"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backToStart": ""
|
||||
"backToStart": "На початок"
|
||||
},
|
||||
"user": {
|
||||
"title": "Користувачі",
|
||||
"name": "Користувач",
|
||||
"page": {
|
||||
"login": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Увійти до облікового запису",
|
||||
"subtitle": "З поверненням! Будь ласка, введіть свої облікові дані"
|
||||
},
|
||||
"invite": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": ""
|
||||
"title": "Приєднатися до Homarr",
|
||||
"subtitle": "Ласкаво просимо до Homarr! Створіть обліковий запис",
|
||||
"description": "Вас запросив {username}"
|
||||
},
|
||||
"init": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Нова інсталяція Homarr",
|
||||
"subtitle": "Будь ласка, створіть користувача-адміністратора"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"email": {
|
||||
"label": "Електронна пошта",
|
||||
"verified": ""
|
||||
"verified": "Підтверджено"
|
||||
},
|
||||
"username": {
|
||||
"label": "Логін"
|
||||
@@ -133,46 +133,46 @@
|
||||
"password": {
|
||||
"label": "Пароль",
|
||||
"requirement": {
|
||||
"length": "",
|
||||
"length": "Включає принаймні 8 символів",
|
||||
"lowercase": "Включає малу літеру",
|
||||
"uppercase": "Включає велику літеру",
|
||||
"number": "Включає кількість",
|
||||
"special": ""
|
||||
"special": "Включає спеціальні символи"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "Підтвердити пароль"
|
||||
},
|
||||
"previousPassword": {
|
||||
"label": ""
|
||||
"label": "Старий пароль"
|
||||
},
|
||||
"homeBoard": {
|
||||
"label": ""
|
||||
"label": "Домашня дошка"
|
||||
},
|
||||
"pingIconsEnabled": {
|
||||
"label": ""
|
||||
"label": "Використовувати значки для ping"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"usernameTaken": ""
|
||||
"usernameTaken": "Це ім'я користувача вже зайнято"
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Логін",
|
||||
"labelWith": "",
|
||||
"labelWith": "Увійти через {provider}",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Вхід успішний",
|
||||
"message": "Ви ввійшли в систему"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося увійти",
|
||||
"message": "Помилка входу"
|
||||
}
|
||||
},
|
||||
"forgotPassword": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Забули пароль?",
|
||||
"description": "Адміністратор може використовувати наступну команду для відновлення пароля:"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
@@ -180,81 +180,81 @@
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Обліковий запис створено",
|
||||
"message": ""
|
||||
"message": "Увійдіть, щоб продовжити"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося створити обліковий запис",
|
||||
"message": "Не вдалося створити ваш обліковий запис"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create": "Створити користувача",
|
||||
"changePassword": {
|
||||
"label": "",
|
||||
"label": "Змінити пароль",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Пароль змінено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося змінити пароль"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changeHomeBoard": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Домашню дошку змінено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося змінити домашню дошку"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changeFirstDayOfWeek": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Початок тижня успішно змінено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдається змінити перший день тижня"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changePingIconsEnabled": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Іконки ping перемкнуто успішно"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося перемикнути ping-значки"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manageAvatar": {
|
||||
"changeImage": {
|
||||
"label": "",
|
||||
"label": "Змінити зображення",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Зображення змінено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося змінити зображення"
|
||||
},
|
||||
"toLarge": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Зображення занадто велике",
|
||||
"message": "Максимальний розмір зображення {size}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "Видалити зображення",
|
||||
"confirm": "Ви впевнені, що хочете видалити зображення?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Зображення видалено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося видалити зображення"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,42 +262,42 @@
|
||||
"editProfile": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Профіль оновлено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося оновити профіль"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"confirm": ""
|
||||
"label": "Видалити користувача назавжди",
|
||||
"description": "Видаляє цього користувача, включаючи його налаштування. Не буде видалено жодної дошки. Користувач не буде сповіщений.",
|
||||
"confirm": "Ви впевнені, що хочете видалити користувача {username} разом з налаштуваннями?"
|
||||
},
|
||||
"select": {
|
||||
"label": "",
|
||||
"notFound": ""
|
||||
"label": "Вибрати користувача",
|
||||
"notFound": "Користувача не знайдено"
|
||||
},
|
||||
"transfer": {
|
||||
"label": ""
|
||||
"label": "Виберіть нового власника"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"name": "",
|
||||
"search": "",
|
||||
"title": "Групи",
|
||||
"name": "Група",
|
||||
"search": "Знайти групу",
|
||||
"field": {
|
||||
"name": "Ім’я",
|
||||
"members": ""
|
||||
"members": "Учасники"
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "Адміністратор",
|
||||
"item": {
|
||||
"admin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Адміністратор",
|
||||
"description": "Учасники з цим дозволом мають повний доступ до всіх функцій і налаштувань"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -305,7 +305,7 @@
|
||||
"title": "Додатки",
|
||||
"item": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "Створення додатків",
|
||||
"description": ""
|
||||
},
|
||||
"use-all": {
|
||||
@@ -433,58 +433,58 @@
|
||||
"confirm": "",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Група {group} перенесена до {user}"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося передати право власності"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addMember": {
|
||||
"label": ""
|
||||
"label": "Додати учасника"
|
||||
},
|
||||
"removeMember": {
|
||||
"label": "",
|
||||
"confirm": ""
|
||||
"label": "Видалити учасника",
|
||||
"confirm": "Ви впевнені, що хочете видалити {user} з цієї групи?"
|
||||
},
|
||||
"delete": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"confirm": "",
|
||||
"label": "Видалити групу",
|
||||
"description": "Як тільки ви видалите групу, то вже не зможете її відновити. Впевніться у своєму рішенні.",
|
||||
"confirm": "Ви дійсно бажаєте видалити групу {name}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Групу {name} видалено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося видалити групу {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"changePermissions": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Дозволи збережено",
|
||||
"message": "Дозволи збережено"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Дозволи не збережено",
|
||||
"message": "Дозволи не збережено"
|
||||
}
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Групу {name} збережено"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не вдалося зберегти групу {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"label": "",
|
||||
"notFound": ""
|
||||
"label": "Виберіть групу",
|
||||
"notFound": "Груп не знайдено"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -493,47 +493,47 @@
|
||||
"list": {
|
||||
"title": "Додатки",
|
||||
"noResults": {
|
||||
"title": "",
|
||||
"action": ""
|
||||
"title": "Поки що немає додатків",
|
||||
"action": "Створити свій перший додаток"
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"title": "Новий додаток",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Успішно",
|
||||
"message": "Додаток створено"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося створити",
|
||||
"message": "Не вдалося створити додаток"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"title": "Редагувати додаток",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Зміни застосовано",
|
||||
"message": "Додаток збережено"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося застосувати зміни",
|
||||
"message": "Не вдалося зберегти додаток"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "Видалити додаток",
|
||||
"message": "Ви дійсно бажаєте видалити додаток {name}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Успішно видалено",
|
||||
"message": "Додаток видалено"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося видалити",
|
||||
"message": "Неможливо видалити додаток"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -543,46 +543,46 @@
|
||||
"label": "Ім’я"
|
||||
},
|
||||
"description": {
|
||||
"label": ""
|
||||
"label": "Опис"
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
"label": "URL-адреса"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"select": {
|
||||
"label": "",
|
||||
"notFound": ""
|
||||
"label": "Оберіть додаток",
|
||||
"notFound": "Додатків не знайдено"
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"search": "",
|
||||
"title": "Інтеграції",
|
||||
"search": "Пошук інтеграцій",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "Тут ще немає інтеграції"
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"title": "Нова інтеграція з {name}",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Успішно створено",
|
||||
"message": "Інтеграцію створено"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не вдалося створити",
|
||||
"message": "Не вдалося створити інтеграцію"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"title": "Редагування інтеграцію {name}",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"title": "Зміни застосовано",
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -695,21 +699,21 @@
|
||||
"lastUpdated": "",
|
||||
"notSet": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"tooltip": "Обов’язковий ключ ще не був встановлений"
|
||||
},
|
||||
"secureNotice": "",
|
||||
"secureNotice": "Цей секрет неможливо буде побачити після створення",
|
||||
"reset": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Скинути секрет",
|
||||
"message": "Ви впевнені, що хочете скинути цей секрет?"
|
||||
},
|
||||
"noSecretsRequired": {
|
||||
"segmentTitle": "",
|
||||
"text": ""
|
||||
"segmentTitle": "Немає секретів",
|
||||
"text": "Для цієї інтеграції не потрібні секрети"
|
||||
},
|
||||
"kind": {
|
||||
"username": {
|
||||
"label": "Логін",
|
||||
"newLabel": ""
|
||||
"newLabel": "Нове ім'я користувача"
|
||||
},
|
||||
"apiKey": {
|
||||
"label": "",
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Блокнот",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Помилка"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "Ghi chú",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": "Lỗi"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@
|
||||
},
|
||||
"url": {
|
||||
"label": ""
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -1151,6 +1155,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1457,7 +1480,12 @@
|
||||
"mediaServer": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {}
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "",
|
||||
@@ -2319,6 +2347,9 @@
|
||||
"error": ""
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2717,6 +2748,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"group": {
|
||||
@@ -2957,6 +2993,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^3.24.1",
|
||||
"zod-form-data": "^2.0.4"
|
||||
"zod-form-data": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -15,8 +15,21 @@ const searchSchema = z.object({
|
||||
limit: z.number().int().positive().default(10),
|
||||
});
|
||||
|
||||
const mediaRequestOptionsSchema = z.object({
|
||||
mediaId: z.number(),
|
||||
mediaType: z.enum(["tv", "movie"]),
|
||||
});
|
||||
|
||||
const requestMediaSchema = z.object({
|
||||
mediaType: z.enum(["tv", "movie"]),
|
||||
mediaId: z.number(),
|
||||
seasons: z.array(z.number().min(0)).optional(),
|
||||
});
|
||||
|
||||
export const commonSchemas = {
|
||||
paginated: paginatedSchema,
|
||||
byId: byIdSchema,
|
||||
search: searchSchema,
|
||||
mediaRequestOptions: mediaRequestOptionsSchema,
|
||||
requestMedia: requestMediaSchema,
|
||||
};
|
||||
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user