mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
chore(release): automatic release v1.31.0
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -31,6 +31,7 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.30.1
|
||||
- 1.30.0
|
||||
- 1.29.0
|
||||
- 1.28.1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.0-alpine AS base
|
||||
FROM node:22.17.1-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.2.1",
|
||||
"@mantine/core": "^8.2.1",
|
||||
"@mantine/dropzone": "^8.2.1",
|
||||
"@mantine/hooks": "^8.2.1",
|
||||
"@mantine/modals": "^8.2.1",
|
||||
"@mantine/tiptap": "^8.2.1",
|
||||
"@mantine/colors-generator": "^8.2.2",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@mantine/dropzone": "^8.2.2",
|
||||
"@mantine/hooks": "^8.2.2",
|
||||
"@mantine/modals": "^8.2.2",
|
||||
"@mantine/tiptap": "^8.2.2",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query-devtools": "^5.83.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.83.0",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@tanstack/react-query-devtools": "^5.84.1",
|
||||
"@tanstack/react-query-next-experimental": "^5.84.1",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/next": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
@@ -76,16 +76,16 @@
|
||||
"glob": "^11.0.3",
|
||||
"jotai": "^2.12.5",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.4",
|
||||
"next": "15.4.5",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.89.2",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.27.0",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
@@ -94,15 +94,15 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.16.4",
|
||||
"@types/node": "^22.17.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function GET(_req: NextRequest, props: { params: Promise<{ id: stri
|
||||
headers.set("Content-Type", image.contentType);
|
||||
headers.set("Content-Length", image.content.length.toString());
|
||||
|
||||
return new NextResponse(image.content, {
|
||||
return new NextResponse(new Uint8Array(image.content), {
|
||||
status: 200,
|
||||
headers,
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
@@ -38,18 +39,18 @@
|
||||
"dotenv": "^17.2.1",
|
||||
"fastify": "^5.4.0",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.12.0"
|
||||
"undici": "7.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.16.4",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"@types/node": "^22.17.0",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -44,19 +44,19 @@
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "^10.0.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^24.2.7",
|
||||
"testcontainers": "^11.4.0",
|
||||
"turbo": "^2.5.5",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1",
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"engines": {
|
||||
"node": ">=22.17.1"
|
||||
"node": ">=22.18.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -71,7 +71,7 @@
|
||||
"tree-sitter-json"
|
||||
],
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.12.0"
|
||||
"proxmox-api>undici": "7.13.0"
|
||||
},
|
||||
"allowUnusedPatches": true,
|
||||
"ignoredBuiltDependencies": [
|
||||
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,21 @@
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/old-import": "workspace:^0.1.0",
|
||||
"@homarr/old-schema": "workspace:^0.1.0",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@trpc/tanstack-react-query": "^11.4.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.3.2",
|
||||
"zod": "^3.25.76"
|
||||
@@ -57,8 +58,8 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
53
packages/api/src/router/test/widgets/app.spec.ts
Normal file
53
packages/api/src/router/test/widgets/app.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import * as ping from "@homarr/ping";
|
||||
|
||||
import { appRouter } from "../../widgets/app";
|
||||
|
||||
// Mock the auth module to return an empty session
|
||||
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
||||
vi.mock("@homarr/ping", () => ({ sendPingRequestAsync: async () => await Promise.resolve(null) }));
|
||||
|
||||
describe("ping should call sendPingRequestAsync with url and return result", () => {
|
||||
test("ping with error response should return error and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ error: "error" }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("error" in result).toBe(true);
|
||||
});
|
||||
|
||||
test("ping with success response should return statusCode and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ statusCode: 200, durationMs: 123 }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("statusCode" in result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
|
||||
import { pingUrlChannel } from "@homarr/redis";
|
||||
import { pingRequestHandler } from "@homarr/request-handler/ping";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
ping: publicProcedure.input(z.object({ url: z.string() })).query(async ({ input }) => {
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
...pingResult,
|
||||
};
|
||||
}),
|
||||
updatedPing: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
@@ -15,20 +23,16 @@ export const appRouter = createTRPCRouter({
|
||||
)
|
||||
.subscription(async ({ input }) => {
|
||||
await pingUrlChannel.addAsync(input.url);
|
||||
const innerHandler = pingRequestHandler.handler({ url: input.url });
|
||||
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
|
||||
return observable<{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }>(
|
||||
(emit) => {
|
||||
// Run ping request in background
|
||||
void innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then(({ data }) => {
|
||||
emit.next({ url: input.url, ...data });
|
||||
});
|
||||
|
||||
const unsubscribe = innerHandler.subscribe((pingResponse) => {
|
||||
emit.next({
|
||||
url: input.url,
|
||||
...pingResponse,
|
||||
});
|
||||
emit.next({ url: input.url, ...pingResult });
|
||||
const unsubscribe = pingChannel.subscribe((message) => {
|
||||
// Only emit if same url
|
||||
if (message.url !== input.url) return;
|
||||
emit.next(message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
||||
215
packages/api/src/router/widgets/firewall.ts
Normal file
215
packages/api/src/router/widgets/firewall.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import type { Integration } from "@homarr/db/schema";
|
||||
import type { IntegrationKindByCategory } from "@homarr/definitions";
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import type {
|
||||
FirewallCpuSummary,
|
||||
FirewallInterfacesSummary,
|
||||
FirewallMemorySummary,
|
||||
FirewallVersionSummary,
|
||||
} from "@homarr/integrations";
|
||||
import {
|
||||
firewallCpuRequestHandler,
|
||||
firewallInterfacesRequestHandler,
|
||||
firewallMemoryRequestHandler,
|
||||
firewallVersionRequestHandler,
|
||||
} from "@homarr/request-handler/firewall";
|
||||
|
||||
import { createManyIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const firewallRouter = createTRPCRouter({
|
||||
getFirewallCpuStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = firewallCpuRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
summary: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}),
|
||||
subscribeFirewallCpuStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"firewall"> }>;
|
||||
summary: FirewallCpuSummary;
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = firewallCpuRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((summary) => {
|
||||
emit.next({
|
||||
integration,
|
||||
summary,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
|
||||
getFirewallInterfacesStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = firewallInterfacesRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
summary: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}),
|
||||
subscribeFirewallInterfacesStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"firewall"> }>;
|
||||
summary: FirewallInterfacesSummary[];
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = firewallInterfacesRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((summary) => {
|
||||
emit.next({
|
||||
integration,
|
||||
summary,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
|
||||
getFirewallVersionStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = firewallVersionRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
summary: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}),
|
||||
subscribeFirewallVersionStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"firewall"> }>;
|
||||
summary: FirewallVersionSummary;
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = firewallVersionRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((summary) => {
|
||||
emit.next({
|
||||
integration,
|
||||
summary,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
|
||||
getFirewallMemoryStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = firewallMemoryRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
summary: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}),
|
||||
subscribeFirewallMemoryStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"firewall"> }>;
|
||||
summary: FirewallMemorySummary;
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = firewallMemoryRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((summary) => {
|
||||
emit.next({
|
||||
integration,
|
||||
summary,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { appRouter } from "./app";
|
||||
import { calendarRouter } from "./calendar";
|
||||
import { dnsHoleRouter } from "./dns-hole";
|
||||
import { downloadsRouter } from "./downloads";
|
||||
import { firewallRouter } from "./firewall";
|
||||
import { healthMonitoringRouter } from "./health-monitoring";
|
||||
import { indexerManagerRouter } from "./indexer-manager";
|
||||
import { mediaReleaseRouter } from "./media-release";
|
||||
@@ -40,5 +41,6 @@ export const widgetRouter = createTRPCRouter({
|
||||
options: optionsRouter,
|
||||
releases: releasesRouter,
|
||||
networkController: networkControllerRouter,
|
||||
firewall: firewallRouter,
|
||||
notifications: notificationsRouter,
|
||||
});
|
||||
|
||||
@@ -34,21 +34,21 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.8",
|
||||
"next": "15.4.4",
|
||||
"ldapts": "8.0.9",
|
||||
"next": "15.4.5",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"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/bcrypt": "6.0.0",
|
||||
"@types/cookies": "0.9.1",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"undici": "7.12.0"
|
||||
"undici": "7.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Agent as HttpsAgent } from "node:https";
|
||||
import path from "node:path";
|
||||
import { checkServerIdentity, rootCertificates } from "node:tls";
|
||||
import axios from "axios";
|
||||
import type { RequestInfo, RequestInit, Response } from "undici";
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { env } from "@homarr/common/env";
|
||||
@@ -131,8 +132,8 @@ export const createAxiosCertificateInstanceAsync = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchWithTrustedCertificatesAsync: typeof fetch = async (url, options) => {
|
||||
const agent = await createCertificateAgentAsync();
|
||||
export const fetchWithTrustedCertificatesAsync = async (url: RequestInfo, options?: RequestInit): Promise<Response> => {
|
||||
const agent = await createCertificateAgentAsync(undefined);
|
||||
return fetch(url, {
|
||||
...options,
|
||||
dispatcher: agent,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"undici": "7.12.0",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"undici": "7.13.0",
|
||||
"zod": "^3.25.76",
|
||||
"zod-validation-error": "^3.5.3"
|
||||
},
|
||||
@@ -42,7 +42,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"ioredis": "5.6.1",
|
||||
"ioredis": "5.7.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@trpc/tanstack-react-query": "^11.4.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.1.0",
|
||||
"react": "19.1.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -42,8 +42,8 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "19.1.8",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"@types/react": "19.1.9",
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { checkCron } from "./validation";
|
||||
|
||||
export const EVERY_5_SECONDS = checkCron("*/5 * * * * *") satisfies string;
|
||||
export const EVERY_30_SECONDS = checkCron("*/30 * * * * *") satisfies string;
|
||||
export const EVERY_MINUTE = checkCron("* * * * *") satisfies string;
|
||||
export const EVERY_5_MINUTES = checkCron("*/5 * * * *") satisfies string;
|
||||
export const EVERY_10_MINUTES = checkCron("*/10 * * * *") satisfies string;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
@@ -43,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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ import { dockerContainersJob } from "./jobs/docker";
|
||||
import { iconsUpdaterJob } from "./jobs/icons-updater";
|
||||
import { dnsHoleJob } from "./jobs/integrations/dns-hole";
|
||||
import { downloadsJob } from "./jobs/integrations/downloads";
|
||||
import {
|
||||
firewallCpuJob,
|
||||
firewallInterfacesJob,
|
||||
firewallMemoryJob,
|
||||
firewallVersionJob,
|
||||
} from "./jobs/integrations/firewall";
|
||||
import { healthMonitoringJob } from "./jobs/integrations/health-monitoring";
|
||||
import { smartHomeEntityStateJob } from "./jobs/integrations/home-assistant";
|
||||
import { indexerManagerJob } from "./jobs/integrations/indexer-manager";
|
||||
@@ -39,6 +45,10 @@ export const jobGroup = createCronJobGroup({
|
||||
minecraftServerStatus: minecraftServerStatusJob,
|
||||
dockerContainers: dockerContainersJob,
|
||||
networkController: networkControllerJob,
|
||||
firewallCpu: firewallCpuJob,
|
||||
firewallMemory: firewallMemoryJob,
|
||||
firewallVersion: firewallVersionJob,
|
||||
firewallInterfaces: firewallInterfacesJob,
|
||||
refreshNotifications: refreshNotificationsJob,
|
||||
});
|
||||
|
||||
|
||||
46
packages/cron-jobs/src/jobs/integrations/firewall.ts
Normal file
46
packages/cron-jobs/src/jobs/integrations/firewall.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { EVERY_5_SECONDS, EVERY_30_SECONDS, EVERY_HOUR, EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import {
|
||||
firewallCpuRequestHandler,
|
||||
firewallInterfacesRequestHandler,
|
||||
firewallMemoryRequestHandler,
|
||||
firewallVersionRequestHandler,
|
||||
} from "@homarr/request-handler/firewall";
|
||||
import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler";
|
||||
|
||||
import { createCronJob } from "../../lib";
|
||||
|
||||
export const firewallCpuJob = createCronJob("firewallCpu", EVERY_5_SECONDS).withCallback(
|
||||
createRequestIntegrationJobHandler(firewallCpuRequestHandler.handler, {
|
||||
widgetKinds: ["firewall"],
|
||||
getInput: {
|
||||
firewall: () => ({}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const firewallMemoryJob = createCronJob("firewallMemory", EVERY_MINUTE).withCallback(
|
||||
createRequestIntegrationJobHandler(firewallMemoryRequestHandler.handler, {
|
||||
widgetKinds: ["firewall"],
|
||||
getInput: {
|
||||
firewall: () => ({}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const firewallInterfacesJob = createCronJob("firewallInterfaces", EVERY_30_SECONDS).withCallback(
|
||||
createRequestIntegrationJobHandler(firewallInterfacesRequestHandler.handler, {
|
||||
widgetKinds: ["firewall"],
|
||||
getInput: {
|
||||
firewall: () => ({}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const firewallVersionJob = createCronJob("firewallVersion", EVERY_HOUR).withCallback(
|
||||
createRequestIntegrationJobHandler(firewallVersionRequestHandler.handler, {
|
||||
widgetKinds: ["firewall"],
|
||||
getInput: {
|
||||
firewall: () => ({}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -2,8 +2,8 @@ import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { logger } from "@homarr/log";
|
||||
import { pingUrlChannel } from "@homarr/redis";
|
||||
import { pingRequestHandler } from "@homarr/request-handler/ping";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
@@ -28,6 +28,16 @@ export const pingJob = createCronJob("ping", EVERY_MINUTE, {
|
||||
});
|
||||
|
||||
const pingAsync = async (url: string) => {
|
||||
const handler = pingRequestHandler.handler({ url });
|
||||
await handler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
const pingResult = await sendPingRequestAsync(url);
|
||||
|
||||
if ("statusCode" in pingResult) {
|
||||
logger.debug(`executed ping for url ${url} with status code ${pingResult.statusCode}`);
|
||||
} else {
|
||||
logger.error(`Executing ping for url ${url} failed with error: ${pingResult.error}`);
|
||||
}
|
||||
|
||||
await pingChannel.publishAsync({
|
||||
url,
|
||||
...pingResult,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,15 +44,15 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.1",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^11.4.0",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.3",
|
||||
"drizzle-orm": "^0.44.4",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"mysql2": "3.14.2",
|
||||
"mysql2": "3.14.3",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -60,11 +60,11 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint": "^9.32.0",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,22 +39,16 @@ export type HomarrDocumentationPath =
|
||||
| "/search"
|
||||
| "/docs/tags"
|
||||
| "/docs/tags/active-directory"
|
||||
| "/docs/tags/ad-guard"
|
||||
| "/docs/tags/ad-guard-home"
|
||||
| "/docs/tags/administration"
|
||||
| "/docs/tags/advanced"
|
||||
| "/docs/tags/analytics"
|
||||
| "/docs/tags/api"
|
||||
| "/docs/tags/apps"
|
||||
| "/docs/tags/background"
|
||||
| "/docs/tags/banner"
|
||||
| "/docs/tags/blocking"
|
||||
| "/docs/tags/boards"
|
||||
| "/docs/tags/bookmark"
|
||||
| "/docs/tags/bookmarks"
|
||||
| "/docs/tags/caddy"
|
||||
| "/docs/tags/certificates"
|
||||
| "/docs/tags/checklist"
|
||||
| "/docs/tags/code"
|
||||
| "/docs/tags/community"
|
||||
| "/docs/tags/configuration"
|
||||
@@ -64,63 +58,37 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/database"
|
||||
| "/docs/tags/developer"
|
||||
| "/docs/tags/development"
|
||||
| "/docs/tags/dns"
|
||||
| "/docs/tags/docker"
|
||||
| "/docs/tags/donation"
|
||||
| "/docs/tags/edit-mode"
|
||||
| "/docs/tags/env"
|
||||
| "/docs/tags/environment-variables"
|
||||
| "/docs/tags/feeds"
|
||||
| "/docs/tags/finance"
|
||||
| "/docs/tags/getting-started"
|
||||
| "/docs/tags/google"
|
||||
| "/docs/tags/grafana"
|
||||
| "/docs/tags/groups"
|
||||
| "/docs/tags/hardware"
|
||||
| "/docs/tags/health"
|
||||
| "/docs/tags/help"
|
||||
| "/docs/tags/icon-picker"
|
||||
| "/docs/tags/icon-repositories"
|
||||
| "/docs/tags/icons"
|
||||
| "/docs/tags/iframe"
|
||||
| "/docs/tags/images"
|
||||
| "/docs/tags/installation"
|
||||
| "/docs/tags/integrade"
|
||||
| "/docs/tags/integration"
|
||||
| "/docs/tags/integrations"
|
||||
| "/docs/tags/interface"
|
||||
| "/docs/tags/jellyserr"
|
||||
| "/docs/tags/jobs"
|
||||
| "/docs/tags/layout"
|
||||
| "/docs/tags/ldap"
|
||||
| "/docs/tags/links"
|
||||
| "/docs/tags/lists"
|
||||
| "/docs/tags/management"
|
||||
| "/docs/tags/market"
|
||||
| "/docs/tags/media"
|
||||
| "/docs/tags/minecraft"
|
||||
| "/docs/tags/monitoring"
|
||||
| "/docs/tags/network"
|
||||
| "/docs/tags/news"
|
||||
| "/docs/tags/notebook"
|
||||
| "/docs/tags/notes"
|
||||
| "/docs/tags/oidc"
|
||||
| "/docs/tags/open-collective"
|
||||
| "/docs/tags/open-media-vault"
|
||||
| "/docs/tags/overseerr"
|
||||
| "/docs/tags/permissions"
|
||||
| "/docs/tags/pgid"
|
||||
| "/docs/tags/pi-hole"
|
||||
| "/docs/tags/ping"
|
||||
| "/docs/tags/programming"
|
||||
| "/docs/tags/proxmox"
|
||||
| "/docs/tags/proxy"
|
||||
| "/docs/tags/puid"
|
||||
| "/docs/tags/releases"
|
||||
| "/docs/tags/repositories"
|
||||
| "/docs/tags/responsive"
|
||||
| "/docs/tags/roles"
|
||||
| "/docs/tags/rss"
|
||||
| "/docs/tags/search"
|
||||
| "/docs/tags/search-engines"
|
||||
| "/docs/tags/security"
|
||||
@@ -128,24 +96,15 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/seo"
|
||||
| "/docs/tags/server"
|
||||
| "/docs/tags/settings"
|
||||
| "/docs/tags/sinkhole"
|
||||
| "/docs/tags/sso"
|
||||
| "/docs/tags/stocks"
|
||||
| "/docs/tags/system"
|
||||
| "/docs/tags/table"
|
||||
| "/docs/tags/tasks"
|
||||
| "/docs/tags/technical-documentation"
|
||||
| "/docs/tags/text"
|
||||
| "/docs/tags/torrent"
|
||||
| "/docs/tags/traefik"
|
||||
| "/docs/tags/translations"
|
||||
| "/docs/tags/unifi-controller"
|
||||
| "/docs/tags/unraid"
|
||||
| "/docs/tags/uploads"
|
||||
| "/docs/tags/usenet"
|
||||
| "/docs/tags/users"
|
||||
| "/docs/tags/variables"
|
||||
| "/docs/tags/widgets"
|
||||
| "/docs/advanced/command-line"
|
||||
| "/docs/advanced/command-line/fix-usernames"
|
||||
| "/docs/advanced/command-line/password-recovery"
|
||||
@@ -188,17 +147,38 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/getting-started/installation/source"
|
||||
| "/docs/getting-started/installation/synology"
|
||||
| "/docs/getting-started/installation/unraid"
|
||||
| "/docs/integrations/cloud"
|
||||
| "/docs/integrations/containers"
|
||||
| "/docs/integrations/dns"
|
||||
| "/docs/integrations/hardware"
|
||||
| "/docs/integrations/adguard-home"
|
||||
| "/docs/integrations/codeberg"
|
||||
| "/docs/integrations/dash-dot"
|
||||
| "/docs/integrations/deluge"
|
||||
| "/docs/integrations/docker-hub"
|
||||
| "/docs/integrations/docker"
|
||||
| "/docs/integrations/emby"
|
||||
| "/docs/integrations/github"
|
||||
| "/docs/integrations/gitlab"
|
||||
| "/docs/integrations/home-assistant"
|
||||
| "/docs/integrations/jellyfin"
|
||||
| "/docs/integrations/jellyseerr"
|
||||
| "/docs/integrations/kubernetes"
|
||||
| "/docs/integrations/media-requester"
|
||||
| "/docs/integrations/media-server"
|
||||
| "/docs/integrations/network"
|
||||
| "/docs/integrations/servarr"
|
||||
| "/docs/integrations/torrent"
|
||||
| "/docs/integrations/usenet"
|
||||
| "/docs/integrations/lidarr"
|
||||
| "/docs/integrations/nextcloud"
|
||||
| "/docs/integrations/npm"
|
||||
| "/docs/integrations/ntfy"
|
||||
| "/docs/integrations/nzbget"
|
||||
| "/docs/integrations/open-media-vault"
|
||||
| "/docs/integrations/overseerr"
|
||||
| "/docs/integrations/pi-hole"
|
||||
| "/docs/integrations/plex"
|
||||
| "/docs/integrations/prowlarr"
|
||||
| "/docs/integrations/proxmox"
|
||||
| "/docs/integrations/q-bittorent"
|
||||
| "/docs/integrations/radarr"
|
||||
| "/docs/integrations/readarr"
|
||||
| "/docs/integrations/sabnzbd"
|
||||
| "/docs/integrations/sonarr"
|
||||
| "/docs/integrations/tdarr"
|
||||
| "/docs/integrations/transmission"
|
||||
| "/docs/integrations/unifi-controller"
|
||||
| "/docs/management/api"
|
||||
| "/docs/management/apps"
|
||||
| "/docs/management/boards"
|
||||
@@ -209,23 +189,32 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/management/settings"
|
||||
| "/docs/management/tasks"
|
||||
| "/docs/management/users"
|
||||
| "/docs/widgets/app"
|
||||
| "/docs/widgets/bookmarks"
|
||||
| "/docs/widgets/calendar"
|
||||
| "/docs/widgets/clock"
|
||||
| "/docs/widgets/dns-hole"
|
||||
| "/docs/widgets/dns-hole-controls"
|
||||
| "/docs/widgets/dns-hole-summary"
|
||||
| "/docs/widgets/docker-containers"
|
||||
| "/docs/widgets/downloads"
|
||||
| "/docs/widgets/health-monitoring"
|
||||
| "/docs/widgets/home-assistant"
|
||||
| "/docs/widgets/iframe"
|
||||
| "/docs/widgets/indexer-manager"
|
||||
| "/docs/widgets/media-requests"
|
||||
| "/docs/widgets/media-releases"
|
||||
| "/docs/widgets/media-request-list"
|
||||
| "/docs/widgets/media-request-stats"
|
||||
| "/docs/widgets/media-server"
|
||||
| "/docs/widgets/media-transcoding"
|
||||
| "/docs/widgets/minecraft-server-status"
|
||||
| "/docs/widgets/network-controller"
|
||||
| "/docs/widgets/network-controller-status"
|
||||
| "/docs/widgets/network-controller-summary"
|
||||
| "/docs/widgets/notebook"
|
||||
| "/docs/widgets/notifications"
|
||||
| "/docs/widgets/releases"
|
||||
| "/docs/widgets/rss"
|
||||
| "/docs/widgets/stocks"
|
||||
| "/docs/widgets/rss-feed"
|
||||
| "/docs/widgets/smart-home-entity-state"
|
||||
| "/docs/widgets/smart-home-execute-automation"
|
||||
| "/docs/widgets/stock-price"
|
||||
| "/docs/widgets/video"
|
||||
| "/docs/widgets/weather"
|
||||
| ""
|
||||
|
||||
@@ -172,6 +172,12 @@ export const integrationDefs = {
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png",
|
||||
category: ["networkController"],
|
||||
},
|
||||
opnsense: {
|
||||
name: "OPNsense",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg",
|
||||
category: ["firewall"],
|
||||
},
|
||||
github: {
|
||||
name: "Github",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
@@ -207,6 +213,27 @@ export const integrationDefs = {
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://codeberg.org",
|
||||
},
|
||||
linuxServerIO: {
|
||||
name: "LinuxServer.io",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.linuxserver.io",
|
||||
},
|
||||
gitHubContainerRegistry: {
|
||||
name: "GitHub Container Registry",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.github.com",
|
||||
},
|
||||
quay: {
|
||||
name: "Quay",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://quay.io",
|
||||
},
|
||||
ntfy: {
|
||||
name: "ntfy",
|
||||
secretKinds: [["topic"], ["topic", "apiKey"]],
|
||||
@@ -297,6 +324,7 @@ export const integrationCategories = [
|
||||
"networkController",
|
||||
"releasesProvider",
|
||||
"notifications",
|
||||
"firewall",
|
||||
] as const;
|
||||
|
||||
export type IntegrationCategory = (typeof integrationCategories)[number];
|
||||
|
||||
@@ -26,6 +26,7 @@ export const widgetKinds = [
|
||||
"releases",
|
||||
"mediaReleases",
|
||||
"dockerContainers",
|
||||
"firewall",
|
||||
"notifications",
|
||||
] as const;
|
||||
export type WidgetKind = (typeof widgetKinds)[number];
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.42",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.2.1",
|
||||
"@mantine/form": "^8.2.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.2.1",
|
||||
"react": "19.1.0",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"react": "19.1.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"@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",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"@types/bcrypt": "6.0.0",
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"octokit": "^5.0.3",
|
||||
"proxmox-api": "1.1.1",
|
||||
"tsdav": "^2.1.5",
|
||||
"undici": "7.12.0",
|
||||
"undici": "7.13.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
@@ -55,7 +55,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-unifi": "^2.5.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ import { QBitTorrentIntegration } from "../download-client/qbittorrent/qbittorre
|
||||
import { SabnzbdIntegration } from "../download-client/sabnzbd/sabnzbd-integration";
|
||||
import { TransmissionIntegration } from "../download-client/transmission/transmission-integration";
|
||||
import { EmbyIntegration } from "../emby/emby-integration";
|
||||
import { GitHubContainerRegistryIntegration } from "../github-container-registry/github-container-registry-integration";
|
||||
import { GithubIntegration } from "../github/github-integration";
|
||||
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
||||
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
||||
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
||||
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
|
||||
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
|
||||
import { LidarrIntegration } from "../media-organizer/lidarr/lidarr-integration";
|
||||
import { RadarrIntegration } from "../media-organizer/radarr/radarr-integration";
|
||||
import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integration";
|
||||
@@ -29,11 +31,13 @@ import { NextcloudIntegration } from "../nextcloud/nextcloud.integration";
|
||||
import { NPMIntegration } from "../npm/npm-integration";
|
||||
import { NTFYIntegration } from "../ntfy/ntfy-integration";
|
||||
import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration";
|
||||
import { OPNsenseIntegration } from "../opnsense/opnsense-integration";
|
||||
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
|
||||
import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-factory";
|
||||
import { PlexIntegration } from "../plex/plex-integration";
|
||||
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
|
||||
import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
|
||||
import { QuayIntegration } from "../quay/quay-integration";
|
||||
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
||||
import type { Integration, IntegrationInput } from "./integration";
|
||||
|
||||
@@ -99,11 +103,15 @@ export const integrationCreators = {
|
||||
emby: EmbyIntegration,
|
||||
nextcloud: NextcloudIntegration,
|
||||
unifiController: UnifiControllerIntegration,
|
||||
opnsense: OPNsenseIntegration,
|
||||
github: GithubIntegration,
|
||||
dockerHub: DockerHubIntegration,
|
||||
gitlab: GitlabIntegration,
|
||||
npm: NPMIntegration,
|
||||
codeberg: CodebergIntegration,
|
||||
linuxServerIO: LinuxServerIOIntegration,
|
||||
gitHubContainerRegistry: GitHubContainerRegistryIntegration,
|
||||
quay: QuayIntegration,
|
||||
ntfy: NTFYIntegration,
|
||||
mock: MockIntegration,
|
||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||
|
||||
@@ -20,7 +20,7 @@ const localLogger = logger.child({ module: "CodebergIntegration" });
|
||||
|
||||
export class CodebergIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback({});
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined);
|
||||
|
||||
return await callback({
|
||||
Authorization: `token ${this.getSecretValue("personalAccessToken")}`,
|
||||
@@ -61,7 +61,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return fetchWithTrustedCertificatesAsync(
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
this.url(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/releases`),
|
||||
{ headers },
|
||||
);
|
||||
|
||||
@@ -30,7 +30,8 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
}
|
||||
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("username") || !this.hasSecretValue("personalAccessToken")) return await callback({});
|
||||
if (!this.hasSecretValue("username") || !this.hasSecretValue("personalAccessToken"))
|
||||
return await callback(undefined);
|
||||
|
||||
const storedSession = await this.sessionStore.getAsync();
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Octokit, RequestError } from "octokit";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
|
||||
|
||||
export class GitHubContainerRegistryIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private static readonly userAgent = "Homarr-Lab/Homarr:GitHubContainerRegistryIntegration";
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const headers: RequestInit["headers"] = {
|
||||
"User-Agent": GitHubContainerRegistryIntegration.userAgent,
|
||||
};
|
||||
|
||||
if (this.hasSecretValue("personalAccessToken"))
|
||||
headers.Authorization = `Bearer ${this.getSecretValue("personalAccessToken")}`;
|
||||
|
||||
const response = await input.fetchAsync(this.url("/octocat"), {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with GitHub Container Registry integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
|
||||
username: owner,
|
||||
package_type: "container",
|
||||
package_name: name,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
if (!release.metadata?.container?.tags || !(release.metadata.container.tags.length > 0)) return acc;
|
||||
|
||||
release.metadata.container.tags.forEach((tag) => {
|
||||
acc.push({
|
||||
latestRelease: tag,
|
||||
latestReleaseAt: new Date(release.updated_at),
|
||||
releaseUrl: release.html_url,
|
||||
releaseDescription: release.description ?? undefined,
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof RequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async getDetailsAsync(
|
||||
api: Octokit,
|
||||
owner: string,
|
||||
name: string,
|
||||
): Promise<DetailsProviderResponse | undefined> {
|
||||
try {
|
||||
const response = await api.rest.packages.getPackageForUser({
|
||||
username: owner,
|
||||
package_type: "container",
|
||||
package_name: name,
|
||||
});
|
||||
|
||||
return {
|
||||
projectUrl: response.data.repository?.html_url ?? response.data.html_url,
|
||||
projectDescription: response.data.repository?.description ?? undefined,
|
||||
isFork: response.data.repository?.fork,
|
||||
isArchived: response.data.repository?.archived,
|
||||
createdAt: new Date(response.data.created_at),
|
||||
starsCount: response.data.repository?.stargazers_count,
|
||||
openIssues: response.data.repository?.open_issues_count,
|
||||
forksCount: response.data.repository?.forks_count,
|
||||
};
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get details for ${owner}\\${name} with GitHub Container Registry integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: error instanceof RequestError ? error.message : String(error),
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getApi() {
|
||||
return new Octokit({
|
||||
baseUrl: this.url("/").origin,
|
||||
request: {
|
||||
fetch: fetchWithTrustedCertificatesAsync,
|
||||
},
|
||||
userAgent: GitHubContainerRegistryIntegration.userAgent,
|
||||
throttle: { enabled: false }, // Disable throttling for this integration, Octokit will retry by default after a set time, thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
|
||||
...(this.hasSecretValue("personalAccessToken") ? { auth: this.getSecretValue("personalAccessToken") } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
}
|
||||
|
||||
const api = this.getApi();
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
|
||||
@@ -21,13 +21,20 @@ export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
|
||||
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
|
||||
export { PlexIntegration } from "./plex/plex-integration";
|
||||
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
|
||||
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
|
||||
|
||||
// Types
|
||||
export type { IntegrationInput } from "./base/integration";
|
||||
export type { DownloadClientJobsAndStatus } from "./interfaces/downloads/download-client-data";
|
||||
export type { ExtendedDownloadClientItem } from "./interfaces/downloads/download-client-items";
|
||||
export type { ExtendedClientStatus } from "./interfaces/downloads/download-client-status";
|
||||
|
||||
export type {
|
||||
FirewallInterface,
|
||||
FirewallCpuSummary,
|
||||
FirewallInterfacesSummary,
|
||||
FirewallVersionSummary,
|
||||
FirewallMemorySummary,
|
||||
} from "./interfaces/firewall-summary/firewall-summary-types";
|
||||
export type { SystemHealthMonitoring } from "./interfaces/health-monitoring/health-monitoring-types";
|
||||
export { MediaRequestStatus } from "./interfaces/media-requests/media-request-types";
|
||||
export type { MediaRequestList, MediaRequestStats } from "./interfaces/media-requests/media-request-types";
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type {
|
||||
FirewallCpuSummary,
|
||||
FirewallInterfacesSummary,
|
||||
FirewallMemorySummary,
|
||||
FirewallVersionSummary,
|
||||
} from "./firewall-summary-types";
|
||||
|
||||
export interface FirewallSummaryIntegration {
|
||||
getFirewallCpuAsync(): Promise<FirewallCpuSummary>;
|
||||
getFirewallMemoryAsync(): Promise<FirewallMemorySummary>;
|
||||
getFirewallInterfacesAsync(): Promise<FirewallInterfacesSummary[]>;
|
||||
getFirewallVersionAsync(): Promise<FirewallVersionSummary>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface FirewallInterfacesSummary {
|
||||
data: FirewallInterface[];
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface FirewallInterface {
|
||||
name: string;
|
||||
receive: number;
|
||||
transmit: number;
|
||||
}
|
||||
|
||||
export interface FirewallVersionSummary {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface FirewallCpuSummary {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface FirewallMemorySummary {
|
||||
used: number;
|
||||
total: number;
|
||||
percent: number;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./linuxserverio-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
|
||||
|
||||
export class LinuxServerIOIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/health"));
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images"));
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
|
||||
if (!release) {
|
||||
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
|
||||
owner,
|
||||
name,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
latestRelease: release.version,
|
||||
latestReleaseAt: release.version_timestamp,
|
||||
releaseDescription: release.changelog?.shift()?.desc,
|
||||
projectUrl: release.github_url,
|
||||
projectDescription: release.description,
|
||||
isArchived: release.deprecated,
|
||||
createdAt: release.initial_date ? new Date(release.initial_date) : undefined,
|
||||
starsCount: release.stars,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const releasesResponseSchema = z.object({
|
||||
data: z.object({
|
||||
repositories: z.object({
|
||||
linuxserver: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
initial_date: z
|
||||
.string()
|
||||
.transform((value) => new Date(value))
|
||||
.optional(),
|
||||
github_url: z.string(),
|
||||
description: z.string(),
|
||||
version: z.string(),
|
||||
version_timestamp: z.string().transform((value) => new Date(value)),
|
||||
stars: z.number(),
|
||||
deprecated: z.boolean(),
|
||||
changelog: z
|
||||
.array(
|
||||
z.object({
|
||||
date: z.string().transform((value) => new Date(value)),
|
||||
desc: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
189
packages/integrations/src/opnsense/opnsense-integration.ts
Normal file
189
packages/integrations/src/opnsense/opnsense-integration.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { createChannelEventHistory } from "@homarr/redis";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { FirewallSummaryIntegration } from "../interfaces/firewall-summary/firewall-summary-integration";
|
||||
import type {
|
||||
FirewallCpuSummary,
|
||||
FirewallInterface,
|
||||
FirewallInterfacesSummary,
|
||||
FirewallMemorySummary,
|
||||
FirewallVersionSummary,
|
||||
} from "../interfaces/firewall-summary/firewall-summary-types";
|
||||
import {
|
||||
opnsenseCPUSchema,
|
||||
opnsenseInterfacesSchema,
|
||||
opnsenseMemorySchema,
|
||||
opnsenseSystemSummarySchema,
|
||||
} from "./opnsense-types";
|
||||
|
||||
@HandleIntegrationErrors([])
|
||||
export class OPNsenseIntegration extends Integration implements FirewallSummaryIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/diagnostics/system/system_information"), {
|
||||
headers: {
|
||||
Authorization: this.getAuthHeaders(),
|
||||
},
|
||||
});
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
const result = await response.json();
|
||||
if (typeof result === "object" && result !== null) return { success: true };
|
||||
|
||||
return TestConnectionError.ParseResult(new ParseError("Expected object data"));
|
||||
}
|
||||
|
||||
private getAuthHeaders() {
|
||||
const username = super.getSecretValue("username");
|
||||
const password = super.getSecretValue("password");
|
||||
return `Basic ${btoa(`${username}:${password}`)}`;
|
||||
}
|
||||
|
||||
public async getFirewallVersionAsync(): Promise<FirewallVersionSummary> {
|
||||
const responseVersion = await fetchWithTrustedCertificatesAsync(
|
||||
this.url("/api/diagnostics/system/system_information"),
|
||||
{
|
||||
headers: {
|
||||
Authorization: this.getAuthHeaders(),
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!responseVersion.ok) {
|
||||
throw new ResponseError(responseVersion);
|
||||
}
|
||||
const summary = opnsenseSystemSummarySchema.parse(await responseVersion.json());
|
||||
|
||||
return {
|
||||
version: summary.versions.at(0) ?? "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
private getInterfacesChannel() {
|
||||
return createChannelEventHistory<FirewallInterface[]>(`integration:${this.integration.id}:interfaces`, 15);
|
||||
}
|
||||
|
||||
public async getFirewallInterfacesAsync(): Promise<FirewallInterfacesSummary[]> {
|
||||
const channel = this.getInterfacesChannel();
|
||||
|
||||
const responseInterfaces = await fetchWithTrustedCertificatesAsync(this.url("/api/diagnostics/traffic/interface"), {
|
||||
headers: {
|
||||
Authorization: this.getAuthHeaders(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!responseInterfaces.ok) {
|
||||
throw new ResponseError(responseInterfaces);
|
||||
}
|
||||
const interfaces = opnsenseInterfacesSchema.parse(await responseInterfaces.json());
|
||||
|
||||
const returnValue: FirewallInterface[] = [];
|
||||
const interfaceKeys = Object.keys(interfaces.interfaces);
|
||||
|
||||
for (const key of interfaceKeys) {
|
||||
const inter = interfaces.interfaces[key];
|
||||
if (!inter) continue;
|
||||
|
||||
const bytesTransmitted = inter["bytes transmitted"];
|
||||
const bytesReceived = inter["bytes received"];
|
||||
const receiveValue = parseInt(bytesReceived, 10);
|
||||
const transmitValue = parseInt(bytesTransmitted, 10);
|
||||
|
||||
returnValue.push({
|
||||
name: inter.name,
|
||||
receive: receiveValue,
|
||||
transmit: transmitValue,
|
||||
});
|
||||
}
|
||||
|
||||
await channel.pushAsync(returnValue);
|
||||
|
||||
return await channel.getSliceAsync(0, 1);
|
||||
}
|
||||
|
||||
public async getFirewallMemoryAsync(): Promise<FirewallMemorySummary> {
|
||||
const responseMemory = await fetchWithTrustedCertificatesAsync(
|
||||
this.url("/api/diagnostics/system/systemResources"),
|
||||
{
|
||||
headers: {
|
||||
Authorization: this.getAuthHeaders(),
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!responseMemory.ok) {
|
||||
throw new ResponseError(responseMemory);
|
||||
}
|
||||
|
||||
const memory = opnsenseMemorySchema.parse(await responseMemory.json());
|
||||
|
||||
// Using parseInt for memoryTotal is normal, the api sends the total memory as a string
|
||||
const memoryTotal = parseInt(memory.memory.total);
|
||||
const memoryUsed = memory.memory.used;
|
||||
const memoryPercent = (100 * memoryUsed) / memoryTotal;
|
||||
return {
|
||||
total: memoryTotal,
|
||||
used: memoryUsed,
|
||||
percent: memoryPercent,
|
||||
};
|
||||
}
|
||||
|
||||
public async getFirewallCpuAsync(): Promise<FirewallCpuSummary> {
|
||||
const responseCpu = await fetchWithTrustedCertificatesAsync(this.url("/api/diagnostics/cpu_usage/stream"), {
|
||||
headers: {
|
||||
Authorization: this.getAuthHeaders(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!responseCpu.ok) {
|
||||
throw new ResponseError(responseCpu);
|
||||
}
|
||||
|
||||
if (!responseCpu.body) {
|
||||
throw new Error("ReadableStream not supported in this environment.");
|
||||
}
|
||||
|
||||
const reader = responseCpu.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let loopCounter = 0;
|
||||
try {
|
||||
while (loopCounter < 10) {
|
||||
loopCounter++;
|
||||
const result = await reader.read();
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
if (!(result.value instanceof Uint8Array)) {
|
||||
throw new Error("Received value is not an Uint8Array.");
|
||||
}
|
||||
|
||||
const value: AllowSharedBufferSource = result.value;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
const lines = chunk.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith("data:")) {
|
||||
continue;
|
||||
}
|
||||
if (loopCounter < 2) {
|
||||
continue;
|
||||
}
|
||||
const data = line.substring(5).trim();
|
||||
const cpuValues = opnsenseCPUSchema.parse(JSON.parse(data));
|
||||
|
||||
return {
|
||||
...cpuValues,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("No valid CPU data found.");
|
||||
} finally {
|
||||
await reader.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
packages/integrations/src/opnsense/opnsense-types.ts
Normal file
30
packages/integrations/src/opnsense/opnsense-types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// API documentation : https://docs.opnsense.org/development/api.html#core-api
|
||||
|
||||
export const opnsenseSystemSummarySchema = z.object({
|
||||
name: z.string(),
|
||||
versions: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const opnsenseMemorySchema = z.object({
|
||||
memory: z.object({
|
||||
total: z.string(),
|
||||
used: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
const interfaceSchema = z.object({
|
||||
"bytes received": z.string(),
|
||||
"bytes transmitted": z.string(),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export const opnsenseInterfacesSchema = z.object({
|
||||
interfaces: z.record(interfaceSchema),
|
||||
time: z.number(),
|
||||
});
|
||||
|
||||
export const opnsenseCPUSchema = z.object({
|
||||
total: z.number(),
|
||||
});
|
||||
109
packages/integrations/src/quay/quay-integration.ts
Normal file
109
packages/integrations/src/quay/quay-integration.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { RequestInit, Response } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./quay-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "QuayIntegration" });
|
||||
|
||||
export class QuayIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined);
|
||||
|
||||
return await callback({
|
||||
Authorization: `token ${this.getSecretValue("personalAccessToken")}`,
|
||||
});
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await this.withHeadersAsync(async (headers) => {
|
||||
return await input.fetchAsync(this.url("/api/v1/discovery"), {
|
||||
headers,
|
||||
});
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
this.url(
|
||||
`/api/v1/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}?includeTags=true&includeStats=true`,
|
||||
),
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const details = {
|
||||
projectDescription: data.description,
|
||||
};
|
||||
|
||||
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
|
||||
if (!tag.name || !tag.last_modified) return acc;
|
||||
|
||||
acc.push({
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: new Date(tag.last_modified),
|
||||
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/integrations/src/quay/quay-schemas.ts
Normal file
11
packages/integrations/src/quay/quay-schemas.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const releasesResponseSchema = z.object({
|
||||
description: z.string().optional(),
|
||||
tags: z.record(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
last_modified: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./interfaces/calendar/calendar-types";
|
||||
export * from "./interfaces/dns-hole-summary/dns-hole-summary-types";
|
||||
export * from "./interfaces/network-controller-summary/network-controller-summary-types";
|
||||
export * from "./interfaces/firewall-summary/firewall-summary-types";
|
||||
export * from "./interfaces/health-monitoring/health-monitoring-types";
|
||||
export * from "./interfaces/indexer-manager/indexer-manager-types";
|
||||
export * from "./interfaces/media-requests/media-request-types";
|
||||
@@ -8,4 +9,5 @@ export * from "./base/searchable-integration";
|
||||
export * from "./homeassistant/homeassistant-types";
|
||||
export * from "./proxmox/proxmox-types";
|
||||
export * from "./unifi-controller/unifi-controller-types";
|
||||
export * from "./opnsense/opnsense-types";
|
||||
export * from "./interfaces/media-releases";
|
||||
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.2.1",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.1",
|
||||
"@mantine/hooks": "^8.2.1",
|
||||
"react": "19.1.0"
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@mantine/hooks": "^8.2.2",
|
||||
"react": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.2.1",
|
||||
"@mantine/notifications": "^8.2.2",
|
||||
"@tabler/icons-react": "^3.34.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.1",
|
||||
"@mantine/hooks": "^8.2.1",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@mantine/hooks": "^8.2.2",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^3.25.76",
|
||||
"zod-form-data": "^2.0.7"
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/ping/eslint.config.js
Normal file
4
packages/ping/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [...baseConfig];
|
||||
1
packages/ping/index.ts
Normal file
1
packages/ping/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
36
packages/ping/package.json
Normal file
36
packages/ping/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@homarr/ping",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.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.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
37
packages/ping/src/index.ts
Normal file
37
packages/ping/src/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
export const sendPingRequestAsync = async (url: string) => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
||||
// 10 seconds timeout:
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
const start = performance.now();
|
||||
|
||||
return await fetch(url, {
|
||||
dispatcher: new LoggingAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false, // Ping should always work, even with untrusted certificates
|
||||
},
|
||||
}),
|
||||
signal: controller.signal,
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
})
|
||||
.then((response) => {
|
||||
const end = performance.now();
|
||||
const durationMs = end - start;
|
||||
return { statusCode: response.status, durationMs };
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(new Error(`Failed to send ping request to "${url}"`, { cause: error }));
|
||||
return {
|
||||
error: extractErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
9
packages/ping/tsconfig.json
Normal file
9
packages/ping/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -27,14 +27,14 @@
|
||||
"@homarr/db": "workspace:^",
|
||||
"@homarr/definitions": "workspace:^",
|
||||
"@homarr/log": "workspace:^",
|
||||
"ioredis": "5.6.1",
|
||||
"ioredis": "5.7.0",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogLevel } from "@homarr/log/constants";
|
||||
import type { LogLevel } from "@homarr/log/constants";
|
||||
|
||||
import { createListChannel, createQueueChannel, createSubPubChannel } from "./lib/channel";
|
||||
|
||||
@@ -9,11 +9,16 @@ export {
|
||||
createIntegrationOptionsChannel,
|
||||
createWidgetOptionsChannel,
|
||||
createChannelWithLatestAndEvents,
|
||||
createChannelEventHistory,
|
||||
handshakeAsync,
|
||||
createSubPubChannel,
|
||||
createGetSetChannel,
|
||||
} from "./lib/channel";
|
||||
|
||||
export const exampleChannel = createSubPubChannel<{ message: string }>("example");
|
||||
export const pingChannel = createSubPubChannel<
|
||||
{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }
|
||||
>("ping");
|
||||
export const pingUrlChannel = createListChannel<string>("ping-url");
|
||||
|
||||
export const homeAssistantEntityState = createSubPubChannel<{
|
||||
|
||||
@@ -232,7 +232,7 @@ export const createChannelEventHistory = <TData>(channelName: string, maxElement
|
||||
if (length <= maxElements) {
|
||||
return;
|
||||
}
|
||||
await getSetClient.ltrim(channelName, length - maxElements, length);
|
||||
await getSetClient.ltrim(channelName, 0, maxElements - 1);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"octokit": "^5.0.3",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.12.0"
|
||||
"undici": "7.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
64
packages/request-handler/src/firewall.ts
Normal file
64
packages/request-handler/src/firewall.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import type { IntegrationKindByCategory } from "@homarr/definitions";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import type {
|
||||
FirewallCpuSummary,
|
||||
FirewallInterfacesSummary,
|
||||
FirewallMemorySummary,
|
||||
FirewallVersionSummary,
|
||||
} from "@homarr/integrations/types";
|
||||
|
||||
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||
|
||||
export const firewallCpuRequestHandler = createCachedIntegrationRequestHandler<
|
||||
FirewallCpuSummary,
|
||||
IntegrationKindByCategory<"firewall">,
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration, _input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return integrationInstance.getFirewallCpuAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "seconds"),
|
||||
queryKey: "firewallCpuSummary",
|
||||
});
|
||||
|
||||
export const firewallMemoryRequestHandler = createCachedIntegrationRequestHandler<
|
||||
FirewallMemorySummary,
|
||||
IntegrationKindByCategory<"firewall">,
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration, _input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getFirewallMemoryAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(15, "seconds"),
|
||||
queryKey: "firewallMemorySummary",
|
||||
});
|
||||
|
||||
export const firewallInterfacesRequestHandler = createCachedIntegrationRequestHandler<
|
||||
FirewallInterfacesSummary[],
|
||||
IntegrationKindByCategory<"firewall">,
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration, _input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getFirewallInterfacesAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(30, "seconds"),
|
||||
queryKey: "firewallInterfacesSummary",
|
||||
});
|
||||
|
||||
export const firewallVersionRequestHandler = createCachedIntegrationRequestHandler<
|
||||
FirewallVersionSummary,
|
||||
IntegrationKindByCategory<"firewall">,
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration, _input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getFirewallVersionAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(1, "hour"),
|
||||
queryKey: "firewallVersionSummary",
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
type PingResponse =
|
||||
| {
|
||||
statusCode: number;
|
||||
durationMs: number;
|
||||
}
|
||||
| {
|
||||
error: string;
|
||||
};
|
||||
export const pingRequestHandler = createCachedWidgetRequestHandler<PingResponse, "app", { url: string }>({
|
||||
queryKey: "pingResult",
|
||||
widgetKind: "app",
|
||||
async requestAsync(input) {
|
||||
return await sendPingRequestAsync(input.url);
|
||||
},
|
||||
cacheDuration: dayjs.duration(1, "minute"),
|
||||
});
|
||||
|
||||
const sendPingRequestAsync = async (url: string) => {
|
||||
try {
|
||||
const start = performance.now();
|
||||
return await fetch(url, {
|
||||
dispatcher: new LoggingAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
}),
|
||||
}).then((response) => {
|
||||
const end = performance.now();
|
||||
logger.debug(`Ping request succeeded url="${url}" status="${response.status}" duration="${end - start}ms"`);
|
||||
return { statusCode: response.status, durationMs: end - start };
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(new Error(`Failed to send ping request to url="${url}"`, { cause: error }));
|
||||
return {
|
||||
error: extractErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.2.1",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
"@mantine/dates": "^8.2.2",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,21 +33,21 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.1",
|
||||
"@mantine/hooks": "^8.2.1",
|
||||
"@mantine/spotlight": "^8.2.1",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@mantine/hooks": "^8.2.2",
|
||||
"@mantine/spotlight": "^8.2.2",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"jotai": "^2.12.5",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,16 +32,16 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.4",
|
||||
"next": "15.4.5",
|
||||
"next-intl": "4.3.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "全局比率"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "媒体发布",
|
||||
"description": "显示来自不同集成的新添加介质或即将发布的版本",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "布局",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": "背景"
|
||||
},
|
||||
"poster": {
|
||||
"label": "海报"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": "显示描述提示"
|
||||
},
|
||||
"showType": {
|
||||
"label": "显示媒体类型徽章"
|
||||
},
|
||||
"showSource": {
|
||||
"label": "显示来源集成"
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": "{length} 分钟"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "媒体请求列表",
|
||||
"description": "查看 Overr 或 Jellyseerr 实例中的所有媒体请求列表",
|
||||
@@ -2309,7 +2338,7 @@
|
||||
"openProjectPage": "打开项目页面",
|
||||
"openReleasePage": "打开发布页面",
|
||||
"releaseDescription": "发布说明",
|
||||
"projectDescription": "",
|
||||
"projectDescription": "项目描述",
|
||||
"created": "已创建",
|
||||
"error": {
|
||||
"label": "错误",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "调试",
|
||||
"info": "信息",
|
||||
"warn": "警告",
|
||||
"error": "错误"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Podívejte se na seznam všech požadavků na média z vaší instance Overseerr nebo Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Globalt Forhold"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "Medieudgivelser",
|
||||
"description": "Vis nyligt tilføjede medier eller kommende udgivelser fra forskellige integrationer",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": "Baggrund"
|
||||
},
|
||||
"poster": {
|
||||
"label": "Plakat"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": "Vis beskrivelsesværktøjstip"
|
||||
},
|
||||
"showType": {
|
||||
"label": "Vis medietype badge"
|
||||
},
|
||||
"showSource": {
|
||||
"label": "Vis kildeintegration"
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": "{length}min"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Medie Forespørgsler Liste",
|
||||
"description": "Se en liste over alle medieforespørgsler fra din Overseerr eller Jellyseerr instans",
|
||||
@@ -2309,7 +2338,7 @@
|
||||
"openProjectPage": "Åbn Projektside",
|
||||
"openReleasePage": "Åbn Udgivelsesside",
|
||||
"releaseDescription": "Udgivelse Beskrivelse",
|
||||
"projectDescription": "",
|
||||
"projectDescription": "Projektbeskrivelse",
|
||||
"created": "Oprettet",
|
||||
"error": {
|
||||
"label": "Fejl",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "Fejlsøg",
|
||||
"info": "Info",
|
||||
"warn": "Advar",
|
||||
"error": "Fejl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Globales Verhältnis"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Liste der Medienanfragen",
|
||||
"description": "Sehen Sie eine Liste aller Medienanfragen von Ihrer Overseerr- oder Jellyseerr-Instanz",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Globales Verhältnis"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Liste der Medienanfragen",
|
||||
"description": "Sehen Sie eine Liste aller Medienanfragen von Ihrer Overseerr- oder Jellyseerr-Instanz",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Δείτε μια λίστα με όλα τα αιτήματα μέσων ενημέρωσης από την περίπτωση Overseerr ή Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2257,6 +2257,9 @@
|
||||
"showDetails": {
|
||||
"label": "Show Details"
|
||||
},
|
||||
"showOnlyIcon": {
|
||||
"label": "Show Only Icon"
|
||||
},
|
||||
"topReleases": {
|
||||
"label": "Top Releases",
|
||||
"description": "The max number of latest releases to show. Zero means no limit."
|
||||
@@ -2273,7 +2276,9 @@
|
||||
"listFoundImages": "List of found images",
|
||||
"listAlreadyImportedImages": "List of already imported images",
|
||||
"allImagesAlreadyImported": "All images already imported",
|
||||
"onlyAdminCanImport": "Only administrators can import from docker"
|
||||
"onlyAdminCanImport": "Only administrators can import from docker",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all"
|
||||
},
|
||||
"provider": {
|
||||
"label": "Provider"
|
||||
@@ -2335,6 +2340,7 @@
|
||||
"starsCount": "Stars",
|
||||
"forksCount": "Forks",
|
||||
"issuesCount": "Open Issues",
|
||||
"markViewed": "Mark as viewed",
|
||||
"openProjectPage": "Open Project Page",
|
||||
"openReleasePage": "Open Release Page",
|
||||
"releaseDescription": "Release Description",
|
||||
@@ -2407,6 +2413,35 @@
|
||||
"internalServerError": "Failed to fetch Network Controller Summary"
|
||||
}
|
||||
},
|
||||
"firewall": {
|
||||
"name": "Firewall Monitoring",
|
||||
"description": "Displays a summary of firewalls",
|
||||
"tab": {
|
||||
"system": "System",
|
||||
"interfaces": "Interfaces"
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": "Unable to get data from firewall"
|
||||
},
|
||||
"option": {
|
||||
"interfaces": "Network interfaces to display"
|
||||
},
|
||||
"widget": {
|
||||
"fwname": "Name",
|
||||
"version": "Version",
|
||||
"versiontitle": "Versions",
|
||||
"cputitle": "CPU usage",
|
||||
"memorytitle": "Memory usage",
|
||||
"cpu": "CPU",
|
||||
"memory": "Memory",
|
||||
"interfaces": {
|
||||
"name": "name",
|
||||
"trans": "Transmited",
|
||||
"recv": "Received",
|
||||
"title": "Network Interfaces"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Notifications",
|
||||
"description": "Display notification history from an integration",
|
||||
@@ -3186,6 +3221,18 @@
|
||||
},
|
||||
"dockerContainers": {
|
||||
"label": "Docker containers"
|
||||
},
|
||||
"firewallCpu": {
|
||||
"label": "Firewall CPU"
|
||||
},
|
||||
"firewallMemory": {
|
||||
"label": "Firewall Memory"
|
||||
},
|
||||
"firewallVersion": {
|
||||
"label": "Firewall Version"
|
||||
},
|
||||
"firewallInterfaces": {
|
||||
"label": "Firewall Interfaces"
|
||||
}
|
||||
},
|
||||
"interval": {
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Mostrar una lista de todas las solicitudes multimedia de tu instancia de Overseerr o Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +620,7 @@
|
||||
"create": {
|
||||
"title": "Créer une nouvelle application",
|
||||
"description": "Créer une nouvelle application ",
|
||||
"action": ""
|
||||
"action": "Ouvrir la création de l'application"
|
||||
},
|
||||
"add": "Ajouter une application"
|
||||
}
|
||||
@@ -705,125 +705,125 @@
|
||||
"error": {
|
||||
"common": {
|
||||
"cause": {
|
||||
"title": ""
|
||||
"title": "Cause avec plus de détails"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Erreur inconnue",
|
||||
"description": "Une erreur inconnue s'est produite, ouvrez la cause ci-dessous pour voir plus de détails"
|
||||
},
|
||||
"parse": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Erreur d'analyse",
|
||||
"description": "La réponse n'a pas pu être analysée. Veuillez vérifier que l'URL pointe vers l'URL de base du service."
|
||||
},
|
||||
"authorization": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"description": "La requête n'a pas été autorisée. Veuillez vérifier que les informations d'identification sont correctes et que vous les avez configurées avec suffisamment d'autorisations."
|
||||
},
|
||||
"statusCode": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"otherDescription": "",
|
||||
"title": "Erreur de réponse",
|
||||
"description": "Réponse {statusCode} ({reason}) inattendue de <url></url>. Veuillez vérifier que l'URL pointe vers l'URL de base de l'intégration.",
|
||||
"otherDescription": "Réponse {statusCode} inattendue de <url></url>reçue. Veuillez vérifier que l'URL pointe vers l'URL de base de l'intégration.",
|
||||
"reason": {
|
||||
"badRequest": "",
|
||||
"notFound": "",
|
||||
"tooManyRequests": "",
|
||||
"internalServerError": "",
|
||||
"serviceUnavailable": "",
|
||||
"gatewayTimeout": ""
|
||||
"badRequest": "Requête incorrecte",
|
||||
"notFound": "Non trouvé",
|
||||
"tooManyRequests": "Trop de requêtes",
|
||||
"internalServerError": "Erreur interne du serveur",
|
||||
"serviceUnavailable": "Service indisponible",
|
||||
"gatewayTimeout": "Délai d'attente de la passerelle dépassé"
|
||||
}
|
||||
},
|
||||
"certificate": {
|
||||
"title": "",
|
||||
"title": "Erreur de certificat",
|
||||
"description": {
|
||||
"expired": "",
|
||||
"notYetValid": "",
|
||||
"untrusted": "",
|
||||
"hostnameMismatch": ""
|
||||
"expired": "Le certificat a expiré.",
|
||||
"notYetValid": "Le certificat n'est pas encore valide.",
|
||||
"untrusted": "Le certificat n'est pas digne de confiance.",
|
||||
"hostnameMismatch": "Le nom d'hôte du certificat ne correspond pas à l'URL."
|
||||
},
|
||||
"alert": {
|
||||
"permission": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Permissions insuffisantes",
|
||||
"message": "Vous n'êtes pas autorisé à faire confiance ou télécharger des certificats. Veuillez contacter votre administrateur pour télécharger le certificat racine nécessaire."
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"title": "Nom d'hôte incohérent",
|
||||
"message": ""
|
||||
"message": "Le nom d'hôte dans le certificat ne correspond pas au nom d'hôte auquel vous vous connectez. Cela pourrait indiquer un risque de sécurité, mais vous pouvez quand même choisir de faire confiance à ce certificat."
|
||||
},
|
||||
"extract": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Échec de l'extraction de l'autorité de certification",
|
||||
"message": "Seuls les certificats auto-signés sans chaîne peuvent être récupérés automatiquement. Si vous utilisez un certificat auto-signé, assurez-vous de télécharger le certificat CA manuellement. Vous pouvez trouver des instructions sur la façon de faire ceci <docsLink></docsLink>."
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"retry": {
|
||||
"label": ""
|
||||
"label": "Réessayer la création"
|
||||
},
|
||||
"trust": {
|
||||
"label": ""
|
||||
"label": "Faire confiance au certificat"
|
||||
},
|
||||
"upload": {
|
||||
"label": ""
|
||||
"label": "Télécharger le certificat"
|
||||
}
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"title": "Faire confiant à l'incohérence du nom d'hôte",
|
||||
"message": ""
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificat approuvé",
|
||||
"message": "Nom d'hôte ajouté à la liste de certificats de confiance"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Impossible de faire confiance au certificat",
|
||||
"message": "Le certificat avec un nom d'hôte incohérent n'a pas pu être approuvé"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selfSigned": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Faire confiance au certificat auto-signé",
|
||||
"message": "Êtes-vous sûr de vouloir faire confiance à ce certificat auto-signé ?"
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Certificat approuvé",
|
||||
"message": "Certificat ajouté à la liste des certificats de confiance"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Impossible de faire confiance au certificat",
|
||||
"message": "Impossible d'ajouter le certificat à la liste des certificats de confiance"
|
||||
}
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Détails",
|
||||
"description": "Examinez les informations sur le certificat avant de décider de lui faire confiance.",
|
||||
"content": {
|
||||
"action": "",
|
||||
"title": ""
|
||||
"action": "Afficher le contenu",
|
||||
"title": "Certificat PEM"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"title": "",
|
||||
"title": "Erreur dans la requête",
|
||||
"description": {
|
||||
"connection": {
|
||||
"hostUnreachable": "",
|
||||
"networkUnreachable": "",
|
||||
"refused": "",
|
||||
"reset": ""
|
||||
"hostUnreachable": "Le serveur n'a pas pu être atteint. Cela signifie généralement que l'hôte est hors ligne ou injoignable depuis votre réseau.",
|
||||
"networkUnreachable": "Le réseau est inaccessible. Veuillez vérifier votre connexion internet ou la configuration du réseau.",
|
||||
"refused": "Le serveur a refusé la connexion. Il n'est peut-être pas en cours d'exécution ou rejette les requêtes sur le port spécifié.",
|
||||
"reset": "La connexion a été fermée de façon inattendue par le serveur. Cela peut se produire si le serveur est instable ou redémarre."
|
||||
},
|
||||
"dns": {
|
||||
"notFound": "",
|
||||
"timeout": "",
|
||||
"noAnswer": ""
|
||||
"notFound": "L'adresse du serveur est introuvable. Veuillez vérifier l'URL pour les fautes de frappe ou les noms de domaine non valides.",
|
||||
"timeout": "La recherche DNS a expiré. Il peut s'agir d'un problème temporaire, veuillez réessayer dans quelques instants.",
|
||||
"noAnswer": "Le serveur DNS n'a pas renvoyé de réponse valide. Le domaine peut exister mais n'a pas d'enregistrements valides."
|
||||
},
|
||||
"timeout": {
|
||||
"aborted": "",
|
||||
"timeout": ""
|
||||
"aborted": "La requête a été annulée avant qu'elle ne puisse être terminée. Cela peut être dû à une action de l'utilisateur ou à un délai d'expiration du système.",
|
||||
"timeout": "La requête a pris trop de temps à être terminée et a été expirée. Vérifiez votre réseau ou réessayez plus tard."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -896,7 +896,7 @@
|
||||
},
|
||||
"tooManyRequests": {
|
||||
"title": "Trop de requêtes en un temps donné",
|
||||
"message": ""
|
||||
"message": "Il y a eu trop de requêtes. Vous avez probablement été limité ou rejeté par le système cible"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -938,12 +938,12 @@
|
||||
"newLabel": "Nouveau domaine"
|
||||
},
|
||||
"personalAccessToken": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Jeton d'accès personnel",
|
||||
"newLabel": "Nouveau jeton d'accès personnel"
|
||||
},
|
||||
"topic": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Sujet",
|
||||
"newLabel": "Nouveau sujet"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1012,7 +1012,7 @@
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"discard": "Abandonner",
|
||||
"close": "",
|
||||
"close": "Fermer",
|
||||
"confirm": "Confirmer",
|
||||
"continue": "Continuer",
|
||||
"previous": "Précédent",
|
||||
@@ -1507,7 +1507,7 @@
|
||||
"width": "Largeur",
|
||||
"height": "Hauteur"
|
||||
},
|
||||
"placeholder": ""
|
||||
"placeholder": "Commencer à écrire vos notes"
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame",
|
||||
@@ -1585,10 +1585,10 @@
|
||||
"description": "Affiche le cours des actions d'une entreprise",
|
||||
"option": {
|
||||
"stock": {
|
||||
"label": ""
|
||||
"label": "Symbole de l'action"
|
||||
},
|
||||
"timeRange": {
|
||||
"label": "",
|
||||
"label": "Intervalle de temps",
|
||||
"option": {
|
||||
"1d": {
|
||||
"label": "1 jour"
|
||||
@@ -1767,7 +1767,7 @@
|
||||
"label": "Afficher les infos de la mémoire"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
"label": "Afficher le temps de disponibilité"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "Afficher les infos sur le système de fichiers"
|
||||
@@ -1776,7 +1776,7 @@
|
||||
"label": "Onglet par défaut"
|
||||
},
|
||||
"visibleClusterSections": {
|
||||
"label": ""
|
||||
"label": "Sections visibles de cluster"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "Exigence de l'indicateur de section"
|
||||
@@ -1848,11 +1848,11 @@
|
||||
}
|
||||
},
|
||||
"dockerContainers": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Statistiques de Docker",
|
||||
"description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)",
|
||||
"option": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "Impossible de récupérer les statistiques des conteneurs"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1955,7 +1955,7 @@
|
||||
"label": "Afficher les entrées Torrent marquées comme terminées"
|
||||
},
|
||||
"showCompletedHttp": {
|
||||
"label": ""
|
||||
"label": "Afficher les entrées diverses marquées comme terminées"
|
||||
},
|
||||
"activeTorrentThreshold": {
|
||||
"label": "Masquer les Torrents terminés sous ce seuil (en kiB/s)"
|
||||
@@ -1970,8 +1970,8 @@
|
||||
"label": "Utiliser le filtre pour calculer le ratio"
|
||||
},
|
||||
"limitPerIntegration": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Limiter les éléments par intégration",
|
||||
"description": "Cela limitera le nombre d'éléments affichés par intégration, pas globalement"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -2051,10 +2051,10 @@
|
||||
"completed": "Complété",
|
||||
"failed": "Échec",
|
||||
"processing": "Traitement en cours",
|
||||
"leeching": "",
|
||||
"stalled": "",
|
||||
"leeching": "En téléchargement",
|
||||
"stalled": "Bloqué",
|
||||
"unknown": "Inconnu",
|
||||
"seeding": ""
|
||||
"seeding": "En partage"
|
||||
},
|
||||
"actions": {
|
||||
"clients": {
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Ratio global"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "Sorties récentes",
|
||||
"description": "Afficher les médias récemment ajoutés ou les publications à venir de différentes intégrations",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "Mise en page",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": "Arrière-plan"
|
||||
},
|
||||
"poster": {
|
||||
"label": "Affiche"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": "Afficher l'info-bulle de description"
|
||||
},
|
||||
"showType": {
|
||||
"label": "Afficher le badge du type de média"
|
||||
},
|
||||
"showSource": {
|
||||
"label": "Afficher l'intégration de la source"
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": "{length}min"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Liste des demandes de médias",
|
||||
"description": "Voir la liste de toutes les demandes de médias de votre instance Overseerr ou Jellyseerr",
|
||||
@@ -2098,15 +2127,15 @@
|
||||
"processing": "Traitement en cours",
|
||||
"partiallyAvailable": "Partiel",
|
||||
"available": "Disponible",
|
||||
"blacklisted": "",
|
||||
"deleted": ""
|
||||
"blacklisted": "Sur la liste noire",
|
||||
"deleted": "Supprimé"
|
||||
},
|
||||
"status": {
|
||||
"pending": "En attente",
|
||||
"approved": "Approuvé",
|
||||
"declined": "Refusé",
|
||||
"failed": "Échec",
|
||||
"completed": ""
|
||||
"completed": "Complété"
|
||||
},
|
||||
"toBeDetermined": "À déterminer"
|
||||
},
|
||||
@@ -2210,115 +2239,115 @@
|
||||
}
|
||||
},
|
||||
"releases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Versions",
|
||||
"description": "Affiche une liste de la version courante des référentiels donnés avec la version regex donnée.",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Nouvelle version dans",
|
||||
"description": "Exemple d'utilisation : 1w (1 semaine), 10M (10 mois). Type d'unité acceptée h (heures), d (jours), w (semaines), M (mois), y (années). Laisser vide pour ne pas mettre en évidence les nouvelles versions."
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Version obsolète dans",
|
||||
"description": "Exemple d'utilisation : 1w (1 semaine), 10M (10 mois). Type d'unité acceptée h (heures), d (jours), w (semaines), M (mois), y (années). Laisser vide pour ne pas mettre en évidence les versions obsolètes."
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Afficher uniquement les surbrillances",
|
||||
"description": "Afficher uniquement les nouvelles versions ou les versions obsolètes. Comme pour les versions ci-dessus."
|
||||
},
|
||||
"showDetails": {
|
||||
"label": ""
|
||||
"label": "Afficher les détails"
|
||||
},
|
||||
"topReleases": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Meilleures sorties",
|
||||
"description": "Le nombre maximum de dernières versions à afficher. Zéro signifie aucune limite."
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"label": "Dépôts",
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
"label": "Ajouter un dépôt"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
"label": "Importer depuis docker",
|
||||
"loading": "Chargement des images docker",
|
||||
"noImagesFound": "Aucune image docker trouvée",
|
||||
"listFoundImages": "Liste des images trouvées",
|
||||
"listAlreadyImportedImages": "Liste des images déjà importées",
|
||||
"allImagesAlreadyImported": "Toutes les images déjà importées",
|
||||
"onlyAdminCanImport": "Seuls les administrateurs peuvent importer depuis docker"
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
"label": "Fournisseur"
|
||||
},
|
||||
"identifier": {
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
"label": "Identifiant",
|
||||
"placeholder": "Nom ou Propriétaire / Nom"
|
||||
},
|
||||
"name": {
|
||||
"label": ""
|
||||
"label": "Nom"
|
||||
},
|
||||
"versionFilter": {
|
||||
"label": "",
|
||||
"label": "Filtre de versions",
|
||||
"prefix": {
|
||||
"label": ""
|
||||
"label": "Préfixe"
|
||||
},
|
||||
"precision": {
|
||||
"label": "",
|
||||
"label": "Précision",
|
||||
"options": {
|
||||
"none": ""
|
||||
"none": "Aucun"
|
||||
}
|
||||
},
|
||||
"suffix": {
|
||||
"label": ""
|
||||
"label": "Suffixe"
|
||||
},
|
||||
"regex": {
|
||||
"label": ""
|
||||
"label": "Expression Régulière"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"label": ""
|
||||
"label": "Modifier"
|
||||
},
|
||||
"editForm": {
|
||||
"title": "",
|
||||
"title": "Modifier le dépôt",
|
||||
"cancel": {
|
||||
"label": ""
|
||||
"label": "Annuler"
|
||||
},
|
||||
"confirm": {
|
||||
"label": ""
|
||||
"label": "Confirmer"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
"title": "Importer depuis Docker"
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
"label": "Exemple"
|
||||
},
|
||||
"invalid": "",
|
||||
"invalid": "Définition de dépôt invalide, veuillez vérifier les valeurs",
|
||||
"noProvider": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"label": "Aucun fournisseur",
|
||||
"tooltip": "Le fournisseur n'a pas pu être analysé, veuillez le définir manuellement après l'importation des images"
|
||||
}
|
||||
}
|
||||
},
|
||||
"not-found": "",
|
||||
"pre-release": "",
|
||||
"archived": "",
|
||||
"not-found": "Non trouvé",
|
||||
"pre-release": "Pré-publication",
|
||||
"archived": "Archivé",
|
||||
"forked": "",
|
||||
"starsCount": "",
|
||||
"starsCount": "Étoiles",
|
||||
"forksCount": "",
|
||||
"issuesCount": "",
|
||||
"openProjectPage": "",
|
||||
"openReleasePage": "",
|
||||
"releaseDescription": "",
|
||||
"projectDescription": "",
|
||||
"created": "",
|
||||
"issuesCount": "Problèmes ouverts",
|
||||
"openProjectPage": "Ouvrir la page du projet",
|
||||
"openReleasePage": "Ouvrir la page de publication",
|
||||
"releaseDescription": "Description de la publication",
|
||||
"projectDescription": "Description du projet",
|
||||
"created": "Créé le",
|
||||
"error": {
|
||||
"label": "",
|
||||
"label": "Erreur",
|
||||
"messages": {
|
||||
"invalidIdentifier": "",
|
||||
"noMatchingVersion": "",
|
||||
"noReleasesFound": "",
|
||||
"noProviderSeleceted": "",
|
||||
"noProviderResponse": ""
|
||||
"invalidIdentifier": "Identifiant non valide",
|
||||
"noMatchingVersion": "Aucune version correspondante trouvée",
|
||||
"noReleasesFound": "Aucune publication trouvée",
|
||||
"noProviderSeleceted": "Aucun fournisseur sélectionné",
|
||||
"noProviderResponse": "Aucune réponse du fournisseur"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2326,62 +2355,62 @@
|
||||
"option": {},
|
||||
"card": {
|
||||
"vpn": {
|
||||
"countConnected": ""
|
||||
"countConnected": "{count} connecté"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "Aucune donnée disponible, toutes les intégrations sont déconnectées",
|
||||
"unknownContentOption": "Option de contenu inconnue pour le widget de résumé du contrôleur réseau : "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "Résumé du contrôleur réseau",
|
||||
"description": "Affiche le résumé d'un contrôleur réseau (comme le contrôleur UniFi)"
|
||||
},
|
||||
"networkControllerStatus": {
|
||||
"card": {
|
||||
"variants": {
|
||||
"wired": {
|
||||
"name": ""
|
||||
"name": "Filaire"
|
||||
},
|
||||
"wifi": {
|
||||
"name": ""
|
||||
"name": "Wi-Fi"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": ""
|
||||
"label": "Utilisateurs"
|
||||
},
|
||||
"guests": {
|
||||
"label": ""
|
||||
"label": "Invités"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"content": {
|
||||
"option": {
|
||||
"wifi": {
|
||||
"label": ""
|
||||
"label": "Wi-Fi"
|
||||
},
|
||||
"wired": {
|
||||
"label": ""
|
||||
"label": "Filaire"
|
||||
}
|
||||
},
|
||||
"label": ""
|
||||
"label": "Contenu du Widget"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "Aucune donnée disponible, toutes les intégrations sont déconnectées",
|
||||
"unknownContentOption": "Option de contenu inconnue pour le widget d'état du réseau: "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "État du réseau",
|
||||
"description": "Afficher les périphériques connectés sur un réseau"
|
||||
},
|
||||
"networkController": {
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "Impossible de récupérer le résumé du contrôleur réseau"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"noItems": "",
|
||||
"name": "Notifications",
|
||||
"description": "Afficher l'historique des notifications à partir d'une intégration",
|
||||
"noItems": "Aucune notification à afficher.",
|
||||
"option": {}
|
||||
}
|
||||
},
|
||||
@@ -2504,10 +2533,10 @@
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "URL de l'arrière-plan",
|
||||
"placeholder": "",
|
||||
"placeholder": "Commencez à taper pour rechercher des images locales",
|
||||
"group": {
|
||||
"your": "",
|
||||
"other": ""
|
||||
"your": "Vos images",
|
||||
"other": "Autres images"
|
||||
}
|
||||
},
|
||||
"backgroundImageAttachment": {
|
||||
@@ -2570,7 +2599,7 @@
|
||||
"label": "Couleur de l'icône"
|
||||
},
|
||||
"clearColor": {
|
||||
"label": ""
|
||||
"label": "Enlever la couleur"
|
||||
},
|
||||
"customCss": {
|
||||
"label": "CSS personnalisé pour ce tableau",
|
||||
@@ -3095,7 +3124,7 @@
|
||||
"idle": "Inactif",
|
||||
"running": "En cours",
|
||||
"error": "Erreur",
|
||||
"disabled": ""
|
||||
"disabled": "Désactivé"
|
||||
},
|
||||
"job": {
|
||||
"minecraftServerStatus": {
|
||||
@@ -3150,28 +3179,28 @@
|
||||
"label": "Transcodage des médias"
|
||||
},
|
||||
"networkController": {
|
||||
"label": ""
|
||||
"label": "Contrôleur réseau"
|
||||
},
|
||||
"refreshNotifications": {
|
||||
"label": ""
|
||||
"label": "Mise à jour des notifications"
|
||||
},
|
||||
"dockerContainers": {
|
||||
"label": ""
|
||||
"label": "Conteneurs Docker"
|
||||
}
|
||||
},
|
||||
"interval": {
|
||||
"seconds": "",
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"seconds": "Chaque {interval, plural, one {}=1 {seconde} other {# secondes}}",
|
||||
"minutes": "Chaque {interval, plural, one {}=1 {minute} other {# minutes}}",
|
||||
"hours": "Chaque {interval, plural, one {}=1 {heure} other {# heures}}",
|
||||
"midnight": "Chaque jour à minuit",
|
||||
"weeklyMonday": "Chaque semaine le lundi"
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
"title": "Paramètres de la tâche pour {jobName}"
|
||||
},
|
||||
"field": {
|
||||
"interval": {
|
||||
"label": ""
|
||||
"label": "Intervalle de planification"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3237,7 +3266,7 @@
|
||||
"updated": "Mis à jour {when}",
|
||||
"search": "Rechercher dans {count} conteneurs",
|
||||
"selected": "{selectCount} sur {totalCount} conteneurs sélectionnés",
|
||||
"footer": ""
|
||||
"footer": "Total des conteneurs {count}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -3257,10 +3286,10 @@
|
||||
},
|
||||
"stats": {
|
||||
"cpu": {
|
||||
"label": ""
|
||||
"label": "Processeur"
|
||||
},
|
||||
"memory": {
|
||||
"label": ""
|
||||
"label": "Mémoire"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
@@ -3271,7 +3300,7 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"title": "",
|
||||
"title": "Actions",
|
||||
"start": {
|
||||
"label": "Début",
|
||||
"notification": {
|
||||
@@ -3376,7 +3405,7 @@
|
||||
"title": "Ressources",
|
||||
"nodes": "Nœuds",
|
||||
"namespaces": "Espaces de noms",
|
||||
"ingresses": "",
|
||||
"ingresses": "Routes",
|
||||
"services": "Services",
|
||||
"pods": "Pods",
|
||||
"configmaps": "ConfigMaps",
|
||||
@@ -3445,7 +3474,7 @@
|
||||
}
|
||||
},
|
||||
"ingresses": {
|
||||
"label": "",
|
||||
"label": "Routes",
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "Nom"
|
||||
@@ -3716,7 +3745,7 @@
|
||||
"certificates": {
|
||||
"label": "Certificats",
|
||||
"hostnames": {
|
||||
"label": ""
|
||||
"label": "Noms d'hôtes"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4117,25 +4146,25 @@
|
||||
"certificate": {
|
||||
"field": {
|
||||
"hostname": {
|
||||
"label": ""
|
||||
"label": "Nom d'Hôte"
|
||||
},
|
||||
"subject": {
|
||||
"label": ""
|
||||
"label": "Sujet"
|
||||
},
|
||||
"issuer": {
|
||||
"label": ""
|
||||
"label": "Émetteur"
|
||||
},
|
||||
"validFrom": {
|
||||
"label": ""
|
||||
"label": "Valable à partir du"
|
||||
},
|
||||
"validTo": {
|
||||
"label": ""
|
||||
"label": "Valable jusqu'au"
|
||||
},
|
||||
"serialNumber": {
|
||||
"label": ""
|
||||
"label": "Numéro de série"
|
||||
},
|
||||
"fingerprint": {
|
||||
"label": ""
|
||||
"label": "Empreinte"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
@@ -4146,19 +4175,19 @@
|
||||
"title": "Il n'y a pas encore de certificats"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Certificat invalide",
|
||||
"description": "Impossible d'analyser le certificat"
|
||||
},
|
||||
"expires": "Expire le {when}",
|
||||
"toHostnames": ""
|
||||
"toHostnames": "Noms d'hôtes de confiance"
|
||||
},
|
||||
"hostnames": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Noms d'hôtes de certificat de confiance",
|
||||
"description": "Certains certificats ne permettent pas au domaine spécifique que Homarr utilise pour les demander, à cause de cela, tous les noms d'hôtes de confiance avec leurs vignettes de certificat sont utilisés pour contourner ces restrictions.",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "Il n'y a pas encore de noms d'hôtes"
|
||||
},
|
||||
"toCertificates": ""
|
||||
"toCertificates": "Certificats"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -4190,19 +4219,29 @@
|
||||
}
|
||||
},
|
||||
"removeHostname": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "Supprimer le nom d'hôte de confiance",
|
||||
"confirm": "Êtes-vous sûr de vouloir supprimer ce nom d'hôte de confiance ? Cela peut empêcher certaines intégrations de fonctionner.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Nom d'hôte supprimé",
|
||||
"message": "Le nom d'hôte a été supprimé avec succès"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Nom d'hôte non supprimé",
|
||||
"message": "Le nom d'hôte n'a pas pu être supprimé"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "Débogage",
|
||||
"info": "Information",
|
||||
"warn": "Avertissement",
|
||||
"error": "Erreur"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "יחס גלובלי"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "רשימת בקשות מדיה",
|
||||
"description": "ראה רשימה של כל בקשות המדיה ממופע Overseerr או Jellyseerr שלך",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Pregledajte popis svih zahtjeva za medijima s vaše instance Overseerr ili Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Az Overseerr vagy Jellyseerr példány összes médiakérelmének listájának megtekintése",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Visualizza un elenco di tutte le richieste media dalla tua istanza Overseerr o Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "グローバル比"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "メディアリリース",
|
||||
"description": "異なる連係機能から、新しく追加されたメディアまたは今後のリリースを表示する",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "レイアウト",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": "背景"
|
||||
},
|
||||
"poster": {
|
||||
"label": "ポスター"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": "説明ツールチップを表示"
|
||||
},
|
||||
"showType": {
|
||||
"label": "メディアタイプのバッジを表示"
|
||||
},
|
||||
"showSource": {
|
||||
"label": "情報元の連携機能を表示"
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": "{length} 分"
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "メディアリクエストリスト",
|
||||
"description": "Overseerr または Jellyseerr からの全てのメディアリクエストのリストを見る",
|
||||
@@ -2309,7 +2338,7 @@
|
||||
"openProjectPage": "プロジェクトページを開く",
|
||||
"openReleasePage": "リリースページを開く",
|
||||
"releaseDescription": "リリースの説明",
|
||||
"projectDescription": "",
|
||||
"projectDescription": "プロジェクトの説明",
|
||||
"created": "作成日",
|
||||
"error": {
|
||||
"label": "エラー",
|
||||
@@ -2570,7 +2599,7 @@
|
||||
"label": "アイコンの色"
|
||||
},
|
||||
"clearColor": {
|
||||
"label": ""
|
||||
"label": "色を消去"
|
||||
},
|
||||
"customCss": {
|
||||
"label": "このボードのカスタム CSS",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "デバッグ",
|
||||
"info": "情報",
|
||||
"warn": "警告",
|
||||
"error": "エラー"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "오버서 또는 젤리서 인스턴스의 모든 미디어 요청 목록 보기",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Peržiūrėkite visų medijų užklausų iš \"Overseerr\" arba \"Jellyseerr\" sąrašą",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Skatiet sarakstu ar visiem multimediju pieprasījumiem no jūsu Overseerr vai Jellyseerr instances",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Globale verhouding"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Media-aanvragen lijst",
|
||||
"description": "Bekijk een lijst met alle media-aanvragen van je Overseerr of Jellyseerr instantie",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Global ratio"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Liste over mediaspillforespørsler",
|
||||
"description": "Se en liste over alle medieforespørsler fra din Overseerr eller Jellyseerr instans",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": "Wskaźnik globalny"
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Lista żądań multimediów",
|
||||
"description": "Zobacz listę wszystkich zapytań o media z Twoich instancji Overseerr lub Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,6 +2079,35 @@
|
||||
},
|
||||
"globalRatio": ""
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"backdrop": {
|
||||
"label": ""
|
||||
},
|
||||
"poster": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": ""
|
||||
},
|
||||
"showType": {
|
||||
"label": ""
|
||||
},
|
||||
"showSource": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"length": {
|
||||
"duration": ""
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "",
|
||||
"description": "Veja uma lista de todas as solicitações de mídia da sua instância do Overseerr ou Jellyseerr",
|
||||
@@ -4204,5 +4233,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": {
|
||||
"option": {
|
||||
"debug": "",
|
||||
"info": "",
|
||||
"warn": "",
|
||||
"error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user