Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94a2f498a7 | ||
|
|
3dc7cd27bc | ||
|
|
53f97f4b50 | ||
|
|
9a5ea40ef0 | ||
|
|
37e2797874 |
No files matched your search
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
name: documentation-sync
|
|
||||||
description: Keep Homarr documentation in sync with code changes. Use when modifying code that affects user-facing behavior, or when adding/modifying widgets, integrations, cron jobs, API routes, env vars, permissions, auth providers, or UI in apps/docs. Covers the docs/ mapping table, integration doc pattern, and widget doc pattern.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Documentation Sync
|
|
||||||
|
|
||||||
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
|
||||||
|
|
||||||
## What triggers a doc update
|
|
||||||
|
|
||||||
| Code change | Doc location |
|
|
||||||
| ---------------------- | ----------------------------------------------------------- |
|
|
||||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
|
||||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
|
||||||
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
|
||||||
| New/changed env vars | `apps/docs/docs/advanced/` |
|
|
||||||
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
|
||||||
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
|
||||||
| UI/UX changes | Relevant getting-started or management pages |
|
|
||||||
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
|
||||||
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
|
||||||
|
|
||||||
## Integration doc pattern
|
|
||||||
|
|
||||||
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
|
||||||
|
|
||||||
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
|
||||||
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
|
||||||
- Import metadata from `@homarr/definitions` where possible
|
|
||||||
|
|
||||||
## Widget doc pattern
|
|
||||||
|
|
||||||
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
|
||||||
|
|
||||||
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
|
||||||
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
- Run docs locally: `pnpm dev:docs` from root
|
|
||||||
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
|
||||||
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
|
||||||
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../.agents/skills
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: codebase-context
|
description: Comprehensive Homarr codebase architecture and conventions reference
|
||||||
description: Comprehensive Homarr codebase architecture and conventions reference. Use when orienting in the monorepo, understanding package dependency layers, the three runtime services and ports, tRPC router structure, multi-database support, widget/integration/cron system architecture, routing, auth providers, env vars, or key patterns before writing code.
|
alwaysApply: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Homarr Codebase Context
|
# Homarr Codebase Context
|
||||||
@@ -27,7 +27,7 @@ Homarr is an open-source, self-hosted dashboard for managing homelab services. I
|
|||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
```text
|
```
|
||||||
homarr/
|
homarr/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||||
@@ -66,6 +66,8 @@ homarr/
|
|||||||
│ ├── analytics/ # Server-side analytics (Umami)
|
│ ├── analytics/ # Server-side analytics (Umami)
|
||||||
│ ├── server-settings/ # Server setting keys/types
|
│ ├── server-settings/ # Server setting keys/types
|
||||||
│ ├── settings/ # User-facing settings UI context
|
│ ├── settings/ # User-facing settings UI context
|
||||||
|
│ ├── old-import/ # Legacy Homarr import
|
||||||
|
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||||
│ └── cli/ # Node CLI for ops (brocli)
|
│ └── cli/ # Node CLI for ops (brocli)
|
||||||
├── tooling/
|
├── tooling/
|
||||||
│ ├── typescript/ # Base tsconfig
|
│ ├── typescript/ # Base tsconfig
|
||||||
@@ -77,11 +79,11 @@ homarr/
|
|||||||
|
|
||||||
## Three Runtime Services
|
## Three Runtime Services
|
||||||
|
|
||||||
| Service | Port | Role |
|
| Service | Port | Role |
|
||||||
| --------- | ---- | -------------------------------------------- |
|
|---------|------|------|
|
||||||
| Next.js | 3000 | Main web app (SSR + API routes) |
|
| Next.js | 3000 | Main web app (SSR + API routes) |
|
||||||
| WebSocket | 3001 | tRPC subscriptions via `ws` |
|
| WebSocket | 3001 | tRPC subscriptions via `ws` |
|
||||||
| Tasks | 3002 | Cron job runner + management API via Fastify |
|
| Tasks | 3002 | Cron job runner + management API via Fastify |
|
||||||
|
|
||||||
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
|
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
|
||||||
|
|
||||||
@@ -110,7 +112,6 @@ In Docker, nginx listens on **7575** and proxies to all three. The tasks service
|
|||||||
### Multi-Database Support
|
### Multi-Database Support
|
||||||
|
|
||||||
Drizzle schemas exist in three parallel implementations:
|
Drizzle schemas exist in three parallel implementations:
|
||||||
|
|
||||||
- `packages/db/schema/sqlite.ts`
|
- `packages/db/schema/sqlite.ts`
|
||||||
- `packages/db/schema/mysql.ts`
|
- `packages/db/schema/mysql.ts`
|
||||||
- `packages/db/schema/postgresql.ts`
|
- `packages/db/schema/postgresql.ts`
|
||||||
@@ -120,7 +121,6 @@ Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-pos
|
|||||||
### Widget System
|
### Widget System
|
||||||
|
|
||||||
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
|
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
|
||||||
|
|
||||||
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
|
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
|
||||||
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
|
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
|
||||||
- Loads dynamically via `next/dynamic`
|
- Loads dynamically via `next/dynamic`
|
||||||
@@ -133,7 +133,6 @@ Integrations are defined in `packages/definitions/src/integration.ts` (`integrat
|
|||||||
### Cron Job System
|
### Cron Job System
|
||||||
|
|
||||||
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
|
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
|
||||||
|
|
||||||
- Uses `createCronJob` with a cron expression
|
- Uses `createCronJob` with a cron expression
|
||||||
- Publishes results to Redis channels
|
- Publishes results to Redis channels
|
||||||
- Widget subscriptions consume those channels via tRPC subscriptions
|
- Widget subscriptions consume those channels via tRPC subscriptions
|
||||||
@@ -161,7 +160,6 @@ Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `o
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
Key env vars (from `.env.example`):
|
Key env vars (from `.env.example`):
|
||||||
|
|
||||||
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
|
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
|
||||||
- `DB_URL`: Database connection string
|
- `DB_URL`: Database connection string
|
||||||
- `AUTH_SECRET`: NextAuth secret
|
- `AUTH_SECRET`: NextAuth secret
|
||||||
@@ -193,3 +191,16 @@ Key env vars (from `.env.example`):
|
|||||||
- Drag-and-drop via `@dnd-kit/*`
|
- Drag-and-drop via `@dnd-kit/*`
|
||||||
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
|
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
|
||||||
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
|
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
|
||||||
|
|
||||||
|
## Documentation (`apps/docs`)
|
||||||
|
|
||||||
|
Docusaurus 3 site at `apps/docs/`, package name `@homarr/docs`.
|
||||||
|
|
||||||
|
- Run with `pnpm dev:docs` from root
|
||||||
|
- Build with `pnpm turbo build --filter=@homarr/docs`
|
||||||
|
- Uses Tailwind CSS 3 for custom pages (not Mantine)
|
||||||
|
- Imports `@homarr/definitions` for type-safe integration/widget metadata
|
||||||
|
- When adding or modifying widgets, integrations, cron jobs, API routes, env vars, or user-facing features, you MUST also update corresponding docs in `apps/docs/docs/`
|
||||||
|
- Integration docs: `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
|
||||||
|
- Widget docs: `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
|
||||||
|
- Strict link checking: broken links/anchors cause build failure
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
description: Keep documentation in sync with code changes
|
||||||
|
globs:
|
||||||
|
- packages/definitions/src/**
|
||||||
|
- packages/widgets/src/**
|
||||||
|
- packages/integrations/src/**
|
||||||
|
- packages/api/src/**
|
||||||
|
- apps/nextjs/src/**
|
||||||
|
- apps/docs/**
|
||||||
|
---
|
||||||
|
|
||||||
|
# Documentation Sync Rule
|
||||||
|
|
||||||
|
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
||||||
|
|
||||||
|
## What triggers a doc update
|
||||||
|
|
||||||
|
| Code change | Doc location |
|
||||||
|
|---|---|
|
||||||
|
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
||||||
|
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
||||||
|
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
||||||
|
| New/changed env vars | `apps/docs/docs/advanced/` |
|
||||||
|
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
||||||
|
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
||||||
|
| UI/UX changes | Relevant getting-started or management pages |
|
||||||
|
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
||||||
|
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
||||||
|
|
||||||
|
## Integration doc pattern
|
||||||
|
|
||||||
|
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
||||||
|
|
||||||
|
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
||||||
|
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
||||||
|
- Import metadata from `@homarr/definitions` where possible
|
||||||
|
|
||||||
|
## Widget doc pattern
|
||||||
|
|
||||||
|
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
||||||
|
|
||||||
|
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
||||||
|
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- Run docs locally: `pnpm dev:docs` from root
|
||||||
|
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
||||||
|
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
||||||
|
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
||||||
+73
-142
@@ -1,8 +1,3 @@
|
|||||||
---
|
|
||||||
name: datatable-migration
|
|
||||||
description: Playbook for migrating tables from mantine-react-table (MRT) to mantine-datatable. Use when converting any table component from mantine-react-table to mantine-datatable, working on DataTable column/sort/expansion/context-menu code, or implementing responsive column visibility, column persistence, or transparent tables in widgets.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Migrating Tables from mantine-react-table to mantine-datatable
|
# Migrating Tables from mantine-react-table to mantine-datatable
|
||||||
|
|
||||||
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
|
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
|
||||||
@@ -12,12 +7,10 @@ This document captures every pattern, fix, and decision from the downloads widge
|
|||||||
Current `mantine-react-table` consumers that could be migrated:
|
Current `mantine-react-table` consumers that could be migrated:
|
||||||
|
|
||||||
**Widgets (in `packages/widgets/`):**
|
**Widgets (in `packages/widgets/`):**
|
||||||
|
|
||||||
- `docker/component.tsx` — Docker container table
|
- `docker/component.tsx` — Docker container table
|
||||||
- `media-server/component.tsx` — Media server sessions
|
- `media-server/component.tsx` — Media server sessions
|
||||||
|
|
||||||
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
|
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
|
||||||
|
|
||||||
- `tools/docker/docker-table.tsx`
|
- `tools/docker/docker-table.tsx`
|
||||||
- `tools/tasks/_components/tasks-table.tsx`
|
- `tools/tasks/_components/tasks-table.tsx`
|
||||||
- `tools/api/components/api-keys.tsx`
|
- `tools/api/components/api-keys.tsx`
|
||||||
@@ -26,13 +19,13 @@ Current `mantine-react-table` consumers that could be migrated:
|
|||||||
- `tools/kubernetes/` — 8 Kubernetes resource tables
|
- `tools/kubernetes/` — 8 Kubernetes resource tables
|
||||||
|
|
||||||
**Modals:**
|
**Modals:**
|
||||||
|
|
||||||
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
|
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
|
||||||
|
|
||||||
**Shared hook to remove once all migrated:**
|
**Shared hook to remove once all migrated:**
|
||||||
|
|
||||||
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
|
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 1. Core Migration: mantine-react-table → mantine-datatable
|
## 1. Core Migration: mantine-react-table → mantine-datatable
|
||||||
|
|
||||||
### Import changes
|
### Import changes
|
||||||
@@ -74,7 +67,6 @@ const columns: DataTableColumn<MyRow>[] = [
|
|||||||
```
|
```
|
||||||
|
|
||||||
Key differences:
|
Key differences:
|
||||||
|
|
||||||
- `accessorKey` → `accessor`
|
- `accessorKey` → `accessor`
|
||||||
- `header` → `title`
|
- `header` → `title`
|
||||||
- `size` → `width`
|
- `size` → `width`
|
||||||
@@ -126,6 +118,8 @@ const sortedData = useMemo(() => {
|
|||||||
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
|
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. Making Tables Transparent (Widget Context)
|
## 2. Making Tables Transparent (Widget Context)
|
||||||
|
|
||||||
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
|
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
|
||||||
@@ -150,21 +144,13 @@ Create a CSS file alongside the component:
|
|||||||
|
|
||||||
/* Hover: translucent instead of opaque */
|
/* Hover: translucent instead of opaque */
|
||||||
.my-table .mantine-datatable-table tbody tr:hover {
|
.my-table .mantine-datatable-table tbody tr:hover {
|
||||||
background-color: color-mix(
|
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||||
in srgb,
|
|
||||||
var(--mantine-color-default-hover) 40%,
|
|
||||||
transparent
|
|
||||||
) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Headers: frosted glass effect */
|
/* Headers: frosted glass effect */
|
||||||
.my-table th {
|
.my-table th {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: color-mix(
|
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
|
||||||
in srgb,
|
|
||||||
var(--mantine-color-body) 60%,
|
|
||||||
transparent
|
|
||||||
) !important;
|
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,16 +172,14 @@ Create a CSS file alongside the component:
|
|||||||
|
|
||||||
/* Row expansion area */
|
/* Row expansion area */
|
||||||
.my-table .mantine-datatable-row-expansion-cell {
|
.my-table .mantine-datatable-row-expansion-cell {
|
||||||
background-color: color-mix(
|
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
|
||||||
in srgb,
|
|
||||||
var(--mantine-color-body) 40%,
|
|
||||||
transparent
|
|
||||||
) !important;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Import it: `import "./styles.css";` and apply via `className="my-table"`.
|
Import it: `import "./styles.css";` and apply via `className="my-table"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 3. Responsive Column Visibility
|
## 3. Responsive Column Visibility
|
||||||
|
|
||||||
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
|
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
|
||||||
@@ -211,38 +195,11 @@ interface SizeConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
|
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
|
||||||
{
|
{ maxWidth: 300, config: { fontSize: "xs", iconSize: 12, cellPadding: 2, showSpeedColumns: false, showTimeColumn: false, showStateColumn: false } },
|
||||||
maxWidth: 300,
|
{ maxWidth: 500, config: { fontSize: "xs", iconSize: 14, cellPadding: 4, showSpeedColumns: false, showTimeColumn: true, showStateColumn: false } },
|
||||||
config: {
|
|
||||||
fontSize: "xs",
|
|
||||||
iconSize: 12,
|
|
||||||
cellPadding: 2,
|
|
||||||
showSpeedColumns: false,
|
|
||||||
showTimeColumn: false,
|
|
||||||
showStateColumn: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 500,
|
|
||||||
config: {
|
|
||||||
fontSize: "xs",
|
|
||||||
iconSize: 14,
|
|
||||||
cellPadding: 4,
|
|
||||||
showSpeedColumns: false,
|
|
||||||
showTimeColumn: true,
|
|
||||||
showStateColumn: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_SIZE_CONFIG: SizeConfig = {
|
const DEFAULT_SIZE_CONFIG: SizeConfig = { fontSize: "sm", iconSize: 14, cellPadding: 4, showSpeedColumns: true, showTimeColumn: true, showStateColumn: true };
|
||||||
fontSize: "sm",
|
|
||||||
iconSize: 14,
|
|
||||||
cellPadding: 4,
|
|
||||||
showSpeedColumns: true,
|
|
||||||
showTimeColumn: true,
|
|
||||||
showStateColumn: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
function getSizeConfig(width: number): SizeConfig {
|
function getSizeConfig(width: number): SizeConfig {
|
||||||
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
|
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
|
||||||
@@ -258,9 +215,7 @@ const size = useMemo(() => getSizeConfig(width), [width]);
|
|||||||
Then use a visibility check lookup table for columns:
|
Then use a visibility check lookup table for columns:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const columnVisibilityChecks: Partial<
|
const columnVisibilityChecks: Partial<Record<string, (ctx: ColumnContext) => boolean>> = {
|
||||||
Record<string, (ctx: ColumnContext) => boolean>
|
|
||||||
> = {
|
|
||||||
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
|
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
|
||||||
downSpeed: (ctx) => ctx.size.showSpeedColumns,
|
downSpeed: (ctx) => ctx.size.showSpeedColumns,
|
||||||
time: (ctx) => ctx.size.showTimeColumn,
|
time: (ctx) => ctx.size.showTimeColumn,
|
||||||
@@ -275,6 +230,8 @@ function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 4. Column Persistence (Drag/Resize → Server)
|
## 4. Column Persistence (Drag/Resize → Server)
|
||||||
|
|
||||||
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
|
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
|
||||||
@@ -306,6 +263,15 @@ Even hidden options need translation keys or the translation test fails:
|
|||||||
"widget.mywidget.option.columnWidths.label": "Column widths"
|
"widget.mywidget.option.columnWidths.label": "Column widths"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Old-import mappings
|
||||||
|
|
||||||
|
Add to `packages/old-import/src/widgets/options.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
columnOrder: () => undefined,
|
||||||
|
columnWidths: () => undefined,
|
||||||
|
```
|
||||||
|
|
||||||
### Dynamic storeKey
|
### Dynamic storeKey
|
||||||
|
|
||||||
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
|
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
|
||||||
@@ -319,13 +285,8 @@ const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted
|
|||||||
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
|
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const {
|
const { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } =
|
||||||
effectiveColumns,
|
useDataTableColumns<MyRow>({ key: storeKey, columns });
|
||||||
columnsOrder,
|
|
||||||
columnsWidth,
|
|
||||||
setColumnsOrder,
|
|
||||||
setMultipleColumnWidths,
|
|
||||||
} = useDataTableColumns<MyRow>({ key: storeKey, columns });
|
|
||||||
|
|
||||||
const lastStoreKey = useRef(storeKey);
|
const lastStoreKey = useRef(storeKey);
|
||||||
const hydrated = useRef(false);
|
const hydrated = useRef(false);
|
||||||
@@ -339,22 +300,11 @@ useEffect(() => {
|
|||||||
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
|
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
|
||||||
if (Object.keys(savedWidths).length > 0) {
|
if (Object.keys(savedWidths).length > 0) {
|
||||||
setMultipleColumnWidths(
|
setMultipleColumnWidths(
|
||||||
Object.entries(savedWidths).map(([accessor, w]) => ({
|
Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w })),
|
||||||
accessor,
|
|
||||||
width: w,
|
|
||||||
})),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => { hydrated.current = true; });
|
||||||
hydrated.current = true;
|
}, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]);
|
||||||
});
|
|
||||||
}, [
|
|
||||||
storeKey,
|
|
||||||
savedOrder,
|
|
||||||
savedWidths,
|
|
||||||
setColumnsOrder,
|
|
||||||
setMultipleColumnWidths,
|
|
||||||
]);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Persist localStorage → server
|
### Persist localStorage → server
|
||||||
@@ -367,26 +317,22 @@ const prevWidths = useRef(columnsWidth);
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hydrated.current) return;
|
if (!hydrated.current) return;
|
||||||
const orderChanged =
|
const orderChanged = JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
|
||||||
JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
|
const widthsChanged = JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
|
||||||
const widthsChanged =
|
|
||||||
JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
|
|
||||||
prevOrder.current = columnsOrder;
|
prevOrder.current = columnsOrder;
|
||||||
prevWidths.current = columnsWidth;
|
prevWidths.current = columnsWidth;
|
||||||
|
|
||||||
if (!orderChanged && !widthsChanged) return;
|
if (!orderChanged && !widthsChanged) return;
|
||||||
|
|
||||||
if (orderChanged)
|
if (orderChanged) persistOption({ columnOrder: JSON.stringify(columnsOrder) });
|
||||||
persistOption({ columnOrder: JSON.stringify(columnsOrder) });
|
|
||||||
if (widthsChanged) {
|
if (widthsChanged) {
|
||||||
const widthMap: Record<string, number> = {};
|
const widthMap: Record<string, number> = {};
|
||||||
for (const entry of columnsWidth) {
|
for (const entry of columnsWidth) {
|
||||||
const key = Object.keys(entry)[0];
|
const key = Object.keys(entry)[0];
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
const v = entry[key as keyof typeof entry];
|
const w = entry[key as keyof typeof entry];
|
||||||
if (typeof v === "number") widthMap[key] = v;
|
if (typeof w === "number") widthMap[key] = w;
|
||||||
else if (typeof v === "string" && v.endsWith("px"))
|
else if (typeof w === "string" && w.endsWith("px")) widthMap[key] = parseInt(w, 10);
|
||||||
widthMap[key] = parseInt(v, 10);
|
|
||||||
}
|
}
|
||||||
persistOption({ columnWidths: JSON.stringify(widthMap) });
|
persistOption({ columnWidths: JSON.stringify(widthMap) });
|
||||||
}
|
}
|
||||||
@@ -395,6 +341,8 @@ useEffect(() => {
|
|||||||
|
|
||||||
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
|
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 5. Context Menu (Right-Click)
|
## 5. Context Menu (Right-Click)
|
||||||
|
|
||||||
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
|
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
|
||||||
@@ -426,41 +374,15 @@ const handleContextMenu = useCallback(
|
|||||||
The menu component uses `Portal` for correct z-index:
|
The menu component uses `Portal` for correct z-index:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
function RowContextMenu({
|
function RowContextMenu({ state, onClose }: { state: ContextMenuState; onClose: () => void }) {
|
||||||
state,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
state: ContextMenuState;
|
|
||||||
onClose: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Menu
|
<Menu opened onClose={onClose} closeOnItemClick={false} position="right-start" offset={0}>
|
||||||
opened
|
|
||||||
onClose={onClose}
|
|
||||||
closeOnItemClick={false}
|
|
||||||
position="right-start"
|
|
||||||
offset={0}
|
|
||||||
>
|
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<Box
|
<Box style={{ position: "fixed", left: state.x, top: state.y, width: 0, height: 0 }} />
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
left: state.x,
|
|
||||||
top: state.y,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<Menu.Item
|
<Menu.Item onClick={() => { /* action */ onClose(); }}>Action</Menu.Item>
|
||||||
onClick={() => {
|
|
||||||
/* action */ onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Action
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Portal>
|
</Portal>
|
||||||
@@ -470,6 +392,8 @@ function RowContextMenu({
|
|||||||
|
|
||||||
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
|
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 6. Row Expansion
|
## 6. Row Expansion
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -489,6 +413,8 @@ function RowContextMenu({
|
|||||||
|
|
||||||
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
|
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 7. Common Pitfalls & Fixes
|
## 7. Common Pitfalls & Fixes
|
||||||
|
|
||||||
### Status indicator dots
|
### Status indicator dots
|
||||||
@@ -549,11 +475,8 @@ Always wrap in try/catch:
|
|||||||
```typescript
|
```typescript
|
||||||
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
|
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
|
||||||
if (!value) return fallback;
|
if (!value) return fallback;
|
||||||
try {
|
try { return JSON.parse(value) as T; }
|
||||||
return JSON.parse(value) as T;
|
catch { return fallback; }
|
||||||
} catch {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -571,19 +494,20 @@ If the widget has configurable default sort, sync it:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSortStatus({
|
setSortStatus({ columnAccessor: options.defaultSort, direction: defaultSortDirection });
|
||||||
columnAccessor: options.defaultSort,
|
|
||||||
direction: defaultSortDirection,
|
|
||||||
});
|
|
||||||
}, [options.defaultSort, defaultSortDirection]);
|
}, [options.defaultSort, defaultSortDirection]);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 8. Accessibility
|
## 8. Accessibility
|
||||||
|
|
||||||
- All `ActionIcon` components need `aria-label`
|
- All `ActionIcon` components need `aria-label`
|
||||||
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
|
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
|
||||||
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
|
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 9. Error Handling
|
## 9. Error Handling
|
||||||
|
|
||||||
- Every mutation needs an `onError` callback with `showErrorNotification`
|
- Every mutation needs an `onError` callback with `showErrorNotification`
|
||||||
@@ -594,14 +518,12 @@ useEffect(() => {
|
|||||||
```typescript
|
```typescript
|
||||||
const mutationOptions = {
|
const mutationOptions = {
|
||||||
onSettled: () => void utils.myQuery.invalidate(),
|
onSettled: () => void utils.myQuery.invalidate(),
|
||||||
onError: () =>
|
onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }),
|
||||||
showErrorNotification({
|
|
||||||
title: t("errors.actionFailed"),
|
|
||||||
message: t("errors.actionFailedMessage"),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 10. i18n
|
## 10. i18n
|
||||||
|
|
||||||
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
|
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
|
||||||
@@ -612,6 +534,8 @@ const mutationOptions = {
|
|||||||
- Errors: `t("errors.{errorKey}")`
|
- Errors: `t("errors.{errorKey}")`
|
||||||
- Hidden options still need `.label` translation keys
|
- Hidden options still need `.label` translation keys
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 11. Edit Mode
|
## 11. Edit Mode
|
||||||
|
|
||||||
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
|
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
|
||||||
@@ -626,6 +550,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
|
|||||||
<DataTable style={{ pointerEvents: tablePointerEvents }} />
|
<DataTable style={{ pointerEvents: tablePointerEvents }} />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 12. DataTable Props Reference (Commonly Used)
|
## 12. DataTable Props Reference (Commonly Used)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -664,6 +590,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
|
|||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 13. Layout Stability
|
## 13. Layout Stability
|
||||||
|
|
||||||
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
|
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
|
||||||
@@ -672,6 +600,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
|
|||||||
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
|
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
|
||||||
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
|
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 14. Checklist for Each Migration
|
## 14. Checklist for Each Migration
|
||||||
|
|
||||||
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
|
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
|
||||||
@@ -680,12 +610,13 @@ if (isEditMode) rowContextMenuHandler = undefined;
|
|||||||
4. Add CSS file for transparent backgrounds (if widget context)
|
4. Add CSS file for transparent backgrounds (if widget context)
|
||||||
5. Add responsive column visibility (if widget context)
|
5. Add responsive column visibility (if widget context)
|
||||||
6. Add hidden options + hydration logic (if column persistence needed)
|
6. Add hidden options + hydration logic (if column persistence needed)
|
||||||
7. Add translation keys (including hidden option labels)
|
7. Add old-import mappings for new options
|
||||||
8. Add context menu if row actions exist
|
8. Add translation keys (including hidden option labels)
|
||||||
9. Add row expansion if detail view exists
|
9. Add context menu if row actions exist
|
||||||
10. Add error handling on all mutations
|
10. Add row expansion if detail view exists
|
||||||
11. Add accessibility attributes
|
11. Add error handling on all mutations
|
||||||
12. Test at multiple widget widths
|
12. Add accessibility attributes
|
||||||
13. Update docs in `apps/docs/docs/widgets/` if applicable
|
13. Test at multiple widget widths
|
||||||
14. Run `pnpm turbo typecheck`
|
14. Update docs in `apps/docs/docs/widgets/` if applicable
|
||||||
15. Run translation spec — catches missing keys
|
15. Run `pnpm turbo typecheck` — catches old-import mismatches
|
||||||
|
16. Run translation spec — catches missing keys
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: mcp-integration
|
description: How to expose tRPC procedures as MCP tools for AI clients
|
||||||
description: Expose tRPC procedures as MCP tools for AI clients. Use when modifying procedures in packages/api/src/router, adding a new tRPC procedure or router, or deciding what to expose via Homarr's MCP interface. Covers .meta({ mcp }), router registration in packages/api/src/mcp.ts, input schema rules, and what to expose vs skip.
|
globs: packages/api/src/router/**/*.ts
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# MCP Integration
|
# MCP Integration Guide
|
||||||
|
|
||||||
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
|
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
|
||||||
|
|
||||||
Exposing a procedure makes it callable by any AI client with an API key. Before adding `.meta({ mcp })`, review what the tool exposes: authorization requirements, sensitive data, tenant isolation, auditability, and whether the action is destructive. Prefer exposing read-only queries over mutations unless the mutation is safe and permission-scoped. When in doubt, ask for a security review before shipping a new MCP tool.
|
|
||||||
|
|
||||||
## Adding MCP to a procedure
|
## Adding MCP to a procedure
|
||||||
|
|
||||||
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
|
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
|
||||||
@@ -17,7 +16,7 @@ Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure cha
|
|||||||
// Read-only tool (query)
|
// Read-only tool (query)
|
||||||
myProcedure: protectedProcedure
|
myProcedure: protectedProcedure
|
||||||
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
|
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
|
||||||
.input(z.object({ limit: z.number().default(10) }))
|
.input(z.object({ limit: z.number().default(50) }))
|
||||||
.query(async ({ ctx, input }) => { ... })
|
.query(async ({ ctx, input }) => { ... })
|
||||||
|
|
||||||
// Write tool (mutation)
|
// Write tool (mutation)
|
||||||
@@ -39,27 +38,23 @@ The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()`
|
|||||||
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
|
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
|
||||||
|
|
||||||
**Must include:**
|
**Must include:**
|
||||||
|
|
||||||
- What the tool returns or does
|
- What the tool returns or does
|
||||||
- Which integrations/services it works with (if applicable)
|
- Which integrations/services it works with (if applicable)
|
||||||
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
|
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
|
||||||
- Permission requirements (if non-obvious)
|
- Permission requirements (if non-obvious)
|
||||||
|
|
||||||
**Good:**
|
**Good:**
|
||||||
|
```
|
||||||
```text
|
|
||||||
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
|
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Bad:**
|
**Bad:**
|
||||||
|
```
|
||||||
```text
|
|
||||||
"Get calendar events"
|
"Get calendar events"
|
||||||
```
|
```
|
||||||
|
|
||||||
**For tools that return permission fields**, explain what they mean:
|
**For tools that return permission fields**, explain what they mean:
|
||||||
|
```
|
||||||
```text
|
|
||||||
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
|
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -82,13 +77,11 @@ export const mcpRouter = createTRPCRouter({
|
|||||||
## What to expose vs. what to skip
|
## What to expose vs. what to skip
|
||||||
|
|
||||||
**Expose:**
|
**Expose:**
|
||||||
|
|
||||||
- Data queries (list, search, get by ID)
|
- Data queries (list, search, get by ID)
|
||||||
- Actions users would ask an AI to do (toggle, create, delete, approve)
|
- Actions users would ask an AI to do (toggle, create, delete, approve)
|
||||||
- Status checks (health, stats, summaries)
|
- Status checks (health, stats, summaries)
|
||||||
|
|
||||||
**Skip:**
|
**Skip:**
|
||||||
|
|
||||||
- Subscriptions (WebSocket-only, not supported by MCP)
|
- Subscriptions (WebSocket-only, not supported by MCP)
|
||||||
- Internal procedures (session management, onboarding steps)
|
- Internal procedures (session management, onboarding steps)
|
||||||
- File upload/download procedures
|
- File upload/download procedures
|
||||||
@@ -33,10 +33,6 @@ body:
|
|||||||
options:
|
options:
|
||||||
# The below comment is used to insert a new version with on-release.yml
|
# The below comment is used to insert a new version with on-release.yml
|
||||||
#NEXT_VERSION#
|
#NEXT_VERSION#
|
||||||
- 1.77.0
|
|
||||||
- 1.76.2
|
|
||||||
- 1.76.1
|
|
||||||
- 1.76.0
|
|
||||||
- 1.75.0
|
- 1.75.0
|
||||||
- 1.74.0
|
- 1.74.0
|
||||||
- 1.73.0
|
- 1.73.0
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 24.18.0
|
node-version: 24.18.0
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
|
- run: npm i -g pnpm
|
||||||
|
if: env.SKIP_RELEASE == 'false'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
+1
-2
@@ -71,8 +71,7 @@ apps/nextjs/public/images/background.png
|
|||||||
|
|
||||||
# personal agent config
|
# personal agent config
|
||||||
.opencode
|
.opencode
|
||||||
.agents/*
|
.agents/
|
||||||
!.agents/skills/
|
|
||||||
skills-lock.json
|
skills-lock.json
|
||||||
|
|
||||||
# docusaurus
|
# docusaurus
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mantine": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@mantine/mcp-server"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -84,16 +84,6 @@ When modifying user-facing code, you MUST also update the corresponding document
|
|||||||
- Lint: oxlint (not ESLint)
|
- Lint: oxlint (not ESLint)
|
||||||
- Format: oxfmt (not Prettier)
|
- Format: oxfmt (not Prettier)
|
||||||
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
||||||
- Mantine: use the `mantine` MCP server (configured in `.mcp.json`; tools `get_item_doc` / `get_item_props` / `search_docs`) for current v9 APIs before writing component code. Prefer built-in primitives (`Combobox`/`useCombobox` for selects, the polymorphic `component` prop, `@mantine/hooks`) over hand-rolled equivalents. Check `packages/ui/` for existing conventions first.
|
|
||||||
|
|
||||||
## MCP servers
|
|
||||||
|
|
||||||
`.mcp.json` at the repo root declares project-scoped MCP servers (the standard Claude Code layout; other tools can import the same `mcpServers` block). See the `mantine` entry there for the Mantine v9 server, and the upstream docs at https://mantine.dev/guides/llms/ for per-tool setup (OpenCode: `.opencode/opencode.json`, Codex: `.codex/config.toml`). Only keyless server definitions belong in `.mcp.json`; anything needing secrets stays in local dotfiles.
|
|
||||||
|
|
||||||
## Agent Skills
|
|
||||||
|
|
||||||
Portable agent skills live in `.agents/skills/` (the Agent Skills open standard). Cursor, Codex, and OpenCode read `.agents/skills/` natively. Claude Code does not scan it, so `.claude/skills/` is a symlink back to `.agents/skills/`. Install skills at `.agents/skills/<name>/SKILL.md` — that single copy is used everywhere. See `.agents/skills/` for available skills (documentation-sync, mcp-integration, codebase-context, datatable-migration).
|
|
||||||
|
|
||||||
- Icons: @tabler/icons-react
|
- Icons: @tabler/icons-react
|
||||||
- Docs app can import from `@homarr/definitions` for shared types
|
- Docs app can import from `@homarr/definitions` for shared types
|
||||||
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
## [1.77.1](https://github.com/homarr-labs/homarr/compare/v1.77.0...v1.77.1) (2026-09-11)
|
|
||||||
|
|
||||||
## [1.77.0](https://github.com/homarr-labs/homarr/compare/v1.76.2...v1.77.0) (2026-09-06)
|
|
||||||
|
|
||||||
## [1.76.2](https://github.com/homarr-labs/homarr/compare/v1.76.1...v1.76.2) (2026-08-27)
|
|
||||||
|
|
||||||
## [1.76.1](https://github.com/homarr-labs/homarr/compare/v1.76.0...v1.76.1) (2026-08-26)
|
|
||||||
|
|
||||||
## [1.76.0](https://github.com/homarr-labs/homarr/compare/v1.75.0...v1.76.0) (2026-08-21)
|
|
||||||
|
|
||||||
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
|
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
|
||||||
|
|
||||||
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
|
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
|
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
A Helm chart to deploy homarr for Kubernetes
|
A Helm chart to deploy homarr for Kubernetes
|
||||||
|
|
||||||
@@ -429,7 +429,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
|
|||||||
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
|
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
|
||||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||||
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
|
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
|
||||||
| image.tag | string | `"v1.76.1"` | Overrides the image tag whose default is the chart appVersion |
|
| image.tag | string | `"v1.75.0"` | Overrides the image tag whose default is the chart appVersion |
|
||||||
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
|
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
|
||||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
| ingress.annotations | object | `{}` | Ingress annotations |
|
||||||
| ingress.enabled | bool | `false` | Enable ingress |
|
| ingress.enabled | bool | `false` | Enable ingress |
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import Alert from '@theme/Admonition';
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
The storage values shown by these widgets come from each pool's root ZFS dataset, so they already account for RAIDZ parity overhead and dataset limits and match what `zfs list` and the TrueNAS web interface report. When a pool cannot report its dataset space, Homarr falls back to the pool's physical values, which read higher on RAIDZ.
|
|
||||||
|
|
||||||
### Adding the integration
|
### Adding the integration
|
||||||
<AddingIntegration />
|
<AddingIntegration />
|
||||||
|
|
||||||
|
|||||||
@@ -52,21 +52,14 @@ The certificates are stored in the `/appdata/trusted-certificates` directory whi
|
|||||||
This means you can automate the update of the certificates by simply replacing or adding a new file in the directory.
|
This means you can automate the update of the certificates by simply replacing or adding a new file in the directory.
|
||||||
|
|
||||||
## Obtaining certificates
|
## Obtaining certificates
|
||||||
|
|
||||||
Every integration has its own way of obtaining certificates.
|
Every integration has its own way of obtaining certificates.
|
||||||
Generally it is recommended to search for the documentation of the integration you are using.
|
Generally it is recommended to search for the documentation of the integration you are using.
|
||||||
Most of the time you can find the certificate in the file system of the integration.
|
Most of the time you can find the certificate in the file system of the integration.
|
||||||
The certificate file needs to be uploaded to Homarr. To first get the file on your computer you can either:
|
|
||||||
- use `scp user@host:/path/to/file ./certificate.pem`
|
|
||||||
- use `cat /path/to/file` to print the certificate. Then recreate the file on your machine.
|
|
||||||
|
|
||||||
Below you can find instructions for some of the integrations.
|
Below you can find instructions for some of the integrations.
|
||||||
|
|
||||||
### Pi-hole (v6)
|
### Pi-hole (v6)
|
||||||
Since version 6 of Pi-hole, the web interface can be accessed through HTTPS.
|
|
||||||
The certificate can be found in the path `/etc/pihole/tls_ca.crt`.
|
|
||||||
You can find more details regarding SSL / TLS for Pi-hole [here](https://docs.pi-hole.net/api/tls/).
|
|
||||||
|
|
||||||
### Proxmox
|
Since version 6 of Pi-hole, the web interface can be accessed through HTTPS.
|
||||||
By default, Proxmox VE uses a node certificate signed by the cluster. You only need to configure Homarr with the certificate of a single node.
|
The certificate can be found in the path `/etc/pihole/tls_ca.crt`. To add the certificate, this file needs to be copied and uploaded to Homarr.
|
||||||
The certificate can be found in any node on the path `/etc/pve/pve-root-ca.pem`.
|
You can find more details regarding SSL / TLS for Pi-hole [here](https://docs.pi-hole.net/api/tls/).
|
||||||
You can learn more about certificate management in Proxmox [here](https://pve.proxmox.com/wiki/Certificate_Management).
|
|
||||||
@@ -1,27 +1,30 @@
|
|||||||
---
|
---
|
||||||
title: "App"
|
title: 'App'
|
||||||
description: "Embeds an app into the board."
|
description: 'Embeds an app into the board.'
|
||||||
hide_title: true
|
hide_title: true
|
||||||
---
|
---
|
||||||
|
|
||||||
import { AddingWidget } from "@site/src/components/widgets/adding";
|
import { AddingWidget } from '@site/src/components/widgets/adding';
|
||||||
import { WidgetConfig } from "@site/src/components/widgets/configuration";
|
import { WidgetConfig } from '@site/src/components/widgets/configuration';
|
||||||
import { WidgetHeader } from "@site/src/components/widgets/header";
|
import { WidgetHeader } from '@site/src/components/widgets/header';
|
||||||
import { appWidget } from ".";
|
import { appWidget } from '.';
|
||||||
|
|
||||||
<WidgetHeader widget={appWidget} categories={["Links", "Apps"]} />
|
|
||||||
|
<WidgetHeader
|
||||||
|
widget={appWidget}
|
||||||
|
categories={['Links', 'Apps']}
|
||||||
|
/>
|
||||||
|
|
||||||
The app widget allows you to add a link to an application or a website that you want to access quickly from your Homarr board.
|
The app widget allows you to add a link to an application or a website that you want to access quickly from your Homarr board.
|
||||||
It also supports a status check to indicate whether Homarr can reach the application or website.
|
It also supports a status check to indicate whether Homarr can reach the application or website.
|
||||||
In view mode, right-clicking uses the browser's link menu. Homarr's widget menu remains available in edit mode.
|
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
import githubApp from "./img/github.png";
|
import githubApp from './img/github.png';
|
||||||
import jellyseerrApp from "./img/jellyseerr-description-tooltip.png";
|
import jellyseerrApp from './img/jellyseerr-description-tooltip.png';
|
||||||
import piHoleApp from "./img/pi-hole-ping-icon.png";
|
import piHoleApp from './img/pi-hole-ping-icon.png';
|
||||||
|
|
||||||
<div className="flex gap-1">
|
<div className='flex gap-1'>
|
||||||
<img src={githubApp} alt="app github with-status-check" width={128 * 1.5} height={128 * 1.5} />
|
<img src={githubApp} alt="app github with-status-check" width={128 * 1.5} height={128 * 1.5} />
|
||||||
<img src={piHoleApp} alt="app pi-hole with-status-check status-check-icon" width={128 * 1.5} height={128 * 1.5} />
|
<img src={piHoleApp} alt="app pi-hole with-status-check status-check-icon" width={128 * 1.5} height={128 * 1.5} />
|
||||||
<img src={jellyseerrApp} alt="app jellyseerr with-description-tooltip" height={128 * 1.5} />
|
<img src={jellyseerrApp} alt="app jellyseerr with-description-tooltip" height={128 * 1.5} />
|
||||||
@@ -31,5 +34,4 @@ import piHoleApp from "./img/pi-hole-ping-icon.png";
|
|||||||
<AddingWidget />
|
<AddingWidget />
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
<WidgetConfig configuration={appWidget.configuration} />
|
<WidgetConfig configuration={appWidget.configuration} />
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "Bookmarks"
|
title: 'Bookmarks'
|
||||||
description: "Displays multiple app links"
|
description: 'Displays multiple app links'
|
||||||
hide_title: true
|
hide_title: true
|
||||||
---
|
---
|
||||||
|
|
||||||
import { AddingWidget } from "@site/src/components/widgets/adding";
|
import { AddingWidget } from '@site/src/components/widgets/adding';
|
||||||
import { WidgetConfig } from "@site/src/components/widgets/configuration";
|
import { WidgetConfig } from '@site/src/components/widgets/configuration';
|
||||||
import { WidgetHeader } from "@site/src/components/widgets/header";
|
import { WidgetHeader } from '@site/src/components/widgets/header';
|
||||||
import { bookmarksWidget } from ".";
|
import { bookmarksWidget } from '.';
|
||||||
|
|
||||||
<WidgetHeader widget={bookmarksWidget} categories={["Links", "Apps"]} />
|
|
||||||
|
<WidgetHeader
|
||||||
|
widget={bookmarksWidget}
|
||||||
|
categories={['Links', 'Apps']}
|
||||||
|
/>
|
||||||
|
|
||||||
The bookmarks widget allows you to display multiple app links in a single widget.
|
The bookmarks widget allows you to display multiple app links in a single widget.
|
||||||
You can add links to your favorite applications, websites, or any other resources you want quick access to.
|
You can add links to your favorite applications, websites, or any other resources you want quick access to.
|
||||||
In view mode, right-clicking uses the browser's link menu. Homarr's widget menu remains available in edit mode.
|
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
import gridIconsOnly from "./img/grid-icons-only.png";
|
import gridIconsOnly from './img/grid-icons-only.png';
|
||||||
import gridWithTitle from "./img/grid-with-title.png";
|
import gridWithTitle from './img/grid-with-title.png';
|
||||||
import verticalListAll from "./img/vertical-list-all.png";
|
import verticalListAll from './img/vertical-list-all.png';
|
||||||
import vericalListSimple from "./img/vertical-list-simple.png";
|
import vericalListSimple from './img/vertical-list-simple.png';
|
||||||
|
|
||||||
<div className='flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
@@ -30,13 +33,12 @@ import vericalListSimple from "./img/vertical-list-simple.png";
|
|||||||
|
|
||||||
<img src={gridWithTitle} alt="bookmarks grid with-title" width={256 + 4} height={256 + 4} />
|
<img src={gridWithTitle} alt="bookmarks grid with-title" width={256 + 4} height={256 + 4} />
|
||||||
<img src={verticalListAll} alt="bookmarks vertical-list all-elements" width={128} height={256 + 4} />
|
<img src={verticalListAll} alt="bookmarks vertical-list all-elements" width={128} height={256 + 4} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### Adding the widget
|
### Adding the widget
|
||||||
|
|
||||||
<AddingWidget />
|
<AddingWidget />
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
<WidgetConfig configuration={bookmarksWidget.configuration} />
|
<WidgetConfig configuration={bookmarksWidget.configuration} />
|
||||||
@@ -43,7 +43,6 @@ import temperature from './img/temperature.png';
|
|||||||
### Interactions
|
### Interactions
|
||||||
|
|
||||||
- Toggle entity state by clicking on the entity (only when clickable enabled).
|
- Toggle entity state by clicking on the entity (only when clickable enabled).
|
||||||
- After a successful toggle, the displayed state refreshes immediately.
|
|
||||||
|
|
||||||
### Adding the widget
|
### Adding the widget
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,4 @@ import withDisplayname from './img/with-displayname.png';
|
|||||||
<AddingWidget />
|
<AddingWidget />
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Use the Home Assistant entity ID (for example, `automation.turn_on_lights`) in the **Automation ID** field, not the automation's internal ID.
|
|
||||||
|
|
||||||
<WidgetConfig configuration={smartHomeExecuteAutomationWidget.configuration} />
|
<WidgetConfig configuration={smartHomeExecuteAutomationWidget.configuration} />
|
||||||
+14
-14
@@ -21,35 +21,35 @@
|
|||||||
"verify:search": "node ./scripts/verify-algolia-search.mjs"
|
"verify:search": "node ./scripts/verify-algolia-search.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^3.10.1",
|
"@docusaurus/core": "^3.10.2",
|
||||||
"@docusaurus/faster": "^3.10.1",
|
"@docusaurus/faster": "^3.10.2",
|
||||||
"@docusaurus/plugin-content-docs": "^3.10.1",
|
"@docusaurus/plugin-content-docs": "^3.10.2",
|
||||||
"@docusaurus/plugin-sitemap": "^3.10.1",
|
"@docusaurus/plugin-sitemap": "^3.10.2",
|
||||||
"@docusaurus/preset-classic": "^3.10.1",
|
"@docusaurus/preset-classic": "^3.10.2",
|
||||||
"@homarr/definitions": "workspace:^",
|
"@homarr/definitions": "workspace:^",
|
||||||
"@mdx-js/react": "^3.1.1",
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@nivo/line": "^0.99.0",
|
"@nivo/line": "^0.99.0",
|
||||||
"@scalar/api-reference-react": "catalog:",
|
"@scalar/api-reference-react": "catalog:",
|
||||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tailwindcss/postcss": "^4.3.2",
|
"@tailwindcss/postcss": "^4.3.3",
|
||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"docusaurus-plugin-image-zoom": "^3.0.1",
|
"docusaurus-plugin-image-zoom": "^3.0.1",
|
||||||
"leader-line-new": "^1.1.9",
|
"leader-line-new": "^1.1.9",
|
||||||
"postcss": "^8.5.16",
|
"postcss": "^8.5.26",
|
||||||
"posthog-docusaurus": "^2.0.5",
|
"posthog-docusaurus": "^2.0.5",
|
||||||
"prism-react-renderer": "^2.4.1",
|
"prism-react-renderer": "^2.4.1",
|
||||||
"random-word-slugs": "^0.1.7",
|
"random-word-slugs": "^0.1.7",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
"swiper": "^14.0.5",
|
"swiper": "^14.1.0",
|
||||||
"tailwindcss": "^4.3.2"
|
"tailwindcss": "^4.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
"@docusaurus/module-type-aliases": "^3.10.2",
|
||||||
"@docusaurus/tsconfig": "^3.10.1",
|
"@docusaurus/tsconfig": "^3.10.2",
|
||||||
"@docusaurus/types": "^3.10.1",
|
"@docusaurus/types": "^3.10.2",
|
||||||
"@fec/remark-a11y-emoji": "^4.0.2",
|
"@fec/remark-a11y-emoji": "^4.0.2",
|
||||||
"@tsconfig/docusaurus": "^2.0.9",
|
"@tsconfig/docusaurus": "^2.0.9",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@docusaurus/theme-common": "^3.10.1",
|
"@docusaurus/theme-common": "^3.10.2",
|
||||||
"@docusaurus/theme-mermaid": "^3.10.1"
|
"@docusaurus/theme-mermaid": "^3.10.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -178,37 +178,35 @@ export const BoardSettingsForm = ({ board, permissions, hasFullAccess, hideVisib
|
|||||||
const isPending = savePartialSettings.isPending || saveLayouts.isPending;
|
const isPending = savePartialSettings.isPending || saveLayouts.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xl">
|
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
||||||
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
<Stack gap="xl">
|
||||||
<Stack gap="xl">
|
<GeneralSettingsContent board={board} form={form} />
|
||||||
<GeneralSettingsContent board={board} form={form} />
|
<LayoutSettingsContent form={form} />
|
||||||
<LayoutSettingsContent form={form} />
|
<BackgroundSettingsContent form={form} />
|
||||||
<BackgroundSettingsContent form={form} />
|
<ColorSettingsContent form={form} />
|
||||||
<ColorSettingsContent form={form} />
|
<CustomCssSettingsContent form={form} />
|
||||||
<CustomCssSettingsContent form={form} />
|
<BehaviorSettingsContent form={form} />
|
||||||
<BehaviorSettingsContent form={form} />
|
|
||||||
|
|
||||||
{form.isDirty() && (
|
{hasFullAccess && (
|
||||||
<UnsavedChangesBar>
|
<SectionCard title={tSection("access.title")}>
|
||||||
<Button type="button" disabled={isPending} variant="default" onClick={handleDiscard}>
|
<BoardAccessSettings board={board} initialPermissions={permissions} />
|
||||||
{t("common.action.discard")}
|
</SectionCard>
|
||||||
</Button>
|
)}
|
||||||
<Button loading={isPending} type="submit" disabled={!form.isValid()}>
|
|
||||||
{t("common.action.saveChanges")}
|
|
||||||
</Button>
|
|
||||||
</UnsavedChangesBar>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{hasFullAccess && (
|
{hasFullAccess && <DangerZoneSettingsContent hideVisibility={hideVisibility} />}
|
||||||
<SectionCard title={tSection("access.title")}>
|
|
||||||
<BoardAccessSettings board={board} initialPermissions={permissions} />
|
|
||||||
</SectionCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasFullAccess && <DangerZoneSettingsContent hideVisibility={hideVisibility} />}
|
{form.isDirty() && (
|
||||||
</Stack>
|
<UnsavedChangesBar>
|
||||||
|
<Button type="button" disabled={isPending} variant="default" onClick={handleDiscard}>
|
||||||
|
{t("common.action.discard")}
|
||||||
|
</Button>
|
||||||
|
<Button loading={isPending} type="submit" disabled={!form.isValid()}>
|
||||||
|
{t("common.action.saveChanges")}
|
||||||
|
</Button>
|
||||||
|
</UnsavedChangesBar>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export async function GET() {
|
|||||||
const date = new Date().toISOString().split("T")[0];
|
const date = new Date().toISOString().split("T")[0];
|
||||||
const filename = `homarr-backup-${date}.zip`;
|
const filename = `homarr-backup-${date}.zip`;
|
||||||
|
|
||||||
return new NextResponse(zipBuffer, {
|
return new NextResponse(zipBuffer as unknown as BodyInit, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/zip",
|
"Content-Type": "application/zip",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { db } from "@homarr/db";
|
|||||||
|
|
||||||
import { getPackageVersion } from "~/versions/package-reader";
|
import { getPackageVersion } from "~/versions/package-reader";
|
||||||
import { extractMcpTools } from "../_extract-tools";
|
import { extractMcpTools } from "../_extract-tools";
|
||||||
import { serializeMcpToolResult } from "../_serialize-result";
|
|
||||||
|
|
||||||
const logger = createLogger({ module: "mcpRoute" });
|
const logger = createLogger({ module: "mcpRoute" });
|
||||||
|
|
||||||
@@ -204,7 +203,7 @@ const mcpHandler = createMcpHandler(
|
|||||||
const input = args && Object.keys(args).length > 0 ? args : undefined;
|
const input = args && Object.keys(args).length > 0 ? args : undefined;
|
||||||
const result = await procedure(input);
|
const result = await procedure(input);
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text" as const, text: serializeMcpToolResult(result) }],
|
content: [{ type: "text" as const, text: JSON.stringify(result) }],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = sanitizeErrorMessage(error);
|
const message = sanitizeErrorMessage(error);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
import { serializeMcpToolResult } from "./_serialize-result";
|
|
||||||
|
|
||||||
describe("serializeMcpToolResult", () => {
|
|
||||||
it("serializes objects as JSON strings", () => {
|
|
||||||
expect(serializeMcpToolResult({ boardId: "1" })).toBe('{"boardId":"1"}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("serializes primitive values", () => {
|
|
||||||
expect(serializeMcpToolResult("hello")).toBe('"hello"');
|
|
||||||
expect(serializeMcpToolResult(42)).toBe("42");
|
|
||||||
expect(serializeMcpToolResult(true)).toBe("true");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("serializes null as the string null", () => {
|
|
||||||
expect(serializeMcpToolResult(null)).toBe("null");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("serializes void results as the string null so MCP content text is always a string", () => {
|
|
||||||
expect(serializeMcpToolResult(undefined)).toBe("null");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export function serializeMcpToolResult(result: unknown): string {
|
|
||||||
return JSON.stringify(result) ?? "null";
|
|
||||||
}
|
|
||||||
@@ -44,13 +44,9 @@ interface WidgetContextMenuProps {
|
|||||||
|
|
||||||
export const WidgetContextMenu = ({ item, definition, widgetStateRef, children }: WidgetContextMenuProps) => {
|
export const WidgetContextMenu = ({ item, definition, widgetStateRef, children }: WidgetContextMenuProps) => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [isEditMode] = useEditMode();
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
if (!session || !settings.enableRightClickOnWidgets) return <>{children}</>;
|
if (!session || !settings.enableRightClickOnWidgets) return <>{children}</>;
|
||||||
const isLinkWidget = item.kind === "app" || item.kind === "bookmarks";
|
|
||||||
// Keep the Homarr menu in edit mode so these widgets can still be edited or removed.
|
|
||||||
if (isLinkWidget && !isEditMode) return <>{children}</>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetContextMenuInner item={item} definition={definition} widgetStateRef={widgetStateRef} settings={settings}>
|
<WidgetContextMenuInner item={item} definition={definition} widgetStateRef={widgetStateRef} settings={settings}>
|
||||||
|
|||||||
@@ -301,8 +301,6 @@ interface UseCssVariableConfiguration {
|
|||||||
isDynamic: boolean;
|
isDynamic: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const minimumResizeWidthDelta = 20;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This hook is used to configure the css variables for the gridstack
|
* This hook is used to configure the css variables for the gridstack
|
||||||
* Those css variables are used to define the size of the gridstack items
|
* Those css variables are used to define the size of the gridstack items
|
||||||
@@ -321,30 +319,9 @@ const useCssVariableConfiguration = ({
|
|||||||
columnCount,
|
columnCount,
|
||||||
isDynamic,
|
isDynamic,
|
||||||
}: UseCssVariableConfiguration) => {
|
}: UseCssVariableConfiguration) => {
|
||||||
const prevSizeRef = useRef<{ width: number; height: number } | null>(null);
|
|
||||||
|
|
||||||
const onResize = useCallback(() => {
|
const onResize = useCallback(() => {
|
||||||
if (!wrapperRef.current) return;
|
if (!wrapperRef.current) return;
|
||||||
if (!gridRef.current) return;
|
if (!gridRef.current) return;
|
||||||
|
|
||||||
const currentWidth = wrapperRef.current.clientWidth;
|
|
||||||
const currentHeight = wrapperRef.current.clientHeight;
|
|
||||||
const prevSize = prevSizeRef.current;
|
|
||||||
|
|
||||||
// Only skip recalculation when the width change is insignificant
|
|
||||||
// and, for dynamic sections, the height is unchanged. This prevents infinite
|
|
||||||
// feedback loops when the vertical scrollbar appears/disappears, causing
|
|
||||||
// clientWidth to oscillate by ~15px, while still reacting to height changes.
|
|
||||||
if (
|
|
||||||
prevSize !== null &&
|
|
||||||
Math.abs(currentWidth - prevSize.width) < minimumResizeWidthDelta &&
|
|
||||||
(!isDynamic || currentHeight === prevSize.height)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSizeRef.current = { width: currentWidth, height: currentHeight };
|
|
||||||
|
|
||||||
handleResizeChange(
|
handleResizeChange(
|
||||||
wrapperRef.current,
|
wrapperRef.current,
|
||||||
gridRef.current,
|
gridRef.current,
|
||||||
|
|||||||
@@ -7,6 +7,42 @@ import { createHomarrContainer } from "./shared/create-homarr-container";
|
|||||||
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
||||||
|
|
||||||
describe("Onboarding", () => {
|
describe("Onboarding", () => {
|
||||||
|
test("Credentials onboarding should be successful", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||||
|
const homarrContainer = await createHomarrContainer({
|
||||||
|
mounts: {
|
||||||
|
"/appdata": localMountPath,
|
||||||
|
},
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const context = await browser.newContext();
|
||||||
|
const page = await context.newPage();
|
||||||
|
const actions = new OnboardingActions(page, db);
|
||||||
|
const assertions = new OnboardingAssertions(page, db);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Act
|
||||||
|
await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
|
||||||
|
await actions.startOnboardingAsync("scratch");
|
||||||
|
await actions.processUserStepAsync({
|
||||||
|
username: "admin",
|
||||||
|
password: "Comp(exP4sswOrd",
|
||||||
|
confirmPassword: "Comp(exP4sswOrd",
|
||||||
|
});
|
||||||
|
await actions.processSettingsStepAsync();
|
||||||
|
await actions.processIntegrationsStepAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await assertions.assertFinishStepVisibleAsync();
|
||||||
|
await assertions.assertUserAndAdminGroupInsertedAsync("admin");
|
||||||
|
await assertions.assertDbOnboardingStepAsync("finish");
|
||||||
|
} finally {
|
||||||
|
await Promise.all([browser.close(), homarrContainer.stop()]);
|
||||||
|
}
|
||||||
|
}, 60_000);
|
||||||
|
|
||||||
test("External provider onboarding setup should be successful", async () => {
|
test("External provider onboarding setup should be successful", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ http {
|
|||||||
|
|
||||||
# Route websockets traffic to port 3001
|
# Route websockets traffic to port 3001
|
||||||
location /websockets {
|
location /websockets {
|
||||||
proxy_pass http://127.0.0.1:3001;
|
proxy_pass http://${HOSTNAME}:3001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "Upgrade";
|
proxy_set_header Connection "Upgrade";
|
||||||
@@ -18,7 +18,7 @@ http {
|
|||||||
|
|
||||||
# Route all other traffic to port 3000
|
# Route all other traffic to port 3000
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:3000;
|
proxy_pass http://${HOSTNAME}:3000;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homarr",
|
"name": "homarr",
|
||||||
"version": "1.77.1",
|
"version": "1.75.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env CI=true turbo build",
|
"build": "cross-env CI=true turbo build",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
@@ -42,13 +41,6 @@ export const smartHomeRouter = createTRPCRouter({
|
|||||||
const client = await createIntegrationAsync(integration);
|
const client = await createIntegrationAsync(integration);
|
||||||
const success = await client.triggerToggleAsync(input.entityId);
|
const success = await client.triggerToggleAsync(input.entityId);
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Home Assistant failed to toggle the entity",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}),
|
}),
|
||||||
executeAutomation: protectedProcedure
|
executeAutomation: protectedProcedure
|
||||||
@@ -63,13 +55,6 @@ export const smartHomeRouter = createTRPCRouter({
|
|||||||
.input(z.object({ automationId: z.string() }))
|
.input(z.object({ automationId: z.string() }))
|
||||||
.mutation(async ({ ctx: { integration }, input }) => {
|
.mutation(async ({ ctx: { integration }, input }) => {
|
||||||
const client = await createIntegrationAsync(integration);
|
const client = await createIntegrationAsync(integration);
|
||||||
const success = await client.triggerAutomationAsync(input.automationId);
|
await client.triggerAutomationAsync(input.automationId);
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Home Assistant failed to trigger the automation",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -43,7 +43,7 @@ async function importCookiesAsync() {
|
|||||||
|
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
if (typeof window !== "undefined") return window.location.origin;
|
if (typeof window !== "undefined") return window.location.origin;
|
||||||
return "http://127.0.0.1:3000";
|
return `http://${process.env.HOSTNAME ?? "localhost"}:3000`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const trpcPath = "/api/trpc";
|
export const trpcPath = "/api/trpc";
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"cookie": "catalog:",
|
"cookie": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
|
"node-fetch": "catalog:",
|
||||||
"octokit": "catalog:",
|
"octokit": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
|||||||
|
|
||||||
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
||||||
import { withTimeoutAsync } from "./timeout";
|
import { withTimeoutAsync } from "./timeout";
|
||||||
|
|
||||||
export const createCustomCheckServerIdentity = (
|
export const createCustomCheckServerIdentity = (
|
||||||
trustedHostnames: TrustedCertificateHostname[],
|
trustedHostnames: TrustedCertificateHostname[],
|
||||||
): typeof checkServerIdentity => {
|
): typeof checkServerIdentity => {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"@jellyfin/sdk": "catalog:",
|
"@jellyfin/sdk": "catalog:",
|
||||||
"ical.js": "catalog:",
|
"ical.js": "catalog:",
|
||||||
"maria2": "catalog:",
|
"maria2": "catalog:",
|
||||||
|
"node-fetch": "catalog:",
|
||||||
"octokit": "catalog:",
|
"octokit": "catalog:",
|
||||||
"proxmox-api": "catalog:",
|
"proxmox-api": "catalog:",
|
||||||
"timezones-ical-library": "catalog:",
|
"timezones-ical-library": "catalog:",
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
|
"@types/node-fetch": "catalog:",
|
||||||
"@types/node-unifi": "catalog:",
|
"@types/node-unifi": "catalog:",
|
||||||
"@types/ws": "catalog:",
|
"@types/ws": "catalog:",
|
||||||
"@types/xml2js": "catalog:",
|
"@types/xml2js": "catalog:",
|
||||||
|
|||||||
@@ -134,10 +134,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
|
|||||||
*/
|
*/
|
||||||
private async postAsync(path: `/api/${string}`, body: Record<string, string>) {
|
private async postAsync(path: `/api/${string}`, body: Record<string, string>) {
|
||||||
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||||
headers: {
|
headers: this.getAuthHeaders(),
|
||||||
...this.getAuthHeaders(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,12 +59,10 @@ const ws = vi.hoisted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = responder(payload.method as string, (payload.params as unknown[] | undefined) ?? []);
|
const result = responder(payload.method as string, (payload.params as unknown[] | undefined) ?? []);
|
||||||
// A responder returning an Error mimics a method the server rejects, e.g. an unsupported filter.
|
|
||||||
const body = result instanceof Error ? { error: { message: result.message } } : { result };
|
|
||||||
const envelope =
|
const envelope =
|
||||||
payload.jsonrpc === "2.0"
|
payload.jsonrpc === "2.0"
|
||||||
? { jsonrpc: "2.0", id: payload.id, ...body }
|
? { jsonrpc: "2.0", id: payload.id, result }
|
||||||
: { msg: "result", id: payload.id, ...body };
|
: { msg: "result", id: payload.id, result };
|
||||||
this.respond(JSON.stringify(envelope));
|
this.respond(JSON.stringify(envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,10 +91,7 @@ const ws = vi.hoisted(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const logs = vi.hoisted(() => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }));
|
|
||||||
|
|
||||||
vi.mock("ws", () => ({ WebSocket: ws.FakeWebSocket }));
|
vi.mock("ws", () => ({ WebSocket: ws.FakeWebSocket }));
|
||||||
vi.mock("@homarr/core/infrastructure/logs", () => ({ createLogger: () => logs }));
|
|
||||||
vi.mock("@homarr/core/infrastructure/certificates", () => ({
|
vi.mock("@homarr/core/infrastructure/certificates", () => ({
|
||||||
getAllTrustedCertificatesAsync: vi.fn().mockResolvedValue([]),
|
getAllTrustedCertificatesAsync: vi.fn().mockResolvedValue([]),
|
||||||
getTrustedCertificateHostnamesAsync: vi.fn().mockResolvedValue([]),
|
getTrustedCertificateHostnamesAsync: vi.fn().mockResolvedValue([]),
|
||||||
@@ -153,14 +148,6 @@ const happyResponder = (method: string) => {
|
|||||||
];
|
];
|
||||||
case "pool.query":
|
case "pool.query":
|
||||||
return [{ name: "tank", status: "ONLINE", healthy: true, free: 500, size: 1000, allocated: 500 }];
|
return [{ name: "tank", status: "ONLINE", healthy: true, free: 500, size: 1000, allocated: 500 }];
|
||||||
case "pool.dataset.query":
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: "tank",
|
|
||||||
used: { parsed: 200 },
|
|
||||||
available: { parsed: 300 },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
case "interface.query":
|
case "interface.query":
|
||||||
return [{ id: "eth0", name: "eth0" }];
|
return [{ id: "eth0", name: "eth0" }];
|
||||||
case "reporting.netdata_get_data":
|
case "reporting.netdata_get_data":
|
||||||
@@ -172,11 +159,6 @@ const happyResponder = (method: string) => {
|
|||||||
|
|
||||||
const emptyAggregations = () => ({ min: {}, mean: {}, max: {} });
|
const emptyAggregations = () => ({ min: {}, mean: {}, max: {} });
|
||||||
|
|
||||||
// `pool.query` reports 500 allocated of a 1000 byte pool, leaving 500 free.
|
|
||||||
const physicalFallbackFileSystem = [{ deviceName: "tank", available: "500", used: "500", percentage: 50 }];
|
|
||||||
|
|
||||||
const ERROR_MESSAGE_LIMIT = 200;
|
|
||||||
|
|
||||||
let integrationCounter = 0;
|
let integrationCounter = 0;
|
||||||
const createIntegration = (decryptedSecrets: IntegrationSecret[]) =>
|
const createIntegration = (decryptedSecrets: IntegrationSecret[]) =>
|
||||||
new TrueNasIntegration({
|
new TrueNasIntegration({
|
||||||
@@ -198,7 +180,6 @@ describe("TrueNasIntegration", () => {
|
|||||||
ws.constructedUrls.length = 0;
|
ws.constructedUrls.length = 0;
|
||||||
ws.failures.length = 0;
|
ws.failures.length = 0;
|
||||||
ws.setResponder(happyResponder);
|
ws.setResponder(happyResponder);
|
||||||
logs.warn.mockClear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("fetches and maps system info over the JSON-RPC API", async () => {
|
test("fetches and maps system info over the JSON-RPC API", async () => {
|
||||||
@@ -210,18 +191,6 @@ describe("TrueNasIntegration", () => {
|
|||||||
// JSON-RPC does not use the legacy "connect" session handshake.
|
// JSON-RPC does not use the legacy "connect" session handshake.
|
||||||
expect(ws.sentPayloads.some((payload) => payload.msg === "connect")).toBe(false);
|
expect(ws.sentPayloads.some((payload) => payload.msg === "connect")).toBe(false);
|
||||||
|
|
||||||
const datasetPayload = ws.sentPayloads.find((payload) => payload.method === "pool.dataset.query");
|
|
||||||
expect(datasetPayload).toMatchObject({
|
|
||||||
params: [
|
|
||||||
[["id", "in", ["tank"]]],
|
|
||||||
{
|
|
||||||
extra: {
|
|
||||||
properties: ["used", "available"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
version: "TrueNAS-SCALE-24.10",
|
version: "TrueNAS-SCALE-24.10",
|
||||||
cpuModelName: "Intel Xeon",
|
cpuModelName: "Intel Xeon",
|
||||||
@@ -235,131 +204,12 @@ describe("TrueNasIntegration", () => {
|
|||||||
loadAverage: null,
|
loadAverage: null,
|
||||||
gpu: [],
|
gpu: [],
|
||||||
network: { up: 20_000, down: 10_000 },
|
network: { up: 20_000, down: 10_000 },
|
||||||
// `available` and `used` come from the root dataset, not the physical pool.
|
// `available` is the free space left on the pool (free = 500), not its total size.
|
||||||
fileSystem: [{ deviceName: "tank", available: "300", used: "200", percentage: 40 }],
|
fileSystem: [{ deviceName: "tank", available: "500", used: "500", percentage: 50 }],
|
||||||
smart: [{ deviceName: "tank", healthy: true, overallStatus: "ONLINE", temperature: null }],
|
smart: [{ deviceName: "tank", healthy: true, overallStatus: "ONLINE", temperature: null }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("falls back to physical pool space when the root dataset is not returned", async () => {
|
|
||||||
ws.setResponder((method) => (method === "pool.dataset.query" ? [] : happyResponder(method)));
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem).toMatchObject(physicalFallbackFileSystem);
|
|
||||||
// Dropping the pool would also hide its health status.
|
|
||||||
expect(result.smart).toMatchObject([{ deviceName: "tank", healthy: true, overallStatus: "ONLINE" }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("falls back to physical pool space when the root dataset reports no usable values", async () => {
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query"
|
|
||||||
? [{ id: "tank", used: { parsed: null }, available: null }]
|
|
||||||
: happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem).toMatchObject(physicalFallbackFileSystem);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("falls back to physical pool space when the root dataset omits the requested properties", async () => {
|
|
||||||
// Datasets that cannot report space, e.g. while locked, come back without `used` and `available`.
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query" ? [{ id: "tank", locked: true }] : happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem).toMatchObject(physicalFallbackFileSystem);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("falls back to physical pool space when the dataset response fails schema validation", async () => {
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query" ? { unexpected: "not an array" } : happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem).toMatchObject(physicalFallbackFileSystem);
|
|
||||||
// A malformed payload must not take the rest of the system info down with it.
|
|
||||||
expect(result).toMatchObject({ cpuTemp: 45, memAvailableInBytes: 6_000_000_000, uptime: 3600 });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("falls back to physical pool space when the dataset query is rejected", async () => {
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query"
|
|
||||||
? new Error("[EINVAL] filters: Value error, Invalid operation: in")
|
|
||||||
: happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem).toMatchObject(physicalFallbackFileSystem);
|
|
||||||
// A rejected dataset query must not take the rest of the system info down with it.
|
|
||||||
expect(result).toMatchObject({ cpuTemp: 45, memAvailableInBytes: 6_000_000_000, uptime: 3600 });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("logs the truncated rejection reason rather than the generic wrapper message", async () => {
|
|
||||||
// TrueNAS answers a rejected method with its Python traceback, which runs to several kilobytes.
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query"
|
|
||||||
? new Error(`[EINVAL] query-filters ${"x".repeat(5000)}`)
|
|
||||||
: happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
const logged = logs.warn.mock.calls.find(([message]) => String(message).includes("Could not retrieve"));
|
|
||||||
const reason = String((logged?.[1] as { error?: string } | undefined)?.error);
|
|
||||||
// The decorator would otherwise surface only "An unknown error occured while executing Integration method".
|
|
||||||
expect(reason).toContain("[EINVAL] query-filters");
|
|
||||||
expect(reason).toHaveLength(ERROR_MESSAGE_LIMIT + 1); // truncated, plus the ellipsis
|
|
||||||
});
|
|
||||||
|
|
||||||
test("keeps reporting a pool that is unhealthy but has no usable dataset space", async () => {
|
|
||||||
ws.setResponder((method) => {
|
|
||||||
if (method === "pool.query")
|
|
||||||
return [
|
|
||||||
{ name: "tank", status: "ONLINE", healthy: true, free: 500, size: 1000, allocated: 500 },
|
|
||||||
{ name: "backup", status: "DEGRADED", healthy: false, free: 10, size: 100, allocated: 90 },
|
|
||||||
];
|
|
||||||
if (method === "pool.dataset.query") return [{ id: "tank", used: { parsed: 200 }, available: { parsed: 300 } }];
|
|
||||||
return happyResponder(method);
|
|
||||||
});
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.smart).toMatchObject([
|
|
||||||
{ deviceName: "tank", healthy: true, overallStatus: "ONLINE" },
|
|
||||||
{ deviceName: "backup", healthy: false, overallStatus: "DEGRADED" },
|
|
||||||
]);
|
|
||||||
expect(result.fileSystem).toMatchObject([
|
|
||||||
{ deviceName: "tank", available: "300", used: "200", percentage: 40 },
|
|
||||||
{ deviceName: "backup", available: "10", used: "90", percentage: 90 },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("reports zero usage for an empty root dataset", async () => {
|
|
||||||
ws.setResponder((method) =>
|
|
||||||
method === "pool.dataset.query"
|
|
||||||
? [{ id: "tank", used: { parsed: 0 }, available: { parsed: 0 } }]
|
|
||||||
: happyResponder(method),
|
|
||||||
);
|
|
||||||
const integration = createIntegration(credentials);
|
|
||||||
|
|
||||||
const result = await integration.getSystemInfoAsync();
|
|
||||||
|
|
||||||
expect(result.fileSystem[0]?.percentage).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("authenticates with an API key when one is configured", async () => {
|
test("authenticates with an API key when one is configured", async () => {
|
||||||
const integration = createIntegration([{ kind: "apiKey", value: "api-key-123" }]);
|
const integration = createIntegration([{ kind: "apiKey", value: "api-key-123" }]);
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,6 @@ const logger = createLogger({ module: "trueNasIntegration" });
|
|||||||
|
|
||||||
const NETWORK_MULTIPLIER = 100;
|
const NETWORK_MULTIPLIER = 100;
|
||||||
|
|
||||||
const ERROR_MESSAGE_LIMIT = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The error decorator replaces a rejected request with a generic message and moves the actionable
|
|
||||||
* reason to `cause`. TrueNAS puts its middleware traceback in there, several kilobytes of it, so the
|
|
||||||
* reason is truncated before it reaches a log line that repeats on every poll.
|
|
||||||
*/
|
|
||||||
const describeRequestError = (error: unknown) => {
|
|
||||||
const cause = error instanceof Error ? error.cause : undefined;
|
|
||||||
const reason = cause instanceof Error ? cause.message : error instanceof Error ? error.message : String(error);
|
|
||||||
|
|
||||||
return reason.length > ERROR_MESSAGE_LIMIT ? `${reason.slice(0, ERROR_MESSAGE_LIMIT)}…` : reason;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class TrueNasIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
export class TrueNasIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||||
private client?: TrueNasClient;
|
private client?: TrueNasClient;
|
||||||
|
|
||||||
@@ -63,10 +49,9 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
|||||||
memUsedInBytes,
|
memUsedInBytes,
|
||||||
fileSystem: datasets.map((dataset) => ({
|
fileSystem: datasets.map((dataset) => ({
|
||||||
deviceName: dataset.name,
|
deviceName: dataset.name,
|
||||||
available: `${dataset.available}`,
|
available: `${dataset.free}`, // free space left on the pool
|
||||||
used: `${dataset.used}`,
|
used: `${dataset.allocated}`,
|
||||||
percentage:
|
percentage: (dataset.allocated / dataset.size) * 100,
|
||||||
dataset.used + dataset.available === 0 ? 0 : (dataset.used / (dataset.used + dataset.available)) * 100,
|
|
||||||
})),
|
})),
|
||||||
availablePkgUpdates: 0,
|
availablePkgUpdates: 0,
|
||||||
network: {
|
network: {
|
||||||
@@ -122,10 +107,9 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
|||||||
.map((pool) => {
|
.map((pool) => {
|
||||||
if (pool.allocated !== null && pool.size !== null && pool.free !== null) {
|
if (pool.allocated !== null && pool.size !== null && pool.free !== null) {
|
||||||
return {
|
return {
|
||||||
name: pool.name,
|
...pool,
|
||||||
status: pool.status,
|
|
||||||
healthy: pool.healthy,
|
|
||||||
allocated: pool.allocated,
|
allocated: pool.allocated,
|
||||||
|
size: pool.size,
|
||||||
free: pool.free,
|
free: pool.free,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -133,76 +117,12 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
|||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.filter((pool) => pool !== null);
|
.filter((pool) => pool !== null);
|
||||||
|
|
||||||
logger.debug("Retrieved pools", {
|
logger.debug("Retrieved pools", {
|
||||||
url: this.integration.url,
|
url: this.integration.url,
|
||||||
totalCount: result.length,
|
totalCount: result.length,
|
||||||
activeCount: activePools.length,
|
activeCount: activePools.length,
|
||||||
});
|
});
|
||||||
|
return activePools;
|
||||||
if (activePools.length === 0) return [];
|
|
||||||
|
|
||||||
const usableSpaceByPool = await this.getUsableSpaceByPoolAsync(activePools.map((pool) => pool.name));
|
|
||||||
|
|
||||||
return activePools.map((pool) => {
|
|
||||||
const usableSpace = usableSpaceByPool.get(pool.name);
|
|
||||||
if (!usableSpace) {
|
|
||||||
logger.warn("Falling back to physical pool space, the root dataset reported no usable space", {
|
|
||||||
url: this.integration.url,
|
|
||||||
pool: pool.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: pool.name,
|
|
||||||
status: pool.status,
|
|
||||||
healthy: pool.healthy,
|
|
||||||
used: usableSpace?.used ?? pool.allocated,
|
|
||||||
available: usableSpace?.available ?? pool.free,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the usable space of each pool's root ZFS dataset, keyed by pool name. Unlike the physical
|
|
||||||
* `pool.query` values, these account for RAIDZ parity overhead and dataset limits, matching `zfs list`.
|
|
||||||
* See https://github.com/homarr-labs/homarr/issues/6566
|
|
||||||
*
|
|
||||||
* A pool is left out when its usable space is unavailable, so the caller keeps reporting the pool with
|
|
||||||
* its physical values instead of dropping it, which would also hide its health status:
|
|
||||||
* - request rejected → method or `extra.properties` unsupported, or the API key lacks the dataset role
|
|
||||||
* - response rejected → the payload does not match the expected shape
|
|
||||||
* - dataset missing → the pool's root dataset was not returned by the query
|
|
||||||
* - property absent → the dataset does not report `used` / `available`, e.g. while locked
|
|
||||||
*/
|
|
||||||
private async getUsableSpaceByPoolAsync(poolNames: string[]) {
|
|
||||||
const usableSpaceByPool = new Map<string, { used: number; available: number }>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const datasets = await this.requestAsync("pool.dataset.query", [
|
|
||||||
[["id", "in", poolNames]],
|
|
||||||
{
|
|
||||||
extra: {
|
|
||||||
properties: ["used", "available"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const dataset of await poolDatasetSchema.parseAsync(datasets)) {
|
|
||||||
const used = dataset.used?.parsed;
|
|
||||||
const available = dataset.available?.parsed;
|
|
||||||
if (typeof used !== "number" || typeof available !== "number") continue;
|
|
||||||
|
|
||||||
usableSpaceByPool.set(dataset.id, { used, available });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn("Could not retrieve root dataset space, continuing with physical pool space", {
|
|
||||||
url: this.integration.url,
|
|
||||||
error: describeRequestError(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return usableSpaceByPool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -343,19 +263,6 @@ const poolSchema = z.array(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Properties are absent or unparsed for datasets that do not report them, e.g. while locked,
|
|
||||||
// so they are optional here and the pool falls back to its physical values instead of failing
|
|
||||||
// the whole request, which would also drop cpu, memory and network data
|
|
||||||
const poolDatasetPropertySchema = z.object({ parsed: z.number().min(0).nullish() }).nullish();
|
|
||||||
|
|
||||||
const poolDatasetSchema = z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
used: poolDatasetPropertySchema,
|
|
||||||
available: poolDatasetPropertySchema,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const reportingNetDataSchema = z.array(
|
const reportingNetDataSchema = z.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@@ -37,16 +37,9 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
|||||||
const systemInfo = await this.getSystemInformationAsync();
|
const systemInfo = await this.getSystemInformationAsync();
|
||||||
|
|
||||||
const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
|
const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
|
||||||
const cpuCount = systemInfo.metrics.cpu.cpus.length;
|
const cpuCount = systemInfo.info.cpu.cores;
|
||||||
|
|
||||||
// Each entry in cpus[] is one logical CPU (thread). Dividing the summed per-thread
|
|
||||||
// utilization by the thread count (not physical cores) gives the correct average and
|
|
||||||
// avoids over-reporting on SMT systems. Guard against zero to avoid Infinity/NaN.
|
|
||||||
let cpuUtilizationNormalized = 0;
|
|
||||||
if (cpuCount > 0) {
|
|
||||||
cpuUtilizationNormalized = cpuUtilization / cpuCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// We use "info" object instead of the stats since this is the exact amount the kernel sees, which is what Unraid displays.
|
||||||
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
|
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
|
||||||
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
|
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
|
||||||
const uptime = dayjs(systemInfo.info.os.uptime);
|
const uptime = dayjs(systemInfo.info.os.uptime);
|
||||||
@@ -54,7 +47,7 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
|||||||
return {
|
return {
|
||||||
version: systemInfo.info.os.release,
|
version: systemInfo.info.os.release,
|
||||||
cpuModelName: systemInfo.info.cpu.brand,
|
cpuModelName: systemInfo.info.cpu.brand,
|
||||||
cpuUtilization: cpuUtilizationNormalized,
|
cpuUtilization: cpuUtilization / cpuCount,
|
||||||
memUsedInBytes: usedMemory,
|
memUsedInBytes: usedMemory,
|
||||||
memAvailableInBytes: totalMemory - usedMemory,
|
memAvailableInBytes: totalMemory - usedMemory,
|
||||||
uptime: dayjs().diff(uptime, "seconds"),
|
uptime: dayjs().diff(uptime, "seconds"),
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
// @vitest-environment node
|
|
||||||
|
|
||||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
||||||
|
|
||||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
|
||||||
|
|
||||||
import { HomeAssistantIntegration } from "../src";
|
|
||||||
|
|
||||||
vi.mock("@homarr/core/infrastructure/http", () => ({
|
|
||||||
fetchWithTrustedCertificatesAsync: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
|
||||||
const integration = new HomeAssistantIntegration({
|
|
||||||
id: "home-assistant",
|
|
||||||
name: "Home Assistant",
|
|
||||||
url: "http://home-assistant.local:8123",
|
|
||||||
externalUrl: null,
|
|
||||||
decryptedSecrets: [{ kind: "apiKey", value: "secret" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("HomeAssistantIntegration actions", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockFetch.mockReset();
|
|
||||||
mockFetch.mockResolvedValue({ ok: true } as never);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
["toggle", () => integration.triggerToggleAsync("light.desk"), "/api/services/homeassistant/toggle", "light.desk"],
|
|
||||||
[
|
|
||||||
"automation",
|
|
||||||
() => integration.triggerAutomationAsync("automation.turn_on_lights"),
|
|
||||||
"/api/services/automation/trigger",
|
|
||||||
"automation.turn_on_lights",
|
|
||||||
],
|
|
||||||
])("sends the %s service call as JSON", async (_action, trigger, path, entityId) => {
|
|
||||||
await expect(trigger()).resolves.toBe(true);
|
|
||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalledWith(new URL(`http://home-assistant.local:8123${path}`), {
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer secret",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ entity_id: entityId }),
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// @vitest-environment node
|
|
||||||
|
|
||||||
import { createServer } from "node:http";
|
|
||||||
import type { AddressInfo } from "node:net";
|
|
||||||
|
|
||||||
import { getServerStatistics, init } from "@immich/sdk";
|
|
||||||
import { describe, expect, test, vi } from "vitest";
|
|
||||||
|
|
||||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
|
||||||
|
|
||||||
vi.mock("@homarr/core/infrastructure/certificates", () => ({
|
|
||||||
getAllTrustedCertificatesAsync: vi.fn(async () => []),
|
|
||||||
getTrustedCertificateHostnamesAsync: vi.fn(async () => []),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("real @immich/sdk end-to-end through fetchWithTrustedCertificatesAsync", () => {
|
|
||||||
test("x-api-key set via init() reaches the server", async () => {
|
|
||||||
let receivedXApiKey: string | string[] | undefined;
|
|
||||||
const server = createServer((req, res) => {
|
|
||||||
receivedXApiKey = req.headers["x-api-key"];
|
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
|
||||||
res.end(JSON.stringify({ usage: 1000, photos: 5, videos: 2 }));
|
|
||||||
});
|
|
||||||
await new Promise<void>((resolve) => server.listen(0, resolve));
|
|
||||||
const port = (server.address() as AddressInfo).port;
|
|
||||||
|
|
||||||
init({ baseUrl: `http://127.0.0.1:${port}/api`, apiKey: "sdk-configured-key" });
|
|
||||||
try {
|
|
||||||
const stats = await getServerStatistics({
|
|
||||||
fetch: fetchWithTrustedCertificatesAsync as unknown as typeof fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(receivedXApiKey).toBe("sdk-configured-key");
|
|
||||||
expect(stats).toMatchObject({ usage: 1000, photos: 5, videos: 2 });
|
|
||||||
} finally {
|
|
||||||
server.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -32,24 +32,6 @@ const DEFAULT_PASSWORD = "12341234";
|
|||||||
const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password
|
const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password
|
||||||
|
|
||||||
describe("Pi-hole v5 integration", () => {
|
describe("Pi-hole v5 integration", () => {
|
||||||
test("getSummaryAsync should return summary from pi-hole", async () => {
|
|
||||||
// Arrange
|
|
||||||
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
|
||||||
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const result = await piHoleIntegration.getSummaryAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(result.adsBlockedToday).toBe(0);
|
|
||||||
expect(result.adsBlockedTodayPercentage).toBe(0);
|
|
||||||
expect(result.dnsQueriesToday).toBe(0);
|
|
||||||
expect(result.domainsBeingBlocked).toBeGreaterThan(1);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
await piholeContainer.stop();
|
|
||||||
}, 20_000); // Timeout of 20 seconds
|
|
||||||
|
|
||||||
test("testConnectionAsync should be successful", async () => {
|
test("testConnectionAsync should be successful", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
|
|||||||
|
|
||||||
return result.data.state;
|
return result.data.state;
|
||||||
},
|
},
|
||||||
// The widget already polls every 30 seconds and refetches after toggles.
|
|
||||||
cacheTtlMs: 0,
|
|
||||||
});
|
});
|
||||||
+1516
-1516
File diff suppressed because it is too large.
Load diff
@@ -1442,15 +1442,15 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "图标 URL",
|
"label": "图标 URL",
|
||||||
"description": "搜索库,粘贴图片URL,或上传文件。",
|
"description": "搜索库,粘贴图片URL,或上传文件。",
|
||||||
"header": "输入名称或对象来筛选图标… Homarr 将为您搜索 {countIcons} 图标",
|
"header": "输入名称或对象来筛选图标… Homarr 将为您搜索 {countIcons} 图标。",
|
||||||
"headerLoading": "按名称搜索图像",
|
"headerLoading": "输入以搜索图标…",
|
||||||
"clear": "清除图像",
|
"clear": "清除图像",
|
||||||
"uploadImage": "上传图片",
|
"uploadImage": "上传图片",
|
||||||
"results": "图像搜索结果",
|
"results": "图像搜索结果",
|
||||||
"localImages": "你的映像",
|
"localImages": "你的映像",
|
||||||
"svgIcons": "SVG 图标",
|
"svgIcons": "SVG 图标",
|
||||||
"otherImages": "其他映像",
|
"otherImages": "其他映像",
|
||||||
"urlLoading": "正在检查图片…",
|
"urlLoading": "",
|
||||||
"urlReady": "图像已准备好使用",
|
"urlReady": "图像已准备好使用",
|
||||||
"urlError": "无法加载此图像。请检查URL,然后重试。",
|
"urlError": "无法加载此图像。请检查URL,然后重试。",
|
||||||
"noResults": "没有与“{search}”匹配的图标。",
|
"noResults": "没有与“{search}”匹配的图标。",
|
||||||
|
|||||||
+1702
-1702
File diff suppressed because it is too large.
Load diff
@@ -1442,7 +1442,7 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Ikon-URL",
|
"label": "Ikon-URL",
|
||||||
"description": "Søg i biblioteket, indsæt en billed-URL, eller upload en fil.",
|
"description": "Søg i biblioteket, indsæt en billed-URL, eller upload en fil.",
|
||||||
"header": "Søg efter {countIcons} billeder efter navn",
|
"header": "Indtast navn eller objekter for at filtrere for ikoner... Homarr vil søge gennem {countIcons} ikoner for dig.",
|
||||||
"headerLoading": "Søg billeder efter navn",
|
"headerLoading": "Søg billeder efter navn",
|
||||||
"clear": "Ryd billede",
|
"clear": "Ryd billede",
|
||||||
"uploadImage": "Upload billede",
|
"uploadImage": "Upload billede",
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "Album ID",
|
"label": "Album ID",
|
||||||
"description": "Dette er et unikt ID som refererer til dit Immich album. Du kan finde dette id ved at åbne et album og kopiere den unikke streng fra URL, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "Dette er et unikt ID som refererer til dit Immich album. Du kan finde dette id ved at åbne et album og kopiere den unikke streng fra URL, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Interval i sekunder",
|
"label": "Interval i sekunder",
|
||||||
|
|||||||
+1652
-1652
File diff suppressed because it is too large.
Load diff
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Icon URL",
|
"label": "Icon URL",
|
||||||
"description": "Suchen Sie die Bibliothek, fügen Sie eine Bild-URL ein oder laden Sie eine Datei hoch.",
|
"description": "Suchen Sie die Bibliothek, fügen Sie eine Bild-URL ein oder laden Sie eine Datei hoch.",
|
||||||
"header": "Suche {countIcons} Bilder nach Namen",
|
"header": "Geben Sie Namen oder Objekte ein, um nach Symbolen zu filtern... Homarr sucht nach {countIcons} Icons für Sie.",
|
||||||
"headerLoading": "Bilder nach Namen suchen",
|
"headerLoading": "Tippe um die Symbole zu durchsuchen…",
|
||||||
"clear": "Bild löschen",
|
"clear": "Bild löschen",
|
||||||
"uploadImage": "Bild hochladen",
|
"uploadImage": "Bild hochladen",
|
||||||
"results": "Bildsuchergebnisse",
|
"results": "Bildsuchergebnisse",
|
||||||
@@ -2375,7 +2375,7 @@
|
|||||||
},
|
},
|
||||||
"visibleStorageVolumes": {
|
"visibleStorageVolumes": {
|
||||||
"label": "Sichtbare Speichervolumes",
|
"label": "Sichtbare Speichervolumes",
|
||||||
"description": "Nur verfügbar, wenn alle ausgewählten Integrationen Synology betreffen. Nicht ausfüllen, um alle erkannten Speichervolumes anzuzeigen.",
|
"description": "Nur verfügbar, wenn alle Integrationen Synology betreffen. Nicht ausfüllen, um alle erkannten Speichervolumes zu zeigen. ",
|
||||||
"placeholder": "Speichervolumen auswählen"
|
"placeholder": "Speichervolumen auswählen"
|
||||||
},
|
},
|
||||||
"defaultTab": {
|
"defaultTab": {
|
||||||
@@ -2485,7 +2485,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Fehler beim Abrufen der Container Statistiken",
|
"internalServerError": "Fehler beim Abrufen der Container Statistiken",
|
||||||
"layoutSaveTitle": "Tabellenlayout nicht gespeichert",
|
"layoutSaveTitle": "Tabellenlayout nicht gespeichert",
|
||||||
"layoutSaveMessage": "Das Docker-Tabellenlayout konnte nicht gespeichert werden. Versuche die Spalte zu verschieben oder eine andere Spaltengröße zu verwenden."
|
"layoutSaveMessage": "Die Docker-Tabelle konnte nicht gespeichert werden. Versuche die Spalte zu verschieben oder eine andere Spaltengröße zu verwenden. "
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "Keine Docker-Container gefunden"
|
"noContainers": "Keine Docker-Container gefunden"
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "Album ID",
|
"label": "Album ID",
|
||||||
"description": "Dies ist eine eindeutige ID, die sich auf Ihr Immich Album bezieht. Sie können diese ID finden, indem Sie ein Album öffnen und die einzigartige Zeichenkette von der URL kopieren, zum Beispiel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "Dies ist eine eindeutige ID, die sich auf Ihr Immich Album bezieht. Sie können diese ID finden, indem Sie ein Album öffnen und die einzigartige Zeichenkette von der URL kopieren, zum Beispiel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Intervall in Sekunden",
|
"label": "Intervall in Sekunden",
|
||||||
@@ -3163,8 +3163,8 @@
|
|||||||
"noColumns": "Spalten in Elementen auswählen",
|
"noColumns": "Spalten in Elementen auswählen",
|
||||||
"noCommunications": "Kann keine Daten von der Integration laden",
|
"noCommunications": "Kann keine Daten von der Integration laden",
|
||||||
"noItems": "",
|
"noItems": "",
|
||||||
"actionFailed": "Aktion fehlgeschlagen",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "Diese Aktion konnte nicht abgeschlossen werden. Bitte versuche es erneut."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3452,7 +3452,7 @@
|
|||||||
"queued": ""
|
"queued": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"movie": "Film",
|
"movie": "",
|
||||||
"episode": ""
|
"episode": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
@@ -3812,7 +3812,7 @@
|
|||||||
},
|
},
|
||||||
"resource": {
|
"resource": {
|
||||||
"routers": "",
|
"routers": "",
|
||||||
"services": "Dienste",
|
"services": "",
|
||||||
"middlewares": ""
|
"middlewares": ""
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
@@ -4098,13 +4098,13 @@
|
|||||||
"temp": "",
|
"temp": "",
|
||||||
"battery": "",
|
"battery": "",
|
||||||
"services": "Dienste",
|
"services": "Dienste",
|
||||||
"uptime": "Verfügbarkeit",
|
"uptime": "",
|
||||||
"agent": "Agent"
|
"agent": "Agent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"beszelAlerts": {
|
"beszelAlerts": {
|
||||||
"name": "Beszel-Meldungen",
|
"name": "Beszel-Alarme",
|
||||||
"description": "Zeige Beszels Benachrichtigungseinstellungen und -verlauf",
|
"description": "Zeige Beszels Alarmeinstellungen und -verlauf",
|
||||||
"option": {
|
"option": {
|
||||||
"showHistory": {
|
"showHistory": {
|
||||||
"label": "Alarmverlauf anzeigen"
|
"label": "Alarmverlauf anzeigen"
|
||||||
@@ -4173,12 +4173,12 @@
|
|||||||
"chart": {
|
"chart": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"title": "CPU Auslastung",
|
"title": "CPU Auslastung",
|
||||||
"subtitle": "Durchschnittliche Systemweite CPU-Auslastung",
|
"subtitle": "",
|
||||||
"series": "CPU"
|
"series": "CPU"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "Speicherverbrauch",
|
"title": "",
|
||||||
"subtitle": "Genaue Auslastung zum erfassten Zeitpunkt",
|
"subtitle": "",
|
||||||
"series": "Belegt",
|
"series": "Belegt",
|
||||||
"cache": ""
|
"cache": ""
|
||||||
},
|
},
|
||||||
@@ -4189,7 +4189,7 @@
|
|||||||
},
|
},
|
||||||
"diskIO": {
|
"diskIO": {
|
||||||
"title": "Festplatten-E/A",
|
"title": "Festplatten-E/A",
|
||||||
"subtitle": "Durchsatz des Root-Dateisystems",
|
"subtitle": "",
|
||||||
"read": "Lesen",
|
"read": "Lesen",
|
||||||
"write": "Schreiben"
|
"write": "Schreiben"
|
||||||
},
|
},
|
||||||
@@ -6096,7 +6096,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"available": "Verfügbar",
|
"available": "",
|
||||||
"partiallyAvailable": "",
|
"partiallyAvailable": "",
|
||||||
"processing": "",
|
"processing": "",
|
||||||
"requested": "",
|
"requested": "",
|
||||||
|
|||||||
+1777
-1777
File diff suppressed because it is too large.
Load diff
@@ -91,7 +91,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"subtitle": "Configure server settings."
|
"subtitle": "Configure server settings"
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"title": "Integrations",
|
"title": "Integrations",
|
||||||
@@ -351,7 +351,7 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"label": "Permanently delete user",
|
"label": "Permanently delete user",
|
||||||
"description": "This deletes this user, including their preferences. This will not delete any boards. The user will not be notified.",
|
"description": "This deletes this user, including their preferences. This will not delete any boards. The user will not be notified.",
|
||||||
"confirm": "Are you sure that you want to delete the user {username}, as well as their preferences?"
|
"confirm": "Are you sure that you want to delete the user {username}, as well as their preferences"
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"label": "Select a user",
|
"label": "Select a user",
|
||||||
@@ -517,7 +517,7 @@
|
|||||||
"transfer": {
|
"transfer": {
|
||||||
"label": "Transfer group ownership",
|
"label": "Transfer group ownership",
|
||||||
"description": "Transfer ownership of this group to another user.",
|
"description": "Transfer ownership of this group to another user.",
|
||||||
"confirm": "Are you want to transfer the ownership of the group, {name} to {username}?",
|
"confirm": "Are you want to transfer the ownership of the group, {name} to {username}",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": "Successfully transferred group {group} to {user}"
|
"message": "Successfully transferred group {group} to {user}"
|
||||||
@@ -536,7 +536,7 @@
|
|||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"label": "Delete group",
|
"label": "Delete group",
|
||||||
"description": "Once you delete this group, you can not revert the change. Please be careful.",
|
"description": "Once you delete this group, you can not revert the change. Please be careful",
|
||||||
"confirm": "Are you sure you want to delete the group, {name}?",
|
"confirm": "Are you sure you want to delete the group, {name}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -1345,8 +1345,8 @@
|
|||||||
"description": "Cloud-native reverse proxy and load balancer"
|
"description": "Cloud-native reverse proxy and load balancer"
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "",
|
||||||
"description": "Monitor which of your Docker containers have image updates available"
|
"description": ""
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "Paperless-ngx",
|
||||||
@@ -1441,18 +1441,18 @@
|
|||||||
"here": "here",
|
"here": "here",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Icon URL",
|
"label": "Icon URL",
|
||||||
"description": "Search the library, paste an image URL, or upload a file.",
|
"description": "",
|
||||||
"header": "Please enter a name or object to filter for the icon. Homarr will search throughout {countIcons} different icons to find the right one",
|
"header": "Please enter a name or object to filter for the icon. Homarr will search throughout {countIcons} different icons to find the right one.",
|
||||||
"headerLoading": "Type to search icons",
|
"headerLoading": "Type to search icons…",
|
||||||
"clear": "Clear image",
|
"clear": "",
|
||||||
"uploadImage": "Upload image",
|
"uploadImage": "",
|
||||||
"results": "Image search results",
|
"results": "",
|
||||||
"localImages": "Your images",
|
"localImages": "",
|
||||||
"svgIcons": "SVG icons",
|
"svgIcons": "",
|
||||||
"otherImages": "Other images",
|
"otherImages": "",
|
||||||
"urlLoading": "Checking image…",
|
"urlLoading": "",
|
||||||
"urlReady": "Image is ready to use",
|
"urlReady": "",
|
||||||
"urlError": "This image could not be loaded. Check the URL and try again.",
|
"urlError": "",
|
||||||
"noResults": "No icons match \"{search}\".",
|
"noResults": "No icons match \"{search}\".",
|
||||||
"error": "Couldn't load icons."
|
"error": "Couldn't load icons."
|
||||||
},
|
},
|
||||||
@@ -1512,7 +1512,7 @@
|
|||||||
"login": "Log-in",
|
"login": "Log-in",
|
||||||
"homeBoard": "Your home board",
|
"homeBoard": "Your home board",
|
||||||
"loggedOut": "Logged out",
|
"loggedOut": "Logged out",
|
||||||
"updateAvailable": "{countUpdates} updates available: {tag}"
|
"updateAvailable": "New update available: {tag}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dangerZone": "Danger zone",
|
"dangerZone": "Danger zone",
|
||||||
@@ -1808,19 +1808,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "",
|
||||||
"description": "Show how many watched containers have updates available.",
|
"description": "",
|
||||||
"title": "What's Up Docker",
|
"title": "",
|
||||||
"monitored": "Monitored",
|
"monitored": "",
|
||||||
"updatesAvailable": "{count, plural, one {# update available} other {# updates available}}",
|
"updatesAvailable": "",
|
||||||
"updateAvailable": "Update available",
|
"updateAvailable": "",
|
||||||
"openReleaseNotes": "Open release notes",
|
"openReleaseNotes": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "Show title"
|
"label": "Show title"
|
||||||
},
|
},
|
||||||
"showRing": {
|
"showRing": {
|
||||||
"label": "Show update ring"
|
"label": ""
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "Layout",
|
"label": "Layout",
|
||||||
@@ -1834,8 +1834,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showUpdateList": {
|
"showUpdateList": {
|
||||||
"label": "Show container update list",
|
"label": "",
|
||||||
"description": "Lists each monitored container that has an image update available"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1931,7 +1931,7 @@
|
|||||||
"label": "Display seconds"
|
"label": "Display seconds"
|
||||||
},
|
},
|
||||||
"useCustomTimezone": {
|
"useCustomTimezone": {
|
||||||
"label": "Use a fixed timezone"
|
"label": " "
|
||||||
},
|
},
|
||||||
"timezone": {
|
"timezone": {
|
||||||
"label": "Time zone",
|
"label": "Time zone",
|
||||||
@@ -2476,19 +2476,19 @@
|
|||||||
"label": "Invert sorting"
|
"label": "Invert sorting"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Column order"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Column widths"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Failed to fetch containers stats",
|
"internalServerError": "Failed to fetch containers stats",
|
||||||
"layoutSaveTitle": "Table layout not saved",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "The Docker table layout could not be saved. Try moving or resizing the column again."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "No containers found"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
@@ -2784,17 +2784,17 @@
|
|||||||
"description": "Disabling this will not work for Plex"
|
"description": "Disabling this will not work for Plex"
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "Show bitrate",
|
"label": "",
|
||||||
"description": "Show the current playback bitrate next to the location"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "Show location",
|
"label": "",
|
||||||
"description": "Show whether the stream is on your LAN or WAN"
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "{count, plural, one {# active stream} other {# active streams}}",
|
"streams": "",
|
||||||
"totalBitrate": "Total: {bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "Currently playing",
|
"currentlyPlaying": "Currently playing",
|
||||||
@@ -2803,9 +2803,9 @@
|
|||||||
"id": "Id",
|
"id": "Id",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"directPlay": "Direct play",
|
"directPlay": "Direct play",
|
||||||
"directStream": "Direct stream",
|
"directStream": "",
|
||||||
"transcodeVideo": "Transcoding video",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "Transcoding audio",
|
"transcodeAudio": "",
|
||||||
"transcoding": "Transcoding",
|
"transcoding": "Transcoding",
|
||||||
"paused": "Paused",
|
"paused": "Paused",
|
||||||
"remaining": "{minutes} min left",
|
"remaining": "{minutes} min left",
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "Album ID",
|
"label": "Album ID",
|
||||||
"description": "This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Interval in seconds",
|
"label": "Interval in seconds",
|
||||||
@@ -3153,18 +3153,18 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Column order"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Column widths"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "",
|
"noColumns": "",
|
||||||
"noCommunications": "",
|
"noCommunications": "",
|
||||||
"noItems": "No downloads found",
|
"noItems": "",
|
||||||
"actionFailed": "Action failed",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "The action could not be completed. Please try again."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3179,7 +3179,7 @@
|
|||||||
"detailsTitle": ""
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"downSpeed": {
|
"downSpeed": {
|
||||||
"columnTitle": "Down",
|
"columnTitle": "",
|
||||||
"detailsTitle": ""
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
@@ -3188,16 +3188,16 @@
|
|||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"columnTitle": "",
|
"columnTitle": "",
|
||||||
"detailsTitle": "Item ID"
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"columnTitle": ""
|
"columnTitle": ""
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"columnTitle": "Job name"
|
"columnTitle": ""
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"columnTitle": "Progress",
|
"columnTitle": "",
|
||||||
"detailsTitle": ""
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"ratio": {
|
"ratio": {
|
||||||
@@ -3217,13 +3217,13 @@
|
|||||||
"detailsTitle": ""
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"columnTitle": "State",
|
"columnTitle": "",
|
||||||
"detailsTitle": ""
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"columnTitle": "",
|
"columnTitle": "",
|
||||||
"detailsTitle": "",
|
"detailsTitle": "",
|
||||||
"eta": "ETA"
|
"eta": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"columnTitle": "",
|
"columnTitle": "",
|
||||||
@@ -3269,21 +3269,21 @@
|
|||||||
},
|
},
|
||||||
"globalRatio": "",
|
"globalRatio": "",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"copyName": "Copy name",
|
"copyName": "",
|
||||||
"copyId": "Copy ID",
|
"copyId": "",
|
||||||
"clearFilters": "Clear filters"
|
"clearFilters": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"filter": "Filter downloads"
|
"filter": ""
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"show": "Show global stats",
|
"show": "",
|
||||||
"hide": "Hide global stats",
|
"hide": "",
|
||||||
"overallProgress": "Overall queue progress",
|
"overallProgress": "",
|
||||||
"totalSize": "Total queue size",
|
"totalSize": "",
|
||||||
"clients": "{count, plural, one {# client} other {# clients}}"
|
"clients": ""
|
||||||
},
|
},
|
||||||
"disconnected": "(disconnected)"
|
"disconnected": ""
|
||||||
},
|
},
|
||||||
"mediaReleases": {
|
"mediaReleases": {
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -3315,7 +3315,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mediaRequests-requestList": {
|
"mediaRequests-requestList": {
|
||||||
"name": "Media Requests List",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"linksTargetNewTab": {
|
"linksTargetNewTab": {
|
||||||
@@ -3337,15 +3337,15 @@
|
|||||||
"unknown": "",
|
"unknown": "",
|
||||||
"pending": "",
|
"pending": "",
|
||||||
"processing": "",
|
"processing": "",
|
||||||
"requested": "Requested",
|
"requested": "",
|
||||||
"partiallyAvailable": "",
|
"partiallyAvailable": "",
|
||||||
"available": "",
|
"available": "",
|
||||||
"blacklisted": "Blacklisted",
|
"blacklisted": "",
|
||||||
"deleted": ""
|
"deleted": ""
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"pending": "",
|
"pending": "",
|
||||||
"approved": "Approved",
|
"approved": "",
|
||||||
"declined": "",
|
"declined": "",
|
||||||
"failed": "",
|
"failed": "",
|
||||||
"completed": ""
|
"completed": ""
|
||||||
@@ -3358,7 +3358,7 @@
|
|||||||
"option": {},
|
"option": {},
|
||||||
"titles": {
|
"titles": {
|
||||||
"stats": {
|
"stats": {
|
||||||
"main": "Media Stats",
|
"main": "",
|
||||||
"approved": "",
|
"approved": "",
|
||||||
"pending": "",
|
"pending": "",
|
||||||
"processing": "",
|
"processing": "",
|
||||||
@@ -3402,15 +3402,15 @@
|
|||||||
"panel": {
|
"panel": {
|
||||||
"statistics": {
|
"statistics": {
|
||||||
"empty": "",
|
"empty": "",
|
||||||
"transcodes": "Transcodes",
|
"transcodes": "",
|
||||||
"transcodesCount": "",
|
"transcodesCount": "",
|
||||||
"healthChecksCount": "",
|
"healthChecksCount": "",
|
||||||
"filesCount": "",
|
"filesCount": "",
|
||||||
"savedSpace": "",
|
"savedSpace": "",
|
||||||
"healthChecks": "Health checks",
|
"healthChecks": "",
|
||||||
"videoCodecs": "Codecs",
|
"videoCodecs": "",
|
||||||
"videoContainers": "Containers",
|
"videoContainers": "",
|
||||||
"videoResolutions": "Resolutions"
|
"videoResolutions": ""
|
||||||
},
|
},
|
||||||
"workers": {
|
"workers": {
|
||||||
"empty": "",
|
"empty": "",
|
||||||
@@ -3620,7 +3620,7 @@
|
|||||||
"option": {},
|
"option": {},
|
||||||
"card": {
|
"card": {
|
||||||
"vpn": {
|
"vpn": {
|
||||||
"countConnected": "{count} connected"
|
"countConnected": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -3637,14 +3637,14 @@
|
|||||||
"name": ""
|
"name": ""
|
||||||
},
|
},
|
||||||
"wifi": {
|
"wifi": {
|
||||||
"name": "Wi-Fi"
|
"name": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"guests": {
|
"guests": {
|
||||||
"label": "Guests"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
@@ -3890,7 +3890,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"cpu": "",
|
"cpu": "",
|
||||||
"memory": "",
|
"memory": "",
|
||||||
"gpu": "GPU",
|
"gpu": "",
|
||||||
"network": ""
|
"network": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3908,7 +3908,7 @@
|
|||||||
"cpu": "",
|
"cpu": "",
|
||||||
"memory": "",
|
"memory": "",
|
||||||
"gpu": "",
|
"gpu": "",
|
||||||
"network": "NET",
|
"network": "",
|
||||||
"up": "",
|
"up": "",
|
||||||
"down": ""
|
"down": ""
|
||||||
}
|
}
|
||||||
@@ -4014,17 +4014,17 @@
|
|||||||
"label": "Show agent version"
|
"label": "Show agent version"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Column order"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Column widths"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noRecords": "No systems found",
|
"noRecords": "No systems found",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Unable to connect to Beszel. Check your integration settings.",
|
"internalServerError": "Unable to connect to Beszel. Check your integration settings.",
|
||||||
"layoutSaveTitle": "Unable to save table layout",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Your column order or widths could not be saved. Please try again."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"column": {
|
"column": {
|
||||||
"system": "System",
|
"system": "System",
|
||||||
@@ -4956,7 +4956,7 @@
|
|||||||
"title": "",
|
"title": "",
|
||||||
"status": {
|
"status": {
|
||||||
"idle": "",
|
"idle": "",
|
||||||
"running": "Running",
|
"running": "",
|
||||||
"error": "",
|
"error": "",
|
||||||
"disabled": ""
|
"disabled": ""
|
||||||
},
|
},
|
||||||
@@ -5422,7 +5422,7 @@
|
|||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "",
|
"label": "",
|
||||||
"confirm": "Click again to remove permanently",
|
"confirm": "",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "",
|
||||||
@@ -5858,7 +5858,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search for anything",
|
"placeholder": "",
|
||||||
"nothingFound": "",
|
"nothingFound": "",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch": ""
|
"fetch": ""
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
+1978
-1978
File diff suppressed because it is too large.
Load diff
@@ -91,7 +91,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Asetukset",
|
"title": "Asetukset",
|
||||||
"subtitle": "Määritä palvelimen asetukset."
|
"subtitle": "Määritä palvelinasetukset."
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"title": "Integraatiot",
|
"title": "Integraatiot",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"message": "Jotain meni pieleen kojelautaasi määritettäessä. Yritä uudelleen."
|
"message": "Jotain meni pieleen kojelautaasi määritettäessä. Yritä uudelleen."
|
||||||
},
|
},
|
||||||
"helpers": {
|
"helpers": {
|
||||||
"tooltip": "URL-avustajat",
|
"tooltip": "URL-apurit",
|
||||||
"description": "Nämä apurit pyrkivät arvaamaan palveluidesi URL-osoitteet. Jos tämä ei toimi, kirjoita osoite manuaalisesti."
|
"description": "Nämä apurit pyrkivät arvaamaan palveluidesi URL-osoitteet. Jos tämä ei toimi, kirjoita osoite manuaalisesti."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"summary": "Homarr havaitse {apps} sovellusta, joista {integrations} löytyy sisäänrakennettu integraatio",
|
"summary": "Homarr havaitse {apps} sovellusta, joista {integrations} löytyy sisäänrakennettu integraatio",
|
||||||
"appBadge": "Sovellus",
|
"appBadge": "Sovellus",
|
||||||
"appSectionLabel": "Ohjauspaneeliin lisättävät sovellukset ({count} valittu)",
|
"appSectionLabel": "Ohjauspaneeliin lisättävät sovellukset ({count} valittu)",
|
||||||
"scanning": "Tutkitaan Dockeria...",
|
"scanning": "Dockeria tutkitaan...",
|
||||||
"scanningDescription": "Tunnistetaan järjestelmässäsi suoritettavia kontteja"
|
"scanningDescription": "Tunnistetaan järjestelmässäsi suoritettavia kontteja"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
},
|
},
|
||||||
"invite": {
|
"invite": {
|
||||||
"title": "Liity Homarriin",
|
"title": "Liity Homarriin",
|
||||||
"subtitle": "Tervetuloa Homarriin! Luo käyttäjätunnuksesi",
|
"subtitle": "Tervetuloa Homarriin! Luo itsellesi käyttäjätunnus",
|
||||||
"description": "Sinut kutsui {username}"
|
"description": "Sinut kutsui {username}"
|
||||||
},
|
},
|
||||||
"init": {
|
"init": {
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"login": {
|
"login": {
|
||||||
"label": "Kirjaudu",
|
"label": "Kirjaudu",
|
||||||
"labelWith": "Kirjaudu palvelulla: {provider}",
|
"labelWith": "Kirjaudu {{provider}}-tunnuksella",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Kirjautuminen onnistui",
|
"title": "Kirjautuminen onnistui",
|
||||||
@@ -302,7 +302,7 @@
|
|||||||
"changeDdgBangs": {
|
"changeDdgBangs": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": "DuckDuckGon Bangs-komentojen tila vaihdettiin"
|
"message": "DuckDuckGo Bangs -komentojen tila vaihdettiin"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": "DuckDuckGo Bangs -komentojen tilaa ei voitu vaihtaa"
|
"message": "DuckDuckGo Bangs -komentojen tilaa ei voitu vaihtaa"
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
},
|
},
|
||||||
"use-all": {
|
"use-all": {
|
||||||
"label": "Sovellusten käyttö",
|
"label": "Sovellusten käyttö",
|
||||||
"description": "Sallii jäsenten lisätä kaikkia sovelluksia paneeleihinsa"
|
"description": "Sallii jäsenten lisätä kaikkia sovelluksia paneeleihinsa."
|
||||||
},
|
},
|
||||||
"modify-all": {
|
"modify-all": {
|
||||||
"label": "Sovellusten muokkaus",
|
"label": "Sovellusten muokkaus",
|
||||||
@@ -414,19 +414,19 @@
|
|||||||
"item": {
|
"item": {
|
||||||
"create": {
|
"create": {
|
||||||
"label": "Paneelien luonti",
|
"label": "Paneelien luonti",
|
||||||
"description": "Sallii jäsenten luoda paneeleita"
|
"description": "Sallii jäsenten luoda paneeleita."
|
||||||
},
|
},
|
||||||
"view-all": {
|
"view-all": {
|
||||||
"label": "Paneelien tarkastelu",
|
"label": "Paneelien tarkastelu",
|
||||||
"description": "Sallii jäsenten tarkastella kaikkia paneeleita"
|
"description": "Sallii jäsenten tarkastella kaikkia paneeleita."
|
||||||
},
|
},
|
||||||
"modify-all": {
|
"modify-all": {
|
||||||
"label": "Paneelien muokkaus",
|
"label": "Paneelien muokkaus",
|
||||||
"description": "Sallii jäsenten muokata kaikkia paneeleita (pois lukien käyttöoikeudet ja vaaravyöhyke)"
|
"description": "Sallii jäsenten muokata kaikkia paneeleita (pois lukien käyttöoikeudet ja vaaravyöhyke)."
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "Paneelien hallinta",
|
"label": "Paneelien hallinta",
|
||||||
"description": "Sallii jäsenten tarkastella, muokata ja poistaa kaikkia paneeleita (mukaan lukien käyttöoikeudet ja vaaravyöhyke)"
|
"description": "Sallii jäsenten tarkastella, muokata ja poistaa kaikkia paneeleita (mukaan lukien käyttöoikeudet ja vaaravyöhyke)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -996,7 +996,7 @@
|
|||||||
"grid": {
|
"grid": {
|
||||||
"widgetCount": "{count} widgettiä",
|
"widgetCount": "{count} widgettiä",
|
||||||
"noWidgets": "Widgettejä ei ole",
|
"noWidgets": "Widgettejä ei ole",
|
||||||
"detected": "Havaittu Dockerilla"
|
"detected": "Havaittu Dockerin kautta"
|
||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
"dnsHole": "DNS",
|
"dnsHole": "DNS",
|
||||||
@@ -1228,7 +1228,7 @@
|
|||||||
},
|
},
|
||||||
"tooManyRequests": {
|
"tooManyRequests": {
|
||||||
"title": "",
|
"title": "",
|
||||||
"message": "Pyyntöjä oli liikaa. Pyyntöjäsi todennäköisesti rajoitetaan tai sinut estetään kohdejärjestelmässä"
|
"message": "Pyyntöjä oli liikaa. Pyyntöjäsi todennäköisesti rajoitetaan tai sinut estetään kohdejärjestelmässä."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1254,8 +1254,8 @@
|
|||||||
"newLabel": "Uusi käyttäjätunnus"
|
"newLabel": "Uusi käyttäjätunnus"
|
||||||
},
|
},
|
||||||
"apiKey": {
|
"apiKey": {
|
||||||
"label": "API-avain",
|
"label": "Rajapinnan avain",
|
||||||
"newLabel": "Uusi API-avain"
|
"newLabel": "Uusi rajapinnan avain"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Salasana",
|
"label": "Salasana",
|
||||||
@@ -1442,15 +1442,15 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Kuvakkeen URL-osoite",
|
"label": "Kuvakkeen URL-osoite",
|
||||||
"description": "Etsi kirjastosta, liitä kuvan URL-osoite tai lataa tiedosto.",
|
"description": "Etsi kirjastosta, liitä kuvan URL-osoite tai lataa tiedosto.",
|
||||||
"header": "Etsi {countIcons} kuvaa nimen mukaan",
|
"header": "Suodata kuvakkeita syöttämällä nimi tai objekti... Homarr etsii {countIcons} kuvakkeesta.",
|
||||||
"headerLoading": "Hae kuvia nimen mukaan",
|
"headerLoading": "Kirjoita hakeaksesi kuvakkeita…",
|
||||||
"clear": "Tyhjennä kuva",
|
"clear": "Tyhjennä kuva",
|
||||||
"uploadImage": "Lataa kuva",
|
"uploadImage": "Lataa kuva",
|
||||||
"results": "Haun tulokset",
|
"results": "Haun tulokset",
|
||||||
"localImages": "Omat kuvat",
|
"localImages": "Omat kuvat",
|
||||||
"svgIcons": "SVG kuvakkeet",
|
"svgIcons": "SVG kuvakkeet",
|
||||||
"otherImages": "Muut kuvat",
|
"otherImages": "Muut kuvat",
|
||||||
"urlLoading": "Tarkistetaan kuvaa…",
|
"urlLoading": "",
|
||||||
"urlReady": "Kuva on valmis käytettäväksi",
|
"urlReady": "Kuva on valmis käytettäväksi",
|
||||||
"urlError": "Kuvaa ei voitu ladata. Tarkista URL-osoite ja yritä uudelleen.",
|
"urlError": "Kuvaa ei voitu ladata. Tarkista URL-osoite ja yritä uudelleen.",
|
||||||
"noResults": "Ei kuvakkeita haulla \"{search}\".",
|
"noResults": "Ei kuvakkeita haulla \"{search}\".",
|
||||||
@@ -2285,7 +2285,7 @@
|
|||||||
},
|
},
|
||||||
"showCurrentWindSpeed": {
|
"showCurrentWindSpeed": {
|
||||||
"label": "Näytä nykyinen tuulen nopeus",
|
"label": "Näytä nykyinen tuulen nopeus",
|
||||||
"description": "Vain nykyiselle säälle"
|
"description": "Vain nykyiselle säälle."
|
||||||
},
|
},
|
||||||
"useImperialSpeed": {
|
"useImperialSpeed": {
|
||||||
"label": "Käytä tuulen nopeudelle mph-yksikköä"
|
"label": "Käytä tuulen nopeudelle mph-yksikköä"
|
||||||
@@ -2476,19 +2476,19 @@
|
|||||||
"label": "Käänteinen järjestys"
|
"label": "Käänteinen järjestys"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Sarakkeiden järjestys"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Sarakkeiden leveydet"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "",
|
"internalServerError": "",
|
||||||
"layoutSaveTitle": "Taulukon asettelua ei tallennettu",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Docker-taulukon asettelua ei voitu tallentaa. Yritä siirtää tai muuttaa sarakkeen kokoa uudelleen."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "Kontteja ei löytynyt"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
@@ -2784,17 +2784,17 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "Näytä bittinopeus",
|
"label": "",
|
||||||
"description": "Näytä nykyinen toiston bittinopeus sijainnin vieressä"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "Näytä sijainti",
|
"label": "",
|
||||||
"description": "Näytä, onko suoratoisto LAN- vai WAN-verkossa."
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "{count, plural, one {# aktiivinen suoratoisto} other {# aktiivista suoratoistoa}}",
|
"streams": "",
|
||||||
"totalBitrate": "Yhteensä: {bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "",
|
"currentlyPlaying": "",
|
||||||
@@ -2803,9 +2803,9 @@
|
|||||||
"id": "",
|
"id": "",
|
||||||
"status": "Tila",
|
"status": "Tila",
|
||||||
"directPlay": "Muuntamaton toisto",
|
"directPlay": "Muuntamaton toisto",
|
||||||
"directStream": "Suora suoratoisto",
|
"directStream": "",
|
||||||
"transcodeVideo": "Videon transkoodaus",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "Äänen transkoodaus",
|
"transcodeAudio": "",
|
||||||
"transcoding": "Transkoodaus",
|
"transcoding": "Transkoodaus",
|
||||||
"paused": "Keskeytetty",
|
"paused": "Keskeytetty",
|
||||||
"remaining": "{minutes} min jäljellä",
|
"remaining": "{minutes} min jäljellä",
|
||||||
@@ -3150,21 +3150,21 @@
|
|||||||
},
|
},
|
||||||
"limitPerIntegration": {
|
"limitPerIntegration": {
|
||||||
"label": "Integraation elementtien määrärajoitus",
|
"label": "Integraation elementtien määrärajoitus",
|
||||||
"description": "Tämä rajoittaa näytettävien elementtien määrää integraatiokohtaisesti, ei kokonaisvaltaisesti"
|
"description": "Tämä rajoittaa näytettävien elementtien määrää integraatiokohtaisesti, ei kokonaisvaltaisesti."
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Sarakkeiden järjestys"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Sarakkeiden leveydet"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "Valitse kohteiden sarakkeet",
|
"noColumns": "Valitse kohteiden sarakkeet",
|
||||||
"noCommunications": "Integraatiolta ei voida noutaa tietoja",
|
"noCommunications": "Integraatiolta ei voida noutaa tietoja",
|
||||||
"noItems": "Latauksia ei löytynyt",
|
"noItems": "",
|
||||||
"actionFailed": "Toiminto epäonnistui",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "Toimintoa ei voitu suorittaa. Yritä uudelleen."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3188,7 +3188,7 @@
|
|||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"columnTitle": "Tunniste",
|
"columnTitle": "Tunniste",
|
||||||
"detailsTitle": "Kohteen ID"
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"columnTitle": "Integraatio"
|
"columnTitle": "Integraatio"
|
||||||
@@ -3223,7 +3223,7 @@
|
|||||||
"time": {
|
"time": {
|
||||||
"columnTitle": "Valmistumisaika",
|
"columnTitle": "Valmistumisaika",
|
||||||
"detailsTitle": "",
|
"detailsTitle": "",
|
||||||
"eta": "Arvioitu aika"
|
"eta": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"columnTitle": "Tyyppi",
|
"columnTitle": "Tyyppi",
|
||||||
@@ -3269,21 +3269,21 @@
|
|||||||
},
|
},
|
||||||
"globalRatio": "Kokonaissuhde",
|
"globalRatio": "Kokonaissuhde",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"copyName": "Kopioi nimi",
|
"copyName": "",
|
||||||
"copyId": "Kopioi ID",
|
"copyId": "",
|
||||||
"clearFilters": "Tyhjennä suodattimet"
|
"clearFilters": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"filter": "Suodata latauksia"
|
"filter": ""
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"show": "Näytä globaalit tilastot",
|
"show": "",
|
||||||
"hide": "Piilota globaalit tilastot",
|
"hide": "",
|
||||||
"overallProgress": "Jonon kokonaisedistyminen",
|
"overallProgress": "",
|
||||||
"totalSize": "Jonon kokonaiskoko",
|
"totalSize": "",
|
||||||
"clients": "{count, plural, one {# asiakas} other {# asiakasta}}"
|
"clients": ""
|
||||||
},
|
},
|
||||||
"disconnected": "(ei yhteydessä)"
|
"disconnected": ""
|
||||||
},
|
},
|
||||||
"mediaReleases": {
|
"mediaReleases": {
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -3745,7 +3745,7 @@
|
|||||||
"unhealthy": "",
|
"unhealthy": "",
|
||||||
"ok": "",
|
"ok": "",
|
||||||
"fail": "",
|
"fail": "",
|
||||||
"noSectionsEnabled": "Ota ainakin yksi osio käyttöön widgetin asetuksista.",
|
"noSectionsEnabled": "Aktivoi ainakin yksi osio widgetin asetuksista.",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": ""
|
"internalServerError": ""
|
||||||
}
|
}
|
||||||
@@ -4014,17 +4014,17 @@
|
|||||||
"label": "Näytä agentin versio"
|
"label": "Näytä agentin versio"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Sarakkeiden järjestys"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Sarakkeiden leveydet"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noRecords": "Järjestelmiä ei löytynyt",
|
"noRecords": "Järjestelmiä ei löytynyt",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Beszel-yhteyden muodostaminen epäonnistui. Tarkista integraatioasetuksesi.",
|
"internalServerError": "Beszel-yhteyden muodostaminen epäonnistui. Tarkista integraatioasetuksesi.",
|
||||||
"layoutSaveTitle": "Taulukon asettelua ei voitu tallentaa",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Sarakkeiden järjestystä tai leveyksiä ei voitu tallentaa. Yritä uudelleen."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"column": {
|
"column": {
|
||||||
"system": "Järjestelmä",
|
"system": "Järjestelmä",
|
||||||
@@ -4423,7 +4423,7 @@
|
|||||||
},
|
},
|
||||||
"itemRadius": {
|
"itemRadius": {
|
||||||
"label": "Kohteen säde",
|
"label": "Kohteen säde",
|
||||||
"description": "Muuttaa paneelin korttien pyöreyttä",
|
"description": "Muuttaa paneelin korttien kulmien pyöristystä.",
|
||||||
"option": {
|
"option": {
|
||||||
"xs": "Erittäin pieni",
|
"xs": "Erittäin pieni",
|
||||||
"sm": "Pieni",
|
"sm": "Pieni",
|
||||||
@@ -4968,7 +4968,7 @@
|
|||||||
"label": "Analytiikka"
|
"label": "Analytiikka"
|
||||||
},
|
},
|
||||||
"ping": {
|
"ping": {
|
||||||
"label": "Pingit"
|
"label": "Ping-koestukset"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
@@ -5333,7 +5333,7 @@
|
|||||||
"refresh": {
|
"refresh": {
|
||||||
"lastUpdated": "Viimeisin päivitys {when}"
|
"lastUpdated": "Viimeisin päivitys {when}"
|
||||||
},
|
},
|
||||||
"search": "Suodata {count} konttia",
|
"search": "Suodata {count} konttia...",
|
||||||
"selected": "{selectCount}/{totalCount} kontista valittu",
|
"selected": "{selectCount}/{totalCount} kontista valittu",
|
||||||
"footer": "Yhteensä {count} konttia",
|
"footer": "Yhteensä {count} konttia",
|
||||||
"totalCpu": "CPU yhteensä: {cpu}%",
|
"totalCpu": "CPU yhteensä: {cpu}%",
|
||||||
@@ -5422,7 +5422,7 @@
|
|||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "Poista",
|
"label": "Poista",
|
||||||
"confirm": "Napsauta uudelleen poistaaksesi pysyvästi",
|
"confirm": "",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "",
|
||||||
@@ -5964,14 +5964,14 @@
|
|||||||
"title": "Asetukset",
|
"title": "Asetukset",
|
||||||
"children": {
|
"children": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": "Valitse muutettava asetus",
|
"title": "Valitse muutettava asetus.",
|
||||||
"backHint": "Palaa takaisin askelpalauttimella tai painamalla Takaisin-painiketta",
|
"backHint": "Palaa takaisin askelpalauttimella tai painamalla Takaisin-painiketta",
|
||||||
"backAction": "Palaa takaisin"
|
"backAction": "Palaa takaisin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"error": {
|
"error": {
|
||||||
"message": "Asetusta ei voitu päivittää"
|
"message": "Asetusta ei voitu päivittää."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
@@ -6055,10 +6055,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pingIconsEnabled": {
|
"pingIconsEnabled": {
|
||||||
"label": "Käytä ping-ikoneita",
|
"label": "Käytä ping-kuvakkeita",
|
||||||
"children": {
|
"children": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": "Käytä ping-ikoneita"
|
"title": "Käytä ping-kuvakkeita"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6237,7 +6237,7 @@
|
|||||||
"label": "Hallinnoi Dockeria"
|
"label": "Hallinnoi Dockeria"
|
||||||
},
|
},
|
||||||
"manageApi": {
|
"manageApi": {
|
||||||
"label": "Swagger API"
|
"label": "Swagger-rajapinta"
|
||||||
},
|
},
|
||||||
"manageLog": {
|
"manageLog": {
|
||||||
"label": "Tarkastele lokeja"
|
"label": "Tarkastele lokeja"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"start": {
|
"start": {
|
||||||
"title": "Bienvenue sur Homarr",
|
"title": "Bienvenue sur Homarr",
|
||||||
"subtitle": "Commençons par configurer votre instance Homarr.",
|
"subtitle": "Commençons par configurer votre instance Homarr.",
|
||||||
"description": "Pour commencer, veuillez sélectionner comment vous souhaitez configurer votre instance Homarr.",
|
"description": "Pour commencer, veuillez selectionner comment vous souhaitez configurer votre instance Homarr.",
|
||||||
"action": {
|
"action": {
|
||||||
"scratch": "Partir de zéro",
|
"scratch": "Partir de zéro",
|
||||||
"importOldmarr": "Importer depuis Homarr avant la 1.0",
|
"importOldmarr": "Importer depuis Homarr avant la 1.0",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"detected": "Docker a détecté {count} services",
|
"detected": "Docker a détecté {count} services",
|
||||||
"detectedBreakdown": "Intégrations {integrations} · Applications {apps}",
|
"detectedBreakdown": "Intégrations {integrations} · Applications {apps}",
|
||||||
"summary": "Les applications {apps} ont été détectées par Homarr, dont {integrations} ont des intégrations intégrées",
|
"summary": "Les applications {apps} ont été détectées par Homarr, dont {integrations} ont des intégrations intégrées",
|
||||||
"appBadge": "Application",
|
"appBadge": "App",
|
||||||
"appSectionLabel": "Applications à ajouter au tableau de bord ({count} sélectionné)",
|
"appSectionLabel": "Applications à ajouter au tableau de bord ({count} sélectionné)",
|
||||||
"scanning": "Analyse de Docker...",
|
"scanning": "Analyse de Docker...",
|
||||||
"scanningDescription": "Recherche de conteneurs en cours d'exécution sur votre système"
|
"scanningDescription": "Recherche de conteneurs en cours d'exécution sur votre système"
|
||||||
@@ -604,7 +604,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"search": "Trouver une app",
|
"search": "Trouver une application",
|
||||||
"page": {
|
"page": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "Applications",
|
"title": "Applications",
|
||||||
@@ -740,36 +740,36 @@
|
|||||||
"unit": "Unité",
|
"unit": "Unité",
|
||||||
"mappings": "Correspondances",
|
"mappings": "Correspondances",
|
||||||
"tablePath": "Chemin du tableau JSON",
|
"tablePath": "Chemin du tableau JSON",
|
||||||
"tablePathHint": "Chemin vers le tableau dans la réponse, par exemple $.data.items",
|
"tablePathHint": "",
|
||||||
"columns": "Colonnes",
|
"columns": "Colonnes",
|
||||||
"header": "En-tête",
|
"header": "En-tête",
|
||||||
"items": "Objets",
|
"items": "Objets",
|
||||||
"gridColumns": "Nombre de colonnes",
|
"gridColumns": "Nombre de colonnes",
|
||||||
"cardStyle": "Style des cartes",
|
"cardStyle": "Style des cartes",
|
||||||
"bars": "Barres",
|
"bars": "Barres",
|
||||||
"valuePath": "Chemin de la valeur JSON",
|
"valuePath": "",
|
||||||
"maxPath": "Chemin de la valeur max JSON",
|
"maxPath": "",
|
||||||
"showPercentage": "Afficher le pourcentage",
|
"showPercentage": "Afficher le pourcentage",
|
||||||
"barSize": "Taille de la barre",
|
"barSize": "Taille de la barre",
|
||||||
"goodValues": "Bonnes valeurs (séparées par des virgules)",
|
"goodValues": "Bonnes valeurs (séparées par des virgules)",
|
||||||
"dotSize": "Taille des points",
|
"dotSize": "Taille des points",
|
||||||
"valueSize": "Taille",
|
"valueSize": "Taille",
|
||||||
"labelPosition": "Position du libellé",
|
"labelPosition": "Position du libellé",
|
||||||
"kvLayout": "Mise en page",
|
"kvLayout": "",
|
||||||
"striped": "Lignes en couleur alternées",
|
"striped": "",
|
||||||
"compact": "Mode Compact",
|
"compact": "Mode Compact",
|
||||||
"maxHeight": "Hauteur maximale (px)",
|
"maxHeight": "",
|
||||||
"color": "Couleur",
|
"color": "Couleur",
|
||||||
"buttonLabel": "Libellé du bouton",
|
"buttonLabel": "",
|
||||||
"buttonColor": "Couleur du bouton",
|
"buttonColor": "",
|
||||||
"confirmText": "Texte de confirmation",
|
"confirmText": "Texte de confirmation",
|
||||||
"confirmTextHint": "Si défini, une popup de confirmation apparaîtra avant d'exécuter la requête",
|
"confirmTextHint": "",
|
||||||
"successMessage": "Message de réussite",
|
"successMessage": "Message de réussite",
|
||||||
"successMessageHint": "Message affiché après une exécution réussie",
|
"successMessageHint": "",
|
||||||
"enabled": "Activé",
|
"enabled": "Activé",
|
||||||
"template": {
|
"template": {
|
||||||
"label": "Template JSX",
|
"label": "Template JSX",
|
||||||
"description": "Modèle JSX Mantine. Accédez aux champs de réponse de l'API via les liaisons de données (par ex. data.name). Utilisez uniquement les composants autorisés. Interdit : constructor, __proto__, eval, Function, window, document."
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
@@ -779,23 +779,23 @@
|
|||||||
"exampleUnit": "°C",
|
"exampleUnit": "°C",
|
||||||
"iconUrl": "https://...",
|
"iconUrl": "https://...",
|
||||||
"url": "https://api.example.com/api/v1/status",
|
"url": "https://api.example.com/api/v1/status",
|
||||||
"headerName": "X-API-Key",
|
"headerName": "",
|
||||||
"jsonPath": "$.data.value",
|
"jsonPath": "$.data.value",
|
||||||
"tablePath": "$.data.items",
|
"tablePath": "",
|
||||||
"columnJsonPath": "$.name",
|
"columnJsonPath": "",
|
||||||
"valuePath": "$.data.used",
|
"valuePath": "",
|
||||||
"maxPath": "$.data.total",
|
"maxPath": "",
|
||||||
"goodValues": "en ligne, vrai,",
|
"goodValues": "",
|
||||||
"color": "bleu"
|
"color": "bleu"
|
||||||
},
|
},
|
||||||
"cardStyleOption": {
|
"cardStyleOption": {
|
||||||
"filled": "Rempli",
|
"filled": "",
|
||||||
"outline": "Contour",
|
"outline": "",
|
||||||
"subtle": "Discret"
|
"subtle": ""
|
||||||
},
|
},
|
||||||
"layoutOption": {
|
"layoutOption": {
|
||||||
"list": "Liste",
|
"list": "",
|
||||||
"grid": "Grille"
|
"grid": ""
|
||||||
},
|
},
|
||||||
"sizeOption": {
|
"sizeOption": {
|
||||||
"sm": "Petit",
|
"sm": "Petit",
|
||||||
@@ -815,7 +815,7 @@
|
|||||||
"displayTypeDescription": {
|
"displayTypeDescription": {
|
||||||
"singleValue": "Afficher une valeur extraite avec une étiquette et une unité",
|
"singleValue": "Afficher une valeur extraite avec une étiquette et une unité",
|
||||||
"keyValue": "Afficher plusieurs valeurs étiquetées côte à côte",
|
"keyValue": "Afficher plusieurs valeurs étiquetées côte à côte",
|
||||||
"table": "Afficher les lignes de données dans un format de tableau",
|
"table": "",
|
||||||
"statGrid": "",
|
"statGrid": "",
|
||||||
"progressBars": "",
|
"progressBars": "",
|
||||||
"statusIndicator": "Points d'état colorés montrant en ligne/hors ligne ou l'état de santé",
|
"statusIndicator": "Points d'état colorés montrant en ligne/hors ligne ou l'état de santé",
|
||||||
@@ -860,14 +860,14 @@
|
|||||||
"title": "Aperçu",
|
"title": "Aperçu",
|
||||||
"test": "Tester",
|
"test": "Tester",
|
||||||
"hint": "Cliquez sur Test pour prévisualiser la réponse API avec votre configuration actuelle.",
|
"hint": "Cliquez sur Test pour prévisualiser la réponse API avec votre configuration actuelle.",
|
||||||
"rawResponse": "Réponse de JSON brute"
|
"rawResponse": ""
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"created": "Widget \"{name}\" créé avec succès.",
|
"created": "Widget \"{name}\" créé avec succès.",
|
||||||
"updated": "Widget \"{name}\" mis à jour avec succès.",
|
"updated": "Widget \"{name}\" mis à jour avec succès.",
|
||||||
"deleted": "Widget \"{name}\" supprimé.",
|
"deleted": "Widget \"{name}\" supprimé.",
|
||||||
"imported": "Widget importé avec succès.",
|
"imported": "Widget importé avec succès.",
|
||||||
"aiPromptCopied": "Prompt IA a été copiée dans le presse-papiers",
|
"aiPromptCopied": "",
|
||||||
"aiPromptDescription": "",
|
"aiPromptDescription": "",
|
||||||
"aiPromptCopy": "Copier dans le presse-papiers",
|
"aiPromptCopy": "Copier dans le presse-papiers",
|
||||||
"aiPromptNoResponse": "",
|
"aiPromptNoResponse": "",
|
||||||
@@ -1168,15 +1168,15 @@
|
|||||||
},
|
},
|
||||||
"invalidUrl": {
|
"invalidUrl": {
|
||||||
"title": "URL invalide",
|
"title": "URL invalide",
|
||||||
"message": "L'URL est invalide"
|
"message": "L'URL est invalide."
|
||||||
},
|
},
|
||||||
"secretNotDefined": {
|
"secretNotDefined": {
|
||||||
"title": "Identifiants manquants",
|
"title": "Identifiants manquants",
|
||||||
"message": "Tous les identifiants n'ont pas été fournis"
|
"message": "Les informations d'identification n'ont pas toutes été fournies"
|
||||||
},
|
},
|
||||||
"invalidCredentials": {
|
"invalidCredentials": {
|
||||||
"title": "Les identifiants sont invalides",
|
"title": "Informations d'identification incorrectes.",
|
||||||
"message": "Les identifiants sont invalides"
|
"message": "Les informations d'identification ne sont pas valides"
|
||||||
},
|
},
|
||||||
"commonError": {
|
"commonError": {
|
||||||
"title": "Échec de la connexion",
|
"title": "Échec de la connexion",
|
||||||
@@ -1290,12 +1290,12 @@
|
|||||||
"newLabel": "Nouvelle clé API (secrète)"
|
"newLabel": "Nouvelle clé API (secrète)"
|
||||||
},
|
},
|
||||||
"patchmonApiKey": {
|
"patchmonApiKey": {
|
||||||
"label": "Clé API",
|
"label": "",
|
||||||
"newLabel": "Nouvelle clé API"
|
"newLabel": "Nouvelle clé API"
|
||||||
},
|
},
|
||||||
"patchmonApiSecret": {
|
"patchmonApiSecret": {
|
||||||
"label": "Clé secrète d'API",
|
"label": "Clé secrète d'API",
|
||||||
"newLabel": "Nouveau secret API"
|
"newLabel": ""
|
||||||
},
|
},
|
||||||
"githubAppId": {
|
"githubAppId": {
|
||||||
"label": "Identifiant de l'application",
|
"label": "Identifiant de l'application",
|
||||||
@@ -1321,12 +1321,12 @@
|
|||||||
"full": "Accès intégral à l'intégration"
|
"full": "Accès intégral à l'intégration"
|
||||||
},
|
},
|
||||||
"audiobookshelf": {
|
"audiobookshelf": {
|
||||||
"name": "Étagère de livres audio",
|
"name": "",
|
||||||
"description": "Serveur de livres audio et de podcasts auto-hébergés"
|
"description": ""
|
||||||
},
|
},
|
||||||
"navidrome": {
|
"navidrome": {
|
||||||
"name": "Navidrome",
|
"name": "Navidrome",
|
||||||
"description": "Serveur de streaming de musique auto-hébergé avec compatibilité avec l'API Subsonic"
|
"description": ""
|
||||||
},
|
},
|
||||||
"uptimeKuma": {
|
"uptimeKuma": {
|
||||||
"name": "Uptime Kuma",
|
"name": "Uptime Kuma",
|
||||||
@@ -1349,15 +1349,15 @@
|
|||||||
"description": "Surveiller lequel de vos conteneurs Docker a des mises à jour d'images disponibles"
|
"description": "Surveiller lequel de vos conteneurs Docker a des mises à jour d'images disponibles"
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"patchmon": {
|
"patchmon": {
|
||||||
"name": "PatchMon",
|
"name": "",
|
||||||
"description": "Gestion et surveillance des correctifs pour les hôtes Linux"
|
"description": ""
|
||||||
},
|
},
|
||||||
"bazarr": {
|
"bazarr": {
|
||||||
"name": "Bazarr",
|
"name": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1440,10 +1440,10 @@
|
|||||||
},
|
},
|
||||||
"here": "ici",
|
"here": "ici",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "URL de l'icône ",
|
"label": "URL de l'icône :",
|
||||||
"description": "Recherchez dans la bibliothèque, collez une URL d'image, ou téléchargez un fichier.",
|
"description": "Recherchez dans la bibliothèque, collez une URL d'image, ou téléchargez un fichier.",
|
||||||
"header": "Rechercher les images {countIcons} par nom",
|
"header": "Tapez le nom ou les objets pour filtrer les icônes... Homarr recherchera les icônes {countIcons} pour vous.",
|
||||||
"headerLoading": "Rechercher des images par nom",
|
"headerLoading": "Tapez pour rechercher les icônes…",
|
||||||
"clear": "Effacer l'image",
|
"clear": "Effacer l'image",
|
||||||
"uploadImage": "Charger une image",
|
"uploadImage": "Charger une image",
|
||||||
"results": "Résultats de la recherche d'images",
|
"results": "Résultats de la recherche d'images",
|
||||||
@@ -1642,10 +1642,10 @@
|
|||||||
"actions": "Actions"
|
"actions": "Actions"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"success": "OK",
|
"success": "",
|
||||||
"error": "Erreur",
|
"error": "",
|
||||||
"loading": "Chargement",
|
"loading": "",
|
||||||
"idle": "Inactif"
|
"idle": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
@@ -1799,7 +1799,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"archiveTeamWarrior": {
|
"archiveTeamWarrior": {
|
||||||
"name": "ArchiveTeam Warrior",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showBroadcastMessage": {
|
"showBroadcastMessage": {
|
||||||
@@ -1953,14 +1953,14 @@
|
|||||||
"description": "Utiliser ISO 8601 pour formater la date (cela remplacera les autres options)"
|
"description": "Utiliser ISO 8601 pour formater la date (cela remplacera les autres options)"
|
||||||
},
|
},
|
||||||
"showWeather": {
|
"showWeather": {
|
||||||
"label": "Afficher la météo",
|
"label": "",
|
||||||
"description": "Afficher une petite icône météo animée et la température dans le coin de la carte"
|
"description": ""
|
||||||
},
|
},
|
||||||
"weatherLocation": {
|
"weatherLocation": {
|
||||||
"label": "Lieu de la météo"
|
"label": ""
|
||||||
},
|
},
|
||||||
"isWeatherFormatFahrenheit": {
|
"isWeatherFormatFahrenheit": {
|
||||||
"label": "Utiliser les degrés Fahrenheit"
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1990,7 +1990,7 @@
|
|||||||
"noStation": "Aucune station sélectionnée",
|
"noStation": "Aucune station sélectionnée",
|
||||||
"option": {
|
"option": {
|
||||||
"baseUrl": {
|
"baseUrl": {
|
||||||
"label": "URL Search.ch"
|
"label": ""
|
||||||
},
|
},
|
||||||
"station": {
|
"station": {
|
||||||
"label": "Station"
|
"label": "Station"
|
||||||
@@ -2022,8 +2022,8 @@
|
|||||||
"clear": "Effacer la mise en forme",
|
"clear": "Effacer la mise en forme",
|
||||||
"heading": "Titre {level}",
|
"heading": "Titre {level}",
|
||||||
"align": "Aligner le texte : {position}",
|
"align": "Aligner le texte : {position}",
|
||||||
"directionLtr": "Définir la direction du texte de gauche à droite",
|
"directionLtr": "",
|
||||||
"directionRtl": "Définir la direction du texte de droite à gauche",
|
"directionRtl": "",
|
||||||
"blockquote": "Citation",
|
"blockquote": "Citation",
|
||||||
"horizontalLine": "Ligne horizontale",
|
"horizontalLine": "Ligne horizontale",
|
||||||
"bulletList": "Liste à puces",
|
"bulletList": "Liste à puces",
|
||||||
@@ -2093,7 +2093,7 @@
|
|||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"save": "Sauvegarder",
|
"save": "Sauvegarder",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"readOnlyViewer": "Vous avez un accès en lecture seule à cette note. La modification est désactivée.",
|
"readOnlyViewer": "",
|
||||||
"saveForbidden": "Vous n'êtes pas autorisé à modifier cette note.",
|
"saveForbidden": "Vous n'êtes pas autorisé à modifier cette note.",
|
||||||
"saveFailed": "Impossible d'enregistrer cette note. Veuillez réessayer."
|
"saveFailed": "Impossible d'enregistrer cette note. Veuillez réessayer."
|
||||||
},
|
},
|
||||||
@@ -2308,13 +2308,13 @@
|
|||||||
"description": "À quoi devrait ressembler la date"
|
"description": "À quoi devrait ressembler la date"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currentWindSpeed": "{currentWindSpeed} {unit}",
|
"currentWindSpeed": "{currentWindSpeed} km/h",
|
||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "Lever du Soleil",
|
"sunrise": "Lever du Soleil",
|
||||||
"sunset": "Coucher du soleil",
|
"sunset": "Coucher du soleil",
|
||||||
"maxWindSpeed": "Vitesse maximale du vent : {maxWindSpeed} {unit}",
|
"maxWindSpeed": "Vitesse maximale du vent : {maxWindSpeed} km/h",
|
||||||
"maxWindGusts": "Rafales de vent maximales : {maxWindGusts} {unit}",
|
"maxWindGusts": "Rafales de vent maximales : {maxWindGusts} km/h",
|
||||||
"humidity": "Humidité : {humidity}%"
|
"humidity": ""
|
||||||
},
|
},
|
||||||
"kind": {
|
"kind": {
|
||||||
"clear": "Clair",
|
"clear": "Clair",
|
||||||
@@ -2374,9 +2374,9 @@
|
|||||||
"label": "Afficher les infos sur le système de fichiers"
|
"label": "Afficher les infos sur le système de fichiers"
|
||||||
},
|
},
|
||||||
"visibleStorageVolumes": {
|
"visibleStorageVolumes": {
|
||||||
"label": "Volumes de stockage visibles",
|
"label": "",
|
||||||
"description": "Uniquement disponible lorsque toutes les intégrations sélectionnées sont Synology. Laissez vide pour afficher tous les volumes de stockage détectés.",
|
"description": "",
|
||||||
"placeholder": "Sélectionnez les volumes de stockage"
|
"placeholder": ""
|
||||||
},
|
},
|
||||||
"defaultTab": {
|
"defaultTab": {
|
||||||
"label": "Onglet par défaut"
|
"label": "Onglet par défaut"
|
||||||
@@ -2398,7 +2398,7 @@
|
|||||||
"loadAverage": "Charge moyenne :",
|
"loadAverage": "Charge moyenne :",
|
||||||
"minute": "1 minute",
|
"minute": "1 minute",
|
||||||
"minutes": "{count} minutes",
|
"minutes": "{count} minutes",
|
||||||
"used": "Utilisé",
|
"used": "Utilisé ",
|
||||||
"available": "Disponible",
|
"available": "Disponible",
|
||||||
"lastSeen": "Dernière mise à jour de statut : {lastSeen}"
|
"lastSeen": "Dernière mise à jour de statut : {lastSeen}"
|
||||||
},
|
},
|
||||||
@@ -2435,7 +2435,7 @@
|
|||||||
"cpu": "Coeurs",
|
"cpu": "Coeurs",
|
||||||
"memory": "Mémoire",
|
"memory": "Mémoire",
|
||||||
"storage": "Stockage",
|
"storage": "Stockage",
|
||||||
"uptime": "Disponible",
|
"uptime": "Disponibilité",
|
||||||
"haState": "État HA",
|
"haState": "État HA",
|
||||||
"storageType": {
|
"storageType": {
|
||||||
"local": "Stockage local",
|
"local": "Stockage local",
|
||||||
@@ -2458,7 +2458,7 @@
|
|||||||
"description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)",
|
"description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)",
|
||||||
"option": {
|
"option": {
|
||||||
"columns": {
|
"columns": {
|
||||||
"label": "Colonnes à afficher"
|
"label": ""
|
||||||
},
|
},
|
||||||
"enableRowSorting": {
|
"enableRowSorting": {
|
||||||
"label": "Activer le tri des éléments"
|
"label": "Activer le tri des éléments"
|
||||||
@@ -2476,19 +2476,19 @@
|
|||||||
"label": "Inverser le tri"
|
"label": "Inverser le tri"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Ordre des colonnes"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Largeurs des colonnes"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Impossible de récupérer les statistiques des conteneurs",
|
"internalServerError": "Impossible de récupérer les statistiques des conteneurs",
|
||||||
"layoutSaveTitle": "Disposition du tableau non sauvegardée",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": ""
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "Aucun conteneur trouvé"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
@@ -2525,9 +2525,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vpn": {
|
"vpn": {
|
||||||
"name": "VPN",
|
"name": "",
|
||||||
"description": "Aperçu de vos connexions VPN",
|
"description": "",
|
||||||
"serviceUnavailable": "VPN indisponible",
|
"serviceUnavailable": "",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
"ups": {
|
"ups": {
|
||||||
@@ -2784,31 +2784,31 @@
|
|||||||
"description": "Désactiver cette option ne fonctionnera pas pour Plex"
|
"description": "Désactiver cette option ne fonctionnera pas pour Plex"
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "Afficher le débit",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "Afficher la localisation",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "",
|
"streams": "",
|
||||||
"totalBitrate": "Total : {bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "En cours de lecture",
|
"currentlyPlaying": "En cours de lecture",
|
||||||
"user": "Utilisateur",
|
"user": "Utilisateur",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
"status": "Statut",
|
"status": "",
|
||||||
"directPlay": "Lecture directe",
|
"directPlay": "",
|
||||||
"directStream": "Flux direct",
|
"directStream": "",
|
||||||
"transcodeVideo": "Transcodage vidéo",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "Transcodage audio",
|
"transcodeAudio": "",
|
||||||
"transcoding": "Transcodage",
|
"transcoding": "",
|
||||||
"paused": "En pause",
|
"paused": "",
|
||||||
"remaining": "{minutes} min restantes",
|
"remaining": "",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"title": "Statistiques détaillées",
|
"title": "Statistiques détaillées",
|
||||||
"video": {
|
"video": {
|
||||||
@@ -2834,111 +2834,111 @@
|
|||||||
"description": "Aperçu de votre archive de documents Paperless-ngx",
|
"description": "Aperçu de votre archive de documents Paperless-ngx",
|
||||||
"documentsTotal": "Nombre total de documents",
|
"documentsTotal": "Nombre total de documents",
|
||||||
"documentsInbox": "Boîte de réception",
|
"documentsInbox": "Boîte de réception",
|
||||||
"correspondents": "Correspondants",
|
"correspondents": "",
|
||||||
"tags": "Tags",
|
"tags": "",
|
||||||
"documentTypes": "Types de documents",
|
"documentTypes": "",
|
||||||
"inboxRatio": "Ratio de la boîte de réception",
|
"inboxRatio": "",
|
||||||
"inbox": "boîte de réception",
|
"inbox": "",
|
||||||
"total": "total",
|
"total": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showInboxRatio": {
|
"showInboxRatio": {
|
||||||
"label": "Afficher le ratio de la boîte de réception",
|
"label": "Afficher le ratio de la boîte de réception",
|
||||||
"description": "Afficher le rapport inbox/total des documents dans la section Héros"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showInboxRing": {
|
"showInboxRing": {
|
||||||
"label": "Afficher l'anneau de la boîte de réception",
|
"label": "",
|
||||||
"description": "Afficher un anneau de progression à côté du ratio de la boîte de réception"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showDocumentsTotal": {
|
"showDocumentsTotal": {
|
||||||
"label": "Afficher le total des documents",
|
"label": "",
|
||||||
"description": "Afficher le nombre total de documents dans votre archive"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showDocumentsInbox": {
|
"showDocumentsInbox": {
|
||||||
"label": "Afficher le compteur de la boîte de réception",
|
"label": "",
|
||||||
"description": "Afficher le nombre de documents non traités dans la boîte de réception"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showCorrespondents": {
|
"showCorrespondents": {
|
||||||
"label": "Afficher les correspondants",
|
"label": "",
|
||||||
"description": "Afficher le nombre total de correspondants"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showTags": {
|
"showTags": {
|
||||||
"label": "Afficher les tags",
|
"label": "",
|
||||||
"description": "Afficher le nombre total de tags"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showDocumentTypes": {
|
"showDocumentTypes": {
|
||||||
"label": "Afficher les types de documents",
|
"label": "",
|
||||||
"description": "Afficher le nombre total de types de documents"
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Échec de la récupération des données Paperless-ngx"
|
"internalServerError": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"patchmon": {
|
"patchmon": {
|
||||||
"name": "PatchMon",
|
"name": "",
|
||||||
"description": "Aperçu de l'état des correctifs des hôtes depuis PatchMon",
|
"description": "",
|
||||||
"title": "Statistiques PatchMon",
|
"title": "",
|
||||||
"totalHosts": "Total des hôtes",
|
"totalHosts": "",
|
||||||
"hostsNeedingUpdates": "Mises à jour requises",
|
"hostsNeedingUpdates": "",
|
||||||
"securityUpdates": "Mises à jour de sécurité",
|
"securityUpdates": "",
|
||||||
"upToDateHosts": "Hôtes à jour",
|
"upToDateHosts": "",
|
||||||
"hostsWithSecurityUpdates": "Hôtes avec mises à jour de sécurité",
|
"hostsWithSecurityUpdates": "",
|
||||||
"recentUpdates24h": "Mises à jour (24h)",
|
"recentUpdates24h": "",
|
||||||
"totalOutdatedPackages": "Paquets obsolètes",
|
"totalOutdatedPackages": "",
|
||||||
"totalRepos": "Dépôts",
|
"totalRepos": "",
|
||||||
"osDistribution": "Répartition des OS",
|
"osDistribution": "",
|
||||||
"compliance": "Conformité des correctifs",
|
"compliance": "",
|
||||||
"complianceRatio": "{upToDate} / {total}",
|
"complianceRatio": "",
|
||||||
"lastUpdated": "Mis à jour {time}",
|
"lastUpdated": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showTotalHosts": {
|
"showTotalHosts": {
|
||||||
"label": "Afficher le nombre total d'hôtes",
|
"label": "",
|
||||||
"description": "Afficher le nombre total d'hôtes surveillés"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showHostsNeedingUpdates": {
|
"showHostsNeedingUpdates": {
|
||||||
"label": "Afficher les hôtes ayant besoin de mises à jour",
|
"label": "",
|
||||||
"description": "Afficher les hôtes avec au moins un paquet obsolète"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showSecurityUpdates": {
|
"showSecurityUpdates": {
|
||||||
"label": "Afficher les mises à jour de sécurité",
|
"label": "",
|
||||||
"description": "Afficher le nombre total de mises à jour de sécurité disponibles"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showUpToDateHosts": {
|
"showUpToDateHosts": {
|
||||||
"label": "Afficher les hôtes à jour",
|
"label": "",
|
||||||
"description": "Afficher les hôtes avec aucun paquet obsolète"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showHostsWithSecurityUpdates": {
|
"showHostsWithSecurityUpdates": {
|
||||||
"label": "Afficher les hôtes avec les mises à jour de sécurité",
|
"label": "",
|
||||||
"description": "Afficher les hôtes nécessitant des correctifs de sécurité"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showRecentUpdates24h": {
|
"showRecentUpdates24h": {
|
||||||
"label": "Afficher les mises à jour récentes (24h)",
|
"label": "",
|
||||||
"description": "Afficher le nombre de mises à jour appliquées au cours des dernières 24 heures"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showTotalOutdatedPackages": {
|
"showTotalOutdatedPackages": {
|
||||||
"label": "Afficher les paquets obsolètes",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"showTotalRepos": {
|
"showTotalRepos": {
|
||||||
"label": "Afficher les dépôts",
|
"label": "",
|
||||||
"description": "Afficher le nombre de dépôts actifs en cours de surveillance"
|
"description": ""
|
||||||
},
|
},
|
||||||
"enableThresholdColors": {
|
"enableThresholdColors": {
|
||||||
"label": "Activer les couleurs de seuil",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"useCustomThresholds": {
|
"useCustomThresholds": {
|
||||||
"label": "Seuils avancés",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"thresholdMode": {
|
"thresholdMode": {
|
||||||
"option": {
|
"option": {
|
||||||
"absolute": "",
|
"absolute": "",
|
||||||
"percent": "Pourcentage du total des hôtes"
|
"percent": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hostsNeedingUpdatesThresholdMode": {
|
"hostsNeedingUpdatesThresholdMode": {
|
||||||
"label": "Nécessite le mode de mise à jour de seuil",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"hostsNeedingUpdatesWarningAt": {
|
"hostsNeedingUpdatesWarningAt": {
|
||||||
@@ -2958,7 +2958,7 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"securityUpdatesCriticalAt": {
|
"securityUpdatesCriticalAt": {
|
||||||
"label": "Seuil critique de mise à jour de sécurité",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"hostsWithSecurityUpdatesThresholdMode": {
|
"hostsWithSecurityUpdatesThresholdMode": {
|
||||||
@@ -2998,33 +2998,33 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"showOsDistribution": {
|
"showOsDistribution": {
|
||||||
"label": "Afficher la répartition des OS",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"osDistributionLimit": {
|
"osDistributionLimit": {
|
||||||
"label": "",
|
"label": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"3": "Top 3",
|
"3": "",
|
||||||
"5": "Top 5",
|
"5": "",
|
||||||
"10": "Top 10",
|
"10": "",
|
||||||
"all": "Tout"
|
"all": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showOsVersion": {
|
"showOsVersion": {
|
||||||
"label": "Afficher la version de l'OS",
|
"label": "",
|
||||||
"description": "Ajouter la version de l'OS à chaque étiquette de distribution lorsque disponible"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showComplianceHero": {
|
"showComplianceHero": {
|
||||||
"label": "Afficher la jauge de conformité",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"osDisplayMode": {
|
"osDisplayMode": {
|
||||||
"label": "Mode d'affichage de l'OS",
|
"label": "",
|
||||||
"description": "Choisir comment afficher la répartition des systèmes d'exploitation",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"bars": "Barres horizontales",
|
"bars": "",
|
||||||
"donut": "Graphique en anneau"
|
"donut": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3033,19 +3033,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bazarr": {
|
"bazarr": {
|
||||||
"name": "Bazarr",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"episodes": "Épisodes manquants",
|
"episodes": "",
|
||||||
"movies": "Films manquants",
|
"movies": "",
|
||||||
"providers": "Problèmes de fournisseurs",
|
"providers": "",
|
||||||
"status": "",
|
"status": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showMissingEpisodes": {
|
"showMissingEpisodes": {
|
||||||
"label": "Afficher les épisodes manquants",
|
"label": "",
|
||||||
"description": "Afficher le nombre d'épisodes avec les sous-titres manquants"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showMissingMovies": {
|
"showMissingMovies": {
|
||||||
"label": "Afficher les films manquants",
|
"label": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"showProviderIssues": {
|
"showProviderIssues": {
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "Identifiant de l'album",
|
"label": "Identifiant de l'album",
|
||||||
"description": "C'est l'identifiant unique qui correspond à l'album Immich. Vous pouvez le trouver en ouvrant un album et copiant l'identifiant unique depuis l'URL, par exemple 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "C'est l'identifiant unique qui correspond à l'album Immich. Vous pouvez le trouver en ouvrant un album et copiant l'identifiant unique depuis l'URL, par exemple 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Intervalle en secondes",
|
"label": "Intervalle en secondes",
|
||||||
@@ -3153,18 +3153,18 @@
|
|||||||
"description": "Cela limitera le nombre d'éléments affichés par intégration, pas globalement"
|
"description": "Cela limitera le nombre d'éléments affichés par intégration, pas globalement"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Ordre des colonnes"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Largeur des colonnes"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "Sélectionner les colonnes dans les éléments",
|
"noColumns": "Sélectionner les colonnes dans les éléments",
|
||||||
"noCommunications": "Impossible de charger les données à partir de l'intégration",
|
"noCommunications": "Impossible de charger les données à partir de l'intégration",
|
||||||
"noItems": "Aucun téléchargement trouvé",
|
"noItems": "",
|
||||||
"actionFailed": "Action échouée",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "Cette action n'a pas pu être complétée. Veuillez ressayer."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3358,7 +3358,7 @@
|
|||||||
"option": {},
|
"option": {},
|
||||||
"titles": {
|
"titles": {
|
||||||
"stats": {
|
"stats": {
|
||||||
"main": "Stats des médias",
|
"main": "Statistiques des médias",
|
||||||
"approved": "Déjà approuvé",
|
"approved": "Déjà approuvé",
|
||||||
"pending": "En attente de validation",
|
"pending": "En attente de validation",
|
||||||
"processing": "En cours de traitement",
|
"processing": "En cours de traitement",
|
||||||
@@ -3428,7 +3428,7 @@
|
|||||||
"file": "Fichier",
|
"file": "Fichier",
|
||||||
"size": "Taille",
|
"size": "Taille",
|
||||||
"transcode": "Transcodeur",
|
"transcode": "Transcodeur",
|
||||||
"healthCheck": "Contrôle de santé"
|
"healthCheck": "Bilan de santé"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3441,26 +3441,26 @@
|
|||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"showQueued": {
|
"showQueued": {
|
||||||
"label": "Afficher les éléments en attente"
|
"label": ""
|
||||||
},
|
},
|
||||||
"pageSize": {
|
"pageSize": {
|
||||||
"label": "Éléments par page"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
"missing": "",
|
"missing": "",
|
||||||
"queued": "En file d’attente"
|
"queued": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"movie": "Film",
|
"movie": "",
|
||||||
"episode": "Épisode"
|
"episode": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"missing": "Aucun élément manquant",
|
"missing": "",
|
||||||
"queued": "Aucun élément en file d'attente",
|
"queued": "",
|
||||||
"noTabsEnabled": "Activer au moins un onglet dans les paramètres du widget"
|
"noTabsEnabled": ""
|
||||||
},
|
},
|
||||||
"timeLeft": "Temps restant"
|
"timeLeft": ""
|
||||||
},
|
},
|
||||||
"rssFeed": {
|
"rssFeed": {
|
||||||
"name": "Flux RSS",
|
"name": "Flux RSS",
|
||||||
@@ -3594,9 +3594,9 @@
|
|||||||
"not-found": "Non trouvé",
|
"not-found": "Non trouvé",
|
||||||
"pre-release": "Pré-publication",
|
"pre-release": "Pré-publication",
|
||||||
"archived": "Archivé",
|
"archived": "Archivé",
|
||||||
"forked": "Forké",
|
"forked": "",
|
||||||
"starsCount": "Étoiles",
|
"starsCount": "Étoiles",
|
||||||
"forksCount": "Forks",
|
"forksCount": "",
|
||||||
"issuesCount": "Problèmes ouverts",
|
"issuesCount": "Problèmes ouverts",
|
||||||
"markViewed": "Marquer comme vu(s)",
|
"markViewed": "Marquer comme vu(s)",
|
||||||
"openProjectPage": "Ouvrir la page du projet",
|
"openProjectPage": "Ouvrir la page du projet",
|
||||||
@@ -3707,7 +3707,7 @@
|
|||||||
"noItems": "Aucune notification à afficher.",
|
"noItems": "Aucune notification à afficher.",
|
||||||
"option": {
|
"option": {
|
||||||
"hideLogos": {
|
"hideLogos": {
|
||||||
"label": "Masquer les logos"
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3751,7 +3751,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uptimeKuma": {
|
"uptimeKuma": {
|
||||||
"name": "Uptime Kuma",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showAverageUptime": {
|
"showAverageUptime": {
|
||||||
@@ -3812,7 +3812,7 @@
|
|||||||
},
|
},
|
||||||
"resource": {
|
"resource": {
|
||||||
"routers": "",
|
"routers": "",
|
||||||
"services": "Services",
|
"services": "",
|
||||||
"middlewares": ""
|
"middlewares": ""
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
@@ -4030,15 +4030,15 @@
|
|||||||
"system": "",
|
"system": "",
|
||||||
"cpu": "",
|
"cpu": "",
|
||||||
"memory": "",
|
"memory": "",
|
||||||
"disk": "Disque",
|
"disk": "",
|
||||||
"gpu": "",
|
"gpu": "",
|
||||||
"loadAvg": "",
|
"loadAvg": "",
|
||||||
"net": "",
|
"net": "",
|
||||||
"temp": "Temp",
|
"temp": "",
|
||||||
"battery": "",
|
"battery": "",
|
||||||
"services": "",
|
"services": "",
|
||||||
"uptime": "",
|
"uptime": "",
|
||||||
"agent": "Agent"
|
"agent": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"beszelSystemGrid": {
|
"beszelSystemGrid": {
|
||||||
@@ -4090,7 +4090,7 @@
|
|||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
"cpu": "",
|
"cpu": "",
|
||||||
"memory": "Mémoire",
|
"memory": "",
|
||||||
"disk": "",
|
"disk": "",
|
||||||
"gpu": "",
|
"gpu": "",
|
||||||
"loadAvg": "",
|
"loadAvg": "",
|
||||||
@@ -4117,8 +4117,8 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"internalServerError": ""
|
"internalServerError": ""
|
||||||
},
|
},
|
||||||
"history": "Historique",
|
"history": "",
|
||||||
"resolved": "Résolu",
|
"resolved": "",
|
||||||
"status": {
|
"status": {
|
||||||
"triggered": "",
|
"triggered": "",
|
||||||
"ok": ""
|
"ok": ""
|
||||||
@@ -4173,12 +4173,12 @@
|
|||||||
"chart": {
|
"chart": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"title": "",
|
"title": "",
|
||||||
"subtitle": "Utilisation moyenne du CPU à l'échelle du système",
|
"subtitle": "",
|
||||||
"series": "CPU"
|
"series": ""
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "Utilisation Mémoire",
|
"title": "",
|
||||||
"subtitle": "Utilisation précise au moment enregistré",
|
"subtitle": "",
|
||||||
"series": "",
|
"series": "",
|
||||||
"cache": ""
|
"cache": ""
|
||||||
},
|
},
|
||||||
@@ -4189,7 +4189,7 @@
|
|||||||
},
|
},
|
||||||
"diskIO": {
|
"diskIO": {
|
||||||
"title": "",
|
"title": "",
|
||||||
"subtitle": "Débit du système de fichiers root",
|
"subtitle": "",
|
||||||
"read": "",
|
"read": "",
|
||||||
"write": ""
|
"write": ""
|
||||||
},
|
},
|
||||||
@@ -5480,7 +5480,7 @@
|
|||||||
"title": "Capacité",
|
"title": "Capacité",
|
||||||
"resource": {
|
"resource": {
|
||||||
"reserved": "Réservé",
|
"reserved": "Réservé",
|
||||||
"used": "Utilisé"
|
"used": "Utilisé "
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
@@ -5766,7 +5766,7 @@
|
|||||||
"label": "Médias"
|
"label": "Médias"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"label": "Apps",
|
"label": "Applications",
|
||||||
"new": {
|
"new": {
|
||||||
"label": "Nouveau"
|
"label": "Nouveau"
|
||||||
},
|
},
|
||||||
@@ -5861,7 +5861,7 @@
|
|||||||
"placeholder": "Rechercher quelque chose",
|
"placeholder": "Rechercher quelque chose",
|
||||||
"nothingFound": "Aucun résultat",
|
"nothingFound": "Aucun résultat",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch": "Une erreur s'est produite lors de la récupération des données"
|
"fetch": "Une erreur s'est produite lors de la récupération des données."
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"appIntegrationBoard": {
|
"appIntegrationBoard": {
|
||||||
@@ -6544,7 +6544,7 @@
|
|||||||
},
|
},
|
||||||
"editMode": {
|
"editMode": {
|
||||||
"title": "Mode édition",
|
"title": "Mode édition",
|
||||||
"description": "Cliquez ici pour entrer en mode édition. Vous pourrez ensuite ajouter des widgets, des applications et des sections — faites glisser pour repositionner, redimensionnez avec la poignée d'angle. Cliquez à nouveau pour enregistrer vos modifications."
|
"description": "Cliquez ici pour entrer en mode édition. Vous pouvez ensuite ajouter des widgets, des applications et des sections — faites glisser pour repositionner, redimensionnez avec la poignée d'angle. Cliquez à nouveau pour enregistrer vos modifications."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Paramètres du tableau de bord",
|
"title": "Paramètres du tableau de bord",
|
||||||
|
|||||||
@@ -1345,8 +1345,8 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "וואטס אפ דוקר",
|
"name": "",
|
||||||
"description": "עקוב אחר אילו מקונטיינרי הדוקר שלך כוללים עדכוני תמונה זמינים"
|
"description": ""
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -1441,18 +1441,18 @@
|
|||||||
"here": "כאן",
|
"here": "כאן",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "קישור של הסמל",
|
"label": "קישור של הסמל",
|
||||||
"description": "חפש בספרייה, הדבק כתובת URL של תמונה, או העלה קובץ.",
|
"description": "",
|
||||||
"header": "הקלד שם או אובייקטים לסינון עבור אייקונים... Homarr יחפש עבורך סמלים של {countIcons} ",
|
"header": "הקלד שם או אובייקטים לסינון עבור אייקונים... Homarr יחפש עבורך סמלים של {countIcons} .",
|
||||||
"headerLoading": "חפש תמונות לפי שם",
|
"headerLoading": "",
|
||||||
"clear": "נקה תמונה",
|
"clear": "",
|
||||||
"uploadImage": "העלה תמונה",
|
"uploadImage": "",
|
||||||
"results": "תוצאות חיפוש תמונות",
|
"results": "",
|
||||||
"localImages": "התמונות שלך",
|
"localImages": "התמונות שלך",
|
||||||
"svgIcons": "סמלי SVG",
|
"svgIcons": "",
|
||||||
"otherImages": "תמונות אחרות",
|
"otherImages": "תמונות אחרות",
|
||||||
"urlLoading": "בודק תמונה…",
|
"urlLoading": "",
|
||||||
"urlReady": "התמונה מוכנה לשימוש",
|
"urlReady": "",
|
||||||
"urlError": "לא ניתן היה לטעון את התמונה. בדוק את כתובת ה-URL ונסה שוב.",
|
"urlError": "",
|
||||||
"noResults": "",
|
"noResults": "",
|
||||||
"error": ""
|
"error": ""
|
||||||
},
|
},
|
||||||
@@ -1808,19 +1808,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "וואטס אפ דוקר",
|
"name": "",
|
||||||
"description": "הצג כמה מהמכולות הנצפות עם עדכונים זמינים.",
|
"description": "",
|
||||||
"title": "What's Up Docker",
|
"title": "",
|
||||||
"monitored": "מנוטר",
|
"monitored": "",
|
||||||
"updatesAvailable": "{count, plural, one {# עדכון זמין} two {# עדכונים זמינים} many {# עדכונים זמינים} other {# עדכונים זמינים}}",
|
"updatesAvailable": "",
|
||||||
"updateAvailable": "עדכון זמין",
|
"updateAvailable": "",
|
||||||
"openReleaseNotes": "פתח הערות שחרור",
|
"openReleaseNotes": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "הצג כותרת"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showRing": {
|
"showRing": {
|
||||||
"label": "הצג טבעת עדכון"
|
"label": ""
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "פריסה",
|
"label": "פריסה",
|
||||||
@@ -1834,8 +1834,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showUpdateList": {
|
"showUpdateList": {
|
||||||
"label": "הצג רשימת עדכוני מכולות",
|
"label": "",
|
||||||
"description": "מפרט כל מכולה מנוטרת שיש עדכון תמונה זמין עבורה"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2641,7 +2641,7 @@
|
|||||||
"today": "היום",
|
"today": "היום",
|
||||||
"24h": "24 השעות האחרונות",
|
"24h": "24 השעות האחרונות",
|
||||||
"7d": "7 הימים האחרונים",
|
"7d": "7 הימים האחרונים",
|
||||||
"30d": "30 הימים האחרונים",
|
"30d": "",
|
||||||
"month": "",
|
"month": "",
|
||||||
"lastMonth": ""
|
"lastMonth": ""
|
||||||
}
|
}
|
||||||
|
|||||||
+1572
-1572
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Icona URL",
|
"label": "Icona URL",
|
||||||
"description": "Cerca nella libreria, incolla un URL di immagine, o carica un file.",
|
"description": "Cerca nella libreria, incolla un URL di immagine, o carica un file.",
|
||||||
"header": "Digita il nome o gli oggetti da filtrare per le icone... Homarr cercherà tra le icone {countIcons} per te",
|
"header": "Digita il nome o gli oggetti da filtrare per le icone... Homarr cercherà tra le icone {countIcons} per te.",
|
||||||
"headerLoading": "Digita per cercare icone",
|
"headerLoading": "Digita per cercare icone…",
|
||||||
"clear": "Cancella immagine",
|
"clear": "Cancella immagine",
|
||||||
"uploadImage": "Carica immagine",
|
"uploadImage": "Carica immagine",
|
||||||
"results": "Risultati della ricerca immagini",
|
"results": "Risultati della ricerca immagini",
|
||||||
@@ -2070,7 +2070,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"anchorNote": {
|
"anchorNote": {
|
||||||
"name": "Ancora Nota",
|
"name": "Nota Anchor",
|
||||||
"description": "Visualizza una nota selezionata da Ancoraggio",
|
"description": "Visualizza una nota selezionata da Ancoraggio",
|
||||||
"option": {
|
"option": {
|
||||||
"noteId": {
|
"noteId": {
|
||||||
@@ -2308,12 +2308,12 @@
|
|||||||
"description": "Come dovrebbe apparire la data"
|
"description": "Come dovrebbe apparire la data"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currentWindSpeed": "{currentWindSpeed} {unit}",
|
"currentWindSpeed": "{currentWindSpeed} km/h",
|
||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "Alba",
|
"sunrise": "Alba",
|
||||||
"sunset": "Tramonto",
|
"sunset": "Tramonto",
|
||||||
"maxWindSpeed": "Velocità massima del vento: {maxWindSpeed} {unit}",
|
"maxWindSpeed": "Max velocità vento: {maxWindSpeed} km/h",
|
||||||
"maxWindGusts": "Raffiche di vento massime: {maxWindGusts} {unit}",
|
"maxWindGusts": "Max raffiche di vento: {maxWindGusts} km/h",
|
||||||
"humidity": "Umidità: {humidity}%"
|
"humidity": "Umidità: {humidity}%"
|
||||||
},
|
},
|
||||||
"kind": {
|
"kind": {
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "ID Album",
|
"label": "ID Album",
|
||||||
"description": "Si tratta di un ID univoco che si riferisce al tuo album Immich. Puoi trovare questo id aprendo un album e copiando la stringa univoca dall'URL, ad esempio 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "Si tratta di un ID univoco che si riferisce al tuo album Immich. Puoi trovare questo id aprendo un album e copiando la stringa univoca dall'URL, ad esempio 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Intervallo in secondi",
|
"label": "Intervallo in secondi",
|
||||||
@@ -3254,7 +3254,7 @@
|
|||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"pause": "Metti in pausa il client",
|
"pause": "Metti in pausa il client",
|
||||||
"resume": "Riprendi il client"
|
"resume": "Ripristina il client"
|
||||||
},
|
},
|
||||||
"item": {
|
"item": {
|
||||||
"pause": "Metti in pausa l'elemento",
|
"pause": "Metti in pausa l'elemento",
|
||||||
@@ -5398,7 +5398,7 @@
|
|||||||
"label": "Arresta",
|
"label": "Arresta",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Containers fermati",
|
"title": "Containers arrestati",
|
||||||
"message": "I containers sono stati arrestati con successo"
|
"message": "I containers sono stati arrestati con successo"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
@@ -1442,7 +1442,7 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "アイコン URL",
|
"label": "アイコン URL",
|
||||||
"description": "ライブラリを検索し、画像の URL を貼り付けるか、ファイルをアップロードします。",
|
"description": "ライブラリを検索し、画像の URL を貼り付けるか、ファイルをアップロードします。",
|
||||||
"header": "{countIcons} 画像を名前で検索",
|
"header": "アイコンをフィルタするために、名前またはオブジェクトを入力してください... Homarrは {countIcons} アイコンを検索します。",
|
||||||
"headerLoading": "名前で画像を検索",
|
"headerLoading": "名前で画像を検索",
|
||||||
"clear": "画像をクリア",
|
"clear": "画像をクリア",
|
||||||
"uploadImage": "画像をアップロード",
|
"uploadImage": "画像をアップロード",
|
||||||
@@ -1450,7 +1450,7 @@
|
|||||||
"localImages": "あなたの画像",
|
"localImages": "あなたの画像",
|
||||||
"svgIcons": "SVGアイコン",
|
"svgIcons": "SVGアイコン",
|
||||||
"otherImages": "その他の画像",
|
"otherImages": "その他の画像",
|
||||||
"urlLoading": "画像を確認中…",
|
"urlLoading": "",
|
||||||
"urlReady": "画像を使用する準備ができました",
|
"urlReady": "画像を使用する準備ができました",
|
||||||
"urlError": "この画像を読み込めませんでした。URLを確認して再度お試し下さい。",
|
"urlError": "この画像を読み込めませんでした。URLを確認して再度お試し下さい。",
|
||||||
"noResults": "",
|
"noResults": "",
|
||||||
@@ -2535,7 +2535,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showBattery": {
|
"showBattery": {
|
||||||
"label": "バッテリーを表示"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showLoad": {
|
"showLoad": {
|
||||||
"label": ""
|
"label": ""
|
||||||
@@ -2641,7 +2641,7 @@
|
|||||||
"today": "今日",
|
"today": "今日",
|
||||||
"24h": "過去24時間",
|
"24h": "過去24時間",
|
||||||
"7d": "過去7日間",
|
"7d": "過去7日間",
|
||||||
"30d": "過去30日間",
|
"30d": "",
|
||||||
"month": "",
|
"month": "",
|
||||||
"lastMonth": ""
|
"lastMonth": ""
|
||||||
}
|
}
|
||||||
@@ -6501,7 +6501,7 @@
|
|||||||
"skip": "",
|
"skip": "",
|
||||||
"manage": {
|
"manage": {
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"title": "Homarrへようこそ!",
|
"title": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"boardsList": {
|
"boardsList": {
|
||||||
|
|||||||
@@ -1042,20 +1042,20 @@
|
|||||||
},
|
},
|
||||||
"unknown": {
|
"unknown": {
|
||||||
"title": "알 수 없는 오류",
|
"title": "알 수 없는 오류",
|
||||||
"description": "알 수 없는 오류가 발생했습니다. 자세한 내용을 보려면 아래 원인을 펼치세요"
|
"description": ""
|
||||||
},
|
},
|
||||||
"parse": {
|
"parse": {
|
||||||
"title": "파싱 오류",
|
"title": "파싱 오류",
|
||||||
"description": "응답을 파싱할 수 없습니다. URL이 서비스의 기본 URL을 가리키는지 확인하세요."
|
"description": ""
|
||||||
},
|
},
|
||||||
"authorization": {
|
"authorization": {
|
||||||
"title": "인증 오류",
|
"title": "인증 오류",
|
||||||
"description": "요청이 승인되지 않았습니다. 자격 증명이 올바르고 충분한 권한으로 구성되어 있는지 확인하세요."
|
"description": ""
|
||||||
},
|
},
|
||||||
"statusCode": {
|
"statusCode": {
|
||||||
"title": "응답 오류",
|
"title": "응답 오류",
|
||||||
"description": "예상치 못한 {statusCode} ({reason}) 응답을 <url></url>에서 받았습니다. URL이 통합의 기본 URL을 가리키는지 확인하세요.",
|
"description": "",
|
||||||
"otherDescription": "예상치 못한 {statusCode} 응답을 <url></url>에서 받았습니다. URL이 통합의 기본 URL을 가리키는지 확인하세요.",
|
"otherDescription": "",
|
||||||
"reason": {
|
"reason": {
|
||||||
"badRequest": "잘못된 요청",
|
"badRequest": "잘못된 요청",
|
||||||
"notFound": "찾을 수 없음",
|
"notFound": "찾을 수 없음",
|
||||||
@@ -1345,8 +1345,8 @@
|
|||||||
"description": "클라우드 네이티브 리버스 프록시 및 로드 밸런서"
|
"description": "클라우드 네이티브 리버스 프록시 및 로드 밸런서"
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "왓츠업 도커",
|
"name": "",
|
||||||
"description": "Docker 컨테이너 중 이미지 업데이트가 가능한 것을 모니터링합니다."
|
"description": ""
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "Paperless-ngx",
|
||||||
@@ -1441,18 +1441,18 @@
|
|||||||
"here": "여기",
|
"here": "여기",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "아이콘 URL",
|
"label": "아이콘 URL",
|
||||||
"description": "라이브러리에서 검색하거나, 이미지 URL을 붙여넣거나, 파일을 업로드하세요.",
|
"description": "",
|
||||||
"header": "이름 또는 주제를 입력해 아이콘을 필터링하세요... Homarr가 {countIcons}에서 아이콘을 검색합니다",
|
"header": "이름 또는 주제를 입력해 아이콘을 필터링하세요... Homarr가 {countIcons}에서 아이콘을 검색합니다.",
|
||||||
"headerLoading": "아이콘을 검색하려면 입력",
|
"headerLoading": "아이콘을 검색하려면 입력…",
|
||||||
"clear": "이미지 지우기",
|
"clear": "",
|
||||||
"uploadImage": "이미지 업로드",
|
"uploadImage": "",
|
||||||
"results": "이미지 검색 결과",
|
"results": "",
|
||||||
"localImages": "내 이미지",
|
"localImages": "내 이미지",
|
||||||
"svgIcons": "SVG 아이콘",
|
"svgIcons": "",
|
||||||
"otherImages": "기타 이미지",
|
"otherImages": "기타 이미지",
|
||||||
"urlLoading": "이미지 확인 중…",
|
"urlLoading": "",
|
||||||
"urlReady": "이미지 사용 준비 완료",
|
"urlReady": "",
|
||||||
"urlError": "이 이미지는 불러올 수 없습니다. URL을 확인하고 다시 시도하십시오.",
|
"urlError": "",
|
||||||
"noResults": "\"{search}\"에 일치하는 아이콘이 없습니다.",
|
"noResults": "\"{search}\"에 일치하는 아이콘이 없습니다.",
|
||||||
"error": "아이콘을 불러올 수 없습니다."
|
"error": "아이콘을 불러올 수 없습니다."
|
||||||
},
|
},
|
||||||
@@ -1808,19 +1808,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "왓츠업 도커",
|
"name": "",
|
||||||
"description": "모니터링 중인 컨테이너에서 사용 가능한 업데이트 수를 표시합니다.",
|
"description": "",
|
||||||
"title": "What's Up Docker",
|
"title": "",
|
||||||
"monitored": "모니터링 중",
|
"monitored": "",
|
||||||
"updatesAvailable": "{count, plural, other {업데이트 #개 사용 가능}}",
|
"updatesAvailable": "",
|
||||||
"updateAvailable": "업데이트 사용 가능",
|
"updateAvailable": "",
|
||||||
"openReleaseNotes": "릴리스 노트 열기",
|
"openReleaseNotes": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "제목 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showRing": {
|
"showRing": {
|
||||||
"label": "업데이트 링 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "레이아웃",
|
"label": "레이아웃",
|
||||||
@@ -1834,8 +1834,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showUpdateList": {
|
"showUpdateList": {
|
||||||
"label": "컨테이너 업데이트 목록 표시",
|
"label": "",
|
||||||
"description": "이미지 업데이트가 있는 각 모니터링 컨테이너를 나열합니다."
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1984,16 +1984,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timetable": {
|
"timetable": {
|
||||||
"name": "시간표",
|
"name": "",
|
||||||
"description": "역의 출발 시간을 표시합니다",
|
"description": "",
|
||||||
"title": "{station} 출발",
|
"title": "",
|
||||||
"noStation": "선택한 역이 없습니다",
|
"noStation": "",
|
||||||
"option": {
|
"option": {
|
||||||
"baseUrl": {
|
"baseUrl": {
|
||||||
"label": "Search.ch URL"
|
"label": "Search.ch URL"
|
||||||
},
|
},
|
||||||
"station": {
|
"station": {
|
||||||
"label": "역"
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2061,7 +2061,7 @@
|
|||||||
},
|
},
|
||||||
"placeholder": "노트 작성 시작",
|
"placeholder": "노트 작성 시작",
|
||||||
"dismiss": {
|
"dismiss": {
|
||||||
"title": "변경사항을 버리시겠습니까??",
|
"title": "변경사항 삭제",
|
||||||
"message": "노트북에 저장되지 않은 변경 사항이 있습니다. 삭제하시겠습니까?",
|
"message": "노트북에 저장되지 않은 변경 사항이 있습니다. 삭제하시겠습니까?",
|
||||||
"action": {
|
"action": {
|
||||||
"discard": "저장하지 않음",
|
"discard": "저장하지 않음",
|
||||||
@@ -2070,32 +2070,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"anchorNote": {
|
"anchorNote": {
|
||||||
"name": "Anchor 노트",
|
"name": "",
|
||||||
"description": "Anchor에서 선택한 노트를 표시합니다",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"noteId": {
|
"noteId": {
|
||||||
"label": "노트"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "제목 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showUpdatedAt": {
|
"showUpdatedAt": {
|
||||||
"label": "마지막 업데이트 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"empty": "표시할 노트를 선택하세요",
|
"empty": "",
|
||||||
"emptyContent": "이 노트에는 내용이 없습니다",
|
"emptyContent": "",
|
||||||
"notFound": "노트를 찾을 수 없습니다",
|
"notFound": "",
|
||||||
"noNotes": "노트를 찾을 수 없습니다",
|
"noNotes": "",
|
||||||
"integrationRequired": "먼저 Anchor 통합을 선택하세요",
|
"integrationRequired": "",
|
||||||
"updatedAt": "{date}에 업데이트됨",
|
"updatedAt": "",
|
||||||
"untitled": "제목 없음",
|
"untitled": "",
|
||||||
"edit": "편집",
|
"edit": "",
|
||||||
"save": "저장",
|
"save": "",
|
||||||
"cancel": "취소",
|
"cancel": "",
|
||||||
"readOnlyViewer": "이 노트에 대한 뷰어 권한이 있습니다. 편집이 비활성화되었습니다.",
|
"readOnlyViewer": "",
|
||||||
"saveForbidden": "이 노트를 편집할 권한이 없습니다.",
|
"saveForbidden": "",
|
||||||
"saveFailed": "이 노트를 저장하지 못했습니다. 다시 시도하세요."
|
"saveFailed": ""
|
||||||
},
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "iFrame",
|
"name": "iFrame",
|
||||||
@@ -2455,7 +2455,7 @@
|
|||||||
},
|
},
|
||||||
"dockerContainers": {
|
"dockerContainers": {
|
||||||
"name": "Docker 상태",
|
"name": "Docker 상태",
|
||||||
"description": "컨테이너 통계 (이 위젯은 관리자 권한으로만 추가할 수 있습니다)",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"columns": {
|
"columns": {
|
||||||
"label": "표시할 열"
|
"label": "표시할 열"
|
||||||
@@ -2464,7 +2464,7 @@
|
|||||||
"label": "항목 정렬 활성화"
|
"label": "항목 정렬 활성화"
|
||||||
},
|
},
|
||||||
"defaultSort": {
|
"defaultSort": {
|
||||||
"label": "기본 정렬에 사용되는 열",
|
"label": "",
|
||||||
"option": {
|
"option": {
|
||||||
"name": "이름",
|
"name": "이름",
|
||||||
"state": "상태",
|
"state": "상태",
|
||||||
@@ -2476,52 +2476,52 @@
|
|||||||
"label": "역순 정렬"
|
"label": "역순 정렬"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "열 순서"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "열 너비"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "컨테이너 상태 불러오기 실패",
|
"internalServerError": "컨테이너 상태 불러오기 실패",
|
||||||
"layoutSaveTitle": "테이블 레이아웃이 저장되지 않았습니다",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Docker 테이블 레이아웃을 저장할 수 없습니다. 열을 다시 이동하거나 크기를 조정해 보세요."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "컨테이너 없음"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"name": "Coolify",
|
"name": "",
|
||||||
"description": "서버와 애플리케이션을 포함한 Coolify 인스턴스 개요",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showServers": {
|
"showServers": {
|
||||||
"label": "서버 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showApplications": {
|
"showApplications": {
|
||||||
"label": "애플리케이션 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showServices": {
|
"showServices": {
|
||||||
"label": "서비스 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"servers": "서버를 찾을 수 없습니다",
|
"servers": "",
|
||||||
"applications": "애플리케이션을 찾을 수 없습니다",
|
"applications": "",
|
||||||
"services": "서비스를 찾을 수 없습니다"
|
"services": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"updated": "{when}에 업데이트됨"
|
"updated": ""
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"noIntegration": "구성된 Coolify 통합이 없습니다",
|
"noIntegration": "",
|
||||||
"internalServerError": "Coolify 데이터를 가져오지 못했습니다"
|
"internalServerError": ""
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"buildServer": "빌드"
|
"buildServer": ""
|
||||||
},
|
},
|
||||||
"instances": {
|
"instances": {
|
||||||
"title": "인스턴스"
|
"title": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vpn": {
|
"vpn": {
|
||||||
@@ -2562,85 +2562,85 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tracearr": {
|
"tracearr": {
|
||||||
"name": "Tracearr",
|
"name": "",
|
||||||
"description": "상세 스트림 및 트랜스코딩 데이터로 강화된 미디어 서버 모니터링",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showStreams": {
|
"showStreams": {
|
||||||
"label": "활성 스트림 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showStats": {
|
"showStats": {
|
||||||
"label": "통계 요약 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showRecentActivity": {
|
"showRecentActivity": {
|
||||||
"label": "최근 활동 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showViolations": {
|
"showViolations": {
|
||||||
"label": "위반 사항 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"activeStreams": "활성 스트림",
|
"activeStreams": "",
|
||||||
"totalUsers": "총 사용자",
|
"totalUsers": "",
|
||||||
"transcodes": "트랜스코드",
|
"transcodes": "",
|
||||||
"bandwidth": "대역폭"
|
"bandwidth": ""
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
"empty": "활성 스트림이 없습니다",
|
"empty": "",
|
||||||
"user": "사용자",
|
"user": "",
|
||||||
"media": "미디어",
|
"media": "",
|
||||||
"state": "상태",
|
"state": "",
|
||||||
"quality": "품질",
|
"quality": "",
|
||||||
"device": "장치",
|
"device": "",
|
||||||
"progress": "진행률",
|
"progress": "",
|
||||||
"videoDecision": {
|
"videoDecision": {
|
||||||
"directPlay": "직접 재생",
|
"directPlay": "",
|
||||||
"transcode": "트랜스코딩",
|
"transcode": "",
|
||||||
"directStream": "직접 스트림"
|
"directStream": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recentActivity": {
|
"recentActivity": {
|
||||||
"title": "최근 활동",
|
"title": "",
|
||||||
"empty": "최근 활동이 없습니다",
|
"empty": "",
|
||||||
"watched": "시청함",
|
"watched": "",
|
||||||
"partial": "부분"
|
"partial": ""
|
||||||
},
|
},
|
||||||
"violations": {
|
"violations": {
|
||||||
"title": "위반 사항",
|
"title": "",
|
||||||
"empty": "최근 위반 사항이 없습니다",
|
"empty": "",
|
||||||
"rule": "규칙",
|
"rule": "",
|
||||||
"user": "사용자"
|
"user": ""
|
||||||
},
|
},
|
||||||
"noSectionsEnabled": "활성화된 섹션이 없습니다",
|
"noSectionsEnabled": "",
|
||||||
"error": {
|
"error": {
|
||||||
"noIntegration": "구성된 Tracearr 통합이 없습니다",
|
"noIntegration": "",
|
||||||
"internalServerError": "Tracearr 데이터를 가져오지 못했습니다"
|
"internalServerError": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"umami": {
|
"umami": {
|
||||||
"name": "Umami Analytics",
|
"name": "",
|
||||||
"description": "Umami 분석 인스턴스의 방문자 통계를 표시합니다",
|
"description": "",
|
||||||
"visitors": "방문자",
|
"visitors": "",
|
||||||
"active": "활성",
|
"active": "",
|
||||||
"pageviews": "페이지뷰",
|
"pageviews": "",
|
||||||
"visits": "방문",
|
"visits": "",
|
||||||
"bounceRate": "이탈률",
|
"bounceRate": "",
|
||||||
"avgDuration": "평균",
|
"avgDuration": "",
|
||||||
"option": {
|
"option": {
|
||||||
"websiteId": {
|
"websiteId": {
|
||||||
"label": "웹사이트",
|
"label": "",
|
||||||
"description": "Umami 인스턴스에서 추적하는 웹사이트 선택",
|
"description": "",
|
||||||
"placeholder": "웹사이트 선택",
|
"placeholder": "",
|
||||||
"loading": "웹사이트 불러오는 중...",
|
"loading": "",
|
||||||
"noWebsites": "웹사이트를 찾을 수 없습니다",
|
"noWebsites": "",
|
||||||
"configureIntegrationFirst": "먼저 Umami 통합을 연결하세요"
|
"configureIntegrationFirst": ""
|
||||||
},
|
},
|
||||||
"timeFrame": {
|
"timeFrame": {
|
||||||
"label": "시간 범위",
|
"label": "",
|
||||||
"description": "방문자 데이터를 표시할 기간",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"today": "오늘",
|
"today": "",
|
||||||
"24h": "지난 24시간",
|
"24h": "",
|
||||||
"7d": "지난 7일",
|
"7d": "",
|
||||||
"30d": "지난 30일",
|
"30d": "지난 30일",
|
||||||
"month": "이번 달",
|
"month": "이번 달",
|
||||||
"lastMonth": "지난 달"
|
"lastMonth": "지난 달"
|
||||||
@@ -2784,17 +2784,17 @@
|
|||||||
"description": "비활성화 옵션은 plex에서는 지원되지 않음"
|
"description": "비활성화 옵션은 plex에서는 지원되지 않음"
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "비트레이트 표시",
|
"label": "",
|
||||||
"description": "위치 옆에 현재 재생 비트레이트를 표시합니다"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "위치 표시",
|
"label": "",
|
||||||
"description": "스트림이 LAN 또는 WAN에 있는지 표시합니다"
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "{count, plural, other {# 활성 스트림}}",
|
"streams": "",
|
||||||
"totalBitrate": "총: {bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "현재 재생 중",
|
"currentlyPlaying": "현재 재생 중",
|
||||||
@@ -2803,9 +2803,9 @@
|
|||||||
"id": "아이디",
|
"id": "아이디",
|
||||||
"status": "상태",
|
"status": "상태",
|
||||||
"directPlay": "직접 재생",
|
"directPlay": "직접 재생",
|
||||||
"directStream": "직접 스트림",
|
"directStream": "",
|
||||||
"transcodeVideo": "비디오 트랜스코딩",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "오디오 트랜스코딩",
|
"transcodeAudio": "",
|
||||||
"transcoding": "트랜스코딩",
|
"transcoding": "트랜스코딩",
|
||||||
"paused": "일시 중지됨",
|
"paused": "일시 중지됨",
|
||||||
"remaining": "남은 시간 {minutes}분",
|
"remaining": "남은 시간 {minutes}분",
|
||||||
@@ -3153,18 +3153,18 @@
|
|||||||
"description": "통합당 표시되는 항목 수를 제한하여 전체 항목이 표시되지 않습니다"
|
"description": "통합당 표시되는 항목 수를 제한하여 전체 항목이 표시되지 않습니다"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "열 순서"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "열 너비"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "항목에서 열 선택",
|
"noColumns": "항목에서 열 선택",
|
||||||
"noCommunications": "통합에서 데이터를 불러올 수 없습니다",
|
"noCommunications": "통합에서 데이터를 불러올 수 없습니다",
|
||||||
"noItems": "다운로드 없음",
|
"noItems": "",
|
||||||
"actionFailed": "작업 실패",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "작업을 완료할 수 없습니다. 다시 시도해 주세요."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3188,7 +3188,7 @@
|
|||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"columnTitle": "아이디",
|
"columnTitle": "아이디",
|
||||||
"detailsTitle": "항목 ID"
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"columnTitle": "통합"
|
"columnTitle": "통합"
|
||||||
@@ -3223,7 +3223,7 @@
|
|||||||
"time": {
|
"time": {
|
||||||
"columnTitle": "완료 시간",
|
"columnTitle": "완료 시간",
|
||||||
"detailsTitle": "완료/완료 후 경과 시간",
|
"detailsTitle": "완료/완료 후 경과 시간",
|
||||||
"eta": "남은 시간"
|
"eta": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"columnTitle": "유형",
|
"columnTitle": "유형",
|
||||||
@@ -3269,49 +3269,49 @@
|
|||||||
},
|
},
|
||||||
"globalRatio": "전체 비율",
|
"globalRatio": "전체 비율",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"copyName": "이름 복사",
|
"copyName": "",
|
||||||
"copyId": "ID 복사",
|
"copyId": "",
|
||||||
"clearFilters": "필터 지우기"
|
"clearFilters": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"filter": "다운로드 필터"
|
"filter": ""
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"show": "전체 통계 표시",
|
"show": "",
|
||||||
"hide": "전체 통계 숨기기",
|
"hide": "",
|
||||||
"overallProgress": "전체 대기열 진행률",
|
"overallProgress": "",
|
||||||
"totalSize": "전체 대기열 크기",
|
"totalSize": "",
|
||||||
"clients": "{count, plural, other {# 클라이언트}}"
|
"clients": ""
|
||||||
},
|
},
|
||||||
"disconnected": "(연결 끊김)"
|
"disconnected": ""
|
||||||
},
|
},
|
||||||
"mediaReleases": {
|
"mediaReleases": {
|
||||||
"name": "미디어 릴리스",
|
"name": "",
|
||||||
"description": "다른 통합에서 새로 추가된 미디어나 예정된 릴리스를 표시합니다",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "레이아웃",
|
"label": "",
|
||||||
"option": {
|
"option": {
|
||||||
"backdrop": {
|
"backdrop": {
|
||||||
"label": "배경"
|
"label": ""
|
||||||
},
|
},
|
||||||
"poster": {
|
"poster": {
|
||||||
"label": "포스터"
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showDescriptionTooltip": {
|
"showDescriptionTooltip": {
|
||||||
"label": "설명 도구 설명 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showType": {
|
"showType": {
|
||||||
"label": "미디어 유형 배지 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showSource": {
|
"showSource": {
|
||||||
"label": "소스 통합 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"length": {
|
"length": {
|
||||||
"duration": "{length}분"
|
"duration": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mediaRequests-requestList": {
|
"mediaRequests-requestList": {
|
||||||
@@ -3337,11 +3337,11 @@
|
|||||||
"unknown": "알 수 없음",
|
"unknown": "알 수 없음",
|
||||||
"pending": "대기 중",
|
"pending": "대기 중",
|
||||||
"processing": "처리 중",
|
"processing": "처리 중",
|
||||||
"requested": "요청됨",
|
"requested": "",
|
||||||
"partiallyAvailable": "부분",
|
"partiallyAvailable": "부분",
|
||||||
"available": "사용 가능",
|
"available": "사용 가능",
|
||||||
"blacklisted": "블랙리스트",
|
"blacklisted": "",
|
||||||
"deleted": "삭제됨"
|
"deleted": ""
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"pending": "대기 중",
|
"pending": "대기 중",
|
||||||
@@ -3479,10 +3479,10 @@
|
|||||||
"label": "게시물 수 제한"
|
"label": "게시물 수 제한"
|
||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": "설명 숨기기"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showPosterImage": {
|
"showPosterImage": {
|
||||||
"label": "포스터 이미지 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3506,7 +3506,7 @@
|
|||||||
"label": "세부 정보 표시"
|
"label": "세부 정보 표시"
|
||||||
},
|
},
|
||||||
"showOnlyIcon": {
|
"showOnlyIcon": {
|
||||||
"label": "아이콘만 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"topReleases": {
|
"topReleases": {
|
||||||
"label": "최상위 릴리스",
|
"label": "최상위 릴리스",
|
||||||
@@ -3515,18 +3515,18 @@
|
|||||||
"repositories": {
|
"repositories": {
|
||||||
"label": "저장소",
|
"label": "저장소",
|
||||||
"addRepository": {
|
"addRepository": {
|
||||||
"label": "저장소 추가"
|
"label": ""
|
||||||
},
|
},
|
||||||
"importRepositories": {
|
"importRepositories": {
|
||||||
"label": "Docker에서 가져오기",
|
"label": "",
|
||||||
"loading": "Docker 이미지 불러오는 중",
|
"loading": "",
|
||||||
"noImagesFound": "Docker 이미지를 찾을 수 없습니다",
|
"noImagesFound": "",
|
||||||
"listFoundImages": "발견된 이미지 목록",
|
"listFoundImages": "",
|
||||||
"listAlreadyImportedImages": "이미 가져온 이미지 목록",
|
"listAlreadyImportedImages": "",
|
||||||
"allImagesAlreadyImported": "모든 이미지를 이미 가져왔습니다",
|
"allImagesAlreadyImported": "",
|
||||||
"onlyAdminCanImport": "관리자만 Docker에서 가져올 수 있습니다",
|
"onlyAdminCanImport": "",
|
||||||
"selectAll": "모두 선택",
|
"selectAll": "",
|
||||||
"deselectAll": "모두 선택 해제"
|
"deselectAll": ""
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"label": "제공자"
|
"label": "제공자"
|
||||||
@@ -3572,15 +3572,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"importForm": {
|
"importForm": {
|
||||||
"title": "Docker에서 가져오기"
|
"title": ""
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"label": "예시"
|
"label": "예시"
|
||||||
},
|
},
|
||||||
"invalid": "잘못된 저장소 정의, 값을 확인하십시오",
|
"invalid": "잘못된 저장소 정의, 값을 확인하십시오",
|
||||||
"noProvider": {
|
"noProvider": {
|
||||||
"label": "제공자 없음",
|
"label": "",
|
||||||
"tooltip": "제공자를 파싱할 수 없습니다. 이미지를 가져온 후 수동으로 설정하세요"
|
"tooltip": ""
|
||||||
},
|
},
|
||||||
"tokens": {
|
"tokens": {
|
||||||
"label": "공급자 토큰",
|
"label": "공급자 토큰",
|
||||||
@@ -3598,20 +3598,20 @@
|
|||||||
"starsCount": "별",
|
"starsCount": "별",
|
||||||
"forksCount": "포크",
|
"forksCount": "포크",
|
||||||
"issuesCount": "열린 이슈",
|
"issuesCount": "열린 이슈",
|
||||||
"markViewed": "본 것으로 표시",
|
"markViewed": "",
|
||||||
"openProjectPage": "프로젝트 페이지 열기",
|
"openProjectPage": "프로젝트 페이지 열기",
|
||||||
"openReleasePage": "릴리스 페이지 열기",
|
"openReleasePage": "릴리스 페이지 열기",
|
||||||
"releaseDescription": "릴리스 설명",
|
"releaseDescription": "릴리스 설명",
|
||||||
"projectDescription": "프로젝트 설명",
|
"projectDescription": "",
|
||||||
"created": "생성됨",
|
"created": "생성됨",
|
||||||
"error": {
|
"error": {
|
||||||
"label": "오류",
|
"label": "오류",
|
||||||
"messages": {
|
"messages": {
|
||||||
"invalidIdentifier": "잘못된 식별자",
|
"invalidIdentifier": "",
|
||||||
"noMatchingVersion": "일치하는 버전을 찾을 수 없습니다",
|
"noMatchingVersion": "",
|
||||||
"noReleasesFound": "릴리스를 찾을 수 없습니다",
|
"noReleasesFound": "",
|
||||||
"noProviderSelected": "선택한 공급자가 없습니다",
|
"noProviderSelected": "선택한 공급자가 없습니다",
|
||||||
"noProviderResponse": "제공자로부터 응답이 없습니다",
|
"noProviderResponse": "",
|
||||||
"unexpected": "예기치 않은 공급자 오류"
|
"unexpected": "예기치 않은 공급자 오류"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3673,31 +3673,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"firewall": {
|
"firewall": {
|
||||||
"name": "방화벽 모니터링",
|
"name": "",
|
||||||
"description": "방화벽 요약을 표시합니다",
|
"description": "",
|
||||||
"tab": {
|
"tab": {
|
||||||
"system": "시스템",
|
"system": "",
|
||||||
"interfaces": "인터페이스"
|
"interfaces": ""
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "방화벽에서 데이터를 가져올 수 없습니다"
|
"internalServerError": ""
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"interfaces": "표시할 네트워크 인터페이스"
|
"interfaces": ""
|
||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"fwname": "이름",
|
"fwname": "",
|
||||||
"version": "버전",
|
"version": "",
|
||||||
"versiontitle": "버전",
|
"versiontitle": "",
|
||||||
"cputitle": "CPU 사용량",
|
"cputitle": "",
|
||||||
"memorytitle": "메모리 사용량",
|
"memorytitle": "",
|
||||||
"cpu": "CPU",
|
"cpu": "",
|
||||||
"memory": "메모리",
|
"memory": "",
|
||||||
"interfaces": {
|
"interfaces": {
|
||||||
"name": "이름",
|
"name": "",
|
||||||
"trans": "전송됨",
|
"trans": "",
|
||||||
"recv": "수신됨",
|
"recv": "",
|
||||||
"title": "네트워크 인터페이스"
|
"title": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3712,42 +3712,42 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speedtestTracker": {
|
"speedtestTracker": {
|
||||||
"name": "Speedtest Tracker",
|
"name": "",
|
||||||
"description": "Speedtest Tracker 인스턴스의 속도 테스트 결과를 표시합니다.",
|
"description": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showLatestResult": {
|
"showLatestResult": {
|
||||||
"label": "최신 결과 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showStats": {
|
"showStats": {
|
||||||
"label": "평균 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showRecentResults": {
|
"showRecentResults": {
|
||||||
"label": "최근 결과 표시"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showPingGraph": {
|
"showPingGraph": {
|
||||||
"label": "핑 그래프 표시"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"latestResult": "최신 결과",
|
"latestResult": "",
|
||||||
"averages": "평균",
|
"averages": "",
|
||||||
"tests": "테스트 {count}개",
|
"tests": "",
|
||||||
"recentResults": "최근 결과",
|
"recentResults": "",
|
||||||
"recentResultsSpeed": "속도",
|
"recentResultsSpeed": "",
|
||||||
"recentResultsPing": "핑",
|
"recentResultsPing": "",
|
||||||
"download": "다운로드",
|
"download": "",
|
||||||
"upload": "업로드",
|
"upload": "",
|
||||||
"ping": "핑",
|
"ping": "",
|
||||||
"status": "상태",
|
"status": "",
|
||||||
"avgDownload": "평균 다운로드",
|
"avgDownload": "",
|
||||||
"avgUpload": "평균 업로드",
|
"avgUpload": "",
|
||||||
"avgPing": "평균 핑",
|
"avgPing": "",
|
||||||
"healthy": "정상",
|
"healthy": "",
|
||||||
"unhealthy": "비정상",
|
"unhealthy": "",
|
||||||
"ok": "정상",
|
"ok": "",
|
||||||
"fail": "실패",
|
"fail": "",
|
||||||
"noSectionsEnabled": "위젯 옵션에서 하나 이상의 섹션을 활성화하세요.",
|
"noSectionsEnabled": "",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "속도 테스트 데이터를 가져오지 못했습니다"
|
"internalServerError": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uptimeKuma": {
|
"uptimeKuma": {
|
||||||
@@ -4014,17 +4014,17 @@
|
|||||||
"label": "에이전트 버전 표시"
|
"label": "에이전트 버전 표시"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "열 순서"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "열 너비"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noRecords": "시스템을 찾을 수 없음",
|
"noRecords": "시스템을 찾을 수 없음",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Beszel에 연결할 수 없습니다. 통합 설정을 확인하십시오.",
|
"internalServerError": "Beszel에 연결할 수 없습니다. 통합 설정을 확인하십시오.",
|
||||||
"layoutSaveTitle": "테이블 레이아웃을 저장할 수 없습니다",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "열 순서나 너비를 저장할 수 없습니다. 다시 시도해 주세요."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"column": {
|
"column": {
|
||||||
"system": "시스템",
|
"system": "시스템",
|
||||||
@@ -4656,7 +4656,7 @@
|
|||||||
"authorization": "권한"
|
"authorization": "권한"
|
||||||
},
|
},
|
||||||
"heroBanner": {
|
"heroBanner": {
|
||||||
"title": "다시 오신 것을 환영합니다",
|
"title": "",
|
||||||
"subtitle": "{app} 보드"
|
"subtitle": "{app} 보드"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4922,7 +4922,7 @@
|
|||||||
"title": "사용자",
|
"title": "사용자",
|
||||||
"enableGravatar": {
|
"enableGravatar": {
|
||||||
"label": "Gravatar 활성화",
|
"label": "Gravatar 활성화",
|
||||||
"description": "사용자 지정 아바타가 설정되지 않고 이메일이 구성된 경우 Libravatar/Gravatar의 사용자 아바타로 대체됩니다"
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -4958,7 +4958,7 @@
|
|||||||
"idle": "대기",
|
"idle": "대기",
|
||||||
"running": "실행 중",
|
"running": "실행 중",
|
||||||
"error": "오류",
|
"error": "오류",
|
||||||
"disabled": "비활성화됨"
|
"disabled": ""
|
||||||
},
|
},
|
||||||
"job": {
|
"job": {
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
@@ -5300,8 +5300,8 @@
|
|||||||
"subtitle": "{count}명이 여러 언어로 번역 기여"
|
"subtitle": "{count}명이 여러 언어로 번역 기여"
|
||||||
},
|
},
|
||||||
"sponsors": {
|
"sponsors": {
|
||||||
"title": "스폰서",
|
"title": "",
|
||||||
"subtitle": "재정적으로 지원 중인 {count}명"
|
"subtitle": ""
|
||||||
},
|
},
|
||||||
"libraries": {
|
"libraries": {
|
||||||
"title": "라이브러리",
|
"title": "라이브러리",
|
||||||
@@ -5315,12 +5315,12 @@
|
|||||||
"action": "행동"
|
"action": "행동"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"toggleBoardEdit": "보드 편집 모드 전환",
|
"toggleBoardEdit": "",
|
||||||
"toggleColorScheme": "라이트/다크 모드 전환",
|
"toggleColorScheme": "",
|
||||||
"saveNotebook": "노트북 저장 (노트북 위젯 내에서만)",
|
"saveNotebook": "",
|
||||||
"openSpotlight": "검색 열기"
|
"openSpotlight": ""
|
||||||
},
|
},
|
||||||
"note": "팁: Mod는 Ctrl 키와 macOS의 ⌘ 키를 모두 의미합니다"
|
"note": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5422,7 +5422,7 @@
|
|||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "제거",
|
"label": "제거",
|
||||||
"confirm": "영구적으로 제거하려면 다시 클릭하세요",
|
"confirm": "",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "컨테이너 제거됨",
|
"title": "컨테이너 제거됨",
|
||||||
|
|||||||
+1578
-1578
File diff suppressed because it is too large.
Load diff
+1706
-1706
File diff suppressed because it is too large.
Load diff
@@ -1442,7 +1442,7 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Icoon-URL",
|
"label": "Icoon-URL",
|
||||||
"description": "Zoek in de bibliotheek, plak een afbeelding-URL of upload een bestand.",
|
"description": "Zoek in de bibliotheek, plak een afbeelding-URL of upload een bestand.",
|
||||||
"header": "Zoek {countIcons} afbeeldingen op naam",
|
"header": "Typ naam of objecten om te filteren op iconen… Homarr doorzoekt {countIcons} iconen voor je.",
|
||||||
"headerLoading": "Afbeeldingen zoeken op naam",
|
"headerLoading": "Afbeeldingen zoeken op naam",
|
||||||
"clear": "Verwijder afbeelding",
|
"clear": "Verwijder afbeelding",
|
||||||
"uploadImage": "Afbeelding uploaden",
|
"uploadImage": "Afbeelding uploaden",
|
||||||
@@ -3452,7 +3452,7 @@
|
|||||||
"queued": ""
|
"queued": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"movie": "Film",
|
"movie": "",
|
||||||
"episode": ""
|
"episode": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
|
|||||||
@@ -1809,7 +1809,7 @@
|
|||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "Hva passer opp",
|
"name": "Hva passer opp",
|
||||||
"description": "Vis hvor mange sette containere som er tilgjengelige.",
|
"description": "Vis hvor mange sette containere som er tilgjengelige",
|
||||||
"title": "Hva passer opp",
|
"title": "Hva passer opp",
|
||||||
"monitored": "Overvåket",
|
"monitored": "Overvåket",
|
||||||
"updatesAvailable": "{count, plural, one {# oppdatering tilgjengelig} other {# oppdateringer tilgjengelig}}",
|
"updatesAvailable": "{count, plural, one {# oppdatering tilgjengelig} other {# oppdateringer tilgjengelig}}",
|
||||||
@@ -2801,7 +2801,7 @@
|
|||||||
"user": "Bruker",
|
"user": "Bruker",
|
||||||
"name": "Navn",
|
"name": "Navn",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
"status": "Status",
|
"status": "Status:",
|
||||||
"directPlay": "Direkte avspilling",
|
"directPlay": "Direkte avspilling",
|
||||||
"directStream": "Direkte strøm",
|
"directStream": "Direkte strøm",
|
||||||
"transcodeVideo": "Konverterer video",
|
"transcodeVideo": "Konverterer video",
|
||||||
@@ -2883,7 +2883,7 @@
|
|||||||
"securityUpdates": "Sikkerhetsoppdateringer",
|
"securityUpdates": "Sikkerhetsoppdateringer",
|
||||||
"upToDateHosts": "Oppdaterte verter",
|
"upToDateHosts": "Oppdaterte verter",
|
||||||
"hostsWithSecurityUpdates": "Verter med sikkerhetsoppdateringer",
|
"hostsWithSecurityUpdates": "Verter med sikkerhetsoppdateringer",
|
||||||
"recentUpdates24h": "Oppdateringer (24 timer)",
|
"recentUpdates24h": "Oppdateringer (2,4 timer)",
|
||||||
"totalOutdatedPackages": "Utdaterte pakker",
|
"totalOutdatedPackages": "Utdaterte pakker",
|
||||||
"totalRepos": "Depoter",
|
"totalRepos": "Depoter",
|
||||||
"osDistribution": "OS distribusjon",
|
"osDistribution": "OS distribusjon",
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "Album ID",
|
"label": "Album ID",
|
||||||
"description": "Dette er en unik ID som refererer til ditt Immich-album. Du kan finne denne ID-en ved å åpne et album og kopiere den unike strengen fra URL-en, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "Dette er en unik ID som refererer til ditt Immich-album. Du kan finne denne ID-en ved å åpne et album og kopiere den unike strengen fra URL-en, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Intervall i sekunder",
|
"label": "Intervall i sekunder",
|
||||||
@@ -4034,7 +4034,7 @@
|
|||||||
"gpu": "GPU",
|
"gpu": "GPU",
|
||||||
"loadAvg": "Last snitt",
|
"loadAvg": "Last snitt",
|
||||||
"net": "Netto",
|
"net": "Netto",
|
||||||
"temp": "Temp",
|
"temp": "Temp.",
|
||||||
"battery": "Flaggermus",
|
"battery": "Flaggermus",
|
||||||
"services": "Tjenester",
|
"services": "Tjenester",
|
||||||
"uptime": "Oppetid",
|
"uptime": "Oppetid",
|
||||||
@@ -4095,7 +4095,7 @@
|
|||||||
"gpu": "GPU",
|
"gpu": "GPU",
|
||||||
"loadAvg": "Last snitt",
|
"loadAvg": "Last snitt",
|
||||||
"net": "Netto",
|
"net": "Netto",
|
||||||
"temp": "Temp",
|
"temp": "Temp.",
|
||||||
"battery": "Flaggermus",
|
"battery": "Flaggermus",
|
||||||
"services": "Tjenester",
|
"services": "Tjenester",
|
||||||
"uptime": "Oppetid",
|
"uptime": "Oppetid",
|
||||||
@@ -5126,7 +5126,7 @@
|
|||||||
"availableTools": {
|
"availableTools": {
|
||||||
"title": "Tilgjengelige verktøy",
|
"title": "Tilgjengelige verktøy",
|
||||||
"description": "{count} verktøy tilgjengelig. Utvid en kategori for å se enkelte verktøy og tilhørende beskrivelser.",
|
"description": "{count} verktøy tilgjengelig. Utvid en kategori for å se enkelte verktøy og tilhørende beskrivelser.",
|
||||||
"columnType": "Type",
|
"columnType": "Type:",
|
||||||
"columnName": "Verktøy",
|
"columnName": "Verktøy",
|
||||||
"columnDescription": "Beskrivelse"
|
"columnDescription": "Beskrivelse"
|
||||||
},
|
},
|
||||||
@@ -5134,7 +5134,7 @@
|
|||||||
"title": "Feilsøking",
|
"title": "Feilsøking",
|
||||||
"missingHeader": {
|
"missingHeader": {
|
||||||
"title": "\"Mangler ApiKey header\"",
|
"title": "\"Mangler ApiKey header\"",
|
||||||
"description": "Klienten din sender ikke headeren {header}. Verifiser {section}-delen i MCP-konfigurasjonen din er riktig. Noen klienter krever omstart etter konfigurasjonsendringer."
|
"description": "Klienten din sender ikke headeren {header} . Verifiser {section} -delen i MCP-konfigurasjonen din er riktig. Noen klienter krever omstart etter konfigurasjonsendringer."
|
||||||
},
|
},
|
||||||
"invalidFormat": {
|
"invalidFormat": {
|
||||||
"title": "\"Ugyldig ApiKey format\"",
|
"title": "\"Ugyldig ApiKey format\"",
|
||||||
@@ -6378,7 +6378,7 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"season": "Sesong",
|
"season": "Sesong",
|
||||||
"episodes": "Episoder",
|
"episodes": "Episoder",
|
||||||
"status": "Status"
|
"status": "Status:"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"requested": "Forespurt"
|
"requested": "Forespurt"
|
||||||
|
|||||||
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "adres URL ikony",
|
"label": "adres URL ikony",
|
||||||
"description": "Przeszukaj bibliotekę, wklej adres URL obrazu lub wgraj plik.",
|
"description": "Przeszukaj bibliotekę, wklej adres URL obrazu lub wgraj plik.",
|
||||||
"header": "Szukaj zdjęć {countIcons} według nazwy",
|
"header": "Wpisz nazwę lub obiekty do filtrowania ikon... Homarr przeszuka dla Ciebie ikony {countIcons}.",
|
||||||
"headerLoading": "Szukaj obrazów według nazwy",
|
"headerLoading": "Wpisz, aby wyszukać ikony…",
|
||||||
"clear": "Wyczyść obraz",
|
"clear": "Wyczyść obraz",
|
||||||
"uploadImage": "Prześlij obraz",
|
"uploadImage": "Prześlij obraz",
|
||||||
"results": "Wyniki wyszukiwania obrazów",
|
"results": "Wyniki wyszukiwania obrazów",
|
||||||
@@ -1892,7 +1892,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showToggleAllButtons": {
|
"showToggleAllButtons": {
|
||||||
"label": "Pokaż przyciski „przełącz wszystkie\""
|
"label": "Pokaż \"pokaż wszystkie przyciski\" przycisk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -2312,8 +2312,8 @@
|
|||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "Wschód słońca",
|
"sunrise": "Wschód słońca",
|
||||||
"sunset": "Zachód słońca",
|
"sunset": "Zachód słońca",
|
||||||
"maxWindSpeed": "Maksymalna prędkość wiatru: {maxWindSpeed} {unit}",
|
"maxWindSpeed": "Maksymalna prędkość wiatru: {maxWindSpeed} km/h",
|
||||||
"maxWindGusts": "Maksymalne porywy wiatru: {maxWindGusts} {unit}",
|
"maxWindGusts": "Maksymalne porywy wiatru: {maxWindGusts} km/h",
|
||||||
"humidity": "Wilgotność: {humidity}%"
|
"humidity": "Wilgotność: {humidity}%"
|
||||||
},
|
},
|
||||||
"kind": {
|
"kind": {
|
||||||
@@ -2348,7 +2348,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"healthMonitoring": {
|
"healthMonitoring": {
|
||||||
"name": "Monitorowanie zdrowia systemu",
|
"name": "Monitorowanie zdrowia systemu",
|
||||||
"description": "Wyświetla informacje o stanie Twojego systemu(ów).",
|
"description": "Wyświetla informacje o stanie Twojego systemu(ów).",
|
||||||
"tab": {
|
"tab": {
|
||||||
"system": "System",
|
"system": "System",
|
||||||
@@ -2395,7 +2395,7 @@
|
|||||||
"memoryAvailable": "Dostępna pamięć {memoryAvailable} GB ({percent}%)",
|
"memoryAvailable": "Dostępna pamięć {memoryAvailable} GB ({percent}%)",
|
||||||
"version": "Wersja: {version}",
|
"version": "Wersja: {version}",
|
||||||
"uptime": "Czas pracy: {months} mies., {days} dni, {hours} godzin, {minutes} minut",
|
"uptime": "Czas pracy: {months} mies., {days} dni, {hours} godzin, {minutes} minut",
|
||||||
"loadAverage": "Średnie obciążenie:",
|
"loadAverage": "Średnie obciążenie",
|
||||||
"minute": "1 minuta",
|
"minute": "1 minuta",
|
||||||
"minutes": "{count} minut",
|
"minutes": "{count} minut",
|
||||||
"used": "Używane",
|
"used": "Używane",
|
||||||
@@ -2719,7 +2719,7 @@
|
|||||||
"query": "Miasto / kod pocztowy",
|
"query": "Miasto / kod pocztowy",
|
||||||
"latitude": "Szerokość geograficzna (Latitude)",
|
"latitude": "Szerokość geograficzna (Latitude)",
|
||||||
"longitude": "Długość geograficzna (Longitude)",
|
"longitude": "Długość geograficzna (Longitude)",
|
||||||
"disabledTooltip": "Podaj miasto lub kod pocztowy",
|
"disabledTooltip": "Podaj miasto lub kod pocztowy.",
|
||||||
"unknownLocation": "Nieznana lokalizacja",
|
"unknownLocation": "Nieznana lokalizacja",
|
||||||
"search": "Szukaj",
|
"search": "Szukaj",
|
||||||
"table": {
|
"table": {
|
||||||
@@ -3150,7 +3150,7 @@
|
|||||||
},
|
},
|
||||||
"limitPerIntegration": {
|
"limitPerIntegration": {
|
||||||
"label": "Limit elementów na integrację",
|
"label": "Limit elementów na integrację",
|
||||||
"description": "To ograniczy liczbę wyświetlanych elementów dla każdej integracji, a nie globalnie"
|
"description": "To ograniczy liczbę wyświetlanych elementów dla każdej integracji, a nie globalnie."
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Kolejność kolumn"
|
"label": "Kolejność kolumn"
|
||||||
@@ -3496,11 +3496,11 @@
|
|||||||
},
|
},
|
||||||
"staleReleaseWithin": {
|
"staleReleaseWithin": {
|
||||||
"label": "Nieaktualne wydanie w ciągu",
|
"label": "Nieaktualne wydanie w ciągu",
|
||||||
"description": "Przykład użycia: 1w (1 tydzień), 10M (10 miesięcy). Obsługiwane jednostki: h (godziny), d (dni), w (tygodnie), M (miesiące), y (lata). Pozostaw puste, aby nie wyróżniać nieaktualnych wydań."
|
"description": "Przykład użycia: 1w (1 tydzień), 10M (10 miesięcy). Obsługiwane jednostki: h (godziny), d (dni), w (tygodnie), M (miesiące), y (lata). Pozostaw puste, aby nie wyróżniać nieaktualnych wydań"
|
||||||
},
|
},
|
||||||
"showOnlyHighlighted": {
|
"showOnlyHighlighted": {
|
||||||
"label": "Pokaż tylko wyróżnione",
|
"label": "Pokaż tylko wyróżnione",
|
||||||
"description": "„Pokaż tylko nowe lub nieaktualne wydania. Zgodnie z powyższymi ustawieniami.”."
|
"description": "„Pokaż tylko nowe lub nieaktualne wydania. Zgodnie z powyższymi ustawieniami.”"
|
||||||
},
|
},
|
||||||
"showDetails": {
|
"showDetails": {
|
||||||
"label": "Pokaż szczegóły"
|
"label": "Pokaż szczegóły"
|
||||||
@@ -3580,7 +3580,7 @@
|
|||||||
"invalid": "Nieprawidłowa definicja repozytorium, sprawdź wartości",
|
"invalid": "Nieprawidłowa definicja repozytorium, sprawdź wartości",
|
||||||
"noProvider": {
|
"noProvider": {
|
||||||
"label": "Brak dostawcy",
|
"label": "Brak dostawcy",
|
||||||
"tooltip": "Nie udało się rozpoznać dostawcy. Ustaw go ręcznie po zaimportowaniu obrazów"
|
"tooltip": "Nie udało się rozpoznać dostawcy. Ustaw go ręcznie po zaimportowaniu obrazów."
|
||||||
},
|
},
|
||||||
"tokens": {
|
"tokens": {
|
||||||
"label": "Tokeny dostawcy",
|
"label": "Tokeny dostawcy",
|
||||||
@@ -3609,7 +3609,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"invalidIdentifier": "Nieprawidłowy identyfikator",
|
"invalidIdentifier": "Nieprawidłowy identyfikator",
|
||||||
"noMatchingVersion": "Nie znaleziono pasującej wersji",
|
"noMatchingVersion": "Nie znaleziono pasującej wersji",
|
||||||
"noReleasesFound": "Nie znaleziono wydań",
|
"noReleasesFound": "Nie znaleziono wydań!",
|
||||||
"noProviderSelected": "Nie wybrano dostawcy",
|
"noProviderSelected": "Nie wybrano dostawcy",
|
||||||
"noProviderResponse": "Brak odpowiedzi od dostawcy",
|
"noProviderResponse": "Brak odpowiedzi od dostawcy",
|
||||||
"unexpected": "Nieoczekiwany błąd dostawcy"
|
"unexpected": "Nieoczekiwany błąd dostawcy"
|
||||||
@@ -3625,10 +3625,10 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"integrationsDisconnected": "Brak dostępnych danych, wszystkie integracje rozłączone",
|
"integrationsDisconnected": "Brak dostępnych danych, wszystkie integracje rozłączone",
|
||||||
"unknownContentOption": "Nieznana opcja zawartości dla widżetu podsumowania kontrolera sieci: "
|
"unknownContentOption": "Nieznana opcja zawartości dla widżetu podsumowania kontrolera sieci:"
|
||||||
},
|
},
|
||||||
"name": "Podsumowanie kontrolera sieci",
|
"name": "Podsumowanie kontrolera sieci",
|
||||||
"description": "Wyświetla podsumowanie kontrolera sieci (np. UniFi Controller)"
|
"description": "Wyświetla podsumowanie kontrolera sieci (np. UniFi Controller)."
|
||||||
},
|
},
|
||||||
"networkControllerStatus": {
|
"networkControllerStatus": {
|
||||||
"card": {
|
"card": {
|
||||||
@@ -3662,7 +3662,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"integrationsDisconnected": "Brak dostępnych danych, wszystkie integracje są rozłączone",
|
"integrationsDisconnected": "Brak dostępnych danych, wszystkie integracje są rozłączone",
|
||||||
"unknownContentOption": "Nieznana opcja zawartości dla widżetu statusu sieci: "
|
"unknownContentOption": "Nieznana opcja zawartości dla widżetu statusu sieci:"
|
||||||
},
|
},
|
||||||
"name": "Stan sieci",
|
"name": "Stan sieci",
|
||||||
"description": "Wyświetl podłączone urządzenia w sieci"
|
"description": "Wyświetl podłączone urządzenia w sieci"
|
||||||
@@ -3713,7 +3713,7 @@
|
|||||||
},
|
},
|
||||||
"speedtestTracker": {
|
"speedtestTracker": {
|
||||||
"name": "Speedtest Tracker",
|
"name": "Speedtest Tracker",
|
||||||
"description": "Wyświetla wyniki testów prędkości z Twojej instancji Speedtest Tracker.",
|
"description": "Wyświetla wyniki testów prędkości z Twojej instancji Speedtest Tracker",
|
||||||
"option": {
|
"option": {
|
||||||
"showLatestResult": {
|
"showLatestResult": {
|
||||||
"label": "Pokaż ostatni wynik"
|
"label": "Pokaż ostatni wynik"
|
||||||
@@ -3745,7 +3745,7 @@
|
|||||||
"unhealthy": "Zły",
|
"unhealthy": "Zły",
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
"fail": "Błąd",
|
"fail": "Błąd",
|
||||||
"noSectionsEnabled": "Włącz co najmniej jedną sekcję w opcjach widgetu.",
|
"noSectionsEnabled": "Włącz co najmniej jedną sekcję w opcjach widgetu",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Nie udało się pobrać danych testu prędkości"
|
"internalServerError": "Nie udało się pobrać danych testu prędkości"
|
||||||
}
|
}
|
||||||
@@ -4232,7 +4232,7 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"duplicate": {
|
"duplicate": {
|
||||||
"title": "Duplikuj tablicę",
|
"title": "Duplikuj tablicę",
|
||||||
"message": "Spowoduje to zduplikowanie tablicy {name} wraz ze wszystkimi jej treściami. Jeśli widżety odwołują się do integracji, których nie możesz używać, zostaną usunięte.",
|
"message": "Spowoduje to zduplikowanie tablicy {name} wraz ze wszystkimi jej treściami. Jeśli widżety odwołują się do integracji, których nie możesz używać, zostaną usunięte",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Tablica została zduplikowana",
|
"title": "Tablica została zduplikowana",
|
||||||
@@ -4269,7 +4269,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Import nie powiódł się",
|
"title": "Import nie powiódł się",
|
||||||
"message": "Nie udało się zaimportować tablicy. Sprawdź logi, aby uzyskać więcej informacji"
|
"message": "Nie udało się zaimportować tablicy. Sprawdź logi, aby uzyskać więcej informacji."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@@ -4281,11 +4281,11 @@
|
|||||||
"label": "Aplikacje",
|
"label": "Aplikacje",
|
||||||
"avoidDuplicates": {
|
"avoidDuplicates": {
|
||||||
"label": "Unikaj duplikatów",
|
"label": "Unikaj duplikatów",
|
||||||
"description": "Ignoruje aplikacje, dla których już istnieje aplikacja z tym samym adresem href"
|
"description": "Ignoruje aplikacje, dla których już istnieje aplikacja z tym samym adresem href."
|
||||||
},
|
},
|
||||||
"onlyImportApps": {
|
"onlyImportApps": {
|
||||||
"label": "Import tylko aplikacje",
|
"label": "Import tylko aplikacje",
|
||||||
"description": "Dodaje tylko aplikacje, tablica musi zostać odtworzona ręcznie"
|
"description": "Dodaje tylko aplikacje, tablica musi zostać odtworzona ręcznie."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
@@ -4416,14 +4416,14 @@
|
|||||||
},
|
},
|
||||||
"disableStatus": {
|
"disableStatus": {
|
||||||
"label": "Wyłącz status aplikacji",
|
"label": "Wyłącz status aplikacji",
|
||||||
"description": "Wyłącza sprawdzanie statusu wszystkich aplikacji na tej tablicy"
|
"description": "Wyłącza sprawdzanie statusu wszystkich aplikacji na tej tablicy."
|
||||||
},
|
},
|
||||||
"columnCount": {
|
"columnCount": {
|
||||||
"label": "Liczba kolumn"
|
"label": "Liczba kolumn"
|
||||||
},
|
},
|
||||||
"itemRadius": {
|
"itemRadius": {
|
||||||
"label": "Promień elementu",
|
"label": "Promień elementu",
|
||||||
"description": "Zmienia zaokrąglenie kafelków na Twojej tablicy",
|
"description": "Zmienia zaokrąglenie kafelków na Twojej tablicy.",
|
||||||
"option": {
|
"option": {
|
||||||
"xs": "Bardzo mały",
|
"xs": "Bardzo mały",
|
||||||
"sm": "Mały",
|
"sm": "Mały",
|
||||||
@@ -4537,11 +4537,11 @@
|
|||||||
"title": "Witamy w Homarr",
|
"title": "Witamy w Homarr",
|
||||||
"description": "Elegancki, nowoczesny pulpit nawigacyjny, który daje Ci dostęp do wszystkich Twoich aplikacji i usług w zasięgu ręki.",
|
"description": "Elegancki, nowoczesny pulpit nawigacyjny, który daje Ci dostęp do wszystkich Twoich aplikacji i usług w zasięgu ręki.",
|
||||||
"link": "Utwórz swoją pierwszą tablicę",
|
"link": "Utwórz swoją pierwszą tablicę",
|
||||||
"notice": "Aby ta strona zniknęła, utwórz tablicę i ustaw ją jako tablicę główną"
|
"notice": "Aby ta strona zniknęła, utwórz tablicę i ustaw ją jako tablicę główną."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Tablica nie znaleziona",
|
"title": "Tablica nie znaleziona",
|
||||||
"description": "Wskazana tablica nie została znaleziona lub nie masz do niej dostępu.”.",
|
"description": "Wskazana tablica nie została znaleziona lub nie masz do niej dostępu.”",
|
||||||
"link": "Wyświetl wszystkie tablice",
|
"link": "Wyświetl wszystkie tablice",
|
||||||
"notice": "Sprawdź link lub skontaktuj się z administratorem, jeśli uważasz, że powinien być dostępny"
|
"notice": "Sprawdź link lub skontaktuj się z administratorem, jeśli uważasz, że powinien być dostępny"
|
||||||
},
|
},
|
||||||
@@ -4550,7 +4550,7 @@
|
|||||||
"admin": {
|
"admin": {
|
||||||
"description": "Nie ustawiłeś jeszcze tablicy głównej dla serwera.",
|
"description": "Nie ustawiłeś jeszcze tablicy głównej dla serwera.",
|
||||||
"link": "Skonfiguruj tablicę główną dla całego serwera",
|
"link": "Skonfiguruj tablicę główną dla całego serwera",
|
||||||
"notice": "Aby ta strona zniknęła dla wszystkich użytkowników, ustaw tablicę główną dla serwera"
|
"notice": "Aby ta strona zniknęła dla wszystkich użytkowników, ustaw tablicę główną dla serwera\n\n"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Nie ustawiłeś jeszcze tablicy głównej.",
|
"description": "Nie ustawiłeś jeszcze tablicy głównej.",
|
||||||
@@ -4560,7 +4560,7 @@
|
|||||||
"anonymous": {
|
"anonymous": {
|
||||||
"description": "Administrator serwera nie ustawił jeszcze tablicy głównej.",
|
"description": "Administrator serwera nie ustawił jeszcze tablicy głównej.",
|
||||||
"link": "Pokaż tablice publiczne",
|
"link": "Pokaż tablice publiczne",
|
||||||
"notice": "Aby ta strona zniknęła, poproś administratora serwera o ustawienie tablicy głównej dla serwera"
|
"notice": "Aby ta strona zniknęła, poproś administratora serwera o ustawienie tablicy głównej dla serwera."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4588,7 +4588,7 @@
|
|||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Nie znaleziono",
|
"title": "Nie znaleziono",
|
||||||
"text": "Nie znaleziono żądanego zasobu"
|
"text": "Nie znaleziono żądanego zasobu."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Nie można załadować tej strony",
|
"title": "Nie można załadować tej strony",
|
||||||
@@ -4719,7 +4719,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"back": "Powrót do użytkowników",
|
"back": "Powrót do użytkowników",
|
||||||
"fieldsDisabledExternalProvider": "Niektóre pola są zablokowane, ponieważ są zarządzane przez zewnętrznego dostawcę uwierzytelniania.",
|
"fieldsDisabledExternalProvider": "Niektóre pola są zablokowane, ponieważ są zarządzane przez zewnętrznego dostawcę uwierzytelniania",
|
||||||
"setting": {
|
"setting": {
|
||||||
"general": {
|
"general": {
|
||||||
"title": "Ogólne",
|
"title": "Ogólne",
|
||||||
|
|||||||
@@ -682,7 +682,7 @@
|
|||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Criar aplicativo",
|
"title": "Criar aplicativo",
|
||||||
"description": "Criar um novo aplicativo ",
|
"description": "Criar um aplicativo",
|
||||||
"action": "Abrir criação de aplicativo"
|
"action": "Abrir criação de aplicativo"
|
||||||
},
|
},
|
||||||
"add": "Adicione um aplicativo"
|
"add": "Adicione um aplicativo"
|
||||||
@@ -769,7 +769,7 @@
|
|||||||
"enabled": "Habilitado",
|
"enabled": "Habilitado",
|
||||||
"template": {
|
"template": {
|
||||||
"label": "Template JSX",
|
"label": "Template JSX",
|
||||||
"description": "Modelo JSX do Mantine. Acesse os campos da resposta da API por meio de ligações de dados (por exemplo, data.name). Use apenas componentes da lista de permissões. Proibido: constructor, __proto__, eval, Function, window, document."
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
@@ -864,8 +864,8 @@
|
|||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"created": "Widget \"{name}\" criado com sucesso.",
|
"created": "Widget \"{name}\" criado com sucesso.",
|
||||||
"updated": "Widget \"{name}\" atualizado com sucesso.",
|
"updated": "Widget \"{name}atualizado com sucesso.",
|
||||||
"deleted": "Widget \"{name}\" excluído.",
|
"deleted": "Widget \"{name}excluído.",
|
||||||
"imported": "Widget importado com sucesso.",
|
"imported": "Widget importado com sucesso.",
|
||||||
"aiPromptCopied": "Prompt de IA copiado para a área de transferência",
|
"aiPromptCopied": "Prompt de IA copiado para a área de transferência",
|
||||||
"aiPromptDescription": "Copie um prompt pré-construído para colar qualquer chatbot de AI (ChatGPT, Claude, Gemini...). Ele inclui o esquema completo de widget e a sua resposta de API para que a AI possa gerar uma configuração JSON pronta para importar para você.",
|
"aiPromptDescription": "Copie um prompt pré-construído para colar qualquer chatbot de AI (ChatGPT, Claude, Gemini...). Ele inclui o esquema completo de widget e a sua resposta de API para que a AI possa gerar uma configuração JSON pronta para importar para você.",
|
||||||
@@ -876,7 +876,7 @@
|
|||||||
"updateError": "Falha ao atualizar o widget. Suas alterações não foram salvas.",
|
"updateError": "Falha ao atualizar o widget. Suas alterações não foram salvas.",
|
||||||
"importError": "Falha ao importar o widget. Certifique-se de que o arquivo seja um widget personalizado válido exportado em JSON.",
|
"importError": "Falha ao importar o widget. Certifique-se de que o arquivo seja um widget personalizado válido exportado em JSON.",
|
||||||
"exportError": "Falha ao exportar o widget.",
|
"exportError": "Falha ao exportar o widget.",
|
||||||
"duplicated": "Widget \"{name}\" duplicado.",
|
"duplicated": "Widget \"{name}duplicado.",
|
||||||
"duplicateError": "Falha ao duplicar o widget.",
|
"duplicateError": "Falha ao duplicar o widget.",
|
||||||
"toggleError": "Falha ao alternar o status do widget.",
|
"toggleError": "Falha ao alternar o status do widget.",
|
||||||
"deleteError": "Falha ao excluir o widget.",
|
"deleteError": "Falha ao excluir o widget.",
|
||||||
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "URL do ícone",
|
"label": "URL do ícone",
|
||||||
"description": "Procure na biblioteca, cole uma URL da imagem ou envie um arquivo.",
|
"description": "Procure na biblioteca, cole uma URL da imagem ou envie um arquivo.",
|
||||||
"header": "Digite o nome ou os objetos para filtrar os ícones... O Homarr pesquisará {countIcons} ícones para você",
|
"header": "Digite o nome ou os objetos para filtrar os ícones... O Homarr pesquisará {countIcons} ícones para você.",
|
||||||
"headerLoading": "Digite para pesquisar ícones",
|
"headerLoading": "Digite para pesquisar ícones…",
|
||||||
"clear": "Limpar imagem",
|
"clear": "Limpar imagem",
|
||||||
"uploadImage": "Upload de imagem",
|
"uploadImage": "Upload de imagem",
|
||||||
"results": "Resultados da busca por imagem",
|
"results": "Resultados da busca por imagem",
|
||||||
@@ -2563,7 +2563,7 @@
|
|||||||
},
|
},
|
||||||
"tracearr": {
|
"tracearr": {
|
||||||
"name": "Tracearr",
|
"name": "Tracearr",
|
||||||
"description": "Monitoramento avançado de servidor de mídia com dados detalhados de transmissão e transcodificação",
|
"description": "Monitoramento avançado de servidor de mídia com dados detalhados de transmissão e transcodificação\n",
|
||||||
"option": {
|
"option": {
|
||||||
"showStreams": {
|
"showStreams": {
|
||||||
"label": "Exibir transmissões ativas"
|
"label": "Exibir transmissões ativas"
|
||||||
@@ -2739,7 +2739,7 @@
|
|||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"noData": "Nenhuma integração encontrada",
|
"noData": "Nenhuma integração encontrada",
|
||||||
"description": "Clique <here></here> para criar uma nova integração"
|
"description": "Clique <here></here< para criar uma nova integração"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"noData": "Nenhum aplicativo encontrado",
|
"noData": "Nenhum aplicativo encontrado",
|
||||||
@@ -2772,7 +2772,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"noUrl": "Nenhum URL de vídeo fornecido",
|
"noUrl": "Nenhum URL de vídeo fornecido",
|
||||||
"forYoutubeUseIframe": "Para vídeos do Youtube, use a opção de iframe"
|
"forYoutubeUseIframe": "Para vídeos do Youtube, use a opção de iFrame"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mediaServer": {
|
"mediaServer": {
|
||||||
@@ -3093,7 +3093,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "ID do Álbum",
|
"label": "ID do Álbum",
|
||||||
"description": "Esta é uma identificação única que se refere ao seu álbum Immich. Você pode encontrar essa identificação abrindo um álbum e copiando a string única da URL, por exemplo 'a21aca09-9f05-47dc-b1a1-7b53c547226e'."
|
"description": "Esta é uma identificação única que se refere ao seu álbum Immich. Você pode encontrar essa identificação abrindo um álbum e copiando a string única da URL, por exemplo 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "Intervalo em segundos",
|
"label": "Intervalo em segundos",
|
||||||
|
|||||||
+1549
-1549
File diff suppressed because it is too large.
Load diff
@@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tokenModal": {
|
"tokenModal": {
|
||||||
"title": "Introdu codul de import",
|
"title": "Introdu token-ul de import",
|
||||||
"field": {
|
"field": {
|
||||||
"token": {
|
"token": {
|
||||||
"label": "Token-ul",
|
"label": "Token-ul",
|
||||||
@@ -540,7 +540,7 @@
|
|||||||
"confirm": "Sunteţi sigur că doriţi să ştergeţi grupul {name}?",
|
"confirm": "Sunteţi sigur că doriţi să ştergeţi grupul {name}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": "Grupul {name} a fost șters cu succes"
|
"message": ""
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": "Imposibil de șters grupul {name}"
|
"message": "Imposibil de șters grupul {name}"
|
||||||
@@ -769,7 +769,7 @@
|
|||||||
"enabled": "Activat",
|
"enabled": "Activat",
|
||||||
"template": {
|
"template": {
|
||||||
"label": "Şablon JSX",
|
"label": "Şablon JSX",
|
||||||
"description": "Șablon Mantine JSX. Accesați câmpurile răspunsului API prin legături de date (de ex. data.name). Utilizați numai componente permise. Interzis: constructor, __proto__, eval, Function, window, document."
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
@@ -1507,7 +1507,7 @@
|
|||||||
"switchToLightMode": "Comutați la modul lumină",
|
"switchToLightMode": "Comutați la modul lumină",
|
||||||
"management": "Gestionare",
|
"management": "Gestionare",
|
||||||
"docker": "Doctor",
|
"docker": "Doctor",
|
||||||
"preferences": "Preferințele dvs",
|
"preferences": "Preferințele dvs.",
|
||||||
"logout": "Deconectare",
|
"logout": "Deconectare",
|
||||||
"login": "Utilizator",
|
"login": "Utilizator",
|
||||||
"homeBoard": "Tabloul de bord",
|
"homeBoard": "Tabloul de bord",
|
||||||
@@ -1875,7 +1875,7 @@
|
|||||||
},
|
},
|
||||||
"dnsHoleControls": {
|
"dnsHoleControls": {
|
||||||
"name": "Controale DNS",
|
"name": "Controale DNS",
|
||||||
"description": "Controlați PiHole sau AdGuard din planșa dvs",
|
"description": "Controlați PiHole sau AdGuard din planșa dvs.",
|
||||||
"option": {
|
"option": {
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "Aspect",
|
"label": "Aspect",
|
||||||
@@ -4035,7 +4035,7 @@
|
|||||||
"loadAvg": "Încărcare medie",
|
"loadAvg": "Încărcare medie",
|
||||||
"net": "Rețea",
|
"net": "Rețea",
|
||||||
"temp": "Temp",
|
"temp": "Temp",
|
||||||
"battery": "Bat",
|
"battery": "Bat.",
|
||||||
"services": "Servicii",
|
"services": "Servicii",
|
||||||
"uptime": "Uptime",
|
"uptime": "Uptime",
|
||||||
"agent": "Agent"
|
"agent": "Agent"
|
||||||
@@ -4096,7 +4096,7 @@
|
|||||||
"loadAvg": "Încărcare medie",
|
"loadAvg": "Încărcare medie",
|
||||||
"net": "Rețea",
|
"net": "Rețea",
|
||||||
"temp": "Temp",
|
"temp": "Temp",
|
||||||
"battery": "Bat",
|
"battery": "Bat.",
|
||||||
"services": "Servicii",
|
"services": "Servicii",
|
||||||
"uptime": "Uptime",
|
"uptime": "Uptime",
|
||||||
"agent": "Agent"
|
"agent": "Agent"
|
||||||
@@ -4334,7 +4334,7 @@
|
|||||||
"label": "URL imagine logo"
|
"label": "URL imagine logo"
|
||||||
},
|
},
|
||||||
"faviconImageUrl": {
|
"faviconImageUrl": {
|
||||||
"label": "URL imagine favicon"
|
"label": "URL imagine fichar@@0icon"
|
||||||
},
|
},
|
||||||
"backgroundImageUrl": {
|
"backgroundImageUrl": {
|
||||||
"label": "URL-ul imaginii de fundal",
|
"label": "URL-ul imaginii de fundal",
|
||||||
@@ -5134,7 +5134,7 @@
|
|||||||
"title": "Depanare",
|
"title": "Depanare",
|
||||||
"missingHeader": {
|
"missingHeader": {
|
||||||
"title": "\"Lipsă header ApiKey",
|
"title": "\"Lipsă header ApiKey",
|
||||||
"description": "Clientul dvs. nu trimite headerul {header}. Verificați dacă secțiunea {section} din configurația MCP este corectă. Unii clienți necesită repornirea după modificarea configurării."
|
"description": "Clientul dvs. nu trimite headerul {header} . Verificați dacă secțiunea {section} din configurația MCP este corectă. Unii clienți necesită repornirea după modificarea configurării."
|
||||||
},
|
},
|
||||||
"invalidFormat": {
|
"invalidFormat": {
|
||||||
"title": "\"Format ApiKey nevalid\"",
|
"title": "\"Format ApiKey nevalid\"",
|
||||||
@@ -6255,7 +6255,7 @@
|
|||||||
"label": "Tablou principal"
|
"label": "Tablou principal"
|
||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Preferințele dvs"
|
"label": "Preferințele dvs."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1346,7 +1346,7 @@
|
|||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "What's Up Docker",
|
||||||
"description": "Sledujte, ktoré z vašich Docker kontajnerov majú k dispozícii aktualizácie obrazov"
|
"description": "Sledujte, ktoré z vašich Docker kontajnerov majú k dispozícii aktualizácie obrazov."
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "Paperless-ngx",
|
||||||
@@ -1442,7 +1442,7 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "URL ikony",
|
"label": "URL ikony",
|
||||||
"description": "Vyhľadajte v knižnici, prilepte URL obrázka alebo nahrajte súbor.",
|
"description": "Vyhľadajte v knižnici, prilepte URL obrázka alebo nahrajte súbor.",
|
||||||
"header": "Zadajte názov alebo objekty na filtrovanie ikon... Homarr pre vás vyhľadá ikony na stránke {countIcons} ",
|
"header": "Zadajte názov alebo objekty na filtrovanie ikon... Homarr pre vás vyhľadá ikony na stránke {countIcons} .",
|
||||||
"headerLoading": "Vyhľadať obrázky podľa názvu",
|
"headerLoading": "Vyhľadať obrázky podľa názvu",
|
||||||
"clear": "Odstrániť obrázok",
|
"clear": "Odstrániť obrázok",
|
||||||
"uploadImage": "Nahrať obrázok",
|
"uploadImage": "Nahrať obrázok",
|
||||||
@@ -1835,7 +1835,7 @@
|
|||||||
},
|
},
|
||||||
"showUpdateList": {
|
"showUpdateList": {
|
||||||
"label": "Zobraziť zoznam aktualizácií kontajnerov",
|
"label": "Zobraziť zoznam aktualizácií kontajnerov",
|
||||||
"description": "Uvádza každý sledovaný kontajner, ktorý má dostupnú aktualizáciu obrazu"
|
"description": "Uvádza každý sledovaný kontajner, ktorý má dostupnú aktualizáciu obrazu."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2308,12 +2308,12 @@
|
|||||||
"description": "Ako by mal dátum vyzerať"
|
"description": "Ako by mal dátum vyzerať"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currentWindSpeed": "{currentWindSpeed} {unit}",
|
"currentWindSpeed": "{currentWindSpeed} km/h",
|
||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "Východ slnka",
|
"sunrise": "Východ slnka",
|
||||||
"sunset": "Západ slnka",
|
"sunset": "Západ slnka",
|
||||||
"maxWindSpeed": "Maximálna rýchlosť vetra: {maxWindSpeed} {unit}",
|
"maxWindSpeed": "Maximálna rýchlosť vetra: {maxWindSpeed} km/h",
|
||||||
"maxWindGusts": "Maximálne nárazy vetra: {maxWindGusts} {unit}",
|
"maxWindGusts": "Maximálne nárazy vetra: {maxWindGusts} km/h",
|
||||||
"humidity": ""
|
"humidity": ""
|
||||||
},
|
},
|
||||||
"kind": {
|
"kind": {
|
||||||
|
|||||||
+1886
-1886
File diff suppressed because it is too large.
Load diff
+1752
-1752
File diff suppressed because it is too large.
Load diff
@@ -1346,7 +1346,7 @@
|
|||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "What's Up Docker",
|
||||||
"description": "Docker konteynerlerinizden hangilerinde imaj güncellemeleri olduğunu izleyin"
|
"description": "Docker konteynerlerinizden hangilerinde imaj güncellemeleri olduğunu izleyin."
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "Paperless-ngx",
|
||||||
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Simge URL'si",
|
"label": "Simge URL'si",
|
||||||
"description": "Kütüphanede arayın, bir görsel URL'si yapıştırın veya bir dosya yükleyin.",
|
"description": "Kütüphanede arayın, bir görsel URL'si yapıştırın veya bir dosya yükleyin.",
|
||||||
"header": "Simgeleri filtrelemek için isim veya nesne yazın... Homarr sizin için {countIcons} simge arasında arama yapacaktır",
|
"header": "Simgeleri filtrelemek için isim veya nesne yazın... Homarr sizin için {countIcons} simge arasında arama yapacaktır.",
|
||||||
"headerLoading": "Simge aramak için yazın",
|
"headerLoading": "Simge aramak için yazın…",
|
||||||
"clear": "Görseli temizle",
|
"clear": "Görseli temizle",
|
||||||
"uploadImage": "Görsel yükle",
|
"uploadImage": "Görsel yükle",
|
||||||
"results": "Görsel arama sonuçları",
|
"results": "Görsel arama sonuçları",
|
||||||
|
|||||||
@@ -682,7 +682,7 @@
|
|||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Створити новий додаток",
|
"title": "Створити новий додаток",
|
||||||
"description": "Створити новий додаток ",
|
"description": "Створити новий додаток",
|
||||||
"action": "Відкрийте створення додатку"
|
"action": "Відкрийте створення додатку"
|
||||||
},
|
},
|
||||||
"add": "Додати додаток"
|
"add": "Додати додаток"
|
||||||
@@ -868,7 +868,7 @@
|
|||||||
"deleted": "Віджет \"{name}\" видалено.",
|
"deleted": "Віджет \"{name}\" видалено.",
|
||||||
"imported": "Віджет успішно імпортовано.",
|
"imported": "Віджет успішно імпортовано.",
|
||||||
"aiPromptCopied": "Запит ШІ скопійовано до буферу обміну",
|
"aiPromptCopied": "Запит ШІ скопійовано до буферу обміну",
|
||||||
"aiPromptDescription": "Скопіюйте запит на вставку в будь-який AI чатбот (ChatGPT, Claude, Gemini...). Вона включає в себе повну схему віджетів і вашу API відповідь, щоб ШІ могла генерувати конфігурацію AI для вашого коректного імпортування.",
|
"aiPromptDescription": "Скопіюйте запит на вставку в будь-який AI чатбот (ChatGPT, Claude, Gemini ...). Вона включає в себе повну схему віджетів і вашу API відповідь, щоб ШІ могла генерувати конфігурацію AI для вашого коректного імпортування.",
|
||||||
"aiPromptCopy": "Скопіювати в буфер обміну",
|
"aiPromptCopy": "Скопіювати в буфер обміну",
|
||||||
"aiPromptNoResponse": "Порада: Спочатку запустіть тест, щоб включити відповідь API в командний рядок для отримання кращих результатів.",
|
"aiPromptNoResponse": "Порада: Спочатку запустіть тест, щоб включити відповідь API в командний рядок для отримання кращих результатів.",
|
||||||
"aiPromptCopyError": "Не вдалося скопіювати запит до буфера обміну. Можливо, ваш браузер не підтримує цю функцію.",
|
"aiPromptCopyError": "Не вдалося скопіювати запит до буфера обміну. Можливо, ваш браузер не підтримує цю функцію.",
|
||||||
@@ -1075,7 +1075,7 @@
|
|||||||
},
|
},
|
||||||
"alert": {
|
"alert": {
|
||||||
"permission": {
|
"permission": {
|
||||||
"title": "Недостатньо прав доступу",
|
"title": "Недостатньо прав доступу.",
|
||||||
"message": "Вам не дозволено довіряти або завантажувати сертифікати. Зверніться до адміністратора, щоб завантажити необхідний кореневий сертифікат."
|
"message": "Вам не дозволено довіряти або завантажувати сертифікати. Зверніться до адміністратора, щоб завантажити необхідний кореневий сертифікат."
|
||||||
},
|
},
|
||||||
"hostnameMismatch": {
|
"hostnameMismatch": {
|
||||||
@@ -1228,7 +1228,7 @@
|
|||||||
},
|
},
|
||||||
"tooManyRequests": {
|
"tooManyRequests": {
|
||||||
"title": "Занадто багато запитів за певний час",
|
"title": "Занадто багато запитів за певний час",
|
||||||
"message": "Було забагато запитів. Ймовірно, цільова система обмежує кількість запитів або відхиляє їх"
|
"message": "Було забагато запитів. Ймовірно, цільова система обмежує кількість запитів або відхиляє їх."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1298,7 +1298,7 @@
|
|||||||
"newLabel": "Новий cекрет API"
|
"newLabel": "Новий cекрет API"
|
||||||
},
|
},
|
||||||
"githubAppId": {
|
"githubAppId": {
|
||||||
"label": "ID додатку",
|
"label": " ID додатку",
|
||||||
"newLabel": "Новий ID додатку"
|
"newLabel": "Новий ID додатку"
|
||||||
},
|
},
|
||||||
"githubInstallationId": {
|
"githubInstallationId": {
|
||||||
@@ -1442,8 +1442,8 @@
|
|||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "URL іконки",
|
"label": "URL іконки",
|
||||||
"description": "Знайдіть бібліотеку, вставте URL-адресу зображення або завантажте файл.",
|
"description": "Знайдіть бібліотеку, вставте URL-адресу зображення або завантажте файл.",
|
||||||
"header": "Шукати зображення {countIcons} за назвою",
|
"header": "Введіть назву або об'єкти для фільтрації значків... Homarr виконає пошук серед {countIcons} значків для вас.",
|
||||||
"headerLoading": "Пошук зображень за назвою",
|
"headerLoading": "Введіть для пошуку іконок…",
|
||||||
"clear": "Очистити зображення",
|
"clear": "Очистити зображення",
|
||||||
"uploadImage": "Вивантажити зображення",
|
"uploadImage": "Вивантажити зображення",
|
||||||
"results": "Результати пошуку зображень",
|
"results": "Результати пошуку зображень",
|
||||||
@@ -1512,7 +1512,7 @@
|
|||||||
"login": "Логін",
|
"login": "Логін",
|
||||||
"homeBoard": "Ваша домашня дошка",
|
"homeBoard": "Ваша домашня дошка",
|
||||||
"loggedOut": "Ви вийшли",
|
"loggedOut": "Ви вийшли",
|
||||||
"updateAvailable": "{countUpdates} оновлень доступно: {tag}"
|
"updateAvailable": "Доступні оновлення {countUpdates} : {tag}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dangerZone": "Небезпечна зона",
|
"dangerZone": "Небезпечна зона",
|
||||||
@@ -2308,11 +2308,11 @@
|
|||||||
"description": "Як має виглядати дата"
|
"description": "Як має виглядати дата"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currentWindSpeed": "{currentWindSpeed} {unit}",
|
"currentWindSpeed": "{currentWindSpeed} км/год",
|
||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "Схід сонця",
|
"sunrise": "Схід сонця",
|
||||||
"sunset": "Захід сонця",
|
"sunset": "Захід сонця",
|
||||||
"maxWindSpeed": "Максимальна швидкість вітру: {maxWindSpeed} {unit}",
|
"maxWindSpeed": "Максимальна швидкість вітру: {maxWindSpeed} км/год",
|
||||||
"maxWindGusts": "Максимальна швидкість вітру: {maxWindGusts} {unit}",
|
"maxWindGusts": "Максимальна швидкість вітру: {maxWindGusts} {unit}",
|
||||||
"humidity": "Вологість: {humidity}%"
|
"humidity": "Вологість: {humidity}%"
|
||||||
},
|
},
|
||||||
@@ -2476,19 +2476,19 @@
|
|||||||
"label": "Інверсія сортування"
|
"label": "Інверсія сортування"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Порядок стовпців"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Ширина стовпців"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Не вдалося отримати статистику контейнерів",
|
"internalServerError": "Не вдалося отримати статистику контейнерів",
|
||||||
"layoutSaveTitle": "Макет таблиці не збережено",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Не вдалося зберегти макет таблиці Docker. Спробуйте ще раз перемістити або змінити розмір стовпця."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "Не знайдено жодних контейнерів"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
@@ -2781,20 +2781,20 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"showOnlyPlaying": {
|
"showOnlyPlaying": {
|
||||||
"label": "Показувати тільки поточне відтворення",
|
"label": "Показувати тільки поточне відтворення",
|
||||||
"description": "Вимкнення цієї опції не працюватиме для Plex"
|
"description": "Вимкнення цієї опції не працюватиме для Plex."
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "Показати бітрейт",
|
"label": "",
|
||||||
"description": "Показувати поточний бітрейт відтворення поруч із розташуванням"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "Показати розташування",
|
"label": "",
|
||||||
"description": "Показувати, чи перебуває потік у вашій локальній або глобальній мережі."
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "{count, plural, one {# активна трансляція} few {# активні трансляції} many {# активних трансляцій} other {# активних трансляцій}}",
|
"streams": "",
|
||||||
"totalBitrate": "Всього: {bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "Зараз відтворюється",
|
"currentlyPlaying": "Зараз відтворюється",
|
||||||
@@ -2803,9 +2803,9 @@
|
|||||||
"id": "Id",
|
"id": "Id",
|
||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"directPlay": "Пряме відтворення",
|
"directPlay": "Пряме відтворення",
|
||||||
"directStream": "Прямий потік",
|
"directStream": "",
|
||||||
"transcodeVideo": "Транскодування відео",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "Транскодування аудіо",
|
"transcodeAudio": "",
|
||||||
"transcoding": "Транскодування",
|
"transcoding": "Транскодування",
|
||||||
"paused": "Призупинено",
|
"paused": "Призупинено",
|
||||||
"remaining": "Залишилося {minutes} хв",
|
"remaining": "Залишилося {minutes} хв",
|
||||||
@@ -2979,7 +2979,7 @@
|
|||||||
},
|
},
|
||||||
"upToDateHostsWarningAt": {
|
"upToDateHostsWarningAt": {
|
||||||
"label": "Поріг попередження для актуальних хостів",
|
"label": "Поріг попередження для актуальних хостів",
|
||||||
"description": "Вмикається жовтий колір, коли значення дорівнює або перевищує цей поріг, але нижче повного покриття"
|
"description": "Вмикається жовтий колір, коли значення дорівнює або перевищує цей поріг, але нижче повного покриття."
|
||||||
},
|
},
|
||||||
"upToDateHostsCriticalAt": {
|
"upToDateHostsCriticalAt": {
|
||||||
"label": "Критичний поріг для оновлених хостів",
|
"label": "Критичний поріг для оновлених хостів",
|
||||||
@@ -2995,7 +2995,7 @@
|
|||||||
},
|
},
|
||||||
"totalOutdatedPackagesCriticalAt": {
|
"totalOutdatedPackagesCriticalAt": {
|
||||||
"label": "Критичний поріг застарілих пакетів",
|
"label": "Критичний поріг застарілих пакетів",
|
||||||
"description": "Вмикається червоний, коли значення досягає цього порогу"
|
"description": "Вмикається червоний коли значення досягає цього порогу"
|
||||||
},
|
},
|
||||||
"showOsDistribution": {
|
"showOsDistribution": {
|
||||||
"label": "Показати розподіл ОС",
|
"label": "Показати розподіл ОС",
|
||||||
@@ -3150,21 +3150,21 @@
|
|||||||
},
|
},
|
||||||
"limitPerIntegration": {
|
"limitPerIntegration": {
|
||||||
"label": "Обмежити елементи за інтегрування",
|
"label": "Обмежити елементи за інтегрування",
|
||||||
"description": "Це обмежить кількість елементів, що відображаються для кожної інтеграції, а не глобально"
|
"description": "Це обмежить кількість елементів, що відображаються для кожної інтеграції, а не глобально."
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Порядок стовпців"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Ширина стовпців"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "Вибір стовпців в елементах",
|
"noColumns": "Вибір стовпців в елементах",
|
||||||
"noCommunications": "Не вдалося завантажити дані з інтеграції",
|
"noCommunications": "Не вдалося завантажити дані з інтеграції",
|
||||||
"noItems": "Не знайдено жодних завантажень",
|
"noItems": "",
|
||||||
"actionFailed": "Дію не виконано",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "Не вдалося виконати дію. Спробуйте ще раз."
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3188,7 +3188,7 @@
|
|||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"columnTitle": "Id",
|
"columnTitle": "Id",
|
||||||
"detailsTitle": "ID елемента"
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"columnTitle": "Інтеграція"
|
"columnTitle": "Інтеграція"
|
||||||
@@ -3223,7 +3223,7 @@
|
|||||||
"time": {
|
"time": {
|
||||||
"columnTitle": "Час завершення",
|
"columnTitle": "Час завершення",
|
||||||
"detailsTitle": "Час з/до завершення",
|
"detailsTitle": "Час з/до завершення",
|
||||||
"eta": "Залишилося"
|
"eta": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"columnTitle": "Тип",
|
"columnTitle": "Тип",
|
||||||
@@ -3269,21 +3269,21 @@
|
|||||||
},
|
},
|
||||||
"globalRatio": "Загальний рейтинг",
|
"globalRatio": "Загальний рейтинг",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"copyName": "Копіювати назву",
|
"copyName": "",
|
||||||
"copyId": "Копіювати ID",
|
"copyId": "",
|
||||||
"clearFilters": "Очистити фільтри"
|
"clearFilters": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"filter": "Фільтрувати завантаження"
|
"filter": ""
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"show": "Показати загальну статистику",
|
"show": "",
|
||||||
"hide": "Сховати загальну статистику",
|
"hide": "",
|
||||||
"overallProgress": "Загальний прогрес черги",
|
"overallProgress": "",
|
||||||
"totalSize": "Загальний розмір черги",
|
"totalSize": "",
|
||||||
"clients": "{count, plural, one {# клієнт} few {# клієнти} many {# клієнтів} other {# клієнтів}}"
|
"clients": ""
|
||||||
},
|
},
|
||||||
"disconnected": "(відключено)"
|
"disconnected": ""
|
||||||
},
|
},
|
||||||
"mediaReleases": {
|
"mediaReleases": {
|
||||||
"name": "Медіа випуски",
|
"name": "Медіа випуски",
|
||||||
@@ -3580,7 +3580,7 @@
|
|||||||
"invalid": "Неправильне визначення репозиторію, перевірте значення",
|
"invalid": "Неправильне визначення репозиторію, перевірте значення",
|
||||||
"noProvider": {
|
"noProvider": {
|
||||||
"label": "Без постачальника",
|
"label": "Без постачальника",
|
||||||
"tooltip": "Не вдалося проаналізувати постачальника, встановіть його вручну після імпорту зображень"
|
"tooltip": "Не вдалося проаналізувати постачальника, встановіть його вручну після імпорту зображень."
|
||||||
},
|
},
|
||||||
"tokens": {
|
"tokens": {
|
||||||
"label": "Токени постачальника",
|
"label": "Токени постачальника",
|
||||||
@@ -3610,7 +3610,7 @@
|
|||||||
"invalidIdentifier": "Неприпустимий ідентифікатор",
|
"invalidIdentifier": "Неприпустимий ідентифікатор",
|
||||||
"noMatchingVersion": "Відповідної версії не знайдено",
|
"noMatchingVersion": "Відповідної версії не знайдено",
|
||||||
"noReleasesFound": "Результатів не знайдено",
|
"noReleasesFound": "Результатів не знайдено",
|
||||||
"noProviderSelected": "Постачальника не обрано",
|
"noProviderSelected": "Постачальника не обрано.",
|
||||||
"noProviderResponse": "Немає відповіді від провайдера",
|
"noProviderResponse": "Немає відповіді від провайдера",
|
||||||
"unexpected": "Неочікувана помилка провайдера"
|
"unexpected": "Неочікувана помилка провайдера"
|
||||||
}
|
}
|
||||||
@@ -3625,7 +3625,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"integrationsDisconnected": "Дані недоступні, всі інтеграції відключені",
|
"integrationsDisconnected": "Дані недоступні, всі інтеграції відключені",
|
||||||
"unknownContentOption": "Невідомий вміст для віджета зведення мережевого контролера: "
|
"unknownContentOption": "Невідомий вміст для віджета зведення мережевого контролера:"
|
||||||
},
|
},
|
||||||
"name": "Зведення мережного контролера",
|
"name": "Зведення мережного контролера",
|
||||||
"description": "Відображає зведену інформацію про мережевий контролер (наприклад, UniFi Controller)"
|
"description": "Відображає зведену інформацію про мережевий контролер (наприклад, UniFi Controller)"
|
||||||
@@ -3735,7 +3735,7 @@
|
|||||||
"recentResultsSpeed": "Швидкість",
|
"recentResultsSpeed": "Швидкість",
|
||||||
"recentResultsPing": "Пінг",
|
"recentResultsPing": "Пінг",
|
||||||
"download": "Завантажити",
|
"download": "Завантажити",
|
||||||
"upload": "Вивантаж",
|
"upload": "Вивантаж.",
|
||||||
"ping": "Пінг",
|
"ping": "Пінг",
|
||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"avgDownload": "Середнє завантаження",
|
"avgDownload": "Середнє завантаження",
|
||||||
@@ -4014,17 +4014,17 @@
|
|||||||
"label": "Показати версію агента"
|
"label": "Показати версію агента"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "Порядок стовпців"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "Ширина стовпців"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noRecords": "Системи не знайдені",
|
"noRecords": "Системи не знайдені",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "Не вдається підключитися до Beszel. Перевірте параметри інтеграції.",
|
"internalServerError": "Не вдається підключитися до Beszel. Перевірте параметри інтеграції.",
|
||||||
"layoutSaveTitle": "Не вдалося зберегти макет таблиці",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "Не вдалося зберегти порядок або ширину стовпців. Спробуйте ще раз."
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"column": {
|
"column": {
|
||||||
"system": "Система",
|
"system": "Система",
|
||||||
@@ -4887,11 +4887,11 @@
|
|||||||
"text": "Не індексувати сайт у пошукових системах і не показувати його в жодному результаті пошуку"
|
"text": "Не індексувати сайт у пошукових системах і не показувати його в жодному результаті пошуку"
|
||||||
},
|
},
|
||||||
"noFollow": {
|
"noFollow": {
|
||||||
"title": "Не переходити",
|
"title": "Не переходити (No follow)",
|
||||||
"text": "Не переходьте за посиланнями під час індексації. Якщо вимкнути це, то сканери намагатимуться переходити за всіма посиланнями на Homarr."
|
"text": "Не переходьте за посиланнями під час індексації. Якщо вимкнути це, то сканери намагатимуться переходити за всіма посиланнями на Homarr."
|
||||||
},
|
},
|
||||||
"noTranslate": {
|
"noTranslate": {
|
||||||
"title": "Не перекладати",
|
"title": "Не перекладати (No translate)",
|
||||||
"text": "Якщо мова сайту не є тією, яку користувач, імовірно, захоче прочитати, Google покаже посилання на переклад у результатах пошуку"
|
"text": "Якщо мова сайту не є тією, яку користувач, імовірно, захоче прочитати, Google покаже посилання на переклад у результатах пошуку"
|
||||||
},
|
},
|
||||||
"noSiteLinksSearchBox": {
|
"noSiteLinksSearchBox": {
|
||||||
@@ -4922,7 +4922,7 @@
|
|||||||
"title": "Користувачі",
|
"title": "Користувачі",
|
||||||
"enableGravatar": {
|
"enableGravatar": {
|
||||||
"label": "Увімкнути Gravatar",
|
"label": "Увімкнути Gravatar",
|
||||||
"description": "Повертається до аватарів користувачів з Libravatar/Gravatar, якщо не встановлено власний аватар, а налаштовано електронну адресу"
|
"description": "Повертається до аватарів користувачів з Libravatar/Gravatar, якщо не встановлено власний аватар, а налаштовано електронну адресу."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -5301,7 +5301,7 @@
|
|||||||
},
|
},
|
||||||
"sponsors": {
|
"sponsors": {
|
||||||
"title": "Спонсори",
|
"title": "Спонсори",
|
||||||
"subtitle": "{count}, що підтримує проект фінансово"
|
"subtitle": "{count} , що підтримує проект фінансово"
|
||||||
},
|
},
|
||||||
"libraries": {
|
"libraries": {
|
||||||
"title": "Бібліотеки",
|
"title": "Бібліотеки",
|
||||||
@@ -5320,7 +5320,7 @@
|
|||||||
"saveNotebook": "Зберегти блокнот (тільки всередині віджету)",
|
"saveNotebook": "Зберегти блокнот (тільки всередині віджету)",
|
||||||
"openSpotlight": "Відкрити пошук"
|
"openSpotlight": "Відкрити пошук"
|
||||||
},
|
},
|
||||||
"note": "Порада: Mod стосується як клавіші Ctrl, так і клавіші ⌘ у macOS"
|
"note": "Порада: Mod стосується як клавіші Ctrl, так і клавіші ⌘ у macOS."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5335,9 +5335,9 @@
|
|||||||
},
|
},
|
||||||
"search": "Пошук контейнерів ({count})",
|
"search": "Пошук контейнерів ({count})",
|
||||||
"selected": "Вибрано {selectCount} із {totalCount} контейнерів",
|
"selected": "Вибрано {selectCount} із {totalCount} контейнерів",
|
||||||
"footer": "Всього {count} контейнерів",
|
"footer": "Всього {count} контейнерів ",
|
||||||
"totalCpu": "Всього ЦП: {cpu}%",
|
"totalCpu": "Всього ЦП: {cpu}%",
|
||||||
"totalMemory": "Всього оперативної пам'яті: {memory}"
|
"totalMemory": "Всього Оперативної Пам'яті: {cpu}%"
|
||||||
},
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -5422,7 +5422,7 @@
|
|||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "Видалити",
|
"label": "Видалити",
|
||||||
"confirm": "Натисніть ще раз, щоб видалити остаточно",
|
"confirm": "",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Контейнери видалено",
|
"title": "Контейнери видалено",
|
||||||
|
|||||||
+1715
-1715
File diff suppressed because it is too large.
Load diff
@@ -1345,8 +1345,8 @@
|
|||||||
"description": "雲原生反向代理和負載平衡器"
|
"description": "雲原生反向代理和負載平衡器"
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "",
|
||||||
"description": "監控您的 Docker 容器中哪些有可用的映像更新"
|
"description": ""
|
||||||
},
|
},
|
||||||
"paperlessNgx": {
|
"paperlessNgx": {
|
||||||
"name": "Paperless-ngx",
|
"name": "Paperless-ngx",
|
||||||
@@ -1441,18 +1441,18 @@
|
|||||||
"here": "此處",
|
"here": "此處",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"label": "Icon 網址",
|
"label": "Icon 網址",
|
||||||
"description": "搜尋圖庫、貼上圖片網址或上傳檔案。",
|
"description": "",
|
||||||
"header": "輸入名稱或物件來篩選 icons,Homarr 會自動搜尋 {countIcons}",
|
"header": "輸入名稱或物件來篩選 icons,Homarr 會自動搜尋 {countIcons}",
|
||||||
"headerLoading": "輸入以搜尋圖示",
|
"headerLoading": "輸入以搜尋圖示…",
|
||||||
"clear": "清除圖片",
|
"clear": "",
|
||||||
"uploadImage": "上傳圖片",
|
"uploadImage": "",
|
||||||
"results": "圖片搜尋結果",
|
"results": "",
|
||||||
"localImages": "你的圖像",
|
"localImages": "你的圖像",
|
||||||
"svgIcons": "SVG 圖示",
|
"svgIcons": "",
|
||||||
"otherImages": "其他圖像",
|
"otherImages": "其他圖像",
|
||||||
"urlLoading": "正在檢查圖片…",
|
"urlLoading": "",
|
||||||
"urlReady": "圖片已可使用",
|
"urlReady": "",
|
||||||
"urlError": "無法載入此圖片。請檢查網址後再試一次。",
|
"urlError": "",
|
||||||
"noResults": "沒有符合 \"{search}\" 的圖示。",
|
"noResults": "沒有符合 \"{search}\" 的圖示。",
|
||||||
"error": "無法載入圖示。"
|
"error": "無法載入圖示。"
|
||||||
},
|
},
|
||||||
@@ -1808,19 +1808,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wud": {
|
"wud": {
|
||||||
"name": "What's Up Docker",
|
"name": "",
|
||||||
"description": "顯示有多少受監控的容器有可用更新。",
|
"description": "",
|
||||||
"title": "What's Up Docker",
|
"title": "",
|
||||||
"monitored": "已監控",
|
"monitored": "",
|
||||||
"updatesAvailable": "{count, plural, other {# 個可用更新}}",
|
"updatesAvailable": "",
|
||||||
"updateAvailable": "有可用更新",
|
"updateAvailable": "",
|
||||||
"openReleaseNotes": "開啟發佈說明",
|
"openReleaseNotes": "",
|
||||||
"option": {
|
"option": {
|
||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "顯示標題"
|
"label": ""
|
||||||
},
|
},
|
||||||
"showRing": {
|
"showRing": {
|
||||||
"label": "顯示更新環"
|
"label": ""
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "顯示布局",
|
"label": "顯示布局",
|
||||||
@@ -1834,8 +1834,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showUpdateList": {
|
"showUpdateList": {
|
||||||
"label": "顯示容器更新清單",
|
"label": "",
|
||||||
"description": "列出每個具有可用映像更新的受監控容器"
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2308,12 +2308,12 @@
|
|||||||
"description": "日期應該是什麼樣式"
|
"description": "日期應該是什麼樣式"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currentWindSpeed": "{currentWindSpeed} {unit}",
|
"currentWindSpeed": "{currentWindSpeed} 公里/時",
|
||||||
"dailyForecast": {
|
"dailyForecast": {
|
||||||
"sunrise": "日出",
|
"sunrise": "日出",
|
||||||
"sunset": "日落",
|
"sunset": "日落",
|
||||||
"maxWindSpeed": "最大風速:{maxWindSpeed} {unit}",
|
"maxWindSpeed": "最大風速:{maxWindSpeed} 公里/時",
|
||||||
"maxWindGusts": "最大陣風:{maxWindGusts} {unit}",
|
"maxWindGusts": "最大陣風:{maxWindGusts} 公里/時",
|
||||||
"humidity": "濕度: {humidity}%"
|
"humidity": "濕度: {humidity}%"
|
||||||
},
|
},
|
||||||
"kind": {
|
"kind": {
|
||||||
@@ -2476,19 +2476,19 @@
|
|||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "欄位順序"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "欄位寬度"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "",
|
"internalServerError": "",
|
||||||
"layoutSaveTitle": "表格版面配置未儲存",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "無法儲存 Docker 表格版面配置。請再次移動或調整欄位大小。"
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"noContainers": "找不到任何容器"
|
"noContainers": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
@@ -2784,17 +2784,17 @@
|
|||||||
"description": "停用此功能將無法在 Plex 上運作"
|
"description": "停用此功能將無法在 Plex 上運作"
|
||||||
},
|
},
|
||||||
"showBitrate": {
|
"showBitrate": {
|
||||||
"label": "顯示位元速率",
|
"label": "",
|
||||||
"description": "在地點旁顯示目前的播放位元速率"
|
"description": ""
|
||||||
},
|
},
|
||||||
"showLocation": {
|
"showLocation": {
|
||||||
"label": "顯示位置",
|
"label": "",
|
||||||
"description": "顯示串流位於 LAN 或 WAN"
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"streams": "{count, plural, other {# 個使用中的串流}}",
|
"streams": "",
|
||||||
"totalBitrate": "總計:{bitrate}"
|
"totalBitrate": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"currentlyPlaying": "目前播放中",
|
"currentlyPlaying": "目前播放中",
|
||||||
@@ -2803,9 +2803,9 @@
|
|||||||
"id": "ID",
|
"id": "ID",
|
||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"directPlay": "直接播放",
|
"directPlay": "直接播放",
|
||||||
"directStream": "直接串流",
|
"directStream": "",
|
||||||
"transcodeVideo": "轉碼影片",
|
"transcodeVideo": "",
|
||||||
"transcodeAudio": "轉碼音訊",
|
"transcodeAudio": "",
|
||||||
"transcoding": "轉碼中",
|
"transcoding": "轉碼中",
|
||||||
"paused": "已暫停",
|
"paused": "已暫停",
|
||||||
"remaining": "剩餘 {minutes} 分鐘",
|
"remaining": "剩餘 {minutes} 分鐘",
|
||||||
@@ -3153,18 +3153,18 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "欄位順序"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "欄位寬度"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noColumns": "於項目中選擇列",
|
"noColumns": "於項目中選擇列",
|
||||||
"noCommunications": "無法從集成中加載數據",
|
"noCommunications": "無法從集成中加載數據",
|
||||||
"noItems": "找不到下載項目",
|
"noItems": "",
|
||||||
"actionFailed": "操作失敗",
|
"actionFailed": "",
|
||||||
"actionFailedMessage": "無法完成此操作,請再試一次。"
|
"actionFailedMessage": ""
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -3188,7 +3188,7 @@
|
|||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"columnTitle": "ID",
|
"columnTitle": "ID",
|
||||||
"detailsTitle": "項目 ID"
|
"detailsTitle": ""
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"columnTitle": "集成"
|
"columnTitle": "集成"
|
||||||
@@ -3223,7 +3223,7 @@
|
|||||||
"time": {
|
"time": {
|
||||||
"columnTitle": "完成時間",
|
"columnTitle": "完成時間",
|
||||||
"detailsTitle": "剩餘完成時間",
|
"detailsTitle": "剩餘完成時間",
|
||||||
"eta": "剩餘時間"
|
"eta": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"columnTitle": "類型",
|
"columnTitle": "類型",
|
||||||
@@ -3269,21 +3269,21 @@
|
|||||||
},
|
},
|
||||||
"globalRatio": "全局速率",
|
"globalRatio": "全局速率",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"copyName": "複製名稱",
|
"copyName": "",
|
||||||
"copyId": "複製 ID",
|
"copyId": "",
|
||||||
"clearFilters": "清除篩選器"
|
"clearFilters": ""
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"filter": "篩選下載項目"
|
"filter": ""
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"show": "顯示全域統計資料",
|
"show": "",
|
||||||
"hide": "隱藏全域統計資料",
|
"hide": "",
|
||||||
"overallProgress": "整體佇列進度",
|
"overallProgress": "",
|
||||||
"totalSize": "佇列總大小",
|
"totalSize": "",
|
||||||
"clients": "{count, plural, other {# 個用戶端}}"
|
"clients": ""
|
||||||
},
|
},
|
||||||
"disconnected": "(斷線)"
|
"disconnected": ""
|
||||||
},
|
},
|
||||||
"mediaReleases": {
|
"mediaReleases": {
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -4014,17 +4014,17 @@
|
|||||||
"label": "顯示代理版本"
|
"label": "顯示代理版本"
|
||||||
},
|
},
|
||||||
"columnOrder": {
|
"columnOrder": {
|
||||||
"label": "欄位順序"
|
"label": ""
|
||||||
},
|
},
|
||||||
"columnWidths": {
|
"columnWidths": {
|
||||||
"label": "欄位寬度"
|
"label": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noRecords": "找不到系統",
|
"noRecords": "找不到系統",
|
||||||
"error": {
|
"error": {
|
||||||
"internalServerError": "無法連線到 Beszel。請檢查整合設定。",
|
"internalServerError": "無法連線到 Beszel。請檢查整合設定。",
|
||||||
"layoutSaveTitle": "無法儲存表格版面配置",
|
"layoutSaveTitle": "",
|
||||||
"layoutSaveMessage": "無法儲存您的欄位順序或寬度。請再試一次。"
|
"layoutSaveMessage": ""
|
||||||
},
|
},
|
||||||
"column": {
|
"column": {
|
||||||
"system": "系統",
|
"system": "系統",
|
||||||
@@ -4876,7 +4876,7 @@
|
|||||||
"title": "分析",
|
"title": "分析",
|
||||||
"general": {
|
"general": {
|
||||||
"title": "發送匿名分析",
|
"title": "發送匿名分析",
|
||||||
"text": "Homarr 使用開源軟體 Umami 發送匿名分析,他從不收集任何個人資料,因此完全符合 GDPR 和 CCPA 要求,我們鼓勵啟用分析,因為可以幫助我們的團隊識別問題,並確認待辦事項的優先級別。"
|
"text": "Homarr 使用開源軟體 Umami 發送匿名分析,他從不收集任何個人資料,因此完全符合 GDPR 和 CCPA 要求,我們鼓勵啟用分析,因為可以幫助我們的團隊識別問題,並確認待辦事項的優先級別"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crawlingAndIndexing": {
|
"crawlingAndIndexing": {
|
||||||
@@ -5422,7 +5422,7 @@
|
|||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "移除",
|
"label": "移除",
|
||||||
"confirm": "再次點擊以永久移除",
|
"confirm": "",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "容器已移除",
|
"title": "容器已移除",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import { filterStorageVolumes, normalizeStorageDeviceName, toScopedStorageVolumeValue } from "./filter-storage-volumes";
|
import {
|
||||||
|
filterStorageVolumes,
|
||||||
|
normalizeStorageDeviceName,
|
||||||
|
toScopedStorageVolumeValue,
|
||||||
|
} from "./filter-storage-volumes";
|
||||||
import { matchFileSystemAndSmart } from "./health-monitoring/system-health";
|
import { matchFileSystemAndSmart } from "./health-monitoring/system-health";
|
||||||
|
|
||||||
describe("normalizeStorageDeviceName", () => {
|
describe("normalizeStorageDeviceName", () => {
|
||||||
@@ -9,12 +13,6 @@ describe("normalizeStorageDeviceName", () => {
|
|||||||
expect(normalizeStorageDeviceName("/dev/sda")).toBe("/dev/sda");
|
expect(normalizeStorageDeviceName("/dev/sda")).toBe("/dev/sda");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("strips partition suffixes from unprefixed device names (OpenMediaVault)", () => {
|
|
||||||
expect(normalizeStorageDeviceName("sda1")).toBe("sda");
|
|
||||||
expect(normalizeStorageDeviceName("sda")).toBe("sda");
|
|
||||||
expect(normalizeStorageDeviceName("nvme0n1p1")).toBe("nvme0n1");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("preserves Synology-style volume names", () => {
|
test("preserves Synology-style volume names", () => {
|
||||||
expect(normalizeStorageDeviceName("volume_1")).toBe("volume_1");
|
expect(normalizeStorageDeviceName("volume_1")).toBe("volume_1");
|
||||||
expect(normalizeStorageDeviceName("volume_2")).toBe("volume_2");
|
expect(normalizeStorageDeviceName("volume_2")).toBe("volume_2");
|
||||||
@@ -145,36 +143,4 @@ describe("matchFileSystemAndSmart", () => {
|
|||||||
overallStatus: "",
|
overallStatus: "",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("joins unprefixed OMV partitions with their base device SMART data", () => {
|
|
||||||
const result = matchFileSystemAndSmart(
|
|
||||||
[
|
|
||||||
{ deviceName: "sda1", used: "3.13 GiB", available: "26853056512", percentage: 12 },
|
|
||||||
{ deviceName: "sdb1", used: "396.24 GiB", available: "1542365618176", percentage: 22 },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ deviceName: "sda", temperature: 0, overallStatus: "BAD_STATUS" },
|
|
||||||
{ deviceName: "sdb", temperature: 33, overallStatus: "GOOD" },
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
|
||||||
deviceName: "sda",
|
|
||||||
used: "3.13 GiB",
|
|
||||||
available: "26853056512",
|
|
||||||
percentage: 12,
|
|
||||||
temperature: 0,
|
|
||||||
overallStatus: "BAD_STATUS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceName: "sdb",
|
|
||||||
used: "396.24 GiB",
|
|
||||||
available: "1542365618176",
|
|
||||||
percentage: 22,
|
|
||||||
temperature: 33,
|
|
||||||
overallStatus: "GOOD",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
@@ -5,16 +5,10 @@ interface StorageVolumeEntry {
|
|||||||
const partitionSuffixPatterns: ReadonlyArray<{ pattern: RegExp; baseGroupIndex: number }> = [
|
const partitionSuffixPatterns: ReadonlyArray<{ pattern: RegExp; baseGroupIndex: number }> = [
|
||||||
// SCSI/SATA/VirtIO: /dev/sda1 -> /dev/sda
|
// SCSI/SATA/VirtIO: /dev/sda1 -> /dev/sda
|
||||||
{ pattern: /^(\/dev\/(?:sd|vd|hd|xvd)[a-z]+)[0-9]+$/, baseGroupIndex: 1 },
|
{ pattern: /^(\/dev\/(?:sd|vd|hd|xvd)[a-z]+)[0-9]+$/, baseGroupIndex: 1 },
|
||||||
// SCSI/SATA/VirtIO without device node prefix: sda1 -> sda (OMV reports unprefixed names)
|
|
||||||
{ pattern: /^((?:sd|vd)[a-z]+)[0-9]+$/, baseGroupIndex: 1 },
|
|
||||||
// NVMe: /dev/nvme0n1p2 -> /dev/nvme0n1
|
// NVMe: /dev/nvme0n1p2 -> /dev/nvme0n1
|
||||||
{ pattern: /^(\/dev\/nvme[0-9]+n[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
{ pattern: /^(\/dev\/nvme[0-9]+n[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
||||||
// NVMe without device node prefix: nvme0n1p2 -> nvme0n1
|
|
||||||
{ pattern: /^(nvme[0-9]+n[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
|
||||||
// eMMC: /dev/mmcblk0p1 -> /dev/mmcblk0
|
// eMMC: /dev/mmcblk0p1 -> /dev/mmcblk0
|
||||||
{ pattern: /^(\/dev\/mmcblk[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
{ pattern: /^(\/dev\/mmcblk[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
||||||
// eMMC without device node prefix: mmcblk0p1 -> mmcblk0
|
|
||||||
{ pattern: /^(mmcblk[0-9]+)p[0-9]+$/, baseGroupIndex: 1 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const normalizeStorageDeviceName = (deviceName: string): string => {
|
export const normalizeStorageDeviceName = (deviceName: string): string => {
|
||||||
@@ -34,14 +28,18 @@ export const toScopedStorageVolumeValue = (integrationId: string, value: string)
|
|||||||
return `${integrationId}:${volumeName}`;
|
return `${integrationId}:${volumeName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const storageDeviceNamesMatch = (leftDeviceName: string, rightDeviceName: string): boolean => {
|
const storageDeviceNamesMatch = (leftDeviceName: string, rightDeviceName: string): boolean => {
|
||||||
return (
|
return (
|
||||||
leftDeviceName === rightDeviceName ||
|
leftDeviceName === rightDeviceName ||
|
||||||
normalizeStorageDeviceName(leftDeviceName) === normalizeStorageDeviceName(rightDeviceName)
|
normalizeStorageDeviceName(leftDeviceName) === normalizeStorageDeviceName(rightDeviceName)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchesVisibleStorageVolume = (visibleVolume: string, integrationId: string, deviceName: string): boolean => {
|
const matchesVisibleStorageVolume = (
|
||||||
|
visibleVolume: string,
|
||||||
|
integrationId: string,
|
||||||
|
deviceName: string,
|
||||||
|
): boolean => {
|
||||||
const separatorIndex = visibleVolume.indexOf(":");
|
const separatorIndex = visibleVolume.indexOf(":");
|
||||||
if (separatorIndex === -1) {
|
if (separatorIndex === -1) {
|
||||||
return storageDeviceNamesMatch(visibleVolume, deviceName);
|
return storageDeviceNamesMatch(visibleVolume, deviceName);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea h="100%" style={{ minHeight: 0 }}>
|
<ScrollArea h="100%">
|
||||||
<Group justify="space-between" w="100%" style={{ padding: "8px" }}>
|
<Group justify="space-between" w="100%" style={{ padding: "8px" }}>
|
||||||
<FirewallMenu
|
<FirewallMenu
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
|
|||||||
@@ -18,18 +18,16 @@ const isClusterIntegration = (integration: { kind: IntegrationKind }) =>
|
|||||||
integration.kind === "proxmox" || integration.kind === "mock";
|
integration.kind === "proxmox" || integration.kind === "mock";
|
||||||
|
|
||||||
export default function HealthMonitoringWidget(props: WidgetComponentProps<"healthMonitoring">) {
|
export default function HealthMonitoringWidget(props: WidgetComponentProps<"healthMonitoring">) {
|
||||||
const { data: integrations, isPending, isError } = clientApi.integration.byIds.useQuery(props.integrationIds);
|
const { data: integrations = [] } = clientApi.integration.byIds.useQuery(props.integrationIds);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
if (isPending || isError) return null;
|
const clusterIntegrationId = integrations.find(isClusterIntegration)?.id;
|
||||||
|
|
||||||
const clusterIntegrationId = integrations?.find(isClusterIntegration)?.id;
|
|
||||||
|
|
||||||
if (!clusterIntegrationId) {
|
if (!clusterIntegrationId) {
|
||||||
return <SystemHealthMonitoring {...props} />;
|
return <SystemHealthMonitoring {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherIntegrationIds = (integrations ?? [])
|
const otherIntegrationIds = integrations
|
||||||
// We want to have the mock integration also in the system tab, so we use it for both
|
// We want to have the mock integration also in the system tab, so we use it for both
|
||||||
.filter((integration) => integration.kind !== "proxmox")
|
.filter((integration) => integration.kind !== "proxmox")
|
||||||
.map((integration) => integration.id);
|
.map((integration) => integration.id);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { formatBytes } from "@homarr/common";
|
|||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { filterStorageVolumes, storageDeviceNamesMatch } from "../filter-storage-volumes";
|
import { filterStorageVolumes, normalizeStorageDeviceName } from "../filter-storage-volumes";
|
||||||
import { WidgetEmptyState } from "../common/empty-state";
|
import { WidgetEmptyState } from "../common/empty-state";
|
||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
import { CpuRing } from "./rings/cpu-ring";
|
import { CpuRing } from "./rings/cpu-ring";
|
||||||
@@ -296,7 +296,12 @@ interface SmartData {
|
|||||||
export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: SmartData[]) => {
|
export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: SmartData[]) => {
|
||||||
return fileSystems
|
return fileSystems
|
||||||
.map((fileSystem) => {
|
.map((fileSystem) => {
|
||||||
const smartDisk = smartData.find((smart) => storageDeviceNamesMatch(smart.deviceName, fileSystem.deviceName));
|
const normalizedFileSystemName = normalizeStorageDeviceName(fileSystem.deviceName);
|
||||||
|
const smartDisk = smartData.find(
|
||||||
|
(smart) =>
|
||||||
|
smart.deviceName === fileSystem.deviceName ||
|
||||||
|
normalizeStorageDeviceName(smart.deviceName) === normalizedFileSystemName,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deviceName: smartDisk?.deviceName ?? fileSystem.deviceName,
|
deviceName: smartDisk?.deviceName ?? fileSystem.deviceName,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useI18n } from "@homarr/translation/client";
|
|||||||
|
|
||||||
import { WidgetEmptyState } from "../common/empty-state";
|
import { WidgetEmptyState } from "../common/empty-state";
|
||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
import { filterStorageVolumes, storageDeviceNamesMatch } from "../filter-storage-volumes";
|
import { filterStorageVolumes } from "../filter-storage-volumes";
|
||||||
import { NoIntegrationDataError } from "../errors/no-data-integration";
|
import { NoIntegrationDataError } from "../errors/no-data-integration";
|
||||||
|
|
||||||
type DisplayMode = WidgetComponentProps<"systemDisks">["options"]["displayMode"];
|
type DisplayMode = WidgetComponentProps<"systemDisks">["options"]["displayMode"];
|
||||||
@@ -140,7 +140,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo
|
|||||||
return (
|
return (
|
||||||
<Stack gap="xs" p="xs" h="100%">
|
<Stack gap="xs" p="xs" h="100%">
|
||||||
{fileSystem.map((item) => {
|
{fileSystem.map((item) => {
|
||||||
const smartItem = smart.find((smart) => storageDeviceNamesMatch(smart.deviceName, item.deviceName));
|
const smartItem = smart.find((smart) => smart.deviceName === item.deviceName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SystemDiskCard
|
<SystemDiskCard
|
||||||
|
|||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"worktree": {
|
|
||||||
"setup": "mise trust\nmise install\npnpm install\n\nif [ -n \"${PASEO_SOURCE_CHECKOUT_PATH:-}\" ] && [ -f \"$PASEO_SOURCE_CHECKOUT_PATH/.env\" ]; then\n cp \"$PASEO_SOURCE_CHECKOUT_PATH/.env\" .env\nelse\n cp .env.example .env\nfi\n\nDB_PATH=\"$PWD/homarr.sqlite\"\nZERO_KEY=\"0000000000000000000000000000000000000000000000000000000000000000\"\nsed -i.bak \\\n -e \"s|^DB_DRIVER=.*|DB_DRIVER='better-sqlite3'|\" \\\n -e \"s|^DB_URL=.*|DB_URL='$DB_PATH'|\" \\\n -e \"s|^SECRET_ENCRYPTION_KEY=.*|SECRET_ENCRYPTION_KEY=$ZERO_KEY|\" \\\n .env\nrm -f .env.bak\n\nrm -f homarr.sqlite\npnpm db:migration:sqlite:run",
|
|
||||||
"teardown": "rm -f homarr.sqlite",
|
|
||||||
"servicePorts": {
|
|
||||||
"range": "3000-4000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"dev": {
|
|
||||||
"type": "service",
|
|
||||||
"command": "PORT=$PASEO_PORT pnpm -F nextjs dev"
|
|
||||||
},
|
|
||||||
"docs": {
|
|
||||||
"type": "service",
|
|
||||||
"command": "pnpm -F @homarr/docs dev --port $PASEO_PORT"
|
|
||||||
},
|
|
||||||
"build": { "command": "pnpm build" },
|
|
||||||
"test": { "command": "pnpm test" },
|
|
||||||
"test:e2e": { "command": "pnpm test:e2e" },
|
|
||||||
"lint": { "command": "pnpm lint" },
|
|
||||||
"typecheck": { "command": "pnpm typecheck" },
|
|
||||||
"db:seed": { "command": "pnpm db:seed" },
|
|
||||||
"db:studio": { "command": "pnpm db:studio" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+6003
-5691
File diff suppressed because it is too large.
Load diff
+119
-100
@@ -3,9 +3,9 @@ packages:
|
|||||||
- packages/*
|
- packages/*
|
||||||
- tooling/*
|
- tooling/*
|
||||||
catalog:
|
catalog:
|
||||||
"@babel/parser": ^7.29.7
|
"@babel/parser": ^8.0.4
|
||||||
"@auth/core": ^0.41.2
|
"@auth/core": ^0.41.3
|
||||||
"@auth/drizzle-adapter": ^1.11.2
|
"@auth/drizzle-adapter": ^1.11.3
|
||||||
"@ctrl/deluge": ^8.0.0
|
"@ctrl/deluge": ^8.0.0
|
||||||
"@ctrl/qbittorrent": ^9.14.0
|
"@ctrl/qbittorrent": ^9.14.0
|
||||||
"@ctrl/transmission": ^7.4.0
|
"@ctrl/transmission": ^7.4.0
|
||||||
@@ -14,29 +14,29 @@ catalog:
|
|||||||
"@dnd-kit/sortable": ^10.0.0
|
"@dnd-kit/sortable": ^10.0.0
|
||||||
"@dnd-kit/utilities": ^3.2.2
|
"@dnd-kit/utilities": ^3.2.2
|
||||||
"@drizzle-team/brocli": ^0.12.0
|
"@drizzle-team/brocli": ^0.12.0
|
||||||
"@extractus/feed-extractor": 7.2.1
|
"@extractus/feed-extractor": 8.0.3
|
||||||
"@gfazioli/mantine-onboarding-tour": ^4.0.6
|
"@gfazioli/mantine-onboarding-tour": ^4.0.9
|
||||||
"@gitbeaker/rest": ^43.8.0
|
"@gitbeaker/rest": ^43.8.0
|
||||||
"@homarr/gridstack": ^1.12.0
|
"@homarr/gridstack": ^1.12.0
|
||||||
"@homarr/node-unifi": ^2.6.0
|
"@homarr/node-unifi": ^2.6.0
|
||||||
"@immich/sdk": ^3.0.2
|
"@immich/sdk": ^3.1.0
|
||||||
"@jellyfin/sdk": ^0.13.0
|
"@jellyfin/sdk": ^0.13.0
|
||||||
"@kubernetes/client-node": ^1.4.0
|
"@kubernetes/client-node": ^2.0.0
|
||||||
"@modelcontextprotocol/sdk": 1.29.0
|
"@modelcontextprotocol/sdk": 1.29.0
|
||||||
"@mantine/charts": ^9.4.1
|
"@mantine/charts": ^9.5.1
|
||||||
"@mantine/colors-generator": ^9.4.1
|
"@mantine/colors-generator": ^9.5.1
|
||||||
"@mantine/core": ^9.4.1
|
"@mantine/core": ^9.5.1
|
||||||
"@mantine/dates": ^9.4.1
|
"@mantine/dates": ^9.5.1
|
||||||
"@mantine/dropzone": ^9.4.1
|
"@mantine/dropzone": ^9.5.1
|
||||||
"@mantine/form": ^9.4.1
|
"@mantine/form": ^9.5.1
|
||||||
"@mantine/hooks": ^9.4.1
|
"@mantine/hooks": ^9.5.1
|
||||||
"@mantine/modals": ^9.4.1
|
"@mantine/modals": ^9.5.1
|
||||||
"@mantine/notifications": ^9.4.1
|
"@mantine/notifications": ^9.5.1
|
||||||
"@mantine/spotlight": ^9.4.1
|
"@mantine/spotlight": ^9.5.1
|
||||||
"@mantine/tiptap": ^9.4.1
|
"@mantine/tiptap": ^9.5.1
|
||||||
"@octokit/auth-app": ^8.2.0
|
"@octokit/auth-app": ^8.2.0
|
||||||
"@paralleldrive/cuid2": ^3.3.0
|
"@paralleldrive/cuid2": ^3.3.0
|
||||||
"@playwright/test": ^1.61.1
|
"@playwright/test": ^1.62.1
|
||||||
"@semantic-release/changelog": ^7.0.0
|
"@semantic-release/changelog": ^7.0.0
|
||||||
"@semantic-release/commit-analyzer": ^13.0.1
|
"@semantic-release/commit-analyzer": ^13.0.1
|
||||||
"@semantic-release/git": ^11.0.1
|
"@semantic-release/git": ^11.0.1
|
||||||
@@ -44,36 +44,36 @@ catalog:
|
|||||||
"@semantic-release/npm": ^13.1.5
|
"@semantic-release/npm": ^13.1.5
|
||||||
"@semantic-release/release-notes-generator": ^14.1.1
|
"@semantic-release/release-notes-generator": ^14.1.1
|
||||||
"@t3-oss/env-nextjs": ^0.13.11
|
"@t3-oss/env-nextjs": ^0.13.11
|
||||||
"@tabler/icons-react": ^3.44.0
|
"@tabler/icons-react": ^3.46.0
|
||||||
"@tanstack/react-devtools": ^0.10.8
|
"@tanstack/react-devtools": ^0.10.10
|
||||||
"@tanstack/react-query": ^5.101.2
|
"@tanstack/react-query": ^5.101.4
|
||||||
"@tanstack/react-query-devtools": ^5.101.2
|
"@tanstack/react-query-devtools": ^5.101.4
|
||||||
"@tanstack/react-query-next-experimental": ^5.101.2
|
"@tanstack/react-query-next-experimental": ^5.101.4
|
||||||
"@testcontainers/mysql": ^12.0.4
|
"@testcontainers/mysql": ^12.1.0
|
||||||
"@testcontainers/postgresql": ^12.0.4
|
"@testcontainers/postgresql": ^12.1.0
|
||||||
"@testcontainers/redis": ^12.0.4
|
"@testcontainers/redis": ^12.1.0
|
||||||
"@tiptap/extension-color": 3.27.3
|
"@tiptap/extension-color": 3.30.1
|
||||||
"@tiptap/extension-highlight": 3.27.3
|
"@tiptap/extension-highlight": 3.30.1
|
||||||
"@tiptap/extension-image": 3.27.3
|
"@tiptap/extension-image": 3.30.1
|
||||||
"@tiptap/extension-link": ^3.27.3
|
"@tiptap/extension-link": ^3.30.1
|
||||||
"@tiptap/extension-placeholder": ^3.27.3
|
"@tiptap/extension-placeholder": ^3.30.1
|
||||||
"@tiptap/extension-table": 3.27.3
|
"@tiptap/extension-table": 3.30.1
|
||||||
"@tiptap/extension-table-cell": 3.27.3
|
"@tiptap/extension-table-cell": 3.30.1
|
||||||
"@tiptap/extension-table-header": 3.27.3
|
"@tiptap/extension-table-header": 3.30.1
|
||||||
"@tiptap/extension-table-row": 3.27.3
|
"@tiptap/extension-table-row": 3.30.1
|
||||||
"@tiptap/extension-task-item": 3.27.3
|
"@tiptap/extension-task-item": 3.30.1
|
||||||
"@tiptap/extension-task-list": 3.27.3
|
"@tiptap/extension-task-list": 3.30.1
|
||||||
"@tiptap/extension-text-align": 3.27.3
|
"@tiptap/extension-text-align": 3.30.1
|
||||||
"@tiptap/extension-text-style": 3.27.3
|
"@tiptap/extension-text-style": 3.30.1
|
||||||
"@tiptap/extension-underline": 3.27.3
|
"@tiptap/extension-underline": 3.30.1
|
||||||
"@tiptap/react": ^3.27.3
|
"@tiptap/react": ^3.30.1
|
||||||
"@tiptap/starter-kit": ^3.27.3
|
"@tiptap/starter-kit": ^3.30.1
|
||||||
"@trpc/client": ^11.18.0
|
"@trpc/client": ^11.18.0
|
||||||
"@trpc/next": ^11.18.0
|
"@trpc/next": ^11.18.0
|
||||||
"@trpc/react-query": ^11.18.0
|
"@trpc/react-query": ^11.18.0
|
||||||
"@trpc/server": ^11.18.0
|
"@trpc/server": ^11.18.0
|
||||||
"@trpc/tanstack-react-query": ^11.18.0
|
"@trpc/tanstack-react-query": ^11.18.0
|
||||||
"@turbo/gen": ^2.10.4
|
"@turbo/gen": ^2.10.10
|
||||||
"@types/adm-zip": 0.5.8
|
"@types/adm-zip": 0.5.8
|
||||||
"@types/bcrypt": 6.0.0
|
"@types/bcrypt": 6.0.0
|
||||||
"@types/better-sqlite3": 7.6.13
|
"@types/better-sqlite3": 7.6.13
|
||||||
@@ -82,34 +82,34 @@ catalog:
|
|||||||
"@types/crypto-js": ^4.2.2
|
"@types/crypto-js": ^4.2.2
|
||||||
"@types/css-modules": ^1.0.5
|
"@types/css-modules": ^1.0.5
|
||||||
"@types/dockerode": ^4.0.1
|
"@types/dockerode": ^4.0.1
|
||||||
"@types/node": ^24.13.3
|
"@types/node": ^26.2.0
|
||||||
"@types/node-unifi": ^2.5.1
|
"@types/node-unifi": ^2.5.1
|
||||||
"@types/pg": ^8.20.0
|
"@types/pg": ^8.21.0
|
||||||
"@types/prismjs": ^1.26.6
|
"@types/prismjs": ^1.26.6
|
||||||
"@types/react": 19.2.17
|
"@types/react": 19.2.18
|
||||||
"@types/react-dom": 19.2.3
|
"@types/react-dom": 19.2.4
|
||||||
"@scalar/api-reference-react": ^0.9.54
|
"@scalar/api-reference-react": ^0.9.63
|
||||||
"@types/video.js": ^7.3.58
|
"@types/video.js": ^7.3.58
|
||||||
"@types/ws": ^8.18.1
|
"@types/ws": ^8.18.1
|
||||||
"@types/xml2js": ^0.4.14
|
"@types/xml2js": ^0.4.14
|
||||||
posthog-js: ^1.399.1
|
"posthog-js": ^1.417.1
|
||||||
posthog-node: ^5.40.0
|
"posthog-node": ^5.49.1
|
||||||
"@vitejs/plugin-react": ^6.0.3
|
"@vitejs/plugin-react": ^6.0.5
|
||||||
"@vitest/coverage-v8": ^4.1.10
|
"@vitest/coverage-v8": ^4.1.10
|
||||||
"@vitest/ui": ^4.1.10
|
"@vitest/ui": ^4.1.10
|
||||||
"@xterm/addon-canvas": ^0.7.0
|
"@xterm/addon-canvas": ^0.7.0
|
||||||
"@xterm/addon-fit": 0.11.0
|
"@xterm/addon-fit": 0.11.0
|
||||||
"@xterm/xterm": ^6.0.0
|
"@xterm/xterm": ^6.0.0
|
||||||
adm-zip: 0.5.18
|
"adm-zip": 0.6.0
|
||||||
jszip: ^3.10.1
|
jszip: ^3.10.1
|
||||||
sql.js: ^1.14.1
|
"sql.js": ^1.14.2
|
||||||
babel-plugin-react-compiler: ^1.0.0
|
babel-plugin-react-compiler: ^1.0.0
|
||||||
bcrypt: ^6.0.0
|
bcrypt: ^6.0.0
|
||||||
better-sqlite3: ^12.11.1
|
"better-sqlite3": ^12.11.1
|
||||||
chroma-js: ^3.2.0
|
chroma-js: ^3.2.0
|
||||||
clsx: ^2.1.1
|
clsx: ^2.1.1
|
||||||
concurrently: ^10.0.3
|
"concurrently": ^10.0.5
|
||||||
conventional-changelog-conventionalcommits: ^10.2.0
|
conventional-changelog-conventionalcommits: ^10.3.0
|
||||||
cookie: 2.0.1
|
cookie: 2.0.1
|
||||||
cookies: ^0.9.1
|
cookies: ^0.9.1
|
||||||
croner: 10.0.1
|
croner: 10.0.1
|
||||||
@@ -124,79 +124,104 @@ catalog:
|
|||||||
drizzle-kit: ^0.31.10
|
drizzle-kit: ^0.31.10
|
||||||
drizzle-orm: ^0.45.2
|
drizzle-orm: ^0.45.2
|
||||||
drizzle-zod: ^0.8.3
|
drizzle-zod: ^0.8.3
|
||||||
esbuild: ^0.28.0
|
"esbuild": ^0.28.2
|
||||||
fast-xml-parser: ^5.9.3
|
"fast-xml-parser": ^5.10.1
|
||||||
fastify: ^5.9.0
|
"fastify": ^5.12.0
|
||||||
flag-icons: ^7.5.0
|
flag-icons: ^7.5.0
|
||||||
glob: ^13.0.6
|
glob: ^13.0.6
|
||||||
ical.js: ^2.2.1
|
ical.js: ^2.2.1
|
||||||
ioredis: 5.11.1
|
"ioredis": 6.0.0
|
||||||
isomorphic-dompurify: ^3.18.0
|
"isomorphic-dompurify": ^3.22.0
|
||||||
jotai: ^2.20.1
|
"jotai": ^2.20.2
|
||||||
jsdom: ^29.1.1
|
jsdom: ^30.0.1
|
||||||
json5: ^2.2.3
|
json5: ^2.2.3
|
||||||
jsonpath-plus: ^10.4.0
|
jsonpath-plus: ^10.4.0
|
||||||
ldapts: 8.1.8
|
"ldapts": 9.0.0
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lucide-react: ^1.24.0
|
"lucide-react": ^1.31.0
|
||||||
"mantine-contextmenu": ^9.3.1
|
"mantine-contextmenu": ^9.3.1
|
||||||
mantine-datatable: ^9.3.1
|
"mantine-datatable": ^9.4.0
|
||||||
mantine-react-table: npm:mantine-react-table-open@9.0.3
|
mantine-react-table: npm:mantine-react-table-open@9.0.3
|
||||||
mcp-handler: ^1.1.0
|
"mcp-handler": ^1.1.0
|
||||||
maria2: ^0.4.1
|
maria2: ^0.4.1
|
||||||
mysql2: 3.22.6
|
"mysql2": 3.23.3
|
||||||
next: 16.3.0-preview.9
|
"next": 16.3.1
|
||||||
next-auth: 5.0.0-beta.32
|
next-auth: 5.0.0-beta.31
|
||||||
next-intl: 4.13.1
|
"next-intl": 4.13.6
|
||||||
node-loader: ^2.1.0
|
node-loader: ^2.1.0
|
||||||
|
node-fetch: ^2.7.0
|
||||||
|
"@types/node-fetch": ^2.6.13
|
||||||
octokit: ^5.0.5
|
octokit: ^5.0.5
|
||||||
oxfmt: ^0.58.0
|
oxfmt: ^0.63.0
|
||||||
oxlint: ^1.73.0
|
oxlint: ^1.78.0
|
||||||
pg: ^8.22.0
|
"pg": ^8.23.0
|
||||||
postcss-preset-mantine: ^1.18.0
|
postcss-preset-mantine: ^1.18.0
|
||||||
prismjs: ^1.30.0
|
prismjs: ^1.30.0
|
||||||
proxmox-api: 1.1.1
|
proxmox-api: 1.1.1
|
||||||
react: 19.2.7
|
react: 19.2.8
|
||||||
react-dom: 19.2.7
|
react-dom: 19.2.8
|
||||||
react-error-boundary: ^6.1.2
|
react-error-boundary: ^6.1.2
|
||||||
react-jsx-parser: ^2.4.1
|
react-jsx-parser: ^2.4.1
|
||||||
react-markdown: ^10.1.0
|
react-markdown: ^10.1.0
|
||||||
react-quill-new: ^3.8.3
|
react-quill-new: ^3.8.3
|
||||||
react-simple-code-editor: ^0.14.1
|
react-simple-code-editor: ^0.14.1
|
||||||
recharts: ^3.9.1
|
"recharts": ^3.10.1
|
||||||
sass: ^1.101.0
|
"sass": ^1.102.0
|
||||||
semantic-release: ^25.0.5
|
semantic-release: ^25.0.9
|
||||||
semver-parser: ^4.2.1
|
semver-parser: ^4.2.1
|
||||||
superjson: 2.2.6
|
superjson: 2.2.6
|
||||||
svgson: ^5.3.1
|
svgson: ^5.3.1
|
||||||
testcontainers: ^12.0.4
|
testcontainers: ^12.1.0
|
||||||
timezones-ical-library: ^2.2.1
|
"timezones-ical-library": ^2.3.1
|
||||||
trpc-to-mcp: ^1.3.2
|
trpc-to-mcp: ^1.3.2
|
||||||
trpc-to-openapi: ^3.3.0
|
trpc-to-openapi: ^3.3.0
|
||||||
tsdav: ^2.3.0
|
"tsdav": ^2.3.1
|
||||||
tsx: 4.20.4
|
tsx: 4.23.12
|
||||||
turbo: ^2.10.4
|
turbo: ^2.10.10
|
||||||
typescript: 7.0.2
|
typescript: 7.0.2
|
||||||
vite: ^8.1.4
|
vite: ^8.2.1
|
||||||
undici: 7.28.0
|
"undici": 8.10.0
|
||||||
use-deep-compare-effect: ^1.8.1
|
use-deep-compare-effect: ^1.8.1
|
||||||
video.js: ^8.23.9
|
"video.js": ^8.24.0
|
||||||
vite-tsconfig-paths: ^6.1.1
|
vite-tsconfig-paths: ^6.1.1
|
||||||
vitest: ^4.1.10
|
vitest: ^4.1.10
|
||||||
winston: 3.19.0
|
winston: 3.19.0
|
||||||
ws: ^8.21.0
|
"ws": ^8.21.3
|
||||||
xml2js: ^0.6.2
|
xml2js: ^0.6.2
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
zod: ^4.4.3
|
zod: ^4.4.3
|
||||||
zod-form-data: ^3.0.2
|
"zod-form-data": ^3.0.3
|
||||||
zod-validation-error: ^5.0.0
|
zod-validation-error: ^5.0.0
|
||||||
|
"@docusaurus/module-type-aliases": ^3.10.2
|
||||||
|
"@docusaurus/tsconfig": ^3.10.2
|
||||||
|
"@docusaurus/types": ^3.10.2
|
||||||
|
"@fec/remark-a11y-emoji": ^4.0.2
|
||||||
|
"@tsconfig/docusaurus": ^2.0.9
|
||||||
|
cheerio: ^1.2.0
|
||||||
|
"@docusaurus/core": ^3.10.2
|
||||||
|
"@docusaurus/faster": ^3.10.2
|
||||||
|
"@docusaurus/plugin-content-docs": ^3.10.2
|
||||||
|
"@docusaurus/plugin-sitemap": ^3.10.2
|
||||||
|
"@docusaurus/preset-classic": ^3.10.2
|
||||||
|
"@mdx-js/react": ^3.1.1
|
||||||
|
"@nivo/line": ^0.99.0
|
||||||
|
"@signalwire/docusaurus-plugin-llms-txt": ^1.2.2
|
||||||
|
"@tailwindcss/postcss": ^4.3.3
|
||||||
|
docusaurus-plugin-image-zoom: ^3.0.1
|
||||||
|
leader-line-new: ^1.1.9
|
||||||
|
postcss: ^8.5.26
|
||||||
|
posthog-docusaurus: ^2.0.5
|
||||||
|
prism-react-renderer: ^2.4.1
|
||||||
|
random-word-slugs: ^0.1.7
|
||||||
|
swiper: ^14.1.0
|
||||||
|
tailwindcss: ^4.3.3
|
||||||
|
|
||||||
catalogMode: strict
|
catalogMode: strict
|
||||||
nodeLinker: hoisted
|
nodeLinker: hoisted
|
||||||
strictPeerDependencies: false
|
strictPeerDependencies: false
|
||||||
engineStrict: true
|
engineStrict: true
|
||||||
verifyDepsBeforeRun: warn
|
verifyDepsBeforeRun: warn
|
||||||
minimumReleaseAge: 4320
|
minimumReleaseAge: 0
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- next
|
- next
|
||||||
- "@next/*"
|
- "@next/*"
|
||||||
@@ -210,21 +235,15 @@ minimumReleaseAgeExclude:
|
|||||||
- undici@7.28.0
|
- undici@7.28.0
|
||||||
# Renovate security update: esbuild@0.28.1
|
# Renovate security update: esbuild@0.28.1
|
||||||
- esbuild@0.28.1
|
- esbuild@0.28.1
|
||||||
# Renovate security update: @auth/core@0.41.3
|
|
||||||
- "@auth/core@0.41.3"
|
|
||||||
# Renovate security update: hono@4.13.7
|
|
||||||
- hono@4.13.7
|
|
||||||
# Renovate security update: next-auth@5.0.0-beta.32
|
|
||||||
- next-auth@5.0.0-beta.32
|
|
||||||
overrides:
|
overrides:
|
||||||
"@babel/helpers@<7.26.10": ">=7.29.7"
|
"@babel/helpers@<7.26.10": ">=7.29.7 <8"
|
||||||
"@babel/runtime@<7.26.10": ">=7.29.7"
|
"@babel/runtime@<7.26.10": ">=7.29.7 <8"
|
||||||
"axios@>=1.0.0 <1.8.2": ">=1.18.1"
|
"axios@>=1.0.0 <1.8.2": ">=1.18.1"
|
||||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.7"
|
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.7"
|
||||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7"
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7"
|
||||||
"esbuild@<=0.24.2": ">=0.28.1"
|
"esbuild@<=0.24.2": ">=0.28.1"
|
||||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.6"
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.6"
|
||||||
"hono@<4.6.5": ">=4.13.7"
|
"hono@<4.6.5": ">=4.12.28"
|
||||||
"linkifyjs@<4.3.2": ">=4.3.3"
|
"linkifyjs@<4.3.2": ">=4.3.3"
|
||||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.16"
|
"nanoid@>=4.0.0 <5.0.9": ">=5.1.16"
|
||||||
"prismjs@<1.30.0": ">=1.30.0"
|
"prismjs@<1.30.0": ">=1.30.0"
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { parse } from "@babel/parser";
|
|||||||
|
|
||||||
export const getRuntimeModuleSpecifiers = (sourceText: string, fileName: string) => {
|
export const getRuntimeModuleSpecifiers = (sourceText: string, fileName: string) => {
|
||||||
try {
|
try {
|
||||||
const ast = parse(sourceText, { sourceFilename: fileName, sourceType: "module", plugins: ["typescript", "jsx"] });
|
const ast = parse(sourceText, {
|
||||||
|
sourceFilename: fileName,
|
||||||
|
sourceType: "module",
|
||||||
|
plugins: ["typescript", ...(fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ["jsx"] : [])],
|
||||||
|
});
|
||||||
return ast.program.body.flatMap((statement) => {
|
return ast.program.body.flatMap((statement) => {
|
||||||
if (statement.type === "ImportDeclaration") {
|
if (statement.type === "ImportDeclaration") {
|
||||||
if (statement.importKind === "type" || statement.importKind === "typeof") return [];
|
if (statement.importKind === "type" || statement.importKind === "typeof") return [];
|
||||||
|
|||||||
+8
-25
@@ -7,31 +7,14 @@ export PGID=${PGID:-0}
|
|||||||
echo "Starting with UID='$PUID', GID='$PGID'"
|
echo "Starting with UID='$PUID', GID='$PGID'"
|
||||||
|
|
||||||
if [ "${PUID}" != "0" ] || [ "${PGID}" != "0" ]; then
|
if [ "${PUID}" != "0" ] || [ "${PGID}" != "0" ]; then
|
||||||
# Only chown the paths the non-root user must actually write to at runtime.
|
# The below command will change the owner of all files in the /app directory (except node_modules) to the new UID and GID
|
||||||
# Recursively chowning the whole /app tree is extremely slow on network- or
|
echo "Changing owner to $PUID:$PGID, this will take about 10 seconds..."
|
||||||
# HDD-backed storage (e.g. TrueNAS Apps), because the Next.js standalone
|
find . -name 'node_modules' -prune -o -mindepth 1 -maxdepth 1 -exec chown -R $PUID:$PGID {} +
|
||||||
# bundle and its node_modules contain tens of thousands of tiny files. This
|
chown -R $PUID:$PGID /var/cache/nginx
|
||||||
# turned a ~10s step into 15+ minutes for some users (#2190). Application
|
chown -R $PUID:$PGID /var/log/nginx
|
||||||
# code, migrations and native modules are only read/executed, so they stay
|
chown -R $PUID:$PGID /var/lib/nginx
|
||||||
# owned by root and do not need chowning.
|
chown -R $PUID:$PGID /run/nginx/nginx.pid
|
||||||
echo "Changing owner to $PUID:$PGID..."
|
chown -R $PUID:$PGID /etc/nginx
|
||||||
# Application data volume (db, redis dumps, trusted certificates).
|
|
||||||
# Ensure the known subdirectories exist and migrate ownership of any
|
|
||||||
# existing content, so a persistent volume with stale or root-owned
|
|
||||||
# entries stays writable after a PUID/PGID change. These trees are small
|
|
||||||
# (a single sqlite db, a redis dump, a few certs), unlike the /app tree.
|
|
||||||
mkdir -p /appdata/db /appdata/redis /appdata/trusted-certificates
|
|
||||||
chown "${PUID}:${PGID}" /appdata
|
|
||||||
chown -R "${PUID}:${PGID}" /appdata/db /appdata/redis /appdata/trusted-certificates
|
|
||||||
# Next.js runtime cache (image optimization, fetch cache, ...)
|
|
||||||
mkdir -p /app/apps/nextjs/.next/cache
|
|
||||||
chown -R "${PUID}:${PGID}" /app/apps/nextjs/.next/cache
|
|
||||||
# nginx runtime directories and the rendered configuration
|
|
||||||
chown -R "${PUID}:${PGID}" /var/cache/nginx
|
|
||||||
chown -R "${PUID}:${PGID}" /var/log/nginx
|
|
||||||
chown -R "${PUID}:${PGID}" /var/lib/nginx
|
|
||||||
chown -R "${PUID}:${PGID}" /run/nginx/nginx.pid
|
|
||||||
chown -R "${PUID}:${PGID}" /etc/nginx
|
|
||||||
echo "Changing owner to $PUID:$PGID, done."
|
echo "Changing owner to $PUID:$PGID, done."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+5
-6
@@ -21,18 +21,20 @@ fi
|
|||||||
export AUTH_SECRET=$(openssl rand -base64 32)
|
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||||
|
|
||||||
# Start nginx proxy
|
# Start nginx proxy
|
||||||
# 1. Create the nginx configuration file from the template
|
# 1. Replace the HOSTNAME in the nginx template file
|
||||||
# 2. Start the nginx server
|
# 2. Create the nginx configuration file from the template
|
||||||
|
# 3. Start the nginx server
|
||||||
# Only listen on IPv6 when the host actually has IPv6 configured (#4596).
|
# Only listen on IPv6 when the host actually has IPv6 configured (#4596).
|
||||||
# Otherwise nginx aborts with "socket() [::]:7575 failed (97: Address family
|
# Otherwise nginx aborts with "socket() [::]:7575 failed (97: Address family
|
||||||
# not supported by protocol)". Check HOMARR_DISABLE_IPV6 first so users can
|
# not supported by protocol)". Check HOMARR_DISABLE_IPV6 first so users can
|
||||||
# force IPv4-only even when the host supports IPv6, then probe for IPv6
|
# force IPv4-only even when the host supports IPv6, then probe for IPv6
|
||||||
# addresses via grep (not -s: proc files always report size 0).
|
# addresses via grep (not -s: proc files always report size 0).
|
||||||
|
export HOSTNAME
|
||||||
export NGINX_LISTEN_IPV6=''
|
export NGINX_LISTEN_IPV6=''
|
||||||
if [ "${HOMARR_DISABLE_IPV6:-false}" != "true" ] && grep -q . /proc/net/if_inet6 2>/dev/null; then
|
if [ "${HOMARR_DISABLE_IPV6:-false}" != "true" ] && grep -q . /proc/net/if_inet6 2>/dev/null; then
|
||||||
NGINX_LISTEN_IPV6='listen [::]:7575;'
|
NGINX_LISTEN_IPV6='listen [::]:7575;'
|
||||||
fi
|
fi
|
||||||
envsubst '${NGINX_LISTEN_IPV6}' < /etc/nginx/templates/nginx.conf > /etc/nginx/nginx.conf
|
envsubst '${HOSTNAME} ${NGINX_LISTEN_IPV6}' < /etc/nginx/templates/nginx.conf > /etc/nginx/nginx.conf
|
||||||
# Start services in the background and store their PIDs
|
# Start services in the background and store their PIDs
|
||||||
nginx -g 'daemon off;' &
|
nginx -g 'daemon off;' &
|
||||||
NGINX_PID=$!
|
NGINX_PID=$!
|
||||||
@@ -65,9 +67,6 @@ terminate() {
|
|||||||
|
|
||||||
trap terminate TERM INT
|
trap terminate TERM INT
|
||||||
|
|
||||||
# Next.js standalone uses HOSTNAME as its bind address. Docker's generated
|
|
||||||
# hostname can be unresolvable, so bind explicitly while nginx uses loopback.
|
|
||||||
export HOSTNAME=0.0.0.0
|
|
||||||
node apps/nextjs/server.js &
|
node apps/nextjs/server.js &
|
||||||
NEXTJS_PID=$!
|
NEXTJS_PID=$!
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontentLine truncated
|
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontentLine truncated
|
||||||
@@ -1 +1 @@
|
|||||||
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowLine truncated
|
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowLine truncated
|
||||||
@@ -5,7 +5,7 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
charm.land/bubbles/v2 v2.1.1
|
charm.land/bubbles/v2 v2.1.1
|
||||||
charm.land/bubbletea/v2 v2.0.8
|
charm.land/bubbletea/v2 v2.0.8
|
||||||
charm.land/lipgloss/v2 v2.0.5
|
charm.land/lipgloss/v2 v2.0.6
|
||||||
github.com/atotto/clipboard v0.1.4
|
github.com/atotto/clipboard v0.1.4
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
)
|
)
|
||||||
@@ -13,20 +13,20 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
github.com/charmbracelet/x/ansi v0.11.8 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
github.com/mattn/go-runewidth v0.0.27 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v1.0.0 // indirect
|
||||||
golang.org/x/sync v0.21.0 // indirect
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
)
|
)
|
||||||
+18
-17
@@ -2,8 +2,8 @@ charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
|
|||||||
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
|
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
|
||||||
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
|
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
|
||||||
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
|
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
|
||||||
charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
|
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
|
||||||
charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
|
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||||
@@ -12,10 +12,10 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
|
|||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 h1:3FmWoGNWK4STvqg0O0Aeav2T7rodWJAPeF0QpH+8gFw=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be h1:qEvkJy1sjJXP+yf8IH2o13NV+Rh4nIUHjaLNJ+pWxpI=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7/go.mod h1:f/jRa757WUmaOZrbPspXymbg/GnbF+rwe4OLsG7aXYo=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
@@ -31,10 +31,10 @@ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
|
||||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -42,15 +42,16 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
|
||||||
|
github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
Reference in new issue
Block a user