Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4b19f486e | ||
|
|
32cf09a62e | ||
|
|
a1ba4372da | ||
|
|
4e153b8046 | ||
|
|
ac788650fb | ||
|
|
a9b0e26c8d | ||
|
|
132db15424 | ||
|
|
860b73fa3f | ||
|
|
e4dd19b574 | ||
|
|
1b4ed02010 | ||
|
|
47b7cbb086 | ||
|
|
7a6477a272 | ||
|
|
bff536008c | ||
|
|
8b040eddef | ||
|
|
ce87d62b35 | ||
|
|
f88cc7381b | ||
|
|
cb54de524b | ||
|
|
7d8c7b9c2d | ||
|
|
73f7d885cd | ||
|
|
470d27e091 | ||
|
|
99c8e5b609 | ||
|
|
b413e2ec7d | ||
|
|
29d4f9e003 | ||
|
|
1f54bf5b9e | ||
|
|
4d8c22a4e6 | ||
|
|
3e9e1e5e4f | ||
|
|
582b981e69 | ||
|
|
f8ec4a43cf | ||
|
|
2a8809551c | ||
|
|
9df59296ff | ||
|
|
701538fcb5 | ||
|
|
0e82f2d199 | ||
|
|
a2b5d60aa8 | ||
|
|
02dabfcd01 | ||
|
|
931f312291 | ||
|
|
175927970b | ||
|
|
c922c5b083 | ||
|
|
73bbcfeee8 | ||
|
|
7a2d97ba91 | ||
|
|
e47d497560 | ||
|
|
b9271ba14a | ||
|
|
9d85677046 | ||
|
|
b30c192b8f | ||
|
|
d4f04da709 | ||
|
|
7960a4cea1 | ||
|
|
243d7e9dd4 | ||
|
|
bdefff636b | ||
|
|
55feb9dff8 | ||
|
|
45af6442a1 | ||
|
|
72debef4b4 | ||
|
|
25635db17e | ||
|
|
1897752e38 | ||
|
|
554853ffbd | ||
|
|
e40237e4a5 | ||
|
|
fa6262cf5e | ||
|
|
49bd02e869 | ||
|
|
3c8fbe371b | ||
|
|
81a8cf4b36 | ||
|
|
b5c008b4ad | ||
|
|
343438391b | ||
|
|
8400b9fb11 | ||
|
|
fe65e73fff | ||
|
|
e023edf9d6 | ||
|
|
927913cf4e | ||
|
|
07d8103051 | ||
|
|
fe08ccf589 | ||
|
|
c55207bd59 | ||
|
|
e35811c921 | ||
|
|
e481e116ff | ||
|
|
32be09a4a7 |
No files matched your search
@@ -31,6 +31,9 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.1.0
|
||||
- 1.0.1
|
||||
- 1.0.0
|
||||
- 1.0.0-beta
|
||||
- Other (describe in "additional information")
|
||||
default: 0
|
||||
@@ -75,4 +78,4 @@ body:
|
||||
- Other (describe in "additional information")
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
@@ -1,3 +1,61 @@
|
||||
## [1.2.0](https://github.com/homarr-labs/homarr/compare/v1.1.0...v1.2.0) (2025-01-24)
|
||||
|
||||
### Features
|
||||
|
||||
* add bookmarks options (apps) ([#2048](https://github.com/homarr-labs/homarr/issues/2048)) ([132db15](https://github.com/homarr-labs/homarr/commit/132db154242959dc2f16add4e58b0bdb7b96fb8c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* date & time widget padding cut off ([#2089](https://github.com/homarr-labs/homarr/issues/2089)) ([e4dd19b](https://github.com/homarr-labs/homarr/commit/e4dd19b574bfe0dcbcd92a3c5993aef550683fc3))
|
||||
* **deps:** update turbo monorepo to ^2.3.4 ([#2085](https://github.com/homarr-labs/homarr/issues/2085)) ([8b040ed](https://github.com/homarr-labs/homarr/commit/8b040eddef8dbc1269489038bc503dcf23ba9580))
|
||||
* issue with foreign key constraint do to empty string for home board ([#2088](https://github.com/homarr-labs/homarr/issues/2088)) ([ac78865](https://github.com/homarr-labs/homarr/commit/ac788650fb60d7aca889ec270dc29edb7ee53123))
|
||||
* system health monitoring gets proxmox id passed ([#2094](https://github.com/homarr-labs/homarr/issues/2094)) ([a1ba437](https://github.com/homarr-labs/homarr/commit/a1ba4372da3430777a85dc83bbfc59f4b03d65fb))
|
||||
* url-encode nzbget credentials ([#2093](https://github.com/homarr-labs/homarr/issues/2093)) ([4e153b8](https://github.com/homarr-labs/homarr/commit/4e153b804664e4fbc63ee5eb945c8096c809c6d5))
|
||||
|
||||
## [1.1.0](https://github.com/homarr-labs/homarr/compare/v1.0.1...v1.1.0) (2025-01-22)
|
||||
|
||||
### Features
|
||||
|
||||
* add dropdown list for switching boards ([#2042](https://github.com/homarr-labs/homarr/issues/2042)) ([02dabfc](https://github.com/homarr-labs/homarr/commit/02dabfcd01e372ada7d80a0501b708b171c8fbc6))
|
||||
* add initial focus on new integration search ([#2015](https://github.com/homarr-labs/homarr/issues/2015)) ([b30c192](https://github.com/homarr-labs/homarr/commit/b30c192b8fbbbfcff8eb09b8f1654eb8dd90b56d))
|
||||
* add support for calendar without integrations, add today mark ([#2016](https://github.com/homarr-labs/homarr/issues/2016)) ([9d85677](https://github.com/homarr-labs/homarr/commit/9d8567704670a9ab471ac3e246c5ba85f908cfd9))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add item does not respect dynamic sections ([#2010](https://github.com/homarr-labs/homarr/issues/2010)) ([d4f04da](https://github.com/homarr-labs/homarr/commit/d4f04da70910b6adbf3764de1499dbd46fe9b6d5))
|
||||
* add missing request handler for rss feeds ([#2006](https://github.com/homarr-labs/homarr/issues/2006)) ([99c8e5b](https://github.com/homarr-labs/homarr/commit/99c8e5b609b17dfe443b35bfff2b883845dc62fe))
|
||||
* certificate card delete button not reachable caused by overflow ([#2037](https://github.com/homarr-labs/homarr/issues/2037)) ([c922c5b](https://github.com/homarr-labs/homarr/commit/c922c5b08387b01136332876782fed1444203096))
|
||||
* change error when section for item not found to logged warning and remove item ([#2044](https://github.com/homarr-labs/homarr/issues/2044)) ([b413e2e](https://github.com/homarr-labs/homarr/commit/b413e2ec7dd37c43665373be914ffa414599a206))
|
||||
* copy only working for https and localhost, adding open in new tab ([#2035](https://github.com/homarr-labs/homarr/issues/2035)) ([7a2d97b](https://github.com/homarr-labs/homarr/commit/7a2d97ba9119e046c06365146fc4efd080f6f6df))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.29.0 ([#2057](https://github.com/homarr-labs/homarr/issues/2057)) ([2a88095](https://github.com/homarr-labs/homarr/commit/2a8809551cfc4f4c09aa5c01cac05914fa913769))
|
||||
* **deps:** update dependency better-sqlite3 to ^11.8.1 ([#2005](https://github.com/homarr-labs/homarr/issues/2005)) ([49bd02e](https://github.com/homarr-labs/homarr/commit/49bd02e869081ec325bd8b4ba2909603a82cd7df))
|
||||
* **deps:** update dependency jotai to ^2.11.1 ([#2026](https://github.com/homarr-labs/homarr/issues/2026)) ([45af644](https://github.com/homarr-labs/homarr/commit/45af6442a1b84be399ddd281a17227a4184490be))
|
||||
* **deps:** update dependency typescript-eslint to ^8.21.0 ([#2032](https://github.com/homarr-labs/homarr/issues/2032)) ([55feb9d](https://github.com/homarr-labs/homarr/commit/55feb9dff87088d7f6d006bcecc61b0435edbb6f))
|
||||
* **deps:** update dependency undici to v7.3.0 ([#2061](https://github.com/homarr-labs/homarr/issues/2061)) ([3e9e1e5](https://github.com/homarr-labs/homarr/commit/3e9e1e5e4f737f8bdb1046585463c987fe43c3d5))
|
||||
* **deps:** update mantine monorepo to ^7.16.1 ([#2014](https://github.com/homarr-labs/homarr/issues/2014)) ([1897752](https://github.com/homarr-labs/homarr/commit/1897752e3893804a5082f34b20929ef007825506))
|
||||
* **deps:** update nextjs monorepo to ^15.1.6 ([#2063](https://github.com/homarr-labs/homarr/issues/2063)) ([4d8c22a](https://github.com/homarr-labs/homarr/commit/4d8c22a4e684b4f921967992d04d2461eb2e52db))
|
||||
* **deps:** update tanstack-query monorepo to ^5.64.2 ([#2013](https://github.com/homarr-labs/homarr/issues/2013)) ([554853f](https://github.com/homarr-labs/homarr/commit/554853ffbda202eff559b5dafa330a15e2649f17))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.17.1 ([#2047](https://github.com/homarr-labs/homarr/issues/2047)) ([701538f](https://github.com/homarr-labs/homarr/commit/701538fcb5c546478f5d31b2869b933e7544cc3e))
|
||||
* **deps:** update tiptap monorepo to v2.11.3 ([#2067](https://github.com/homarr-labs/homarr/issues/2067)) ([1f54bf5](https://github.com/homarr-labs/homarr/commit/1f54bf5b9e24b6d063f5ce9165c87d2d036fb8c6))
|
||||
* docker hosts and ports env variable wrongly used ([#2050](https://github.com/homarr-labs/homarr/issues/2050)) ([73f7d88](https://github.com/homarr-labs/homarr/commit/73f7d885cddbbaa2ac879953fe5ee24a6abe582c))
|
||||
* empty env variables should be ignored ([#2040](https://github.com/homarr-labs/homarr/issues/2040)) ([1759279](https://github.com/homarr-labs/homarr/commit/175927970b25648104fac5a31649471b3ebeaa9e))
|
||||
* entity state and execute automation cards aren't movable ([#2043](https://github.com/homarr-labs/homarr/issues/2043)) ([29d4f9e](https://github.com/homarr-labs/homarr/commit/29d4f9e00313fe3e7ae207547cbd7a143b314535))
|
||||
* health monitoring cpu temp ring when no data, disks health when no data ([#2029](https://github.com/homarr-labs/homarr/issues/2029)) ([bdefff6](https://github.com/homarr-labs/homarr/commit/bdefff636b06cb33020765e606bfe8e41c931722))
|
||||
* hide current language switch in other users preferences ([#2069](https://github.com/homarr-labs/homarr/issues/2069)) ([470d27e](https://github.com/homarr-labs/homarr/commit/470d27e091a006af8a8c3ceccfc439902ebc5f18))
|
||||
* unable to change home board in user preferences ([#2011](https://github.com/homarr-labs/homarr/issues/2011)) ([7960a4c](https://github.com/homarr-labs/homarr/commit/7960a4cea11d839f5a0d1889e98bb949c11b68fa))
|
||||
* unable to move items with touch devices ([#2038](https://github.com/homarr-labs/homarr/issues/2038)) ([931f312](https://github.com/homarr-labs/homarr/commit/931f312291c0a6f1f22a26adf89dfbf912e4fe5e))
|
||||
* url of batch link can get to long with 414 http code ([#2036](https://github.com/homarr-labs/homarr/issues/2036)) ([73bbcfe](https://github.com/homarr-labs/homarr/commit/73bbcfeee86bf0950145070a6a2ce528b708fe7c))
|
||||
* User options switch on load value always false ([#1995](https://github.com/homarr-labs/homarr/issues/1995)) ([fa6262c](https://github.com/homarr-labs/homarr/commit/fa6262cf5e780fc3c5b445f988de9d9eb5b86ab5))
|
||||
|
||||
## [1.0.1](https://github.com/homarr-labs/homarr/compare/v1.0.0...v1.0.1) (2025-01-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add error from ldap bind operation to log statement ([#1980](https://github.com/homarr-labs/homarr/issues/1980)) ([927913c](https://github.com/homarr-labs/homarr/commit/927913cf4e0a6e4f3a6b7dbf9ae9034d0780b5fb))
|
||||
* add missing old-import proxmox integration kind mapping ([#1979](https://github.com/homarr-labs/homarr/issues/1979)) ([e023edf](https://github.com/homarr-labs/homarr/commit/e023edf9d62417b054eb565a7f794267539eb992))
|
||||
* **deps:** update dependency flag-icons to ^7.3.2 ([#1981](https://github.com/homarr-labs/homarr/issues/1981)) ([c55207b](https://github.com/homarr-labs/homarr/commit/c55207bd59f0c51869e8fdb36e083a8327ca46b1))
|
||||
* **deps:** update nextjs monorepo to ^15.1.5 ([#1978](https://github.com/homarr-labs/homarr/issues/1978)) ([e35811c](https://github.com/homarr-labs/homarr/commit/e35811c921aea8c96dca398841ece396d71d4979))
|
||||
|
||||
## 1.0.0 (2025-01-17)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:22.13.0-alpine AS base
|
||||
FROM node:22.13.1-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import "@homarr/auth/env";
|
||||
import "@homarr/db/env";
|
||||
import "@homarr/common/env";
|
||||
import "@homarr/docker/env";
|
||||
|
||||
import type { NextConfig } from "next";
|
||||
import MillionLint from "@million/lint";
|
||||
|
||||
+18
-16
@@ -23,8 +23,10 @@
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
"@homarr/gridstack": "^1.11.3",
|
||||
"@homarr/gridstack": "^1.12.0",
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/modals": "workspace:^0.1.0",
|
||||
@@ -39,18 +41,18 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^7.16.0",
|
||||
"@mantine/core": "^7.16.0",
|
||||
"@mantine/dropzone": "^7.16.0",
|
||||
"@mantine/hooks": "^7.16.0",
|
||||
"@mantine/modals": "^7.16.0",
|
||||
"@mantine/tiptap": "^7.16.0",
|
||||
"@mantine/colors-generator": "^7.16.1",
|
||||
"@mantine/core": "^7.16.1",
|
||||
"@mantine/dropzone": "^7.16.1",
|
||||
"@mantine/hooks": "^7.16.1",
|
||||
"@mantine/modals": "^7.16.1",
|
||||
"@mantine/tiptap": "^7.16.1",
|
||||
"@million/lint": "1.0.14",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@tabler/icons-react": "^3.28.1",
|
||||
"@tanstack/react-query": "^5.64.1",
|
||||
"@tanstack/react-query-devtools": "^5.64.1",
|
||||
"@tanstack/react-query-next-experimental": "5.64.1",
|
||||
"@tabler/icons-react": "^3.29.0",
|
||||
"@tanstack/react-query": "^5.64.2",
|
||||
"@tanstack/react-query-devtools": "^5.64.2",
|
||||
"@tanstack/react-query-next-experimental": "5.64.2",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
"@trpc/react-query": "next",
|
||||
@@ -62,11 +64,11 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"flag-icons": "^7.3.1",
|
||||
"flag-icons": "^7.3.2",
|
||||
"glob": "^11.0.1",
|
||||
"jotai": "^2.11.0",
|
||||
"jotai": "^2.11.1",
|
||||
"mantine-react-table": "2.0.0-beta.8",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "19.0.0",
|
||||
@@ -83,9 +85,9 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/node": "^22.10.10",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.0.7",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"concurrently": "^9.1.2",
|
||||
|
||||
@@ -94,6 +94,7 @@ export function TRPCReactProvider(props: PropsWithChildren) {
|
||||
false: unstable_httpBatchStreamLink({
|
||||
transformer: superjson,
|
||||
url: getTrpcUrl(),
|
||||
maxURLLength: 2083, // Suggested by tRPC: https://trpc.io/docs/client/links/httpBatchLink#setting-a-maximum-url-length
|
||||
headers: createHeadersCallbackForSource("nextjs-react (json)"),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { MouseEvent } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Group, Menu } from "@mantine/core";
|
||||
import { useHotkeys } from "@mantine/hooks";
|
||||
@@ -9,9 +10,11 @@ import {
|
||||
IconBox,
|
||||
IconBoxAlignTop,
|
||||
IconChevronDown,
|
||||
IconLayoutBoard,
|
||||
IconPencil,
|
||||
IconPencilOff,
|
||||
IconPlus,
|
||||
IconReplace,
|
||||
IconResize,
|
||||
IconSettings,
|
||||
} from "@tabler/icons-react";
|
||||
@@ -49,6 +52,8 @@ export const BoardContentHeaderActions = () => {
|
||||
<HeaderButton href={`/boards/${board.name}/settings`}>
|
||||
<IconSettings stroke={1.5} />
|
||||
</HeaderButton>
|
||||
|
||||
<SelectBoardsMenu />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -151,6 +156,32 @@ const EditModeMenu = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SelectBoardsMenu = () => {
|
||||
const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();
|
||||
|
||||
return (
|
||||
<Menu position="bottom-end" withArrow>
|
||||
<Menu.Target>
|
||||
<HeaderButton w="auto" px={4}>
|
||||
<IconReplace stroke={1.5} />
|
||||
</HeaderButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
|
||||
{boards.map((board) => (
|
||||
<Menu.Item
|
||||
key={board.id}
|
||||
component={Link}
|
||||
href={`/boards/${board.name}`}
|
||||
leftSection={<IconLayoutBoard size={20} />}
|
||||
>
|
||||
{board.name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const usePreventLeaveWithDirty = (isDirty: boolean) => {
|
||||
const t = useI18n();
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
@@ -15,7 +15,9 @@ export const IntegrationCreateDropdownContent = () => {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filteredKinds = useMemo(() => {
|
||||
return integrationKinds.filter((kind) => kind.includes(search.toLowerCase()));
|
||||
return integrationKinds.filter((kind) =>
|
||||
getIntegrationName(kind).toLowerCase().includes(search.toLowerCase().trim()),
|
||||
);
|
||||
}, [search]);
|
||||
|
||||
const handleSearch = React.useCallback(
|
||||
@@ -29,6 +31,7 @@ export const IntegrationCreateDropdownContent = () => {
|
||||
leftSection={<IconSearch stroke={1.5} size={20} />}
|
||||
placeholder={t("integration.page.list.search")}
|
||||
value={search}
|
||||
data-autofocus
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
||||
|
||||
@@ -102,7 +102,15 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) {
|
||||
|
||||
const IntegrationSelectMenu = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<Menu width={256} trapFocus position="bottom-end" withinPortal shadow="md" keepMounted={false}>
|
||||
<Menu
|
||||
width={256}
|
||||
trapFocus
|
||||
position="bottom-end"
|
||||
withinPortal
|
||||
shadow="md"
|
||||
keepMounted={false}
|
||||
withInitialFocusPlaceholder={false}
|
||||
>
|
||||
{children}
|
||||
<MenuDropdown>
|
||||
<IntegrationCreateDropdownContent />
|
||||
|
||||
@@ -153,7 +153,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
{
|
||||
label: t("items.help.items.submitIssue"),
|
||||
icon: IconBrandGithub,
|
||||
href: "https://github.com/ajnart/homarr/issues/new/choose",
|
||||
href: "https://github.com/homarr-labs/homarr/issues/new/choose",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
@@ -165,7 +165,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
{
|
||||
label: t("items.help.items.sourceCode"),
|
||||
icon: IconGitFork,
|
||||
href: "https://github.com/ajnart/homarr",
|
||||
href: "https://github.com/homarr-labs/homarr",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -15,6 +15,11 @@ export const CopyMedia = ({ media }: CopyMediaProps) => {
|
||||
|
||||
const url = typeof window !== "undefined" ? `${window.location.origin}/api/user-medias/${media.id}` : "";
|
||||
|
||||
// Clipboard only works on localhost or secure connections (https)
|
||||
if (url.startsWith("http://") && !url.startsWith("http://localhost")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CopyButton value={url}>
|
||||
{({ copy, copied }) => (
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Anchor, Group, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from "@mantine/core";
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { IconExternalLink } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { humanFileSize } from "@homarr/common";
|
||||
import type { inferSearchParamsFromSchema } from "@homarr/common/types";
|
||||
import { createLocalImageUrl } from "@homarr/icons/local";
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
import { SearchInput, TablePagination, UserAvatar } from "@homarr/ui";
|
||||
import { z } from "@homarr/validation";
|
||||
@@ -91,13 +106,14 @@ interface RowProps {
|
||||
|
||||
const Row = async ({ media }: RowProps) => {
|
||||
const session = await auth();
|
||||
const t = await getI18n();
|
||||
const canDelete = media.creatorId === session?.user.id || session?.user.permissions.includes("media-full-all");
|
||||
|
||||
return (
|
||||
<TableTr>
|
||||
<TableTd w={64}>
|
||||
<Image
|
||||
src={`/api/user-medias/${media.id}`}
|
||||
src={createLocalImageUrl(media.id)}
|
||||
alt={media.name}
|
||||
width={64}
|
||||
height={64}
|
||||
@@ -121,6 +137,17 @@ const Row = async ({ media }: RowProps) => {
|
||||
<TableTd w={64}>
|
||||
<Group wrap="nowrap" gap="xs">
|
||||
<CopyMedia media={media} />
|
||||
<Tooltip label={t("media.action.open.label")} openDelay={500}>
|
||||
<ActionIcon
|
||||
component="a"
|
||||
href={createLocalImageUrl(media.id)}
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="subtle"
|
||||
>
|
||||
<IconExternalLink size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{canDelete && <DeleteMedia media={media} />}
|
||||
</Group>
|
||||
</TableTd>
|
||||
|
||||
@@ -59,10 +59,17 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
||||
{x509Certificates.map((cert) => (
|
||||
<Card key={cert.x509.fingerprint} withBorder>
|
||||
<Group wrap="nowrap">
|
||||
<IconCertificate color={getMantineColor(iconColor(cert.x509.validToDate), 6)} size={32} stroke={1.5} />
|
||||
<Stack flex={1} gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text fw={500}>{cert.x509.subject}</Text>
|
||||
<IconCertificate
|
||||
color={getMantineColor(iconColor(cert.x509.validToDate), 6)}
|
||||
style={{ minWidth: 32 }}
|
||||
size={32}
|
||||
stroke={1.5}
|
||||
/>
|
||||
<Stack flex={1} gap="xs" maw="calc(100% - 48px)">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
|
||||
{cert.x509.subject}
|
||||
</Text>
|
||||
<Text c="gray.6" ta="end" size="sm">
|
||||
{cert.fileName}
|
||||
</Text>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { MantineReactTable } from "mantine-react-table";
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import type { DockerContainerState } from "@homarr/definitions";
|
||||
import type { ContainerState } from "@homarr/docker";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddDockerAppToHomarr } from "@homarr/modals-collection";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
@@ -246,9 +246,9 @@ const containerStates = {
|
||||
exited: "red",
|
||||
removing: "pink",
|
||||
dead: "dark",
|
||||
} satisfies Record<DockerContainerState, MantineColor>;
|
||||
} satisfies Record<ContainerState, MantineColor>;
|
||||
|
||||
const ContainerStateBadge = ({ state }: { state: DockerContainerState }) => {
|
||||
const ContainerStateBadge = ({ state }: { state: ContainerState }) => {
|
||||
const t = useScopedI18n("docker.field.state.option");
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro
|
||||
});
|
||||
const form = useZodForm(validation.user.changeHomeBoards, {
|
||||
initialValues: {
|
||||
homeBoardId: user.homeBoardId ?? "",
|
||||
mobileHomeBoardId: user.mobileHomeBoardId ?? "",
|
||||
homeBoardId: user.homeBoardId,
|
||||
mobileHomeBoardId: user.mobileHomeBoardId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+4
-1
@@ -51,7 +51,10 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<Switch {...form.getInputProps("pingIconsEnabled")} label={t("user.field.pingIconsEnabled.label")} />
|
||||
<Switch
|
||||
{...form.getInputProps("pingIconsEnabled", { type: "checkbox" })}
|
||||
label={t("user.field.pingIconsEnabled.label")}
|
||||
/>
|
||||
|
||||
<Group justify="end">
|
||||
<Button type="submit" color="teal" loading={isPending}>
|
||||
|
||||
@@ -83,10 +83,12 @@ export default async function EditUserPage(props: Props) {
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Stack mb="lg">
|
||||
<Title order={2}>{tGeneral("item.language")}</Title>
|
||||
<CurrentLanguageCombobox />
|
||||
</Stack>
|
||||
{session?.user.id === user.id && (
|
||||
<Stack mb="lg">
|
||||
<Title order={2}>{tGeneral("item.language")}</Title>
|
||||
<CurrentLanguageCombobox />
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Stack mb="lg">
|
||||
<Title order={2}>{tGeneral("item.board.title")}</Title>
|
||||
|
||||
@@ -30,7 +30,9 @@ const handler = auth(async (req) => {
|
||||
req,
|
||||
createContext: () => createTRPCContext({ session: req.auth, headers: req.headers }),
|
||||
onError({ error, path, type }) {
|
||||
logger.error(`tRPC Error with ${type} on '${path}': (${error.code}) - ${error.message}`);
|
||||
logger.error(
|
||||
`tRPC Error with ${type} on '${path}': (${error.code}) - ${error.message}\n${error.stack}\n${error.cause}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
|
||||
import type { Board, DynamicSection, EmptySection, Item } from "~/app/[locale]/boards/_types";
|
||||
import { getFirstEmptyPosition } from "./empty-position";
|
||||
|
||||
export interface CreateItemInput {
|
||||
kind: WidgetKind;
|
||||
}
|
||||
|
||||
export const createItemCallback =
|
||||
({ kind }: CreateItemInput) =>
|
||||
(previous: Board): Board => {
|
||||
const firstSection = previous.sections
|
||||
.filter((section): section is EmptySection => section.kind === "empty")
|
||||
.sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset)
|
||||
.at(0);
|
||||
|
||||
if (!firstSection) return previous;
|
||||
|
||||
const dynamicSectionsOfFirstSection = previous.sections.filter(
|
||||
(section): section is DynamicSection => section.kind === "dynamic" && section.parentSectionId === firstSection.id,
|
||||
);
|
||||
const elements = [...firstSection.items, ...dynamicSectionsOfFirstSection];
|
||||
const emptyPosition = getFirstEmptyPosition(elements, previous.columnCount);
|
||||
|
||||
if (!emptyPosition) {
|
||||
console.error("Your board is full");
|
||||
return previous;
|
||||
}
|
||||
|
||||
const widget = {
|
||||
id: createId(),
|
||||
kind,
|
||||
options: {},
|
||||
width: 1,
|
||||
height: 1,
|
||||
...emptyPosition,
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
customCssClasses: [],
|
||||
},
|
||||
} satisfies Modify<
|
||||
Item,
|
||||
{
|
||||
kind: WidgetKind;
|
||||
}
|
||||
>;
|
||||
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (section.id !== firstSection.id) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.concat(widget),
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { createId } from "@homarr/db/client";
|
||||
|
||||
import type { Board, DynamicSection, EmptySection } from "~/app/[locale]/boards/_types";
|
||||
import { getFirstEmptyPosition } from "./empty-position";
|
||||
|
||||
export interface DuplicateItemInput {
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
export const duplicateItemCallback =
|
||||
({ itemId }: DuplicateItemInput) =>
|
||||
(previous: Board): Board => {
|
||||
const itemToDuplicate = previous.sections
|
||||
.flatMap((section) => section.items.map((item) => ({ ...item, sectionId: section.id })))
|
||||
.find((item) => item.id === itemId);
|
||||
if (!itemToDuplicate) return previous;
|
||||
|
||||
const currentSection = previous.sections.find((section) => section.id === itemToDuplicate.sectionId);
|
||||
if (!currentSection) return previous;
|
||||
|
||||
const dynamicSectionsOfCurrentSection = previous.sections.filter(
|
||||
(section): section is DynamicSection =>
|
||||
section.kind === "dynamic" && section.parentSectionId === currentSection.id,
|
||||
);
|
||||
const elements = [...currentSection.items, ...dynamicSectionsOfCurrentSection];
|
||||
let sectionId = currentSection.id;
|
||||
let emptyPosition = getFirstEmptyPosition(
|
||||
elements,
|
||||
currentSection.kind === "dynamic" ? currentSection.width : previous.columnCount,
|
||||
currentSection.kind === "dynamic" ? currentSection.height : undefined,
|
||||
{
|
||||
width: itemToDuplicate.width,
|
||||
height: itemToDuplicate.height,
|
||||
},
|
||||
);
|
||||
|
||||
if (!emptyPosition) {
|
||||
const firstSection = previous.sections
|
||||
.filter((section): section is EmptySection => section.kind === "empty")
|
||||
.sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset)
|
||||
.at(0);
|
||||
|
||||
if (!firstSection) return previous;
|
||||
|
||||
const dynamicSectionsOfFirstSection = previous.sections.filter(
|
||||
(section): section is DynamicSection =>
|
||||
section.kind === "dynamic" && section.parentSectionId === firstSection.id,
|
||||
);
|
||||
const elements = [...firstSection.items, ...dynamicSectionsOfFirstSection];
|
||||
emptyPosition = getFirstEmptyPosition(elements, previous.columnCount, undefined, {
|
||||
width: itemToDuplicate.width,
|
||||
height: itemToDuplicate.height,
|
||||
});
|
||||
if (!emptyPosition) {
|
||||
console.error("Your board is full");
|
||||
return previous;
|
||||
}
|
||||
|
||||
sectionId = firstSection.id;
|
||||
}
|
||||
|
||||
const widget = structuredClone(itemToDuplicate);
|
||||
widget.id = createId();
|
||||
widget.xOffset = emptyPosition.xOffset;
|
||||
widget.yOffset = emptyPosition.yOffset;
|
||||
widget.sectionId = sectionId;
|
||||
|
||||
const result = {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (section.id !== sectionId) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.concat(widget),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Item } from "~/app/[locale]/boards/_types";
|
||||
|
||||
export const getFirstEmptyPosition = (
|
||||
elements: Pick<Item, "yOffset" | "xOffset" | "width" | "height">[],
|
||||
columnCount: number,
|
||||
rowCount = 9999,
|
||||
size: { width: number; height: number } = { width: 1, height: 1 },
|
||||
) => {
|
||||
for (let yOffset = 0; yOffset < rowCount + 1 - size.height; yOffset++) {
|
||||
for (let xOffset = 0; xOffset < columnCount; xOffset++) {
|
||||
const isOccupied = elements.some(
|
||||
(element) =>
|
||||
element.yOffset < yOffset + size.height &&
|
||||
element.yOffset + element.height > yOffset &&
|
||||
element.xOffset < xOffset + size.width &&
|
||||
element.xOffset + element.width > xOffset,
|
||||
);
|
||||
|
||||
if (!isOccupied) {
|
||||
return { xOffset, yOffset };
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
import { createItemCallback } from "../create-item";
|
||||
import * as emptyPosition from "../empty-position";
|
||||
import { createDynamicSection, createEmptySection, createItem } from "./shared";
|
||||
|
||||
describe("item actions create-item", () => {
|
||||
test("should add it to first section", () => {
|
||||
const spy = vi.spyOn(emptyPosition, "getFirstEmptyPosition");
|
||||
spy.mockReturnValue({ xOffset: 5, yOffset: 5 });
|
||||
const input = {
|
||||
sections: [createEmptySection("1", 2), createEmptySection("2", 0), createEmptySection("3", 1)],
|
||||
columnCount: 4,
|
||||
} satisfies Pick<Board, "sections" | "columnCount">;
|
||||
|
||||
const result = createItemCallback({
|
||||
kind: "clock",
|
||||
})(input as unknown as Board);
|
||||
|
||||
const firstSection = result.sections.find((section) => section.id === "2");
|
||||
const item = firstSection?.items.at(0);
|
||||
expect(item).toEqual(expect.objectContaining({ kind: "clock", xOffset: 5, yOffset: 5 }));
|
||||
expect(spy).toHaveBeenCalledWith([], input.columnCount);
|
||||
});
|
||||
test("should correctly pass dynamic section and items to getFirstEmptyPosition", () => {
|
||||
const spy = vi.spyOn(emptyPosition, "getFirstEmptyPosition");
|
||||
spy.mockReturnValue({ xOffset: 5, yOffset: 5 });
|
||||
const firstSection = createEmptySection("2", 0);
|
||||
const expectedItem = createItem({ id: "12", xOffset: 1, yOffset: 2, width: 3, height: 2 });
|
||||
firstSection.items.push(expectedItem);
|
||||
const dynamicSectionInFirst = createDynamicSection({
|
||||
id: "4",
|
||||
parentSectionId: "2",
|
||||
yOffset: 0,
|
||||
xOffset: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
});
|
||||
|
||||
const input = {
|
||||
sections: [
|
||||
createEmptySection("1", 2),
|
||||
firstSection,
|
||||
createEmptySection("3", 1),
|
||||
dynamicSectionInFirst,
|
||||
createDynamicSection({ id: "5", parentSectionId: "3", yOffset: 1 }),
|
||||
],
|
||||
columnCount: 4,
|
||||
} satisfies Pick<Board, "sections" | "columnCount">;
|
||||
|
||||
const result = createItemCallback({
|
||||
kind: "clock",
|
||||
})(input as unknown as Board);
|
||||
|
||||
const firstSectionResult = result.sections.find((section) => section.id === "2");
|
||||
const item = firstSectionResult?.items.find((item) => item.id !== "12");
|
||||
expect(item).toEqual(expect.objectContaining({ kind: "clock", xOffset: 5, yOffset: 5 }));
|
||||
expect(spy).toHaveBeenCalledWith([expectedItem, dynamicSectionInFirst], input.columnCount);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
import { duplicateItemCallback } from "../duplicate-item";
|
||||
import * as emptyPosition from "../empty-position";
|
||||
import { createEmptySection, createItem } from "./shared";
|
||||
|
||||
describe("item actions duplicate-item", () => {
|
||||
test("should copy it in the same section", () => {
|
||||
const spy = vi.spyOn(emptyPosition, "getFirstEmptyPosition");
|
||||
spy.mockReturnValue({ xOffset: 5, yOffset: 5 });
|
||||
const currentSection = createEmptySection("2", 1);
|
||||
const currentItem = createItem({
|
||||
id: "1",
|
||||
xOffset: 1,
|
||||
yOffset: 3,
|
||||
width: 3,
|
||||
height: 2,
|
||||
kind: "minecraftServerStatus",
|
||||
integrationIds: ["1"],
|
||||
options: { address: "localhost" },
|
||||
advancedOptions: { customCssClasses: ["test"] },
|
||||
});
|
||||
const otherItem = createItem({
|
||||
id: "2",
|
||||
});
|
||||
currentSection.items.push(currentItem, otherItem);
|
||||
const input = {
|
||||
columnCount: 10,
|
||||
sections: [createEmptySection("1", 0), currentSection, createEmptySection("3", 2)],
|
||||
} satisfies Pick<Board, "sections" | "columnCount">;
|
||||
|
||||
const result = duplicateItemCallback({ itemId: currentItem.id })(input as unknown as Board);
|
||||
|
||||
const section = result.sections.find((section) => section.id === "2");
|
||||
expect(section?.items.length).toBe(3);
|
||||
const duplicatedItem = section?.items.find((item) => item.id !== currentItem.id && item.id !== otherItem.id);
|
||||
|
||||
expect(duplicatedItem).toEqual(
|
||||
expect.objectContaining({
|
||||
kind: "minecraftServerStatus",
|
||||
xOffset: 5,
|
||||
yOffset: 5,
|
||||
width: 3,
|
||||
height: 2,
|
||||
integrationIds: ["1"],
|
||||
options: { address: "localhost" },
|
||||
advancedOptions: { customCssClasses: ["test"] },
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import type { Item } from "~/app/[locale]/boards/_types";
|
||||
import { getFirstEmptyPosition } from "../empty-position";
|
||||
|
||||
describe("get first empty position", () => {
|
||||
test.each([
|
||||
[[[" ", " ", " ", " "]], [1, 1], 0, 0],
|
||||
[[["a", " ", " ", " "]], [1, 1], 1, 0],
|
||||
[[[" ", "a", " ", " "]], [1, 1], 0, 0],
|
||||
[
|
||||
[
|
||||
["a", "a", " ", " "],
|
||||
["a", "a", " ", " "],
|
||||
],
|
||||
[1, 1],
|
||||
2,
|
||||
0,
|
||||
],
|
||||
[[["a", "a", "a", "a"]], [1, 1], 0, 1],
|
||||
[
|
||||
[
|
||||
["a", "a", "a", "a"],
|
||||
["a", "a", "a", "a"],
|
||||
],
|
||||
[1, 1],
|
||||
0,
|
||||
2,
|
||||
],
|
||||
[
|
||||
[
|
||||
["a", "a", " ", "b", "b"],
|
||||
["a", "a", " ", "b", "b"],
|
||||
],
|
||||
[1, 2],
|
||||
2,
|
||||
0,
|
||||
],
|
||||
[
|
||||
[
|
||||
["a", "a", " ", " ", "b", "b"],
|
||||
["a", "a", " ", " ", "b", "b"],
|
||||
],
|
||||
[2, 2],
|
||||
2,
|
||||
0,
|
||||
],
|
||||
[
|
||||
[
|
||||
["a", "a", " ", "d", "b", "b"],
|
||||
["a", "a", "c", "e", "b", "b"],
|
||||
],
|
||||
[1, 1],
|
||||
2,
|
||||
0,
|
||||
],
|
||||
[
|
||||
[
|
||||
["a", "a", " ", " ", "b", "b"],
|
||||
["a", "a", " ", "e", "b", "b"],
|
||||
],
|
||||
[2, 2],
|
||||
0,
|
||||
2,
|
||||
],
|
||||
])("should return the first empty position", (layout, size, expectedX, expectedY) => {
|
||||
const elements = createElementsFromLayout(layout);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const result = getFirstEmptyPosition(elements, layout[0]!.length, undefined, { width: size[0]!, height: size[1]! });
|
||||
|
||||
expect(result).toEqual({ xOffset: expectedX, yOffset: expectedY });
|
||||
});
|
||||
|
||||
test.each([
|
||||
[[[" ", " "]], [1, 1], 0, 0, 1],
|
||||
[[["a", " "]], [1, 1], 1, 0, 1],
|
||||
[[["a", "a"]], [1, 1], undefined, undefined, 1],
|
||||
[[["a", "a"]], [1, 1], 0, 1, 2],
|
||||
[
|
||||
[
|
||||
["a", "b", " ", " "],
|
||||
["a", "c", " ", "d"],
|
||||
],
|
||||
[2, 2],
|
||||
undefined,
|
||||
undefined,
|
||||
3,
|
||||
],
|
||||
[
|
||||
[
|
||||
["a", "b", " ", " "],
|
||||
["a", "c", " ", "d"],
|
||||
],
|
||||
[2, 2],
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
],
|
||||
[[["a", "b"]], [2, 1], 0, 1, 2],
|
||||
])("should return the first empty position with limited rows", (layout, size, expectedX, expectedY, rowCount) => {
|
||||
const elements = createElementsFromLayout(layout);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const result = getFirstEmptyPosition(elements, layout[0]!.length, rowCount, { width: size[0]!, height: size[1]! });
|
||||
|
||||
expect(result).toEqual(expectedX !== undefined ? { xOffset: expectedX, yOffset: expectedY } : undefined);
|
||||
});
|
||||
});
|
||||
|
||||
const createElementsFromLayout = (layout: string[][]) => {
|
||||
const elements: (Pick<Item, "xOffset" | "yOffset" | "width" | "height"> & { char: string })[] = [];
|
||||
for (let yOffset = 0; yOffset < layout.length; yOffset++) {
|
||||
const row = layout[yOffset];
|
||||
if (!row) continue;
|
||||
for (let xOffset = 0; xOffset < row.length; xOffset++) {
|
||||
const item = row[xOffset];
|
||||
if (item === " " || !item) continue;
|
||||
|
||||
const existing = elements.find((element) => element.char === item);
|
||||
if (existing) {
|
||||
existing.height = yOffset - existing.yOffset + 1;
|
||||
existing.width = xOffset - existing.xOffset + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
elements.push({
|
||||
yOffset,
|
||||
xOffset,
|
||||
width: 1,
|
||||
height: 1,
|
||||
char: item,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { DynamicSection, EmptySection, Item } from "~/app/[locale]/boards/_types";
|
||||
|
||||
export const createEmptySection = (id: string, yOffset: number): EmptySection => ({
|
||||
id,
|
||||
kind: "empty",
|
||||
yOffset,
|
||||
xOffset: 0,
|
||||
items: [],
|
||||
});
|
||||
|
||||
export const createDynamicSection = (section: Omit<Partial<DynamicSection>, "kind">): DynamicSection => ({
|
||||
id: section.id ?? "0",
|
||||
kind: "dynamic",
|
||||
parentSectionId: section.parentSectionId ?? "0",
|
||||
height: section.height ?? 1,
|
||||
width: section.width ?? 1,
|
||||
yOffset: section.yOffset ?? 0,
|
||||
xOffset: section.xOffset ?? 0,
|
||||
items: section.items ?? [],
|
||||
});
|
||||
|
||||
export const createItem = (item: Partial<Item>): Item => ({
|
||||
id: item.id ?? "0",
|
||||
width: item.width ?? 1,
|
||||
height: item.height ?? 1,
|
||||
yOffset: item.yOffset ?? 0,
|
||||
xOffset: item.xOffset ?? 0,
|
||||
kind: item.kind ?? "clock",
|
||||
integrationIds: item.integrationIds ?? [],
|
||||
options: item.options ?? {},
|
||||
advancedOptions: item.advancedOptions ?? { customCssClasses: [] },
|
||||
});
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
||||
|
||||
import type { EmptySection, Item } from "~/app/[locale]/boards/_types";
|
||||
import type { Item } from "~/app/[locale]/boards/_types";
|
||||
import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client";
|
||||
import type { CreateItemInput } from "./actions/create-item";
|
||||
import { createItemCallback } from "./actions/create-item";
|
||||
import type { DuplicateItemInput } from "./actions/duplicate-item";
|
||||
import { duplicateItemCallback } from "./actions/duplicate-item";
|
||||
|
||||
interface MoveAndResizeItem {
|
||||
itemId: string;
|
||||
@@ -42,87 +43,19 @@ interface UpdateItemIntegrations {
|
||||
newIntegrations: string[];
|
||||
}
|
||||
|
||||
interface CreateItem {
|
||||
kind: WidgetKind;
|
||||
}
|
||||
|
||||
interface DuplicateItem {
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
export const useItemActions = () => {
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
|
||||
const createItem = useCallback(
|
||||
({ kind }: CreateItem) => {
|
||||
updateBoard((previous) => {
|
||||
const lastSection = previous.sections
|
||||
.filter((section): section is EmptySection => section.kind === "empty")
|
||||
.sort((sectionA, sectionB) => sectionB.yOffset - sectionA.yOffset)[0];
|
||||
|
||||
if (!lastSection) return previous;
|
||||
|
||||
const widget = {
|
||||
id: createId(),
|
||||
kind,
|
||||
options: {},
|
||||
width: 1,
|
||||
height: 1,
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
customCssClasses: [],
|
||||
},
|
||||
} satisfies Modify<
|
||||
Omit<Item, "yOffset" | "xOffset">,
|
||||
{
|
||||
kind: WidgetKind;
|
||||
}
|
||||
>;
|
||||
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (section.id !== lastSection.id) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.concat(widget as unknown as Item),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
(input: CreateItemInput) => {
|
||||
updateBoard(createItemCallback(input));
|
||||
},
|
||||
[updateBoard],
|
||||
);
|
||||
|
||||
const duplicateItem = useCallback(
|
||||
({ itemId }: DuplicateItem) => {
|
||||
updateBoard((previous) => {
|
||||
const itemToDuplicate = previous.sections
|
||||
.flatMap((section) => section.items)
|
||||
.find((item) => item.id === itemId);
|
||||
|
||||
if (!itemToDuplicate) return previous;
|
||||
|
||||
const newItem = {
|
||||
...itemToDuplicate,
|
||||
id: createId(),
|
||||
yOffset: undefined,
|
||||
xOffset: undefined,
|
||||
} satisfies Modify<Item, { yOffset?: number; xOffset?: number }>;
|
||||
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (!section.items.some((item) => item.id === itemId)) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.concat(newItem as unknown as Item),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
({ itemId }: DuplicateItemInput) => {
|
||||
updateBoard(duplicateItemCallback({ itemId }));
|
||||
},
|
||||
[updateBoard],
|
||||
);
|
||||
|
||||
@@ -79,7 +79,12 @@ const InnerContent = ({ item, ...dimensions }: InnerContentProps) => {
|
||||
>
|
||||
<Throw
|
||||
error={new NoIntegrationSelectedError()}
|
||||
when={widgetSupportsIntegrations && item.integrationIds.length === 0}
|
||||
when={
|
||||
widgetSupportsIntegrations &&
|
||||
item.integrationIds.length === 0 &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
(!("integrationsRequired" in definition) || definition.integrationsRequired !== false)
|
||||
}
|
||||
/>
|
||||
<BoardItemMenu offset={4} item={newItem} />
|
||||
<Comp
|
||||
|
||||
@@ -12,11 +12,7 @@ export const env = createEnv({
|
||||
* Specify your server-side environment variables schema here. This way you can ensure the app isn't
|
||||
* built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
// Comma separated list of docker hostnames that can be used to connect to query the docker endpoints (localhost:2375,host.docker.internal:2375, ...)
|
||||
DOCKER_HOSTNAMES: z.string().optional(),
|
||||
DOCKER_PORTS: z.string().optional(),
|
||||
},
|
||||
server: {},
|
||||
/**
|
||||
* Specify your client-side environment variables schema here.
|
||||
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
|
||||
@@ -30,9 +26,8 @@ export const env = createEnv({
|
||||
runtimeEnv: {
|
||||
PORT: process.env.PORT,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
DOCKER_HOSTNAMES: process.env.DOCKER_HOSTNAMES,
|
||||
DOCKER_PORTS: process.env.DOCKER_PORTS,
|
||||
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
|
||||
},
|
||||
skipValidation: shouldSkipEnvValidation(),
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -38,13 +38,13 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.2.3"
|
||||
"undici": "7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/node": "^22.10.10",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.18.0",
|
||||
"prettier": "^3.4.2",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@types/ws": "^8.5.14",
|
||||
"eslint": "^9.18.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.3"
|
||||
|
||||
+3
-10
@@ -24,10 +24,10 @@
|
||||
<strong>Demo ✨</strong>
|
||||
</a>
|
||||
•
|
||||
<a href="https://homarr.dev/docs/getting-started/installation/">
|
||||
<a href="https://homarr.dev/docs/category/installation-1/">
|
||||
<strong>Install 💻</strong>
|
||||
</a> •
|
||||
<a href="https://translate.homarr.dev/">
|
||||
<a href="https://crowdin.com/project/homarr_labs">
|
||||
<strong>Translations 🈺</strong>
|
||||
</a> •
|
||||
<a href="https://discord.com/invite/aCsmEV5RgA">
|
||||
@@ -35,13 +35,6 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
> [!WARNING]
|
||||
> This is a beta version of Homarr. You can download the old but stable version at https://github.com/ajnart/homarr/. Running this version is at your own risk.
|
||||
|
||||
Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips. With Homarr, you can access and control everything in one location. Homarr seamlessly integrates with the apps you've added, providing you with valuable information and giving you complete control. Installation is a breeze, and Homarr supports a wide range of deployment methods.
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||

|
||||
|
||||
@@ -100,7 +93,7 @@ Homarr has a [built-in collection of widgets and integrations](https://homarr.de
|
||||
Since we are updating Homarr very frequently, we recommend reading our official installation guides:
|
||||
|
||||
<h2>
|
||||
<a href="https://homarr.dev/docs/getting-started/installation/">
|
||||
<a href="https://homarr.dev/docs/category/installation-1/">
|
||||
Please click here for official installation instructions
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
@@ -49,9 +49,9 @@ describe("Onboarding", () => {
|
||||
environment: {
|
||||
AUTH_PROVIDERS: "ldap",
|
||||
AUTH_LDAP_URI: "ldap://host.docker.internal:3890",
|
||||
AUTH_LDAP_BASE: "",
|
||||
AUTH_LDAP_BIND_DN: "",
|
||||
AUTH_LDAP_BIND_PASSWORD: "",
|
||||
AUTH_LDAP_BASE: "not-used",
|
||||
AUTH_LDAP_BIND_DN: "not-used",
|
||||
AUTH_LDAP_BIND_PASSWORD: "not-used",
|
||||
},
|
||||
mounts: {
|
||||
"/appdata": localMountPath,
|
||||
|
||||
+9
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -38,29 +38,29 @@
|
||||
"@semantic-release/github": "^11.0.1",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.3.3",
|
||||
"@turbo/gen": "^2.3.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"@vitest/ui": "^2.1.8",
|
||||
"@vitest/coverage-v8": "^3.0.4",
|
||||
"@vitest/ui": "^3.0.4",
|
||||
"conventional-changelog-conventionalcommits": "^8.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^26.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"semantic-release": "^24.2.1",
|
||||
"testcontainers": "^10.16.0",
|
||||
"turbo": "^2.3.3",
|
||||
"testcontainers": "^10.17.1",
|
||||
"turbo": "^2.3.4",
|
||||
"typescript": "^5.7.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^2.1.8"
|
||||
"vitest": "^3.0.4"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"engines": {
|
||||
"node": ">=22.13.0"
|
||||
"node": ">=22.13.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"allowNonAppliedPatches": true,
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.2.3"
|
||||
"proxmox-api>undici": "7.3.0"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"pretty-print-error": "patches/pretty-print-error.patch"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
@@ -42,9 +43,8 @@
|
||||
"@trpc/client": "next",
|
||||
"@trpc/react-query": "next",
|
||||
"@trpc/server": "next",
|
||||
"dockerode": "4.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"superjson": "2.2.2",
|
||||
@@ -54,7 +54,6 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.34",
|
||||
"eslint": "^9.18.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.3"
|
||||
|
||||
@@ -98,56 +98,59 @@ export const createManyIntegrationMiddleware = <TKind extends IntegrationKind>(
|
||||
action: IntegrationAction,
|
||||
...kinds: AtLeastOneOf<TKind> // Ensure at least one kind is provided
|
||||
) => {
|
||||
return publicProcedure
|
||||
.input(z.object({ integrationIds: z.array(z.string()).min(1) }))
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
const dbIntegrations = await ctx.db.query.integrations.findMany({
|
||||
where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)),
|
||||
with: {
|
||||
secrets: true,
|
||||
items: {
|
||||
return publicProcedure.input(z.object({ integrationIds: z.array(z.string()) })).use(async ({ ctx, input, next }) => {
|
||||
const dbIntegrations =
|
||||
input.integrationIds.length >= 1
|
||||
? await ctx.db.query.integrations.findMany({
|
||||
where: and(inArray(integrations.id, input.integrationIds), inArray(integrations.kind, kinds)),
|
||||
with: {
|
||||
item: {
|
||||
secrets: true,
|
||||
items: {
|
||||
with: {
|
||||
section: {
|
||||
columns: {
|
||||
boardId: true,
|
||||
item: {
|
||||
with: {
|
||||
section: {
|
||||
columns: {
|
||||
boardId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
userPermissions: true,
|
||||
groupPermissions: true,
|
||||
},
|
||||
},
|
||||
userPermissions: true,
|
||||
groupPermissions: true,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const offset = input.integrationIds.length - dbIntegrations.length;
|
||||
if (offset !== 0) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: `${offset} of the specified integrations not found or not of kinds ${kinds.join(",")}: ([${input.integrationIds.join(",")}] compared to [${dbIntegrations.map(({ id, kind }) => `${kind}:${id}`).join(",")}])`,
|
||||
});
|
||||
}
|
||||
|
||||
const offset = input.integrationIds.length - dbIntegrations.length;
|
||||
if (offset !== 0) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: `${offset} of the specified integrations not found or not of kinds ${kinds.join(",")}: ([${input.integrationIds.join(",")}] compared to [${dbIntegrations.map(({ id, kind }) => `${kind}:${id}`).join(",")}])`,
|
||||
});
|
||||
}
|
||||
|
||||
if (dbIntegrations.length >= 1) {
|
||||
await throwIfActionIsNotAllowedAsync(action, ctx.db, dbIntegrations, ctx.session);
|
||||
}
|
||||
|
||||
return next({
|
||||
ctx: {
|
||||
integrations: dbIntegrations.map(
|
||||
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
|
||||
...rest,
|
||||
kind: kind as TKind,
|
||||
decryptedSecrets: secrets.map((secret) => ({
|
||||
...secret,
|
||||
value: decryptSecret(secret.value),
|
||||
})),
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
return next({
|
||||
ctx: {
|
||||
integrations: dbIntegrations.map(
|
||||
({ secrets, kind, items: _ignore1, groupPermissions: _ignore2, userPermissions: _ignore3, ...rest }) => ({
|
||||
...rest,
|
||||
kind: kind as TKind,
|
||||
decryptedSecrets: secrets.map((secret) => ({
|
||||
...secret,
|
||||
value: decryptSecret(secret.value),
|
||||
})),
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import type Docker from "dockerode";
|
||||
import type { Container } from "dockerode";
|
||||
|
||||
import { db, like, or } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
import type { DockerContainerState } from "@homarr/definitions";
|
||||
import { DockerSingleton } from "@homarr/docker";
|
||||
import type { Container, ContainerInfo, ContainerState, Docker, Port } from "@homarr/docker";
|
||||
import { logger } from "@homarr/log";
|
||||
import { createCacheChannel } from "@homarr/redis";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||
import { DockerSingleton } from "./docker-singleton";
|
||||
|
||||
const dockerCache = createCacheChannel<{
|
||||
containers: (Docker.ContainerInfo & { instance: string; iconUrl: string | null })[];
|
||||
containers: (ContainerInfo & { instance: string; iconUrl: string | null })[];
|
||||
}>("docker-containers", 5 * 60 * 1000);
|
||||
|
||||
export const dockerRouter = createTRPCRouter({
|
||||
getContainers: permissionRequiredProcedure.requiresPermission("admin").query(async () => {
|
||||
const result = await dockerCache
|
||||
.consumeAsync(async () => {
|
||||
const dockerInstances = DockerSingleton.getInstance();
|
||||
const dockerInstances = DockerSingleton.getInstances();
|
||||
const containers = await Promise.all(
|
||||
// Return all the containers of all the instances into only one item
|
||||
dockerInstances.map(({ instance, host: key }) =>
|
||||
@@ -33,8 +31,7 @@ export const dockerRouter = createTRPCRouter({
|
||||
),
|
||||
).then((containers) => containers.flat());
|
||||
|
||||
const extractImage = (container: Docker.ContainerInfo) =>
|
||||
container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
|
||||
const extractImage = (container: ContainerInfo) => container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
|
||||
const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
|
||||
const dbIcons =
|
||||
likeQueries.length >= 1
|
||||
@@ -151,7 +148,7 @@ const getContainerOrDefaultAsync = async (instance: Docker, id: string) => {
|
||||
};
|
||||
|
||||
const getContainerOrThrowAsync = async (id: string) => {
|
||||
const dockerInstances = DockerSingleton.getInstance();
|
||||
const dockerInstances = DockerSingleton.getInstances();
|
||||
const containers = await Promise.all(dockerInstances.map(({ instance }) => getContainerOrDefaultAsync(instance, id)));
|
||||
const foundContainer = containers.find((container) => container) ?? null;
|
||||
|
||||
@@ -168,21 +165,21 @@ const getContainerOrThrowAsync = async (id: string) => {
|
||||
interface DockerContainer {
|
||||
name: string;
|
||||
id: string;
|
||||
state: DockerContainerState;
|
||||
state: ContainerState;
|
||||
image: string;
|
||||
ports: Docker.Port[];
|
||||
ports: Port[];
|
||||
iconUrl: string | null;
|
||||
}
|
||||
|
||||
function sanitizeContainers(
|
||||
containers: (Docker.ContainerInfo & { instance: string; iconUrl: string | null })[],
|
||||
containers: (ContainerInfo & { instance: string; iconUrl: string | null })[],
|
||||
): DockerContainer[] {
|
||||
return containers.map((container) => {
|
||||
return {
|
||||
name: container.Names[0]?.split("/")[1] ?? "Unknown",
|
||||
id: container.Id,
|
||||
instance: container.instance,
|
||||
state: container.State as DockerContainerState,
|
||||
state: container.State as ContainerState,
|
||||
image: container.Image,
|
||||
ports: container.Ports,
|
||||
iconUrl: container.iconUrl,
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import Docker from "dockerode";
|
||||
|
||||
interface DockerInstance {
|
||||
host: string;
|
||||
instance: Docker;
|
||||
}
|
||||
|
||||
export class DockerSingleton {
|
||||
private static instances: DockerInstance[];
|
||||
|
||||
private createInstances() {
|
||||
const instances: DockerInstance[] = [];
|
||||
const hostVariable = process.env.DOCKER_HOST;
|
||||
const portVariable = process.env.DOCKER_PORT;
|
||||
if (hostVariable === undefined || portVariable === undefined) {
|
||||
instances.push({ host: "socket", instance: new Docker() });
|
||||
return instances;
|
||||
}
|
||||
const hosts = hostVariable.split(",");
|
||||
const ports = portVariable.split(",");
|
||||
|
||||
if (hosts.length !== ports.length) {
|
||||
throw new Error("The number of hosts and ports must match");
|
||||
}
|
||||
|
||||
hosts.forEach((host, i) => {
|
||||
instances.push({
|
||||
host: `${host}:${ports[i]}`,
|
||||
instance: new Docker({
|
||||
host,
|
||||
port: parseInt(ports[i] ?? "", 10),
|
||||
}),
|
||||
});
|
||||
return instances;
|
||||
});
|
||||
return instances;
|
||||
}
|
||||
|
||||
public static findInstance(key: string): DockerInstance | undefined {
|
||||
return this.instances.find((instance) => instance.host === key);
|
||||
}
|
||||
|
||||
public static getInstance(): DockerInstance[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!DockerSingleton.instances) {
|
||||
DockerSingleton.instances = new DockerSingleton().createInstances();
|
||||
}
|
||||
|
||||
return this.instances;
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,13 @@ export const userRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.userId), "view");
|
||||
// Only allow user to select boards they have access to
|
||||
if (input.homeBoardId) {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.homeBoardId), "view");
|
||||
}
|
||||
if (input.mobileHomeBoardId) {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.mobileHomeBoardId), "view");
|
||||
}
|
||||
|
||||
await ctx.db
|
||||
.update(users)
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
import type { RssFeed } from "@homarr/cron-jobs";
|
||||
import { createItemChannel } from "@homarr/redis";
|
||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { createOneItemMiddleware } from "../../middlewares/item";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const rssFeedRouter = createTRPCRouter({
|
||||
getFeeds: publicProcedure.unstable_concat(createOneItemMiddleware("rssFeed")).query(async ({ input }) => {
|
||||
const channel = createItemChannel<RssFeed[]>(input.itemId);
|
||||
return await channel.getAsync();
|
||||
}),
|
||||
getFeeds: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
urls: z.array(z.string()),
|
||||
maximumAmountPosts: z.number(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const rssFeeds = await Promise.all(
|
||||
input.urls.map(async (url) => {
|
||||
const innerHandler = rssFeedsRequestHandler.handler({
|
||||
url,
|
||||
count: input.maximumAmountPosts,
|
||||
});
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return rssFeeds
|
||||
.flatMap((rssFeed) => rssFeed.data.entries)
|
||||
.slice(0, input.maximumAmountPosts)
|
||||
.sort((entryA, entryB) => {
|
||||
return entryA.published && entryB.published
|
||||
? new Date(entryB.published).getTime() - new Date(entryA.published).getTime()
|
||||
: 0;
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -2,9 +2,11 @@ import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapte
|
||||
import { cookies } from "next/headers";
|
||||
import NextAuth from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { formatError } from "pretty-print-error";
|
||||
|
||||
import { db } from "@homarr/db";
|
||||
import type { SupportedAuthProvider } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createAdapter } from "./adapter";
|
||||
import { createSessionCallback } from "./callbacks";
|
||||
@@ -26,15 +28,16 @@ export const createConfiguration = (
|
||||
const adapter = createAdapter(db, provider);
|
||||
return NextAuth({
|
||||
logger: {
|
||||
error: (code, ...message) => {
|
||||
error: (error) => {
|
||||
// Remove the big error message for failed login attempts
|
||||
// as it is not useful for the user.
|
||||
if (code.name === "CredentialsSignin") {
|
||||
console.warn("The login attempt of a user was not successful.");
|
||||
if (error.name === "CredentialsSignin") {
|
||||
logger.warn("The login attempt of a user was not successful.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(code, ...message);
|
||||
logger.error(formatError(error));
|
||||
logger.error(formatError(error.cause));
|
||||
},
|
||||
},
|
||||
trustHost: true,
|
||||
|
||||
@@ -86,4 +86,5 @@ export const env = createEnv({
|
||||
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: process.env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE,
|
||||
},
|
||||
skipValidation,
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -34,8 +34,9 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "7.3.1",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"next-auth": "5.0.0-beta.25",
|
||||
"pretty-print-error": "^1.1.2",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CredentialsSignin } from "@auth/core/errors";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import type { Database, InferInsertModel } from "@homarr/db";
|
||||
import { and, createId, eq } from "@homarr/db";
|
||||
import { users } from "@homarr/db/schema";
|
||||
@@ -21,8 +22,8 @@ export const authorizeWithLdapCredentialsAsync = async (
|
||||
distinguishedName: env.AUTH_LDAP_BIND_DN,
|
||||
password: env.AUTH_LDAP_BIND_PASSWORD,
|
||||
})
|
||||
.catch(() => {
|
||||
logger.error("Failed to connect to LDAP server");
|
||||
.catch((error) => {
|
||||
logger.error(`Failed to connect to LDAP server ${extractErrorMessage(error)}`);
|
||||
throw new CredentialsSignin();
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"undici": "7.2.3"
|
||||
"undici": "7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -26,4 +26,5 @@ export const env = createEnv({
|
||||
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
|
||||
},
|
||||
skipValidation: shouldSkipEnvValidation(),
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -29,10 +29,10 @@
|
||||
"dependencies": {
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"undici": "7.2.3",
|
||||
"undici": "7.3.0",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@extractus/feed-extractor": "^7.1.3",
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
|
||||
@@ -11,7 +11,6 @@ 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";
|
||||
import { sessionCleanupJob } from "./jobs/session-cleanup";
|
||||
import { updateCheckerJob } from "./jobs/update-checker";
|
||||
@@ -38,4 +37,3 @@ export const jobGroup = createCronJobGroup({
|
||||
});
|
||||
|
||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||
export type { RssFeed };
|
||||
@@ -1,139 +1,36 @@
|
||||
import type { FeedData, FeedEntry } from "@extractus/feed-extractor";
|
||||
import { extract } from "@extractus/feed-extractor";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import { createItemChannel } from "@homarr/redis";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
// This import is done that way to avoid circular dependencies.
|
||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../widgets";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const rssFeedsJob = createCronJob("rssFeeds", EVERY_5_MINUTES).withCallback(async () => {
|
||||
const itemsForIntegration = await db.query.items.findMany({
|
||||
const rssItems = await db.query.items.findMany({
|
||||
where: eq(items.kind, "rssFeed"),
|
||||
});
|
||||
|
||||
for (const item of itemsForIntegration) {
|
||||
const options = SuperJSON.parse<WidgetComponentProps<"rssFeed">["options"]>(item.options);
|
||||
const itemOptions = rssItems.map((item) => SuperJSON.parse<WidgetComponentProps<"rssFeed">["options"]>(item.options));
|
||||
|
||||
const feeds = await Promise.all(
|
||||
options.feedUrls.map(async (feedUrl) => ({
|
||||
feedUrl,
|
||||
feed: (await extract(feedUrl, {
|
||||
getExtraEntryFields: (feedEntry) => {
|
||||
const media = attemptGetImageFromEntry(feedUrl, feedEntry);
|
||||
if (!media) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
enclosure: media,
|
||||
};
|
||||
},
|
||||
})) as ExtendedFeedData,
|
||||
})),
|
||||
);
|
||||
|
||||
const channel = createItemChannel<RssFeed[]>(item.id);
|
||||
await channel.publishAndUpdateLastStateAsync(feeds);
|
||||
for (const option of itemOptions) {
|
||||
const maxAmountPosts = typeof option.maximumAmountPosts === "number" ? option.maximumAmountPosts : 100;
|
||||
for (const url of option.feedUrls) {
|
||||
try {
|
||||
const innerHandler = rssFeedsRequestHandler.handler({
|
||||
url,
|
||||
count: maxAmountPosts,
|
||||
});
|
||||
await innerHandler.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Failed to update RSS feed", { url, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const attemptGetImageFromEntry = (feedUrl: string, entry: object) => {
|
||||
const media = getFirstMediaProperty(entry);
|
||||
if (media !== null) {
|
||||
return media;
|
||||
}
|
||||
return getImageFromStringAsFallback(feedUrl, JSON.stringify(entry));
|
||||
};
|
||||
|
||||
const getImageFromStringAsFallback = (feedUrl: string, content: string) => {
|
||||
const regex = /https?:\/\/\S+?\.(jpg|jpeg|png|gif|bmp|svg|webp|tiff)/i;
|
||||
const result = regex.exec(content);
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.debug(
|
||||
`Falling back to regex image search for '${feedUrl}'. Found ${result.length} matches in content: ${content}`,
|
||||
);
|
||||
return result[0];
|
||||
};
|
||||
|
||||
const mediaProperties = [
|
||||
{
|
||||
path: ["enclosure", "@_url"],
|
||||
},
|
||||
{
|
||||
path: ["media:content", "@_url"],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The RSS and Atom standards are poorly adhered to in most of the web.
|
||||
* We want to show pretty background images on the posts and therefore need to extract
|
||||
* the enclosure (aka. media images). This function uses the dynamic properties defined above
|
||||
* to search through the possible paths and detect valid image URLs.
|
||||
* @param feedObject The object to scan for.
|
||||
* @returns the value of the first path that is found within the object
|
||||
*/
|
||||
const getFirstMediaProperty = (feedObject: object) => {
|
||||
for (const mediaProperty of mediaProperties) {
|
||||
let propertyIndex = 0;
|
||||
let objectAtPath: object = feedObject;
|
||||
while (propertyIndex < mediaProperty.path.length) {
|
||||
const key = mediaProperty.path[propertyIndex];
|
||||
if (key === undefined) {
|
||||
break;
|
||||
}
|
||||
const propertyEntries = Object.entries(objectAtPath);
|
||||
const propertyEntry = propertyEntries.find(([entryKey]) => entryKey === key);
|
||||
if (!propertyEntry) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const [_, propertyEntryValue] = propertyEntry;
|
||||
objectAtPath = propertyEntryValue as object;
|
||||
propertyIndex++;
|
||||
}
|
||||
|
||||
const validationResult = z.string().url().safeParse(objectAtPath);
|
||||
if (!validationResult.success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug(`Found an image in the feed entry: ${validationResult.data}`);
|
||||
return validationResult.data;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* We extend the feed with custom properties.
|
||||
* This interface adds properties on top of the default ones.
|
||||
*/
|
||||
interface ExtendedFeedEntry extends FeedEntry {
|
||||
enclosure?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* We extend the feed with custom properties.
|
||||
* This interface omits the default entries with our custom definition.
|
||||
*/
|
||||
type ExtendedFeedData = Modify<
|
||||
FeedData,
|
||||
{
|
||||
entries?: ExtendedFeedEntry;
|
||||
}
|
||||
>;
|
||||
|
||||
export interface RssFeed {
|
||||
feedUrl: string;
|
||||
feed: ExtendedFeedData;
|
||||
}
|
||||
+6
-1
@@ -27,7 +27,11 @@ export const env = createEnv({
|
||||
.default(drivers.betterSqlite3),
|
||||
...(urlRequired
|
||||
? {
|
||||
DB_URL: z.string(),
|
||||
DB_URL:
|
||||
// Fallback to the default sqlite file path in production
|
||||
process.env.NODE_ENV === "production" && isDriver("better-sqlite3")
|
||||
? z.string().default("/appdata/db/db.sqlite")
|
||||
: z.string().nonempty(),
|
||||
}
|
||||
: {}),
|
||||
...(hostRequired
|
||||
@@ -58,4 +62,5 @@ export const env = createEnv({
|
||||
DB_PORT: process.env.DB_PORT,
|
||||
},
|
||||
skipValidation: shouldSkipEnvValidation(),
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -44,8 +44,8 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@testcontainers/mysql": "^10.16.0",
|
||||
"better-sqlite3": "^11.8.0",
|
||||
"@testcontainers/mysql": "^10.17.1",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.38.4",
|
||||
|
||||
@@ -10,6 +10,7 @@ export type HomarrDocumentationPath =
|
||||
| "/blog/2024/09/23/version-1.0"
|
||||
| "/blog/2024/12/17/open-beta-1.0"
|
||||
| "/blog/2024/12/31/migrate-secret-enryption-key"
|
||||
| "/blog/2025/01/19/migration-guide-1.0"
|
||||
| "/blog/archive"
|
||||
| "/blog/authors"
|
||||
| "/blog/authors/ajnart"
|
||||
@@ -51,6 +52,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/bookmark"
|
||||
| "/docs/tags/bookmarks"
|
||||
| "/docs/tags/caddy"
|
||||
| "/docs/tags/certificates"
|
||||
| "/docs/tags/checklist"
|
||||
| "/docs/tags/code"
|
||||
| "/docs/tags/community"
|
||||
@@ -113,6 +115,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/search"
|
||||
| "/docs/tags/search-engines"
|
||||
| "/docs/tags/security"
|
||||
| "/docs/tags/self-signed"
|
||||
| "/docs/tags/seo"
|
||||
| "/docs/tags/server"
|
||||
| "/docs/tags/settings"
|
||||
@@ -163,10 +166,10 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/getting-started/installation/home-assistant"
|
||||
| "/docs/getting-started/installation/portainer"
|
||||
| "/docs/getting-started/installation/qnap"
|
||||
| "/docs/getting-started/installation/railway"
|
||||
| "/docs/getting-started/installation/saltbox"
|
||||
| "/docs/getting-started/installation/source"
|
||||
| "/docs/getting-started/installation/synology"
|
||||
| "/docs/getting-started/installation/truenas"
|
||||
| "/docs/getting-started/installation/unraid"
|
||||
| "/docs/integrations/containers"
|
||||
| "/docs/integrations/dns"
|
||||
@@ -179,6 +182,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/management/api"
|
||||
| "/docs/management/apps"
|
||||
| "/docs/management/boards"
|
||||
| "/docs/management/certificates"
|
||||
| "/docs/management/integrations"
|
||||
| "/docs/management/media"
|
||||
| "/docs/management/search-engines"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@homarr/docker",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./env": "./src/env.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"dockerode": "^4.0.4"
|
||||
},
|
||||
"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.34",
|
||||
"eslint": "^9.18.0",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
// Comma separated list of docker hostnames that can be used to connect to query the docker endpoints (localhost:2375,host.docker.internal:2375, ...)
|
||||
DOCKER_HOSTNAMES: z.string().optional(),
|
||||
DOCKER_PORTS: z.string().optional(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
DOCKER_HOSTNAMES: process.env.DOCKER_HOSTNAMES,
|
||||
DOCKER_PORTS: process.env.DOCKER_PORTS,
|
||||
},
|
||||
skipValidation: shouldSkipEnvValidation(),
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import type Docker from "dockerode";
|
||||
|
||||
export type { DockerInstance } from "./singleton";
|
||||
export { DockerSingleton } from "./singleton";
|
||||
export type { ContainerInfo, Container, Port } from "dockerode";
|
||||
export type { Docker };
|
||||
|
||||
export const containerStates = ["created", "running", "paused", "restarting", "exited", "removing", "dead"] as const;
|
||||
|
||||
export type ContainerState = (typeof containerStates)[number];
|
||||
@@ -0,0 +1,53 @@
|
||||
import Docker from "dockerode";
|
||||
|
||||
import { env } from "./env";
|
||||
|
||||
export interface DockerInstance {
|
||||
host: string;
|
||||
instance: Docker;
|
||||
}
|
||||
|
||||
export class DockerSingleton {
|
||||
private static instances: DockerInstance[] | null = null;
|
||||
|
||||
private createInstances() {
|
||||
const hostVariable = env.DOCKER_HOSTNAMES;
|
||||
const portVariable = env.DOCKER_PORTS;
|
||||
if (hostVariable === undefined || portVariable === undefined) {
|
||||
return [{ host: "socket", instance: new Docker() }];
|
||||
}
|
||||
const hostnames = hostVariable.split(",");
|
||||
const ports = portVariable.split(",");
|
||||
|
||||
if (hostnames.length !== ports.length) {
|
||||
throw new Error("The number of hosts and ports must match");
|
||||
}
|
||||
|
||||
return hostnames.map((host, i) => {
|
||||
// Check above ensures that ports[i] is not undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const port = ports[i]!;
|
||||
|
||||
return {
|
||||
host: `${host}:${port}`,
|
||||
instance: new Docker({
|
||||
host,
|
||||
port: parseInt(port, 10),
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public static findInstance(host: string): DockerInstance | undefined {
|
||||
return this.instances?.find((instance) => instance.host === host);
|
||||
}
|
||||
|
||||
public static getInstances(): DockerInstance[] {
|
||||
if (this.instances) {
|
||||
return this.instances;
|
||||
}
|
||||
|
||||
this.instances = new DockerSingleton().createInstances();
|
||||
return this.instances;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^7.16.0"
|
||||
"@mantine/form": "^7.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"proxmox-api": "1.1.1",
|
||||
"undici": "7.2.3",
|
||||
"undici": "7.3.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -96,7 +96,7 @@ export class NzbGetIntegration extends DownloadClientIntegration {
|
||||
): Promise<ReturnType<NzbGetClient[CallType]>> {
|
||||
const username = this.getSecretValue("username");
|
||||
const password = this.getSecretValue("password");
|
||||
const url = this.url(`/${username}:${password}/jsonrpc`);
|
||||
const url = this.url(`/${encodeURIComponent(username)}:${encodeURIComponent(password)}/jsonrpc`);
|
||||
const body = JSON.stringify({ method, params });
|
||||
return await fetchWithTrustedCertificatesAsync(url, { method: "POST", body })
|
||||
.then(async (response) => {
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.16.0",
|
||||
"@tabler/icons-react": "^3.28.1",
|
||||
"@mantine/core": "^7.16.1",
|
||||
"@tabler/icons-react": "^3.29.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.16.0",
|
||||
"@mantine/hooks": "^7.16.0",
|
||||
"@mantine/core": "^7.16.1",
|
||||
"@mantine/hooks": "^7.16.1",
|
||||
"react": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^7.16.0",
|
||||
"@tabler/icons-react": "^3.28.1"
|
||||
"@mantine/notifications": "^7.16.1",
|
||||
"@tabler/icons-react": "^3.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.16.0",
|
||||
"@mantine/hooks": "^7.16.0",
|
||||
"@mantine/core": "^7.16.1",
|
||||
"@mantine/hooks": "^7.16.1",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"superjson": "2.2.2",
|
||||
|
||||
@@ -25,7 +25,7 @@ const mapping: Record<OldmarrIntegrationType, IntegrationKind | null> = {
|
||||
overseerr: "overseerr",
|
||||
pihole: "piHole",
|
||||
prowlarr: "prowlarr",
|
||||
proxmox: null,
|
||||
proxmox: "proxmox",
|
||||
qBittorrent: "qBittorrent",
|
||||
radarr: "radarr",
|
||||
readarr: "readarr",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const mapApp = (
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
): InferInsertModel<typeof items> => {
|
||||
): InferInsertModel<typeof items> | null => {
|
||||
if (app.area.type === "sidebar") throw new Error("Mapping app in sidebar is not supported");
|
||||
|
||||
const shapeForSize = app.shape[boardSize];
|
||||
@@ -25,7 +25,8 @@ export const mapApp = (
|
||||
|
||||
const sectionId = sectionMap.get(app.area.properties.id)?.id;
|
||||
if (!sectionId) {
|
||||
throw new Error(`Failed to find section for app appId='${app.id}' sectionId='${app.area.properties.id}'`);
|
||||
logger.warn(`Failed to find section for app appId='${app.id}' sectionId='${app.area.properties.id}'. Removing app`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -69,9 +70,10 @@ export const mapWidget = (
|
||||
|
||||
const sectionId = sectionMap.get(widget.area.properties.id)?.id;
|
||||
if (!sectionId) {
|
||||
throw new Error(
|
||||
`Failed to find section for widget widgetId='${widget.id}' sectionId='${widget.area.properties.id}'`,
|
||||
logger.warn(
|
||||
`Failed to find section for widget widgetId='${widget.id}' sectionId='${widget.area.properties.id}'. Removing widget`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,5 +10,5 @@ export const prepareItems = (
|
||||
) =>
|
||||
widgets
|
||||
.map((widget) => mapWidget(widget, boardSize, appsMap, sectionMap))
|
||||
.filter((widget) => widget !== null)
|
||||
.concat(apps.map((app) => mapApp(app, boardSize, appsMap, sectionMap)));
|
||||
.concat(apps.map((app) => mapApp(app, boardSize, appsMap, sectionMap)))
|
||||
.filter((widget) => widget !== null);
|
||||
@@ -2,8 +2,8 @@ import { objectEntries } from "@homarr/common";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../widgets/src/definition";
|
||||
import { mapKind } from "./definitions";
|
||||
import type { InversedWidgetMapping, OldmarrWidgetDefinitions, WidgetMapping } from "./definitions";
|
||||
import { mapKind } from "./definitions";
|
||||
|
||||
// This type enforces, that for all widget mappings there is a corresponding option mapping,
|
||||
// each option of newmarr can be mapped from the value of the oldmarr options
|
||||
@@ -38,6 +38,9 @@ const optionMapping: OptionMapping = {
|
||||
|
||||
return mappedLayouts[oldOptions.layout];
|
||||
},
|
||||
hideIcon: (oldOptions) => oldOptions.items.some((item) => item.hideIcon),
|
||||
hideHostname: (oldOptions) => oldOptions.items.some((item) => item.hideHostname),
|
||||
openNewTab: (oldOptions) => oldOptions.items.some((item) => item.openNewTab),
|
||||
},
|
||||
calendar: {
|
||||
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@extractus/feed-extractor": "7.1.3",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { FeedData, FeedEntry } from "@extractus/feed-extractor";
|
||||
import { extract } from "@extractus/feed-extractor";
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "rssFeedList",
|
||||
widgetKind: "rssFeed",
|
||||
async requestAsync(input: { url: string; count: number }) {
|
||||
const result = (await extract(input.url, {
|
||||
getExtraEntryFields: (feedEntry) => {
|
||||
const media = attemptGetImageFromEntry(input.url, feedEntry);
|
||||
if (!media) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
enclosure: media,
|
||||
};
|
||||
},
|
||||
})) as ExtendedFeedData;
|
||||
|
||||
return {
|
||||
...result,
|
||||
entries: result.entries?.slice(0, input.count) ?? [],
|
||||
};
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
});
|
||||
|
||||
const attemptGetImageFromEntry = (feedUrl: string, entry: object) => {
|
||||
const media = getFirstMediaProperty(entry);
|
||||
if (media !== null) {
|
||||
return media;
|
||||
}
|
||||
return getImageFromStringAsFallback(feedUrl, JSON.stringify(entry));
|
||||
};
|
||||
|
||||
const getImageFromStringAsFallback = (feedUrl: string, content: string) => {
|
||||
const regex = /https?:\/\/\S+?\.(jpg|jpeg|png|gif|bmp|svg|webp|tiff)/i;
|
||||
const result = regex.exec(content);
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.debug(
|
||||
`Falling back to regex image search for '${feedUrl}'. Found ${result.length} matches in content: ${content}`,
|
||||
);
|
||||
return result[0];
|
||||
};
|
||||
|
||||
const mediaProperties = [
|
||||
{
|
||||
path: ["enclosure", "@_url"],
|
||||
},
|
||||
{
|
||||
path: ["media:content", "@_url"],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The RSS and Atom standards are poorly adhered to in most of the web.
|
||||
* We want to show pretty background images on the posts and therefore need to extract
|
||||
* the enclosure (aka. media images). This function uses the dynamic properties defined above
|
||||
* to search through the possible paths and detect valid image URLs.
|
||||
* @param feedObject The object to scan for.
|
||||
* @returns the value of the first path that is found within the object
|
||||
*/
|
||||
const getFirstMediaProperty = (feedObject: object) => {
|
||||
for (const mediaProperty of mediaProperties) {
|
||||
let propertyIndex = 0;
|
||||
let objectAtPath: object = feedObject;
|
||||
while (propertyIndex < mediaProperty.path.length) {
|
||||
const key = mediaProperty.path[propertyIndex];
|
||||
if (key === undefined) {
|
||||
break;
|
||||
}
|
||||
const propertyEntries = Object.entries(objectAtPath);
|
||||
const propertyEntry = propertyEntries.find(([entryKey]) => entryKey === key);
|
||||
if (!propertyEntry) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const [_, propertyEntryValue] = propertyEntry;
|
||||
objectAtPath = propertyEntryValue as object;
|
||||
propertyIndex++;
|
||||
}
|
||||
|
||||
const validationResult = z.string().url().safeParse(objectAtPath);
|
||||
if (!validationResult.success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug(`Found an image in the feed entry: ${validationResult.data}`);
|
||||
return validationResult.data;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* We extend the feed with custom properties.
|
||||
* This interface adds properties on top of the default ones.
|
||||
*/
|
||||
interface ExtendedFeedEntry extends FeedEntry {
|
||||
enclosure?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* We extend the feed with custom properties.
|
||||
* This interface omits the default entries with our custom definition.
|
||||
*/
|
||||
type ExtendedFeedData = Modify<
|
||||
FeedData,
|
||||
{
|
||||
entries?: ExtendedFeedEntry[];
|
||||
}
|
||||
>;
|
||||
|
||||
export interface RssFeed {
|
||||
feedUrl: string;
|
||||
feed: ExtendedFeedData;
|
||||
}
|
||||
@@ -32,12 +32,12 @@
|
||||
"@homarr/modals-collection": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.16.0",
|
||||
"@mantine/hooks": "^7.16.0",
|
||||
"@mantine/spotlight": "^7.16.0",
|
||||
"@tabler/icons-react": "^3.28.1",
|
||||
"jotai": "^2.11.0",
|
||||
"next": "15.1.4",
|
||||
"@mantine/core": "^7.16.1",
|
||||
"@mantine/hooks": "^7.16.1",
|
||||
"@mantine/spotlight": "^7.16.1",
|
||||
"@tabler/icons-react": "^3.29.0",
|
||||
"jotai": "^2.11.1",
|
||||
"next": "15.1.6",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
|
||||
@@ -61,7 +61,7 @@ const helpMode = {
|
||||
{
|
||||
label: t("submitIssue.label"),
|
||||
icon: IconBrandGithub,
|
||||
href: "https://github.com/ajnart/homarr/issues/new/choose",
|
||||
href: "https://github.com/homarr-labs/homarr/issues/new/choose",
|
||||
},
|
||||
{
|
||||
label: t("discord.label"),
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.8",
|
||||
"next": "15.1.4",
|
||||
"next": "15.1.6",
|
||||
"next-intl": "3.26.3",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "密码",
|
||||
"newLabel": "新密码"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "密鑰 ID",
|
||||
"newLabel": "新密鑰 ID"
|
||||
},
|
||||
"realm": {
|
||||
"label": "領域",
|
||||
"newLabel": "新領域"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "复制 URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -897,7 +908,7 @@
|
||||
"passwordRequirements": "密码不符合要求",
|
||||
"boardAlreadyExists": "具有此名称的面板已存在",
|
||||
"invalidFileType": "无效的文件类型,例如 {expected}",
|
||||
"invalidFileName": "",
|
||||
"invalidFileName": "無效的檔案名稱",
|
||||
"fileTooLarge": "文件太大,最大大小为 {maxSize}",
|
||||
"invalidConfiguration": "无效配置",
|
||||
"groupNameTaken": "用户组名称已存在"
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "书签",
|
||||
"add": "添加书签"
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "显示文件系统信息"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": "預設頁面"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "部分指示需求"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "获取健康状态失败"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "處理器",
|
||||
"memory": "記憶體"
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": "節點"
|
||||
},
|
||||
"qemu": {
|
||||
"name": "虛擬機"
|
||||
},
|
||||
"lxc": {
|
||||
"name": "LXCs"
|
||||
},
|
||||
"storage": {
|
||||
"name": "儲存裝置"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "節點",
|
||||
"vmId": "虛擬機 ID",
|
||||
"plugin": "插件"
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "核心",
|
||||
"memory": "記憶體",
|
||||
"storage": "儲存裝置",
|
||||
"uptime": "運行時間",
|
||||
"haState": "HA 状态",
|
||||
"storageType": {
|
||||
"local": "本地存储",
|
||||
"shared": "共享存储"
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "名称",
|
||||
"cpu": "处理器",
|
||||
"memory": "内存",
|
||||
"node": "节点"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -2102,7 +2174,7 @@
|
||||
"docker": "Docker",
|
||||
"logs": "日志",
|
||||
"api": "API",
|
||||
"certificates": "",
|
||||
"certificates": "证书",
|
||||
"tasks": "任务"
|
||||
}
|
||||
},
|
||||
@@ -2710,7 +2782,7 @@
|
||||
"label": "日志"
|
||||
},
|
||||
"certificates": {
|
||||
"label": ""
|
||||
"label": "证书"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -3110,39 +3182,39 @@
|
||||
"certificate": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "可信证书",
|
||||
"description": "Homarr 用于从集成请求数据。",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "还没有证书"
|
||||
},
|
||||
"expires": ""
|
||||
"expires": "到期时间 {when}"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "添加证书",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "已添加证书",
|
||||
"message": "已成功添加证书"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "添加证书失败",
|
||||
"message": "无法添加证书"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "删除证书",
|
||||
"confirm": "您确定要删除证书吗?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "证书已删除",
|
||||
"message": "证书删除成功"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "证书未删除",
|
||||
"message": "无法删除证书"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Heslo",
|
||||
"newLabel": "Nové heslo"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Zkopírovat URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Zobrazit informace o souborovém systému"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Passwort",
|
||||
"newLabel": "Neues Passwort"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "URL in Zwischenablage kopieren"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Lesezeichen",
|
||||
"add": "Lesezeichen hinzufügen"
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Dateisystem Info anzeigen"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "Fehler beim Abrufen des Gesundheitsstatus"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Κωδικός",
|
||||
"newLabel": "Νέος κωδικός"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Εμφάνιση Πληροφοριών Συστήματος Αρχείων"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -785,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Copy URL"
|
||||
},
|
||||
"open": {
|
||||
"label": "Open media"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1064,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": "Hide icons"
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": "Hide hostnames"
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": "Open in new tab"
|
||||
},
|
||||
"items": {
|
||||
"label": "Bookmarks",
|
||||
"add": "Add bookmark"
|
||||
@@ -1407,6 +1419,10 @@
|
||||
"healthMonitoring": {
|
||||
"name": "System Health Monitoring",
|
||||
"description": "Displays information showing the health and status of your system(s).",
|
||||
"tab": {
|
||||
"system": "System",
|
||||
"cluster": "Cluster"
|
||||
},
|
||||
"option": {
|
||||
"fahrenheit": {
|
||||
"label": "CPU Temp in Fahrenheit"
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Contraseña",
|
||||
"newLabel": "Nueva contraseña"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Mostrar información del sistema de archivos"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": ""
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Mot de passe",
|
||||
"newLabel": "Nouveau mot de passe"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Copier l'URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Afficher les infos sur le système de fichiers"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "סיסמה",
|
||||
"newLabel": "סיסמה חדשה"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "מזהה אסימון",
|
||||
"newLabel": "מזהה אסימון חדש"
|
||||
},
|
||||
"realm": {
|
||||
"label": "תחום",
|
||||
"newLabel": "תחום חדש"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "העתק קישור"
|
||||
},
|
||||
"open": {
|
||||
"label": "פתיחת מדיה"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -897,7 +908,7 @@
|
||||
"passwordRequirements": "הסיסמה אינה עומדת בדרישות",
|
||||
"boardAlreadyExists": "לוח בשם זה כבר קיים",
|
||||
"invalidFileType": "סוג קובץ לא חוקי, צפוי {expected}",
|
||||
"invalidFileName": "",
|
||||
"invalidFileName": "שם קובץ לא חוקי",
|
||||
"fileTooLarge": "הקובץ גדול מדי, הגודל המרבי הוא {maxSize}",
|
||||
"invalidConfiguration": "תצורה לא חוקית",
|
||||
"groupNameTaken": "שם הקבוצה כבר תפוס"
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "סימניות",
|
||||
"add": "הוסף סימניה"
|
||||
@@ -1180,7 +1200,7 @@
|
||||
"label": "כתובת השרת"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
"label": "שרת Bedrock"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "הצג מידע על מערכת הקבצים"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": "כרטיסיית ברירת מחדל"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "דרישת מציין מקטע"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "נכשל בעדכון סטטוס בריאות המערכת"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "מעבד",
|
||||
"memory": "זכרון פנימי"
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": "צמתים"
|
||||
},
|
||||
"qemu": {
|
||||
"name": "מכונות וירטואליות"
|
||||
},
|
||||
"lxc": {
|
||||
"name": "קונטיינרים של לינוקס"
|
||||
},
|
||||
"storage": {
|
||||
"name": "אחסון"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "צומת",
|
||||
"vmId": "מזהה מכונה ורטואלית",
|
||||
"plugin": "תוסף"
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "ליבות",
|
||||
"memory": "זיכרון",
|
||||
"storage": "אחסון",
|
||||
"uptime": "זמן ריצה",
|
||||
"haState": "מצב זמינות גבוהה",
|
||||
"storageType": {
|
||||
"local": "אחסון מקומי",
|
||||
"shared": "אחסון משותף"
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "שם",
|
||||
"cpu": "מעבד",
|
||||
"memory": "זכרון פנימי",
|
||||
"node": "צומת"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -2102,7 +2174,7 @@
|
||||
"docker": "דוקר",
|
||||
"logs": "קובצי לוג",
|
||||
"api": "ממשק API",
|
||||
"certificates": "",
|
||||
"certificates": "אישורי אבטחה",
|
||||
"tasks": "משימות"
|
||||
}
|
||||
},
|
||||
@@ -2156,10 +2228,10 @@
|
||||
}
|
||||
},
|
||||
"setMobileHomeBoard": {
|
||||
"label": "",
|
||||
"label": "הגדרה כלוח שלך בנייד",
|
||||
"badge": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"label": "מכשיר נייד",
|
||||
"tooltip": "לוח זה יופיע כלוח הנייד שלך"
|
||||
}
|
||||
},
|
||||
"duplicate": {
|
||||
@@ -2199,10 +2271,10 @@
|
||||
"item": {
|
||||
"language": "שפה ואיזור",
|
||||
"board": {
|
||||
"title": "",
|
||||
"title": "לוח בית",
|
||||
"type": {
|
||||
"general": "",
|
||||
"mobile": ""
|
||||
"general": "כללי",
|
||||
"mobile": "מכשיר נייד"
|
||||
}
|
||||
},
|
||||
"defaultSearchEngine": "מנוע חיפוש ברירת מחדל",
|
||||
@@ -2364,7 +2436,7 @@
|
||||
"title": "לוחות",
|
||||
"homeBoard": {
|
||||
"label": "לוח בית גלובלי",
|
||||
"mobileLabel": "",
|
||||
"mobileLabel": "לוח גלובלי למכשיר נייד",
|
||||
"description": "רק לוחות ציבוריים זמינים לבחירה"
|
||||
}
|
||||
},
|
||||
@@ -2442,10 +2514,10 @@
|
||||
"label": "ניטור בריאות המערכת"
|
||||
},
|
||||
"dnsHole": {
|
||||
"label": ""
|
||||
"label": "נתוני חור DNS"
|
||||
},
|
||||
"sessionCleanup": {
|
||||
"label": ""
|
||||
"label": "ניקוי סשן"
|
||||
},
|
||||
"updateChecker": {
|
||||
"label": "בודק עדכונים"
|
||||
@@ -2459,8 +2531,8 @@
|
||||
"title": "ממשק API",
|
||||
"modal": {
|
||||
"createApiToken": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "נוצר אסימון API",
|
||||
"description": "האסימון API נוצר. היזהר, אסימון זה מוצפן בבסיס הנתונים ולא יישלח אליך שוב. אם תאבד את האסימון הזה, לא תוכל לשחזר אותו בשנית.",
|
||||
"button": "העתקה וסגירה"
|
||||
}
|
||||
},
|
||||
@@ -2486,19 +2558,19 @@
|
||||
},
|
||||
"about": {
|
||||
"version": "גרסה {version}",
|
||||
"text": "",
|
||||
"text": "Homarr הוא פרויקט קוד פתוח מונחה קהילה שמתוחזק על ידי מתנדבים. בזכות האנשים האלה, Homarr הוא פרויקט שצומח מאז 2021. הצוות שלנו עובד מרחוק ממדינות שונות בזמן הפנוי שלהם, ללא תמורה.",
|
||||
"accordion": {
|
||||
"contributors": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "תורמים",
|
||||
"subtitle": "{count} מתחזקי קוד & Homarr"
|
||||
},
|
||||
"translators": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "מתרגמים",
|
||||
"subtitle": "{count} תורמים בתרגום לשפות שונות"
|
||||
},
|
||||
"libraries": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "ספריות",
|
||||
"subtitle": "{count} בשימוש בקוד של Homarr"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2507,9 +2579,9 @@
|
||||
"docker": {
|
||||
"title": "מיכלים",
|
||||
"table": {
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": ""
|
||||
"updated": "עודכן ב- {when}",
|
||||
"search": "חפש {count} קונטיינרים",
|
||||
"selected": "{selectCount} מ- {totalCount} קונטיינרים נבחרו"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -2522,9 +2594,9 @@
|
||||
"running": "פועל",
|
||||
"paused": "מושהה",
|
||||
"restarting": "מפעיל מחדש",
|
||||
"exited": "",
|
||||
"exited": "נסגר",
|
||||
"removing": "מסיר",
|
||||
"dead": ""
|
||||
"dead": "מת"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
@@ -2539,12 +2611,12 @@
|
||||
"label": "התחל",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "קונטיינרים הופעלו",
|
||||
"message": "הקונטיינרים הופעלו בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים לא הופעלו",
|
||||
"message": "לא היה ניתן להפעיל את הקונטיינרים"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2552,12 +2624,12 @@
|
||||
"label": "עצור",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים נעצרו",
|
||||
"message": "הקונטיינרים נעצרו בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים לא נעצרו",
|
||||
"message": "לא היה ניתן לעצור את הקונטיינרים"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2565,12 +2637,12 @@
|
||||
"label": "אתחל",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים הופעלו מחדש",
|
||||
"message": "הקונטיינרים הופעלו מחדש בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים לא הופעלו מחדש",
|
||||
"message": "לא היה ניתן להפעיל את הקונטיינרים מחדש"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2578,61 +2650,61 @@
|
||||
"label": "הסר",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים הוסרו",
|
||||
"message": "הקונטיינרים הוסרו בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים לא הוסרו",
|
||||
"message": "לא היה ניתן להסיר את הקונטיינרים"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"label": "",
|
||||
"label": "רענון",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים התרעננו",
|
||||
"message": "כעת את/ה צופה בנתונים העדכניים ביותר"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הקונטיינרים לא התרעננו",
|
||||
"message": "משהו השתבש בזמן רענון הקונטיינרים"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToHomarr": {
|
||||
"label": "",
|
||||
"label": "הוסף ל- Homarr",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "הוסף ל- Homarr",
|
||||
"message": "האפליקציות שנבחרו הוספו ל- Homarr"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "לא היה ניתן להוסיף ל- Homarr",
|
||||
"message": "לא היה ניתן להוסיף את האפליקציות שנבחרו ל- Homarr"
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
"title": "הוספת דוקר קונטיינר/ים ל- Homarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "נכשל בקבלת קונטיינרי דוקר"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"title": "",
|
||||
"title": "הרשאות",
|
||||
"userSelect": {
|
||||
"title": ""
|
||||
"title": "הוספת הרשאת משתמש"
|
||||
},
|
||||
"groupSelect": {
|
||||
"title": ""
|
||||
"title": "הוספת הרשאת קבוצה"
|
||||
},
|
||||
"tab": {
|
||||
"user": "משתמשים",
|
||||
"group": "קבוצות",
|
||||
"inherited": ""
|
||||
"inherited": "קבוצות בירושה"
|
||||
},
|
||||
"field": {
|
||||
"user": {
|
||||
@@ -2710,7 +2782,7 @@
|
||||
"label": "קבצי לוג"
|
||||
},
|
||||
"certificates": {
|
||||
"label": ""
|
||||
"label": "אישורי אבטחה"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2758,14 +2830,14 @@
|
||||
"label": "קביעה כלוח הבית"
|
||||
},
|
||||
"mobileBoard": {
|
||||
"label": ""
|
||||
"label": "הגדרה כלוח שלך בנייד"
|
||||
},
|
||||
"settings": {
|
||||
"label": "פתיחת הגדרות"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת פעולה עבור הלוח"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2775,99 +2847,99 @@
|
||||
}
|
||||
},
|
||||
"command": {
|
||||
"help": "",
|
||||
"help": "הפעלת מצב פקודות",
|
||||
"group": {
|
||||
"localCommand": {
|
||||
"title": ""
|
||||
"title": "פקודות מקומיות"
|
||||
},
|
||||
"globalCommand": {
|
||||
"title": "",
|
||||
"title": "פקודות גלובליות",
|
||||
"option": {
|
||||
"colorScheme": {
|
||||
"light": "עבור למצב בהיר",
|
||||
"dark": "עבור למצב כהה"
|
||||
},
|
||||
"language": {
|
||||
"label": "",
|
||||
"label": "שינוי שפה",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת שפה מועודפת"
|
||||
}
|
||||
}
|
||||
},
|
||||
"newBoard": {
|
||||
"label": ""
|
||||
"label": "יצירת לוח חדש"
|
||||
},
|
||||
"importBoard": {
|
||||
"label": ""
|
||||
"label": "ייבוא לוח"
|
||||
},
|
||||
"newApp": {
|
||||
"label": ""
|
||||
"label": "יצירת אפליקציה חדשה"
|
||||
},
|
||||
"newIntegration": {
|
||||
"label": "",
|
||||
"label": "יצירת אינטגרציה חדשה",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת סוג האינטגרציה שברצונך ליצור"
|
||||
}
|
||||
}
|
||||
},
|
||||
"newUser": {
|
||||
"label": ""
|
||||
"label": "יצירת משתמש חדש"
|
||||
},
|
||||
"newInvite": {
|
||||
"label": ""
|
||||
"label": "יצירת הזמנה חדשה"
|
||||
},
|
||||
"newGroup": {
|
||||
"label": ""
|
||||
"label": "יצירת קבוצה חדשה"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
"requestMovie": "בקשת סרט",
|
||||
"requestSeries": "בקשת סדרה",
|
||||
"openIn": "פתיחה ב- {kind}"
|
||||
},
|
||||
"external": {
|
||||
"help": "",
|
||||
"help": "שימוש במנוע חיפוש חיצוני",
|
||||
"group": {
|
||||
"searchEngine": {
|
||||
"title": "מנועי חיפוש",
|
||||
"children": {
|
||||
"action": {
|
||||
"search": {
|
||||
"label": ""
|
||||
"label": "חיפוש עם {name}"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת פעולה עבור מנוע חיפוש"
|
||||
},
|
||||
"searchResults": {
|
||||
"title": ""
|
||||
"title": "בחירת תוצאה בחיפוש לביצוע פעולות"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"google": {
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "גוגל",
|
||||
"description": "חיפוש באינטרנט עם גוגל"
|
||||
},
|
||||
"bing": {
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "בינג",
|
||||
"description": "חיפוש באינטרנט עם בינג"
|
||||
},
|
||||
"duckduckgo": {
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "DuckDuckGo",
|
||||
"description": "חיפוש באינטרנט עם DuckDuckGo"
|
||||
},
|
||||
"torrent": {
|
||||
"name": "טורנטים",
|
||||
"description": ""
|
||||
"description": "חיפוש טורנטים באמצעות torrentdownloads.pro"
|
||||
},
|
||||
"youTube": {
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "יוטיוב",
|
||||
"description": "חיפוש וידאו ביוטיוב"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2876,7 +2948,7 @@
|
||||
"help": {
|
||||
"group": {
|
||||
"mode": {
|
||||
"title": ""
|
||||
"title": "מצבים"
|
||||
},
|
||||
"help": {
|
||||
"title": "עזרה",
|
||||
@@ -2885,7 +2957,7 @@
|
||||
"label": "תיעוד"
|
||||
},
|
||||
"submitIssue": {
|
||||
"label": ""
|
||||
"label": "שליחת בעיה"
|
||||
},
|
||||
"discord": {
|
||||
"label": "קהילת דיסקורד"
|
||||
@@ -2897,75 +2969,75 @@
|
||||
"home": {
|
||||
"group": {
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "חיפוש",
|
||||
"option": {
|
||||
"other": {
|
||||
"label": ""
|
||||
"label": "חיפוש באמצעות מנוע חיפוש אחר"
|
||||
},
|
||||
"no-default": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "אין מנוע חיפוש ברירת מחדל",
|
||||
"description": "הגדרת מנוע חיפוש ברירת מחדל בהעדפות"
|
||||
},
|
||||
"search": {
|
||||
"label": ""
|
||||
"label": "חיפוש {query} עם {name}"
|
||||
},
|
||||
"from-integration": {
|
||||
"description": ""
|
||||
"description": "התחל/י להקליד כדי לחפש"
|
||||
}
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"title": ""
|
||||
"title": "תוצאות מקומיות"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"help": "",
|
||||
"help": "חיפוש עבור דפים",
|
||||
"group": {
|
||||
"page": {
|
||||
"title": "",
|
||||
"title": "דפים",
|
||||
"option": {
|
||||
"manageHome": {
|
||||
"label": ""
|
||||
"label": "ניהול דף בית"
|
||||
},
|
||||
"manageBoard": {
|
||||
"label": ""
|
||||
"label": "ניהול לוחות"
|
||||
},
|
||||
"manageApp": {
|
||||
"label": ""
|
||||
"label": "ניהול אפליקציות"
|
||||
},
|
||||
"manageIntegration": {
|
||||
"label": ""
|
||||
"label": "ניהול אינטגרציות"
|
||||
},
|
||||
"manageSearchEngine": {
|
||||
"label": ""
|
||||
"label": "ניהול מנועי חיפוש"
|
||||
},
|
||||
"manageMedia": {
|
||||
"label": ""
|
||||
"label": "ניהול מדיה"
|
||||
},
|
||||
"manageUser": {
|
||||
"label": "ניהול משתמשים"
|
||||
},
|
||||
"manageInvite": {
|
||||
"label": ""
|
||||
"label": "ניהול הזמנות"
|
||||
},
|
||||
"manageGroup": {
|
||||
"label": ""
|
||||
"label": "ניהול קבוצות"
|
||||
},
|
||||
"manageDocker": {
|
||||
"label": ""
|
||||
"label": "ניהול דוקר"
|
||||
},
|
||||
"manageApi": {
|
||||
"label": ""
|
||||
"label": "Swagger API"
|
||||
},
|
||||
"manageLog": {
|
||||
"label": "הצג לוגים"
|
||||
},
|
||||
"manageTask": {
|
||||
"label": ""
|
||||
"label": "ניהול משימות"
|
||||
},
|
||||
"manageSettings": {
|
||||
"label": ""
|
||||
"label": "הגדרות כלליות"
|
||||
},
|
||||
"about": {
|
||||
"label": "אודות"
|
||||
@@ -2981,18 +3053,18 @@
|
||||
}
|
||||
},
|
||||
"userGroup": {
|
||||
"help": "",
|
||||
"help": "חיפוש משתמשים או קבוצות",
|
||||
"group": {
|
||||
"user": {
|
||||
"title": "משתמשים",
|
||||
"children": {
|
||||
"action": {
|
||||
"detail": {
|
||||
"label": ""
|
||||
"label": "הצגת נתוני משתמש"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת פעולה עבור משתנש"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3001,17 +3073,17 @@
|
||||
"children": {
|
||||
"action": {
|
||||
"detail": {
|
||||
"label": ""
|
||||
"label": "הצגת נתוני קבוצה"
|
||||
},
|
||||
"manageMember": {
|
||||
"label": ""
|
||||
"label": "ניהול חברים"
|
||||
},
|
||||
"managePermission": {
|
||||
"label": ""
|
||||
"label": "ניהול הרשאות"
|
||||
}
|
||||
},
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "בחירת פעולה עבור הקבוצה"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3019,16 +3091,16 @@
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"search": "",
|
||||
"search": "חיפוש מנוע חיפוש",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "שם"
|
||||
},
|
||||
"short": {
|
||||
"label": ""
|
||||
"label": "קצר"
|
||||
},
|
||||
"urlTemplate": {
|
||||
"label": ""
|
||||
"label": "תבנית חיפוש קישור אינטרנט"
|
||||
},
|
||||
"description": {
|
||||
"label": "תיאור"
|
||||
@@ -3038,53 +3110,53 @@
|
||||
"list": {
|
||||
"title": "מנועי חיפוש",
|
||||
"noResults": {
|
||||
"title": "",
|
||||
"action": ""
|
||||
"title": "עדיין אין מנוע חיפוש",
|
||||
"action": "צור את מנוע החיפוש הראשון שלך"
|
||||
},
|
||||
"interactive": ""
|
||||
"interactive": "אינטראקטיבי, עושה שימוש באינטגרציה"
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"title": "מנוע חיפוש חדש",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "מנוע חיפוש נוצר",
|
||||
"message": "מנוע חיפוש נוצר בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "מנוע חיפוש לא נוצר",
|
||||
"message": "לא היה ניתן ליצור את מנוע החיפוש"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"title": "עריכת מנוע חיפוש",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "השינויים הוחלו בהצלחה",
|
||||
"message": ""
|
||||
"message": "מנוע החיפוש נשמר בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "לא ניתן להחיל שינויים",
|
||||
"message": ""
|
||||
"message": "לא היה ניתן לשמור את מנוע החיפוש"
|
||||
}
|
||||
},
|
||||
"configControl": "",
|
||||
"configControl": "הגדרות",
|
||||
"searchEngineType": {
|
||||
"generic": "",
|
||||
"fromIntegration": ""
|
||||
"generic": "כללי",
|
||||
"fromIntegration": "מאינטגרציה"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "מחיקת מנוע חיפוש",
|
||||
"message": "האם אתה בטוח/ה שברצונך למחוק את מנוע החיפוש {name}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "מנוע החיפוש נמחק",
|
||||
"message": "מנוע החיפוש נמחק בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "מנוע החיפוש לא נמחק",
|
||||
"message": "לא היה ניתן למחוק את מנוע החיפוש"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3092,15 +3164,15 @@
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"title": "בקשה {name}",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
"season": "עונה",
|
||||
"episodes": "פרקים"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
"send": "שליחת בקשה"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3110,39 +3182,39 @@
|
||||
"certificate": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "תעודות מהימנות",
|
||||
"description": "נעשה שימוש ב-Homarr כדי לבקש נתונים מאינטגרציות.",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "אין עדיין תעודות אבטחה"
|
||||
},
|
||||
"expires": ""
|
||||
"expires": "פג ב- {when}"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "הוספת תעודת אבטחה",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "תעודת האבטחה הוספה",
|
||||
"message": "תעודת האבטחה נוספה בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "נכשל בהוספת תעודת אבטחה",
|
||||
"message": "לא היה ניתן להוסיף תעודת אבטחה"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "הסרת תעודת אבטחה",
|
||||
"confirm": "האם את/ה בטוח/ה שברצונך להסיר את תעודת האבטחה?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "תעודת האבטחה הוסרה",
|
||||
"message": "תעודת האבטחה הוסרה בהצלחה"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "תעודת אבטחה לא הוסרה",
|
||||
"message": "לא היה ניתן להסיר את תעודת האבטחה"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Lozinka",
|
||||
"newLabel": ""
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": ""
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Jelszó",
|
||||
"newLabel": "Új jelszó"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "URL másolása"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Könyjelzők",
|
||||
"add": "Könyvjelző hozzáadása"
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Fájlrendszer-információk megjelenítése"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "",
|
||||
"newLabel": "Nuova password"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Mostra Informazioni Filesystem"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "パスワード",
|
||||
"newLabel": "新しいパスワード"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "ファイルシステム情報を表示"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "비밀번호",
|
||||
"newLabel": ""
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": ""
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Slaptažodis",
|
||||
"newLabel": "Naujas slaptažodis"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": ""
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Parole",
|
||||
"newLabel": "Jauna parole"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Rādīt failu sistēmas informāciju"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -213,10 +213,10 @@
|
||||
"changeDefaultSearchEngine": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Standaard zoekmachine succesvol gewijzigd"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Standaard zoekmachine kan niet worden gewijzigd"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -499,7 +499,7 @@
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"search": "",
|
||||
"search": "Vind een app",
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Apps",
|
||||
@@ -625,8 +625,8 @@
|
||||
"label": "URL"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Zoekmachine aanmaken",
|
||||
"description": "Integratie \"{kind}\" kan worden gebruikt met de zoekmachines. Controleer dit om automatisch de zoekmachine te configureren."
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Wachtwoord",
|
||||
"newLabel": "Nieuw wachtwoord"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "Token ID",
|
||||
"newLabel": "Nieuwe token ID"
|
||||
},
|
||||
"realm": {
|
||||
"label": "Realm",
|
||||
"newLabel": "Nieuwe Realm"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "URL kopiëren"
|
||||
},
|
||||
"open": {
|
||||
"label": "Media openen"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -897,7 +908,7 @@
|
||||
"passwordRequirements": "Wachtwoord voldoet niet aan de eisen",
|
||||
"boardAlreadyExists": "Er bestaat al een bord met deze naam",
|
||||
"invalidFileType": "Ongeldig bestandstype, verwachtte {expected}",
|
||||
"invalidFileName": "",
|
||||
"invalidFileName": "Ongeldige bestandsnaam",
|
||||
"fileTooLarge": "Bestand is te groot, maximale grootte is {maxSize}",
|
||||
"invalidConfiguration": "Ongeldige configuratie",
|
||||
"groupNameTaken": "Groepsnaam al in gebruik"
|
||||
@@ -967,7 +978,7 @@
|
||||
},
|
||||
"create": {
|
||||
"title": "Kies een item om toe te voegen",
|
||||
"search": "",
|
||||
"search": "Items filteren",
|
||||
"addToBoard": "Toevoegen aan bord"
|
||||
},
|
||||
"moveResize": {
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Bladwijzers",
|
||||
"add": "Bladwijzer toevoegen"
|
||||
@@ -1094,7 +1114,7 @@
|
||||
"dnsQueriesToday": "Aanvragen vandaag",
|
||||
"domainsBeingBlocked": "Domeinen op blokkadelijst"
|
||||
},
|
||||
"domainsTooltip": ""
|
||||
"domainsTooltip": "Vanwege meerdere integraties kan Homarr het exacte aantal geblokkeerde domeinen niet berekenen"
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"name": "DNS-hole bedieningen",
|
||||
@@ -1170,22 +1190,22 @@
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Minecraft server status",
|
||||
"description": "Geeft de status van een Minecraft server weer",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "Titel"
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
"label": "Serveradres"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
"label": "Bedrock server"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
"online": "Online",
|
||||
"offline": "Offline"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
@@ -1283,7 +1303,7 @@
|
||||
},
|
||||
"error": {
|
||||
"noUrl": "Geen iFrame URL opgegeven",
|
||||
"unsupportedProtocol": "",
|
||||
"unsupportedProtocol": "De opgegeven URL gebruikt een niet-ondersteund protocol. Gebruik een van ({supportedProtocols})",
|
||||
"noBrowerSupport": "Je browser ondersteunt geen iframes. Update je browser."
|
||||
}
|
||||
},
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Bestandssysteem info weergeven"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": "Standaard tab"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "Sectie indicator vereiste"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "Gezondheidsstatus ophalen mislukt"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "CPU",
|
||||
"memory": "RAM"
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": "Nodes"
|
||||
},
|
||||
"qemu": {
|
||||
"name": "VM's"
|
||||
},
|
||||
"lxc": {
|
||||
"name": "LXC's"
|
||||
},
|
||||
"storage": {
|
||||
"name": "Opslagruimte"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "Node",
|
||||
"vmId": "VM ID",
|
||||
"plugin": "Plugin"
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "Cores",
|
||||
"memory": "Geheugen",
|
||||
"storage": "Opslagruimte",
|
||||
"uptime": "Uptime",
|
||||
"haState": "HA status",
|
||||
"storageType": {
|
||||
"local": "Lokale opslag",
|
||||
"shared": "Gedeelde opslag"
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "Naam",
|
||||
"cpu": "CPU",
|
||||
"memory": "RAM",
|
||||
"node": "Node"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1497,9 +1569,9 @@
|
||||
"description": "De huidige streams op je mediaservers weergeven",
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
"user": "Gebruiker",
|
||||
"name": "Naam",
|
||||
"id": "Id"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
@@ -1776,16 +1848,16 @@
|
||||
"board": {
|
||||
"action": {
|
||||
"duplicate": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "Bord dupliceren",
|
||||
"message": "Dit zal het bord {name} dupliceren met al zijn inhoud. Als widgets refereren aan integraties, die je niet mag gebruiken, zullen ze worden verwijderd.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Bord gedupliceerd",
|
||||
"message": "Het bord is succesvol gedupliceerd"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Kan board niet dupliceren",
|
||||
"message": "Het bord kon niet worden gedupliceerd"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2102,7 +2174,7 @@
|
||||
"docker": "Docker",
|
||||
"logs": "Logs",
|
||||
"api": "API",
|
||||
"certificates": "",
|
||||
"certificates": "Certificaten",
|
||||
"tasks": "Taken"
|
||||
}
|
||||
},
|
||||
@@ -2156,14 +2228,14 @@
|
||||
}
|
||||
},
|
||||
"setMobileHomeBoard": {
|
||||
"label": "",
|
||||
"label": "Instellen als je mobiele bord",
|
||||
"badge": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"label": "Mobiel",
|
||||
"tooltip": "Dit bord zal getoond worden als je mobiele bord"
|
||||
}
|
||||
},
|
||||
"duplicate": {
|
||||
"label": ""
|
||||
"label": "Bord dupliceren"
|
||||
},
|
||||
"delete": {
|
||||
"label": "Permanent verwijderen",
|
||||
@@ -2199,13 +2271,13 @@
|
||||
"item": {
|
||||
"language": "Taal & regio",
|
||||
"board": {
|
||||
"title": "",
|
||||
"title": "Home bord",
|
||||
"type": {
|
||||
"general": "",
|
||||
"mobile": ""
|
||||
"general": "Algemeen",
|
||||
"mobile": "Mobiel"
|
||||
}
|
||||
},
|
||||
"defaultSearchEngine": "",
|
||||
"defaultSearchEngine": "Standaard zoekmachine",
|
||||
"firstDayOfWeek": "Eerste dag van de week",
|
||||
"accessibility": "Toegankelijkheid"
|
||||
}
|
||||
@@ -2364,15 +2436,15 @@
|
||||
"title": "Borden",
|
||||
"homeBoard": {
|
||||
"label": "Globaal home-bord",
|
||||
"mobileLabel": "",
|
||||
"mobileLabel": "Globaal mobiel bord",
|
||||
"description": "Alleen openbare borden zijn beschikbaar voor selectie"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "Zoeken",
|
||||
"defaultSearchEngine": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Globale standaard zoekmachine",
|
||||
"description": "Integratie zoekmachines kunnen hier niet geselecteerd worden"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
@@ -2403,7 +2475,7 @@
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
"label": "Minecraft server status"
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Icoon updater"
|
||||
@@ -2601,19 +2673,19 @@
|
||||
}
|
||||
},
|
||||
"addToHomarr": {
|
||||
"label": "",
|
||||
"label": "Toevoegen aan Homarr",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Toegevoegd aan Homarr",
|
||||
"message": "Geselecteerde apps zijn toegevoegd aan Homarr"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Kon niet toevoegen aan Homarr",
|
||||
"message": "Geselecteerde apps konden niet worden toegevoegd aan Homarr"
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
"title": "Docker container('s) toevoegen aan Homarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2710,7 +2782,7 @@
|
||||
"label": "Logs"
|
||||
},
|
||||
"certificates": {
|
||||
"label": ""
|
||||
"label": "Certificaten"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2758,7 +2830,7 @@
|
||||
"label": "Instellen als home-bord"
|
||||
},
|
||||
"mobileBoard": {
|
||||
"label": ""
|
||||
"label": "Instellen als mobiel bord"
|
||||
},
|
||||
"settings": {
|
||||
"label": "Instellingen openen"
|
||||
@@ -2826,9 +2898,9 @@
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
"requestMovie": "Film aanvragen",
|
||||
"requestSeries": "Series aanvragen",
|
||||
"openIn": "Openen in {kind}"
|
||||
},
|
||||
"external": {
|
||||
"help": "Gebruik een externe zoekmachine",
|
||||
@@ -2897,20 +2969,20 @@
|
||||
"home": {
|
||||
"group": {
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "Zoeken",
|
||||
"option": {
|
||||
"other": {
|
||||
"label": ""
|
||||
"label": "Zoeken met andere zoekmachine"
|
||||
},
|
||||
"no-default": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Geen standaard zoekmachine",
|
||||
"description": "Stel een standaard zoekmachine in in voorkeuren"
|
||||
},
|
||||
"search": {
|
||||
"label": ""
|
||||
"label": "Zoeken naar \"{query}\" met {name}"
|
||||
},
|
||||
"from-integration": {
|
||||
"description": ""
|
||||
"description": "Typ om te zoeken"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3092,15 +3164,15 @@
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"title": "\"{name}\" aanvragen",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
"season": "Seizoen",
|
||||
"episodes": "Afleveringen"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
"send": "Aanvraag versturen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3110,39 +3182,39 @@
|
||||
"certificate": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Vertrouwde certificaten",
|
||||
"description": "Gebruikt door Homarr om gegevens op te vragen van integraties.",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "Er zijn nog geen certificaten"
|
||||
},
|
||||
"expires": ""
|
||||
"expires": "Verloopt {when}"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "Certificaat toevoegen",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificaat toegevoegd",
|
||||
"message": "Het certificaat is succesvol toegevoegd"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificaat toevoegen mislukt",
|
||||
"message": "Het certificaat kon niet worden toegevoegd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "Certificaten verwijderen",
|
||||
"confirm": "Weet je zeker dat je het certificaat wilt verwijderen?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificaat verwijderd",
|
||||
"message": "Het certificaat is succesvol verwijderd"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificaat niet verwijderd",
|
||||
"message": "Certificaat kon niet worden verwijderd"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Passord",
|
||||
"newLabel": "Nytt passord"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Vis filsysteminfo"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Hasło",
|
||||
"newLabel": "Nowe hasło"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Kopiuj adres URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Zakładki",
|
||||
"add": "Dodaj zakładkę"
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Pokaż informacje o systemie plików"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "Nie udało się pobrać stanu zdrowia"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -2,123 +2,123 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Bem-vindo ao Homarr",
|
||||
"subtitle": "Vamos começar configurando a sua instância Homarr.",
|
||||
"description": "Para começar, por favor selecione como você deseja configurar sua instância Homarr.",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"importOldmarr": ""
|
||||
"scratch": "Começar do zero",
|
||||
"importOldmarr": "Importe de uma instância Homarr anterior a 1,0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Importar dados",
|
||||
"subtitle": "Você pode importar dados de uma instância existente do Homarr.",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Arraste o arquivo zip aqui ou clique para procurar",
|
||||
"description": "O arquivo zip enviado será processado e você poderá selecionar o que deseja importar"
|
||||
},
|
||||
"fileInfo": {
|
||||
"action": {
|
||||
"change": ""
|
||||
"change": "Alterar arquivo"
|
||||
}
|
||||
},
|
||||
"importSettings": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Importar configurações",
|
||||
"description": "Configure o comportamento da importação"
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "{count} quadros encontrados",
|
||||
"description": "Escolha todos os quadros com o tamanho que você deseja importar",
|
||||
"action": {
|
||||
"selectAll": "",
|
||||
"unselectAll": ""
|
||||
"selectAll": "Selecionar todos",
|
||||
"unselectAll": "Desmarcar Todos"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Resumo da importação",
|
||||
"description": "No resumo abaixo, você pode ver o que será importado",
|
||||
"action": {
|
||||
"import": ""
|
||||
"import": "Confirmar a importação e continuar"
|
||||
},
|
||||
"entities": {
|
||||
"apps": "Aplicativos",
|
||||
"boards": "Placas",
|
||||
"integrations": "",
|
||||
"credentialUsers": ""
|
||||
"integrations": "Integrações",
|
||||
"credentialUsers": "Usuários credenciados"
|
||||
}
|
||||
},
|
||||
"tokenModal": {
|
||||
"title": "",
|
||||
"title": "Insira o token de importação",
|
||||
"field": {
|
||||
"token": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Token",
|
||||
"description": "Insira o token de importação mostrado em sua instância homarr anterior"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Token inválido",
|
||||
"message": "O token que você inseriu é inválido"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Usuário Administrador",
|
||||
"subtitle": "Especifique as credenciais para seu usuário administrador.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Usuário criado",
|
||||
"message": "O usuário foi criado com sucesso"
|
||||
},
|
||||
"error": {
|
||||
"title": ""
|
||||
"title": "Falha ao criar usuário"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Grupo externo",
|
||||
"subtitle": "Especifique o grupo que deve ser usado para usuários externos.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Nome do grupo",
|
||||
"description": "O nome tem que coincidir com o grupo administrador do provedor externo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
"subtitle": ""
|
||||
"subtitle": "Definir as configurações do servidor."
|
||||
},
|
||||
"finish": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Concluir configuração",
|
||||
"subtitle": "Você está pronto para começar!",
|
||||
"description": "Você concluiu com sucesso o processo de configuração. Agora você pode começar a usar o Homarr. Selecione sua próxima ação:",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": ""
|
||||
"goToBoard": "Ir para o quadro {name}",
|
||||
"createBoard": "Crie seu primeiro quadro",
|
||||
"inviteUser": "Convidar outros usuários",
|
||||
"docs": "Ler a documentação"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backToStart": ""
|
||||
"backToStart": "Voltar ao início"
|
||||
},
|
||||
"user": {
|
||||
"title": "Usuários",
|
||||
"name": "Usuário",
|
||||
"page": {
|
||||
"login": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Acesse a sua conta",
|
||||
"subtitle": "Bem-vindo(a) de volta! Por favor, insira suas credenciais"
|
||||
},
|
||||
"invite": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": ""
|
||||
"title": "Junte-se ao Homarr",
|
||||
"subtitle": "Bem-vindo ao Homarr! Por favor, crie sua conta",
|
||||
"description": "Você foi convidado(a) por {username}"
|
||||
},
|
||||
"init": {
|
||||
"title": "",
|
||||
"title": "Nova instalação do Homarr",
|
||||
"subtitle": ""
|
||||
}
|
||||
},
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Senha",
|
||||
"newLabel": "Nova senha"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Mostrar informações do sistema de arquivos"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Parola",
|
||||
"newLabel": "Parolă nouă"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": ""
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "",
|
||||
"add": ""
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Afișare informații despre sistemul de fișiere"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -213,10 +213,10 @@
|
||||
"changeDefaultSearchEngine": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Поисковая система по умолчанию успешно изменена"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Не удалось изменить поисковую систему по умолчанию"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -499,7 +499,7 @@
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"search": "",
|
||||
"search": "Поиск приложений",
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Приложения",
|
||||
@@ -625,8 +625,8 @@
|
||||
"label": "URL-адрес"
|
||||
},
|
||||
"attemptSearchEngineCreation": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Создать поисковую систему",
|
||||
"description": "Интеграцию \"{kind}\" можно использовать с поисковыми системами. Включите эту опцию для автоматической настройки поисковой системы."
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Пароль",
|
||||
"newLabel": "Новый пароль"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "ID токена",
|
||||
"newLabel": "Новый ID токена"
|
||||
},
|
||||
"realm": {
|
||||
"label": "Область",
|
||||
"newLabel": "Новая область"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Копировать URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -897,7 +908,7 @@
|
||||
"passwordRequirements": "Пароль не соответствует требованиям",
|
||||
"boardAlreadyExists": "Панель с таким именем уже существует",
|
||||
"invalidFileType": "Неверный тип файла, ожидается {expected}",
|
||||
"invalidFileName": "",
|
||||
"invalidFileName": "Недопустимое имя файла",
|
||||
"fileTooLarge": "Файл слишком большой, максимальный размер {maxSize}",
|
||||
"invalidConfiguration": "Неверная конфигурация",
|
||||
"groupNameTaken": "Имя группы уже занято"
|
||||
@@ -967,7 +978,7 @@
|
||||
},
|
||||
"create": {
|
||||
"title": "Выберите тип элемента",
|
||||
"search": "",
|
||||
"search": "Фильтр элементов",
|
||||
"addToBoard": "Добавить на панель"
|
||||
},
|
||||
"moveResize": {
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Закладки",
|
||||
"add": "Добавить закладку"
|
||||
@@ -1094,7 +1114,7 @@
|
||||
"dnsQueriesToday": "DNS-запросов сегодня",
|
||||
"domainsBeingBlocked": "Доменов в списке блокировки"
|
||||
},
|
||||
"domainsTooltip": ""
|
||||
"domainsTooltip": "Из-за наличия нескольких интеграций Homarr не может рассчитать точное количество заблокированных доменов"
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"name": "Управление DNS-фильтром",
|
||||
@@ -1170,22 +1190,22 @@
|
||||
}
|
||||
},
|
||||
"minecraftServerStatus": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Статус сервера Minecraft",
|
||||
"description": "Отображает текущий статус и информацию о сервере Minecraft",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "Заголовок"
|
||||
},
|
||||
"domain": {
|
||||
"label": ""
|
||||
"label": "Домен сервера"
|
||||
},
|
||||
"isBedrockServer": {
|
||||
"label": ""
|
||||
"label": "Сервер Bedrock Edition"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"online": "",
|
||||
"offline": ""
|
||||
"online": "В сети",
|
||||
"offline": "Не в сети"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
@@ -1283,7 +1303,7 @@
|
||||
},
|
||||
"error": {
|
||||
"noUrl": "URL для iFrame не указан",
|
||||
"unsupportedProtocol": "",
|
||||
"unsupportedProtocol": "Указанный URL использует неподдерживаемый протокол. Пожалуйста, используйте один из ({supportedProtocols})",
|
||||
"noBrowerSupport": "Ваш браузер не поддерживает iframes. Пожалуйста, обновите браузер."
|
||||
}
|
||||
},
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Показать информацию о файловой системе"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": "Вкладка по умолчанию"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "Пороговое значение индикатора раздела"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "Не удалось получить данные о состоянии системы"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "ЦП",
|
||||
"memory": "Память"
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": "Узлы"
|
||||
},
|
||||
"qemu": {
|
||||
"name": "Виртуальные машины"
|
||||
},
|
||||
"lxc": {
|
||||
"name": "Контейнеры LXC"
|
||||
},
|
||||
"storage": {
|
||||
"name": "Хранилище"
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "Узел",
|
||||
"vmId": "ID виртуальной машины",
|
||||
"plugin": "Плагин"
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "Ядер",
|
||||
"memory": "Память",
|
||||
"storage": "Хранилище",
|
||||
"uptime": "Время работы",
|
||||
"haState": "Состояние HA",
|
||||
"storageType": {
|
||||
"local": "Локальное",
|
||||
"shared": "Общее"
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "Название",
|
||||
"cpu": "ЦП",
|
||||
"memory": "Память",
|
||||
"node": "Узел"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1497,9 +1569,9 @@
|
||||
"description": "Отображает текущие сеансы воспроизведения на медиасерверах",
|
||||
"option": {},
|
||||
"items": {
|
||||
"user": "",
|
||||
"name": "",
|
||||
"id": ""
|
||||
"user": "Пользователь",
|
||||
"name": "Название",
|
||||
"id": "Id"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
@@ -1776,16 +1848,16 @@
|
||||
"board": {
|
||||
"action": {
|
||||
"duplicate": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "Дублировать панель",
|
||||
"message": "Это действие создаст копию панели {name} со всем её содержимым. Если виджеты используют интеграции, к которым у вас нет доступа, они будут удалены.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Панель продублирована",
|
||||
"message": "Панель успешно продублирована"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Ошибка дублирования",
|
||||
"message": "Не удалось продублировать панель"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2102,7 +2174,7 @@
|
||||
"docker": "Docker",
|
||||
"logs": "Логи",
|
||||
"api": "API",
|
||||
"certificates": "",
|
||||
"certificates": "Сертификаты",
|
||||
"tasks": "Задачи"
|
||||
}
|
||||
},
|
||||
@@ -2156,14 +2228,14 @@
|
||||
}
|
||||
},
|
||||
"setMobileHomeBoard": {
|
||||
"label": "",
|
||||
"label": "Сделать домашней панелью для мобильных устройств",
|
||||
"badge": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"label": "Мобильная",
|
||||
"tooltip": "Эта панель будет показываться как ваша домашняя панель на мобильных устройствах"
|
||||
}
|
||||
},
|
||||
"duplicate": {
|
||||
"label": ""
|
||||
"label": "Дублировать"
|
||||
},
|
||||
"delete": {
|
||||
"label": "Удалить навсегда",
|
||||
@@ -2199,13 +2271,13 @@
|
||||
"item": {
|
||||
"language": "Язык и регион",
|
||||
"board": {
|
||||
"title": "",
|
||||
"title": "Домашняя панель",
|
||||
"type": {
|
||||
"general": "",
|
||||
"mobile": ""
|
||||
"general": "Основная",
|
||||
"mobile": "Мобильная"
|
||||
}
|
||||
},
|
||||
"defaultSearchEngine": "",
|
||||
"defaultSearchEngine": "Поисковая система по умолчанию",
|
||||
"firstDayOfWeek": "Первый день недели",
|
||||
"accessibility": "Специальные возможности"
|
||||
}
|
||||
@@ -2363,16 +2435,16 @@
|
||||
"board": {
|
||||
"title": "Панели",
|
||||
"homeBoard": {
|
||||
"label": "Общая домашняя панель",
|
||||
"mobileLabel": "",
|
||||
"label": "Глобальная домашняя панель",
|
||||
"mobileLabel": "Глобальная мобильная панель",
|
||||
"description": "Для выбора доступны только публичные панели"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "Поиск",
|
||||
"defaultSearchEngine": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Глобальная поисковая система",
|
||||
"description": "Поисковые системы из интеграций нельзя выбрать здесь"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
@@ -2403,7 +2475,7 @@
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
"label": ""
|
||||
"label": "Статус сервера Minecraft"
|
||||
},
|
||||
"iconsUpdater": {
|
||||
"label": "Обновление иконок"
|
||||
@@ -2601,19 +2673,19 @@
|
||||
}
|
||||
},
|
||||
"addToHomarr": {
|
||||
"label": "",
|
||||
"label": "Добавить в Homarr",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Приложение добавлено",
|
||||
"message": "Приложение успешно добавлено в Homarr"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не удалось добавить",
|
||||
"message": "Не удалось добавить приложение в Homarr"
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
"title": "Добавить контейнер(ы) Docker в Homarr"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2710,7 +2782,7 @@
|
||||
"label": "Логи"
|
||||
},
|
||||
"certificates": {
|
||||
"label": ""
|
||||
"label": "Сертификаты"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2758,7 +2830,7 @@
|
||||
"label": "Сделать домашней панелью"
|
||||
},
|
||||
"mobileBoard": {
|
||||
"label": ""
|
||||
"label": "Сделать домашней панелью для мобильных устройств"
|
||||
},
|
||||
"settings": {
|
||||
"label": "Открыть настройки"
|
||||
@@ -2826,9 +2898,9 @@
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"requestMovie": "",
|
||||
"requestSeries": "",
|
||||
"openIn": ""
|
||||
"requestMovie": "Запросить фильм",
|
||||
"requestSeries": "Запросить сериал",
|
||||
"openIn": "Открыть в {kind}"
|
||||
},
|
||||
"external": {
|
||||
"help": "Использовать внешнюю поисковую систему",
|
||||
@@ -2897,20 +2969,20 @@
|
||||
"home": {
|
||||
"group": {
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "Поиск",
|
||||
"option": {
|
||||
"other": {
|
||||
"label": ""
|
||||
"label": "Искать в другой поисковой системе"
|
||||
},
|
||||
"no-default": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Нет поисковой системы по умолчанию",
|
||||
"description": "Установите поисковую систему по умолчанию в настройках"
|
||||
},
|
||||
"search": {
|
||||
"label": ""
|
||||
"label": "Искать \"{query}\" через {name}"
|
||||
},
|
||||
"from-integration": {
|
||||
"description": ""
|
||||
"description": "Начните вводить для поиска"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3092,15 +3164,15 @@
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"title": "Запрос \"{name}\"",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
"season": "Сезон",
|
||||
"episodes": "Серии"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
"send": "Отправить запрос"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3110,39 +3182,39 @@
|
||||
"certificate": {
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Доверенные сертификаты",
|
||||
"description": "Используются Homarr для запроса данных из интеграций.",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "Доверенные сертификаты отсутствуют"
|
||||
},
|
||||
"expires": ""
|
||||
"expires": "Истекает {when}"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "Создать сертификат",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Сертификат создан",
|
||||
"message": "Сертификат успешно создан"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не удалось создать сертификат",
|
||||
"message": "Произошла ошибка при создании сертификата"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "Удалить сертификат",
|
||||
"confirm": "Вы уверены, что хотите удалить этот сертификат?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Сертификат удален",
|
||||
"message": "Сертификат успешно удален"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Не удалось удалить сертификат",
|
||||
"message": "Произошла ошибка при удалении сертификата"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +733,14 @@
|
||||
"password": {
|
||||
"label": "Heslo",
|
||||
"newLabel": "Nové heslo"
|
||||
},
|
||||
"tokenId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
},
|
||||
"realm": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,6 +785,9 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Skopírovať URL"
|
||||
},
|
||||
"open": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1056,6 +1067,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideIcon": {
|
||||
"label": ""
|
||||
},
|
||||
"hideHostname": {
|
||||
"label": ""
|
||||
},
|
||||
"openNewTab": {
|
||||
"label": ""
|
||||
},
|
||||
"items": {
|
||||
"label": "Záložky",
|
||||
"add": "Pridať záložku"
|
||||
@@ -1411,6 +1431,12 @@
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Zobraziť informácie o súborovom systéme"
|
||||
},
|
||||
"defaultTab": {
|
||||
"label": ""
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
@@ -1430,6 +1456,52 @@
|
||||
"memory": {},
|
||||
"error": {
|
||||
"internalServerError": "Nepodarilo sa načítať zdravotný stav"
|
||||
},
|
||||
"cluster": {
|
||||
"summary": {
|
||||
"cpu": "",
|
||||
"memory": ""
|
||||
},
|
||||
"resource": {
|
||||
"node": {
|
||||
"name": ""
|
||||
},
|
||||
"qemu": {
|
||||
"name": ""
|
||||
},
|
||||
"lxc": {
|
||||
"name": ""
|
||||
},
|
||||
"storage": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"rightSection": {
|
||||
"node": "",
|
||||
"vmId": "",
|
||||
"plugin": ""
|
||||
},
|
||||
"detail": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"storage": "",
|
||||
"uptime": "",
|
||||
"haState": "",
|
||||
"storageType": {
|
||||
"local": "",
|
||||
"shared": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"name": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"node": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user