Compare commits
88
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0513a8e6f5 | ||
|
|
0b200dc9e2 | ||
|
|
377f4302c0 | ||
|
|
740e9a5082 | ||
|
|
e7652ea4eb | ||
|
|
863f935ef4 | ||
|
|
7388c907a5 | ||
|
|
3d07306e7d | ||
|
|
f37fc0edfe | ||
|
|
c59cdeda47 | ||
|
|
ad444e8fa7 | ||
|
|
fa52b55828 | ||
|
|
1ce1113a8c | ||
|
|
e7a9797484 | ||
|
|
d104aea8b8 | ||
|
|
ab3ee3bac1 | ||
|
|
838066ac75 | ||
|
|
b1a559d801 | ||
|
|
eecf50d461 | ||
|
|
d862ae03e3 | ||
|
|
f6da091577 | ||
|
|
22f0cd885e | ||
|
|
08c6ce9071 | ||
|
|
c3e1a53d09 | ||
|
|
9e86de6e17 | ||
|
|
98c4cd1e39 | ||
|
|
f67702e1d3 | ||
|
|
a9e68e13d5 | ||
|
|
1b3770aafb | ||
|
|
7928f15452 | ||
|
|
80327337ce | ||
|
|
f72a994fb3 | ||
|
|
1910786f5b | ||
|
|
6ec66b1fe2 | ||
|
|
aafd883c32 | ||
|
|
f32242bf79 | ||
|
|
62aeb043b5 | ||
|
|
ffd2f802bb | ||
|
|
3e7bf62500 | ||
|
|
7071d76c52 | ||
|
|
c7a21b1a5f | ||
|
|
6274a66048 | ||
|
|
07f81cee92 | ||
|
|
3ad72e63b1 | ||
|
|
ed3f848e8a | ||
|
|
8484daaf75 | ||
|
|
aeed7dbfd0 | ||
|
|
68881e7ee5 | ||
|
|
61553cfc8e | ||
|
|
cf7336ba87 | ||
|
|
dac82347e8 | ||
|
|
74aa561f47 | ||
|
|
cb77a7dc6d | ||
|
|
81509fd96d | ||
|
|
960dc33af8 | ||
|
|
c051a42791 | ||
|
|
a542ee0798 | ||
|
|
9ac9042564 | ||
|
|
2fe3dcb01b | ||
|
|
a0a0449c70 | ||
|
|
d4afafe0f7 | ||
|
|
cae0cad92f | ||
|
|
df2a9a5329 | ||
|
|
8f6cc6fa60 | ||
|
|
d699aad529 | ||
|
|
0362487156 | ||
|
|
5b354aca20 | ||
|
|
124e8de6c5 | ||
|
|
a0585e3dd7 | ||
|
|
a3deed3b09 | ||
|
|
ae5aebda20 | ||
|
|
362c663060 | ||
|
|
0d1e3a625a | ||
|
|
ccd8a85e0f | ||
|
|
f0ba693077 | ||
|
|
94f176d544 | ||
|
|
a86fc5bcb8 | ||
|
|
9bdbe6f425 | ||
|
|
1b1671e0e6 | ||
|
|
3337cfa3c7 | ||
|
|
161fc86770 | ||
|
|
76ae423fd9 | ||
|
|
1f408685e6 | ||
|
|
b608ffe490 | ||
|
|
f579b283f2 | ||
|
|
9518ebb088 | ||
|
|
9895c1b3ff | ||
|
|
7e5ca418cc |
No files matched your search
@@ -31,6 +31,8 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.23.0
|
||||
- 1.22.0
|
||||
- 1.21.0
|
||||
- 1.20.0
|
||||
- 1.19.1
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
|
||||
|
||||
- [ ] Builds without warnings or errors (``pnpm build``, autofix with ``pnpm format:fix``)
|
||||
- [ ] Pull request targets ``dev`` branch
|
||||
- [ ] Builds without warnings or errors (`pnpm build`, autofix with `pnpm format:fix`)
|
||||
- [ ] Pull request targets `dev` branch
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] No shorthand variable names are used (eg. ``x``, ``y``, ``i`` or any abbrevation)
|
||||
|
||||
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
|
||||
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
|
||||
@@ -1,3 +1,47 @@
|
||||
## [1.23.0](https://github.com/homarr-labs/homarr/compare/v1.22.0...v1.23.0) (2025-06-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **releases-widget:** add `import from docker` functionality ([#3130](https://github.com/homarr-labs/homarr/issues/3130)) ([7071d76](https://github.com/homarr-labs/homarr/commit/7071d76c526f1b59a955abcc595894975d574218))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Allow nullable on cpuModelName and default to "Unknown CPU" if null in OMV Integration ([#3310](https://github.com/homarr-labs/homarr/issues/3310)) ([3e7bf62](https://github.com/homarr-labs/homarr/commit/3e7bf62500f4ce0a8662104e2a656ea98cf2558f))
|
||||
* csp prevents external stylesheets ([#3337](https://github.com/homarr-labs/homarr/issues/3337)) ([c7a21b1](https://github.com/homarr-labs/homarr/commit/c7a21b1a5f4e7c9d994d81b75a3fd4fa7c013daf))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.34.0 ([#3303](https://github.com/homarr-labs/homarr/issues/3303)) ([5b354ac](https://github.com/homarr-labs/homarr/commit/5b354aca2047e8316b331e327cf6031690dcfef9))
|
||||
* **deps:** update dependency dockerode to ^4.0.7 ([#3331](https://github.com/homarr-labs/homarr/issues/3331)) ([07f81ce](https://github.com/homarr-labs/homarr/commit/07f81cee92627e3b6ee4b2bbf084657f9a83ac34))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.1 ([#3280](https://github.com/homarr-labs/homarr/issues/3280)) ([9895c1b](https://github.com/homarr-labs/homarr/commit/9895c1b3ff028a7ea7273ab2625620c9132f5213))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.2 ([#3315](https://github.com/homarr-labs/homarr/issues/3315)) ([9ac9042](https://github.com/homarr-labs/homarr/commit/9ac90425647bf1f10d3f299f043c5d40067347bc))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.2.4 ([#3336](https://github.com/homarr-labs/homarr/issues/3336)) ([3ad72e6](https://github.com/homarr-labs/homarr/commit/3ad72e63b1adecd18c53897a6023f09d6f5d011c))
|
||||
* **deps:** update dependency node-cron to ^4.1.0 ([#3286](https://github.com/homarr-labs/homarr/issues/3286)) ([1f40868](https://github.com/homarr-labs/homarr/commit/1f408685e69397fa9e3a69bd4331a8240da3713b))
|
||||
* **deps:** update dependency sass to ^1.89.1 ([#3285](https://github.com/homarr-labs/homarr/issues/3285)) ([b608ffe](https://github.com/homarr-labs/homarr/commit/b608ffe4900a5dd97ceb2669464c477404d090e7))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.24.0 ([#3325](https://github.com/homarr-labs/homarr/issues/3325)) ([cf7336b](https://github.com/homarr-labs/homarr/commit/cf7336ba87c617578ab52e2ece94fd84df3fc623))
|
||||
* **deps:** update dependency typescript-eslint to ^8.33.1 ([#3302](https://github.com/homarr-labs/homarr/issues/3302)) ([124e8de](https://github.com/homarr-labs/homarr/commit/124e8de6c584b0efca91d92079f8b3c400bccab2))
|
||||
* **deps:** update dependency video.js to ^8.23.3 ([#3320](https://github.com/homarr-labs/homarr/issues/3320)) ([81509fd](https://github.com/homarr-labs/homarr/commit/81509fd96dfe0561964eb58d3d21c02400db2e8c))
|
||||
* **deps:** update dependency zod to ^3.25.43 ([#3290](https://github.com/homarr-labs/homarr/issues/3290)) ([161fc86](https://github.com/homarr-labs/homarr/commit/161fc867703c4cf634eaaeec1b73f5e3cbfa7fec))
|
||||
* **deps:** update dependency zod to ^3.25.45 ([#3291](https://github.com/homarr-labs/homarr/issues/3291)) ([3337cfa](https://github.com/homarr-labs/homarr/commit/3337cfa3c7e34debcccdbb95ee0bc39f2af98a91))
|
||||
* **deps:** update dependency zod to ^3.25.46 ([#3292](https://github.com/homarr-labs/homarr/issues/3292)) ([1b1671e](https://github.com/homarr-labs/homarr/commit/1b1671e0e6d808469ba7f8185c4590cf2f2f6f44))
|
||||
* **deps:** update dependency zod to ^3.25.47 ([#3296](https://github.com/homarr-labs/homarr/issues/3296)) ([94f176d](https://github.com/homarr-labs/homarr/commit/94f176d544272e0378e65255aba58b05d56019b2))
|
||||
* **deps:** update dependency zod to ^3.25.48 ([#3297](https://github.com/homarr-labs/homarr/issues/3297)) ([ccd8a85](https://github.com/homarr-labs/homarr/commit/ccd8a85e0fcd2cf40dbfb6058787d24a02680b93))
|
||||
* **deps:** update dependency zod to ^3.25.49 ([#3304](https://github.com/homarr-labs/homarr/issues/3304)) ([0362487](https://github.com/homarr-labs/homarr/commit/0362487156457ae825cac501e7d53db58c31ab0b))
|
||||
* **deps:** update dependency zod to ^3.25.50 ([#3312](https://github.com/homarr-labs/homarr/issues/3312)) ([a0a0449](https://github.com/homarr-labs/homarr/commit/a0a0449c704ff57725ab5e0b459eb209fc929446))
|
||||
* **deps:** update dependency zod to ^3.25.51 ([#3316](https://github.com/homarr-labs/homarr/issues/3316)) ([a542ee0](https://github.com/homarr-labs/homarr/commit/a542ee079871c7609ed97c76aa2017305adbfdc9))
|
||||
* **deps:** update dependency zod to ^3.25.55 ([#3332](https://github.com/homarr-labs/homarr/issues/3332)) ([ed3f848](https://github.com/homarr-labs/homarr/commit/ed3f848e8afb57cc1b9249551f0260eb52bbd4a6))
|
||||
* **deps:** update tanstack-query monorepo to ^5.79.1 ([#3300](https://github.com/homarr-labs/homarr/issues/3300)) ([a3deed3](https://github.com/homarr-labs/homarr/commit/a3deed3b090aed0df0f45a9bd04c006640691e05))
|
||||
* **deps:** update tanstack-query monorepo to ^5.79.2 ([#3301](https://github.com/homarr-labs/homarr/issues/3301)) ([a0585e3](https://github.com/homarr-labs/homarr/commit/a0585e3dd7d9cfbccf8ff5535ae2c753a8b92764))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.0 ([#3307](https://github.com/homarr-labs/homarr/issues/3307)) ([df2a9a5](https://github.com/homarr-labs/homarr/commit/df2a9a5329e06847904af56a0d058c0da59f6380))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.2 ([#3308](https://github.com/homarr-labs/homarr/issues/3308)) ([cae0cad](https://github.com/homarr-labs/homarr/commit/cae0cad92f68f716887e077194d4e30402466d57))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.3 ([#3317](https://github.com/homarr-labs/homarr/issues/3317)) ([c051a42](https://github.com/homarr-labs/homarr/commit/c051a42791de4eae4baf20360efe5b0c8925c22b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.5 ([#3321](https://github.com/homarr-labs/homarr/issues/3321)) ([cb77a7d](https://github.com/homarr-labs/homarr/commit/cb77a7dc6d60560250765d129aac9bd236693e2b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.6 ([#3326](https://github.com/homarr-labs/homarr/issues/3326)) ([61553cf](https://github.com/homarr-labs/homarr/commit/61553cfc8e48a1179a148007fb970b1f4e6d509c))
|
||||
* **deps:** update testcontainers-node monorepo to v11 (major) ([#3250](https://github.com/homarr-labs/homarr/issues/3250)) ([0d1e3a6](https://github.com/homarr-labs/homarr/commit/0d1e3a625a04abd5f667640ab40e01d28e017488))
|
||||
* **deps:** update tiptap monorepo to v2.13.0 ([#3323](https://github.com/homarr-labs/homarr/issues/3323)) ([dac8234](https://github.com/homarr-labs/homarr/commit/dac82347e86dd7faacb24adbfc61ace9c917b8cd))
|
||||
* **deps:** update tiptap monorepo to v2.14.0 ([#3334](https://github.com/homarr-labs/homarr/issues/3334)) ([6274a66](https://github.com/homarr-labs/homarr/commit/6274a66048163513e3faaf7011d7d6aa6ad9f161))
|
||||
* **deps:** update trpc monorepo to ^11.2.0 ([#3293](https://github.com/homarr-labs/homarr/issues/3293)) ([f0ba693](https://github.com/homarr-labs/homarr/commit/f0ba693077a0c7f7897c81f02ad66b77fce52f1c))
|
||||
* **deps:** update trpc monorepo to ^11.3.0 ([#3322](https://github.com/homarr-labs/homarr/issues/3322)) ([74aa561](https://github.com/homarr-labs/homarr/commit/74aa561f470f0f76421f356998caa91dc8bb65a1))
|
||||
* **deps:** update trpc monorepo to ^11.3.1 ([#3329](https://github.com/homarr-labs/homarr/issues/3329)) ([8484daa](https://github.com/homarr-labs/homarr/commit/8484daaf7516ad72dd9e7de3117b5e0d50415dbb))
|
||||
* **nextcloud:** correct calendar event date mapping and support showU… ([#3330](https://github.com/homarr-labs/homarr/issues/3330)) ([ffd2f80](https://github.com/homarr-labs/homarr/commit/ffd2f802bb777f13eac1ee45e1b5f0ca2b596672))
|
||||
|
||||
## [1.22.0](https://github.com/homarr-labs/homarr/compare/v1.21.0...v1.22.0) (2025-05-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -17,31 +17,16 @@ const withNextIntl = createNextIntlPlugin({
|
||||
requestConfig: "../../packages/translation/src/request.ts",
|
||||
});
|
||||
|
||||
interface WebpackConfig {
|
||||
module: {
|
||||
rules: {
|
||||
test: RegExp;
|
||||
loader: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
/** We already do linting and typechecking as separate tasks in CI */
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
webpack: (config: WebpackConfig, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.module.rules.push({
|
||||
test: /\.node$/,
|
||||
loader: "node-loader",
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
/**
|
||||
* dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612
|
||||
*/
|
||||
serverExternalPackages: ["dockerode"],
|
||||
experimental: {
|
||||
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
||||
},
|
||||
@@ -62,12 +47,13 @@ const nextConfig: NextConfig = {
|
||||
script-src * 'unsafe-inline' 'unsafe-eval';
|
||||
base-uri 'self';
|
||||
connect-src *;
|
||||
style-src 'self' 'unsafe-inline';
|
||||
style-src * 'unsafe-inline';
|
||||
frame-ancestors *;
|
||||
frame-src *;
|
||||
form-action 'self';
|
||||
img-src * data:;
|
||||
font-src * data:;
|
||||
media-src * data:;
|
||||
`
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim(),
|
||||
|
||||
+21
-21
@@ -48,21 +48,21 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.0.2",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/dropzone": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/modals": "^8.0.2",
|
||||
"@mantine/tiptap": "^8.0.2",
|
||||
"@mantine/colors-generator": "^8.1.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/dropzone": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@mantine/modals": "^8.1.0",
|
||||
"@mantine/tiptap": "^8.1.0",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"@tanstack/react-query-devtools": "^5.79.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.79.0",
|
||||
"@trpc/client": "^11.1.4",
|
||||
"@trpc/next": "^11.1.4",
|
||||
"@trpc/react-query": "^11.1.4",
|
||||
"@trpc/server": "^11.1.4",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@tanstack/react-query-devtools": "^5.80.6",
|
||||
"@tanstack/react-query-next-experimental": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/next": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -81,24 +81,24 @@
|
||||
"react-dom": "19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.89.0",
|
||||
"sass": "^1.89.2",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.22.0",
|
||||
"swagger-ui-react": "^5.24.1",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.15.28",
|
||||
"@types/node": "^22.15.31",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.6",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import Link from "next/link";
|
||||
import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core";
|
||||
import { Card, Group, SimpleGrid, Space, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { isProviderEnabled } from "@homarr/auth/server";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { catchTrpcNotFound } from "~/errors/trpc-catch-error";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { HeroBanner } from "./_components/hero-banner";
|
||||
|
||||
@@ -30,6 +33,8 @@ export async function generateMetadata() {
|
||||
export default async function ManagementPage() {
|
||||
const statistics = await api.home.getStats();
|
||||
const session = await auth();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
const suggestions = isAdmin ? await api.integration.suggestedIntegrations().catch(catchTrpcNotFound) : [];
|
||||
const t = await getScopedI18n("management.page.home");
|
||||
|
||||
const links: LinkProps[] = [
|
||||
@@ -102,6 +107,37 @@ export default async function ManagementPage() {
|
||||
),
|
||||
)}
|
||||
</SimpleGrid>
|
||||
{suggestions.length >= 1 && (
|
||||
<>
|
||||
<Space h="md" />
|
||||
<Stack gap="sm">
|
||||
<Stack gap={0}>
|
||||
<Title order={3}>Suggested integrations</Title>
|
||||
<Text c="dimmed">Based on your Docker containers</Text>
|
||||
</Stack>
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{suggestions.slice(0, 6).map((suggestion) => (
|
||||
<Card key={suggestion.kind} component={Link} href="#" radius="lg">
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<IntegrationAvatar kind={suggestion.kind} size="sm" />
|
||||
<Stack gap={0}>
|
||||
<Text size="md" fw={500}>
|
||||
{getIntegrationName(suggestion.kind)}
|
||||
</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
Show active media streams
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<IconArrowRight />
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -44,12 +44,12 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.15.28",
|
||||
"@types/node": "^22.15.31",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -39,21 +39,21 @@
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.5.4",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"@vitest/ui": "^3.1.4",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@vitest/coverage-v8": "^3.2.3",
|
||||
"@vitest/ui": "^3.2.3",
|
||||
"conventional-changelog-conventionalcommits": "^9.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"semantic-release": "^24.2.5",
|
||||
"testcontainers": "^10.28.0",
|
||||
"testcontainers": "^11.0.3",
|
||||
"turbo": "^2.5.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.4"
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"packageManager": "pnpm@10.12.1",
|
||||
"engines": {
|
||||
"node": ">=22.16.0"
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -41,24 +41,24 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"@trpc/client": "^11.1.4",
|
||||
"@trpc/react-query": "^11.1.4",
|
||||
"@trpc/server": "^11.1.4",
|
||||
"@trpc/tanstack-react-query": "^11.1.4",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@trpc/tanstack-react-query": "^11.3.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.3.1",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -14,15 +14,19 @@ import {
|
||||
integrationUserPermissions,
|
||||
searchEngines,
|
||||
} from "@homarr/db/schema";
|
||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||
import {
|
||||
getIconUrl,
|
||||
getIntegrationKindsByCategory,
|
||||
getIntegrationName,
|
||||
getPermissionsWithParents,
|
||||
integrationDefs,
|
||||
integrationKinds,
|
||||
integrationSecretKindObject,
|
||||
} from "@homarr/definitions";
|
||||
import type { ContainerInfo, Port } from "@homarr/docker";
|
||||
import { DockerSingleton } from "@homarr/docker";
|
||||
import { env } from "@homarr/docker/env";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { logger } from "@homarr/log";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
@@ -527,8 +531,169 @@ export const integrationRouter = createTRPCRouter({
|
||||
const integrationInstance = await createIntegrationAsync(ctx.integration);
|
||||
return await integrationInstance.searchAsync(encodeURI(input.query));
|
||||
}),
|
||||
suggestedIntegrations: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
||||
if (!env.ENABLE_DOCKER)
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Docker is not enabled",
|
||||
});
|
||||
|
||||
const dockerInstances = DockerSingleton.getInstances();
|
||||
if (dockerInstances.length === 0) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "No Docker instances found",
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove those that were already added
|
||||
const containers = await Promise.all(
|
||||
dockerInstances.map(({ instance, host, isSocket }) =>
|
||||
instance.listContainers().then((containers) =>
|
||||
containers.map((containerInfo) => ({
|
||||
info: containerInfo,
|
||||
host,
|
||||
isSocket,
|
||||
})),
|
||||
),
|
||||
),
|
||||
).then((results) => results.flat());
|
||||
|
||||
// Default docker hostname consist out of first 12 characters of the container id
|
||||
const homarrContainer = containers.find((container) => container.info.Id.slice(0, 12) === process.env.HOSTNAME);
|
||||
|
||||
const suggestions = containers
|
||||
.map((container) => getSuggestionFromContainer(container, homarrContainer))
|
||||
.filter((suggestion) => suggestion !== null);
|
||||
|
||||
const groupedSuggestions = suggestions.reduce(
|
||||
(acc, suggestion) => {
|
||||
acc[suggestion.kind] ??= [];
|
||||
// is defined above
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
acc[suggestion.kind]!.push(suggestion);
|
||||
return acc;
|
||||
},
|
||||
{} as Partial<Record<IntegrationKind, IntegrationSuggestion[]>>,
|
||||
);
|
||||
|
||||
return Object.entries(groupedSuggestions)
|
||||
.map(([kind, suggestions]) => ({
|
||||
kind: kind as IntegrationKind,
|
||||
suggestions,
|
||||
}))
|
||||
.sort((itemA, itemB) => {
|
||||
if (itemA.suggestions.length !== itemB.suggestions.length)
|
||||
return itemB.suggestions.length - itemA.suggestions.length;
|
||||
|
||||
const createdAtA = Math.max(...itemA.suggestions.map((suggestion) => suggestion.createdAt?.getTime() ?? 0));
|
||||
const createdAtB = Math.max(...itemB.suggestions.map((suggestion) => suggestion.createdAt?.getTime() ?? 0));
|
||||
return createdAtB - createdAtA;
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
interface IntegrationSuggestion {
|
||||
containerId: string;
|
||||
name: string;
|
||||
internalUrl: string | null;
|
||||
externalUrl: string | null;
|
||||
kind: IntegrationKind;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
interface ContainerInfoWithHost {
|
||||
info: ContainerInfo;
|
||||
host: string;
|
||||
isSocket: boolean | undefined;
|
||||
}
|
||||
|
||||
const getSuggestionFromContainer = (
|
||||
container: ContainerInfoWithHost,
|
||||
homarrContainer: ContainerInfoWithHost | undefined,
|
||||
): IntegrationSuggestion | null => {
|
||||
const integrationKind = getSuggestionKind(container.info);
|
||||
|
||||
if (!integrationKind) return null;
|
||||
|
||||
return {
|
||||
containerId: container.info.Id,
|
||||
name: getIntegrationName(integrationKind),
|
||||
kind: integrationKind,
|
||||
internalUrl: getInternalUrl(container, homarrContainer),
|
||||
externalUrl: getExternalUrl(container),
|
||||
createdAt: container.info.Created ? new Date(container.info.Created * 1000) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const getSuggestionKind = (container: ContainerInfo): IntegrationKind | null => {
|
||||
const [image, _tag] = container.Image.split(":");
|
||||
if (!image) return null;
|
||||
return (
|
||||
objectEntries(integrationDefs).find(
|
||||
([, integration]) =>
|
||||
"containerImages" in integration &&
|
||||
integration.containerImages.some((containerImage) => containerImage.toLowerCase() === image.toLowerCase()),
|
||||
)?.[0] ?? null
|
||||
);
|
||||
};
|
||||
|
||||
const calculatePortScore = (port: Port): number => {
|
||||
let score = 0;
|
||||
|
||||
if (port.PublicPort) score += 10; // Public ports are more likely to be used
|
||||
if (port.PrivatePort % 1000 === 80) score += 5; // Default http ports are most likely suffixed with 80 like 8080 or 80
|
||||
if (port.PrivatePort % 1000 === 443) score += 10; // Default https ports are most likely suffixed with 443 like 8443 or 443
|
||||
return score;
|
||||
};
|
||||
|
||||
const getPort = (container: ContainerInfoWithHost): Port | null => {
|
||||
const ports = container.info.Ports.filter((port) => port.Type === "tcp");
|
||||
return (
|
||||
ports
|
||||
.map((port) => ({ port, score: calculatePortScore(port) }))
|
||||
.sort((portA, portB) => portB.score - portA.score)
|
||||
.at(0)?.port ?? null
|
||||
);
|
||||
};
|
||||
|
||||
const getInternalUrl = (
|
||||
container: ContainerInfoWithHost,
|
||||
homarrContainer: ContainerInfoWithHost | undefined,
|
||||
): string | null => {
|
||||
const port = getPort(container);
|
||||
if (!port) return null;
|
||||
const protocol = port.PrivatePort % 1000 === 443 ? "https" : "http";
|
||||
|
||||
if (homarrContainer) {
|
||||
const matchingNetwork = Object.entries(homarrContainer.info.NetworkSettings.Networks).find(
|
||||
([_key, homarrNetwork]) =>
|
||||
Object.entries(container.info.NetworkSettings.Networks).some(
|
||||
([_name, network]) => homarrNetwork.NetworkID === network.NetworkID,
|
||||
),
|
||||
)?.[1];
|
||||
if (matchingNetwork) return `${protocol}://${matchingNetwork.IPAddress}:${port.PrivatePort}`;
|
||||
}
|
||||
|
||||
// TODO: Let user specify the internal ip-address for integrations --> use as default before falling back to host.docker.internal
|
||||
if (container.host === homarrContainer?.host && container.isSocket === homarrContainer.isSocket) {
|
||||
return `${protocol}://host.docker.internal:${port.PublicPort}`;
|
||||
}
|
||||
|
||||
// TODO: Change this
|
||||
return `${protocol}://localhost:${port.PublicPort}`;
|
||||
};
|
||||
|
||||
const getExternalUrl = (container: ContainerInfoWithHost): string | null => {
|
||||
const port = getPort(container);
|
||||
if (!port) return null;
|
||||
const protocol = port.PrivatePort % 1000 === 443 ? "https" : "http";
|
||||
|
||||
// TODO: Use some labels like "traefik.host" to determine the external url
|
||||
// For now we just use localhost as external url
|
||||
return `${protocol}://localhost:${port.PublicPort}`;
|
||||
};
|
||||
|
||||
interface UpdateSecretInput {
|
||||
integrationId: string;
|
||||
value: string;
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
"next-auth": "5.0.0-beta.28",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookies": "0.9.0",
|
||||
"eslint": "^9.27.0",
|
||||
"@types/cookies": "0.9.1",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -34,14 +34,14 @@
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"undici": "7.10.0",
|
||||
"zod": "^3.25.42",
|
||||
"zod": "^3.25.62",
|
||||
"zod-validation-error": "^3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"node-cron": "^4.0.7"
|
||||
"node-cron": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions";
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { dnsHoleRequestHandler } from "@homarr/request-handler/dns-hole";
|
||||
import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler";
|
||||
|
||||
import { createCronJob } from "../../lib";
|
||||
|
||||
export const dnsHoleJob = createCronJob("dnsHole", EVERY_5_SECONDS).withCallback(
|
||||
export const dnsHoleJob = createCronJob("dnsHole", EVERY_MINUTE).withCallback(
|
||||
createRequestIntegrationJobHandler(dnsHoleRequestHandler.handler, {
|
||||
widgetKinds: ["dnsHoleSummary", "dnsHoleControls"],
|
||||
getInput: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
@@ -10,7 +10,7 @@ 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 () => {
|
||||
export const rssFeedsJob = createCronJob("rssFeeds", EVERY_10_MINUTES).withCallback(async () => {
|
||||
const rssItems = await db.query.items.findMany({
|
||||
where: eq(items.kind, "rssFeed"),
|
||||
});
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^10.28.0",
|
||||
"@testcontainers/mysql": "^11.0.3",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"drizzle-orm": "^0.44.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"mysql2": "3.14.1"
|
||||
},
|
||||
@@ -61,9 +61,9 @@
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import * as mysqlSchema from "../schema/mysql";
|
||||
|
||||
describe("Mysql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
const mysqlContainer = await new MySqlContainer().start();
|
||||
const mysqlContainer = await new MySqlContainer("mysql:latest").start();
|
||||
|
||||
const connection = mysql.createConnection({
|
||||
host: mysqlContainer.getHost(),
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.2.3",
|
||||
"zod": "^3.25.42"
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"tsx": "4.19.4",
|
||||
"eslint": "^9.28.0",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ interface integrationDefinition {
|
||||
iconUrl: string;
|
||||
secretKinds: AtLeastOneOf<IntegrationSecretKind[]>; // at least one secret kind set is required
|
||||
category: AtLeastOneOf<IntegrationCategory>;
|
||||
containerImages?: string[]; // Optional, used for container images to suggest in the UI
|
||||
}
|
||||
|
||||
export const integrationDefs = {
|
||||
@@ -24,30 +25,45 @@ export const integrationDefs = {
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
containerImages: ["linuxserver/sabnzbd", "lscr.io/linuxserver/sabnzbd", "ghcr.io/linuxserver/sabnzbd"],
|
||||
},
|
||||
nzbGet: {
|
||||
name: "NZBGet",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
containerImages: [
|
||||
"nzbgetcom/nzbget",
|
||||
"ghcr.io/nzbgetcom/nzbget",
|
||||
"linuxserver/nzbget",
|
||||
"lscr.io/linuxserver/nzbget",
|
||||
"ghcr.io/linuxserver/nzbget",
|
||||
],
|
||||
},
|
||||
deluge: {
|
||||
name: "Deluge",
|
||||
secretKinds: [["password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/deluge.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: ["linuxserver/deluge", "lscr.io/linuxserver/deluge", "ghcr.io/linuxserver/deluge"],
|
||||
},
|
||||
transmission: {
|
||||
name: "Transmission",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/transmission.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: [
|
||||
"linuxserver/transmission",
|
||||
"lscr.io/linuxserver/transmission",
|
||||
"ghcr.io/linuxserver/transmission",
|
||||
],
|
||||
},
|
||||
qBittorrent: {
|
||||
name: "qBittorrent",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: ["linuxserver/qbittorrent", "lscr.io/linuxserver/qbittorrent", "ghcr.io/linuxserver/qbittorrent"],
|
||||
},
|
||||
aria2: {
|
||||
name: "Aria2",
|
||||
@@ -60,78 +76,144 @@ export const integrationDefs = {
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/sonarr",
|
||||
"ghcr.io/hotio/sonarr",
|
||||
"linuxserver/sonarr",
|
||||
"lscr.io/linuxserver/sonarr",
|
||||
"ghcr.io/linuxserver/sonarr",
|
||||
],
|
||||
},
|
||||
radarr: {
|
||||
name: "Radarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/radarr",
|
||||
"ghcr.io/hotio/radarr",
|
||||
"linuxserver/radarr",
|
||||
"lscr.io/linuxserver/radarr",
|
||||
"ghcr.io/linuxserver/radarr",
|
||||
],
|
||||
},
|
||||
lidarr: {
|
||||
name: "Lidarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/lidarr",
|
||||
"ghcr.io/hotio/lidarr",
|
||||
"linuxserver/lidarr",
|
||||
"lscr.io/linuxserver/lidarr",
|
||||
"ghcr.io/linuxserver/lidarr",
|
||||
],
|
||||
},
|
||||
readarr: {
|
||||
name: "Readarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/readarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/readarr",
|
||||
"ghcr.io/hotio/readarr",
|
||||
"linuxserver/readarr",
|
||||
"lscr.io/linuxserver/readarr",
|
||||
"ghcr.io/linuxserver/readarr",
|
||||
],
|
||||
},
|
||||
prowlarr: {
|
||||
name: "Prowlarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg",
|
||||
category: ["indexerManager"],
|
||||
containerImages: [
|
||||
"hotio/prowlarr",
|
||||
"ghcr.io/hotio/prowlarr",
|
||||
"linuxserver/prowlarr",
|
||||
"lscr.io/linuxserver/prowlarr",
|
||||
"ghcr.io/linuxserver/prowlarr",
|
||||
],
|
||||
},
|
||||
jellyfin: {
|
||||
name: "Jellyfin",
|
||||
secretKinds: [["username", "password"], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: [
|
||||
"jellyfin/jellyfin",
|
||||
"ghcr.io/jellyfin/jellyfin",
|
||||
"linuxserver/jellyfin",
|
||||
"lscr.io/linuxserver/jellyfin",
|
||||
"ghcr.io/linuxserver/jellyfin",
|
||||
],
|
||||
},
|
||||
emby: {
|
||||
name: "Emby",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: [
|
||||
"emby/embyserver",
|
||||
"emby/embyserver_arm64v8",
|
||||
"emby/embyserver_arm32v7",
|
||||
"linuxserver/emby",
|
||||
"lscr.io/linuxserver/emby",
|
||||
"ghcr.io/linuxserver/emby",
|
||||
],
|
||||
},
|
||||
plex: {
|
||||
name: "Plex",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: ["plexinc/pms-docker", "linuxserver/plex", "lscr.io/linuxserver/plex", "ghcr.io/linuxserver/plex"],
|
||||
},
|
||||
jellyseerr: {
|
||||
name: "Jellyseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
containerImages: ["fallenbagel/jellyseerr", "ghcr.io/fallenbagel/jellyseerr"],
|
||||
},
|
||||
overseerr: {
|
||||
name: "Overseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
containerImages: [
|
||||
"sct/overseerr",
|
||||
"linuxserver/overseerr",
|
||||
"lscr.io/linuxserver/overseerr",
|
||||
"ghcr.io/linuxserver/overseerr",
|
||||
],
|
||||
},
|
||||
piHole: {
|
||||
name: "Pi-hole",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg",
|
||||
category: ["dnsHole"],
|
||||
containerImages: ["pihole/pihole"],
|
||||
},
|
||||
adGuardHome: {
|
||||
name: "AdGuard Home",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/adguard-home.svg",
|
||||
category: ["dnsHole"],
|
||||
containerImages: ["adguard/adguardhome"],
|
||||
},
|
||||
homeAssistant: {
|
||||
name: "Home Assistant",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
|
||||
category: ["smartHomeServer"],
|
||||
containerImages: [
|
||||
"homeassistant/home-assistant",
|
||||
"linuxserver/home-assistant",
|
||||
"lscr.io/linuxserver/home-assistant",
|
||||
"ghcr.io/linuxserver/home-assistant",
|
||||
],
|
||||
},
|
||||
openmediavault: {
|
||||
name: "OpenMediaVault",
|
||||
@@ -144,12 +226,14 @@ export const integrationDefs = {
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/dashdot.png",
|
||||
category: ["healthMonitoring"],
|
||||
containerImages: ["mauricenino/dashdot", "ghcr.io/mauricenino/dashdot"],
|
||||
},
|
||||
tdarr: {
|
||||
name: "Tdarr",
|
||||
secretKinds: [[], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png",
|
||||
category: ["mediaTranscoding"],
|
||||
containerImages: ["haveagitgat/tdarr", "ghcr.io/haveagitgat/tdarr"],
|
||||
},
|
||||
proxmox: {
|
||||
name: "Proxmox",
|
||||
@@ -168,6 +252,16 @@ export const integrationDefs = {
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png",
|
||||
category: ["networkController"],
|
||||
containerImages: [
|
||||
"jacobalberty/unifi",
|
||||
"ghcr.io/jacobalberty/unifi",
|
||||
"linuxserver/unifi-controller",
|
||||
"lscr.io/linuxserver/unifi-controller",
|
||||
"ghcr.io/linuxserver/unifi-controller",
|
||||
"linuxserver/unifi-network-application",
|
||||
"lscr.io/linuxserver/unifi-network-application",
|
||||
"ghcr.io/linuxserver/unifi-network-application",
|
||||
],
|
||||
},
|
||||
} as const satisfies Record<string, integrationDefinition>;
|
||||
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"dockerode": "^4.0.6"
|
||||
"dockerode": "^4.0.7"
|
||||
},
|
||||
"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.39",
|
||||
"eslint": "^9.27.0",
|
||||
"@types/dockerode": "^3.3.40",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { env } from "./env";
|
||||
export interface DockerInstance {
|
||||
host: string;
|
||||
instance: Docker;
|
||||
isSocket?: boolean;
|
||||
}
|
||||
|
||||
export class DockerSingleton {
|
||||
@@ -14,7 +15,7 @@ export class DockerSingleton {
|
||||
const hostVariable = env.DOCKER_HOSTNAMES;
|
||||
const portVariable = env.DOCKER_PORTS;
|
||||
if (hostVariable === undefined || portVariable === undefined) {
|
||||
return [{ host: "socket", instance: new Docker() }];
|
||||
return [{ host: "socket", instance: new Docker(), isSocket: true }];
|
||||
}
|
||||
const hostnames = hostVariable.split(",");
|
||||
const ports = portVariable.split(",");
|
||||
|
||||
Vendored
+3
-3
@@ -23,14 +23,14 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.6",
|
||||
"zod": "^3.25.42"
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,14 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^8.0.2",
|
||||
"zod": "^3.25.42"
|
||||
"@mantine/form": "^8.1.0",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,15 @@
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"react": "19.1.0",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"tsdav": "^2.1.4",
|
||||
"undici": "7.10.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-unifi": "^2.5.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
import dayjs from "dayjs";
|
||||
import objectSupport from "dayjs/plugin/objectSupport";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import * as ical from "node-ical";
|
||||
import { DAVClient } from "tsdav";
|
||||
import type { Dispatcher, RequestInit as UndiciFetchRequestInit } from "undici";
|
||||
@@ -15,9 +12,6 @@ import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { CalendarEvent } from "../calendar-types";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(objectSupport);
|
||||
|
||||
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
||||
export class NextcloudIntegration extends Integration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
@@ -27,7 +21,7 @@ export class NextcloudIntegration extends Integration {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||
public async getCalendarEventsAsync(start: Date, end: Date, _showUnmonitored?: boolean): Promise<CalendarEvent[]> {
|
||||
const client = await this.createCalendarClientAsync();
|
||||
await client.login();
|
||||
|
||||
@@ -57,14 +51,7 @@ export class NextcloudIntegration extends Integration {
|
||||
|
||||
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
|
||||
|
||||
const date = dayjs.utc({
|
||||
days: veventObject.start.getDay(),
|
||||
month: veventObject.start.getMonth(),
|
||||
year: veventObject.start.getFullYear(),
|
||||
hours: veventObject.start.getHours(),
|
||||
minutes: veventObject.start.getMinutes(),
|
||||
seconds: veventObject.start.getSeconds(),
|
||||
});
|
||||
const date = veventObject.start;
|
||||
|
||||
const eventUrlWithoutHost = new URL(event.url).pathname;
|
||||
const dateInMillis = veventObject.start.valueOf();
|
||||
@@ -75,7 +62,7 @@ export class NextcloudIntegration extends Integration {
|
||||
|
||||
return {
|
||||
name: veventObject.summary,
|
||||
date: date.toDate(),
|
||||
date,
|
||||
subName: "",
|
||||
description: veventObject.description,
|
||||
links: [
|
||||
|
||||
@@ -66,7 +66,7 @@ export class OpenMediaVaultIntegration extends Integration {
|
||||
|
||||
return {
|
||||
version: systemResult.data.response.version,
|
||||
cpuModelName: systemResult.data.response.cpuModelName,
|
||||
cpuModelName: systemResult.data.response.cpuModelName ?? "Unknown CPU",
|
||||
cpuUtilization: systemResult.data.response.cpuUtilization,
|
||||
memUsed: systemResult.data.response.memUsed,
|
||||
memAvailable: systemResult.data.response.memAvailable,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
||||
export const systemInformationSchema = z.object({
|
||||
response: z.object({
|
||||
version: z.string(),
|
||||
cpuModelName: z.string(),
|
||||
cpuModelName: z.string().nullable(),
|
||||
cpuUtilization: z.number(),
|
||||
memUsed: z.string(),
|
||||
memAvailable: z.string(),
|
||||
|
||||
@@ -77,6 +77,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
protected async testingAsync({ fetchAsync }: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const sessionId = await this.getSessionAsync(fetchAsync);
|
||||
await this.clearSessionAsync(sessionId, fetchAsync);
|
||||
await this.sessionStore.clearAsync();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -64,26 +64,29 @@ describe("Home Assistant integration", () => {
|
||||
const prepareHomeAssistantContainerAsync = async () => {
|
||||
const homeAssistantContainer = createHomeAssistantContainer();
|
||||
const startedContainer = await homeAssistantContainer.start();
|
||||
|
||||
await startedContainer.exec(["unzip", "-o", "/tmp/config.zip", "-d", "/config"]);
|
||||
await startedContainer.restart();
|
||||
return startedContainer;
|
||||
};
|
||||
|
||||
const createHomeAssistantContainer = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/home-assistant-config.zip"),
|
||||
target: "/tmp/config.zip",
|
||||
},
|
||||
])
|
||||
.withPrivilegedMode()
|
||||
.withExposedPorts(8123)
|
||||
.withWaitStrategy(Wait.forHttp("/", 8123));
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/home-assistant-config.zip"),
|
||||
target: "/tmp/config.zip",
|
||||
},
|
||||
])
|
||||
.withPrivilegedMode()
|
||||
.withExposedPorts(8123)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/onboarding.html", 8123))
|
||||
);
|
||||
};
|
||||
|
||||
const createHomeAssistantIntegration = (container: StartedTestContainer, apiKeyOverride?: string) => {
|
||||
console.log("Creating Home Assistant integration...");
|
||||
return new HomeAssistantIntegration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
|
||||
@@ -145,8 +145,8 @@ describe("Nzbget integration", () => {
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
// NzbGet is slow and we wait for a second before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// NzbGet is slow and we wait for a few seconds before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const result = await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
expect(result.items).toHaveLength(0);
|
||||
|
||||
|
||||
@@ -211,12 +211,15 @@ const createPiHoleIntegrationV5 = (container: StartedTestContainer, apiKey: stri
|
||||
};
|
||||
|
||||
const createPiHoleV6Container = (password: string) => {
|
||||
return new GenericContainer("pihole/pihole:latest")
|
||||
.withEnvironment({
|
||||
FTLCONF_webserver_api_password: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
.withWaitStrategy(Wait.forHttp("/admin", 80));
|
||||
return (
|
||||
new GenericContainer("pihole/pihole:latest")
|
||||
.withEnvironment({
|
||||
FTLCONF_webserver_api_password: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/admin/login", 80))
|
||||
);
|
||||
};
|
||||
|
||||
const createPiHoleIntegrationV6 = (container: StartedTestContainer, apiKey: string) => {
|
||||
|
||||
@@ -192,16 +192,19 @@ describe("Sabnzbd integration", () => {
|
||||
});
|
||||
|
||||
const createSabnzbdContainer = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/sabnzbd.ini"),
|
||||
target: "/config/sabnzbd.ini",
|
||||
},
|
||||
])
|
||||
.withExposedPorts(1212)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
.withWaitStrategy(Wait.forHttp("/", 1212));
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/sabnzbd.ini"),
|
||||
target: "/config/sabnzbd.ini",
|
||||
},
|
||||
])
|
||||
.withExposedPorts(1212)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/sabnzbd/wizard/", 1212))
|
||||
);
|
||||
};
|
||||
|
||||
const createSabnzbdIntegration = (container: StartedTestContainer, apiKey: string) => {
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
"ioredis": "5.6.1",
|
||||
"superjson": "2.2.2",
|
||||
"winston": "3.17.0",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,19 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,15 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"react": "19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0"
|
||||
"@mantine/notifications": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,14 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^3.25.42",
|
||||
"zod": "^3.25.62",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/adm-zip": "0.5.7",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,13 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"zod": "^3.25.42"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.0.2",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,10 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/spotlight": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@mantine/spotlight": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"jotai": "^2.12.5",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -263,14 +263,14 @@
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
"label": "删除图像",
|
||||
"label": "删除头像",
|
||||
"confirm": "确定要移除这张图片吗?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": "图像删除成功"
|
||||
},
|
||||
"error": {
|
||||
"message": "无法删除图像"
|
||||
"message": "无法删除头像"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,17 +612,17 @@
|
||||
"select": {
|
||||
"label": "选择应用",
|
||||
"notFound": "未找到应用",
|
||||
"search": "",
|
||||
"noResults": "",
|
||||
"action": "",
|
||||
"title": ""
|
||||
"search": "搜索应用",
|
||||
"noResults": "没有结果",
|
||||
"action": "选择 {app}",
|
||||
"title": "选择一个应用程序添加到此面板"
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"action": ""
|
||||
"title": "创建应用",
|
||||
"description": "创建应用 ",
|
||||
"action": "打开应用创建"
|
||||
},
|
||||
"add": ""
|
||||
"add": "添加应用"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -705,125 +705,125 @@
|
||||
"error": {
|
||||
"common": {
|
||||
"cause": {
|
||||
"title": ""
|
||||
"title": "更多详情"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "未知错误",
|
||||
"description": "发生未知错误,打开下面的原因以查看更多详情"
|
||||
},
|
||||
"parse": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "解析错误",
|
||||
"description": "无法解析该响应。请确认URL指向服务的基本URL。"
|
||||
},
|
||||
"authorization": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "授权错误",
|
||||
"description": "请求未被授权。请验证凭据是否正确,并且您拥有足够的权限。"
|
||||
},
|
||||
"statusCode": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"otherDescription": "",
|
||||
"title": "响应错误",
|
||||
"description": "收到来自<url></url>的意外{statusCode} ({reason})响应。请验证URL是否指向集成的基本URL。",
|
||||
"otherDescription": "从<url></url>收到意外的{statusCode}响应。请验证URL是否指向集成的基本URL。",
|
||||
"reason": {
|
||||
"badRequest": "",
|
||||
"notFound": "",
|
||||
"tooManyRequests": "",
|
||||
"internalServerError": "",
|
||||
"serviceUnavailable": "",
|
||||
"gatewayTimeout": ""
|
||||
"badRequest": "错误请求",
|
||||
"notFound": "未找到",
|
||||
"tooManyRequests": "请求过于频繁",
|
||||
"internalServerError": "服务器内部错误",
|
||||
"serviceUnavailable": "服务暂时不可用",
|
||||
"gatewayTimeout": "网关超时"
|
||||
}
|
||||
},
|
||||
"certificate": {
|
||||
"title": "",
|
||||
"title": "证书错误",
|
||||
"description": {
|
||||
"expired": "",
|
||||
"notYetValid": "",
|
||||
"untrusted": "",
|
||||
"hostnameMismatch": ""
|
||||
"expired": "此证书已经过期。",
|
||||
"notYetValid": "此证书尚未生效。",
|
||||
"untrusted": "此证书不受信任。",
|
||||
"hostnameMismatch": "证书主机名与 URL 不匹配。"
|
||||
},
|
||||
"alert": {
|
||||
"permission": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "权限不足",
|
||||
"message": "您无权信任或上传证书。请与您的管理员联系上传必要的根证书。"
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名不匹配",
|
||||
"message": "证书中的主机名与您连接的主机名不匹配。 这可能会显示安全风险,但您仍然可以选择信任此证书。"
|
||||
},
|
||||
"extract": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "CA证书提取失败",
|
||||
"message": "只有自签署证书链可以自动获取。如果使用的是自签名证书,请确保手动上传CA证书。您可以找到有关如何执行此操作的说明<docsLink></docsLink>。"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"retry": {
|
||||
"label": ""
|
||||
"label": "重试创建"
|
||||
},
|
||||
"trust": {
|
||||
"label": ""
|
||||
"label": "信任证书"
|
||||
},
|
||||
"upload": {
|
||||
"label": ""
|
||||
"label": "上传证书"
|
||||
}
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任主机名不匹配",
|
||||
"message": "您确定要信任主机名不匹配的证书吗?"
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "可信证书",
|
||||
"message": "将主机名添加到受信任证书列表中"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任证书失败",
|
||||
"message": "主机名不匹配的证书不可信"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selfSigned": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任自签名证书",
|
||||
"message": "您确定要信任此自签名证书吗?"
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "可信证书",
|
||||
"message": "已添加证书到可信证书列表"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任证书失败",
|
||||
"message": "向受信任证书列表添加证书失败"
|
||||
}
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "详情",
|
||||
"description": "在决定信任证书信息之前,请检查该证书信息。",
|
||||
"content": {
|
||||
"action": "",
|
||||
"title": ""
|
||||
"action": "显示内容",
|
||||
"title": "PEM 证书"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"title": "",
|
||||
"title": "错误请求",
|
||||
"description": {
|
||||
"connection": {
|
||||
"hostUnreachable": "",
|
||||
"networkUnreachable": "",
|
||||
"refused": "",
|
||||
"reset": ""
|
||||
"hostUnreachable": "无法连接到服务器。这通常意味着主机离线或无法从您的网络访问。",
|
||||
"networkUnreachable": "网络不可达。请检查网络连接或网络配置。",
|
||||
"refused": "服务器拒绝连接。它可能没有运行或正在拒绝指定端口上的请求。",
|
||||
"reset": "服务器意外关闭了连接。如果服务器不稳定或重新启动,就会发生这种情况。"
|
||||
},
|
||||
"dns": {
|
||||
"notFound": "",
|
||||
"timeout": "",
|
||||
"noAnswer": ""
|
||||
"notFound": "找不到服务器地址。请检查URL是否有拼写错误或无效的域名。",
|
||||
"timeout": "DNS查找超时。这可能是暂时的问题,请稍后再试。",
|
||||
"noAnswer": "DNS服务器没有返回有效的响应。域可能存在,但没有有效记录。"
|
||||
},
|
||||
"timeout": {
|
||||
"aborted": "",
|
||||
"timeout": ""
|
||||
"aborted": "请求在完成之前被中止。这可能是由于用户操作或系统超时造成的。",
|
||||
"timeout": "请求耗时太长。请检查您的网络或稍后再试。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -895,8 +895,8 @@
|
||||
"message": "该路径可能不正确"
|
||||
},
|
||||
"tooManyRequests": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "指定时间内请求过多",
|
||||
"message": "请求太多了。您可能会受到速率限制或被目标系统拒绝"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1004,7 +1004,7 @@
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"discard": "Discard",
|
||||
"close": "",
|
||||
"close": "关闭",
|
||||
"confirm": "确认",
|
||||
"continue": "继续",
|
||||
"previous": "上一步",
|
||||
@@ -1220,7 +1220,7 @@
|
||||
"label": "集成"
|
||||
},
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "标题"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": "自定义 CSS 类"
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"label": "显示内存信息"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
"label": "显示运行时间"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "显示文件系统信息"
|
||||
@@ -1767,7 +1767,7 @@
|
||||
"label": "默认标签"
|
||||
},
|
||||
"visibleClusterSections": {
|
||||
"label": ""
|
||||
"label": "可见集群部分"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "部分指标要求"
|
||||
@@ -1839,11 +1839,11 @@
|
||||
}
|
||||
},
|
||||
"dockerContainers": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Docker 状态",
|
||||
"description": "您的容器状态 (这个小部件只能用管理员权限添加)",
|
||||
"option": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "获取容器统计信息失败"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1912,8 +1912,8 @@
|
||||
"description": "显示媒体服务器上的当前流",
|
||||
"option": {
|
||||
"showOnlyPlaying": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只显示当前正在播放",
|
||||
"description": "禁用此功能对plex不起作用"
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
@@ -1946,7 +1946,7 @@
|
||||
"label": "显示标记为已完成的 Torrent 条目"
|
||||
},
|
||||
"showCompletedHttp": {
|
||||
"label": ""
|
||||
"label": "显示标记为已完成的杂项"
|
||||
},
|
||||
"activeTorrentThreshold": {
|
||||
"label": "隐藏在此阈值下已完成的 Torrent (kiB/s)"
|
||||
@@ -1961,8 +1961,8 @@
|
||||
"label": "使用过滤器来计算比率"
|
||||
},
|
||||
"limitPerIntegration": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "限制每个集成的项目",
|
||||
"description": "这将限制每个集成显示的项目数量,而不是全局项目"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -2095,7 +2095,7 @@
|
||||
"approved": "已批准",
|
||||
"declined": "已拒绝",
|
||||
"failed": "失败",
|
||||
"completed": ""
|
||||
"completed": "已完成"
|
||||
},
|
||||
"toBeDetermined": "待定"
|
||||
},
|
||||
@@ -2199,94 +2199,106 @@
|
||||
}
|
||||
},
|
||||
"releases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "发布",
|
||||
"description": "显示特定版本正则表达式中当前版本的列表。",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "内部新版本",
|
||||
"description": "使用示例:1w(1周),10M(10个月)。支持单位类型h(小时),d(天),w(周),M(月),y(年)。留空不突出显示新版本。"
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "内部过期发布",
|
||||
"description": "使用示例:1w(1周),10M(10个月)。支持单位类型h(小时),d(天),w(周),M(月),y(年)。留空不突出显示过时的版本。"
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只显示高亮显示",
|
||||
"description": "只显示新的或过时的版本。如上所述。"
|
||||
},
|
||||
"showDetails": {
|
||||
"label": ""
|
||||
"label": "展开详情"
|
||||
},
|
||||
"topReleases": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "热门发布",
|
||||
"description": "要显示的最新版本的最大数量。零表示没有限制。"
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"label": ""
|
||||
"label": "仓库",
|
||||
"addRepository": {
|
||||
"label": "添加仓库"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "从 Docker 导入",
|
||||
"loading": "正在加载 docker 映像",
|
||||
"noImagesFound": "没有找到 docker 映像",
|
||||
"listFoundImages": "找到的映像列表",
|
||||
"listAlreadyImportedImages": "已导入的映像列表",
|
||||
"allImagesAlreadyImported": "所有映像已导入",
|
||||
"onlyAdminCanImport": "只有管理员才能从 docker 导入"
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
"label": "提供者"
|
||||
},
|
||||
"identifier": {
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
"label": "标识符",
|
||||
"placeholder": "姓名或所有者名称"
|
||||
},
|
||||
"name": {
|
||||
"label": ""
|
||||
"label": "名称"
|
||||
},
|
||||
"versionFilter": {
|
||||
"label": "",
|
||||
"label": "版本过滤器",
|
||||
"prefix": {
|
||||
"label": ""
|
||||
"label": "前缀"
|
||||
},
|
||||
"precision": {
|
||||
"label": "",
|
||||
"label": "精度",
|
||||
"options": {
|
||||
"none": ""
|
||||
"none": "空"
|
||||
}
|
||||
},
|
||||
"suffix": {
|
||||
"label": ""
|
||||
"label": "后缀"
|
||||
},
|
||||
"regex": {
|
||||
"label": ""
|
||||
"label": "正则表达式"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"label": ""
|
||||
"label": "编辑"
|
||||
},
|
||||
"editForm": {
|
||||
"title": "",
|
||||
"title": "编辑仓库",
|
||||
"cancel": {
|
||||
"label": ""
|
||||
"label": "取消"
|
||||
},
|
||||
"confirm": {
|
||||
"label": ""
|
||||
"label": "确认"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
"importForm": {
|
||||
"title": "从 Docker 导入"
|
||||
},
|
||||
"invalid": ""
|
||||
"example": {
|
||||
"label": "示例"
|
||||
},
|
||||
"invalid": "仓库定义无效,请检查值"
|
||||
}
|
||||
},
|
||||
"not-found": "",
|
||||
"pre-release": "",
|
||||
"archived": "",
|
||||
"forked": "",
|
||||
"starsCount": "",
|
||||
"forksCount": "",
|
||||
"issuesCount": "",
|
||||
"openProjectPage": "",
|
||||
"openReleasePage": "",
|
||||
"releaseDescription": "",
|
||||
"created": "",
|
||||
"not-found": "未找到",
|
||||
"pre-release": "预发行",
|
||||
"archived": "已归档",
|
||||
"forked": "复刻成功",
|
||||
"starsCount": "星",
|
||||
"forksCount": "复刻",
|
||||
"issuesCount": "打开问题",
|
||||
"openProjectPage": "打开项目页面",
|
||||
"openReleasePage": "打开发布页面",
|
||||
"releaseDescription": "发布说明",
|
||||
"created": "已创建",
|
||||
"error": {
|
||||
"label": "",
|
||||
"label": "错误",
|
||||
"options": {
|
||||
"noMatchingVersion": ""
|
||||
"noMatchingVersion": "没有找到匹配的版本"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2294,56 +2306,56 @@
|
||||
"option": {},
|
||||
"card": {
|
||||
"vpn": {
|
||||
"countConnected": ""
|
||||
"countConnected": "{count} 已连接"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "没有可用的数据,所有集成已断开",
|
||||
"unknownContentOption": "网络控制器摘要小部件的未知内容选项: "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "网络控制器概述",
|
||||
"description": "显示网络控制器摘要(如Unifi Controller)"
|
||||
},
|
||||
"networkControllerStatus": {
|
||||
"card": {
|
||||
"variants": {
|
||||
"wired": {
|
||||
"name": ""
|
||||
"name": "有线"
|
||||
},
|
||||
"wifi": {
|
||||
"name": ""
|
||||
"name": "Wi-Fi"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": ""
|
||||
"label": "用户数"
|
||||
},
|
||||
"guests": {
|
||||
"label": ""
|
||||
"label": "游客"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"content": {
|
||||
"option": {
|
||||
"wifi": {
|
||||
"label": ""
|
||||
"label": "Wi-Fi"
|
||||
},
|
||||
"wired": {
|
||||
"label": ""
|
||||
"label": "有线"
|
||||
}
|
||||
},
|
||||
"label": ""
|
||||
"label": "小部件内容"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "没有可用的数据,所有集成已断开",
|
||||
"unknownContentOption": "网络状态小部件的未知内容选项: "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "网络状态",
|
||||
"description": "在网络上显示已连接的设备"
|
||||
},
|
||||
"networkController": {
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "获取网络控制器概述失败"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2466,10 +2478,10 @@
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "背景图片 URL",
|
||||
"placeholder": "",
|
||||
"placeholder": "开始输入以搜索本地映像",
|
||||
"group": {
|
||||
"your": "你的图像",
|
||||
"other": ""
|
||||
"your": "你的映像",
|
||||
"other": "其他映像"
|
||||
}
|
||||
},
|
||||
"backgroundImageAttachment": {
|
||||
@@ -3111,7 +3123,7 @@
|
||||
"label": "网络控制器"
|
||||
},
|
||||
"dockerContainers": {
|
||||
"label": ""
|
||||
"label": "Docker 容器"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3177,7 +3189,7 @@
|
||||
"updated": "已更新 {when}",
|
||||
"search": "搜索 {count} 容器",
|
||||
"selected": "{totalCount} 容器的 {selectCount}",
|
||||
"footer": ""
|
||||
"footer": "总计 {count} 容器"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -3197,10 +3209,10 @@
|
||||
},
|
||||
"stats": {
|
||||
"cpu": {
|
||||
"label": ""
|
||||
"label": "CPU"
|
||||
},
|
||||
"memory": {
|
||||
"label": ""
|
||||
"label": "内存"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
@@ -3211,7 +3223,7 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"title": "",
|
||||
"title": "操作",
|
||||
"start": {
|
||||
"label": "开始",
|
||||
"notification": {
|
||||
@@ -3656,7 +3668,7 @@
|
||||
"certificates": {
|
||||
"label": "证书",
|
||||
"hostnames": {
|
||||
"label": ""
|
||||
"label": "主机名"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4057,25 +4069,25 @@
|
||||
"certificate": {
|
||||
"field": {
|
||||
"hostname": {
|
||||
"label": ""
|
||||
"label": "主机名"
|
||||
},
|
||||
"subject": {
|
||||
"label": ""
|
||||
"label": "主题"
|
||||
},
|
||||
"issuer": {
|
||||
"label": ""
|
||||
"label": "发行者"
|
||||
},
|
||||
"validFrom": {
|
||||
"label": ""
|
||||
"label": "有效期自"
|
||||
},
|
||||
"validTo": {
|
||||
"label": ""
|
||||
"label": "有效期至"
|
||||
},
|
||||
"serialNumber": {
|
||||
"label": ""
|
||||
"label": "序号"
|
||||
},
|
||||
"fingerprint": {
|
||||
"label": ""
|
||||
"label": "指纹"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
@@ -4086,19 +4098,19 @@
|
||||
"title": "还没有证书"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "无效证书",
|
||||
"description": "解析证书失败"
|
||||
},
|
||||
"expires": "到期时间 {when}",
|
||||
"toHostnames": ""
|
||||
"toHostnames": "信任的主机名"
|
||||
},
|
||||
"hostnames": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "信任的证书主机名",
|
||||
"description": "某些证书不允许指定域名使用 Homarr 请求他们, 由于这个原因,所有信任的主机名及其证书缩略图都被用来绕过这些限制。",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "还没有主机名"
|
||||
},
|
||||
"toCertificates": ""
|
||||
"toCertificates": "证书"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -4130,16 +4142,16 @@
|
||||
}
|
||||
},
|
||||
"removeHostname": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "删除受信任的主机名",
|
||||
"confirm": "您确定要删除此受信任的主机名吗?这可能会导致一些集成停止工作。",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名已删除",
|
||||
"message": "主机名已成功删除"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名未删除",
|
||||
"message": "无法删除主机名"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Tilføj repository"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "Udbyder"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Bekræft"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "Eksempel"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Repository hinzufügen"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "Anbieter"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Bestätigen"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "Beispiel"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Add repository"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "Import from docker",
|
||||
"loading": "Loading docker images",
|
||||
"noImagesFound": "No docker images found",
|
||||
"listFoundImages": "List of found images",
|
||||
"listAlreadyImportedImages": "List of already imported images",
|
||||
"allImagesAlreadyImported": "All images already imported",
|
||||
"onlyAdminCanImport": "Only administrators can import from docker"
|
||||
},
|
||||
"provider": {
|
||||
"label": "Provider"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Confirm"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": "Import from Docker"
|
||||
},
|
||||
"example": {
|
||||
"label": "Example"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -747,7 +747,7 @@
|
||||
"message": ""
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"title": "",
|
||||
"title": "Nom d'hôte incohérent",
|
||||
"message": ""
|
||||
},
|
||||
"extract": {
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"label": "הצג מידע זיכרון"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
"label": "הצג זמן פעולה"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "הצג מידע על מערכת הקבצים"
|
||||
@@ -1767,7 +1767,7 @@
|
||||
"label": "כרטיסיית ברירת מחדל"
|
||||
},
|
||||
"visibleClusterSections": {
|
||||
"label": ""
|
||||
"label": "מקטעי אשכול גלויים"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "דרישת מציין מקטע"
|
||||
@@ -1961,8 +1961,8 @@
|
||||
"label": "השתמש במסנן כדי לחשב יחס"
|
||||
},
|
||||
"limitPerIntegration": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "הגבלת פריטים לכל אינטגרציה",
|
||||
"description": "פעולה זו תגביל את מספר הפריטים המוצגים בכל אינטגרציה, לא באופן גלובלי"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "מאגרים",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "הוסף מאגר"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "ספק"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "אשר"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "דוגמא"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Depolar",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Depo Ekle"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "Docker'dan içe aktar",
|
||||
"loading": "Docker imajları yükleniyor",
|
||||
"noImagesFound": "Hiç docker imajı bulunamadı",
|
||||
"listFoundImages": "Bulunan imajların listesi",
|
||||
"listAlreadyImportedImages": "Daha önce içe aktarılan imajların listesi",
|
||||
"allImagesAlreadyImported": "Tüm imajlar daha önce içe aktarıldı",
|
||||
"onlyAdminCanImport": "Sadece yöneticiler docker'dan içe aktarabilir"
|
||||
},
|
||||
"provider": {
|
||||
"label": "Sağlayıcı"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Onayla"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": "Docker'dan içe aktar"
|
||||
},
|
||||
"example": {
|
||||
"label": "Örnek"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "儲存庫",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "新增儲存庫"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "提供者"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "確認"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "範例"
|
||||
},
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/dates": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/css-modules": "^1.0.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^3.25.42",
|
||||
"zod": "^3.25.62",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -48,25 +48,25 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^8.0.2",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@tiptap/extension-color": "2.12.0",
|
||||
"@tiptap/extension-highlight": "2.12.0",
|
||||
"@tiptap/extension-image": "2.12.0",
|
||||
"@tiptap/extension-link": "^2.12.0",
|
||||
"@tiptap/extension-table": "2.12.0",
|
||||
"@tiptap/extension-table-cell": "2.12.0",
|
||||
"@tiptap/extension-table-header": "2.12.0",
|
||||
"@tiptap/extension-table-row": "2.12.0",
|
||||
"@tiptap/extension-task-item": "2.12.0",
|
||||
"@tiptap/extension-task-list": "2.12.0",
|
||||
"@tiptap/extension-text-align": "2.12.0",
|
||||
"@tiptap/extension-text-style": "2.12.0",
|
||||
"@tiptap/extension-underline": "2.12.0",
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"@mantine/charts": "^8.1.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tiptap/extension-color": "2.14.0",
|
||||
"@tiptap/extension-highlight": "2.14.0",
|
||||
"@tiptap/extension-image": "2.14.0",
|
||||
"@tiptap/extension-link": "^2.14.0",
|
||||
"@tiptap/extension-table": "2.14.0",
|
||||
"@tiptap/extension-table-cell": "2.14.0",
|
||||
"@tiptap/extension-table-header": "2.14.0",
|
||||
"@tiptap/extension-table-row": "2.14.0",
|
||||
"@tiptap/extension-task-item": "2.14.0",
|
||||
"@tiptap/extension-task-list": "2.14.0",
|
||||
"@tiptap/extension-text-align": "2.14.0",
|
||||
"@tiptap/extension-text-style": "2.14.0",
|
||||
"@tiptap/extension-underline": "2.14.0",
|
||||
"@tiptap/react": "^2.14.0",
|
||||
"@tiptap/starter-kit": "^2.14.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
@@ -75,15 +75,15 @@
|
||||
"react-dom": "19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.3",
|
||||
"video.js": "^8.22.0",
|
||||
"zod": "^3.25.42"
|
||||
"video.js": "^8.23.3",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ActionIcon, Button, Divider, Fieldset, Group, Select, Stack, Text, TextInput } from "@mantine/core";
|
||||
import {
|
||||
Accordion,
|
||||
ActionIcon,
|
||||
Button,
|
||||
Checkbox,
|
||||
Code,
|
||||
Divider,
|
||||
Fieldset,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import type { CheckboxProps } from "@mantine/core";
|
||||
import type { FormErrors } from "@mantine/form";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { IconEdit, IconTrash, IconTriangleFilled } from "@tabler/icons-react";
|
||||
import {
|
||||
IconBrandDocker,
|
||||
IconEdit,
|
||||
IconPlus,
|
||||
IconSquare,
|
||||
IconSquareCheck,
|
||||
IconTrash,
|
||||
IconTriangleFilled,
|
||||
} from "@tabler/icons-react";
|
||||
import { escapeForRegEx } from "@tiptap/react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { findBestIconMatch, IconPicker } from "@homarr/forms-collection";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
import { MaskedImage } from "@homarr/ui";
|
||||
|
||||
import { Providers } from "../releases/releases-providers";
|
||||
import { isProviderKey, Providers } from "../releases/releases-providers";
|
||||
import type { ReleasesRepository, ReleasesVersionFilter } from "../releases/releases-repository";
|
||||
import type { CommonWidgetInputProps } from "./common";
|
||||
import { useWidgetInputTranslation } from "./common";
|
||||
@@ -32,11 +59,14 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const form = useFormContext();
|
||||
const repositories = form.values.options[property] as ReleasesRepository[];
|
||||
const { openModal } = useModalAction(ReleaseEditModal);
|
||||
const { openModal: openEditModal } = useModalAction(RepositoryEditModal);
|
||||
const { openModal: openImportModal } = useModalAction(RepositoryImportModal);
|
||||
const versionFilterPrecisionOptions = useMemo(
|
||||
() => [tRepository("versionFilter.precision.options.none"), "#", "#.#", "#.#.#", "#.#.#.#", "#.#.#.#.#"],
|
||||
[tRepository],
|
||||
);
|
||||
const { data: session } = useSession();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
|
||||
const onRepositorySave = useCallback(
|
||||
(repository: ReleasesRepository, index: number): FormValidation => {
|
||||
@@ -62,8 +92,8 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
[form, property],
|
||||
);
|
||||
|
||||
const addNewItem = () => {
|
||||
const item = {
|
||||
const addNewRepository = () => {
|
||||
const repository: ReleasesRepository = {
|
||||
providerKey: "DockerHub",
|
||||
identifier: "",
|
||||
};
|
||||
@@ -74,16 +104,16 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
...previous,
|
||||
options: {
|
||||
...previous.options,
|
||||
[property]: [...previousValues, item],
|
||||
[property]: [...previousValues, repository],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const index = repositories.length;
|
||||
|
||||
openModal({
|
||||
openEditModal({
|
||||
fieldPath: `options.${property}.${index}`,
|
||||
repository: item,
|
||||
repository,
|
||||
onRepositorySave: (saved) => onRepositorySave(saved, index),
|
||||
onRepositoryCancel: () => onRepositoryRemove(index),
|
||||
versionFilterPrecisionOptions,
|
||||
@@ -106,24 +136,56 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
return (
|
||||
<Fieldset legend={t("label")}>
|
||||
<Stack gap="5">
|
||||
<Button onClick={addNewItem}>{tRepository("addRRepository.label")}</Button>
|
||||
<Group grow>
|
||||
<Button leftSection={<IconPlus />} onClick={addNewRepository}>
|
||||
{tRepository("addRepository.label")}
|
||||
</Button>
|
||||
<Tooltip label={tRepository("importRepositories.onlyAdminCanImport")} disabled={isAdmin} withArrow>
|
||||
<Button
|
||||
disabled={!isAdmin}
|
||||
leftSection={<IconBrandDocker stroke={1.25} />}
|
||||
onClick={() =>
|
||||
openImportModal({
|
||||
repositories,
|
||||
versionFilterPrecisionOptions,
|
||||
onConfirm: (selectedRepositories) => {
|
||||
if (!selectedRepositories.length) return;
|
||||
|
||||
form.setValues((previous) => {
|
||||
const previousValues = previous.options?.[property] as ReleasesRepository[];
|
||||
return {
|
||||
...previous,
|
||||
options: {
|
||||
...previous.options,
|
||||
[property]: [...previousValues, ...selectedRepositories],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
isAdmin,
|
||||
})
|
||||
}
|
||||
>
|
||||
{tRepository("importRepositories.label")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
|
||||
{repositories.map((repository, index) => {
|
||||
return (
|
||||
<Stack key={`${repository.providerKey}.${repository.identifier}`} gap={5}>
|
||||
<Group align="center" gap="xs">
|
||||
<MaskedOrNormalImage
|
||||
hasColor={false}
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey]?.iconUrl}
|
||||
<Image
|
||||
src={repository.iconUrl ?? Providers[repository.providerKey].iconUrl}
|
||||
style={{
|
||||
height: "1em",
|
||||
width: "1em",
|
||||
height: "1.2em",
|
||||
width: "1.2em",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text c="dimmed" fw={100} size="xs">
|
||||
{Providers[repository.providerKey]?.name}
|
||||
{Providers[repository.providerKey].name}
|
||||
</Text>
|
||||
|
||||
<Group justify="space-between" align="center" style={{ flex: 1 }} gap={5}>
|
||||
@@ -135,7 +197,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
|
||||
<Button
|
||||
onClick={() =>
|
||||
openModal({
|
||||
openEditModal({
|
||||
fieldPath: `options.${property}.${index}`,
|
||||
repository,
|
||||
onRepositorySave: (saved) => onRepositorySave(saved, index),
|
||||
@@ -185,7 +247,7 @@ const formatIdentifierName = (identifier: string) => {
|
||||
return unformattedName?.replace(/[-_]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (char) => char.toUpperCase()) ?? "";
|
||||
};
|
||||
|
||||
interface ReleaseEditProps {
|
||||
interface RepositoryEditProps {
|
||||
fieldPath: string;
|
||||
repository: ReleasesRepository;
|
||||
onRepositorySave: (repository: ReleasesRepository) => FormValidation;
|
||||
@@ -193,7 +255,7 @@ interface ReleaseEditProps {
|
||||
versionFilterPrecisionOptions: string[];
|
||||
}
|
||||
|
||||
const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions }) => {
|
||||
const RepositoryEditModal = createModal<RepositoryEditProps>(({ innerProps, actions }) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository }));
|
||||
@@ -262,7 +324,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
value={tempRepository.providerKey}
|
||||
error={formErrors[`${innerProps.fieldPath}.providerKey`]}
|
||||
onChange={(value) => {
|
||||
if (value && Providers[value]) {
|
||||
if (value && isProviderKey(value)) {
|
||||
handleChange({ providerKey: value });
|
||||
}
|
||||
}}
|
||||
@@ -386,7 +448,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
{tRepository("editForm.cancel.label")}
|
||||
</Button>
|
||||
|
||||
<Button data-autofocus onClick={handleConfirm} color="red.9" loading={loading}>
|
||||
<Button data-autofocus onClick={handleConfirm} loading={loading}>
|
||||
{tRepository("editForm.confirm.label")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -398,3 +460,247 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
},
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
interface ReleasesRepositoryImport extends ReleasesRepository {
|
||||
alreadyImported: boolean;
|
||||
}
|
||||
|
||||
interface ContainerImageSelectorProps {
|
||||
containerImage: ReleasesRepositoryImport;
|
||||
versionFilterPrecisionOptions: string[];
|
||||
onImageSelectionChanged?: (isSelected: boolean) => void;
|
||||
}
|
||||
|
||||
const ContainerImageSelector = ({
|
||||
containerImage,
|
||||
versionFilterPrecisionOptions,
|
||||
onImageSelectionChanged,
|
||||
}: ContainerImageSelectorProps) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const checkBoxProps: CheckboxProps = !onImageSelectionChanged
|
||||
? {
|
||||
disabled: true,
|
||||
checked: true,
|
||||
}
|
||||
: {
|
||||
onChange: (event) => onImageSelectionChanged(event.currentTarget.checked),
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
key={`${Providers[containerImage.providerKey].name}/${containerImage.identifier}`}
|
||||
gap="xl"
|
||||
justify="space-between"
|
||||
>
|
||||
<Group gap="md">
|
||||
<Checkbox
|
||||
label={
|
||||
<Group>
|
||||
<Image
|
||||
src={containerImage.iconUrl}
|
||||
style={{
|
||||
height: "1.2em",
|
||||
width: "1.2em",
|
||||
}}
|
||||
/>
|
||||
<Text>{containerImage.identifier}</Text>
|
||||
</Group>
|
||||
}
|
||||
{...checkBoxProps}
|
||||
/>
|
||||
|
||||
{containerImage.versionFilter && (
|
||||
<Group gap={5}>
|
||||
<Text c="dimmed" size="xs">
|
||||
{tRepository("versionFilter.label")}:
|
||||
</Text>
|
||||
|
||||
<Code>{containerImage.versionFilter.prefix && containerImage.versionFilter.prefix}</Code>
|
||||
<Code color="var(--mantine-primary-color-light)" fw={700}>
|
||||
{versionFilterPrecisionOptions[containerImage.versionFilter.precision]}
|
||||
</Code>
|
||||
<Code>{containerImage.versionFilter.suffix && containerImage.versionFilter.suffix}</Code>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Group>
|
||||
<MaskedImage
|
||||
color="dimmed"
|
||||
imageUrl={Providers[containerImage.providerKey].iconUrl}
|
||||
style={{
|
||||
height: "1em",
|
||||
width: "1em",
|
||||
}}
|
||||
/>
|
||||
<Text ff="monospace" c="dimmed" size="sm">
|
||||
{Providers[containerImage.providerKey].name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
interface RepositoryImportProps {
|
||||
repositories: ReleasesRepository[];
|
||||
versionFilterPrecisionOptions: string[];
|
||||
onConfirm: (selectedRepositories: ReleasesRepositoryImport[]) => void;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps, actions }) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedImages, setSelectedImages] = useState([] as ReleasesRepositoryImport[]);
|
||||
|
||||
const docker = clientApi.docker.getContainers.useQuery(undefined, {
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: innerProps.isAdmin,
|
||||
});
|
||||
|
||||
const containersImages: ReleasesRepositoryImport[] = useMemo(
|
||||
() =>
|
||||
docker.data?.containers.reduce<ReleasesRepositoryImport[]>((acc, containerImage) => {
|
||||
const providerKey = containerImage.image.startsWith("ghcr.io/") ? "Github" : "DockerHub";
|
||||
const [identifier, version] = containerImage.image.replace(/^(ghcr\.io\/|docker\.io\/)/, "").split(":");
|
||||
|
||||
if (!identifier) return acc;
|
||||
|
||||
if (acc.some((item) => item.providerKey === providerKey && item.identifier === identifier)) return acc;
|
||||
|
||||
acc.push({
|
||||
providerKey,
|
||||
identifier,
|
||||
iconUrl: containerImage.iconUrl ?? undefined,
|
||||
name: formatIdentifierName(identifier),
|
||||
versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined,
|
||||
alreadyImported: innerProps.repositories.some(
|
||||
(item) => item.providerKey === providerKey && item.identifier === identifier,
|
||||
),
|
||||
});
|
||||
return acc;
|
||||
}, []) ?? [],
|
||||
[docker.data, innerProps.repositories],
|
||||
);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
setLoading(true);
|
||||
|
||||
innerProps.onConfirm(selectedImages);
|
||||
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
}, [innerProps, selectedImages, actions]);
|
||||
|
||||
const allImagesImported = useMemo(
|
||||
() => containersImages.every((containerImage) => containerImage.alreadyImported),
|
||||
[containersImages],
|
||||
);
|
||||
|
||||
const anyImagesImported = useMemo(
|
||||
() => containersImages.some((containerImage) => containerImage.alreadyImported),
|
||||
[containersImages],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{docker.isPending ? (
|
||||
<Stack justify="center" align="center">
|
||||
<Loader size="xl" />
|
||||
<Title order={3}>{tRepository("importRepositories.loading")}</Title>
|
||||
</Stack>
|
||||
) : containersImages.length === 0 ? (
|
||||
<Stack justify="center" align="center">
|
||||
<IconBrandDocker stroke={1} size={128} />
|
||||
<Title order={3}>{tRepository("importRepositories.noImagesFound")}</Title>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack>
|
||||
<Accordion defaultValue={!allImagesImported ? "foundImages" : anyImagesImported ? "alreadyImported" : ""}>
|
||||
<Accordion.Item value="foundImages">
|
||||
<Accordion.Control disabled={allImagesImported} icon={<IconSquare stroke={1.25} />}>
|
||||
<Group>
|
||||
{tRepository("importRepositories.listFoundImages")}
|
||||
{allImagesImported && (
|
||||
<Text c="dimmed" size="sm">
|
||||
{tRepository("importRepositories.allImagesAlreadyImported")}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{!allImagesImported &&
|
||||
containersImages
|
||||
.filter((containerImage) => !containerImage.alreadyImported)
|
||||
.map((containerImage) => {
|
||||
return (
|
||||
<ContainerImageSelector
|
||||
key={`${containerImage.providerKey}/${containerImage.identifier}`}
|
||||
containerImage={containerImage}
|
||||
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
|
||||
onImageSelectionChanged={(isSelected) =>
|
||||
isSelected
|
||||
? setSelectedImages([...selectedImages, containerImage])
|
||||
: setSelectedImages(selectedImages.filter((img) => img !== containerImage))
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="alreadyImported">
|
||||
<Accordion.Control disabled={!anyImagesImported} icon={<IconSquareCheck stroke={1.25} />}>
|
||||
{tRepository("importRepositories.listAlreadyImportedImages")}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{anyImagesImported &&
|
||||
containersImages
|
||||
.filter((containerImage) => containerImage.alreadyImported)
|
||||
.map((containerImage) => {
|
||||
return (
|
||||
<ContainerImageSelector
|
||||
key={`${containerImage.providerKey}/${containerImage.identifier}`}
|
||||
containerImage={containerImage}
|
||||
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={actions.closeModal} color="gray.5">
|
||||
{tRepository("editForm.cancel.label")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleConfirm} loading={loading} disabled={selectedImages.length === 0}>
|
||||
{tRepository("editForm.confirm.label")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle(t) {
|
||||
return t("widget.releases.option.repositories.importForm.title");
|
||||
},
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
const parseImageVersionToVersionFilter = (imageVersion: string): ReleasesVersionFilter | undefined => {
|
||||
const version = /(?<=\D|^)\d+(?:\.\d+)*(?![\d.])/.exec(imageVersion)?.[0];
|
||||
|
||||
if (!version) return undefined;
|
||||
|
||||
const [prefix, suffix] = imageVersion.split(version);
|
||||
|
||||
return {
|
||||
prefix,
|
||||
precision: version.split(".").length,
|
||||
suffix,
|
||||
};
|
||||
};
|
||||
@@ -156,7 +156,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
>
|
||||
<MaskedOrNormalImage
|
||||
className="releases-repository-header-icon"
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey]?.iconUrl}
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey].iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
style={{
|
||||
width: "1em",
|
||||
@@ -474,7 +474,7 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
||||
<Group className="releases-repository-expanded-header-provider-wrapper" gap={5} align="center">
|
||||
<MaskedOrNormalImage
|
||||
className="releases-repository-expanded-header-provider-icon"
|
||||
imageUrl={Providers[repository.providerKey]?.iconUrl}
|
||||
imageUrl={Providers[repository.providerKey].iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
style={{
|
||||
width: "1em",
|
||||
@@ -482,7 +482,7 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
||||
}}
|
||||
/>
|
||||
<Text className="releases-repository-expanded-header-provider-name" size="xs" c="iconColor" ff="monospace">
|
||||
{Providers[repository.providerKey]?.name}
|
||||
{Providers[repository.providerKey].name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
@@ -3,16 +3,7 @@ export interface ReleasesProvider {
|
||||
iconUrl: string;
|
||||
}
|
||||
|
||||
interface ProvidersProps {
|
||||
[key: string]: ReleasesProvider;
|
||||
DockerHub: ReleasesProvider;
|
||||
Github: ReleasesProvider;
|
||||
Gitlab: ReleasesProvider;
|
||||
Npm: ReleasesProvider;
|
||||
Codeberg: ReleasesProvider;
|
||||
}
|
||||
|
||||
export const Providers: ProvidersProps = {
|
||||
export const Providers = {
|
||||
DockerHub: {
|
||||
name: "Docker Hub",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/docker.svg",
|
||||
@@ -33,4 +24,10 @@ export const Providers: ProvidersProps = {
|
||||
name: "Codeberg",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/codeberg.svg",
|
||||
},
|
||||
} as const satisfies Record<string, ReleasesProvider>;
|
||||
|
||||
export type ProviderKey = keyof typeof Providers;
|
||||
|
||||
export const isProviderKey = (key: string): key is ProviderKey => {
|
||||
return key in Providers;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ProviderKey } from "./releases-providers";
|
||||
|
||||
export interface ReleasesVersionFilter {
|
||||
prefix?: string;
|
||||
precision: number;
|
||||
@@ -5,7 +7,7 @@ export interface ReleasesVersionFilter {
|
||||
}
|
||||
|
||||
export interface ReleasesRepository {
|
||||
providerKey: string;
|
||||
providerKey: ProviderKey;
|
||||
identifier: string;
|
||||
name?: string;
|
||||
versionFilter?: ReleasesVersionFilter;
|
||||
|
||||
Generated
+1495
-1432
File diff suppressed because it is too large.
Load diff
@@ -24,12 +24,12 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"typescript-eslint": "^8.33.0"
|
||||
"typescript-eslint": "^8.34.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.14",
|
||||
"prettier-plugin-packagejson": "^2.5.15",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user