diff --git a/.eslintrc.js b/.eslintrc.js
index 2490d3526..bddf82eae 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -18,6 +18,7 @@ module.exports = {
project: './tsconfig.json',
},
rules: {
+ 'import/no-cycle': 'off',
'react/react-in-jsx-scope': 'off',
'react/no-children-prop': 'off',
'unused-imports/no-unused-imports': 'warn',
@@ -28,5 +29,6 @@ module.exports = {
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
+ 'linebreak-style': 0,
},
};
diff --git a/README.md b/README.md
index 3331187db..14fca34f2 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,9 @@
Join the discord! — Don't forget to star the repo if you are enjoying the project!
+
- Demo ↗️ • Install ➡️ • Read the Docs 📄
+ Demo ↗️ • Install ➡️
---
@@ -36,16 +37,14 @@ Homarr is a simple and lightweight homepage for your server, that helps you easi
It integrates with the services you use to display information on the homepage (E.g. Show upcoming Sonarr/Radarr releases).
-For a full list of integrations, [head over to our documentation](https://homarr.vercel.app/docs/advanced-configuration/integrations).
+For a full list of integrations, [head over to our documentation](https://homarr.dev/docs/advanced-configuration/integrations).
If you have any questions about Homarr or want to share information with us, please go to one of the following places:
- [Github Discussions](https://github.com/ajnart/homarr/discussions)
- [Discord Server](https://discord.gg/aCsmEV5RgA)
-*Before you file an [issue](https://github.com/ajnart/homarr/issues/new/choose), make sure you have read the [known issues](#-known-issues) section.*
-
-**For more information, [read the documentation!](https://homarr.vercel.app/docs/about)**
+**For more information, [read the documentation!](https://homarr.dev/docs/about)**
Table of Contents
@@ -53,10 +52,7 @@ If you have any questions about Homarr or want to share information with us, ple
- [✨ Features](#-features)
- [👀 Preview](#-preview)
-- [💥 Known Issues](#-known-issues)
-- [🚀 Installation](#-installation)
- - [🐳 Deploying from Docker Image](#-deploying-from-docker-image)
- - [🛠️ Building from Source](#️-building-from-source)
+- [🛠️ Running a dev environment](#️-running-a-dev-environment)
- [💖 Contributing](#-contributing)
- [📜 License](#-license)
@@ -87,58 +83,7 @@ If you have any questions about Homarr or want to share information with us, ple
---
-## 💥 Known Issues
-- Posters on the Calendar get blocked by adblockers. (IMDb posters)
-
-**[⤴️ Back to Top](#homarr)**
-
----
-
-## 🚀 Installation
-### 🐳 Deploying from Docker Image
-> Supported architectures: x86-64, ARM, ARM64
-
-_Requirements_:
-- [Docker](https://docs.docker.com/get-docker/)
-
-**Standard Docker Install**
-```bash
-docker run \
- --name homarr \
- --restart unless-stopped \
- -p 7575:7575 \
- -v ./homarr/configs:/app/data/configs \
- -v ./homarr/icons:/app/public/icons \
- -d ghcr.io/ajnart/homarr:latest
-```
-
-**Docker Compose**
-```yml
-version: '3'
-#---------------------------------------------------------------------#
-# Homarr - A homepage for your server. #
-#---------------------------------------------------------------------#
-services:
- homarr:
- container_name: homarr
- image: ghcr.io/ajnart/homarr:latest
- restart: unless-stopped
- volumes:
- - ./homarr/configs:/app/data/configs
- - ./homarr/icons:/app/public/icons
- ports:
- - '7575:7575'
-```
-
-```sh
-docker compose up -d
-```
-
-*Getting EACCESS errors in the logs? Try running `sudo chmod 777 /directory-you-mounted-to`!*
-
-**[⤴️ Back to Top](#homarr)**
-
-### 🛠️ Building from Source
+### 🛠️ Running a dev environment
_Requirements_:
- [Git](https://git-scm.com/downloads)
@@ -197,7 +142,7 @@ SOFTWARE.
---
- Thank you for visiting! For more information read the documentation!
+ Thank you for visiting! For more information read the documentation!
diff --git a/data/configs/default.json b/data/configs/default.json
index 3f2acea0d..6da172167 100644
--- a/data/configs/default.json
+++ b/data/configs/default.json
@@ -1,20 +1,383 @@
{
- "name": "default",
- "services": [
+ "schemaVersion": 1,
+ "configProperties": {
+ "name": "default"
+ },
+ "categories": [
{
- "name": "example",
- "id": "09c45847-8afc-4c1a-9697-f03192de948a",
- "type": "Other",
- "icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif",
- "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f",
+ "position": 1,
+ "name": "Welcome to Homarr 🎉",
+ "type": "category"
+ }
+ ],
+ "wrappers": [
+ {
+ "id": "default",
+ "position": 1
+ }
+ ],
+ "apps": [
+ {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337",
+ "name": "Discord",
+ "url": "https://discord.com/invite/aCsmEV5RgA",
+ "behaviour": {
+ "onClickUrl": "https://discord.com/invite/aCsmEV5RgA",
+ "isOpeningNewTab": true,
+ "externalUrl": "https://discord.com/invite/aCsmEV5RgA"
+ },
+ "network": {
+ "enabledStatusChecker": false,
+ "okStatus": [
+ 200
+ ]
+ },
+ "appearance": {
+ "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png"
+ },
+ "integration": {
+ "type": null,
+ "properties": []
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "md": {
+ "location": {
+ "x": 3,
+ "y": 1
+ },
+ "size": {
+ "width": 3,
+ "height": 1
+ }
+ },
+ "sm": {
+ "location": {
+ "x": 2,
+ "y": 1
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 2,
+ "y": 1
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ }
+ }
+ },
+ {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330",
+ "name": "Contribute",
+ "url": "https://github.com/ajnart/homarr",
+ "behaviour": {
+ "onClickUrl": "https://github.com/ajnart/homarr",
+ "externalUrl": "https://github.com/ajnart/homarr",
+ "isOpeningNewTab": true
+ },
+ "network": {
+ "enabledStatusChecker": false,
+ "okStatus": []
+ },
+ "appearance": {
+ "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png"
+ },
+ "integration": {
+ "type": null,
+ "properties": []
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "md": {
+ "location": {
+ "x": 2,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "sm": {
+ "location": {
+ "x": 0,
+ "y": 2
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 4,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 2
+ }
+ }
+ }
+ },
+ {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
+ "name": "Donate",
+ "url": "https://ko-fi.com/ajnart",
+ "behaviour": {
+ "onClickUrl": "https://ko-fi.com/ajnart",
+ "externalUrl": "https://ko-fi.com/ajnart",
+ "isOpeningNewTab": true
+ },
+ "network": {
+ "enabledStatusChecker": false,
+ "okStatus": [
+ 200
+ ]
+ },
+ "appearance": {
+ "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png"
+ },
+ "integration": {
+ "type": null,
+ "properties": []
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "md": {
+ "location": {
+ "x": 2,
+ "y": 1
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ },
+ "sm": {
+ "location": {
+ "x": 2,
+ "y": 2
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 3,
+ "y": 1
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ }
+ }
+ },
+ {
+ "id": "5df743d9-5cb1-457c-85d2-64ff86855652",
+ "name": "Documentation",
+ "url": "https://homarr.dev",
+ "behaviour": {
+ "onClickUrl": "https://homarr.dev",
+ "externalUrl": "https://homarr.dev",
+ "isOpeningNewTab": true
+ },
+ "network": {
+ "enabledStatusChecker": false,
+ "okStatus": [
+ 200
+ ]
+ },
+ "appearance": {
+ "iconUrl": "/imgs/logo/logo.png"
+ },
+ "integration": {
+ "type": null,
+ "properties": []
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "md": {
+ "location": {
+ "x": 0,
+ "y": 1
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "sm": {
+ "location": {
+ "x": 0,
+ "y": 0
+ },
+ "size": {
+ "width": 1,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 0,
+ "y": 1
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ }
+ }
+ }
+ ],
+ "widgets": [
+ {
+ "id": "date",
+ "properties": {
+ "display24HourFormat": true
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "sm": {
+ "location": {
+ "x": 0,
+ "y": 1
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "md": {
+ "location": {
+ "x": 4,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 2,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ }
+ }
+ },
+ {
+ "id": "weather",
+ "properties": {
+ "displayInFahrenheit": false,
+ "location": "Paris"
+ },
+ "area": {
+ "type": "category",
+ "properties": {
+ "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
+ }
+ },
+ "shape": {
+ "md": {
+ "location": {
+ "x": 0,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "sm": {
+ "location": {
+ "x": 1,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ },
+ "lg": {
+ "location": {
+ "x": 0,
+ "y": 0
+ },
+ "size": {
+ "width": 2,
+ "height": 1
+ }
+ }
+ }
}
],
"settings": {
- "searchUrl": "https://google.com/search?q="
- },
- "modules": {
- "Search Bar": {
- "enabled": true
+ "common": {
+ "searchEngine": {
+ "type": "google",
+ "properties": {}
+ }
+ },
+ "customization": {
+ "layout": {
+ "enabledLeftSidebar": false,
+ "enabledRightSidebar": false,
+ "enabledDocker": false,
+ "enabledPing": false,
+ "enabledSearchbar": true
+ },
+ "pageTitle": "Homarr v0.11 ⭐️",
+ "logoImageUrl": "/imgs/logo/logo.png",
+ "faviconUrl": "/imgs/favicon/favicon-squared",
+ "backgroundImageUrl": "",
+ "customCss": "",
+ "colors": {
+ "primary": "red",
+ "secondary": "yellow",
+ "shade": 7
+ },
+ "appOpacity": 100
}
}
}
\ No newline at end of file
diff --git a/data/constants.ts b/data/constants.ts
index 8ca22cd0b..bad03062e 100644
--- a/data/constants.ts
+++ b/data/constants.ts
@@ -1,2 +1,3 @@
export const REPO_URL = 'ajnart/homarr';
-export const CURRENT_VERSION = 'v0.10.7';
+export const CURRENT_VERSION = 'v0.11.0';
+export const ICON_PICKER_SLICE_LIMIT = 36;
diff --git a/next.config.js b/next.config.js
index b9989cfa8..dde3ec977 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,3 @@
-const { env } = require('process');
-
const { i18n } = require('./next-i18next.config');
const withBundleAnalyzer = require('@next/bundle-analyzer')({
@@ -10,7 +8,7 @@ module.exports = withBundleAnalyzer({
images: {
domains: ['cdn.jsdelivr.net'],
},
- reactStrictMode: false,
+ reactStrictMode: true,
output: 'standalone',
i18n,
});
diff --git a/package.json b/package.json
index bef8a0f18..2894dad75 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "homarr",
- "version": "0.10.7",
+ "version": "0.11.0",
"description": "Homarr - A homepage for your server.",
"license": "MIT",
"repository": {
@@ -32,27 +32,27 @@
"@dnd-kit/utilities": "^3.2.0",
"@emotion/react": "^11.10.5",
"@emotion/server": "^11.10.0",
- "@mantine/carousel": "^5.1.0",
- "@mantine/core": "^5.7.2",
- "@mantine/dates": "^5.7.2",
- "@mantine/dropzone": "^5.7.2",
- "@mantine/form": "^5.7.2",
- "@mantine/hooks": "^5.7.2",
- "@mantine/modals": "^5.7.2",
- "@mantine/next": "^5.2.3",
- "@mantine/notifications": "^5.7.2",
- "@mantine/prism": "^5.0.0",
+ "@mantine/carousel": "^5.9.3",
+ "@mantine/core": "^5.9.3",
+ "@mantine/dates": "^5.9.3",
+ "@mantine/dropzone": "^5.9.3",
+ "@mantine/form": "^5.9.3",
+ "@mantine/hooks": "^5.9.3",
+ "@mantine/modals": "^5.9.3",
+ "@mantine/next": "^5.9.3",
+ "@mantine/notifications": "^5.9.3",
+ "@mantine/prism": "^5.9.3",
"@nivo/core": "^0.79.0",
"@nivo/line": "^0.79.1",
- "@tabler/icons": "^1.78.0",
+ "@tabler/icons": "^1.106.0",
"@tanstack/react-query": "^4.2.1",
- "add": "^2.0.6",
"axios": "^0.27.2",
"consola": "^2.15.3",
"cookies-next": "^2.1.1",
"dayjs": "^1.11.6",
"dockerode": "^3.3.2",
"embla-carousel-react": "^7.0.0",
+ "fily-publish-gridstack": "^0.0.13",
"framer-motion": "^6.5.1",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
@@ -61,6 +61,7 @@
"next": "12.2.0",
"next-i18next": "^11.3.0",
"nzbget-api": "^0.0.3",
+ "ping": "^0.4.2",
"prism-react-renderer": "^1.3.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -68,13 +69,15 @@
"sharp": "^0.30.7",
"systeminformation": "^5.12.1",
"uuid": "^8.3.2",
- "yarn": "^1.22.19"
+ "yarn": "^1.22.19",
+ "zustand": "^4.1.4"
},
"devDependencies": {
"@next/bundle-analyzer": "^12.1.4",
"@next/eslint-plugin-next": "^12.1.4",
"@types/dockerode": "^3.3.9",
"@types/node": "17.0.1",
+ "@types/ping": "^0.4.1",
"@types/react": "17.0.1",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.30.7",
@@ -92,6 +95,7 @@
"eslint-plugin-unused-imports": "^2.0.0",
"jest": "^28.1.3",
"prettier": "^2.7.1",
+ "sass": "^1.56.1",
"typescript": "^4.7.4"
},
"resolutions": {
diff --git a/public/imgs/favicon/favicon-squared.png b/public/imgs/favicon/favicon-squared.png
index 66e321630..25581ea5c 100644
Binary files a/public/imgs/favicon/favicon-squared.png and b/public/imgs/favicon/favicon-squared.png differ
diff --git a/public/locales/da/authentication/login.json b/public/locales/da/authentication/login.json
index d81ca6743..8443dd103 100644
--- a/public/locales/da/authentication/login.json
+++ b/public/locales/da/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Velkommen tilbage!",
- "text": "Angiv venligst adgangskoden",
+ "text": "Indtast venligst din adgangskode",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Din adgangskode er ved at blive tjekket..."
},
"correct": {
- "title": "Adgangskode korrekt, omdirigerer dig..."
+ "title": "Log ind vellykket, omdirigerer..."
},
"wrong": {
- "title": "Adgangskoden er forkert, prøv venligst igen."
+ "title": "Kodeordet du tastede ind, var forkert. Prøv venligst igen."
}
}
}
diff --git a/public/locales/da/common.json b/public/locales/da/common.json
index d0ee0bd85..42592511e 100644
--- a/public/locales/da/common.json
+++ b/public/locales/da/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Gem"
+ "save": "Gem",
+ "about": "Om",
+ "cancel": "Annuller",
+ "close": "Luk",
+ "delete": "Slet",
+ "ok": "OK",
+ "edit": "Rediger",
+ "version": "Version",
+ "changePosition": "Ændre placering",
+ "remove": "Fjern",
+ "removeConfirm": "Er du sikker på, at du ønsker at fjerne {{item}} ?",
+ "sections": {
+ "settings": "Indstillinger",
+ "dangerZone": "Farezone"
+ },
+ "secrets": {
+ "apiKey": "API nøgle",
+ "username": "Brugernavn",
+ "password": "Adgangskode"
},
"tip": "Tip: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutter",
"hours": "timer"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/da/layout/add-service-app-shelf.json b/public/locales/da/layout/add-service-app-shelf.json
index 80d76446b..c990a8485 100644
--- a/public/locales/da/layout/add-service-app-shelf.json
+++ b/public/locales/da/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Avancerede indstillinger",
"form": {
- "httpStatusCodes": {
- "label": "HTTP status kode",
- "placeholder": "Vælg gyldige statuskoder",
- "clearButtonLabel": "Ryd valgte",
- "nothingFound": "Intet fundet"
- },
"openServiceInNewTab": {
"label": "Åbn tjeneste i ny fane"
},
diff --git a/public/locales/da/layout/element-selector/selector.json b/public/locales/da/layout/element-selector/selector.json
new file mode 100644
index 000000000..f6e731cf9
--- /dev/null
+++ b/public/locales/da/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Tilføj et nyt felt",
+ "text": "Felter er det vigtigste element i Homarr. De bruges til at vise dine apps og andre oplysninger. Du kan tilføje så mange felter, som du ønsker."
+ },
+ "widgetDescription": "Widgets interagerer med dine apps for at give dig mere kontrol over dine programmer. De kræver normalt nogle få konfigurationer, før de kan bruges.",
+ "goBack": "Gå tilbage til det forrige trin",
+ "actionIcon": {
+ "tooltip": "Tilføj et felt"
+ }
+}
diff --git a/public/locales/da/layout/header/actions/toggle-edit-mode.json b/public/locales/da/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..e4c5db360
--- /dev/null
+++ b/public/locales/da/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "I redigeringstilstand kan du justere felter og konfigurere apps. Ændringerne gemmes først, når du forlader redigeringstilstand.",
+ "button": {
+ "disabled": "Gå i redigeringstilstand",
+ "enabled": "Afslut og gem"
+ },
+ "popover": {
+ "title": "Redigeringstilstand er aktiveret for <1>{{size}}1> størrelse",
+ "text": "Du kan justere og konfigurere dine apps nu. Ændringer er ikke gemt indtil du forlader redigeringstilstanden"
+ },
+ "screenSizes": {
+ "small": "lille",
+ "medium": "mellem",
+ "large": "stor"
+ }
+}
diff --git a/public/locales/da/layout/mobile/drawer.json b/public/locales/da/layout/mobile/drawer.json
new file mode 100644
index 000000000..c8f63000f
--- /dev/null
+++ b/public/locales/da/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} sidebjælke"
+}
diff --git a/public/locales/da/layout/modals/about.json b/public/locales/da/layout/modals/about.json
new file mode 100644
index 000000000..d60c97911
--- /dev/null
+++ b/public/locales/da/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr er et elegant , moderne dashboard, der giver dig alle dine apps og tjenester lige ved hånden. Med Homarr kan du få adgang til og styre alt på ét praktisk sted. Homarr integrerer problemfrit med de apps, du har tilføjet, og giver dig værdifulde oplysninger og fuld kontrol. Installationen er en leg, og Homarr understøtter en lang række implementeringsmetoder.",
+ "i18n": "Indlæst I18n oversættelse navnerum",
+ "locales": "Konfigurerede I18n landestandarder",
+ "contact": "Har du problemer eller spørgsmål? Kontakt os!",
+ "addToDashboard": "Tilføj til dashboard"
+}
diff --git a/public/locales/da/layout/modals/add-app.json b/public/locales/da/layout/modals/add-app.json
new file mode 100644
index 000000000..49d7d0241
--- /dev/null
+++ b/public/locales/da/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Generelt",
+ "behaviour": "Adfærd",
+ "network": "Netværk",
+ "appearance": "Udseende",
+ "integration": "Integration"
+ },
+ "general": {
+ "appname": {
+ "label": "App navn",
+ "description": "Bruges til visning af appen på dashboardet."
+ },
+ "internalAddress": {
+ "label": "Intern adresse",
+ "description": "Appens interne IP."
+ },
+ "externalAddress": {
+ "label": "Ekstern adresse",
+ "description": "URL-adresse, der åbnes, når du klikker på appen."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Åbn i nyt faneblad",
+ "description": "Åbn appen i en ny fane i stedet for den aktuelle fane."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Statuskontrol",
+ "description": "Kontrollerer, om din app er online ved hjælp af en simpel HTTP(S)-anmodning."
+ },
+ "statusCodes": {
+ "label": "HTTP statuskoder",
+ "description": "De HTTP-statuskoder, der betragtes som online."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "App Ikon",
+ "description": "Det ikon, der vises på dashboarded."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Konfiguration af integration",
+ "description": "Den integrationskonfiguration, der skal bruges til at oprette forbindelse til din app.",
+ "placeholder": "Vælg en integration",
+ "defined": "Defineret",
+ "undefined": "Udefineret",
+ "public": "Offentlig",
+ "private": "Privat",
+ "explanationPrivate": "En privat hemmelighed sendes kun én gang til serveren. Når din browser har opdateret siden, vil den aldrig blive sendt igen.",
+ "explanationPublic": "En offentlig hemmelighed vil altid blive sendt til klienten og er tilgængelig via API'en. Den bør ikke indeholde fortrolige værdier som f. eks. brugernavne, adgangskoder, tokens, certifikater og lignende!"
+ },
+ "secrets": {
+ "description": "Hvis du vil opdatere en hemmelighed, skal du indtaste en værdi og klikke på knappen Gem. Hvis du vil fjerne en hemmelighed, skal du bruge knappen \"clear\".",
+ "warning": "Dine legitimationsoplysninger fungerer som adgang til dine integrationer, og du bør aldrig dele dem med andre. Det officielle Homarr-team vil aldrig bede om legitimationsoplysninger. Sørg for at opbevare og administrere dine hemmeligheder sikkert .",
+ "clear": "Ryd hemmelighed",
+ "save": "Gem hemmelighed",
+ "update": "Opdater hemmelighed"
+ }
+ },
+ "validation": {
+ "popover": "Din formular indeholder ugyldige data. Derfor kan den ikke gemmes. Løs alle problemer og klik på denne knap igen for at gemme dine ændringer"
+ }
+}
diff --git a/public/locales/da/layout/modals/change-position.json b/public/locales/da/layout/modals/change-position.json
new file mode 100644
index 000000000..84261868a
--- /dev/null
+++ b/public/locales/da/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X akse position",
+ "width": "Bredde",
+ "height": "Højde",
+ "yPosition": "Y akse position",
+ "zeroOrHigher": "0 eller højere",
+ "betweenXandY": "Mellem {{min}} og {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/da/layout/screen-sizes.json b/public/locales/da/layout/screen-sizes.json
new file mode 100644
index 000000000..8c747f1da
--- /dev/null
+++ b/public/locales/da/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "lille",
+ "medium": "mellem",
+ "large": "stor"
+ }
+}
diff --git a/public/locales/da/layout/tools.json b/public/locales/da/layout/tools.json
new file mode 100644
index 000000000..85fbf92c4
--- /dev/null
+++ b/public/locales/da/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Du har i øjeblikket ingen værktøjer"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Søg efter ikoner...",
+ "searchLimitationTitle": "Søgningen er begrænset til {{max}} ikoner",
+ "searchLimitationMessage": "For at holde det hele hurtigt og hurtigt er søgningen begrænset til {{max}} ikoner. Brug søgefeltet til at finde flere ikoner"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/da/modules/calendar.json b/public/locales/da/modules/calendar.json
index ab77fa36b..a32cb723b 100644
--- a/public/locales/da/modules/calendar.json
+++ b/public/locales/da/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Kalender",
- "description": "Et kalendermodul til visning af kommende udgivelser. Det interagerer med Sonarr- og Radarr-API'erne.",
+ "description": "Viser en kalender med kommende udgivelser fra understøttede integrationer.",
"settings": {
+ "title": "Indstillinger for kalender widget",
"sundayStart": {
"label": "Søndag første ugedag"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr udgivelsestype"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/common.json b/public/locales/da/modules/common.json
index 525018e17..68d59d38a 100644
--- a/public/locales/da/modules/common.json
+++ b/public/locales/da/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Indstillinger"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Der er fundet en ubrugt parameter i konfigurationen {{key}}. Homarr kan ikke fortolke og bruge denne parameter. For at undgå uventet adfærd skal du sikkerhedskopiere din konfiguration og rette konfigurationen."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/dashdot.json b/public/locales/da/modules/dashdot.json
index 465ebcbf2..beefc98aa 100644
--- a/public/locales/da/modules/dashdot.json
+++ b/public/locales/da/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Et modul til visning af graferne for din kørende Dash. instans.",
+ "description": "Viser graferne for en ekstern Dash. instans i Homarr.",
"settings": {
+ "title": "Indstillinger for Dash. widget",
"cpuMultiView": {
"label": "CPU Multikerne Visning"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/date.json b/public/locales/da/modules/date.json
index 3e52aa6de..f40ee2391 100644
--- a/public/locales/da/modules/date.json
+++ b/public/locales/da/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Dato",
- "description": "Vis det aktuelle klokkeslæt og den aktuelle dato på et kort",
+ "name": "Dato og tid",
+ "description": "Viser aktuel dag og klokkeslæt.",
"settings": {
+ "title": "Indstillinger for dato og tid widget",
"display24HourFormat": {
"label": "Vis fuld tid (24-timer)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/dlspeed.json b/public/locales/da/modules/dlspeed.json
index 737c0f075..4e4811ed5 100644
--- a/public/locales/da/modules/dlspeed.json
+++ b/public/locales/da/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Download hastighed",
- "description": "Vis den aktuelle downloadhastighed for understøttede tjenester"
+ "description": "Viser download- og uploadhastigheden for understøttede integrationer."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/docker.json b/public/locales/da/modules/docker.json
index 154f40f14..a5b6cf21f 100644
--- a/public/locales/da/modules/docker.json
+++ b/public/locales/da/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Tillader dig nemt at administrere dine docker containere"
+ "description": "Giver dig mulighed for nemt at se og administrere alle dine Docker Containers."
},
"search": {
"placeholder": "Søg efter container- eller imagenavn"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Tilføj tjeneste",
- "message": "Tilføj tjeneste til Homarr"
+ "title": "Tilføj app",
+ "message": "Tilføj app til Homarr"
},
"restart": {
"title": "Genstart"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Docker integration mislykkedes",
- "message": "Har du glemt at montere docker socket ?"
+ "message": "Har du glemt at mounte docker socket?"
},
"unknownError": {
"title": "Der opstod en fejl"
},
"oneServiceAtATime": {
- "title": "Du må kun tilføje én tjeneste ad gangen!"
+ "title": "Tilføj kun én app eller tjeneste ad gangen!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/overseerr.json b/public/locales/da/modules/overseerr.json
index ad0e86084..0ebd9a9bd 100644
--- a/public/locales/da/modules/overseerr.json
+++ b/public/locales/da/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Giver dig mulighed for at søge og tilføje medier fra Overseerr/Jellyseerr"
+ "description": "Giver dig mulighed for at søge og tilføje medier fra Overseerr eller Jellyseerr."
},
"popup": {
"item": {
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/ping.json b/public/locales/da/modules/ping.json
index 092f95ca4..c8ca496cd 100644
--- a/public/locales/da/modules/ping.json
+++ b/public/locales/da/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Giver dig mulighed for at kontrollere, om tjenesten er oppe eller returnerer en bestemt HTTP-statuskode."
+ "description": "Viser en statusindikator afhængig af HTTP-svarkoden for en given URL."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Indlæser..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/search.json b/public/locales/da/modules/search.json
index 0c37951eb..876c99352 100644
--- a/public/locales/da/modules/search.json
+++ b/public/locales/da/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Søgebjælke",
- "description": "Søgebjælke til at søge på nettet, Youtube, Torrents eller Overseerr"
+ "description": "En søgelinje, der giver dig mulighed for at søge i din brugerdefinerede søgemaskine, YouTube og understøttede integrationer."
},
"input": {
"placeholder": "Søg på nettet..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "Web",
- "description": "Søg ved hjælp af din søgemaskine (defineret i indstillinger)"
+ "description": "Søg..."
},
"youtube": {
"name": "YouTube",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Søg efter film og tv-serier ved hjælp af Overseerr (modul skal være aktiveret)"
+ "description": "Søg efter film og tv-udsendelser på Overseerr"
}
},
"tip": "Du kan vælge søgefeltet med genvejen ",
"switchedSearchEngine": "Skiftede til søgning med {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/torrents-status.json b/public/locales/da/modules/torrents-status.json
index 229e81b56..8db870296 100644
--- a/public/locales/da/modules/torrents-status.json
+++ b/public/locales/da/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Vis den aktuelle downloadhastighed for understøttede tjenester",
+ "description": "Viser en liste over torrents fra understøttede Torrent-klienter.",
"settings": {
- "hideComplete": {
- "label": "Skjul fuldførte torrents"
+ "title": "Indstillinger for Torrent-widget",
+ "refreshInterval": {
+ "label": "Opdateringsinterval (i sekunder)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Vis fuldførte torrents"
+ },
+ "displayStaleTorrents": {
+ "label": "Vis torrents uden aktivitet"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Ingen understøttede downloadklienter fundet!",
- "text": "Tilføj en downloadtjeneste for at se dine aktuelle downloads"
+ "title": "Ingen understøttede Torrent-klienter fundet!",
+ "text": "Tilføj en understøttet Torrent-klient for at se dine aktuelle downloads"
+ },
+ "generic": {
+ "title": "Der opstod en uventet fejl",
+ "text": "Homarr kunne ikke kommunikere med dine Torrent-klienter. Kontroller venligst din konfiguration"
}
+ },
+ "loading": {
+ "title": "Indlæser..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/modules/usenet.json b/public/locales/da/modules/usenet.json
index c66a44c52..99ebc60e5 100644
--- a/public/locales/da/modules/usenet.json
+++ b/public/locales/da/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Tillader dig at se din usenet (Sabnzbd eller NZBGet) kø og historie, pause og genoptage downloads"
+ "description": "Tillader dig at se og administrere din Usenet instans."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Ingen understøttede downloadklienter fundet!",
- "text": "Tilføj en downloadtjeneste for at se dine aktuelle downloads"
+ "text": "Tilføj en understøttet Usenet Download Client for at se dine aktuelle downloads"
}
}
},
diff --git a/public/locales/da/modules/weather.json b/public/locales/da/modules/weather.json
index 5112fb794..1976d55a7 100644
--- a/public/locales/da/modules/weather.json
+++ b/public/locales/da/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Vejr",
- "description": "Se det aktuelle vejr på din placering",
+ "description": "Viser de aktuelle vejroplysninger for en bestemt placering.",
"settings": {
+ "title": "Indstillinger for vejr widget",
"displayInFahrenheit": {
"label": "Vis i Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Ukendt"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/settings/common.json b/public/locales/da/settings/common.json
index bf0186826..cd7e64ce5 100644
--- a/public/locales/da/settings/common.json
+++ b/public/locales/da/settings/common.json
@@ -11,5 +11,19 @@
"credits": {
"madeWithLove": "Lavet med ❤️ af @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Forøg gitteret (udnyt al pladsen)",
+ "layout": {
+ "title": "Dashboard layout",
+ "main": "Primær",
+ "sidebar": "Sidepanel",
+ "cannotturnoff": "Kan ikke slås fra",
+ "dashboardlayout": "Dashboard layout",
+ "enablersidebar": "Aktivér højre sidepanel",
+ "enablelsidebar": "Aktiver venstre sidebar",
+ "enablesearchbar": "Aktiver søgelinje",
+ "enabledocker": "Aktiver integration af docker",
+ "enableping": "Aktiver pings",
+ "enablelsidebardesc": "Valgfrit. Kan kun bruges til apps og integrationer",
+ "enablersidebardesc": "Valgfrit. Kan kun bruges til apps og integrationer"
+ }
+}
diff --git a/public/locales/da/settings/customization/page-appearance.json b/public/locales/da/settings/customization/page-appearance.json
index c96002ca5..ff8ef2979 100644
--- a/public/locales/da/settings/customization/page-appearance.json
+++ b/public/locales/da/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Sidetitel",
- "placeholder": "Homarr 🦞"
+ "label": "Sidetitel"
+ },
+ "metaTitle": {
+ "label": "Metatitel"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Tilpasset CSS",
- "placeholder": "Brugerdefineret CSS vil blive eksekveret som det sidste"
+ "placeholder": "Brugerdefineret CSS vil blive anvendt sidst"
},
"buttons": {
"submit": "Indsend"
diff --git a/public/locales/da/settings/general/config-changer.json b/public/locales/da/settings/general/config-changer.json
index df61e77dc..7303244b8 100644
--- a/public/locales/da/settings/general/config-changer.json
+++ b/public/locales/da/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Konfigurations indlæser"
+ "label": "Konfigurationsskifter",
+ "description": "{{configCount}} konfigurationer er tilgængelige",
+ "loadingNew": "Indlæser din konfiguration...",
+ "pleaseWait": "Vent venligst, indtil din nye konfiguration er indlæst!"
},
"modal": {
- "title": "Vælg navnet på din nye konfiguration",
- "form": {
- "configName": {
- "label": "Konfigurationens navn",
- "placeholder": "Dit nye konfigurationsnavn"
+ "copy": {
+ "title": "Vælg navnet på din nye konfiguration",
+ "form": {
+ "configName": {
+ "label": "Konfigurationens navn",
+ "validation": {
+ "required": "Konfigurationsnavn er påkrævet",
+ "notUnique": "Konfigurationsnavnet er allerede i brug"
+ },
+ "placeholder": "Dit nye konfigurationsnavn"
+ },
+ "submitButton": "Bekræft"
},
- "submitButton": "Bekræft"
+ "events": {
+ "configSaved": {
+ "title": "Konfigurationen gemt",
+ "message": "Konfigurationen gemt som {{configName}}"
+ },
+ "configCopied": {
+ "title": "Konfigurationen kopieret",
+ "message": "Konfigurationen kopieret som {{configName}}"
+ },
+ "configNotCopied": {
+ "title": "Kan ikke kopiere konfigurationen",
+ "message": "Din konfiguration blev ikke kopieret som {{configName}}"
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Konfigurationen gemt",
- "message": "Konfigurationen gemt som {{configName}}"
+ "confirmDeletion": {
+ "title": "Bekræft sletning af din konfiguration",
+ "warningText": "Du er ved at slette '{{configName}} '",
+ "text": "Bemærk venligst, at sletningen ikke kan fortrydes, og at dine data vil gå tabt permanent. Når du klikker på denne knap, slettes filen permanent fra din disk. Sørg for at oprette en passende sikkerhedskopi af din konfiguration.",
+ "buttons": {
+ "confirm": "Ja, slet '{{configName}} '"
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Sletning af konfiguration mislykkedes",
"message": "Sletning af konfiguration mislykkedes"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Standardkonfiguration kan ikke slettes",
+ "message": "Konfigurationen blev ikke slettet fra filsystemet"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
+ "title": "Upload af konfiguration",
"text": "Træk filer hertil for at uploade en konfiguration. Kun understøttelse af JSON."
},
"reject": {
+ "title": "Træk og slip Upload afvist",
"text": "Dette filformat understøttes ikke. Upload venligst kun JSON."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/settings/general/module-enabler.json b/public/locales/da/settings/general/module-enabler.json
index 4450dd64d..9e26dfeeb 100644
--- a/public/locales/da/settings/general/module-enabler.json
+++ b/public/locales/da/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Modul aktivator"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/da/settings/general/search-engine.json b/public/locales/da/settings/general/search-engine.json
index 5fedbdce2..d3578aece 100644
--- a/public/locales/da/settings/general/search-engine.json
+++ b/public/locales/da/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Søgemaskine",
+ "configurationName": "Konfiguration af søgemaskiner",
"tips": {
- "generalTip": "Brug præfikserne !yt og !t foran din forespørgsel for at søge på YouTube eller efter en Torrent.",
+ "generalTip": "Der er flere præfikser, du kan bruge! Hvis du tilføjer disse foran din forespørgsel, filtreres resultaterne. !s (Web), !t (Torrents), !y (YouTube) og !m (Media).",
"placeholderTip": "%s kan bruges som en pladsholder for forespørgslen."
},
"customEngine": {
+ "title": "Brugerdefineret søgemaskine",
"label": "Forespørgsels URL",
"placeholder": "Brugerdefineret forespørgsels URL"
},
"searchNewTab": {
"label": "Åben søgeresultater i en ny fane"
+ },
+ "searchEnabled": {
+ "label": "Søgning aktiveret"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/da/settings/general/widget-positions.json b/public/locales/da/settings/general/widget-positions.json
index 70a6a3d69..92bd90b40 100644
--- a/public/locales/da/settings/general/widget-positions.json
+++ b/public/locales/da/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Placer widgets til venstre"
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/authentication/login.json b/public/locales/de/authentication/login.json
index bfd3d21d9..72e0156d5 100644
--- a/public/locales/de/authentication/login.json
+++ b/public/locales/de/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Willkommen zurück!",
- "text": "Bitte geben Sie das Passwort ein",
+ "text": "Bitte geben Sie Ihr Kennwort ein",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Ihr Passwort wird geprüft..."
},
"correct": {
- "title": "Passwort korrekt, sie werden weitergeleitet..."
+ "title": "Anmeldung erfolgreich, Weiterleitung..."
},
"wrong": {
- "title": "Das Passwort ist falsch, bitte versuchen Sie es erneut."
+ "title": "Das von dir eingegebene Passwort ist nicht korrekt. Bitte versuche es noch mal."
}
}
}
diff --git a/public/locales/de/common.json b/public/locales/de/common.json
index e5843b606..8b172d7e3 100644
--- a/public/locales/de/common.json
+++ b/public/locales/de/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Speichern"
+ "save": "Speichern",
+ "about": "Über",
+ "cancel": "Abbrechen",
+ "close": "Schließen",
+ "delete": "Löschen",
+ "ok": "OK",
+ "edit": "Bearbeiten",
+ "version": "Version",
+ "changePosition": "Position wechseln",
+ "remove": "Entfernen",
+ "removeConfirm": "Sind Sie sicher, dass Sie {{item}} entfernen möchten?",
+ "sections": {
+ "settings": "Einstellungen",
+ "dangerZone": "Gefahrenzone"
+ },
+ "secrets": {
+ "apiKey": "API-Schlüssel",
+ "username": "Benutzername",
+ "password": "Passwort"
},
"tip": "Tipp: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "Minuten",
"hours": "Stunden"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/de/layout/add-service-app-shelf.json b/public/locales/de/layout/add-service-app-shelf.json
index 090578f1a..11b165974 100644
--- a/public/locales/de/layout/add-service-app-shelf.json
+++ b/public/locales/de/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Erweiterte Optionen",
"form": {
- "httpStatusCodes": {
- "label": "HTTP Status Nummern",
- "placeholder": "Gültige Statuscodes auswählen",
- "clearButtonLabel": "Auswahl löschen",
- "nothingFound": "Nichts gefunden"
- },
"openServiceInNewTab": {
"label": "Service in einem neuen Tab öffnen"
},
diff --git a/public/locales/de/layout/element-selector/selector.json b/public/locales/de/layout/element-selector/selector.json
new file mode 100644
index 000000000..c665ab8e2
--- /dev/null
+++ b/public/locales/de/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Neue Kachel hinzufügen",
+ "text": "Kacheln sind das Hauptelement von Homarr. Sie werden verwendet, um Ihre Anwendungen und andere Informationen anzuzeigen. Sie können so viele Kacheln hinzufügen, wie Sie möchten."
+ },
+ "widgetDescription": "Widgets interagieren mit Ihren Anwendungen, um Ihnen mehr Kontrolle über sie zu geben. Sie erfordern in der Regel eine zusätzliche Konfiguration vor der Verwendung.",
+ "goBack": "Zurück auf die vorherige Seite",
+ "actionIcon": {
+ "tooltip": "Kachel hinzufügen"
+ }
+}
diff --git a/public/locales/de/layout/header/actions/toggle-edit-mode.json b/public/locales/de/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..6824b9d63
--- /dev/null
+++ b/public/locales/de/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "Im Bearbeitungsmodus können Sie Kacheln anpassen und Anwendungen konfigurieren. Die Änderungen werden erst gespeichert, wenn Sie den Bearbeitungsmodus verlassen.",
+ "button": {
+ "disabled": "Bearbeitungsmodus aktivieren",
+ "enabled": "Speichern und Beenden"
+ },
+ "popover": {
+ "title": "Der Bearbeitungsmodus ist für <1>{{size}}1> aktiviert",
+ "text": "Sie können Ihre Apps jetzt anpassen und konfigurieren. Änderungen werden nicht gespeichert bis Sie den Bearbeitungsmodus verlassen"
+ },
+ "screenSizes": {
+ "small": "Klein",
+ "medium": "Mittel",
+ "large": "Groß"
+ }
+}
diff --git a/public/locales/de/layout/mobile/drawer.json b/public/locales/de/layout/mobile/drawer.json
new file mode 100644
index 000000000..e8ff05536
--- /dev/null
+++ b/public/locales/de/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} Seitenleiste"
+}
diff --git a/public/locales/de/layout/modals/about.json b/public/locales/de/layout/modals/about.json
new file mode 100644
index 000000000..2bd54bf47
--- /dev/null
+++ b/public/locales/de/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr ist ein schlankes , modernes Dashboard, das alle Ihre Apps und Dienste auf Knopfdruck zur Verfügung stellt. Mittels Homarr können Sie von einem einzigen Ort aus auf alles zugreifen und steuern. Es lässt sich nahtlos in die von Ihnen bevorzugten Apps integrieren und versorgt Sie mit wertvollen Informationen und der vollständige Kontrolle. Die Installation ist ein Kinderspiel, und es werden eine breite Palette von Konfigurations unterstützt.",
+ "i18n": "Geladene I18n Übersetzungs Namensräume",
+ "locales": "Konfigurierte I18n Sprachumgebungen",
+ "contact": "Haben Sie Probleme oder Fragen? Nehmen Sie Kontakt mit uns auf!",
+ "addToDashboard": "Zum Dashboard hinzufügen"
+}
diff --git a/public/locales/de/layout/modals/add-app.json b/public/locales/de/layout/modals/add-app.json
new file mode 100644
index 000000000..758e777d7
--- /dev/null
+++ b/public/locales/de/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Allgemein",
+ "behaviour": "Verhalten",
+ "network": "Netzwerk",
+ "appearance": "Aussehen",
+ "integration": "Integration"
+ },
+ "general": {
+ "appname": {
+ "label": "Anwendungsname",
+ "description": "Wird für die Anzeige der App auf dem Dashboard verwendet."
+ },
+ "internalAddress": {
+ "label": "Interne Adresse",
+ "description": "Interne IP-Adresse der Anwendung."
+ },
+ "externalAddress": {
+ "label": "Externe Adresse",
+ "description": "URL, die beim Anklicken der App geöffnet wird."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "In neuem Tab öffnen",
+ "description": "Öffnen Sie die App in einer neuen Registerkarte, anstatt in der aktuellen Registerkarte."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Statusprüfer",
+ "description": "Prüft, ob Ihre Anwendung online und mittels einer einfachen HTTP(S)-Anfrage erreichbar ist."
+ },
+ "statusCodes": {
+ "label": "HTTP Statuscodes",
+ "description": "Die HTTP-Statuscodes, die als online angesehen werden."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "App Symbol",
+ "description": "Das Symbol, das auf dem Dashboard angezeigt werden soll."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Integrations-Konfiguration",
+ "description": "Die Integrationskonfiguration, die für die Verbindung mit Ihrer Anwendung verwendet wird.",
+ "placeholder": "Integration auswählen",
+ "defined": "Definiert",
+ "undefined": "Nicht definiert",
+ "public": "Öffentlich sichtbar",
+ "private": "Privat",
+ "explanationPrivate": "Ein private Phrase wird nur einmal an den Server gesendet. Sobald Ihr Browser die Seite aktualisiert hat, wird es nie wieder gesendet.",
+ "explanationPublic": "Ein öffentliche Phrase wird immer an den Client gesendet und ist über die API zugänglich. Es sollte keine vertraulichen Werte wie Benutzernamen, Passwörter, Token, Zertifikate und ähnliches enthalten!"
+ },
+ "secrets": {
+ "description": "Um eine Phrase zu aktualisieren, geben Sie einen Wert ein und klicken Sie auf Speichern. Um eine Phrase zu entfernen, klicken Sie auf die Schaltfläche Löschen.",
+ "warning": "Ihre Anmeldedaten dienen als Zugang für Homarr und sie sollten diese niemals an andere Personen weitergeben. Das Homarr-Team wird Sie niemals nach Ihren Zugangsdaten fragen. Stellen Sie sicher, dass sie ihre Zugangsdaten sicher aufbewahren und verwalten .",
+ "clear": "Phrase löschen",
+ "save": "Phrase speichern",
+ "update": "Phrase ändern"
+ }
+ },
+ "validation": {
+ "popover": "Ihr Formular enthält ungültige Angaben, daher konnte es nicht gespeichert werden. Bitte beseitigen Sie diese ungültigen Angaben und klicken Sie erneut auf diesen Button, um Ihre Änderungen zu speichern"
+ }
+}
diff --git a/public/locales/de/layout/modals/change-position.json b/public/locales/de/layout/modals/change-position.json
new file mode 100644
index 000000000..1b6e6437c
--- /dev/null
+++ b/public/locales/de/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "Position der X-Achse",
+ "width": "Breite",
+ "height": "Höhe",
+ "yPosition": "Position der Y-Achse",
+ "zeroOrHigher": "0 oder höher",
+ "betweenXandY": "Zwischen {{min}} und {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/de/layout/screen-sizes.json b/public/locales/de/layout/screen-sizes.json
new file mode 100644
index 000000000..563d99a0b
--- /dev/null
+++ b/public/locales/de/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "Klein",
+ "medium": "Mittel",
+ "large": "Groß"
+ }
+}
diff --git a/public/locales/de/layout/tools.json b/public/locales/de/layout/tools.json
new file mode 100644
index 000000000..8cd43010e
--- /dev/null
+++ b/public/locales/de/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Sie haben derzeit keine Werkzeuge"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Suche nach Symbolen...",
+ "searchLimitationTitle": "Die Suche ist auf {{max}} Symbole beschränkt",
+ "searchLimitationMessage": "Um die Suche schnell und effizient zu halten, ist die Suche auf {{max}} Symbole beschränkt. Verwenden Sie die Suche, um weitere Symbole zu finden"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/de/modules/calendar.json b/public/locales/de/modules/calendar.json
index 3aea9f379..d6ddf5949 100644
--- a/public/locales/de/modules/calendar.json
+++ b/public/locales/de/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Kalender",
- "description": "Ein Kalendermodul für die Anzeige der kommenden Veröffentlichungen. Es interagiert mit der Sonarr- und Radarr-API.",
+ "description": "Zeigt einen Kalender mit anstehenden Veröffentlichungen von unterstützten Widgets an.",
"settings": {
+ "title": "Einstellungen für das Kalender Widget",
"sundayStart": {
"label": "Wochenbeginn am Sonntag"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr Veröffentlichungs Typ"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/common.json b/public/locales/de/modules/common.json
index 1ee890fe2..c7014d161 100644
--- a/public/locales/de/modules/common.json
+++ b/public/locales/de/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Einstellungen"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Ungenutzter Parameter in der Konfiguration erkannt {{key}}. Homarr ist nicht in der Lage, diesen Parameter zu interpretieren und zu verwenden. Um ein unerwartetes Verhalten zu vermeiden, sichern Sie Ihre Konfiguration und korrigieren Sie sie."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/dashdot.json b/public/locales/de/modules/dashdot.json
index 4b49d328b..a484a30fb 100644
--- a/public/locales/de/modules/dashdot.json
+++ b/public/locales/de/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Ein Modul, welches die Graphen aus einer laufenden Dash. Instanz anzeigt.",
+ "description": "Zeigt die Graphen einer externen Dash.-Instanz innerhalb von Homarr an.",
"settings": {
+ "title": "Einstellungen für Dash. Widget",
"cpuMultiView": {
"label": "CPU Multi-Core View"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/date.json b/public/locales/de/modules/date.json
index 11448ecfc..63de4fdc0 100644
--- a/public/locales/de/modules/date.json
+++ b/public/locales/de/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Datum",
- "description": "Die aktuelle Zeit und das aktuelle Datum in der Card anzeigen",
+ "name": "Datum und Zeit",
+ "description": "Zeigt das aktuelle Datum und die Uhrzeit an",
"settings": {
+ "title": "Einstellungen für das Widget \"Datum und Uhrzeit",
"display24HourFormat": {
"label": "24-Stunden Format"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/dlspeed.json b/public/locales/de/modules/dlspeed.json
index de1183baa..ecefcd65c 100644
--- a/public/locales/de/modules/dlspeed.json
+++ b/public/locales/de/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Download Geschwindigkeit",
- "description": "Zeige die aktuellen Downloadgeschwindigkeiten von unterstützten Services"
+ "description": "Zeigt die Download- und Upload-Geschwindigkeit der unterstützten Widgets an."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/docker.json b/public/locales/de/modules/docker.json
index fcd1c56a8..718402eeb 100644
--- a/public/locales/de/modules/docker.json
+++ b/public/locales/de/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Ermöglicht dir die einfache Verwaltung deiner Docker-Container"
+ "description": "Ermöglicht es Ihnen, alle Ihre Docker-Container zu sehen und zu verwalten."
},
"search": {
"placeholder": "Suche nach Container- oder Image namen"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Service hinzufügen",
- "message": "Service zu Homarr hinzufügen"
+ "title": "App hinzufügen",
+ "message": "App zu Homarr hinzufügen"
},
"restart": {
"title": "Neustarten"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Docker-Integration fehlgeschlagen",
- "message": "Hast du vergessen, den Docker Socket zu verbinden?"
+ "message": "Haben Sie vergessen, den Docker-Socket zu mounten?"
},
"unknownError": {
"title": "Es ist ein Fehler aufgetreten"
},
"oneServiceAtATime": {
- "title": "Bitte nur einen Service gleichzeitig hinzufügen!"
+ "title": "Bitte fügen Sie immer nur eine Anwendung oder einen Dienst auf einmal hinzu!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/overseerr.json b/public/locales/de/modules/overseerr.json
index ef98e3adf..60767f669 100644
--- a/public/locales/de/modules/overseerr.json
+++ b/public/locales/de/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Ermöglicht das Suchen und Hinzufügen von Medien via Overseerr/Jellyseerr"
+ "description": "Ermöglicht Ihnen das Suchen und Hinzufügen von Medien aus Overseerr oder Jellyseerr."
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Kreuze die Staffeln an, die heruntergeladen werden sollen",
+ "caption": "Markieren Sie die Staffeln, die Sie herunterladen möchten",
"table": {
"header": {
"season": "Staffel",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/ping.json b/public/locales/de/modules/ping.json
index 27d5c61b8..20ae061aa 100644
--- a/public/locales/de/modules/ping.json
+++ b/public/locales/de/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Ermöglicht es Ihnen, zu überprüfen, ob der Service aktiv ist oder einen bestimmten HTTP-Statuscode zurückgibt."
+ "description": "Zeigt einen Statusindikator in Abhängigkeit vom HTTP-Antwortcode einer bestimmten URL an."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Wird geladen..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/search.json b/public/locales/de/modules/search.json
index 739e7632b..8d04cb708 100644
--- a/public/locales/de/modules/search.json
+++ b/public/locales/de/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Suchleiste",
- "description": "Suchleiste zum Durchsuchen des Internets, von Youtube, Torrents oder Overseerr"
+ "description": "Eine Suchleiste, mit der Sie Ihre eigene Suchmaschine, YouTube und andere unterstützte Konfigurationen durchsuchen können."
},
"input": {
"placeholder": "Das Internet durchsuchen..."
},
- "switched-to": "",
+ "switched-to": "Wechseln zu",
"searchEngines": {
"search": {
- "name": "",
- "description": ""
+ "name": "Web",
+ "description": "Suche..."
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "YouTube",
+ "description": "Auf YouTube suchen"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrents",
+ "description": "Suche nach Torrents"
},
"overseerr": {
"name": "Overseerr",
- "description": ""
+ "description": "Suche nach Filmen und TV-Sendungen auf Overseerr"
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "Sie können die Suchleiste mit dem Tastenkürzel auswählen ",
+ "switchedSearchEngine": "Umgestellt auf die Suche mit {{searchEngine}}"
+}
diff --git a/public/locales/de/modules/torrents-status.json b/public/locales/de/modules/torrents-status.json
index 0f12a88ae..4b9053bc6 100644
--- a/public/locales/de/modules/torrents-status.json
+++ b/public/locales/de/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Zeige die aktuellen Downloadgeschwindigkeiten von unterstützten Services",
+ "description": "Zeigt eine Liste von Torrents der unterstützten Torrent-Clients an.",
"settings": {
- "hideComplete": {
- "label": "Abgeschlossene Torrents ausblenden"
+ "title": "Einstellungen für das Torrent Widget",
+ "refreshInterval": {
+ "label": "Aktualisierungsintervall (in Sekunden)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Abgeschlossene Torrents anzeigen"
+ },
+ "displayStaleTorrents": {
+ "label": "Angehaltene Torrents anzeigen"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Keine unterstützten Download-Clients gefunden!",
- "text": "Fügen Sie einen Download-Service hinzu, um Ihre aktuellen Downloads anzuzeigen"
+ "title": "Keine unterstützten Torrent Clients gefunden!",
+ "text": "Fügen Sie einen unterstützten Torrent Client hinzu, um Ihre aktuellen Downloads anzuzeigen"
+ },
+ "generic": {
+ "title": "Ein unerwarteter Fehler ist aufgetreten",
+ "text": "Homarr konnte nicht mit Ihren Torrent Clients kommunizieren. Bitte überprüfen Sie Ihre Konfiguration"
}
+ },
+ "loading": {
+ "title": "Wird geladen..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/modules/usenet.json b/public/locales/de/modules/usenet.json
index bdda79882..f14981934 100644
--- a/public/locales/de/modules/usenet.json
+++ b/public/locales/de/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
- "description": ""
+ "name": "Usenet",
+ "description": "Ermöglicht es Ihnen, Ihre Usenet-Instanz anzuzeigen und zu verwalten."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Keine unterstützten Download-Clients gefunden!",
- "text": "Fügen Sie einen Download-Service hinzu, um Ihre aktuellen Downloads anzuzeigen"
+ "text": "Fügen Sie einen unterstützten Usenet Download Client hinzu, um Ihre aktuellen Downloads anzuzeigen"
}
}
},
diff --git a/public/locales/de/modules/weather.json b/public/locales/de/modules/weather.json
index 00fcb929c..16a5d8039 100644
--- a/public/locales/de/modules/weather.json
+++ b/public/locales/de/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Wetter",
- "description": "Aktuelles Wetter an deinem Standort anzeigen",
+ "description": "Zeigt die aktuellen Wetterinformationen für einen bestimmten Ort an.",
"settings": {
+ "title": "Einstellungen für das Wetter Widget",
"displayInFahrenheit": {
"label": "In Fahrenheit anzeigen"
},
@@ -29,4 +30,4 @@
"unknown": "Unbekannt"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/settings/common.json b/public/locales/de/settings/common.json
index 273b28efc..e2a08e507 100644
--- a/public/locales/de/settings/common.json
+++ b/public/locales/de/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Anpassungen"
},
"tips": {
- "configTip": "Laden Sie Ihre Konfigurationsdatei durch Ziehen und Ablegen auf die Seite!"
+ "configTip": "Laden Sie Ihre Konfigurationsdatei hoch, indem Sie sie per Drag & Drop auf diese Seite ziehen!"
},
"credits": {
"madeWithLove": "Gemacht mit ❤️ von @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Erweitertes Raster (nutzt den ganzen Platz)",
+ "layout": {
+ "title": "Dashboard Anordnung",
+ "main": "Haupt",
+ "sidebar": "Seitenleiste",
+ "cannotturnoff": "Kann nicht deaktiviert werden",
+ "dashboardlayout": "Dashboard Anordnung",
+ "enablersidebar": "Rechte Seitenleiste aktivieren",
+ "enablelsidebar": "Linke Seitenleiste aktivieren",
+ "enablesearchbar": "Suchleiste aktivieren",
+ "enabledocker": "Docker Integration aktivieren",
+ "enableping": "Pings aktivieren",
+ "enablelsidebardesc": "Optional. Kann nur für Anwendungen und Integrationen verwendet werden",
+ "enablersidebardesc": "Optional. Kann nur für Anwendungen und Integrationen verwendet werden"
+ }
+}
diff --git a/public/locales/de/settings/customization/page-appearance.json b/public/locales/de/settings/customization/page-appearance.json
index e4e3d1e9d..d55ac9c5f 100644
--- a/public/locales/de/settings/customization/page-appearance.json
+++ b/public/locales/de/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Seiten Titel",
- "placeholder": "Homarr 🦞"
+ "label": "Seiten Titel"
+ },
+ "metaTitle": {
+ "label": "Meta Titel"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Benutzerdefiniertes CSS",
- "placeholder": "Benutzerdefiniertes CSS wird zuletzt ausgeführt"
+ "placeholder": "Benutzerdefiniertes CSS wird zuletzt angewendet"
},
"buttons": {
"submit": "Absenden"
diff --git a/public/locales/de/settings/general/config-changer.json b/public/locales/de/settings/general/config-changer.json
index c22184269..87c200f02 100644
--- a/public/locales/de/settings/general/config-changer.json
+++ b/public/locales/de/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Konfigurationslader"
+ "label": "Konfigurationslader",
+ "description": "",
+ "loadingNew": "Ihre Konfiguration wird geladen...",
+ "pleaseWait": "Bitte warten Sie, bis Ihre neue Konfiguration geladen ist!"
},
"modal": {
- "title": "Wählen Sie den Namen für Ihre neue Konfiguration",
- "form": {
- "configName": {
- "label": "Konfigurationsname",
- "placeholder": "Ihr neuer Konfigurationsname"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Bestätigen"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Konfiguration gespeichert",
- "message": "Konfiguration gespeichert als {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Löschung der Konfiguration fehlgeschlagen",
"message": "Löschung der Konfiguration fehlgeschlagen"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Die Standardkonfiguration kann nicht gelöscht werden",
+ "message": "Die Konfiguration wurde nicht vom System gelöscht"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Ziehen Sie Dateien hierher, um eine Konfiguration hochzuladen. Unterstützung nur für JSON."
+ "title": "Konfiguration hochladen",
+ "text": "Ziehen Sie unterstütze Dateien hierher, um eine Konfiguration hochzuladen. Nur für JSON-Dateien."
},
"reject": {
- "text": "Dieses Dateiformat wird nicht unterstützt. Bitte lade nur JSON hoch."
+ "title": "Drag and Drop Upload abgelehnt",
+ "text": "Dieses Dateiformat wird nicht unterstützt. Bitte laden Sie nur JSON-Dateien hoch."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/settings/general/module-enabler.json b/public/locales/de/settings/general/module-enabler.json
index b14f9bb89..9e26dfeeb 100644
--- a/public/locales/de/settings/general/module-enabler.json
+++ b/public/locales/de/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Modul-Aktivierer"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/de/settings/general/search-engine.json b/public/locales/de/settings/general/search-engine.json
index 4ace118a1..900ecb677 100644
--- a/public/locales/de/settings/general/search-engine.json
+++ b/public/locales/de/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Suchmaschine",
+ "configurationName": "Suchmaschinen Einstellungen",
"tips": {
- "generalTip": "Verwenden die Präfixe !yt und !t vor deiner Suchanfrage, um auf YouTube bzw. nach einem Torrent zu suchen.",
+ "generalTip": "Es gibt mehrere Präfixe, die Sie verwenden können! Wenn Sie diese vor Ihrer Abfrage hinzufügen, werden die Ergebnisse gefiltert. z.b. !s (Web), !t (Torrents), !y (YouTube), und !m (Medien).",
"placeholderTip": "%s kann als Platzhalter für deine Suchanfrage verwendet werden."
},
"customEngine": {
+ "title": "Benutzerdefinierte Suchmaschine",
"label": "Suchanfrage URL",
"placeholder": "Benutzerdefinierte Abfrage-URL"
},
"searchNewTab": {
"label": "Öffne Suchergebnisse in einem neuen Tab"
+ },
+ "searchEnabled": {
+ "label": "Suchmaschine aktiviert"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/de/settings/general/widget-positions.json b/public/locales/de/settings/general/widget-positions.json
index d34be8358..22bf81801 100644
--- a/public/locales/de/settings/general/widget-positions.json
+++ b/public/locales/de/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Widgets auf der linken Seite positionieren"
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/authentication/login.json b/public/locales/en/authentication/login.json
index f52e3da6f..68f32cbe8 100644
--- a/public/locales/en/authentication/login.json
+++ b/public/locales/en/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Welcome back!",
- "text": "Please enter the Password",
+ "text": "Please enter your password",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Your password is being checked..."
},
"correct": {
- "title": "Password correct, redirecting you..."
+ "title": "Sign in successful, redirecting..."
},
"wrong": {
- "title": "Password is wrong, please try again."
+ "title": "The password you entered is incorrect, please try again."
}
}
}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 64232fcf5..5c14a9ca6 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Save"
+ "save": "Save",
+ "about": "About",
+ "cancel": "Cancel",
+ "close": "Close",
+ "delete": "Delete",
+ "ok": "OK",
+ "edit": "Edit",
+ "version": "Version",
+ "changePosition": "Change position",
+ "remove": "Remove",
+ "removeConfirm": "Are you sure that you want to remove {{item}} ?",
+ "sections": {
+ "settings": "Settings",
+ "dangerZone": "Danger zone"
+ },
+ "secrets": {
+ "apiKey": "Api key",
+ "username": "Username",
+ "password": "Password"
},
"tip": "Tip: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutes",
"hours": "hours"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/en/layout/add-service-app-shelf.json b/public/locales/en/layout/add-service-app-shelf.json
deleted file mode 100644
index 735110906..000000000
--- a/public/locales/en/layout/add-service-app-shelf.json
+++ /dev/null
@@ -1,134 +0,0 @@
-{
- "actionIcon": {
- "tooltip": "Add a service"
- },
- "modal": {
- "title": "Add service",
- "form": {
- "validation": {
- "invalidUrl": "Please enter a valid URL",
- "noStatusCodeSelected": "Please select a status code"
- }
- },
- "tabs": {
- "options": {
- "title": "Options",
- "form": {
- "serviceName": {
- "label": "Service name",
- "placeholder": "Plex"
- },
- "iconUrl": {
- "label": "Icon URL"
- },
- "serviceUrl": {
- "label": "Service URL"
- },
- "onClickUrl": {
- "label": "On Click URL"
- },
- "serviceType": {
- "label": "Service type",
- "defaultValue": "Other",
- "placeholder": "Pick one"
- },
- "category": {
- "label": "Category",
- "placeholder": "Select a category or create a new one",
- "nothingFound": "Nothing found",
- "createLabel": "+ Create {{query}}"
- },
- "integrations": {
- "apiKey": {
- "label": "API key",
- "placeholder": "Your API key",
- "validation": {
- "noKey": "Invalid Key"
- },
- "tip": {
- "text": "Get your API key",
- "link": "here."
- }
- },
- "qBittorrent": {
- "username": {
- "label": "Username",
- "placeholder": "admin",
- "validation": {
- "invalidUsername": "Invalid username"
- }
- },
- "password": {
- "label": "Password",
- "placeholder": "adminadmin",
- "validation": {
- "invalidPassword": "Invalid password"
- }
- }
- },
- "deluge": {
- "password": {
- "label": "Password",
- "placeholder": "password",
- "validation": {
- "invalidPassword": "Invalid password"
- }
- }
- },
- "transmission": {
- "username": {
- "label": "Username",
- "placeholder": "admin",
- "validation": {
- "invalidUsername": "Invalid username"
- }
- },
- "password": {
- "label": "Password",
- "placeholder": "adminadmin",
- "validation": {
- "invalidPassword": "Invalid password"
- }
- }
- },
- "nzbget": {
- "username": {
- "label": "Username",
- "placeholder": "admin",
- "validation": {
- "invalidUsername": "Invalid username"
- }
- },
- "password": {
- "label": "Password",
- "placeholder": "password",
- "validation": {
- "invalidPassword": "Invalid password"
- }
- }
- }
- }
- }
- },
- "advancedOptions": {
- "title": "Advanced options",
- "form": {
- "httpStatusCodes": {
- "label": "HTTP Status Codes",
- "placeholder": "Select valid status codes",
- "clearButtonLabel": "Clear selection",
- "nothingFound": "Nothing found"
- },
- "openServiceInNewTab": {
- "label": "Open service in new tab"
- },
- "buttons": {
- "submit": {
- "content": "Add service"
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/public/locales/en/layout/app-shelf-menu.json b/public/locales/en/layout/app-shelf-menu.json
deleted file mode 100644
index 006e906c2..000000000
--- a/public/locales/en/layout/app-shelf-menu.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "modal": {
- "title": "Modify a service",
- "buttons": {
- "save": "Save service"
- }
- },
- "menu": {
- "labels": {
- "settings": "Settings",
- "dangerZone": "Danger zone"
- },
- "actions": {
- "edit": "Edit",
- "delete": "Delete"
- }
- }
-}
\ No newline at end of file
diff --git a/public/locales/en/layout/app-shelf.json b/public/locales/en/layout/app-shelf.json
deleted file mode 100644
index 2074f4105..000000000
--- a/public/locales/en/layout/app-shelf.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "accordions": {
- "downloads": {
- "text": "Your downloads",
- "torrents": "Your Torrent downloads",
- "usenet": "Your Usenet downloads"
- },
- "others": {
- "text": "Others"
- }
- }
-}
\ No newline at end of file
diff --git a/public/locales/en/layout/element-selector/selector.json b/public/locales/en/layout/element-selector/selector.json
new file mode 100644
index 000000000..7f7b0c778
--- /dev/null
+++ b/public/locales/en/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Add a new tile",
+ "text": "Tiles are the main element of Homarr. They are used to display your apps and other information. You can add as many tiles as you want."
+ },
+ "widgetDescription": "Widgets interact with your apps, to provide you with more control over your applications. They usually require additional configuration before use.",
+ "goBack": "Go back to the previous step",
+ "actionIcon": {
+ "tooltip": "Add a tile"
+ }
+}
diff --git a/public/locales/en/layout/header/actions/toggle-edit-mode.json b/public/locales/en/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..965eb3b8a
--- /dev/null
+++ b/public/locales/en/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "In Edit Mode, you can adjust tiles and configure apps. Changes are not saved until you exit Edit Mode.",
+ "button": {
+ "disabled": "Enter Edit Mode",
+ "enabled": "Exit and Save"
+ },
+ "popover": {
+ "title": "Edit mode is enabled for <1>{{size}}1> size",
+ "text": "You can adjust and configure your apps now. Changes are not saved until you exit edit mode"
+ },
+ "screenSizes": {
+ "small": "small",
+ "medium": "medium",
+ "large": "large"
+ }
+}
diff --git a/public/locales/en/layout/mobile/drawer.json b/public/locales/en/layout/mobile/drawer.json
new file mode 100644
index 000000000..42d8f7df0
--- /dev/null
+++ b/public/locales/en/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} sidebar"
+}
diff --git a/public/locales/en/layout/modals/about.json b/public/locales/en/layout/modals/about.json
new file mode 100644
index 000000000..fa0044a91
--- /dev/null
+++ b/public/locales/en/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr is a sleek , modern dashboard that puts all of your apps and services at your fingertips. With Homarr, you can access and control everything in one convenient location. Homarr seamlessly integrates with the apps you've added, providing you with valuable information and giving you complete control. Installation is a breeze, and Homarr supports a wide range of deployment methods.",
+ "i18n": "Loaded I18n translation namespaces",
+ "locales": "Configured I18n locales",
+ "contact": "Having trouble or questions? Connect with us!",
+ "addToDashboard": "Add to Dashboard"
+}
diff --git a/public/locales/en/layout/modals/add-app.json b/public/locales/en/layout/modals/add-app.json
new file mode 100644
index 000000000..d0343a93b
--- /dev/null
+++ b/public/locales/en/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "General",
+ "behaviour": "Behaviour",
+ "network": "Network",
+ "appearance": "Appearance",
+ "integration": "Integration"
+ },
+ "general": {
+ "appname": {
+ "label": "App name",
+ "description": "Used to display the app on the dashboard."
+ },
+ "internalAddress": {
+ "label": "Internal address",
+ "description": "Internal IP-address of the app."
+ },
+ "externalAddress": {
+ "label": "External address",
+ "description": "URL that will be opened when clicking on the app."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Open in new tab",
+ "description": "Open the app in a new tab instead of the current one."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Status checker",
+ "description": "Checks if your app is online using a simple HTTP(S) request."
+ },
+ "statusCodes": {
+ "label": "HTTP status codes",
+ "description": "The HTTP status codes that are considered as online."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "App Icon",
+ "description": "The icon that will be displayed on the dashboard."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Integration configuration",
+ "description": "The integration configuration that will be used to connect to your app.",
+ "placeholder": "Select an integration",
+ "defined": "Defined",
+ "undefined": "Undefined",
+ "public": "Public",
+ "private": "Private",
+ "explanationPrivate": "A private secret will be sent to the server only once. Once your browser has refreshed the page, it will never be sent again.",
+ "explanationPublic": "A public secret will always be sent to the client and is accessible over the API. It should not contain any confidential values such as usernames, passwords, tokens, certificates and similar!"
+ },
+ "secrets": {
+ "description": "To update a secret, enter a value and click the save button. To remove a secret, use the clear button.",
+ "warning": "Your credentials act as the access for your integrations and you should never share them with anybody else. The Homarr team will never ask for credentials. Make sure to store and manage your secrets safely .",
+ "clear": "Clear secret",
+ "save": "Save secret",
+ "update": "Update secret"
+ }
+ },
+ "validation": {
+ "popover": "Your form contains invalid data. Hence, it can't be saved. Please resolve all issues and click this button again to save your changes"
+ }
+}
diff --git a/public/locales/en/layout/modals/change-position.json b/public/locales/en/layout/modals/change-position.json
new file mode 100644
index 000000000..464a676de
--- /dev/null
+++ b/public/locales/en/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X axis position",
+ "width": "Width",
+ "height": "Height",
+ "yPosition": "Y axis position",
+ "zeroOrHigher": "0 or higher",
+ "betweenXandY": "Between {{min}} and {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/en/modules/calendar.json b/public/locales/en/modules/calendar.json
index d470eabe9..fe7fc32ce 100644
--- a/public/locales/en/modules/calendar.json
+++ b/public/locales/en/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendar",
- "description": "A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.",
+ "description": "Displays a calendar with upcoming releases, from supported integrations.",
"settings": {
+ "title": "Settings for Calendar widget",
"sundayStart": {
"label": "Start the week on Sunday"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr release type"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/common.json b/public/locales/en/modules/common.json
index 3f4b36b03..18a748dcd 100644
--- a/public/locales/en/modules/common.json
+++ b/public/locales/en/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Settings"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Un-used parameter in configuration detected {{key}}. Homarr is unable to interpret and use this parameter. To avoid any unexpected behavior, back up your configuration and correct your configuration."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/dashdot.json b/public/locales/en/modules/dashdot.json
index c44e04ca2..9268364f7 100644
--- a/public/locales/en/modules/dashdot.json
+++ b/public/locales/en/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "A module for displaying the graphs of your running Dash. instance.",
+ "description": "Displays the graphs of an external Dash. instance inside of Homarr.",
"settings": {
+ "title": "Settings for Dash. widget",
"cpuMultiView": {
"label": "CPU Multi-Core View"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/date.json b/public/locales/en/modules/date.json
index 521e220a4..3f925d2d5 100644
--- a/public/locales/en/modules/date.json
+++ b/public/locales/en/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Date",
- "description": "Show the current time and date in a card",
+ "name": "Date and Time",
+ "description": "Displays the current date and time.",
"settings": {
+ "title": "Settings for Date and Time widget",
"display24HourFormat": {
"label": "Display full time (24-hour)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/dlspeed.json b/public/locales/en/modules/dlspeed.json
index ff9bc709e..3d11e5894 100644
--- a/public/locales/en/modules/dlspeed.json
+++ b/public/locales/en/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Download Speed",
- "description": "Show the current download speed of supported services"
+ "description": "Displays the Download and Upload speed of supported integrations."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/docker.json b/public/locales/en/modules/docker.json
index 432d48f82..436a78eeb 100644
--- a/public/locales/en/modules/docker.json
+++ b/public/locales/en/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Allows you to easily manage your docker containers"
+ "description": "Allows you to easily see and manage all of your Docker Containers."
},
"search": {
"placeholder": "Search by container or image name"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Add service",
- "message": "Add service to Homarr"
+ "title": "Add app",
+ "message": "Add app Homarr"
},
"restart": {
"title": "Restart"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Docker integration failed",
- "message": "Did you forget to mount the docker socket ?"
+ "message": "Did you forget to mount the docker socket?"
},
"unknownError": {
"title": "There was an error"
},
"oneServiceAtATime": {
- "title": "Please only add one service at a time!"
+ "title": "Please only add one app or service at a time!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/overseerr.json b/public/locales/en/modules/overseerr.json
index e7b44289e..09e7b69c7 100644
--- a/public/locales/en/modules/overseerr.json
+++ b/public/locales/en/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Allows you to search and add media from Overseerr/Jellyseerr"
+ "description": "Allows you to search and add media from Overseerr or Jellyseerr."
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Tick the seasons that you want to be downloaded",
+ "caption": "Tick the seasons you want to download",
"table": {
"header": {
"season": "Season",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/ping.json b/public/locales/en/modules/ping.json
index 403c8027b..50d78f53a 100644
--- a/public/locales/en/modules/ping.json
+++ b/public/locales/en/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Allows you to check if the service is up or returns a specific HTTP status code."
+ "description": "Displays a status indicator depeding on the HTTP response code of a given URL."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Loading..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/search.json b/public/locales/en/modules/search.json
index cf3f50de0..625009afe 100644
--- a/public/locales/en/modules/search.json
+++ b/public/locales/en/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Search Bar",
- "description": "Search bar to search the web, Youtube, Torrents or Overseerr"
+ "description": "A search bar that allows you to search your custom search engine, YouTube, and supported integrations."
},
"input": {
"placeholder": "Search the web..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "Web",
- "description": "Search using your search engine (defined in settings)"
+ "description": "Search..."
},
"youtube": {
"name": "Youtube",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Search for Movies and TV Shows using Overseerr (module must be enabled)"
+ "description": "Search for Movies and TV Shows on Overseerr"
}
},
"tip": "You can select the search bar with the shortcut ",
"switchedSearchEngine": "Switched to searching with {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/torrents-status.json b/public/locales/en/modules/torrents-status.json
index 7e8970a92..fab92cd83 100644
--- a/public/locales/en/modules/torrents-status.json
+++ b/public/locales/en/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Show the current download speed of supported services",
+ "description": "Displays a list of torrents from supported Torrent clients.",
"settings": {
- "hideComplete": {
- "label": "Hide completed torrents"
+ "title": "Settings for Torrent widget",
+ "refreshInterval": {
+ "label": "Refresh interval (in seconds)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Display completed torrents"
+ },
+ "displayStaleTorrents": {
+ "label": "Display stale torrents"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "No supported download clients found!",
- "text": "Add a download service to view your current downloads"
+ "title": "No supported Torrent clients found!",
+ "text": "Add a supported Torrent client to view your current downloads"
+ },
+ "generic": {
+ "title": "An unexpected error occured",
+ "text": "Homarr was unable to communicate with your Torrent clients. Please check your configuration"
}
+ },
+ "loading": {
+ "title": "Loading..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/modules/usenet.json b/public/locales/en/modules/usenet.json
index f9df705a3..d21c37ff4 100644
--- a/public/locales/en/modules/usenet.json
+++ b/public/locales/en/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Allows you to see your usenet (Sabnzbd or NZBGet) queue and history, pause and resume downloads"
+ "description": "Allows you to view and manage your Usenet instance."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "No supported download clients found!",
- "text": "Add a download service to view your current downloads"
+ "text": "Add a supported Usenet Download Client to view your current downloads"
}
}
},
diff --git a/public/locales/en/modules/weather.json b/public/locales/en/modules/weather.json
index 405c36263..791f80897 100644
--- a/public/locales/en/modules/weather.json
+++ b/public/locales/en/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Weather",
- "description": "Look up the current weather in your location",
+ "description": "Displays the current weather information of a set location.",
"settings": {
+ "title": "Settings for weather widget",
"displayInFahrenheit": {
"label": "Display in Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Unknown"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/settings/common.json b/public/locales/en/settings/common.json
index 4ff9b984e..193f5195b 100644
--- a/public/locales/en/settings/common.json
+++ b/public/locales/en/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Customizations"
},
"tips": {
- "configTip": "Upload your config file by dragging and dropping it onto the page!"
+ "configTip": "Upload your config file by drag and dropping it onto the page!"
},
"credits": {
"madeWithLove": "Made with ❤️ by @"
},
- "grow": "Grow grid (take all space)"
-}
\ No newline at end of file
+ "grow": "Grow grid (take all space)",
+ "layout": {
+ "title": "Dashboard layout",
+ "main": "Main",
+ "sidebar": "Sidebar",
+ "cannotturnoff": "Cannot be turned off",
+ "dashboardlayout": "Dashboard layout",
+ "enablersidebar": "Enable right sidebar",
+ "enablelsidebar": "Enable left sidebar",
+ "enablesearchbar": "Enable search bar",
+ "enabledocker": "Enable docker integration",
+ "enableping": "Enable pings",
+ "enablelsidebardesc": "Optional. Can be used for apps and integrations only",
+ "enablersidebardesc": "Optional. Can be used for apps and integrations only"
+ }
+}
diff --git a/public/locales/en/settings/customization/page-appearance.json b/public/locales/en/settings/customization/page-appearance.json
index 63fa96886..459154ceb 100644
--- a/public/locales/en/settings/customization/page-appearance.json
+++ b/public/locales/en/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Page Title",
- "placeholder": "Homarr 🦞"
+ "label": "Page Title"
+ },
+ "metaTitle": {
+ "label": "Meta Title"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Custom CSS",
- "placeholder": "Custom CSS will be executed last"
+ "placeholder": "Custom CSS will be applied last"
},
"buttons": {
"submit": "Submit"
diff --git a/public/locales/en/settings/general/config-changer.json b/public/locales/en/settings/general/config-changer.json
index ad4ac012d..82e0a8374 100644
--- a/public/locales/en/settings/general/config-changer.json
+++ b/public/locales/en/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Config loader"
+ "label": "Config changer",
+ "description": "{{configCount}} configurations are available",
+ "loadingNew": "Loading your config...",
+ "pleaseWait": "Please wait until your new config is loaded!"
},
"modal": {
- "title": "Choose the name of your new config",
- "form": {
- "configName": {
- "label": "Config name",
- "placeholder": "Your new config name"
+ "copy": {
+ "title": "Choose the name of your new config",
+ "form": {
+ "configName": {
+ "label": "Config name",
+ "validation": {
+ "required": "Config name is required",
+ "notUnique": "Config name is already in use"
+ },
+ "placeholder": "Your new config name"
+ },
+ "submitButton": "Confirm"
},
- "submitButton": "Confirm"
+ "events": {
+ "configSaved": {
+ "title": "Config saved",
+ "message": "Config saved as {{configName}}"
+ },
+ "configCopied": {
+ "title": "Config copied",
+ "message": "Config copied as {{configName}}"
+ },
+ "configNotCopied": {
+ "title": "Unable to copy config",
+ "message": "Your config was not copied as {{configName}}"
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Config saved",
- "message": "Config saved as {{configName}}"
+ "confirmDeletion": {
+ "title": "Confirm deletion of your config",
+ "warningText": "You're about to delete '{{configName}} '",
+ "text": "Please note, that the deletion is not invertible and your data will be lost permanently. After clicking this button, the file will be permanently deleted from your disk. Make sure to create an adequate backup of your configuration.",
+ "buttons": {
+ "confirm": "Yes, delete '{{configName}} '"
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Config delete failed",
"message": "Config delete failed"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Default config can't be deleted",
+ "message": "Configuration was not deleted from the file system"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Drag files here to upload a config. Support for JSON only."
+ "title": "Configuration Upload",
+ "text": "Drag files here to upload a config. Support for JSON files only."
},
"reject": {
- "text": "This file format is not supported. Please only upload JSON."
+ "title": "Drag and Drop Upload rejected",
+ "text": "This file format is not supported. Please only upload JSON files."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/settings/general/module-enabler.json b/public/locales/en/settings/general/module-enabler.json
deleted file mode 100644
index 179753b6f..000000000
--- a/public/locales/en/settings/general/module-enabler.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "title": "Module enabler"
-}
\ No newline at end of file
diff --git a/public/locales/en/settings/general/search-engine.json b/public/locales/en/settings/general/search-engine.json
index 8d419fcf8..86fe8b862 100644
--- a/public/locales/en/settings/general/search-engine.json
+++ b/public/locales/en/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Search engine",
+ "configurationName": "Search engine configuration",
"tips": {
- "generalTip": "Use the prefixes !yt and !t in front of your query to search on YouTube or for a Torrent respectively.",
+ "generalTip": "There are multiple prefixes you can use! Adding these infront of your query will filter the results. !s (Web), !t (Torrents), !y (YouTube), and !m (Media).",
"placeholderTip": "%s can be used as a placeholder for the query."
},
"customEngine": {
+ "title": "Custom search engine",
"label": "Query URL",
"placeholder": "Custom query URL"
},
"searchNewTab": {
"label": "Open search results in new tab"
+ },
+ "searchEnabled": {
+ "label": "Search enabled"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/settings/general/widget-positions.json b/public/locales/en/settings/general/widget-positions.json
index 746578cce..41d0d60d6 100644
--- a/public/locales/en/settings/general/widget-positions.json
+++ b/public/locales/en/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
- "label": "Position widgets on left"
-}
\ No newline at end of file
+ "label": "Position widgets on the left"
+}
diff --git a/public/locales/es/authentication/login.json b/public/locales/es/authentication/login.json
index 268d533c8..7944a509a 100644
--- a/public/locales/es/authentication/login.json
+++ b/public/locales/es/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "¡Bienvenido/a otra vez!",
- "text": "Por favor, introduce la Contraseña",
+ "text": "Por favor, introduce tu contraseña",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Tu contraseña está siendo comprobada..."
},
"correct": {
- "title": "Contraseña correcta, redireccionándote..."
+ "title": "Inicio de sesión satisfactorio, redirigiendo..."
},
"wrong": {
- "title": "Contraseña incorrecta, por favor, intenta otra vez."
+ "title": "La contraseña introducida es incorrecta, por favor, intenta de nuevo."
}
}
}
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index c52a23152..24494ca8d 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Guardar"
+ "save": "Guardar",
+ "about": "Sobre",
+ "cancel": "Cancelar",
+ "close": "Cerrar",
+ "delete": "Eliminar",
+ "ok": "OK",
+ "edit": "Editar",
+ "version": "Versión",
+ "changePosition": "Cambiar posición",
+ "remove": "Eliminar",
+ "removeConfirm": "Seguro que quieres eliminar {{item}} ?",
+ "sections": {
+ "settings": "Ajustes",
+ "dangerZone": "Zona de riesgo"
+ },
+ "secrets": {
+ "apiKey": "Clave API",
+ "username": "Nombre de usuario",
+ "password": "Contraseña"
},
"tip": "Consejo: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutos",
"hours": "horas"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/es/layout/add-service-app-shelf.json b/public/locales/es/layout/add-service-app-shelf.json
index 50ea3ad56..20815426b 100644
--- a/public/locales/es/layout/add-service-app-shelf.json
+++ b/public/locales/es/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Opciones avanzadas",
"form": {
- "httpStatusCodes": {
- "label": "Códigos de Estado HTTP",
- "placeholder": "Seleccionar códigos de estado válidos",
- "clearButtonLabel": "Borrar selección",
- "nothingFound": "No se ha encontrado ningún resultado"
- },
"openServiceInNewTab": {
"label": "Abrir el servicio en una nueva pestaña"
},
diff --git a/public/locales/es/layout/element-selector/selector.json b/public/locales/es/layout/element-selector/selector.json
new file mode 100644
index 000000000..448cf2d7c
--- /dev/null
+++ b/public/locales/es/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Añadir nueva tarjeta",
+ "text": "Las tarjetas son el elemento principal de Homarr. Éstas se usan para mostrar tus apps, e información extra. Puedes agregar tantas tarjetas como quieras."
+ },
+ "widgetDescription": "Los widgets interactúan con tus apps, para otorgarte mayor control sobre éstas. Por lo general, éstos requieren de configuración adicional antes de poder usarlos.",
+ "goBack": "Volver al paso anterior",
+ "actionIcon": {
+ "tooltip": "Añadir tarjeta"
+ }
+}
diff --git a/public/locales/es/layout/header/actions/toggle-edit-mode.json b/public/locales/es/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..d0f4f8f3b
--- /dev/null
+++ b/public/locales/es/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "En Modo Edición, puedes ajustar tarjetas y configurar apps. Los cambios no se guardan hasta que salgas del Modo Edición.",
+ "button": {
+ "disabled": "Abrir Modo Edición",
+ "enabled": "Salir y Guardar"
+ },
+ "popover": {
+ "title": "Modo edición activado para el tamaño <1>{{size}}1>",
+ "text": "Puedes ajustar y configurar tus apps ahora. Los cambios no son guardados hasta que salgas del modo edición"
+ },
+ "screenSizes": {
+ "small": "pequeño",
+ "medium": "medio",
+ "large": "grande"
+ }
+}
diff --git a/public/locales/es/layout/mobile/drawer.json b/public/locales/es/layout/mobile/drawer.json
new file mode 100644
index 000000000..d640fbb11
--- /dev/null
+++ b/public/locales/es/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} barra lateral"
+}
diff --git a/public/locales/es/layout/modals/about.json b/public/locales/es/layout/modals/about.json
new file mode 100644
index 000000000..914f56aad
--- /dev/null
+++ b/public/locales/es/layout/modals/about.json
@@ -0,0 +1,6 @@
+{
+ "i18n": "I18n translation namespaces cargados",
+ "locales": "Configurar I18n locales",
+ "contact": "¿Tienes problemas o preguntas? ¡Conéctate con nosotros!",
+ "addToDashboard": "Añadir al Dashboard"
+}
diff --git a/public/locales/es/layout/modals/add-app.json b/public/locales/es/layout/modals/add-app.json
new file mode 100644
index 000000000..c99a35771
--- /dev/null
+++ b/public/locales/es/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "General",
+ "behaviour": "Comportamiento",
+ "network": "Red",
+ "appearance": "Apariencia",
+ "integration": "Integración"
+ },
+ "general": {
+ "appname": {
+ "label": "Nombre de la app",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "Dirección interna",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "Dirección externa",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Abrir en nueva pestaña",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Verificador de estado",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "Códigos HTTP de estado",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "Icono App",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Configuración de integración",
+ "description": "",
+ "placeholder": "Seleccionar una integración",
+ "defined": "Definido",
+ "undefined": "Sin definir",
+ "public": "Público",
+ "private": "Privado",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "Para actualizar una clave, introducir un valor y pulsar el botón guardar. Para eliminar una clave, usa el botón limpiar.",
+ "warning": "",
+ "clear": "Limpiar clave",
+ "save": "Guardar clave",
+ "update": "Actualizar clave"
+ }
+ },
+ "validation": {
+ "popover": "Tu formulario contiene datos no válidos. Por lo tanto, no pude ser guardado. Por favor, resuelve los problemas, y presiona este botón, otra vez, para guardar cambios"
+ }
+}
diff --git a/public/locales/es/layout/modals/change-position.json b/public/locales/es/layout/modals/change-position.json
new file mode 100644
index 000000000..d797e40c9
--- /dev/null
+++ b/public/locales/es/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "Posición eje X",
+ "width": "Ancho",
+ "height": "Altura",
+ "yPosition": "Posición eje Y",
+ "zeroOrHigher": "0 o superior",
+ "betweenXandY": "Entre {{min}} y {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/es/layout/screen-sizes.json b/public/locales/es/layout/screen-sizes.json
new file mode 100644
index 000000000..8d1288896
--- /dev/null
+++ b/public/locales/es/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "pequeño",
+ "medium": "medio",
+ "large": "grande"
+ }
+}
diff --git a/public/locales/es/layout/tools.json b/public/locales/es/layout/tools.json
new file mode 100644
index 000000000..117b2a1ec
--- /dev/null
+++ b/public/locales/es/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Actualmente, no tienes ninguna herramienta"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Buscar iconos...",
+ "searchLimitationTitle": "Búsqueda limitada a {{max}} iconos",
+ "searchLimitationMessage": "Para mantener las cosas ágiles y rápidas, la búsqueda está limitada a {{max}} iconos. Usa el cuadro de búsqueda para encontrar más iconos"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/es/modules/calendar.json b/public/locales/es/modules/calendar.json
index 94bb9ff6d..e390b0496 100644
--- a/public/locales/es/modules/calendar.json
+++ b/public/locales/es/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendario",
- "description": "Un módulo de calendario para mostrar próximos lanzamientos. Interactúa con las API de Sonarr y Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Marcar Domingo como primer día de la semana"
+ },
+ "radarrReleaseType": {
+ "label": "Tipo de release en Radarr"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/common.json b/public/locales/es/modules/common.json
index 9a96df72d..07775cac0 100644
--- a/public/locales/es/modules/common.json
+++ b/public/locales/es/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Ajustes"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/dashdot.json b/public/locales/es/modules/dashdot.json
index 8345d26fd..fbd4c8916 100644
--- a/public/locales/es/modules/dashdot.json
+++ b/public/locales/es/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Un módulo para mostrar los gráficos de la instancia de Dash. en ejecución.",
+ "description": "",
"settings": {
+ "title": "Ajustes para el widget Dash.",
"cpuMultiView": {
"label": "Vista CPU Multinúcleo"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/date.json b/public/locales/es/modules/date.json
index 127072703..4ed6f3fb1 100644
--- a/public/locales/es/modules/date.json
+++ b/public/locales/es/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Fecha",
- "description": "Mostrar la fecha y hora actuales en una tarjeta",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Mostrar hora completa (24 horas)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/dlspeed.json b/public/locales/es/modules/dlspeed.json
index 19bfd2dc6..b2da20c34 100644
--- a/public/locales/es/modules/dlspeed.json
+++ b/public/locales/es/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Velocidad de Descarga",
- "description": "Mostrar la velocidad de descarga actual de los servicios compatibles"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/docker.json b/public/locales/es/modules/docker.json
index edd23cc74..c4b9b273f 100644
--- a/public/locales/es/modules/docker.json
+++ b/public/locales/es/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Permite administrar sus contenedores docker de manera sencilla"
+ "description": ""
},
"search": {
"placeholder": "Buscar por nombre de contenedor o nombre de imagen"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Añadir servicio",
- "message": "Añadir servicio a Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Reiniciar"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Error en la integración con Docker",
- "message": "¿Has olvidado montar el socket de docker?"
+ "message": ""
},
"unknownError": {
"title": "Se ha producido un error"
},
"oneServiceAtATime": {
- "title": "Por favor, ¡añade un servicio a la vez!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/overseerr.json b/public/locales/es/modules/overseerr.json
index 373ec6e1d..2c03b0502 100644
--- a/public/locales/es/modules/overseerr.json
+++ b/public/locales/es/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Te permite buscar y añadir medios desde Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Marca las temporadas que quieres que se descarguen",
+ "caption": "",
"table": {
"header": {
"season": "Temporada",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/ping.json b/public/locales/es/modules/ping.json
index ecf4efdb2..5315f83dd 100644
--- a/public/locales/es/modules/ping.json
+++ b/public/locales/es/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Latencia",
- "description": "Te permite comprobar si el servicio está operativo o si devuelve un código HTTP en específico."
+ "description": ""
},
"states": {
"online": "En línea {{response}}",
"offline": "Desconectado {{response}}",
"loading": "Cargando..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/search.json b/public/locales/es/modules/search.json
index 9d1742273..b76b6cddb 100644
--- a/public/locales/es/modules/search.json
+++ b/public/locales/es/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Barra de búsqueda",
- "description": "Barra de búsqueda para buscar en la web, YouTube, Torrents u Overseerr"
+ "description": ""
},
"input": {
"placeholder": "Buscar en Internet..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "Web",
- "description": "Buscar usando tu motor de búsqueda (definido en ajustes)"
+ "description": ""
},
"youtube": {
"name": "Youtube",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Buscar Películas y Series TV usando Overseerr (el módulo debe esta activado)"
+ "description": ""
}
},
"tip": "Puedes seleccionar la barra de búsqueda con el atajo ",
"switchedSearchEngine": "Cambiado a buscando con {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/torrents-status.json b/public/locales/es/modules/torrents-status.json
index 1cf179205..fae30838b 100644
--- a/public/locales/es/modules/torrents-status.json
+++ b/public/locales/es/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Mostrar la velocidad de descarga actual de los servicios compatibles",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Ocultar torrents completados"
+ "title": "",
+ "refreshInterval": {
+ "label": "Intervalo de refresco (en segundos)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Mostrar torrents completados"
+ },
+ "displayStaleTorrents": {
+ "label": "Mostrar torrents estancados"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "¡No se han encontrado clientes de descarga compatibles!",
- "text": "Añade un servicio de descarga para ver tus descargas actuales"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "Ocurrió un error inesperado",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Cargando..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/modules/usenet.json b/public/locales/es/modules/usenet.json
index fda32e8b4..3023f8675 100644
--- a/public/locales/es/modules/usenet.json
+++ b/public/locales/es/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Te permite ver la cola e historial de tu usenet (Sabnzbd or NZBGet), pausar y reanudar descargas"
+ "description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "¡No se han encontrado clientes de descarga compatibles!",
- "text": "Añade un servicio de descarga para ver tus descargas actuales"
+ "text": ""
}
}
},
diff --git a/public/locales/es/modules/weather.json b/public/locales/es/modules/weather.json
index 3532932ba..12fbe7f37 100644
--- a/public/locales/es/modules/weather.json
+++ b/public/locales/es/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Clima",
- "description": "Consulta el clima actual en tu ubicación",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Mostrar en Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Desconocido"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/settings/common.json b/public/locales/es/settings/common.json
index 4fb995e74..bd5b62586 100644
--- a/public/locales/es/settings/common.json
+++ b/public/locales/es/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Personalizaciones"
},
"tips": {
- "configTip": "¡Sube tu archivo de configuración arrastrándolo y soltándolo en la página!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "Hecho con ❤️ por @"
},
- "grow": "Aumentar cuadrícula (toma todo el espacio)"
-}
\ No newline at end of file
+ "grow": "Aumentar cuadrícula (toma todo el espacio)",
+ "layout": {
+ "title": "Diseño del dashboard",
+ "main": "Principal",
+ "sidebar": "Barra lateral",
+ "cannotturnoff": "No puede ser apagado",
+ "dashboardlayout": "Diseño del dashboard",
+ "enablersidebar": "Habilitar barra lateral derecha",
+ "enablelsidebar": "Habilitar barra lateral izquierda",
+ "enablesearchbar": "Habilitar barra de búsqueda",
+ "enabledocker": "Habilitar integración docker",
+ "enableping": "Habilitar pings",
+ "enablelsidebardesc": "Opcional. Solo puede usarse con apps e integraciones",
+ "enablersidebardesc": "Opcional. Solo puede usarse con apps e integraciones"
+ }
+}
diff --git a/public/locales/es/settings/customization/page-appearance.json b/public/locales/es/settings/customization/page-appearance.json
index d55ab3519..b858f1e83 100644
--- a/public/locales/es/settings/customization/page-appearance.json
+++ b/public/locales/es/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Título de Página",
- "placeholder": "Homarr 🦞"
+ "label": "Título de Página"
+ },
+ "metaTitle": {
+ "label": "Tarjeta Meta"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "CSS Personalizado",
- "placeholder": "El CSS personalizado se ejecutará en último lugar"
+ "placeholder": ""
},
"buttons": {
"submit": "Aplicar"
diff --git a/public/locales/es/settings/general/config-changer.json b/public/locales/es/settings/general/config-changer.json
index c7259767b..4001ec8c7 100644
--- a/public/locales/es/settings/general/config-changer.json
+++ b/public/locales/es/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Cargador de configuración"
+ "label": "",
+ "description": "",
+ "loadingNew": "Cargando tu configuración...",
+ "pleaseWait": ""
},
"modal": {
- "title": "Escoge el nombre de tu nueva configuración",
- "form": {
- "configName": {
- "label": "Nombre de la configuración",
- "placeholder": "Tu nuevo nombre de configuración"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Confirmar"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Configuración guardada",
- "message": "Configuración guardada como {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Error en el borrado de la configuración",
"message": "Error en el borrado de la configuración"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Arrastra un archivo aqui para subir una configuración. Sólo se admiten archivos JSON."
+ "title": "Configuración subida",
+ "text": ""
},
"reject": {
- "text": "Este formato de archivo no es compatible. Por favor, sube solo archivos JSON."
+ "title": "Subida Arrastar y Soltar rechada",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/settings/general/module-enabler.json b/public/locales/es/settings/general/module-enabler.json
index b3ed9372d..9e26dfeeb 100644
--- a/public/locales/es/settings/general/module-enabler.json
+++ b/public/locales/es/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Activador de Módulos"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/es/settings/general/search-engine.json b/public/locales/es/settings/general/search-engine.json
index 0b576aa00..8c55f8426 100644
--- a/public/locales/es/settings/general/search-engine.json
+++ b/public/locales/es/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Motor de búsqueda",
+ "configurationName": "Configuración del motor de búsqueda",
"tips": {
- "generalTip": "Usa los prefijos !yt y !t delante de tu consulta para buscar en YouTube o un torrent respectivamente.",
+ "generalTip": "",
"placeholderTip": "%s puede utilizarse como modelo para la petición."
},
"customEngine": {
+ "title": "Motor de búsqueda personalizado",
"label": "URL de la Petición",
"placeholder": "URL de petición personalizada"
},
"searchNewTab": {
"label": "Abrir los resultados de la búsqueda en una pestaña nueva"
+ },
+ "searchEnabled": {
+ "label": "Búsqueda habilitada"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/es/settings/general/widget-positions.json b/public/locales/es/settings/general/widget-positions.json
index b14e5648b..0967ef424 100644
--- a/public/locales/es/settings/general/widget-positions.json
+++ b/public/locales/es/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Colocar widgets a la izquierda"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/fr/authentication/login.json b/public/locales/fr/authentication/login.json
index 9e47001a8..112db8720 100644
--- a/public/locales/fr/authentication/login.json
+++ b/public/locales/fr/authentication/login.json
@@ -18,10 +18,10 @@
"message": "Votre mot de passe est en cours de vérification..."
},
"correct": {
- "title": "Mot de passe correct, redirection en cours..."
+ "title": "Inscription réussie, redirection..."
},
"wrong": {
- "title": "Mot de passe erroné, veuillez réessayer."
+ "title": "Le mot de passe saisi est incorrect, veuillez réessayer."
}
}
}
diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json
index cf78741aa..aa92409e4 100644
--- a/public/locales/fr/common.json
+++ b/public/locales/fr/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Sauvegarder"
+ "save": "Sauvegarder",
+ "about": "À propos",
+ "cancel": "Annuler",
+ "close": "Fermer",
+ "delete": "Supprimer",
+ "ok": "OK",
+ "edit": "Modifier",
+ "version": "Version",
+ "changePosition": "Modifier la position",
+ "remove": "Supprimer",
+ "removeConfirm": "Êtes-vous sûr de vouloir supprimer {{item}} ?",
+ "sections": {
+ "settings": "Paramètres",
+ "dangerZone": "Zone de danger"
+ },
+ "secrets": {
+ "apiKey": "Clé d'API",
+ "username": "Nom d'utilisateur",
+ "password": "Mot de passe"
},
"tip": "Conseil : ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutes",
"hours": "heures"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/fr/layout/add-service-app-shelf.json b/public/locales/fr/layout/add-service-app-shelf.json
index f94e8c0a4..fe2b74135 100644
--- a/public/locales/fr/layout/add-service-app-shelf.json
+++ b/public/locales/fr/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Options avancées",
"form": {
- "httpStatusCodes": {
- "label": "Codes d'état HTTP",
- "placeholder": "Sélectionnez les codes d'état valides",
- "clearButtonLabel": "Effacer la sélection",
- "nothingFound": "Rien trouvé"
- },
"openServiceInNewTab": {
"label": "Ouvrir le service dans un nouvel onglet"
},
diff --git a/public/locales/fr/layout/element-selector/selector.json b/public/locales/fr/layout/element-selector/selector.json
new file mode 100644
index 000000000..95b5836ee
--- /dev/null
+++ b/public/locales/fr/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Ajouter une tuile",
+ "text": ""
+ },
+ "widgetDescription": "Les widgets interagissent avec vos applications, pour vous permettre de mieux les contrôler. Ils nécessitent généralement quelques configurations supplémentaires avant d'être utilisés.",
+ "goBack": "Retourner à la page précédente",
+ "actionIcon": {
+ "tooltip": "Ajouter une tuile"
+ }
+}
diff --git a/public/locales/fr/layout/header/actions/toggle-edit-mode.json b/public/locales/fr/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..39e034157
--- /dev/null
+++ b/public/locales/fr/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "Entrer en mode édition",
+ "enabled": "Quitter et sauvegarder"
+ },
+ "popover": {
+ "title": "",
+ "text": "Vous pouvez désormais ajuster et configurer vos applications. Les modifications ne sont pas enregistrées jusqu'à ce que vous quittiez le mode édition"
+ },
+ "screenSizes": {
+ "small": "petit",
+ "medium": "moyen",
+ "large": "grand"
+ }
+}
diff --git a/public/locales/fr/layout/mobile/drawer.json b/public/locales/fr/layout/mobile/drawer.json
new file mode 100644
index 000000000..de438318a
--- /dev/null
+++ b/public/locales/fr/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} barre latérale"
+}
diff --git a/public/locales/fr/layout/modals/about.json b/public/locales/fr/layout/modals/about.json
new file mode 100644
index 000000000..582f5d996
--- /dev/null
+++ b/public/locales/fr/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr est un tableau de bord élégant , moderne qui met toutes vos applications et services au bout de vos doigts. Avec Homarr, vous pouvez accéder et contrôler tout dans un seul endroit. Homarr s'intègre de façon transparente avec les applications que vous avez ajoutées, vous fournissant des informations précieuses et vous donnant un contrôle total. L'installation est un jeu d'enfant, et Homarr prend en charge un large éventail de méthodes de déploiement.",
+ "i18n": "Espaces de traduction I18n chargés",
+ "locales": "Locales I18n configurées",
+ "contact": "Vous avez des problèmes ou des questions ? Communiquez-le nous !",
+ "addToDashboard": "Ajouter au tableau de bord"
+}
diff --git a/public/locales/fr/layout/modals/add-app.json b/public/locales/fr/layout/modals/add-app.json
new file mode 100644
index 000000000..dd58e754c
--- /dev/null
+++ b/public/locales/fr/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Général",
+ "behaviour": "Comportement",
+ "network": "Réseau",
+ "appearance": "Apparence",
+ "integration": "Intégration"
+ },
+ "general": {
+ "appname": {
+ "label": "Nom de l'application",
+ "description": "Utilisé pour afficher l'application sur le tableau de bord."
+ },
+ "internalAddress": {
+ "label": "Adresse interne",
+ "description": "IP interne de l'application."
+ },
+ "externalAddress": {
+ "label": "Adresse externe",
+ "description": "URL qui sera ouverte dans le navigateur lorsque l'on clique sur l'application."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Ouvrir dans un nouvel onglet",
+ "description": "Ouvrez l'application dans un nouvel onglet au lieu de l'onglet actuel."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Vérificateur de statut",
+ "description": "Vérifie si votre application est en ligne en utilisant une simple requête HTTP(S)."
+ },
+ "statusCodes": {
+ "label": "Codes d'état HTTP",
+ "description": "Les codes d'état HTTP qui sont considérés comme étant en ligne."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "Icône de l’app",
+ "description": "L'icône qui sera affichée sur le tableau de bord."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Configuration d’intégrations",
+ "description": "",
+ "placeholder": "Sélectionner une itégration",
+ "defined": "Défini",
+ "undefined": "Indéfini",
+ "public": "Public",
+ "private": "Privé",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "Pour mettre à jour un secret, entrez une valeur et cliquez sur le bouton Enregistrer. Pour supprimer un secret, utilisez le bouton Effacer.",
+ "warning": "",
+ "clear": "Effacer le secret",
+ "save": "Sauvegarder le secret",
+ "update": "Mettre à jour le secret"
+ }
+ },
+ "validation": {
+ "popover": "Votre formulaire contient des données invalides et ne peut être sauvegardé. Veuillez résoudre tous les problèmes et cliquez à nouveau sur ce bouton pour enregistrer vos modifications"
+ }
+}
diff --git a/public/locales/fr/layout/modals/change-position.json b/public/locales/fr/layout/modals/change-position.json
new file mode 100644
index 000000000..4e1016a5f
--- /dev/null
+++ b/public/locales/fr/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "Position sur l'axe X",
+ "width": "Largeur",
+ "height": "Hauteur",
+ "yPosition": "Position sur l'axe X",
+ "zeroOrHigher": "0 ou plus",
+ "betweenXandY": "Entre {{min}} et {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/fr/layout/screen-sizes.json b/public/locales/fr/layout/screen-sizes.json
new file mode 100644
index 000000000..487cdf1ab
--- /dev/null
+++ b/public/locales/fr/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "petit",
+ "medium": "moyen",
+ "large": "grand"
+ }
+}
diff --git a/public/locales/fr/layout/tools.json b/public/locales/fr/layout/tools.json
new file mode 100644
index 000000000..b4745d34f
--- /dev/null
+++ b/public/locales/fr/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Vous ne disposez actuellement d'aucun outil"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Rechercher des icônes",
+ "searchLimitationTitle": "La recherche est limitée à {{max}} icônes",
+ "searchLimitationMessage": "Pour que les choses restent rapides, la recherche est limitée à {{max}} icônes. Utilisez le champ de recherche pour trouver d'autres icônes"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/fr/modules/calendar.json b/public/locales/fr/modules/calendar.json
index fcf3e6001..9ffc23c68 100644
--- a/public/locales/fr/modules/calendar.json
+++ b/public/locales/fr/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendrier",
- "description": "Un module de calendrier pour afficher les prochaines versions. Il interagit avec les API Sonarr et Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Commencez la semaine par dimanche"
+ },
+ "radarrReleaseType": {
+ "label": "Type de sortie Radarr"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/common.json b/public/locales/fr/modules/common.json
index e13463d31..cc7a46382 100644
--- a/public/locales/fr/modules/common.json
+++ b/public/locales/fr/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Paramètres"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Un paramètre non utilisé dans votre configuration a été détectée {{key}}. Homarr est incapable d'interpréter et d'utiliser ce paramètre. Pour éviter tout comportement inattendu, sauvegardez votre configuration et corrigez-la."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/dashdot.json b/public/locales/fr/modules/dashdot.json
index b33cbba31..46c383e86 100644
--- a/public/locales/fr/modules/dashdot.json
+++ b/public/locales/fr/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Un module pour afficher les graphiques de votre instance Dash. en cours.",
+ "description": "",
"settings": {
+ "title": "Paramètres du widget Dash",
"cpuMultiView": {
"label": "Vue du CPU multi-cœur"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/date.json b/public/locales/fr/modules/date.json
index c1f777c4f..08db74791 100644
--- a/public/locales/fr/modules/date.json
+++ b/public/locales/fr/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Date",
- "description": "Affiches l'heure et la date actuelles dans un module",
+ "name": "Date et heure",
+ "description": "Affiche la date et l'heure courante.",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Affichage 24 h"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/dlspeed.json b/public/locales/fr/modules/dlspeed.json
index 187fbd054..aa4f78111 100644
--- a/public/locales/fr/modules/dlspeed.json
+++ b/public/locales/fr/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Vitesse de téléchargement",
- "description": "Afficher la vitesse de téléchargement actuelle des services pris en charge"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/docker.json b/public/locales/fr/modules/docker.json
index 2cefad874..ddf7efa72 100644
--- a/public/locales/fr/modules/docker.json
+++ b/public/locales/fr/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Vous permet de gérer facilement vos conteneurs docker"
+ "description": ""
},
"search": {
"placeholder": "Recherche par nom de conteneur ou d'image"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Ajouter un service",
- "message": "Ajouter un service à Homarr"
+ "title": "Ajouter une application",
+ "message": ""
},
"restart": {
"title": "Redémarrer"
@@ -41,7 +41,7 @@
"title": "Rafraîchir les données"
},
"remove": {
- "title": "Retirer"
+ "title": "Supprimer"
},
"addToHomarr": {
"title": "Ajouter à Homarr"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "L'intégration de Docker a échoué",
- "message": "Avez-vous oublié de monter le docker socket ?"
+ "message": "Avez-vous oublié de monter le socket Docker ?"
},
"unknownError": {
"title": "Une erreur s’est produite"
},
"oneServiceAtATime": {
- "title": "Veuillez n'ajouter qu'un seul service à la fois !"
+ "title": "Veuillez n'ajouter qu'une application ou service à la fois !"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/overseerr.json b/public/locales/fr/modules/overseerr.json
index 2884a5b98..975dc1769 100644
--- a/public/locales/fr/modules/overseerr.json
+++ b/public/locales/fr/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Permet de rechercher et d'ajouter des médias depuis Overseerr/Jellyseerr"
+ "description": "Permet de rechercher et d'ajouter des médias depuis Overseerr ou Jellyseerr."
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Cochez les saisons que vous souhaitez télécharger",
+ "caption": "",
"table": {
"header": {
"season": "Saison",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/ping.json b/public/locales/fr/modules/ping.json
index fdb946c0e..d9317be27 100644
--- a/public/locales/fr/modules/ping.json
+++ b/public/locales/fr/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Permet de vérifier si le service est en place ou renvoie un code d'état HTTP spécifique."
+ "description": ""
},
"states": {
"online": "En ligne {{response}}",
"offline": "Hors ligne {{response}}",
"loading": "Chargement..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/search.json b/public/locales/fr/modules/search.json
index f81d6b1ee..9100ecbbe 100644
--- a/public/locales/fr/modules/search.json
+++ b/public/locales/fr/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Barre de recherche",
- "description": "Barre de recherche pour rechercher sur le web, Youtube, Torrents ou Overseerr"
+ "description": "Une barre de recherche qui vous permet d'effectuer des recherches dans un moteur de recherche personnalisé, sur YouTube et dans les intégrations prises en charge."
},
"input": {
"placeholder": "Cherchez sur le web..."
@@ -10,11 +10,11 @@
"searchEngines": {
"search": {
"name": "Web",
- "description": "Recherche à l'aide de votre moteur de recherche (défini dans les paramètres)"
+ "description": "Rechercher..."
},
"youtube": {
"name": "YouTube",
- "description": "Cherchez sur YouTube"
+ "description": "Rechercher sur YouTube"
},
"torrents": {
"name": "Torrents",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Chercher des films et séries TV avec Overseerr (le module doit-être activé)"
+ "description": "Rechercher des films et sériess sur Overseerr"
}
},
"tip": "Vous pouvez sélectionner la barre de recherche avec le raccourci ",
"switchedSearchEngine": "Basculer vers la recherche avec {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/torrents-status.json b/public/locales/fr/modules/torrents-status.json
index aea9aec81..fb9c03ab2 100644
--- a/public/locales/fr/modules/torrents-status.json
+++ b/public/locales/fr/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Afficher la vitesse de téléchargement actuelle des services pris en charge",
+ "description": "Affiche une liste de torrents provenant de clients Torrent pris en charge.",
"settings": {
- "hideComplete": {
+ "title": "Paramètres du widget Torrent",
+ "refreshInterval": {
+ "label": "Intervalle d'actualisation en secondes"
+ },
+ "displayCompletedTorrents": {
"label": "Cacher les torrents terminés"
+ },
+ "displayStaleTorrents": {
+ "label": "Afficher les torrents périmés"
}
}
},
@@ -13,8 +20,8 @@
"header": {
"name": "Nom",
"size": "Taille",
- "download": "Duvet",
- "upload": "Up",
+ "download": "Descendant",
+ "upload": "Montant",
"estimatedTimeOfArrival": "ETA",
"progress": "Progrès"
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Aucun client de téléchargement supporté n'a été trouvé !",
- "text": "Ajouter un service de téléchargement pour afficher vos téléchargements en cours"
+ "title": "Aucun client Torrent supporté n'a été trouvé !",
+ "text": "Ajouter un client Torrent pris en charge pour voir vos téléchargements en cours"
+ },
+ "generic": {
+ "title": "Une erreur inattendue s'est produite",
+ "text": "Homarr n'a pas pu communiquer avec vos clients Torrent. Veuillez vérifier votre configuration"
}
+ },
+ "loading": {
+ "title": "Chargement..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/modules/usenet.json b/public/locales/fr/modules/usenet.json
index 9e747820b..afd2ea7f0 100644
--- a/public/locales/fr/modules/usenet.json
+++ b/public/locales/fr/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Permet de voir la file d'attente et l'historique de vos téléchargements usenet (Sabnzbd ou NZBGet), de mettre en pause et de reprendre les téléchargements"
+ "description": "Vous permet d'afficher et de gérer votre instance Usenet."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Aucun client de téléchargement supporté n'a été trouvé !",
- "text": "Ajouter un service de téléchargement pour afficher vos téléchargements en cours"
+ "text": "Ajoutez un client de téléchargement Usenet pris en charge pour afficher vos téléchargements"
}
}
},
diff --git a/public/locales/fr/modules/weather.json b/public/locales/fr/modules/weather.json
index 45669569c..8accacb79 100644
--- a/public/locales/fr/modules/weather.json
+++ b/public/locales/fr/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Météo",
- "description": "Consultez la météo actuelle dans votre région",
+ "description": "Affiche la météo actuelle d'un emplacement préconfiguré.",
"settings": {
+ "title": "Paramètres du widget météo",
"displayInFahrenheit": {
"label": "Affichage en Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Inconnu"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/settings/common.json b/public/locales/fr/settings/common.json
index 43759b7b5..80cdaca53 100644
--- a/public/locales/fr/settings/common.json
+++ b/public/locales/fr/settings/common.json
@@ -2,14 +2,28 @@
"title": "Paramètres",
"tooltip": "Paramètres",
"tabs": {
- "common": "Common",
+ "common": "Général",
"customizations": "Personnalisations"
},
"tips": {
- "configTip": "Téléchargez votre fichier de configuration en le faisant glisser et en le déposant sur la page !"
+ "configTip": "Importer votre fichier de configuration en le faisant glisser et en le déposant sur la page !"
},
"credits": {
"madeWithLove": "Fait avec ❤️ par @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Grille progressive (prend toute la largeur)",
+ "layout": {
+ "title": "Disposition du tableau de bord",
+ "main": "Principal",
+ "sidebar": "Barre latérale",
+ "cannotturnoff": "Ne peut être désactivé",
+ "dashboardlayout": "Disposition du tableau de bord",
+ "enablersidebar": "Activer la barre latérale droite",
+ "enablelsidebar": "Activer la barre latérale gauche",
+ "enablesearchbar": "Activer la barre de recherche",
+ "enabledocker": "Activer l'intégration Docker",
+ "enableping": "Activer les \"pings\"",
+ "enablelsidebardesc": "Optionnel. Ne peut être utilisé que pour les applications et les intégrations",
+ "enablersidebardesc": "Optionnel. Ne peut être utilisé que pour les applications et les intégrations"
+ }
+}
diff --git a/public/locales/fr/settings/customization/page-appearance.json b/public/locales/fr/settings/customization/page-appearance.json
index d604e5c40..0072601b6 100644
--- a/public/locales/fr/settings/customization/page-appearance.json
+++ b/public/locales/fr/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Titre de la page",
- "placeholder": "Homarr 🦞"
+ "label": "Titre de la page"
+ },
+ "metaTitle": {
+ "label": "Titre Méta"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "CSS personnalisé",
- "placeholder": "Le CSS personnalisé sera exécuté en dernier"
+ "placeholder": "Le CSS personnalisé sera appliqué en dernier"
},
"buttons": {
"submit": "Soumettre"
diff --git a/public/locales/fr/settings/general/color-schema.json b/public/locales/fr/settings/general/color-schema.json
index b9a69545a..236cb9728 100644
--- a/public/locales/fr/settings/general/color-schema.json
+++ b/public/locales/fr/settings/general/color-schema.json
@@ -1,3 +1,3 @@
{
- "label": "Passez en mode {{scheme}}"
+ "label": "Passer en mode {{scheme}}"
}
\ No newline at end of file
diff --git a/public/locales/fr/settings/general/config-changer.json b/public/locales/fr/settings/general/config-changer.json
index 2951c8163..c964b9391 100644
--- a/public/locales/fr/settings/general/config-changer.json
+++ b/public/locales/fr/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Chargeur de configuration"
+ "label": "",
+ "description": "",
+ "loadingNew": "Chargement des configurations ...",
+ "pleaseWait": "Veuillez attendre que votre nouvelle configuration soit chargée !"
},
"modal": {
- "title": "Choisissez le nom de votre nouvelle configuration",
- "form": {
- "configName": {
- "label": "Nom de la configuration",
- "placeholder": "Le nom de votre nouvelle configuration"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Confirmer"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Configuration sauvegardée",
- "message": "Configuration enregistrée sous {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "La suppression de la configuration a échoué",
"message": "La suppression de la configuration a échoué"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "La configuration par défaut ne peut pas être supprimée",
+ "message": "La configuration n'a pas pu être supprimée du système de fichiers"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Faites glisser les fichiers ici pour télécharger une configuration. Support pour JSON uniquement."
+ "title": "Téléversement de la configuration",
+ "text": "Faites glisser les fichiers ici pour importer votre configuration. Supporte uniquement les fichiers JSON."
},
"reject": {
- "text": "Ce format de fichier n'est pas pris en charge. Veuillez télécharger uniquement JSON."
+ "title": "Upload par glisser-déposer rejeté",
+ "text": "Ce format de fichier n'est pas pris en charge. Veuillez ne téléverser que des fichiers JSON."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/settings/general/module-enabler.json b/public/locales/fr/settings/general/module-enabler.json
index 179753b6f..9e26dfeeb 100644
--- a/public/locales/fr/settings/general/module-enabler.json
+++ b/public/locales/fr/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Module enabler"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/fr/settings/general/search-engine.json b/public/locales/fr/settings/general/search-engine.json
index fa2b7e2b0..ba73acb6d 100644
--- a/public/locales/fr/settings/general/search-engine.json
+++ b/public/locales/fr/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Moteur de recherche",
+ "configurationName": "Configuration du moteur de recherche",
"tips": {
- "generalTip": "Utilisez les préfixes !yt et !t devant votre requête pour rechercher respectivement sur YouTube ou pour un Torrent.",
+ "generalTip": "Il existe plusieurs préfixes que vous pouvez utiliser ! L'ajout de ces préfixes devant votre requête filtrera les résultats. !s (Web), !t (Torrents), !y (YouTube), et !m (Media).",
"placeholderTip": "%s peut être utilisé en tant que placeholder pour la requête."
},
"customEngine": {
+ "title": "Moteur de recherche personnalisé",
"label": "URL de la requête",
"placeholder": "URL de requête personnalisée"
},
"searchNewTab": {
"label": "Ouvrir les résultats de la recherche dans un nouvel onglet"
+ },
+ "searchEnabled": {
+ "label": "Recherche activée"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/fr/settings/general/theme-selector.json b/public/locales/fr/settings/general/theme-selector.json
index aae766602..170184905 100644
--- a/public/locales/fr/settings/general/theme-selector.json
+++ b/public/locales/fr/settings/general/theme-selector.json
@@ -1,3 +1,3 @@
{
- "label": "Passez en mode {{theme}}"
+ "label": "Passer en mode {{theme}}"
}
\ No newline at end of file
diff --git a/public/locales/fr/settings/general/widget-positions.json b/public/locales/fr/settings/general/widget-positions.json
index 28649cb99..96674015a 100644
--- a/public/locales/fr/settings/general/widget-positions.json
+++ b/public/locales/fr/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Positionner les widgets à gauche"
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/authentication/login.json b/public/locales/he/authentication/login.json
index 586b0ab5d..b4e9fa11e 100644
--- a/public/locales/he/authentication/login.json
+++ b/public/locales/he/authentication/login.json
@@ -1,6 +1,6 @@
{
- "title": "ברוך הבא!",
- "text": "אנא הזן את הסיסמה",
+ "title": "ברוך שובך!",
+ "text": "נא להזין סיסמה",
"form": {
"fields": {
"password": {
@@ -9,7 +9,7 @@
}
},
"buttons": {
- "submit": "כניסה למערכת"
+ "submit": "התחבר\\י"
}
},
"notifications": {
@@ -18,10 +18,10 @@
"message": "הסיסמה בבדיקה..."
},
"correct": {
- "title": "הסיסמה נכונה לניתוב מחדש..."
+ "title": "התחברת בהצלחה"
},
"wrong": {
- "title": "הסיסמה שגויה אנא נסה שוב."
+ "title": "הסיסמה שגויה, נסה שנית"
}
}
}
diff --git a/public/locales/he/common.json b/public/locales/he/common.json
index 8bb412672..a27baca6f 100644
--- a/public/locales/he/common.json
+++ b/public/locales/he/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "שמור"
+ "save": "שמור",
+ "about": "אודות",
+ "cancel": "בטל",
+ "close": "סגור",
+ "delete": "מחיקה",
+ "ok": "אישור",
+ "edit": "עריכה",
+ "version": "גרסה",
+ "changePosition": "שנה מיקום",
+ "remove": "הסר",
+ "removeConfirm": "האם אתה בטוח שברצונך להסיר את {{item}} ?",
+ "sections": {
+ "settings": "הגדרות",
+ "dangerZone": "אזור מסוכן"
+ },
+ "secrets": {
+ "apiKey": "מפתח API",
+ "username": "שם משתמש",
+ "password": "סיסמה"
},
"tip": "טיפ:",
"time": {
@@ -8,4 +25,4 @@
"minutes": "דקות",
"hours": "שעות"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/he/layout/add-service-app-shelf.json b/public/locales/he/layout/add-service-app-shelf.json
index 4b6ed80d5..1acdb404d 100644
--- a/public/locales/he/layout/add-service-app-shelf.json
+++ b/public/locales/he/layout/add-service-app-shelf.json
@@ -36,7 +36,7 @@
"label": "קטגוריה",
"placeholder": "בחר קטגוריה קיימת או צור חדשה",
"nothingFound": "לא נמצא דבר",
- "createLabel": "יצירת שאילתה"
+ "createLabel": "יצירת שאילתה {{query}}"
},
"integrations": {
"apiKey": {
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "אפשרויות מתקדמות",
"form": {
- "httpStatusCodes": {
- "label": "סטטוס קוד HTTP",
- "placeholder": "בחר קודי מצב חוקיים",
- "clearButtonLabel": "נקה בחירה",
- "nothingFound": "לא נמצא דבר"
- },
"openServiceInNewTab": {
"label": "פתיחת שירות בכרטיסיה חדשה"
},
diff --git a/public/locales/he/layout/element-selector/selector.json b/public/locales/he/layout/element-selector/selector.json
new file mode 100644
index 000000000..8c50729af
--- /dev/null
+++ b/public/locales/he/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "הוספת אריח חדש",
+ "text": "אריחים הם המרכיב העיקרי של Homarr. הם משמשים להצגת אפליקציות ומידע נוסף. ניתן להוסיף אריחים ללא הגבלה."
+ },
+ "widgetDescription": "ווידג'טים מקיימים אינטראקציה עם האפליקציות, כדי לספק שליטה רבה יותר על היישומים. בדרך כלל דורשים תצורה נוספת לפני השימוש.",
+ "goBack": "חזרה לשלב הקודם",
+ "actionIcon": {
+ "tooltip": "הוספת אריח"
+ }
+}
diff --git a/public/locales/he/layout/header/actions/toggle-edit-mode.json b/public/locales/he/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..7486123e6
--- /dev/null
+++ b/public/locales/he/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "במצב עריכה, ניתן להתאים אריחים ולהגדיר אפליקציות. שינויים לא נשמרים עד יציאה ממצב עריכה.",
+ "button": {
+ "disabled": "כניסה למצב עריכה",
+ "enabled": "יציאה ושמירה"
+ },
+ "popover": {
+ "title": "מצב עריכה מופעל עבור גודל <1>{{size}}1>",
+ "text": "ניתן להתאים ולהגדיר את האפליקציות עכשיו. השינויים לא נשמרים עד יציאה ממצב עריכה."
+ },
+ "screenSizes": {
+ "small": "קטן",
+ "medium": "בינוני",
+ "large": "גדול"
+ }
+}
diff --git a/public/locales/he/layout/mobile/drawer.json b/public/locales/he/layout/mobile/drawer.json
new file mode 100644
index 000000000..ceb832da3
--- /dev/null
+++ b/public/locales/he/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "סרגל צד {{position}}"
+}
diff --git a/public/locales/he/layout/modals/about.json b/public/locales/he/layout/modals/about.json
new file mode 100644
index 000000000..6f3d57a9b
--- /dev/null
+++ b/public/locales/he/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr הוא לוח מחוונים אלגנטי , מודרני , אשר שם את כל האפליקציות והשירותים בהישג יד. עם Homarr, ניתן לגשת לכל דבר ולשלוט בו במיקום נוח אחד. Homarr משתלב בצורה חלקה עם האפליקציות, מספק מידע רב ערך ונותן שליטה מלאה. ההתקנה היא קלה ותומכת במגוון רחב של שיטות פריסה.",
+ "i18n": "מרחבי שמות טעונים של תרגום i18n",
+ "locales": "אזורי i18n שתצורתם נקבעה",
+ "contact": "נתקלת בבעיות או בשאלות? צור איתנו קשר!",
+ "addToDashboard": "הוספה ללוח מחוונים"
+}
diff --git a/public/locales/he/layout/modals/add-app.json b/public/locales/he/layout/modals/add-app.json
new file mode 100644
index 000000000..feae550bb
--- /dev/null
+++ b/public/locales/he/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "כללי",
+ "behaviour": "התנהגות",
+ "network": "רשת",
+ "appearance": "מראה",
+ "integration": "אינטגרציה"
+ },
+ "general": {
+ "appname": {
+ "label": "שם אפליקציה",
+ "description": "משמש להצגת האפליקציה בלוח המחוונים."
+ },
+ "internalAddress": {
+ "label": "כתובת פנימית",
+ "description": "כתובת פנימית של האפליקציה."
+ },
+ "externalAddress": {
+ "label": "כתובת חיצונית",
+ "description": "כתובת אינטרנט שתיפתח בעת לחיצה על האפליקציה."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "פתיחה בכרטיסיה חדשה",
+ "description": "פתח את האפליקציה בלשונית חדשה במקום הנוכחית."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "בודק מצב",
+ "description": "בודק אם האפליקציה שלך מקוונת באמצעות בקשת אינטרנט פשוטה."
+ },
+ "statusCodes": {
+ "label": "קוד מצב HTTP ",
+ "description": "קודי סטטוס שנחשבים מקוונים."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "סמל אפליקציה",
+ "description": "הסמל שיוצג בלוח המחוונים."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "תצורת אינטגרציה",
+ "description": "תצורת האינטגרציה שתשמש לחיבור לאפליקציה שלך.",
+ "placeholder": "בחר אינטגרציה",
+ "defined": "מוגדר",
+ "undefined": "לא מוגדר",
+ "public": "ציבורי",
+ "private": "פרטי",
+ "explanationPrivate": "קוד פרטי יישלח לשרת פעם אחת בלבד. לאחר שהדפדפן שלך ירענן את הדף, הוא לעולם לא יישלח שוב.",
+ "explanationPublic": "קוד ציבורי יישלח תמיד ללקוח והוא נגיש דרך ה-API. זה לא אמור להכיל ערכים סודיים כגון שמות משתמש, סיסמאות, אסימונים, אישורים וכדומה!"
+ },
+ "secrets": {
+ "description": "כדי לעדכן את הקוד, הזן ערך ולחץ על הלחצן שמור. כדי להסיר סוד השתמש בלחצן נקה.",
+ "warning": "האישורים שלך משמשים כגישה לאינטגרציות שלך ואתה לעולם לא תשתף אותם עם אף אחד אחר. צוות Homarr לעולם לא יבקש אישורים. הקפד לאחסן ולנהל את אישורים שלך בבטחה .",
+ "clear": "נקה אישורים",
+ "save": "שמור אישורים",
+ "update": "עדכן אישורים"
+ }
+ },
+ "validation": {
+ "popover": "הטופס שלך מכיל מידע שגוי ולכן לא ניתן לשמירה. תקן את השגיאות ותשמור בשנית."
+ }
+}
diff --git a/public/locales/he/layout/modals/change-position.json b/public/locales/he/layout/modals/change-position.json
new file mode 100644
index 000000000..df7a9dd93
--- /dev/null
+++ b/public/locales/he/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "מיקום ציר X",
+ "width": "רוחב",
+ "height": "גובה",
+ "yPosition": "מיקום ציר Y",
+ "zeroOrHigher": "אפס או גבוה יותר",
+ "betweenXandY": "בין {{min}} ל- {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/he/layout/screen-sizes.json b/public/locales/he/layout/screen-sizes.json
new file mode 100644
index 000000000..30bc77a02
--- /dev/null
+++ b/public/locales/he/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "קטן",
+ "medium": "בינוני",
+ "large": "גדול"
+ }
+}
diff --git a/public/locales/he/layout/tools.json b/public/locales/he/layout/tools.json
new file mode 100644
index 000000000..7629d8e6e
--- /dev/null
+++ b/public/locales/he/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "כרגע אין לך כלים"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "מחפש אחר סמלים",
+ "searchLimitationTitle": "חיפוש מוגבל ל- {{max}} סמלים",
+ "searchLimitationMessage": "כדי לשמור על פעולות מהירות, החיפוש מוגבל ל- {{max}} סמלים. השתמש בתיבת החיפוש למציאת סמלים נוספים"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/he/modules/calendar.json b/public/locales/he/modules/calendar.json
index f5bec3620..4bb55e5f1 100644
--- a/public/locales/he/modules/calendar.json
+++ b/public/locales/he/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "לוח שנה",
- "description": "מודול לוח שנה להצגת עדכונים. מקושר ל- Sonarr ו- Radarr API.",
+ "description": "מציג לוח שנה עם עדכונים מאינטגרציות נתמכות.",
"settings": {
+ "title": "הגדרות עבור ווידג'ט יומן",
"sundayStart": {
- "label": "להתחיל את השבוע ביום ראשון"
+ "label": "התחל את השבוע ביום ראשון"
+ },
+ "radarrReleaseType": {
+ "label": "סוג שחרור של Radarr"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/common.json b/public/locales/he/modules/common.json
index 0247e829e..8fd412e0b 100644
--- a/public/locales/he/modules/common.json
+++ b/public/locales/he/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "הגדרות"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "זוהה פרמטר לא בשימוש בתצורה {{key}}. לא ניתן להשתמש בפרמטר זה. כדי להימנע מכל התנהגות בלתי צפויה, גבה ותקן את התצורה."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/dashdot.json b/public/locales/he/modules/dashdot.json
index 407f6e782..cb9db2f42 100644
--- a/public/locales/he/modules/dashdot.json
+++ b/public/locales/he/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "מודול להצגת הגרפים משירות Dash.",
+ "description": "מציג נתוני גרפים.",
"settings": {
+ "title": "הגדרות עבור וידג׳ט Dash.",
"cpuMultiView": {
"label": "תצוגת מעבד מרובת ליבות"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/date.json b/public/locales/he/modules/date.json
index 0e804c8e5..5afc50fd3 100644
--- a/public/locales/he/modules/date.json
+++ b/public/locales/he/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "תאריך",
- "description": "הצגת השעה והתאריך הנוכחיים",
+ "name": "תאריך ושעה",
+ "description": "מציג את התאריך והשעה הנוכחיים.",
"settings": {
+ "title": "הגדרות עבור ווידג'ט תאריך ושעה",
"display24HourFormat": {
"label": "הצגת זמן בפורמט 24 שעות"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/dlspeed.json b/public/locales/he/modules/dlspeed.json
index d82ab968c..e5a190159 100644
--- a/public/locales/he/modules/dlspeed.json
+++ b/public/locales/he/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "מהירות הורדה",
- "description": "מציג את מהירות ההורדה הנוכחית של שירות נתמך"
+ "description": "מציג את מהירות ההורדה וההעלאה של אינטגרציות נתמכות."
},
"card": {
"table": {
@@ -19,17 +19,17 @@
},
"lineChart": {
"title": "מהירות הורדה נוכחית",
- "download": "הורדה",
- "upload": "העלאה",
- "timeSpan": "לפני שניות",
- "totalDownload": "הורדה ממתינה",
- "totalUpload": "העלאה ממתינה"
+ "download": "הורדה {{download}}",
+ "upload": "העלאה {{upload}}",
+ "timeSpan": "לפני {{seconds}} שניות",
+ "totalDownload": "הורדות: {{download}}",
+ "totalUpload": "העלאות: {{upload}}"
},
"errors": {
"noDownloadClients": {
"title": "לא נמצאו לקוחות הורדה נתמכים!",
- "text": "הוסף שירות הורדה כדי להציג את ההורדות הנוכחיות שלך"
+ "text": "הוסף שירות הורדה כדי להציג ססטוס ההורדה / העלאה נוכחית"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/docker.json b/public/locales/he/modules/docker.json
index 55d15bcbc..4067d78ca 100644
--- a/public/locales/he/modules/docker.json
+++ b/public/locales/he/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "דוקר",
- "description": "מאפשר ניהול בקלות את הקונטיינרים בדוקר"
+ "description": "מאפשר לראות ולנהל בקלות את כל מכולות הדוקר."
},
"search": {
"placeholder": "חיפוש לפי קונטיינר או שם קובץ תמונה"
@@ -14,7 +14,7 @@
"state": "מצב"
},
"body": {
- "portCollapse": "יציאות"
+ "portCollapse": "עוד {{ports}}"
},
"states": {
"running": "פועל",
@@ -25,14 +25,14 @@
},
"actionBar": {
"addService": {
- "title": "הוספת שירות",
- "message": "הוסף שירות ל- Homarr"
+ "title": "הוספת יישום",
+ "message": "הוספת יישום ל-Homarr"
},
"restart": {
"title": "אתחל"
},
"stop": {
- "title": "עצר"
+ "title": "עצור"
},
"start": {
"title": "התחל"
@@ -67,17 +67,17 @@
},
"errors": {
"integrationFailed": {
- "title": "שילוב docker נכשל",
- "message": "האם שכחת לטעון את ה- Docker?"
+ "title": "שילוב דוקר נכשל",
+ "message": "האם שכחת לטעון את הדוקר?"
},
"unknownError": {
"title": "אירעה שגיאה"
},
"oneServiceAtATime": {
- "title": "אנא הוסף שירות אחד בלבד בכל פעם!"
+ "title": "אנא הוסף רק אפליקציה או שירות אחד בכל פעם!"
}
},
"actionIcon": {
- "tooltip": "Docker"
+ "tooltip": "דוקר"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/overseerr.json b/public/locales/he/modules/overseerr.json
index 9b273f167..f025277e7 100644
--- a/public/locales/he/modules/overseerr.json
+++ b/public/locales/he/modules/overseerr.json
@@ -1,12 +1,12 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "מאפשר חיפוש והוספת מידה מ- Overseerr/Jellyseerr"
+ "description": "מאפשר לך לחפש ולהוסיף מדיה מ-Overseerr או Jellyseerr."
},
"popup": {
"item": {
"buttons": {
- "askFor": "בקש מ",
+ "askFor": "בקש {{title}}",
"cancel": "בטל",
"request": "בקשה"
},
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/ping.json b/public/locales/he/modules/ping.json
index 4fe08857a..55acb8638 100644
--- a/public/locales/he/modules/ping.json
+++ b/public/locales/he/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "פינג",
- "description": "מאפשר בדיקה אם השירות פעיל או מחזיר קוד ספציפי של HTTP"
+ "description": "מציג מחוון סטטוס בהתאם לקוד תגובת בקשת רשת של כתובת אינטרנט נתונה."
},
"states": {
- "online": "מקוון",
- "offline": "לא מקוון",
+ "online": "מקוון {{response}}",
+ "offline": "לא מקוון {{response}}",
"loading": "טוען..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/search.json b/public/locales/he/modules/search.json
index b6aaf78a1..940239c6c 100644
--- a/public/locales/he/modules/search.json
+++ b/public/locales/he/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "סרגל חיפוש",
- "description": "סרגל לחיפוש ברשת, יוטיוב, טורנטים או Overseerr"
+ "description": "שורת חיפוש המאפשרת לחפש במנוע החיפוש, ביוטיוב ובאינטגרציות נתמכות."
},
"input": {
"placeholder": "חפש באינטרנט..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "אינטרנט",
- "description": "חיפוש באמצעות מנוע החיפוש שלך (מוגדר בהגדרות)"
+ "description": "חיפוש..."
},
"youtube": {
"name": "יוטיוב",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "חיפוש סרטים ותוכניות טלוויזיה באמצעות Overseerr (המודול חייב להיות מאופשר)"
+ "description": "חפש סרטים ותוכניות טלוויזיה ב-Overseerr"
}
},
"tip": "באפשרותך לבחור את סרגל החיפוש באמצעות קיצור הדרך",
"switchedSearchEngine": "עבור לחיפוש עם"
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/torrents-status.json b/public/locales/he/modules/torrents-status.json
index 27e9d0dd7..093a96df4 100644
--- a/public/locales/he/modules/torrents-status.json
+++ b/public/locales/he/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "טורנט",
- "description": "מציג את מהירות ההורדה הנוכחית של שירות נתמך",
+ "description": "מציג רשימה של טורנטים מלקוחות טורנט נתמכים.",
"settings": {
- "hideComplete": {
- "label": "הסתר טורנטים שהושלמו"
+ "title": "הגדרות עבור וידג׳ט טורנט",
+ "refreshInterval": {
+ "label": "מרווח הזמן לרענון (בשניות)"
+ },
+ "displayCompletedTorrents": {
+ "label": "הצג טורנטים שהושלמו"
+ },
+ "displayStaleTorrents": {
+ "label": "הצג טורנטים שהושלמו"
}
}
},
@@ -24,17 +31,24 @@
},
"lineChart": {
"title": "מהירות הורדה נוכחית",
- "download": "הורדה",
- "upload": "העלאה",
- "timeSpan": "לפני שניות",
- "totalDownload": "הורדה ממתינה",
- "totalUpload": "העלאה ממתינה"
+ "download": "הורדה {{download}}",
+ "upload": "העלאה {{upload}}",
+ "timeSpan": "לפני {{seconds}} שניות",
+ "totalDownload": "הורדות: {{download}}",
+ "totalUpload": "העלאות: {{upload}}"
},
"errors": {
"noDownloadClients": {
- "title": "לא נמצאו לקוחות הורדה נתמכים!",
- "text": "הוסף שירות הורדה כדי להציג את ההורדות הנוכחיות שלך"
+ "title": "לא נמצאו לקוחות טורנט נתמכים!",
+ "text": "הוסף לקוח טורנט נתמך כדי להציג את ההורדות הנוכחיות שלך"
+ },
+ "generic": {
+ "title": "שגיאה לא צפויה התרחשה",
+ "text": "לא ניתן לתקשר עם לקוחות הטורנט שלך. אנא בדוק את התצורה שלך"
}
+ },
+ "loading": {
+ "title": "טוען..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/modules/usenet.json b/public/locales/he/modules/usenet.json
index 86add712d..a76171842 100644
--- a/public/locales/he/modules/usenet.json
+++ b/public/locales/he/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "קבוצת דיון",
- "description": "מאפשר צפייה בהיסטוריה, תור ההורדות, עצירה וחידוש הורדות בקבוצות הדיון (Sabnzbd או NZBGet)"
+ "description": "מאפשר לך להציג ולנהל את מופע Usenet שלך."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "לא נמצאו לקוחות הורדה נתמכים!",
- "text": "הוסף שירות הורדה כדי להציג את ההורדות הנוכחיות שלך"
+ "text": "הוסף לקוח הורדה נתמך של Usenet כדי להציג את ההורדות הנוכחיות שלך"
}
}
},
diff --git a/public/locales/he/modules/weather.json b/public/locales/he/modules/weather.json
index 81e34a55f..82217d6d0 100644
--- a/public/locales/he/modules/weather.json
+++ b/public/locales/he/modules/weather.json
@@ -1,10 +1,11 @@
{
"descriptor": {
"name": "מזג אוויר",
- "description": "חפש את מזג האוויר הנוכחי במיקום שלך",
+ "description": "מציג את מידע מזג האוויר הנוכחי של מיקום מוגדר.",
"settings": {
+ "title": "הגדרות עבור ווידג'ט מזג אוויר",
"displayInFahrenheit": {
- "label": "להציג בפרנהייט"
+ "label": "פרנהייט"
},
"location": {
"label": "מיקום מזג האוויר"
@@ -29,4 +30,4 @@
"unknown": "לא ידוע"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/settings/common.json b/public/locales/he/settings/common.json
index b3b4b0c68..63be54c7f 100644
--- a/public/locales/he/settings/common.json
+++ b/public/locales/he/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "התאמה אישית"
},
"tips": {
- "configTip": "העלה את קובץ ההגדרות שלך על-ידי גרירה ושחרור שלו לדף!"
+ "configTip": "העלה את קובץ התצורה שלך על ידי גרירה ושחרור שלו אל הדף!"
},
"credits": {
"madeWithLove": "נעשה ב- ❤️ ע״י @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "הגדלת אריחים (תופס את כל השטח)",
+ "layout": {
+ "title": "פריסת לוח מחוונים",
+ "main": "ראשי",
+ "sidebar": "סרגל צד",
+ "cannotturnoff": "לא ניתן להסרה",
+ "dashboardlayout": "פריסת לוח מחוונים",
+ "enablersidebar": "הפעלת סרגל הצד הימני",
+ "enablelsidebar": "הפעלת סרגל הצד השמאלי",
+ "enablesearchbar": "הפעלת סרגל חיפוש",
+ "enabledocker": "הפעלת אינטגרצית דוקר",
+ "enableping": "הפעלת פינג",
+ "enablelsidebardesc": "אופציונלי. ניתן להשתמש עבור יישומים ואינטגרציות בלבד",
+ "enablersidebardesc": "אופציונלי. ניתן להשתמש עבור יישומים ואינטגרציות בלבד"
+ }
+}
diff --git a/public/locales/he/settings/customization/color-selector.json b/public/locales/he/settings/customization/color-selector.json
index 5e856a2c8..c6dfd5967 100644
--- a/public/locales/he/settings/customization/color-selector.json
+++ b/public/locales/he/settings/customization/color-selector.json
@@ -1,3 +1,3 @@
{
- "suffix": "צבע"
+ "suffix": "צבע {{color}}"
}
\ No newline at end of file
diff --git a/public/locales/he/settings/customization/page-appearance.json b/public/locales/he/settings/customization/page-appearance.json
index ba69da064..3e08c2634 100644
--- a/public/locales/he/settings/customization/page-appearance.json
+++ b/public/locales/he/settings/customization/page-appearance.json
@@ -1,22 +1,24 @@
{
"pageTitle": {
- "label": "כותרת העמוד",
- "placeholder": "Homarr 🦞"
+ "label": "כותרת העמוד"
+ },
+ "metaTitle": {
+ "label": "כותרת דף"
},
"logo": {
"label": "סמל"
},
"favicon": {
- "label": "אייקון לצד שם העמוד"
+ "label": "אייקון לצד שם הדף"
},
"background": {
"label": "רקע"
},
"customCSS": {
"label": "CSS מותאם אישית",
- "placeholder": "css מותאם אישית יבוצע אחרון"
+ "placeholder": "CSS מותאם אישית יוחל אחרון"
},
"buttons": {
- "submit": "שמירה"
+ "submit": "שלח"
}
}
diff --git a/public/locales/he/settings/general/color-schema.json b/public/locales/he/settings/general/color-schema.json
index 0c6c5d0d1..f24f983a8 100644
--- a/public/locales/he/settings/general/color-schema.json
+++ b/public/locales/he/settings/general/color-schema.json
@@ -1,3 +1,3 @@
{
- "label": "מעבר למצב סכמה"
+ "label": "מעבר למראה {{scheme}}"
}
\ No newline at end of file
diff --git a/public/locales/he/settings/general/config-changer.json b/public/locales/he/settings/general/config-changer.json
index eb3e91f88..e9df3fbb8 100644
--- a/public/locales/he/settings/general/config-changer.json
+++ b/public/locales/he/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "טעינת קובץ הגדרות"
+ "label": "מחליף הגדרות",
+ "description": "{{configCount}} תצורות זמינות",
+ "loadingNew": "טוען הגדרות...",
+ "pleaseWait": "אנא המתן עד לטעינת התצורה החדשה שלך!"
},
"modal": {
- "title": "בחירת שם קובץ הגדרות חדש",
- "form": {
- "configName": {
- "label": "שם קובץ הגדרות",
- "placeholder": "שם קובץ הגדרות חדש"
+ "copy": {
+ "title": "בחר/י את שם התצורה החדשה שלך",
+ "form": {
+ "configName": {
+ "label": "שם תצורה",
+ "validation": {
+ "required": "נדרש שם תצורה",
+ "notUnique": "שם התצורה כבר בשימוש"
+ },
+ "placeholder": "שם התצורה החדש שלך"
+ },
+ "submitButton": "לאשר"
},
- "submitButton": "אישור"
+ "events": {
+ "configSaved": {
+ "title": "התצורה נשמרה",
+ "message": "התצורה נשמרה בשם {{configName}}"
+ },
+ "configCopied": {
+ "title": "התצורה הועתקה",
+ "message": "התצורה הועתקה בשם {{configName}}"
+ },
+ "configNotCopied": {
+ "title": "לא ניתן להעתיק את התצורה",
+ "message": "התצורה שלך לא הועתקה בשם {{configName}}"
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "קובץ הגדרות נשמר",
- "message": "קובץ הגדרות נשמר בשם {{configName}}"
+ "confirmDeletion": {
+ "title": "אשר את מחיקת התצורה שלך",
+ "warningText": "אתה עומד למחוק את ' {{configName}} '",
+ "text": "שים לב שהמחיקה לא ניתנת לשינוי והנתונים שלך יאבדו לצמיתות. לאחר לחיצה על כפתור זה, הקובץ יימחק לצמיתות מהדיסק שלך. הקפד ליצור גיבוי מתאים של התצורה שלך.",
+ "buttons": {
+ "confirm": "כן, למחוק ' {{configName}} '"
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "נכשל במחיקת קובץ הגדרות",
"message": "נכשל במחיקת קובץ הגדרות"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "לא ניתן למחוק את תצורת ברירת המחדל",
+ "message": "התצורה לא נמחקה ממערכת הקבצים"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "גרור קובץ לכאן כדי להעלות את קובץ ההגדרות, תמיכה ב- JSON בלבד"
+ "title": "קובץ הגדרות עלה",
+ "text": "גרור לכאן קבצים כדי להעלות תצורה. תמיכה בקבצי JSON בלבד."
},
"reject": {
- "text": "תבנית קובץ זו אינה נתמכת, ניתן להעלות קובץ JSON בלבד"
+ "title": "גרור ושחרר העלאה שנדחתה",
+ "text": "פורמט קובץ זה אינו נתמך. נא להעלות רק קובצי JSON."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/settings/general/module-enabler.json b/public/locales/he/settings/general/module-enabler.json
index cbfa34dbd..9e26dfeeb 100644
--- a/public/locales/he/settings/general/module-enabler.json
+++ b/public/locales/he/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "מודול מופעל"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/he/settings/general/search-engine.json b/public/locales/he/settings/general/search-engine.json
index b21e58b98..0f7325a17 100644
--- a/public/locales/he/settings/general/search-engine.json
+++ b/public/locales/he/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "מנוע חיפוש",
+ "configurationName": "הגדרות מנוע חיפוש",
"tips": {
- "generalTip": "השתמש בקידומות yt ו- t לפני השאילתה שלך כדי לחפש ב- Youtube או עבור סיקור",
+ "generalTip": "ישנן מספר קידומות שבהן ניתן להשתמש! הוספה לפני השאילתה שלך תסנן את התוצאות. !s (אינטרנט), !t (טורנטים), !y! ,(YouTube) m (מדיה).",
"placeholderTip": "s% יכול לשמש כמציין מיקום עבור השאילתה."
},
"customEngine": {
+ "title": "מנוע חיפוש מותאם אישית",
"label": "כתובת URL של שאילתה",
"placeholder": "כתובת URL של שאילתה מותאמת אישית"
},
"searchNewTab": {
"label": "פתיחת תוצאות חיפוש בכרטיסיה חדשה"
+ },
+ "searchEnabled": {
+ "label": "חיפוש מאופשר"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/he/settings/general/theme-selector.json b/public/locales/he/settings/general/theme-selector.json
index f22eabf08..2806dbfae 100644
--- a/public/locales/he/settings/general/theme-selector.json
+++ b/public/locales/he/settings/general/theme-selector.json
@@ -1,3 +1,3 @@
{
- "label": "החלפת ערכת נושא"
+ "label": "מעבר למראה {{theme}}"
}
\ No newline at end of file
diff --git a/public/locales/he/settings/general/widget-positions.json b/public/locales/he/settings/general/widget-positions.json
index 77e470db1..0a06fcb50 100644
--- a/public/locales/he/settings/general/widget-positions.json
+++ b/public/locales/he/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
- "label": "מקם ווידג'טים משמאל"
-}
\ No newline at end of file
+ "label": "מקם ווידג'טים בצד שמאל"
+}
diff --git a/public/locales/it/authentication/login.json b/public/locales/it/authentication/login.json
index 847451237..c4f632b70 100644
--- a/public/locales/it/authentication/login.json
+++ b/public/locales/it/authentication/login.json
@@ -18,10 +18,10 @@
"message": "La tua password è in fase di controllo..."
},
"correct": {
- "title": "Password corretta, reindirizzamento..."
+ "title": "Accesso effettuato, reindirizzamento..."
},
"wrong": {
- "title": "Password errata. Per favore riprova."
+ "title": "La password inserita non è corretta. Si prega di riprovare."
}
}
}
diff --git a/public/locales/it/common.json b/public/locales/it/common.json
index 5ec01c8be..2dac6ccd1 100644
--- a/public/locales/it/common.json
+++ b/public/locales/it/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Salva"
+ "save": "Salva",
+ "about": "Info",
+ "cancel": "Annulla",
+ "close": "Chiudi",
+ "delete": "Elimina",
+ "ok": "OK",
+ "edit": "Modifica",
+ "version": "Versione",
+ "changePosition": "Cambia posizione",
+ "remove": "Rimuovi",
+ "removeConfirm": "Siete sicuri di voler rimuovere {{item}}?",
+ "sections": {
+ "settings": "Impostazioni",
+ "dangerZone": "Danger zone"
+ },
+ "secrets": {
+ "apiKey": "Chiave API",
+ "username": "Nome utente",
+ "password": "Password"
},
"tip": "Suggerimento: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minuti",
"hours": "ore"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/it/layout/add-service-app-shelf.json b/public/locales/it/layout/add-service-app-shelf.json
index cc3831387..f93d2fee6 100644
--- a/public/locales/it/layout/add-service-app-shelf.json
+++ b/public/locales/it/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Impostazioni avanzate",
"form": {
- "httpStatusCodes": {
- "label": "Status Codes HTTP",
- "placeholder": "Selezionare status codes validi",
- "clearButtonLabel": "Elimina selezione",
- "nothingFound": "Nessun risultato"
- },
"openServiceInNewTab": {
"label": "Apri servizio in una nuova scheda"
},
diff --git a/public/locales/it/layout/element-selector/selector.json b/public/locales/it/layout/element-selector/selector.json
new file mode 100644
index 000000000..03a055510
--- /dev/null
+++ b/public/locales/it/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Aggiungi nuovo riquadro",
+ "text": "I tile sono l'elemento principale di Homarr. Sono utilizzati per visualizzare le tue app e altre informazioni. Puoi aggiungere tutti i tile che vuoi."
+ },
+ "widgetDescription": "I widget interagiscono con le applicazioni per fornire un maggior controllo. Normalmente richiedono configurazioni aggiuntive prima di essere utilizzati.",
+ "goBack": "Torna indietro allo step precedente",
+ "actionIcon": {
+ "tooltip": "Aggiungi riquadro"
+ }
+}
diff --git a/public/locales/it/layout/header/actions/toggle-edit-mode.json b/public/locales/it/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..070a49a6a
--- /dev/null
+++ b/public/locales/it/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "In Edit Mode, è possibile regolare i tile e configurare le applicazioni. Le modifiche non vengono salvate fino all'uscita dalla Edit Mode.",
+ "button": {
+ "disabled": "Attiva Edit Mode",
+ "enabled": "Salva ed esci"
+ },
+ "popover": {
+ "title": "Edit mode abilitata per dimensione <1>{{size}}1>",
+ "text": "Ora è possibile regolare e configurare le applicazioni. Le modifiche non verranno salvate finché non si esce dalla edit mode"
+ },
+ "screenSizes": {
+ "small": "piccolo",
+ "medium": "medio",
+ "large": "grande"
+ }
+}
diff --git a/public/locales/it/layout/mobile/drawer.json b/public/locales/it/layout/mobile/drawer.json
new file mode 100644
index 000000000..30c2fede5
--- /dev/null
+++ b/public/locales/it/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} barra laterale"
+}
diff --git a/public/locales/it/layout/modals/about.json b/public/locales/it/layout/modals/about.json
new file mode 100644
index 000000000..b5e5c5c6a
--- /dev/null
+++ b/public/locales/it/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr è una elegante e moderna dashboard che mette tutte le vostre app e i vostri servizi a portata di mano. Con Homarr, potete controllare tutto in un'unica comoda posizione. Homarr si integra perfettamente con le app aggiunte, fornendo informazioni preziose e offrendo un controllo completo. L'installazione è semplice e Homarr supporta un'ampia gamma di metodi di deployment.",
+ "i18n": "Translation namespaces I18n caricati",
+ "locales": "I18n locales configurati",
+ "contact": "Problemi o domande? Contattaci!",
+ "addToDashboard": "Aggiungi alla dashboard"
+}
diff --git a/public/locales/it/layout/modals/add-app.json b/public/locales/it/layout/modals/add-app.json
new file mode 100644
index 000000000..924813a35
--- /dev/null
+++ b/public/locales/it/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Generale",
+ "behaviour": "Comportamento",
+ "network": "Rete",
+ "appearance": "Aspetto",
+ "integration": "Integrazione"
+ },
+ "general": {
+ "appname": {
+ "label": "Nome app",
+ "description": "Utilizzato per visualizzare l'app sulla dashboard."
+ },
+ "internalAddress": {
+ "label": "Indirizzo interno",
+ "description": "IP interno dell'app."
+ },
+ "externalAddress": {
+ "label": "Indirizzo esterno",
+ "description": "URL che verrà aperto nel browser al clic dell'app."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Apri in una nuova scheda",
+ "description": "Apri l'app in una nuova scheda invece di quella attuale."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Status checker",
+ "description": "Verifica se l'applicazione è online utilizzando una semplice richiesta HTTP(S)."
+ },
+ "statusCodes": {
+ "label": "Codici di stato HTTP",
+ "description": "Gli status code HTTP considerati online."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "Icona App",
+ "description": "L'icona che verrà visualizzata sulla dashboard."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Configurazione integrazioni",
+ "description": "La configurazione dell'integrazione che verrà utilizzata per connettersi all'applicazione.",
+ "placeholder": "Seleziona un'integrazione",
+ "defined": "Definito",
+ "undefined": "Indefinito",
+ "public": "Pubblico",
+ "private": "Privato",
+ "explanationPrivate": "Un secret privato verrà inviato al server una sola volta. Una volta che il browser avrà aggiornato la pagina, non verrà mai inviato di nuovo.",
+ "explanationPublic": "Un secret pubblico sarà sempre inviato al client ed è accessibile tramite API. Non dovrebbe contenere dati sensibili come nomi utente, password, token, certificati e simili!"
+ },
+ "secrets": {
+ "description": "Per aggiornare un secret, inserisci un valore e fai clic sul pulsante Salva. Per rimuoverlo, usa il pulsante rimuovi.",
+ "warning": "Le tue credenziali fungono da accesso per le tue integrazioni e non devi mai condividerle con nessuno. Il team ufficiale di Homarr non chiederà mai le credenziali. Assicurati di memorizzare e gestire i tuoi dati privati in modo sicuro .",
+ "clear": "Rimuovi secret",
+ "save": "Salva secret",
+ "update": "Aggiorna secret"
+ }
+ },
+ "validation": {
+ "popover": "Il form contiene dati invalidi. Pertanto, non può essere salvato. Risolvere tutti i problemi e fare nuovamente clic su questo pulsante per salvare le modifiche"
+ }
+}
diff --git a/public/locales/it/layout/modals/change-position.json b/public/locales/it/layout/modals/change-position.json
new file mode 100644
index 000000000..12df9bd38
--- /dev/null
+++ b/public/locales/it/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "Posizione asse X",
+ "width": "Larghezza",
+ "height": "Altezza",
+ "yPosition": "Posizione asse Y",
+ "zeroOrHigher": "0 o maggiore",
+ "betweenXandY": "Fra {{min}} e {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/it/layout/screen-sizes.json b/public/locales/it/layout/screen-sizes.json
new file mode 100644
index 000000000..84227d21a
--- /dev/null
+++ b/public/locales/it/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "piccolo",
+ "medium": "medio",
+ "large": "grande"
+ }
+}
diff --git a/public/locales/it/layout/tools.json b/public/locales/it/layout/tools.json
new file mode 100644
index 000000000..70f9565d9
--- /dev/null
+++ b/public/locales/it/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Al momento non si dispone di alcuno strumento"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Cerca un'icona...",
+ "searchLimitationTitle": "La ricerca è limitata a {{max}} icone",
+ "searchLimitationMessage": "Per garantire le performance, la ricerca è limitata a {{max}} icone. Usa la barra di ricerca per trovare più icone"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/it/modules/calendar.json b/public/locales/it/modules/calendar.json
index baa9106ce..2ce0f0f48 100644
--- a/public/locales/it/modules/calendar.json
+++ b/public/locales/it/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendario",
- "description": "Un calendario per la visualizzazione delle prossime uscite. Può comunicare con le API di Sonarr e Radarr.",
+ "description": "Mostra un calendario con le prossime versioni dalle integrazioni supportate.",
"settings": {
+ "title": "Impostazioni per il widget Calendario",
"sundayStart": {
"label": "Inizia la settimana di domenica"
+ },
+ "radarrReleaseType": {
+ "label": "Tipo di release Radarr"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/common.json b/public/locales/it/modules/common.json
index 162178e59..c5f229a8f 100644
--- a/public/locales/it/modules/common.json
+++ b/public/locales/it/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Impostazioni"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Parametro non utilizzato nella configurazione rilevata {{key}}. Homarr non è in grado d'interpretare e utilizzare questo parametro. Onde evitare comportamenti imprevisti, eseguire un backup della configurazione e correggerla."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/dashdot.json b/public/locales/it/modules/dashdot.json
index 620dcae18..0c235b998 100644
--- a/public/locales/it/modules/dashdot.json
+++ b/public/locales/it/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Un modulo per visualizzare i grafici dell'istanza di Dash. in esecuzione.",
+ "description": "Visualizza i grafici di un'istanza Dash. esterna all'interno di Homarr.",
"settings": {
+ "title": "Impostazioni del widget Dash.",
"cpuMultiView": {
"label": "Vista CPU Multi-Core"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/date.json b/public/locales/it/modules/date.json
index e68b8f2ec..e0f6ff5a0 100644
--- a/public/locales/it/modules/date.json
+++ b/public/locales/it/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Data",
- "description": "Mostra l'ora e la data corrente in una scheda",
+ "name": "Data e Ora",
+ "description": "Visualizza la data e l'ora correnti.",
"settings": {
+ "title": "Impostazioni per il widget Data e Ora",
"display24HourFormat": {
"label": "Visualizza formato 24 ore"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/dlspeed.json b/public/locales/it/modules/dlspeed.json
index 1e2b24047..89319eea0 100644
--- a/public/locales/it/modules/dlspeed.json
+++ b/public/locales/it/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Velocità Di Download",
- "description": "Mostra la velocità di download corrente dei servizi supportati"
+ "description": "Visualizza la velocità di download e upload delle integrazioni supportate."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/docker.json b/public/locales/it/modules/docker.json
index 6411a2537..b5b314a97 100644
--- a/public/locales/it/modules/docker.json
+++ b/public/locales/it/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Gestisci facilmente i tuoi container di docker"
+ "description": "Permette di vedere e gestire facilmente tutti i vostri Container di Docker."
},
"search": {
"placeholder": "Ricerca per container nome immagine"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Aggiungi servizio",
- "message": "Aggiungi servizio a Homarr"
+ "title": "Aggiungi App",
+ "message": "Aggiungi app ad Homarr"
},
"restart": {
"title": "Riavvia"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Integrazione Docker fallita",
- "message": "Hai dimenticato di montare il socket di docker?"
+ "message": "Vi siete dimenticati di montare il socket di Docker?"
},
"unknownError": {
"title": "Si è verificato un errore"
},
"oneServiceAtATime": {
- "title": "Si prega di aggiungere solo un servizio alla volta!"
+ "title": "Aggiungi solo un'app o un servizio alla volta!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/overseerr.json b/public/locales/it/modules/overseerr.json
index f360e9788..99a674cc0 100644
--- a/public/locales/it/modules/overseerr.json
+++ b/public/locales/it/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Permette di cercare e aggiungere media da Overseerr/Jellyseerr"
+ "description": "Permette di cercare e aggiungere media da Overseerr o Jellyseerr."
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Seleziona le stagioni che desideri scaricare",
+ "caption": "Spuntare le stagioni che si desidera scaricare",
"table": {
"header": {
"season": "Stagione",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/ping.json b/public/locales/it/modules/ping.json
index 9235a48e9..623156fe1 100644
--- a/public/locales/it/modules/ping.json
+++ b/public/locales/it/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Consente di controllare se il servizio è attivo o restituisce uno specifico status code HTTP."
+ "description": "Visualizza un indicatore di stato in base al codice di risposta HTTP di un determinato URL."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Caricamento in corso..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/search.json b/public/locales/it/modules/search.json
index 06f1404db..f12a59ca8 100644
--- a/public/locales/it/modules/search.json
+++ b/public/locales/it/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Barra di ricerca",
- "description": "Barra di ricerca per cercare sul web, Youtube, Torrent o Overseerr"
+ "description": "Una barra di ricerca che consente di cercare nel motore di ricerca personalizzato, YouTube e integrazioni supportate."
},
"input": {
"placeholder": "Cerca sul web..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "Web",
- "description": "Cerca usando il tuo motore di ricerca (definito nelle impostazioni)"
+ "description": "Cerca..."
},
"youtube": {
"name": "Youtube",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Cerca film e serie TV con Overseerr (il modulo deve essere abilitato)"
+ "description": "Cerca film e serie TV su Overseerr"
}
},
"tip": "Puoi selezionare la barra di ricerca con la scorciatoia ",
"switchedSearchEngine": "Ricerca cambiata con {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/torrents-status.json b/public/locales/it/modules/torrents-status.json
index cad61981b..eed33ce83 100644
--- a/public/locales/it/modules/torrents-status.json
+++ b/public/locales/it/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Mostra la velocità di download corrente dei servizi supportati",
+ "description": "Visualizza un elenco di torrent dai client Torrent supportati.",
"settings": {
- "hideComplete": {
- "label": "Nascondi torrent completati"
+ "title": "Impostazioni per il widget Torrent",
+ "refreshInterval": {
+ "label": "Intervallo di aggiornamento (in secondi)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Mostra torrent completati"
+ },
+ "displayStaleTorrents": {
+ "label": "Mostra torrent in stallo"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Nessun client di download supportato trovato!",
- "text": "Aggiungi un servizio di download per visualizzare i tuoi download attuali"
+ "title": "Nessun client Torrent supportato trovato!",
+ "text": "Aggiungi un client Torrent supportato per visualizzare i download attuali"
+ },
+ "generic": {
+ "title": "Si è verificato un errore inaspettato",
+ "text": "Homarr non è riuscito a comunicare con i client Torrent. Controlla la tua configurazione"
}
+ },
+ "loading": {
+ "title": "Caricamento in corso..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/modules/usenet.json b/public/locales/it/modules/usenet.json
index 6fb5bb105..c7bdc8cd7 100644
--- a/public/locales/it/modules/usenet.json
+++ b/public/locales/it/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Permette di vedere la coda e la cronologia di usenet (Sabnzbd o NZBGet), di mettere in pausa e riprendere i download"
+ "description": "Consente di visualizzare e gestire la propria istanza Usenet."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Nessun client di download supportato trovato!",
- "text": "Aggiungi un servizio di download per visualizzare i tuoi download attuali"
+ "text": "Aggiungi un client di download Usenet supportato per visualizzare i download attuali"
}
}
},
diff --git a/public/locales/it/modules/weather.json b/public/locales/it/modules/weather.json
index a2fe6610c..0f882abef 100644
--- a/public/locales/it/modules/weather.json
+++ b/public/locales/it/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Meteo",
- "description": "Consulta il meteo attuale della propria località",
+ "description": "Mostra le informazioni meteo attuali di una località.",
"settings": {
+ "title": "Impostazioni per l'integrazione meteo",
"displayInFahrenheit": {
"label": "Mostra in Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Sconosciuto"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/settings/common.json b/public/locales/it/settings/common.json
index 31e06d5ec..552939da4 100644
--- a/public/locales/it/settings/common.json
+++ b/public/locales/it/settings/common.json
@@ -11,5 +11,19 @@
"credits": {
"madeWithLove": "Realizzato con ❤️ da @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Espandi la griglia (occupa tutto lo spazio)",
+ "layout": {
+ "title": "Layout della Dashboard",
+ "main": "Main",
+ "sidebar": "Barra laterale",
+ "cannotturnoff": "Impossibile disattivare",
+ "dashboardlayout": "Layout della Dashboard",
+ "enablersidebar": "Abilita la barra laterale destra",
+ "enablelsidebar": "Abilita la barra laterale sinistra",
+ "enablesearchbar": "Abilita barra di ricerca",
+ "enabledocker": "Abilita integrazione di docker",
+ "enableping": "Abilita i ping",
+ "enablelsidebardesc": "Facoltativo. Può essere utilizzato solo per applicazioni e integrazioni",
+ "enablersidebardesc": "Facoltativo. Può essere utilizzato solo per applicazioni e integrazioni"
+ }
+}
diff --git a/public/locales/it/settings/customization/page-appearance.json b/public/locales/it/settings/customization/page-appearance.json
index 9f724bc2b..4df78cf7d 100644
--- a/public/locales/it/settings/customization/page-appearance.json
+++ b/public/locales/it/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Titolo pagina",
- "placeholder": "Homarr 🦞"
+ "label": "Titolo pagina"
+ },
+ "metaTitle": {
+ "label": "Titolo meta"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "CSS personalizzato",
- "placeholder": "I CSS personalizzati saranno eseguiti per ultimi"
+ "placeholder": "I CSS personalizzati saranno applicati per ultimi"
},
"buttons": {
"submit": "Invia"
diff --git a/public/locales/it/settings/general/config-changer.json b/public/locales/it/settings/general/config-changer.json
index b9dec1e85..e7b4a2b30 100644
--- a/public/locales/it/settings/general/config-changer.json
+++ b/public/locales/it/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Carica configurazione"
+ "label": "Config changer",
+ "description": "",
+ "loadingNew": "Caricamento della configurazione...",
+ "pleaseWait": "Attendere fino al caricamento della nuova configurazione!"
},
"modal": {
- "title": "Scegli il nome della nuova configurazione",
- "form": {
- "configName": {
- "label": "Nome configurazione",
- "placeholder": "Nuovo nome configurazione"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Conferma"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Configurazione salvata",
- "message": "Configurazione salvata come {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Eliminazione configurazione fallita",
"message": "Eliminazione configurazione fallita"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Il config predefinito non può essere eliminato",
+ "message": "La configurazione non è stata eliminata dal file system"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Trascina qui i file per caricare una configurazione. Solo file JSON supportati."
+ "title": "Caricamento configurazione",
+ "text": "Trascinare i file qui per caricare una configurazione. Supporto solo per i file JSON."
},
"reject": {
+ "title": "Caricamento Drag and Drop rifiutato",
"text": "Formato file non supportato. Caricare solo file JSON."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/settings/general/module-enabler.json b/public/locales/it/settings/general/module-enabler.json
index 07c1d273c..9e26dfeeb 100644
--- a/public/locales/it/settings/general/module-enabler.json
+++ b/public/locales/it/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Abilitatore moduli"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/it/settings/general/search-engine.json b/public/locales/it/settings/general/search-engine.json
index a10bfaf54..956a59a82 100644
--- a/public/locales/it/settings/general/search-engine.json
+++ b/public/locales/it/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Motore di ricerca",
+ "configurationName": "Configurazione del motore di ricerca",
"tips": {
- "generalTip": "Usa i prefissi !yt e !t di fronte alla tua ricerca per cercare rispettivamente su YouTube o un Torrent.",
+ "generalTip": "È possibile utilizzare diversi prefissi! L'aggiunta di questi prefissi davanti alla query filtrerà i risultati. !s (Web), !t (Torrent), !y (YouTube) e !m (Media).",
"placeholderTip": "%s può essere usato come segnaposto per la ricerca."
},
"customEngine": {
+ "title": "Motore di ricerca personalizzato",
"label": "URL di ricerca",
"placeholder": "URL di ricerca personalizzato"
},
"searchNewTab": {
"label": "Apri i risultati della ricerca in una nuova scheda"
+ },
+ "searchEnabled": {
+ "label": "Ricerca abilitata"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/it/settings/general/widget-positions.json b/public/locales/it/settings/general/widget-positions.json
index 8d141a558..9cfb7dcf4 100644
--- a/public/locales/it/settings/general/widget-positions.json
+++ b/public/locales/it/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Posiziona widget a sinistra"
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/authentication/login.json b/public/locales/ja/authentication/login.json
index 2630421a1..546f9bcf4 100644
--- a/public/locales/ja/authentication/login.json
+++ b/public/locales/ja/authentication/login.json
@@ -1,21 +1,21 @@
{
- "title": "",
+ "title": "お帰りなさい",
"text": "",
"form": {
"fields": {
"password": {
"label": "パスワード",
- "placeholder": ""
+ "placeholder": "パスワード"
}
},
"buttons": {
- "submit": ""
+ "submit": "サインイン"
}
},
"notifications": {
"checking": {
- "title": "",
- "message": ""
+ "title": "パスワードの確認",
+ "message": "パスワードは確認中です..."
},
"correct": {
"title": ""
diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json
index 41628d546..6f1b00766 100644
--- a/public/locales/ja/common.json
+++ b/public/locales/ja/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "保存"
+ "save": "保存",
+ "about": "について",
+ "cancel": "キャンセル",
+ "close": "閉じる",
+ "delete": "削除",
+ "ok": "よっしゃー",
+ "edit": "編集",
+ "version": "バージョン",
+ "changePosition": "ポジションを変更する",
+ "remove": "削除",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "設定",
+ "dangerZone": "デンジャーゾーン"
+ },
+ "secrets": {
+ "apiKey": "Apiキー",
+ "username": "ユーザー名",
+ "password": "パスワード"
},
"tip": "ヒント ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "議事録",
"hours": "時間"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/ja/layout/add-service-app-shelf.json b/public/locales/ja/layout/add-service-app-shelf.json
index 9d48b1ce9..574240262 100644
--- a/public/locales/ja/layout/add-service-app-shelf.json
+++ b/public/locales/ja/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "高度なオプション",
"form": {
- "httpStatusCodes": {
- "label": "HTTPステータスコード",
- "placeholder": "有効なステータスコードを選択する",
- "clearButtonLabel": "クリア選択",
- "nothingFound": "何も見つかりません"
- },
"openServiceInNewTab": {
"label": "新しいタブでサービスを開く"
},
diff --git a/public/locales/ja/layout/element-selector/selector.json b/public/locales/ja/layout/element-selector/selector.json
new file mode 100644
index 000000000..1fe2fe166
--- /dev/null
+++ b/public/locales/ja/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "新しいタイルを追加する",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "前のステップに戻る",
+ "actionIcon": {
+ "tooltip": "タイルを追加する"
+ }
+}
diff --git a/public/locales/ja/layout/header/actions/toggle-edit-mode.json b/public/locales/ja/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..bf76a5328
--- /dev/null
+++ b/public/locales/ja/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "編集モードに入る",
+ "enabled": "終了と保存"
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ja/layout/mobile/drawer.json b/public/locales/ja/layout/mobile/drawer.json
new file mode 100644
index 000000000..8549fe35e
--- /dev/null
+++ b/public/locales/ja/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} サイドバー"
+}
diff --git a/public/locales/ja/layout/modals/about.json b/public/locales/ja/layout/modals/about.json
new file mode 100644
index 000000000..9b88f6ddb
--- /dev/null
+++ b/public/locales/ja/layout/modals/about.json
@@ -0,0 +1,6 @@
+{
+ "i18n": "ロードされたI18n翻訳名前空間",
+ "locales": "設定されたI18nロケール",
+ "contact": "お困りごとやご質問はありませんか?私たちにご連絡ください。",
+ "addToDashboard": "ダッシュボードに追加"
+}
diff --git a/public/locales/ja/layout/modals/add-app.json b/public/locales/ja/layout/modals/add-app.json
new file mode 100644
index 000000000..f76a5fb3a
--- /dev/null
+++ b/public/locales/ja/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "一般",
+ "behaviour": "ビヘイビア",
+ "network": "ネットワーク",
+ "appearance": "外観",
+ "integration": "統合化"
+ },
+ "general": {
+ "appname": {
+ "label": "アプリ名",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "内部アドレス",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "外部アドレス",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "新しいタブで開く",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "ステータスチェッカー",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "HTTPステータスコード",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "アプリアイコン",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "インテグレーション構成",
+ "description": "",
+ "placeholder": "インテグレーションを選択する",
+ "defined": "定義",
+ "undefined": "未定義",
+ "public": "公開",
+ "private": "プライベート",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "シークレットを更新するには、値を入力し、保存ボタンをクリックします。シークレットを削除するには、クリアボタンを使用します。",
+ "warning": "",
+ "clear": "クリアシークレット",
+ "save": "秘密を守る",
+ "update": "アップデートシークレット"
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/ja/layout/modals/change-position.json b/public/locales/ja/layout/modals/change-position.json
new file mode 100644
index 000000000..251afd45d
--- /dev/null
+++ b/public/locales/ja/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X軸位置",
+ "width": "幅",
+ "height": "高さ",
+ "yPosition": "Y軸位置",
+ "zeroOrHigher": "0以上",
+ "betweenXandY": "{{min}} と {{max}}の間"
+}
\ No newline at end of file
diff --git a/public/locales/ja/layout/screen-sizes.json b/public/locales/ja/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/ja/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ja/layout/tools.json b/public/locales/ja/layout/tools.json
new file mode 100644
index 000000000..c433c061b
--- /dev/null
+++ b/public/locales/ja/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "現在お持ちでないツール"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "アイコンの検索...",
+ "searchLimitationTitle": "検索対象は {{max}} のアイコンに限定されます。",
+ "searchLimitationMessage": "迅速な処理を行うため、検索対象は {{max}} のアイコンに限定されています。その他のアイコンを探すには、検索ボックスをご利用ください。"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/ja/modules/calendar.json b/public/locales/ja/modules/calendar.json
index cb3c90213..40f3f5a4f 100644
--- a/public/locales/ja/modules/calendar.json
+++ b/public/locales/ja/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "カレンダー",
- "description": "今後のリリースを表示するためのカレンダーモジュールです。SonarrとRadarrのAPIと連動しています。",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "週の始まりは日曜日"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/common.json b/public/locales/ja/modules/common.json
index cc4ef4555..2b539b6d0 100644
--- a/public/locales/ja/modules/common.json
+++ b/public/locales/ja/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "設定"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/dashdot.json b/public/locales/ja/modules/dashdot.json
index fa792b081..6172cd65b 100644
--- a/public/locales/ja/modules/dashdot.json
+++ b/public/locales/ja/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "ダッシュ",
- "description": "実行中のDash.インスタンスのグラフを表示するためのモジュールです。",
+ "description": "",
"settings": {
+ "title": "Dash.ウィジェットの設定",
"cpuMultiView": {
"label": "CPUマルチコアビュー"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/date.json b/public/locales/ja/modules/date.json
index ea0e82593..2e45b2e37 100644
--- a/public/locales/ja/modules/date.json
+++ b/public/locales/ja/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "日付",
- "description": "カードに現在時刻と日付を表示する",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "フルタイム(24時間)表示"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/dlspeed.json b/public/locales/ja/modules/dlspeed.json
index e688d2aa0..47b687619 100644
--- a/public/locales/ja/modules/dlspeed.json
+++ b/public/locales/ja/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "ダウンロード速度",
- "description": "対応サービスの現在のダウンロード速度を表示する"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/docker.json b/public/locales/ja/modules/docker.json
index 63ad3e54b..f53a45c3c 100644
--- a/public/locales/ja/modules/docker.json
+++ b/public/locales/ja/modules/docker.json
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "サービス追加",
- "message": "ホーマーにサービスを追加する"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "再スタート"
@@ -49,35 +49,35 @@
},
"actions": {
"start": {
- "start": "",
- "end": ""
+ "start": "スタート",
+ "end": "開始"
},
"stop": {
- "start": "",
+ "start": "停止",
"end": "停止中"
},
"restart": {
- "start": "",
- "end": ""
+ "start": "再スタート",
+ "end": "再スタート"
},
"remove": {
- "start": "",
- "end": ""
+ "start": "削除",
+ "end": "削除"
}
},
"errors": {
"integrationFailed": {
"title": "Dockerとの連携に失敗",
- "message": "ドッカーソケットをマウントするのを忘れていませんか?"
+ "message": ""
},
"unknownError": {
"title": "エラーが発生しました"
},
"oneServiceAtATime": {
- "title": "一度に1つのサービスのみを追加してください"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "ドッカー"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/overseerr.json b/public/locales/ja/modules/overseerr.json
index 36c49d328..16c7ee862 100644
--- a/public/locales/ja/modules/overseerr.json
+++ b/public/locales/ja/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "オーバーホール",
- "description": "Overseerr/Jellyseerrからメディアを検索して追加できるようにする。"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "ダウンロードしたい季節にチェックを入れる",
+ "caption": "",
"table": {
"header": {
"season": "シーズン",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/ping.json b/public/locales/ja/modules/ping.json
index 3c65a9837..38f4eecc3 100644
--- a/public/locales/ja/modules/ping.json
+++ b/public/locales/ja/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "ピング",
- "description": "サービスが稼働しているか、特定のHTTPステータスコードを返しているかどうかを確認できるようにします。"
+ "description": ""
},
"states": {
"online": "オンライン {{response}}",
"offline": "オフライン {{response}}",
"loading": "読み込み中..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/search.json b/public/locales/ja/modules/search.json
index 3efea179d..165ba4037 100644
--- a/public/locales/ja/modules/search.json
+++ b/public/locales/ja/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "検索バー",
- "description": "ウェブ、Youtube、Torrent、Overseerrを検索するための検索バー"
+ "description": ""
},
"input": {
"placeholder": "ウェブで検索..."
},
- "switched-to": "",
+ "switched-to": "に切り替わりました。",
"searchEngines": {
"search": {
- "name": "",
+ "name": "ウェブ",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "Youtube",
+ "description": "Youtubeで検索"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "トレント",
+ "description": "トレントを検索する"
},
"overseerr": {
"name": "オーバーホール",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "ショートカットで検索バーを選択することができます ",
+ "switchedSearchEngine": "{{searchEngine}}で検索するように変更しました。"
+}
diff --git a/public/locales/ja/modules/torrents-status.json b/public/locales/ja/modules/torrents-status.json
index 33a54b4ab..4da76108a 100644
--- a/public/locales/ja/modules/torrents-status.json
+++ b/public/locales/ja/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "奔流",
- "description": "対応サービスの現在のダウンロード速度を表示する",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "完了したトレントを隠す"
+ "title": "",
+ "refreshInterval": {
+ "label": "リフレッシュ間隔(秒)"
+ },
+ "displayCompletedTorrents": {
+ "label": "完了したトレントを表示する"
+ },
+ "displayStaleTorrents": {
+ "label": "古くなったトレントを表示する"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "対応するダウンロードクライアントが見つかりません",
- "text": "ダウンロードサービスを追加して、現在のダウンロードを表示する"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "予期せぬエラーが発生しました",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "読み込み中..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/modules/usenet.json b/public/locales/ja/modules/usenet.json
index 5c95fd2f5..22fa97379 100644
--- a/public/locales/ja/modules/usenet.json
+++ b/public/locales/ja/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
+ "name": "ユーズネット",
"description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "対応するダウンロードクライアントが見つかりません",
- "text": "ダウンロードサービスを追加して、現在のダウンロードを表示する"
+ "text": ""
}
}
},
diff --git a/public/locales/ja/modules/weather.json b/public/locales/ja/modules/weather.json
index cdf742ae4..e7461b4cb 100644
--- a/public/locales/ja/modules/weather.json
+++ b/public/locales/ja/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "天気",
- "description": "現在地の天気を調べる",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "表示単位:華氏"
},
@@ -29,4 +30,4 @@
"unknown": "不明"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/settings/common.json b/public/locales/ja/settings/common.json
index d17465f6b..c9d9243aa 100644
--- a/public/locales/ja/settings/common.json
+++ b/public/locales/ja/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "カスタマイズ"
},
"tips": {
- "configTip": "設定ファイルをドラッグ&ドロップでアップロードしよう"
+ "configTip": ""
},
"credits": {
"madeWithLove": "で作った❤️ by @さん"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "グロースグリッド(全領域を占有)",
+ "layout": {
+ "title": "ダッシュボードのレイアウト",
+ "main": "メイン",
+ "sidebar": "サイドバー",
+ "cannotturnoff": "OFFにできない",
+ "dashboardlayout": "ダッシュボードのレイアウト",
+ "enablersidebar": "右サイドバーを有効にする",
+ "enablelsidebar": "左サイドバーを有効にする",
+ "enablesearchbar": "検索バーを有効にする",
+ "enabledocker": "dockerとの統合を有効にする",
+ "enableping": "Pingを有効にする",
+ "enablelsidebardesc": "オプションです。アプリと統合にのみ使用可能",
+ "enablersidebardesc": "オプションです。アプリと統合にのみ使用可能"
+ }
+}
diff --git a/public/locales/ja/settings/customization/page-appearance.json b/public/locales/ja/settings/customization/page-appearance.json
index 48a8d31b2..1bbf56b73 100644
--- a/public/locales/ja/settings/customization/page-appearance.json
+++ b/public/locales/ja/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "ページタイトル",
- "placeholder": "ホーマー"
+ "label": "ページタイトル"
+ },
+ "metaTitle": {
+ "label": "メタ・タイトル"
},
"logo": {
"label": "ロゴマーク"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "カスタムCSS",
- "placeholder": "カスタムCSSは最後に実行されます"
+ "placeholder": ""
},
"buttons": {
"submit": "提出"
diff --git a/public/locales/ja/settings/general/config-changer.json b/public/locales/ja/settings/general/config-changer.json
index 173eb68ff..c9b9a549b 100644
--- a/public/locales/ja/settings/general/config-changer.json
+++ b/public/locales/ja/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "コンフィグローダ"
+ "label": "",
+ "description": "",
+ "loadingNew": "コンフィグを読み込む...",
+ "pleaseWait": ""
},
"modal": {
- "title": "新しいコンフィグの名前を選択します。",
- "form": {
- "configName": {
- "label": "コンフィグ名",
- "placeholder": "新しいコンフィグ名"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "確認"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "コンフィグ保存",
- "message": "{{configName}}として保存されたコンフィグ"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "コンフィグ削除の失敗",
"message": "コンフィグ削除の失敗"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "ここにファイルをドラッグしてコンフィグをアップロードしてください。JSONのみ対応。"
+ "title": "コンフィギュレーションアップロード",
+ "text": ""
},
"reject": {
- "text": "このファイル形式はサポートされていません。JSONのみアップロードしてください。"
+ "title": "ドラッグ&ドロップによるアップロードを拒否",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/settings/general/module-enabler.json b/public/locales/ja/settings/general/module-enabler.json
index c559d8e12..9e26dfeeb 100644
--- a/public/locales/ja/settings/general/module-enabler.json
+++ b/public/locales/ja/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "モジュールイネーブラー"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/ja/settings/general/search-engine.json b/public/locales/ja/settings/general/search-engine.json
index 8ea9536c4..2b41ccf0c 100644
--- a/public/locales/ja/settings/general/search-engine.json
+++ b/public/locales/ja/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "検索エンジン",
+ "configurationName": "",
"tips": {
- "generalTip": "YouTube を検索する場合は「!yt」、Torrent を検索する場合は「!t」という接頭辞を付けてください。",
+ "generalTip": "",
"placeholderTip": "%s は、クエリのプレースホルダとして使用することができます。"
},
"customEngine": {
+ "title": "カスタム検索エンジン",
"label": "クエリURL",
"placeholder": "カスタムクエリURL"
},
"searchNewTab": {
- "label": ""
+ "label": "検索結果を新しいタブで開く"
+ },
+ "searchEnabled": {
+ "label": "検索可能"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ja/settings/general/widget-positions.json b/public/locales/ja/settings/general/widget-positions.json
index bc924ba82..0967ef424 100644
--- a/public/locales/ja/settings/general/widget-positions.json
+++ b/public/locales/ja/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "ウィジェットを左側に配置する"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json
index fb60078fe..a23341b5d 100644
--- a/public/locales/ko/common.json
+++ b/public/locales/ko/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "저장"
+ "save": "",
+ "about": "",
+ "cancel": "취소",
+ "close": "",
+ "delete": "삭제",
+ "ok": "",
+ "edit": "수정",
+ "version": "",
+ "changePosition": "",
+ "remove": "제거",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "설정",
+ "dangerZone": "위험한 설정"
+ },
+ "secrets": {
+ "apiKey": "",
+ "username": "사용자 이름",
+ "password": "비밀번호"
},
"tip": "팁: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "분",
"hours": "시간"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/ko/layout/add-service-app-shelf.json b/public/locales/ko/layout/add-service-app-shelf.json
index 76d8977ef..7fe728856 100644
--- a/public/locales/ko/layout/add-service-app-shelf.json
+++ b/public/locales/ko/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "고급 설정",
"form": {
- "httpStatusCodes": {
- "label": "HTTP 상태 코드",
- "placeholder": "유효한 상태 코드를 선택해 주세요",
- "clearButtonLabel": "모두 선택 취소",
- "nothingFound": "찾을 수 없음"
- },
"openServiceInNewTab": {
"label": "새 탭에서 서비스 열기"
},
diff --git a/public/locales/ko/layout/element-selector/selector.json b/public/locales/ko/layout/element-selector/selector.json
new file mode 100644
index 000000000..2a4f14e0d
--- /dev/null
+++ b/public/locales/ko/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "",
+ "actionIcon": {
+ "tooltip": ""
+ }
+}
diff --git a/public/locales/ko/layout/header/actions/toggle-edit-mode.json b/public/locales/ko/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..56487b8b1
--- /dev/null
+++ b/public/locales/ko/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "",
+ "enabled": ""
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ko/layout/mobile/drawer.json b/public/locales/ko/layout/mobile/drawer.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/ko/layout/mobile/drawer.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/ko/layout/modals/about.json b/public/locales/ko/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/ko/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/ko/layout/modals/add-app.json b/public/locales/ko/layout/modals/add-app.json
new file mode 100644
index 000000000..fa07081e7
--- /dev/null
+++ b/public/locales/ko/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "네트워크",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/ko/layout/modals/change-position.json b/public/locales/ko/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/ko/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/ko/layout/screen-sizes.json b/public/locales/ko/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/ko/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ko/layout/tools.json b/public/locales/ko/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/ko/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/ko/modules/calendar.json b/public/locales/ko/modules/calendar.json
index ec1a2470a..c07ab21b8 100644
--- a/public/locales/ko/modules/calendar.json
+++ b/public/locales/ko/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "캘린더",
- "description": "일정을 표시하기 위한 캘린더 모듈입니다. Sonarr API와 Radarr API로 작동합니다.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "한 주의 시작을 일요일로 설정"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/common.json b/public/locales/ko/modules/common.json
index 3b6f3312f..6eba896fa 100644
--- a/public/locales/ko/modules/common.json
+++ b/public/locales/ko/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "설정"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/dashdot.json b/public/locales/ko/modules/dashdot.json
index deed26758..7111a2872 100644
--- a/public/locales/ko/modules/dashdot.json
+++ b/public/locales/ko/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "실행 중인 Dash. 인스턴스의 그래프를 표시하는 모듈입니다.",
+ "description": "",
"settings": {
+ "title": "",
"cpuMultiView": {
"label": "CPU 멀티 코어 보기"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/date.json b/public/locales/ko/modules/date.json
index ddcae6973..8bb5d6de4 100644
--- a/public/locales/ko/modules/date.json
+++ b/public/locales/ko/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "날짜",
- "description": "시간과 날짜를 보여주는 카드를 추가합니다",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "24시간제로 표시"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/dlspeed.json b/public/locales/ko/modules/dlspeed.json
index 5ec5cd979..597f8e12b 100644
--- a/public/locales/ko/modules/dlspeed.json
+++ b/public/locales/ko/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "다운로드 속도",
- "description": "지원하는 서비스의 현재 다운로드 속도를 표시합니다"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/docker.json b/public/locales/ko/modules/docker.json
index 855eb6667..f9213952a 100644
--- a/public/locales/ko/modules/docker.json
+++ b/public/locales/ko/modules/docker.json
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "서비스 추가",
- "message": "Homarr에 서비스 추가"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "재시작"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Docker 통합 실패",
- "message": "도커 소켓 마운트를 잊지 않으셨나요?"
+ "message": ""
},
"unknownError": {
"title": "오류가 발생했습니다"
},
"oneServiceAtATime": {
- "title": "한 번에 하나의 서비스만 추가해 주세요!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/overseerr.json b/public/locales/ko/modules/overseerr.json
index 60230d7b0..50f802d9e 100644
--- a/public/locales/ko/modules/overseerr.json
+++ b/public/locales/ko/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Overseerr/Jellyseerr에서 미디어를 추가하고 검색할 수 있습니다"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "다운로드하려는 시즌을 선택하세요",
+ "caption": "",
"table": {
"header": {
"season": "시즌",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/ping.json b/public/locales/ko/modules/ping.json
index e583a4633..dba49b218 100644
--- a/public/locales/ko/modules/ping.json
+++ b/public/locales/ko/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "핑",
- "description": "서비스가 작동 중인지 또는 특정 HTTP 상태 코드를 반환하는지 확인할 수 있습니다"
+ "description": ""
},
"states": {
"online": "온라인 {{response}}",
"offline": "오프라인 {{response}}",
"loading": "불러오는 중…"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/search.json b/public/locales/ko/modules/search.json
index 41d0e72f7..308df4e6f 100644
--- a/public/locales/ko/modules/search.json
+++ b/public/locales/ko/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "검색창",
- "description": "웹, 유튜브. 토렌트, Overseerr를 검색할 수 있는 검색창"
+ "description": ""
},
"input": {
"placeholder": "웹에서 검색..."
@@ -27,4 +27,4 @@
},
"tip": "",
"switchedSearchEngine": ""
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/torrents-status.json b/public/locales/ko/modules/torrents-status.json
index fad5c677f..01e1fb831 100644
--- a/public/locales/ko/modules/torrents-status.json
+++ b/public/locales/ko/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "토렌트",
- "description": "지원하는 서비스의 현재 다운로드 속도를 표시합니다",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "완료된 토렌트 숨기기"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "지원되는 다운로드 클라이언트를 찾을 수 없습니다!",
- "text": "현재 다운로드를 보려면 다운로드 서비스를 추가하세요"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "불러오는 중…"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/modules/usenet.json b/public/locales/ko/modules/usenet.json
index 5b2b547af..44a15b52b 100644
--- a/public/locales/ko/modules/usenet.json
+++ b/public/locales/ko/modules/usenet.json
@@ -7,7 +7,7 @@
"errors": {
"noDownloadClients": {
"title": "지원되는 다운로드 클라이언트를 찾을 수 없습니다!",
- "text": "현재 다운로드를 보려면 다운로드 서비스를 추가하세요"
+ "text": ""
}
}
},
diff --git a/public/locales/ko/modules/weather.json b/public/locales/ko/modules/weather.json
index 60712f31c..b5caa8d55 100644
--- a/public/locales/ko/modules/weather.json
+++ b/public/locales/ko/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "날씨",
- "description": "현재 위치의 날씨 보기",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "화씨로 표시"
},
@@ -29,4 +30,4 @@
"unknown": "알 수 없음"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/settings/common.json b/public/locales/ko/settings/common.json
index 5b38bc24b..0e4e749fd 100644
--- a/public/locales/ko/settings/common.json
+++ b/public/locales/ko/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "커스터마이징"
},
"tips": {
- "configTip": "설정 파일을 드래그 앤 드롭으로 업로드해 보세요!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "Made with ❤️ by @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "",
+ "layout": {
+ "title": "",
+ "main": "",
+ "sidebar": "",
+ "cannotturnoff": "",
+ "dashboardlayout": "",
+ "enablersidebar": "",
+ "enablelsidebar": "",
+ "enablesearchbar": "",
+ "enabledocker": "",
+ "enableping": "",
+ "enablelsidebardesc": "",
+ "enablersidebardesc": ""
+ }
+}
diff --git a/public/locales/ko/settings/customization/page-appearance.json b/public/locales/ko/settings/customization/page-appearance.json
index a7ab68df2..ec84dee44 100644
--- a/public/locales/ko/settings/customization/page-appearance.json
+++ b/public/locales/ko/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "페이지 제목",
- "placeholder": "Homarr 🦞"
+ "label": "페이지 제목"
+ },
+ "metaTitle": {
+ "label": ""
},
"logo": {
"label": "로고"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "커스텀 CSS",
- "placeholder": "커스텀 CSS는 마지막으로 실행됩니다"
+ "placeholder": ""
},
"buttons": {
"submit": "적용"
diff --git a/public/locales/ko/settings/general/config-changer.json b/public/locales/ko/settings/general/config-changer.json
index f55d6d064..cb1e4745f 100644
--- a/public/locales/ko/settings/general/config-changer.json
+++ b/public/locales/ko/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "설정 불러오기"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "새로운 설정의 이름을 입력하세요",
- "form": {
- "configName": {
- "label": "설정 이름",
- "placeholder": "새로운 설정 이름"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "확인"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "설정 저장됨",
- "message": "{{configName}} 로 설정 저장됨"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "설정 삭제 실패",
"message": "설정 삭제 실패"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "구성을 업로드하려면 여기로 파일을 드래그하세요. JSON 형식만 지원합니다."
+ "title": "",
+ "text": ""
},
"reject": {
- "text": "이 파일 형식은 지원되지 않습니다. JSON 형식만 업로드하세요."
+ "title": "",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/settings/general/module-enabler.json b/public/locales/ko/settings/general/module-enabler.json
index a7d107e9e..9e26dfeeb 100644
--- a/public/locales/ko/settings/general/module-enabler.json
+++ b/public/locales/ko/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "모듈 활성화"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/ko/settings/general/search-engine.json b/public/locales/ko/settings/general/search-engine.json
index 38cc44498..844b076ce 100644
--- a/public/locales/ko/settings/general/search-engine.json
+++ b/public/locales/ko/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "검색 엔진",
+ "configurationName": "",
"tips": {
- "generalTip": "YouTube 또는 Torrent에서 검색하려면 각각 검색어 앞에 !yt 및 !t 접두사를 사용하십시오.",
+ "generalTip": "",
"placeholderTip": "%s는 쿼리의 자리 표시자로 사용할 수 있습니다."
},
"customEngine": {
+ "title": "",
"label": "쿼리 URL",
"placeholder": "커스텀 쿼리 URL"
},
"searchNewTab": {
"label": ""
+ },
+ "searchEnabled": {
+ "label": ""
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ko/settings/general/widget-positions.json b/public/locales/ko/settings/general/widget-positions.json
index f6f5efba1..0967ef424 100644
--- a/public/locales/ko/settings/general/widget-positions.json
+++ b/public/locales/ko/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "위젯을 왼쪽에 배치"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/lol/authentication/login.json b/public/locales/lol/authentication/login.json
index 302fe120d..aad381afa 100644
--- a/public/locales/lol/authentication/login.json
+++ b/public/locales/lol/authentication/login.json
@@ -1,27 +1,27 @@
{
- "title": "",
- "text": "",
+ "title": "Welcom Bak!",
+ "text": "Plz Entr Ur Pasword",
"form": {
"fields": {
"password": {
"label": "Password",
- "placeholder": ""
+ "placeholder": "Ur Pasword"
}
},
"buttons": {
- "submit": ""
+ "submit": "Sign In"
}
},
"notifications": {
"checking": {
- "title": "",
- "message": ""
+ "title": "Checkin Ur Pasword",
+ "message": "Ur Pasword Iz Bean Checkd..."
},
"correct": {
- "title": ""
+ "title": "Sign In Succesful, Redirectin..."
},
"wrong": {
- "title": ""
+ "title": "Teh Pasword U Enterd Iz Incorrect, Plz Try Again."
}
}
}
diff --git a/public/locales/lol/common.json b/public/locales/lol/common.json
index 2e9b3ea27..8eb46d61c 100644
--- a/public/locales/lol/common.json
+++ b/public/locales/lol/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Save"
+ "save": "Saev",
+ "about": "'bout",
+ "cancel": "Cancel",
+ "close": "Cloes",
+ "delete": "Deleet",
+ "ok": "K",
+ "edit": "Edit",
+ "version": "Vershun",
+ "changePosition": "Change Posishun",
+ "remove": "Remoev",
+ "removeConfirm": "R U Sure Dat U Wants 2 Remoov {{item}} ?",
+ "sections": {
+ "settings": "Settingz",
+ "dangerZone": "Dangr zoen"
+ },
+ "secrets": {
+ "apiKey": "Api key",
+ "username": "Usernaem",
+ "password": "Password"
},
"tip": "Tip: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutz",
"hours": "hourz"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/lol/layout/add-service-app-shelf.json b/public/locales/lol/layout/add-service-app-shelf.json
index 39dd49297..3356e4054 100644
--- a/public/locales/lol/layout/add-service-app-shelf.json
+++ b/public/locales/lol/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Advancd opshuns",
"form": {
- "httpStatusCodes": {
- "label": "HTTP Status Codes",
- "placeholder": "Select valid status coeds",
- "clearButtonLabel": "Clear selecshun",
- "nothingFound": "Nofin findz"
- },
"openServiceInNewTab": {
"label": "Open survis in nu tab"
},
diff --git a/public/locales/lol/layout/element-selector/selector.json b/public/locales/lol/layout/element-selector/selector.json
new file mode 100644
index 000000000..941461707
--- /dev/null
+++ b/public/locales/lol/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Add New Tile",
+ "text": "Tilez R Teh Main Element Ov Homarr. They R Usd 2 Display Ur Apps An Othr Informashun. U Can Add As Lotz Da Tilez As U Wants."
+ },
+ "widgetDescription": "Widgets Interact Wif Ur Apps, 2 Provide U Wif Moar Control Ovar Ur Applicashuns. They Usually Require Addishunal Configurashun Before Use.",
+ "goBack": "Go Bak 2 Teh Previous Step",
+ "actionIcon": {
+ "tooltip": "Add Tile"
+ }
+}
diff --git a/public/locales/lol/layout/header/actions/toggle-edit-mode.json b/public/locales/lol/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..616aa7dea
--- /dev/null
+++ b/public/locales/lol/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "In Edit Mode, U Can Adjust Tilez An Configure Apps. Changez R Not Savd Til U Exit Edit Mode.",
+ "button": {
+ "disabled": "Entr Edit Mode",
+ "enabled": "Exit An Save"
+ },
+ "popover": {
+ "title": "Edit Mode Iz Enabld 4 <1>{{size}}1> Size",
+ "text": "U Can Adjust An Configure Ur Apps Nao. Changez R Not Savd Til U Exit Edit Mode"
+ },
+ "screenSizes": {
+ "small": "small",
+ "medium": "medium",
+ "large": "large"
+ }
+}
diff --git a/public/locales/lol/layout/mobile/drawer.json b/public/locales/lol/layout/mobile/drawer.json
new file mode 100644
index 000000000..7c82c2d51
--- /dev/null
+++ b/public/locales/lol/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} Sidebar"
+}
diff --git a/public/locales/lol/layout/modals/about.json b/public/locales/lol/layout/modals/about.json
new file mode 100644
index 000000000..926b2a843
--- /dev/null
+++ b/public/locales/lol/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr Iz Sleek , Modern Dashbord Dat Puts All Ov Ur Apps An Servicez At Ur Fingertips. Wif Homarr, U Can Acces An Control Evrythin In Wan Convenient Locashun. Homarr Seamlesly Integratez Wif Teh Apps Uve Addd, Providin U Wif Valuable Informashun An Givin U Complete Control. Installashun Iz Breeze, An Homarr Supports Wide Range Ov Deployment Methodz.",
+ "i18n": "Loadd I18N Tranzlashun Namespacez",
+ "locales": "Configurd I18N Localez",
+ "contact": "Havin Trouble Or Queshuns? Connect Wif Us!",
+ "addToDashboard": "Add 2 Dashbord"
+}
diff --git a/public/locales/lol/layout/modals/add-app.json b/public/locales/lol/layout/modals/add-app.json
new file mode 100644
index 000000000..6c3e7645d
--- /dev/null
+++ b/public/locales/lol/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "General",
+ "behaviour": "Behaviour",
+ "network": "Netwerk",
+ "appearance": "Appearance",
+ "integration": "Integrashun"
+ },
+ "general": {
+ "appname": {
+ "label": "App Naym",
+ "description": "Usd 2 Display Teh App On Teh Dashbord."
+ },
+ "internalAddress": {
+ "label": "Internal Addres",
+ "description": "Internal IP-address Ov Teh App."
+ },
+ "externalAddress": {
+ "label": "External address",
+ "description": "URL Dat Will Be Opend When Clickin On Teh App."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Open in new tab",
+ "description": "Open Teh App In New Tab Instead Ov Teh Current Wan."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Status Checkr",
+ "description": "Checkz If Ur App Iz Online Usin Simple HTTP(S) Request."
+ },
+ "statusCodes": {
+ "label": "HTTP Status Codez",
+ "description": "Teh HTTP Status Codez Dat R Considerd As Online."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "App Icon",
+ "description": "Teh Icon Dat Will Be Displayd On Teh Dashbord."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Integrashun Configurashun",
+ "description": "Teh Integrashun Configurashun Dat Will Be Usd 2 Connect 2 Ur App.",
+ "placeholder": "Select An Integrashun",
+ "defined": "Defind",
+ "undefined": "Undefind",
+ "public": "Public",
+ "private": "Private",
+ "explanationPrivate": "A Private Seekret Will Be Sent 2 Teh Servr Only Once. Once Ur Browsr Has Refreshd Teh Paeg, It Will Nevr Be Sent Again.",
+ "explanationPublic": "A Public Seekret Will Always Be Sent 2 Teh Client An Iz Accesible Ovar Teh Api. It Shud Not Contain Any Confidential Valuez Such As Usernamez, Paswordz, Tokens, Certificatez An Similar!"
+ },
+ "secrets": {
+ "description": "2 Update Seekret, Entr Value An Click Teh Save Butn. 2 Remoov Seekret, Use Teh Clear Butn.",
+ "warning": "Ur Credentials Act As Teh Acces 4 Ur Integrashuns An U Shud Nevr Share Them Wif Anybody Else. Teh Homarr Team Will Nevr Ask 4 Credentials. Mak Sure 2 Store An Manage Ur Secrets Safely .",
+ "clear": "Clear Seekret",
+ "save": "Save Seekret",
+ "update": "Update Seekret"
+ }
+ },
+ "validation": {
+ "popover": "Ur Form Contains Invalid Data. Hence, It Cant Be Savd. Plz Resolve All Issuez An Click Dis Butn Again 2 Save Ur Changez"
+ }
+}
diff --git a/public/locales/lol/layout/modals/change-position.json b/public/locales/lol/layout/modals/change-position.json
new file mode 100644
index 000000000..cf3c63cc8
--- /dev/null
+++ b/public/locales/lol/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X Axis Posishun",
+ "width": "Width",
+ "height": "Height",
+ "yPosition": "Y Axis Posishun",
+ "zeroOrHigher": "0 Or Highr",
+ "betweenXandY": "Tween {{min}} An {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/lol/layout/screen-sizes.json b/public/locales/lol/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/lol/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/lol/layout/tools.json b/public/locales/lol/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/lol/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/lol/modules/calendar.json b/public/locales/lol/modules/calendar.json
index b58ac229e..603eb170d 100644
--- a/public/locales/lol/modules/calendar.json
+++ b/public/locales/lol/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendar",
- "description": "Calendar moduel 4 displaying upcomin releasez, Srsly. It interacts wif teh Sonarr an Radarr API.",
+ "description": "Displayz calendar wif upcomin releasez, frum supportd integrations.",
"settings": {
+ "title": "Settings 4 Calendar Widget",
"sundayStart": {
"label": "Start teh week on Sunday"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr Release Type"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/common.json b/public/locales/lol/modules/common.json
index 4895885d3..6b9bf14bd 100644
--- a/public/locales/lol/modules/common.json
+++ b/public/locales/lol/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Settingz"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Un-Usd Parametr In Configurashun Detectd {{key}}. Homarr Iz Unable 2 Interpret An Use Dis Parametr. 2 Avoid Any Unexpectd Behavior, Bak Up Ur Configurashun An Correct Ur Configurashun."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/dashdot.json b/public/locales/lol/modules/dashdot.json
index efdd6bd88..cdcded471 100644
--- a/public/locales/lol/modules/dashdot.json
+++ b/public/locales/lol/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Moduel 4 displaying teh graphs ov ur runnin Dash. instanz.",
+ "description": "Displays Teh Grafs Ov An External Dash. Instance Inside Ov Homarr.",
"settings": {
+ "title": "Settings 4 Dash. Widget",
"cpuMultiView": {
"label": "CPU Multi-Coer View"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/date.json b/public/locales/lol/modules/date.json
index fc6851fde..3b0b45bd0 100644
--- a/public/locales/lol/modules/date.json
+++ b/public/locales/lol/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Daet",
- "description": "Show teh current tiem an daet in card",
+ "name": "Date An Tiem",
+ "description": "Displays Teh Current Date An Tiem.",
"settings": {
+ "title": "Settings 4 Date An Tiem Widget",
"display24HourFormat": {
"label": "Display fol tiem (24-hour)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/dlspeed.json b/public/locales/lol/modules/dlspeed.json
index 0be4fb54b..219538ed5 100644
--- a/public/locales/lol/modules/dlspeed.json
+++ b/public/locales/lol/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Download Sped",
- "description": "Show teh current download sped ov supportd servicez"
+ "description": "Displays Teh Download An Upload Sped Ov Supportd Integrashuns."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/docker.json b/public/locales/lol/modules/docker.json
index c5e635e77..c1ec180ef 100644
--- a/public/locales/lol/modules/docker.json
+++ b/public/locales/lol/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Dockah",
- "description": ""
+ "description": "Allows U 2 Easily C An Manage All Ov Ur Dockr Containers."
},
"search": {
"placeholder": "Search by contaneah or imaeg naym"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Add survis",
- "message": "Add survis 2 Homarr"
+ "title": "Add app",
+ "message": "Add App 2 Homarr"
},
"restart": {
"title": "Restart"
@@ -49,35 +49,35 @@
},
"actions": {
"start": {
- "start": "",
- "end": ""
+ "start": "Startin",
+ "end": "Startd"
},
"stop": {
- "start": "",
+ "start": "Stoppin",
"end": "Stopped"
},
"restart": {
- "start": "",
- "end": ""
+ "start": "Restartin",
+ "end": "Restartd"
},
"remove": {
- "start": "",
- "end": ""
+ "start": "Removin",
+ "end": "Removd"
}
},
"errors": {
"integrationFailed": {
"title": "Dockah integrashn faild",
- "message": "Did u forget 2 mount teh dockah socket ?"
+ "message": "Did U Forget 2 Mount Teh Dockr Socket?"
},
"unknownError": {
"title": "Thar wuz an error"
},
"oneServiceAtATime": {
- "title": "Plz only add wan survis at tiem!"
+ "title": "Plz Only Add Wan App Or Service At Tiem!"
}
},
"actionIcon": {
"tooltip": "Dockah"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/overseerr.json b/public/locales/lol/modules/overseerr.json
index 8664f923e..7867f831b 100644
--- a/public/locales/lol/modules/overseerr.json
+++ b/public/locales/lol/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Allowz u 2 search an add media frum Overseerr/Jellyseerr"
+ "description": "Allows U 2 Search An Add Media Frum Overseerr Or Jellyseerr."
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Tick teh seasons dat u wantz 2 bees downloaded",
+ "caption": "Tick Teh Seasons U Wants 2 Download",
"table": {
"header": {
"season": "Season",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/ping.json b/public/locales/lol/modules/ping.json
index f5c944627..35ca650e3 100644
--- a/public/locales/lol/modules/ping.json
+++ b/public/locales/lol/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Allowz u 2 check if teh survis iz up or returns specific HTTP status coed."
+ "description": "Displays Status Indicator Dependin On Teh HTTP Response Code Ov Given URL."
},
"states": {
"online": "Onlien {{response}}",
"offline": "Offlien {{response}}",
"loading": "Loadin..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/search.json b/public/locales/lol/modules/search.json
index 3ee484112..9165db59e 100644
--- a/public/locales/lol/modules/search.json
+++ b/public/locales/lol/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Search Bar",
- "description": "Search bar 2 search teh web, yootoob, Torrents or Overseerr"
+ "description": "A Search Bar Dat Allows U 2 Search Ur Custom Search Engine, Youtube, An Supportd Integrashuns."
},
"input": {
"placeholder": "Search teh web..."
},
- "switched-to": "",
+ "switched-to": "Switchd 2",
"searchEngines": {
"search": {
- "name": "",
- "description": ""
+ "name": "Web",
+ "description": "Search..."
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "Youtube",
+ "description": "Search on Youtube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrents",
+ "description": "Search 4 Torrents"
},
"overseerr": {
"name": "Overseerr",
- "description": ""
+ "description": "Search 4 Movies An TV Shows On Overseerr"
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "U Can Select Teh Search Bar Wif Teh Shortcut ",
+ "switchedSearchEngine": "Switchd 2 Searchin Wif {{searchEngine}}"
+}
diff --git a/public/locales/lol/modules/torrents-status.json b/public/locales/lol/modules/torrents-status.json
index 864ce3919..7c87b0cea 100644
--- a/public/locales/lol/modules/torrents-status.json
+++ b/public/locales/lol/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Show teh current download sped ov supportd servicez",
+ "description": "Displays List Ov Torrents Frum Supportd Torrent Clients.",
"settings": {
- "hideComplete": {
- "label": "Hied completd torrentz"
+ "title": "Settings 4 Torrent Widget",
+ "refreshInterval": {
+ "label": "Refresh Interval (In Secondz)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Display Completd Torrents"
+ },
+ "displayStaleTorrents": {
+ "label": "Display Stale Torrents"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "No supportd download clientz findz!",
- "text": "Add download survis 2 view ur current downloadz"
+ "title": "No Supportd Torrent Clients Findz!",
+ "text": "Add Supportd Torrent Client 2 View Ur Current Downloadz"
+ },
+ "generic": {
+ "title": "An Unexpectd Error Occurd",
+ "text": "Homarr Wuz Unable 2 Speek Wif Ur Torrent Clients. Plz Check Ur Configurashun"
}
+ },
+ "loading": {
+ "title": "Loadin..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/modules/usenet.json b/public/locales/lol/modules/usenet.json
index ea078dc3c..73debdd1f 100644
--- a/public/locales/lol/modules/usenet.json
+++ b/public/locales/lol/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
- "description": ""
+ "name": "Usenet",
+ "description": "Allows U 2 View An Manage Ur Usenet Instance."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "No supportd download clientz findz!",
- "text": "Add download survis 2 view ur current downloadz"
+ "text": "Add Supportd Usenet Download Client 2 View Ur Current Downloadz"
}
}
},
diff --git a/public/locales/lol/modules/weather.json b/public/locales/lol/modules/weather.json
index d0318319d..fd35719f8 100644
--- a/public/locales/lol/modules/weather.json
+++ b/public/locales/lol/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Weafr",
- "description": "Look up teh current weafr in ur locashun",
+ "description": "Displays Teh Current Weathr Informashun Ov Set Locashun.",
"settings": {
+ "title": "Settings 4 Weathr Widget",
"displayInFahrenheit": {
"label": "Display in Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Unknown"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/settings/common.json b/public/locales/lol/settings/common.json
index 74687468a..fd2c87840 100644
--- a/public/locales/lol/settings/common.json
+++ b/public/locales/lol/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Customahzations"
},
"tips": {
- "configTip": "Upload ur config fiel by dragging an dropping it onto teh paeg!"
+ "configTip": "Upload Ur Config File By Drag An Droppin It Onto Teh Paeg!"
},
"credits": {
"madeWithLove": "Maded wif ❤️ by @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Grow Grid (Taek All Space)",
+ "layout": {
+ "title": "Dashbord Layout",
+ "main": "Main",
+ "sidebar": "Sidebar",
+ "cannotturnoff": "Cant Be Turnd Off",
+ "dashboardlayout": "Dashbord Layout",
+ "enablersidebar": "Enable Rite Sidebar",
+ "enablelsidebar": "Enable Left Sidebar",
+ "enablesearchbar": "Enable Search Bar",
+ "enabledocker": "Enable Dockr Integrashun",
+ "enableping": "Enable Pings",
+ "enablelsidebardesc": "Opshunal. Can Be Usd 4 Apps An Integrashuns Only",
+ "enablersidebardesc": "Opshunal. Can Be Usd 4 Apps An Integrashuns Only"
+ }
+}
diff --git a/public/locales/lol/settings/customization/page-appearance.json b/public/locales/lol/settings/customization/page-appearance.json
index d2596232d..965b599bf 100644
--- a/public/locales/lol/settings/customization/page-appearance.json
+++ b/public/locales/lol/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Paeg Titel",
- "placeholder": "Homarr 🦞"
+ "label": "Paeg Titel"
+ },
+ "metaTitle": {
+ "label": "Meta Title"
},
"logo": {
"label": "Logooo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Custom CSS",
- "placeholder": "Custom CSS will bees executed last"
+ "placeholder": "Custom CSS Will Be Applid Last"
},
"buttons": {
"submit": "Submit"
diff --git a/public/locales/lol/settings/general/config-changer.json b/public/locales/lol/settings/general/config-changer.json
index 9b75cbab6..8cd05fcd2 100644
--- a/public/locales/lol/settings/general/config-changer.json
+++ b/public/locales/lol/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Config loadah"
+ "label": "Config Changr",
+ "description": "",
+ "loadingNew": "Loadin Ur Config...",
+ "pleaseWait": "Plz Wait Til Ur New Config Iz Loadd!"
},
"modal": {
- "title": "Chooz teh naym ov ur nu config",
- "form": {
- "configName": {
- "label": "Config naym",
- "placeholder": "Ur nu config naym"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Confirm"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Config saved",
- "message": "Config saved as {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Config deleet faild",
"message": "Config deleet faild"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Drag filez hah 2 upload config. Support 4 JSON only."
+ "title": "Configurashun Upload",
+ "text": "Drag Filez Her 2 Upload Config. Support 4 JSON Filez Only."
},
"reject": {
- "text": "Dis fiel format iz not supportd. Srsly plz only upload JSON."
+ "title": "Drag An Drop Upload Rejectd",
+ "text": "Dis File Format Iz Not Supportd. Plz Only Upload JSON Filez."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/settings/general/module-enabler.json b/public/locales/lol/settings/general/module-enabler.json
index be4366845..9e26dfeeb 100644
--- a/public/locales/lol/settings/general/module-enabler.json
+++ b/public/locales/lol/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Moduel enablah"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/lol/settings/general/search-engine.json b/public/locales/lol/settings/general/search-engine.json
index d011753b1..6e22ac92e 100644
--- a/public/locales/lol/settings/general/search-engine.json
+++ b/public/locales/lol/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Search engien",
+ "configurationName": "Search Engine Configurashun",
"tips": {
- "generalTip": "Ues teh prefixes !yt an !t in frunt ov ur quewee 2 search on yootoob or 4 Torrent respectively.",
+ "generalTip": "Thar R Multiple Prefixez U Can Use! Addin Thees Infront Ov Ur Query Will Filtr Teh Results. !S (Web), !T (Torrents), !Y (Youtube), An !M (Media).",
"placeholderTip": "%s can bees usd as placeholdah 4 teh quewee."
},
"customEngine": {
+ "title": "Custom Search Engine",
"label": "Quewee URL",
"placeholder": "Custom quewee URL"
},
"searchNewTab": {
- "label": ""
+ "label": "Open Search Results In New Tab"
+ },
+ "searchEnabled": {
+ "label": "Search Enabld"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/lol/settings/general/widget-positions.json b/public/locales/lol/settings/general/widget-positions.json
index 6a8772c99..8ac278113 100644
--- a/public/locales/lol/settings/general/widget-positions.json
+++ b/public/locales/lol/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
- "label": "Posishun widgetz on left"
-}
\ No newline at end of file
+ "label": "Posishun Widgets On Teh Left"
+}
diff --git a/public/locales/nl/authentication/login.json b/public/locales/nl/authentication/login.json
index f8049f9f6..e4408eeba 100644
--- a/public/locales/nl/authentication/login.json
+++ b/public/locales/nl/authentication/login.json
@@ -1,27 +1,27 @@
{
- "title": "",
- "text": "",
+ "title": "Welkom terug!",
+ "text": "Voer uw wachtwoord in",
"form": {
"fields": {
"password": {
"label": "Wachtwoord",
- "placeholder": ""
+ "placeholder": "Uw wachtwoord"
}
},
"buttons": {
- "submit": ""
+ "submit": "Inloggen"
}
},
"notifications": {
"checking": {
- "title": "",
- "message": ""
+ "title": "Bezig met uw wachtwoord controleren",
+ "message": "Uw wachtwoord wordt gecontroleerd..."
},
"correct": {
- "title": ""
+ "title": "Log in succesvol, redirect..."
},
"wrong": {
- "title": ""
+ "title": "Het ingevoerde wachtwoord is onjuist, probeer het opnieuw."
}
}
}
diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json
index c9b35befd..36b13c7a7 100644
--- a/public/locales/nl/common.json
+++ b/public/locales/nl/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Opslaan"
+ "save": "Opslaan",
+ "about": "Over",
+ "cancel": "Annuleer",
+ "close": "Sluiten",
+ "delete": "Verwijder",
+ "ok": "OK",
+ "edit": "Wijzig",
+ "version": "Versie",
+ "changePosition": "Positie wijzigen",
+ "remove": "Verwijder",
+ "removeConfirm": "Weet u zeker dat u {{item}} wilt verwijderen?",
+ "sections": {
+ "settings": "Instellingen",
+ "dangerZone": "Gevarenzone"
+ },
+ "secrets": {
+ "apiKey": "API-sleutel",
+ "username": "Gebruikersnaam",
+ "password": "Wachtwoord"
},
"tip": "Tip: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minuten",
"hours": "uren"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/nl/layout/add-service-app-shelf.json b/public/locales/nl/layout/add-service-app-shelf.json
index 7720848f4..c6a781177 100644
--- a/public/locales/nl/layout/add-service-app-shelf.json
+++ b/public/locales/nl/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Geavanceerde opties",
"form": {
- "httpStatusCodes": {
- "label": "HTTP-statuscodes",
- "placeholder": "Selecteer geldige statuscodes",
- "clearButtonLabel": "Selectie wissen",
- "nothingFound": "Geen resultaten"
- },
"openServiceInNewTab": {
"label": "Service in nieuw tabblad openen"
},
diff --git a/public/locales/nl/layout/element-selector/selector.json b/public/locales/nl/layout/element-selector/selector.json
new file mode 100644
index 000000000..8bbff652a
--- /dev/null
+++ b/public/locales/nl/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Nieuwe tegel toevoegen",
+ "text": "Tegels zijn het belangrijkste element van Homarr. Ze worden gebruikt om je apps en andere informatie weer te geven. Je kunt zoveel tegels toevoegen als je wilt."
+ },
+ "widgetDescription": "Widgets werken samen met uw apps, om u meer controle over uw toepassingen te geven. Ze vereisen meestal extra configuratie voor gebruik.",
+ "goBack": "Ga terug naar de vorige stap",
+ "actionIcon": {
+ "tooltip": "Tegel toevoegen"
+ }
+}
diff --git a/public/locales/nl/layout/header/actions/toggle-edit-mode.json b/public/locales/nl/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..e0a996780
--- /dev/null
+++ b/public/locales/nl/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "In de bewerkingsmodus kunt u tegels aanpassen en apps configureren. Wijzigingen worden niet opgeslagen totdat u de bewerkingsmodus afsluit.",
+ "button": {
+ "disabled": "Ga naar de bewerkingsmodus",
+ "enabled": "Afsluiten en opslaan"
+ },
+ "popover": {
+ "title": "Bewerkingsmodus is ingeschakeld voor de <1>{{size}}1> grootte",
+ "text": "U kunt uw apps nu aanpassen en configureren. Wijzigingen zijn niet opgeslagen totdat u de bewerkingsmodus verlaat"
+ },
+ "screenSizes": {
+ "small": "klein",
+ "medium": "gemiddeld",
+ "large": "groot"
+ }
+}
diff --git a/public/locales/nl/layout/mobile/drawer.json b/public/locales/nl/layout/mobile/drawer.json
new file mode 100644
index 000000000..dc3c40d98
--- /dev/null
+++ b/public/locales/nl/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} zijbalk"
+}
diff --git a/public/locales/nl/layout/modals/about.json b/public/locales/nl/layout/modals/about.json
new file mode 100644
index 000000000..42e91ade2
--- /dev/null
+++ b/public/locales/nl/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr is een sterk , modern dashboard dat al je apps en diensten binnen handbereik brengt. Met Homarr heb je toegang tot en controle over alles op één handige locatie. Homarr integreert naadloos met de apps die je hebt toegevoegd en geeft je waardevolle informatie en volledige controle. Installatie is een fluitje van een cent en Homarr ondersteunt een breed scala aan implementatiemethoden.",
+ "i18n": "\"I18n translation namespaces\" geladen",
+ "locales": "\"I18n locales\" geconfigureerd",
+ "contact": "Problemen of vragen? Neem contact met ons op!",
+ "addToDashboard": "Aan dashboard toevoegen"
+}
diff --git a/public/locales/nl/layout/modals/add-app.json b/public/locales/nl/layout/modals/add-app.json
new file mode 100644
index 000000000..3bad4836a
--- /dev/null
+++ b/public/locales/nl/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Algemeen",
+ "behaviour": "Gedrag",
+ "network": "Netwerk",
+ "appearance": "Opmaak",
+ "integration": "Integratie"
+ },
+ "general": {
+ "appname": {
+ "label": "App naam",
+ "description": "Wordt gebruikt om de app op het dashboard weer te geven."
+ },
+ "internalAddress": {
+ "label": "Intern adres",
+ "description": "Intern IP-adres van de app."
+ },
+ "externalAddress": {
+ "label": "Extern adres",
+ "description": "URL die wordt geopend wanneer op de app wordt geklikt."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Open in nieuw tabblad",
+ "description": "Open de app in een nieuw tabblad in plaats van de huidige."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Status checker",
+ "description": "Controleert of uw app online is met een eenvoudig HTTP(S)-verzoek."
+ },
+ "statusCodes": {
+ "label": "HTTP-statuscodes",
+ "description": "De HTTP-statuscodes die als online worden beschouwd."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "App icoon",
+ "description": "Het pictogram dat op het dashboard wordt weergegeven."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Integratie configuratie",
+ "description": "De integratieconfiguratie die zal worden gebruikt om verbinding te maken met uw app.",
+ "placeholder": "Selecteer een integratie",
+ "defined": "Gedefinieerd",
+ "undefined": "Ongedefinieerd",
+ "public": "Openbaar",
+ "private": "Privé",
+ "explanationPrivate": "Een geheim wordt slechts eenmaal naar de server gestuurd. Zodra uw browser de pagina heeft ververst, wordt het nooit meer verzonden.",
+ "explanationPublic": "Een publiek geheim wordt altijd naar de client gestuurd en is toegankelijk via de API. Het mag geen vertrouwelijke waarden bevatten zoals gebruikersnamen, wachtwoorden, tokens, certificaten en dergelijke!"
+ },
+ "secrets": {
+ "description": "Om een geheim bij te werken, voert u een waarde in en klikt u op de knop Opslaan. Om een geheim te verwijderen, gebruikt u de knop wissen.",
+ "warning": "Uw referenties fungeren als de toegang tot uw integraties en u zou deze nooit hoeven delen met iemand anders. Het Homarr team zal nooit om inloggegevens vragen. Zorg ervoor dat uw geheimen veilig opslaat en beheert .",
+ "clear": "Wis geheim",
+ "save": "Geheim opslaan",
+ "update": "Update geheim"
+ }
+ },
+ "validation": {
+ "popover": "Uw formulier bevat ongeldige gegevens. Daarom kan het niet worden opgeslagen. Los alle problemen op en klik opnieuw op deze knop om uw wijzigingen op te slaan"
+ }
+}
diff --git a/public/locales/nl/layout/modals/change-position.json b/public/locales/nl/layout/modals/change-position.json
new file mode 100644
index 000000000..bfd2993ea
--- /dev/null
+++ b/public/locales/nl/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X-as positie",
+ "width": "Breedte",
+ "height": "Hoogte",
+ "yPosition": "Y-as positie",
+ "zeroOrHigher": "0 of hoger",
+ "betweenXandY": "Tussen {{min}} en {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/nl/layout/screen-sizes.json b/public/locales/nl/layout/screen-sizes.json
new file mode 100644
index 000000000..27d38a1fa
--- /dev/null
+++ b/public/locales/nl/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "klein",
+ "medium": "gemiddeld",
+ "large": "groot"
+ }
+}
diff --git a/public/locales/nl/layout/tools.json b/public/locales/nl/layout/tools.json
new file mode 100644
index 000000000..a4b260760
--- /dev/null
+++ b/public/locales/nl/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Je hebt momenteel geen tools"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Zoek naar iconen...",
+ "searchLimitationTitle": "Zoeken is beperkt tot {{max}} iconen",
+ "searchLimitationMessage": "Om alles snel en vlot te laten verlopen, is de zoekopdracht beperkt tot {{max}} iconen. Gebruik het zoekvak om meer iconen te vinden"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/nl/modules/calendar.json b/public/locales/nl/modules/calendar.json
index ea966125a..4d355b377 100644
--- a/public/locales/nl/modules/calendar.json
+++ b/public/locales/nl/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Kalender",
- "description": "Een kalender module voor het weergeven van aankomende releases. Deze module werkt samen met de Sonarr en Radarr API.",
+ "description": "Toont een kalender met komende releases, van ondersteunde integraties.",
"settings": {
+ "title": "Instellingen voor Kalender widget",
"sundayStart": {
"label": "Begin de week op zondag"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr release type"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/common.json b/public/locales/nl/modules/common.json
index 4b90a1518..29ea4b19f 100644
--- a/public/locales/nl/modules/common.json
+++ b/public/locales/nl/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Instellingen"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "Ongebruikte parameter in configuratie gedetecteerd {{key}}. Homarr kan deze parameter niet interpreteren en gebruiken. Maak een back-up van uw configuratie en corrigeer uw configuratie om onverwacht gedrag te voorkomen."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/dashdot.json b/public/locales/nl/modules/dashdot.json
index a95922fad..c301c6f33 100644
--- a/public/locales/nl/modules/dashdot.json
+++ b/public/locales/nl/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Een module voor het weergeven van de grafieken van uw Dash. instance.",
+ "description": "Toont de grafieken van een externe Dash. instantie binnen Homarr.",
"settings": {
+ "title": "Instellingen voor Dash. widget",
"cpuMultiView": {
"label": "CPU Multi-Core Weergave"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/date.json b/public/locales/nl/modules/date.json
index 6d8989c4c..bc34adcf3 100644
--- a/public/locales/nl/modules/date.json
+++ b/public/locales/nl/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Datum",
- "description": "Toon de huidige tijd en datum in een kaart",
+ "name": "Datum en tijd",
+ "description": "Toont de huidige datum en tijd.",
"settings": {
+ "title": "Instellingen voor datum en tijd widget",
"display24HourFormat": {
"label": "Volledige tijd weergeven (24-uur)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/dlspeed.json b/public/locales/nl/modules/dlspeed.json
index 0b8ab9d43..559cd3e0a 100644
--- a/public/locales/nl/modules/dlspeed.json
+++ b/public/locales/nl/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Downloadsnelheid",
- "description": "Toon de huidige downloadsnelheid van ondersteunde diensten"
+ "description": "Toont de download- en uploadsnelheid van ondersteunde integraties."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/docker.json b/public/locales/nl/modules/docker.json
index ee2a048ee..fc60406be 100644
--- a/public/locales/nl/modules/docker.json
+++ b/public/locales/nl/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": ""
+ "description": "Hiermee kunt u gemakkelijk al uw Docker Containers zien en beheren."
},
"search": {
"placeholder": "Zoek op container of afbeeldingsnaam"
@@ -17,7 +17,7 @@
"portCollapse": "{{ports}} meer"
},
"states": {
- "running": "Draait",
+ "running": "Actief",
"created": "Aangemaakt",
"stopped": "Gestopt",
"unknown": "Onbekend"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Service toevoegen",
- "message": "Service toevoegen aan Homarr"
+ "title": "App toevoegen",
+ "message": "App aan Homarr toevoegen"
},
"restart": {
"title": "Herstart"
@@ -38,7 +38,7 @@
"title": "Start"
},
"refreshData": {
- "title": "Gegevens verversen"
+ "title": "Gegevens vernieuwen"
},
"remove": {
"title": "Verwijder"
@@ -49,35 +49,35 @@
},
"actions": {
"start": {
- "start": "",
- "end": ""
+ "start": "Bezig met starten",
+ "end": "Gestart"
},
"stop": {
- "start": "",
+ "start": "Bezig met Stoppen",
"end": "Gestopt"
},
"restart": {
- "start": "",
- "end": ""
+ "start": "Bezig met herstarten",
+ "end": "Opnieuw gestart"
},
"remove": {
- "start": "",
- "end": ""
+ "start": "Bezig met verwijderen",
+ "end": "Verwijderd"
}
},
"errors": {
"integrationFailed": {
"title": "Docker integratie mislukt",
- "message": "Bent u vergeten de doktersocket te koppelen?"
+ "message": "Bent u vergeten de dokter-socket te koppelen?"
},
"unknownError": {
"title": "Er is een fout opgetreden"
},
"oneServiceAtATime": {
- "title": "Voeg alstublieft slechts één dienst per keer toe!"
+ "title": "Voeg slechts één app of dienst per keer toe!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/overseerr.json b/public/locales/nl/modules/overseerr.json
index d185f5f6e..a8b92c2aa 100644
--- a/public/locales/nl/modules/overseerr.json
+++ b/public/locales/nl/modules/overseerr.json
@@ -1,12 +1,12 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Staat je toe om media van Overseerr/Jellyseerr te zoeken en toe te voegen"
+ "description": "Hiermee kunt u media zoeken en toevoegen vanuit Overseerr of Jellyseerr."
},
"popup": {
"item": {
"buttons": {
- "askFor": "Vraag om {{title}}",
+ "askFor": "Vraag naar {{title}}",
"cancel": "Annuleer",
"request": "Aanvraag"
},
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/ping.json b/public/locales/nl/modules/ping.json
index cc57fbe46..6801f9056 100644
--- a/public/locales/nl/modules/ping.json
+++ b/public/locales/nl/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Hiermee kunt u controleren of de service up is of een specifieke HTTP-statuscode retourneert."
+ "description": "Toont een statusindicator op basis van de HTTP-responscode van een gegeven URL."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
- "loading": "Laden..."
+ "loading": "Bezig met laden..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/search.json b/public/locales/nl/modules/search.json
index a1fec76b9..dae9a0474 100644
--- a/public/locales/nl/modules/search.json
+++ b/public/locales/nl/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Zoekbalk",
- "description": "Zoekbalk om te zoeken op het web, Youtube, Torrents of Overseerr"
+ "description": "Een zoekbalk waarmee u uw aangepaste zoekmachine, YouTube en ondersteunde integraties kunt doorzoeken."
},
"input": {
- "placeholder": "Doorzoek het web..."
+ "placeholder": "Zoek op het web..."
},
- "switched-to": "",
+ "switched-to": "Omgeschakeld naar",
"searchEngines": {
"search": {
- "name": "",
- "description": ""
+ "name": "Web",
+ "description": "Zoeken..."
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "YouTube",
+ "description": "Zoek op YouTube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrents",
+ "description": "Zoek naar Torrents"
},
"overseerr": {
"name": "Overseerr",
- "description": ""
+ "description": "Zoek naar Films en TV-series op Overseerr"
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "U kunt de zoekbalk selecteren met de sneltoets ",
+ "switchedSearchEngine": "Omgeschakeld naar zoeken met {{searchEngine}}"
+}
diff --git a/public/locales/nl/modules/torrents-status.json b/public/locales/nl/modules/torrents-status.json
index 255e12322..48a864a97 100644
--- a/public/locales/nl/modules/torrents-status.json
+++ b/public/locales/nl/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Toon de huidige downloadsnelheid van ondersteunde diensten",
+ "description": "Toont een lijst met torrents van ondersteunde Torrent-clients.",
"settings": {
- "hideComplete": {
- "label": "Verberg voltooide torrents"
+ "title": "Instellingen voor Torrent widget",
+ "refreshInterval": {
+ "label": "Verversingsinterval (in seconden)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Afgeronde torrents weergeven"
+ },
+ "displayStaleTorrents": {
+ "label": "Toon verouderde torrents"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Geen ondersteunde download clients gevonden!",
- "text": "Voeg een downloadservice toe om uw huidige downloads weer te geven"
+ "title": "Geen ondersteunde Torrent clients gevonden!",
+ "text": "Voeg een ondersteunde Torrent-client toe om je huidige downloads te bekijken"
+ },
+ "generic": {
+ "title": "Onbekende fout opgetreden",
+ "text": "Homarr kon niet communiceren met je Torrent-clients. Controleer je configuratie"
}
+ },
+ "loading": {
+ "title": "Bezig met laden..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/modules/usenet.json b/public/locales/nl/modules/usenet.json
index 91c7bd8d5..1761b9e54 100644
--- a/public/locales/nl/modules/usenet.json
+++ b/public/locales/nl/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
- "description": ""
+ "name": "Usenet",
+ "description": "Maakt het mogelijk om je Usenet installatie te bekijken en te beheren."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Geen ondersteunde download clients gevonden!",
- "text": "Voeg een downloadservice toe om uw huidige downloads weer te geven"
+ "text": "Voeg een ondersteunde Usenet Download Client toe om uw huidige downloads te bekijken"
}
}
},
@@ -42,7 +42,7 @@
"empty": "Leeg",
"error": {
"title": "Fout",
- "message": "Fout bij laden geschiedenis"
+ "message": "Fout bij het laden van de geschiedenis"
},
"paused": "Gepauzeerd"
}
diff --git a/public/locales/nl/modules/weather.json b/public/locales/nl/modules/weather.json
index 25ca62188..8f3a5320c 100644
--- a/public/locales/nl/modules/weather.json
+++ b/public/locales/nl/modules/weather.json
@@ -1,10 +1,11 @@
{
"descriptor": {
"name": "Weer",
- "description": "Zoek het actuele weer op uw locatie",
+ "description": "Toont de huidige weersinformatie van een ingestelde locatie.",
"settings": {
+ "title": "Instellingen voor weer widget",
"displayInFahrenheit": {
- "label": "Toon in Fahrenheit"
+ "label": "Weergeven in Fahrenheit"
},
"location": {
"label": "Weerslocatie"
@@ -29,4 +30,4 @@
"unknown": "Onbekend"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/settings/common.json b/public/locales/nl/settings/common.json
index 535908947..68f6bd5cd 100644
--- a/public/locales/nl/settings/common.json
+++ b/public/locales/nl/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Aanpassingen"
},
"tips": {
- "configTip": "Upload uw configuratiebestand door het op de pagina te slepen!"
+ "configTip": "Upload uw configuratiebestand door het naar de pagina te slepen!"
},
"credits": {
"madeWithLove": "Gemaakt met ❤️ door @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Groei raster (neem alle ruimte)",
+ "layout": {
+ "title": "Dashboard lay-out",
+ "main": "Algemeen",
+ "sidebar": "Zijbalk",
+ "cannotturnoff": "Kan niet worden uitgeschakeld",
+ "dashboardlayout": "Dashboard lay-out",
+ "enablersidebar": "Rechter zijbalk inschakelen",
+ "enablelsidebar": "Linker zijbalk inschakelen",
+ "enablesearchbar": "Zoekbalk inschakelen",
+ "enabledocker": "Docker-integratie inschakelen",
+ "enableping": "Pings inschakelen",
+ "enablelsidebardesc": "Optioneel. Kan alleen worden gebruikt voor apps en integraties",
+ "enablersidebardesc": "Optioneel. Kan alleen worden gebruikt voor apps en integraties"
+ }
+}
diff --git a/public/locales/nl/settings/customization/app-width.json b/public/locales/nl/settings/customization/app-width.json
index ee95be0dc..01afead26 100644
--- a/public/locales/nl/settings/customization/app-width.json
+++ b/public/locales/nl/settings/customization/app-width.json
@@ -1,3 +1,3 @@
{
- "label": "Applicatie breedte"
+ "label": "App Breedte"
}
\ No newline at end of file
diff --git a/public/locales/nl/settings/customization/page-appearance.json b/public/locales/nl/settings/customization/page-appearance.json
index 6694ed701..626757ae9 100644
--- a/public/locales/nl/settings/customization/page-appearance.json
+++ b/public/locales/nl/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Paginatitel",
- "placeholder": "Homarr 🦞"
+ "label": "Paginatitel"
+ },
+ "metaTitle": {
+ "label": "Meta Titel"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Eigen CSS",
- "placeholder": "Eigen CSS wordt als laatste uitgevoerd"
+ "placeholder": "Eigen CSS wordt als laatste toegepast"
},
"buttons": {
"submit": "Indienen"
diff --git a/public/locales/nl/settings/general/color-schema.json b/public/locales/nl/settings/general/color-schema.json
index a74b1f119..c66b90890 100644
--- a/public/locales/nl/settings/general/color-schema.json
+++ b/public/locales/nl/settings/general/color-schema.json
@@ -1,3 +1,3 @@
{
- "label": "Overschakelen naar {{scheme}} modus"
+ "label": "Schakel over naar {{scheme}} modus"
}
\ No newline at end of file
diff --git a/public/locales/nl/settings/general/config-changer.json b/public/locales/nl/settings/general/config-changer.json
index 04e3cf6cf..29875ed0c 100644
--- a/public/locales/nl/settings/general/config-changer.json
+++ b/public/locales/nl/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Configuratie lader"
+ "label": "Configuratiewisselaar",
+ "description": "",
+ "loadingNew": "Uw configuratie wordt geladen...",
+ "pleaseWait": "Wacht tot uw nieuwe configuratie is geladen!"
},
"modal": {
- "title": "Kies de naam van uw nieuwe configuratie",
- "form": {
- "configName": {
- "label": "Configuratie naam",
- "placeholder": "Uw nieuwe configuratienaam"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Bevestig"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Configuratie opgeslagen",
- "message": "Configuratie opgeslagen als {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,26 +55,32 @@
"deleteFailed": {
"title": "Configuratie verwijderen mislukt",
"message": "Configuratie verwijderen mislukt"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Standaardconfiguratie kan niet worden verwijderd",
+ "message": "Configuratie is niet verwijderd uit het bestandssysteem"
}
}
},
- "saveCopy": "Sla op als een kopie"
+ "saveCopy": "Kopie opslaan"
},
"dropzone": {
"notifications": {
"invalidConfig": {
"title": "Kan configuratie niet laden",
- "message": "Kon uw configuratie niet laden. Ongeldig JSON formaat."
+ "message": "Kon uw configuratie niet laden. Ongeldige JSON-indeling."
},
"loadedSuccessfully": {
"title": "Configuratie {{configName}} succesvol geladen"
}
},
"accept": {
- "text": "Sleep bestanden hierheen om een configuratie te uploaden. Alleen ondersteuning voor JSON."
+ "title": "Configuratie Upload",
+ "text": "Sleep bestanden hierheen om een configuratie te uploaden. Alleen ondersteuning voor JSON-bestanden."
},
"reject": {
- "text": "Dit bestandsformaat wordt niet ondersteund. Upload a.u.b. alleen JSON."
+ "title": "Drag & Drop Upload afgewezen",
+ "text": "Dit bestandsformaat wordt niet ondersteund. Upload alleen JSON-bestanden."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/settings/general/module-enabler.json b/public/locales/nl/settings/general/module-enabler.json
index ea042f81c..9e26dfeeb 100644
--- a/public/locales/nl/settings/general/module-enabler.json
+++ b/public/locales/nl/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Module Inschakeler"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/nl/settings/general/search-engine.json b/public/locales/nl/settings/general/search-engine.json
index 7ef1b89da..a52e4d39d 100644
--- a/public/locales/nl/settings/general/search-engine.json
+++ b/public/locales/nl/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Zoekmachine",
+ "configurationName": "Zoekmachine configuratie",
"tips": {
- "generalTip": "Gebruik de voorvoegsels !yt en !t voor uw zoekopdracht om te zoeken op YouTube of naar een Torrent respectievelijk.",
+ "generalTip": "Er zijn meerdere voorvoegsels die u kunt gebruiken! Door deze voor uw zoekopdracht toe te voegen worden de resultaten gefilterd. !s (Web), !t (Torrents), !y (YouTube), en !m (Media).",
"placeholderTip": "%s kan worden gebruikt als plaatshouder voor de query."
},
"customEngine": {
+ "title": "Aangepaste zoekmachine",
"label": "Query URL",
- "placeholder": "Eigen query URL"
+ "placeholder": "Aangepaste query-URL"
},
"searchNewTab": {
- "label": ""
+ "label": "Open zoekresultaten in nieuw tabblad"
+ },
+ "searchEnabled": {
+ "label": "Zoeken ingeschakeld"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/nl/settings/general/widget-positions.json b/public/locales/nl/settings/general/widget-positions.json
index e41e4243c..1bf3b56f3 100644
--- a/public/locales/nl/settings/general/widget-positions.json
+++ b/public/locales/nl/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Plaats widgets aan de linkerkant"
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/authentication/login.json b/public/locales/pl/authentication/login.json
index 17eb7f0a6..c17a87cbb 100644
--- a/public/locales/pl/authentication/login.json
+++ b/public/locales/pl/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Witaj ponownie!",
- "text": "Wprowadź hasło",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Twoje hasło jest sprawdzane..."
},
"correct": {
- "title": "Hasło poprawne, przekierowywanie..."
+ "title": ""
},
"wrong": {
- "title": "To hasło jest nieprawidłowe. Spróbuj ponownie."
+ "title": ""
}
}
}
diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json
index 96b2656e5..a1f900a40 100644
--- a/public/locales/pl/common.json
+++ b/public/locales/pl/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Zapisz"
+ "save": "",
+ "about": "",
+ "cancel": "Anuluj",
+ "close": "",
+ "delete": "Usuń",
+ "ok": "",
+ "edit": "Edytuj",
+ "version": "",
+ "changePosition": "",
+ "remove": "Usuń",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "Ustawienia",
+ "dangerZone": "Strefa zagrożenia"
+ },
+ "secrets": {
+ "apiKey": "",
+ "username": "Nazwa użytkownika",
+ "password": "Hasło"
},
"tip": "Wskazówka: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minut",
"hours": "godziny"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/pl/layout/add-service-app-shelf.json b/public/locales/pl/layout/add-service-app-shelf.json
index afc37e457..7c9cc75a9 100644
--- a/public/locales/pl/layout/add-service-app-shelf.json
+++ b/public/locales/pl/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Opcje zaawansowane",
"form": {
- "httpStatusCodes": {
- "label": "Kod statusu HTTP",
- "placeholder": "Wybierz prawidłowe kody statusu",
- "clearButtonLabel": "Wyczyść wybór",
- "nothingFound": "Nic nie znaleziono"
- },
"openServiceInNewTab": {
"label": "Otwórz usługę w nowej karcie"
},
diff --git a/public/locales/pl/layout/element-selector/selector.json b/public/locales/pl/layout/element-selector/selector.json
new file mode 100644
index 000000000..2a4f14e0d
--- /dev/null
+++ b/public/locales/pl/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "",
+ "actionIcon": {
+ "tooltip": ""
+ }
+}
diff --git a/public/locales/pl/layout/header/actions/toggle-edit-mode.json b/public/locales/pl/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..56487b8b1
--- /dev/null
+++ b/public/locales/pl/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "",
+ "enabled": ""
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/pl/layout/mobile/drawer.json b/public/locales/pl/layout/mobile/drawer.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/pl/layout/mobile/drawer.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/pl/layout/modals/about.json b/public/locales/pl/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/pl/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/pl/layout/modals/add-app.json b/public/locales/pl/layout/modals/add-app.json
new file mode 100644
index 000000000..308de00f4
--- /dev/null
+++ b/public/locales/pl/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "Sieć",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/pl/layout/modals/change-position.json b/public/locales/pl/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/pl/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/pl/layout/screen-sizes.json b/public/locales/pl/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/pl/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/pl/layout/tools.json b/public/locales/pl/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/pl/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/pl/modules/calendar.json b/public/locales/pl/modules/calendar.json
index f0c955d35..b8382f5de 100644
--- a/public/locales/pl/modules/calendar.json
+++ b/public/locales/pl/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Kalendarz",
- "description": "Moduł kalendarza do wyświetlania nadchodzących wydań. Współpracuje z API Sonarra i Radarra.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Rozpoczynaj tydzień od niedzieli"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/common.json b/public/locales/pl/modules/common.json
index 489e89f1c..1ccc1c899 100644
--- a/public/locales/pl/modules/common.json
+++ b/public/locales/pl/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Ustawienia"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/dashdot.json b/public/locales/pl/modules/dashdot.json
index eda73bf02..e01fdbc8d 100644
--- a/public/locales/pl/modules/dashdot.json
+++ b/public/locales/pl/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Moduł do wyświetlania wykresów z uruchomionej instancji Dash.",
+ "description": "",
"settings": {
+ "title": "",
"cpuMultiView": {
"label": "Widok wielordzeniowy procesora"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/date.json b/public/locales/pl/modules/date.json
index 80b581fc6..90537536f 100644
--- a/public/locales/pl/modules/date.json
+++ b/public/locales/pl/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Data",
- "description": "Pokaż bieżącą datę i godzinę w karcie",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Wyświetlaj pełną godzinę (24 godziny)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/dlspeed.json b/public/locales/pl/modules/dlspeed.json
index 92eda8be8..397287dc4 100644
--- a/public/locales/pl/modules/dlspeed.json
+++ b/public/locales/pl/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Prędkość pobierania",
- "description": "Pokaż bieżącą prędkość pobierania obsługiwanych usług"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/docker.json b/public/locales/pl/modules/docker.json
index 3e0ad09e5..0e15bd362 100644
--- a/public/locales/pl/modules/docker.json
+++ b/public/locales/pl/modules/docker.json
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Dodaj usługę",
- "message": "Dodaj usługę do Homarra"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Uruchom ponownie"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Integracja Dockera nie powiodła się",
- "message": "Czy zapomniałeś nie zamontować docker socket?"
+ "message": ""
},
"unknownError": {
"title": "Wystąpił błąd"
},
"oneServiceAtATime": {
- "title": "Nie dodawaj wielu usług naraz!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/overseerr.json b/public/locales/pl/modules/overseerr.json
index c2f615187..9921e9454 100644
--- a/public/locales/pl/modules/overseerr.json
+++ b/public/locales/pl/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Umożliwia wyszukiwanie i dodawanie mediów z Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Zaznacz sezony, które chcesz pobrać",
+ "caption": "",
"table": {
"header": {
"season": "Sezon",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/ping.json b/public/locales/pl/modules/ping.json
index 122670e96..71b18bd88 100644
--- a/public/locales/pl/modules/ping.json
+++ b/public/locales/pl/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Pozwala sprawdzić, czy usługa jest aktywna lub zwraca określony kod statusu HTTP."
+ "description": ""
},
"states": {
"online": "Dostępny {{response}}",
"offline": "Niedostępny {{response}}",
"loading": "Ładowanie..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/search.json b/public/locales/pl/modules/search.json
index 2ea5ddfa4..be4b6e466 100644
--- a/public/locales/pl/modules/search.json
+++ b/public/locales/pl/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Pasek wyszukiwania",
- "description": "Pasek wyszukiwania pozwala przeszukiwać strony internetowe, YouTube, Torrenty i Overseerr"
+ "description": ""
},
"input": {
"placeholder": "Szukaj w internecie..."
@@ -27,4 +27,4 @@
},
"tip": "",
"switchedSearchEngine": ""
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/torrents-status.json b/public/locales/pl/modules/torrents-status.json
index 984a59666..5e973604b 100644
--- a/public/locales/pl/modules/torrents-status.json
+++ b/public/locales/pl/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Torrent",
- "description": "Pokaż bieżącą prędkość pobierania obsługiwanych usług",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Ukryj ukończone torrenty"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Nie znaleziono obsługiwanych klientów pobierania!",
- "text": "Dodaj usługę pobierania, aby wyświetlić aktualnie pobierane pliki"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Ładowanie..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/modules/usenet.json b/public/locales/pl/modules/usenet.json
index 64473ac0c..e4bc613b3 100644
--- a/public/locales/pl/modules/usenet.json
+++ b/public/locales/pl/modules/usenet.json
@@ -7,7 +7,7 @@
"errors": {
"noDownloadClients": {
"title": "Nie znaleziono obsługiwanych klientów pobierania!",
- "text": "Dodaj usługę pobierania, aby wyświetlić aktualnie pobierane pliki"
+ "text": ""
}
}
},
diff --git a/public/locales/pl/modules/weather.json b/public/locales/pl/modules/weather.json
index b4de96096..1258d7365 100644
--- a/public/locales/pl/modules/weather.json
+++ b/public/locales/pl/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Pogoda",
- "description": "Sprawdź aktualną pogodę w swojej lokalizacji",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Wyświetlaj w Fahrenheitach"
},
@@ -29,4 +30,4 @@
"unknown": "Nieznany"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/settings/common.json b/public/locales/pl/settings/common.json
index 7e9c62714..61bc5b718 100644
--- a/public/locales/pl/settings/common.json
+++ b/public/locales/pl/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Personalizacja"
},
"tips": {
- "configTip": "Prześlij swój plik konfiguracyjny, przeciągając go i upuszczając na stronę!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "Wykonane z ❤️ przez @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "",
+ "layout": {
+ "title": "",
+ "main": "",
+ "sidebar": "",
+ "cannotturnoff": "",
+ "dashboardlayout": "",
+ "enablersidebar": "",
+ "enablelsidebar": "",
+ "enablesearchbar": "",
+ "enabledocker": "",
+ "enableping": "",
+ "enablelsidebardesc": "",
+ "enablersidebardesc": ""
+ }
+}
diff --git a/public/locales/pl/settings/customization/page-appearance.json b/public/locales/pl/settings/customization/page-appearance.json
index 7880d6595..a3663a791 100644
--- a/public/locales/pl/settings/customization/page-appearance.json
+++ b/public/locales/pl/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Tytuł strony",
- "placeholder": "Homarr 🦞"
+ "label": "Tytuł strony"
+ },
+ "metaTitle": {
+ "label": ""
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Niestandardowy CSS",
- "placeholder": "Niestandardowy CSS zostanie wykonany jako ostatni"
+ "placeholder": ""
},
"buttons": {
"submit": ""
diff --git a/public/locales/pl/settings/general/config-changer.json b/public/locales/pl/settings/general/config-changer.json
index a14a99214..5e692e232 100644
--- a/public/locales/pl/settings/general/config-changer.json
+++ b/public/locales/pl/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Wybór konfiguracji"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "Wybierz nazwę nowej konfiguracji",
- "form": {
- "configName": {
- "label": "Nazwa konfiguracji",
- "placeholder": "Twoja nowa nazwa konfiguracji"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Potwierdź"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Konfiguracja zapisana",
- "message": "Konfiguracja zapisana jako {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Nie udało się usunąć konfiguracji",
"message": "Nie udało się usunąć konfiguracji"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Przeciągnij pliki tutaj, aby przesłać konfigurację. Wsparcie tylko dla plików JSON."
+ "title": "",
+ "text": ""
},
"reject": {
- "text": "Ten format pliku nie jest obsługiwany. Przesyłaj tylko pliki JSON."
+ "title": "",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/settings/general/module-enabler.json b/public/locales/pl/settings/general/module-enabler.json
index 901523fad..9e26dfeeb 100644
--- a/public/locales/pl/settings/general/module-enabler.json
+++ b/public/locales/pl/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Wybór modułów"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/pl/settings/general/search-engine.json b/public/locales/pl/settings/general/search-engine.json
index 3365d9024..1a9fbffce 100644
--- a/public/locales/pl/settings/general/search-engine.json
+++ b/public/locales/pl/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Silnik wyszukiwania",
+ "configurationName": "",
"tips": {
- "generalTip": "Użyj prefiksów !yt i !t aby szukać plików torrent lub wyszukać zapytanie na YouTube.",
+ "generalTip": "",
"placeholderTip": "%s może być użyte jako symbol zastępczy dla zapytania."
},
"customEngine": {
+ "title": "",
"label": "Adres URL zapytania",
"placeholder": "Własny adres URL zapytania"
},
"searchNewTab": {
"label": ""
+ },
+ "searchEnabled": {
+ "label": ""
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pl/settings/general/widget-positions.json b/public/locales/pl/settings/general/widget-positions.json
index 62039ed15..0967ef424 100644
--- a/public/locales/pl/settings/general/widget-positions.json
+++ b/public/locales/pl/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Umieść widżety po lewej stronie"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/pt/authentication/login.json b/public/locales/pt/authentication/login.json
index 30f543e65..266e00475 100644
--- a/public/locales/pt/authentication/login.json
+++ b/public/locales/pt/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Bem-vindo de volta!",
- "text": "Por favor digite a senha",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Sua senha está sendo verificada..."
},
"correct": {
- "title": "Senha correta, redirecionando você..."
+ "title": ""
},
"wrong": {
- "title": "A senha está errada, por favor tente novamente."
+ "title": ""
}
}
}
diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json
index 8ece5df7f..9121db8a5 100644
--- a/public/locales/pt/common.json
+++ b/public/locales/pt/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Salvar"
+ "save": "",
+ "about": "",
+ "cancel": "Cancelar",
+ "close": "",
+ "delete": "Apagar",
+ "ok": "",
+ "edit": "Editar",
+ "version": "",
+ "changePosition": "",
+ "remove": "Excluir",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "Configurações",
+ "dangerZone": "Zona de risco"
+ },
+ "secrets": {
+ "apiKey": "",
+ "username": "Usuário",
+ "password": "Senha"
},
"tip": "Dica: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minutos",
"hours": "horas"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/pt/layout/add-service-app-shelf.json b/public/locales/pt/layout/add-service-app-shelf.json
index 6c0e1a860..1abae73d2 100644
--- a/public/locales/pt/layout/add-service-app-shelf.json
+++ b/public/locales/pt/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Opções avançadas",
"form": {
- "httpStatusCodes": {
- "label": "Códigos de estado HTTP",
- "placeholder": "Selecione um código de estado válido",
- "clearButtonLabel": "Limpar seleção",
- "nothingFound": "Nenhum resultado encontrado"
- },
"openServiceInNewTab": {
"label": "Abrir links em uma nova aba"
},
diff --git a/public/locales/pt/layout/element-selector/selector.json b/public/locales/pt/layout/element-selector/selector.json
new file mode 100644
index 000000000..2a4f14e0d
--- /dev/null
+++ b/public/locales/pt/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "",
+ "actionIcon": {
+ "tooltip": ""
+ }
+}
diff --git a/public/locales/pt/layout/header/actions/toggle-edit-mode.json b/public/locales/pt/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..56487b8b1
--- /dev/null
+++ b/public/locales/pt/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "",
+ "enabled": ""
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/pt/layout/mobile/drawer.json b/public/locales/pt/layout/mobile/drawer.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/pt/layout/mobile/drawer.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/pt/layout/modals/about.json b/public/locales/pt/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/pt/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/pt/layout/modals/add-app.json b/public/locales/pt/layout/modals/add-app.json
new file mode 100644
index 000000000..cded7b9f7
--- /dev/null
+++ b/public/locales/pt/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "Rede",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/pt/layout/modals/change-position.json b/public/locales/pt/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/pt/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/pt/layout/screen-sizes.json b/public/locales/pt/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/pt/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/pt/layout/tools.json b/public/locales/pt/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/pt/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/pt/modules/calendar.json b/public/locales/pt/modules/calendar.json
index eaa7538d7..45ad9d7bc 100644
--- a/public/locales/pt/modules/calendar.json
+++ b/public/locales/pt/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Calendário",
- "description": "Um módulo de calendário para exibir os próximos lançamentos. Interage com o Sonarr e o Radarr API.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Comece a semana no Domingo"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/common.json b/public/locales/pt/modules/common.json
index ad436ac69..6fa228cf8 100644
--- a/public/locales/pt/modules/common.json
+++ b/public/locales/pt/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Configurações"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/dashdot.json b/public/locales/pt/modules/dashdot.json
index f9e47755a..3b945da26 100644
--- a/public/locales/pt/modules/dashdot.json
+++ b/public/locales/pt/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "",
- "description": "Um módulo para exibir os gráficos da sua instância Dash. em execução.",
+ "description": "",
"settings": {
+ "title": "",
"cpuMultiView": {
"label": "Visualização múltipla de CPU"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/date.json b/public/locales/pt/modules/date.json
index 078ba2720..cf5815ee0 100644
--- a/public/locales/pt/modules/date.json
+++ b/public/locales/pt/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Data",
- "description": "Mostrar a hora e a data atuais em um card",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Mostrar tempo (24 horas)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/dlspeed.json b/public/locales/pt/modules/dlspeed.json
index c93394dcc..719ec407e 100644
--- a/public/locales/pt/modules/dlspeed.json
+++ b/public/locales/pt/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Velocidade de Transferência",
- "description": "Mostrar a velocidade atual de download dos serviços suportados"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/docker.json b/public/locales/pt/modules/docker.json
index fee767599..58ad2754e 100644
--- a/public/locales/pt/modules/docker.json
+++ b/public/locales/pt/modules/docker.json
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Adicionar serviço",
- "message": "Adicionar serviço ao Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Reiniciar"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Integração com Docker falhou",
- "message": "Você esqueceu de montar o Docker socket?"
+ "message": ""
},
"unknownError": {
"title": "Ocorreu um erro"
},
"oneServiceAtATime": {
- "title": "Por favor, acrescente apenas um serviço de cada vez!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/overseerr.json b/public/locales/pt/modules/overseerr.json
index 1398f6dfa..aebe7e9ef 100644
--- a/public/locales/pt/modules/overseerr.json
+++ b/public/locales/pt/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Permite que você pesquise e adicione conteúdo do Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Marque as temporadas que você deseja que sejam baixadas",
+ "caption": "",
"table": {
"header": {
"season": "Temporada",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/ping.json b/public/locales/pt/modules/ping.json
index efc43c919..c2dc22556 100644
--- a/public/locales/pt/modules/ping.json
+++ b/public/locales/pt/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Latência",
- "description": "Permite verificar se o serviço está ativo ou retorna um código de estado HTTP específico."
+ "description": ""
},
"states": {
"online": "Online {{response}}",
"offline": "{{response}} off-line",
"loading": "Carregando..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/search.json b/public/locales/pt/modules/search.json
index 8669c1b5d..0784da6b1 100644
--- a/public/locales/pt/modules/search.json
+++ b/public/locales/pt/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Barra de pesquisa",
- "description": "Barra de pesquisa para pesquisar na ‘internet’, YouTube, torrents ou Overseerr"
+ "description": ""
},
"input": {
"placeholder": "Pesquisar na Internet..."
@@ -27,4 +27,4 @@
},
"tip": "",
"switchedSearchEngine": ""
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/torrents-status.json b/public/locales/pt/modules/torrents-status.json
index 4de386e43..06434c0d0 100644
--- a/public/locales/pt/modules/torrents-status.json
+++ b/public/locales/pt/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Torrent",
- "description": "Mostrar a velocidade atual de download dos serviços suportados",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Ocultar torrents completos"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Nenhum cliente de download suportado encontrado!",
- "text": "Adicione um serviço de download para ver seus downloads atuais"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Carregando..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/modules/usenet.json b/public/locales/pt/modules/usenet.json
index c975cb4e0..49c1b7219 100644
--- a/public/locales/pt/modules/usenet.json
+++ b/public/locales/pt/modules/usenet.json
@@ -7,7 +7,7 @@
"errors": {
"noDownloadClients": {
"title": "Nenhum cliente de download suportado encontrado!",
- "text": "Adicione um serviço de download para ver seus downloads atuais"
+ "text": ""
}
}
},
diff --git a/public/locales/pt/modules/weather.json b/public/locales/pt/modules/weather.json
index e843f1ea9..b263780c6 100644
--- a/public/locales/pt/modules/weather.json
+++ b/public/locales/pt/modules/weather.json
@@ -3,6 +3,7 @@
"name": "Tempo",
"description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Mostrar em Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Desconhecido"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/settings/common.json b/public/locales/pt/settings/common.json
index 09f60b792..16b3db5c8 100644
--- a/public/locales/pt/settings/common.json
+++ b/public/locales/pt/settings/common.json
@@ -11,5 +11,19 @@
"credits": {
"madeWithLove": "Feito com ❤️ por @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "",
+ "layout": {
+ "title": "",
+ "main": "",
+ "sidebar": "",
+ "cannotturnoff": "",
+ "dashboardlayout": "",
+ "enablersidebar": "",
+ "enablelsidebar": "",
+ "enablesearchbar": "",
+ "enabledocker": "",
+ "enableping": "",
+ "enablelsidebardesc": "",
+ "enablersidebardesc": ""
+ }
+}
diff --git a/public/locales/pt/settings/customization/page-appearance.json b/public/locales/pt/settings/customization/page-appearance.json
index 6b8c66ecf..b6b548ccb 100644
--- a/public/locales/pt/settings/customization/page-appearance.json
+++ b/public/locales/pt/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "",
- "placeholder": ""
+ "label": ""
+ },
+ "metaTitle": {
+ "label": ""
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "CSS Personalizado",
- "placeholder": "O CSS personalizado será executado por último"
+ "placeholder": ""
},
"buttons": {
"submit": "Enviar"
diff --git a/public/locales/pt/settings/general/config-changer.json b/public/locales/pt/settings/general/config-changer.json
index 793552a11..dc8f3ea4b 100644
--- a/public/locales/pt/settings/general/config-changer.json
+++ b/public/locales/pt/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Carregar configurações"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "",
- "form": {
- "configName": {
- "label": "",
- "placeholder": ""
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": ""
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "",
- "message": ""
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "",
"message": ""
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
+ "title": "",
"text": ""
},
"reject": {
+ "title": "",
"text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/settings/general/search-engine.json b/public/locales/pt/settings/general/search-engine.json
index df277ccef..9cb91483a 100644
--- a/public/locales/pt/settings/general/search-engine.json
+++ b/public/locales/pt/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "",
+ "configurationName": "",
"tips": {
"generalTip": "",
"placeholderTip": ""
},
"customEngine": {
+ "title": "",
"label": "",
"placeholder": ""
},
"searchNewTab": {
"label": ""
+ },
+ "searchEnabled": {
+ "label": ""
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/pt/settings/general/widget-positions.json b/public/locales/pt/settings/general/widget-positions.json
index 9e26dfeeb..0967ef424 100644
--- a/public/locales/pt/settings/general/widget-positions.json
+++ b/public/locales/pt/settings/general/widget-positions.json
@@ -1 +1 @@
-{}
\ No newline at end of file
+{}
diff --git a/public/locales/ru/authentication/login.json b/public/locales/ru/authentication/login.json
index b5b05dea9..4b8444033 100644
--- a/public/locales/ru/authentication/login.json
+++ b/public/locales/ru/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "С возвращением!",
- "text": "Пожалуйста, введите пароль",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Ваш пароль проверяется..."
},
"correct": {
- "title": "Пароль верный, перенаправляет вас..."
+ "title": ""
},
"wrong": {
- "title": "Пароль неверный, попробуйте еще раз."
+ "title": ""
}
}
}
diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json
index f5e2a45c2..394ab5ef6 100644
--- a/public/locales/ru/common.json
+++ b/public/locales/ru/common.json
@@ -1,11 +1,28 @@
{
- "actions": {
- "save": "Сохранить"
+ "save": "",
+ "about": "",
+ "cancel": "Отмена",
+ "close": "",
+ "delete": "Удалить",
+ "ok": "",
+ "edit": "Изменить",
+ "version": "",
+ "changePosition": "",
+ "remove": "Удалить",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "Настройки",
+ "dangerZone": "Опасная зона"
+ },
+ "secrets": {
+ "apiKey": "",
+ "username": "Имя пользователя",
+ "password": "Пароль"
},
"tip": "Совет: ",
"time": {
- "seconds": "секунды",
+ "seconds": "секунд",
"minutes": "минут",
- "hours": "часы"
+ "hours": "часов"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/ru/layout/add-service-app-shelf.json b/public/locales/ru/layout/add-service-app-shelf.json
index ae52c1cc7..d463835ac 100644
--- a/public/locales/ru/layout/add-service-app-shelf.json
+++ b/public/locales/ru/layout/add-service-app-shelf.json
@@ -15,17 +15,17 @@
"title": "Настройки",
"form": {
"serviceName": {
- "label": "Имя сервиса",
+ "label": "Название сервиса",
"placeholder": "Plex"
},
"iconUrl": {
"label": "URL-адрес иконки"
},
"serviceUrl": {
- "label": "Адрес сервиса"
+ "label": "URL-адрес сервиса"
},
"onClickUrl": {
- "label": "URL-адрес при нажатии"
+ "label": "URL-адрес по клику"
},
"serviceType": {
"label": "Тип сервиса",
@@ -36,7 +36,7 @@
"label": "Категория",
"placeholder": "Выберите категорию или создайте новую",
"nothingFound": "Ничего не найдено",
- "createLabel": "+ Добавить {{query}}"
+ "createLabel": "+ Создать {{query}}"
},
"integrations": {
"apiKey": {
@@ -69,7 +69,7 @@
"deluge": {
"password": {
"label": "Пароль",
- "placeholder": "Введите пароль",
+ "placeholder": "пароль",
"validation": {
"invalidPassword": "Неверный пароль"
}
@@ -101,7 +101,7 @@
},
"password": {
"label": "Пароль",
- "placeholder": "Введите пароль",
+ "placeholder": "пароль",
"validation": {
"invalidPassword": "Неверный пароль"
}
@@ -111,14 +111,8 @@
}
},
"advancedOptions": {
- "title": "Дополнительные параметры",
+ "title": "Дополнительные настройки",
"form": {
- "httpStatusCodes": {
- "label": "Коды состояния HTTP",
- "placeholder": "Выберите подходящие коды состояния",
- "clearButtonLabel": "Очистить выбранное",
- "nothingFound": "Ничего не найдено"
- },
"openServiceInNewTab": {
"label": "Открывать сервис в новой вкладке"
},
diff --git a/public/locales/ru/layout/element-selector/selector.json b/public/locales/ru/layout/element-selector/selector.json
new file mode 100644
index 000000000..2a4f14e0d
--- /dev/null
+++ b/public/locales/ru/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "",
+ "actionIcon": {
+ "tooltip": ""
+ }
+}
diff --git a/public/locales/ru/layout/header/actions/toggle-edit-mode.json b/public/locales/ru/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..56487b8b1
--- /dev/null
+++ b/public/locales/ru/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "",
+ "enabled": ""
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ru/layout/mobile/drawer.json b/public/locales/ru/layout/mobile/drawer.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/ru/layout/mobile/drawer.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/ru/layout/modals/about.json b/public/locales/ru/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/ru/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/ru/layout/modals/add-app.json b/public/locales/ru/layout/modals/add-app.json
new file mode 100644
index 000000000..2476a828a
--- /dev/null
+++ b/public/locales/ru/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "Сеть",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/ru/layout/modals/change-position.json b/public/locales/ru/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/ru/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/ru/layout/screen-sizes.json b/public/locales/ru/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/ru/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/ru/layout/tools.json b/public/locales/ru/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/ru/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/ru/modules/calendar.json b/public/locales/ru/modules/calendar.json
index b55010493..588df0f7a 100644
--- a/public/locales/ru/modules/calendar.json
+++ b/public/locales/ru/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Календарь",
- "description": "Модуль календаря для отображения предстоящих релизов. Интегрируется с Sonarr и Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Начинать неделю с воскресенья"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/common.json b/public/locales/ru/modules/common.json
index 127ac067c..eb23e76a3 100644
--- a/public/locales/ru/modules/common.json
+++ b/public/locales/ru/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Настройки"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/dashdot.json b/public/locales/ru/modules/dashdot.json
index b15988789..d9179c2ce 100644
--- a/public/locales/ru/modules/dashdot.json
+++ b/public/locales/ru/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Модуль для отображения графиков из Dash.",
+ "description": "",
"settings": {
+ "title": "",
"cpuMultiView": {
"label": "Многоядерный процессор Вид"
},
@@ -16,7 +17,7 @@
"label": "Графики"
},
"url": {
- "label": "Тире. URL"
+ "label": "URL-адрес Dash."
}
}
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/date.json b/public/locales/ru/modules/date.json
index cdb0f4a48..c0f48b3c5 100644
--- a/public/locales/ru/modules/date.json
+++ b/public/locales/ru/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Дата",
- "description": "Показать текущее время и дату в карточке",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Отображение полного времени (24 часа)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/dlspeed.json b/public/locales/ru/modules/dlspeed.json
index 516735d11..a437478c1 100644
--- a/public/locales/ru/modules/dlspeed.json
+++ b/public/locales/ru/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Скорость загрузки",
- "description": "Показать текущую скорость загрузки поддерживаемых сервисов"
+ "description": ""
},
"card": {
"table": {
@@ -10,7 +10,7 @@
"size": "Размер",
"download": "Вниз",
"upload": "Вверх",
- "estimatedTimeOfArrival": "РАСЧЕТНОЕ ВРЕМЯ ПРИБЫТИЯ",
+ "estimatedTimeOfArrival": "ETA",
"progress": "Прогресс"
},
"body": {
@@ -19,11 +19,11 @@
},
"lineChart": {
"title": "Текущая скорость загрузки",
- "download": "Скачать: {{download}}",
- "upload": "Загрузка: {{upload}}",
- "timeSpan": "{{seconds}} несколько секунд назад",
+ "download": "Загрузка: {{download}}",
+ "upload": "Отдача: {{upload}}",
+ "timeSpan": "{{seconds}} секунд назад",
"totalDownload": "Скачать: {{download}}/s",
- "totalUpload": "Загрузка: {{upload}}/s"
+ "totalUpload": "Отдача: {{upload}}/s"
},
"errors": {
"noDownloadClients": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/docker.json b/public/locales/ru/modules/docker.json
index 9177ac326..5f77a749d 100644
--- a/public/locales/ru/modules/docker.json
+++ b/public/locales/ru/modules/docker.json
@@ -1,23 +1,23 @@
{
"descriptor": {
"name": "Docker",
- "description": "Позволяет легко управлять контейнерами docker"
+ "description": ""
},
"search": {
- "placeholder": "Поиск по имени контейнера или изображения"
+ "placeholder": "Поиск по названию контейнера или образа"
},
"table": {
"header": {
"name": "Имя",
- "image": "Изображение",
+ "image": "Образ",
"ports": "Порты",
- "state": "Государство"
+ "state": "Состояние"
},
"body": {
- "portCollapse": "{{ports}} больше"
+ "portCollapse": "Ещё {{ports}}"
},
"states": {
- "running": "Бег",
+ "running": "Запущено",
"created": "Создано",
"stopped": "Остановлено",
"unknown": "Неизвестно"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Добавить сервис",
- "message": "Добавить услугу в Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Перезапустить"
@@ -35,7 +35,7 @@
"title": "Остановить"
},
"start": {
- "title": "Начало"
+ "title": "Запустить"
},
"refreshData": {
"title": "Обновить данные"
@@ -49,11 +49,11 @@
},
"actions": {
"start": {
- "start": "Начало",
- "end": "Начало"
+ "start": "Запуск",
+ "end": "Запущен"
},
"stop": {
- "start": "Остановка",
+ "start": "Останавливается",
"end": "Остановлено"
},
"restart": {
@@ -61,23 +61,23 @@
"end": "Перезапущен"
},
"remove": {
- "start": "Удаление",
+ "start": "Удаляется",
"end": "Удалено"
}
},
"errors": {
"integrationFailed": {
"title": "Сбой интеграции Docker",
- "message": "Вы забыли смонтировать сокет docker?"
+ "message": ""
},
"unknownError": {
"title": "Произошла ошибка"
},
"oneServiceAtATime": {
- "title": "Пожалуйста, добавляйте только одну услугу за один раз!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/overseerr.json b/public/locales/ru/modules/overseerr.json
index e13e28541..2dc62f333 100644
--- a/public/locales/ru/modules/overseerr.json
+++ b/public/locales/ru/modules/overseerr.json
@@ -1,12 +1,12 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Позволяет искать и добавлять медиафайлы из Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
"buttons": {
- "askFor": "Спросите на {{title}}",
+ "askFor": "Запросить {{title}}",
"cancel": "Отмена",
"request": "Запрос"
},
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Отметьте сезоны, которые вы хотите загрузить",
+ "caption": "",
"table": {
"header": {
"season": "Сезон",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/ping.json b/public/locales/ru/modules/ping.json
index f81fc4737..a8d2d635b 100644
--- a/public/locales/ru/modules/ping.json
+++ b/public/locales/ru/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Пинг",
- "description": "Позволяет проверить, работает ли служба или возвращает определенный код состояния HTTP."
+ "description": ""
},
"states": {
"online": "Онлайн {{response}}",
- "offline": "Offline {{response}}",
+ "offline": "Оффлайн {{response}}",
"loading": "Загрузка..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/search.json b/public/locales/ru/modules/search.json
index 33ec9789b..5c78264a5 100644
--- a/public/locales/ru/modules/search.json
+++ b/public/locales/ru/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
- "name": "Поиск",
- "description": "Строка поиска для поиска в Интернете, Youtube, Torrents или Overseerr"
+ "name": "Панель поиска",
+ "description": ""
},
"input": {
"placeholder": "Искать в интернете..."
},
- "switched-to": "",
+ "switched-to": "Переключен на",
"searchEngines": {
"search": {
- "name": "",
+ "name": "Web",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "Youtube",
+ "description": "Поиск на Youtube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Торренты",
+ "description": "Поиск по торрентам"
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "Вы можете выбрать строку поиска с помощью сочетания клавиш ",
+ "switchedSearchEngine": "Переключился на поиск с помощью {{searchEngine}}"
+}
diff --git a/public/locales/ru/modules/torrents-status.json b/public/locales/ru/modules/torrents-status.json
index 3448c5a20..e3916904f 100644
--- a/public/locales/ru/modules/torrents-status.json
+++ b/public/locales/ru/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Торрент",
- "description": "Показать текущую скорость загрузки поддерживаемых сервисов",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Скрыть завершенные торренты"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -15,7 +22,7 @@
"size": "Размер",
"download": "Вниз",
"upload": "Вверх",
- "estimatedTimeOfArrival": "РАСЧЕТНОЕ ВРЕМЯ ПРИБЫТИЯ",
+ "estimatedTimeOfArrival": "ETA",
"progress": "Прогресс"
},
"body": {
@@ -24,17 +31,24 @@
},
"lineChart": {
"title": "Текущая скорость загрузки",
- "download": "Скачать: {{download}}",
- "upload": "Загрузка: {{upload}}",
- "timeSpan": "{{seconds}} несколько секунд назад",
+ "download": "Загрузка: {{download}}",
+ "upload": "Отдача: {{upload}}",
+ "timeSpan": "{{seconds}} секунд назад",
"totalDownload": "Скачать: {{download}}/s",
- "totalUpload": "Загрузка: {{upload}}/s"
+ "totalUpload": "Отдача: {{upload}}/s"
},
"errors": {
"noDownloadClients": {
- "title": "Не найдено ни одного поддерживаемого клиента загрузки!",
- "text": "Добавьте службу загрузки для просмотра текущих загрузок"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Загрузка..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/modules/usenet.json b/public/locales/ru/modules/usenet.json
index 160de8c4f..d7a5d17e0 100644
--- a/public/locales/ru/modules/usenet.json
+++ b/public/locales/ru/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
+ "name": "Usenet",
"description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Не найдено ни одного поддерживаемого клиента загрузки!",
- "text": "Добавьте службу загрузки для просмотра текущих загрузок"
+ "text": ""
}
}
},
@@ -23,10 +23,10 @@
"header": {
"name": "Имя",
"size": "Размер",
- "eta": "РАСЧЕТНОЕ ВРЕМЯ ПРИБЫТИЯ",
+ "eta": "ETA",
"progress": "Прогресс"
},
- "empty": "Пустой",
+ "empty": "Пусто",
"error": {
"title": "Ошибка",
"message": "Произошла ошибка"
@@ -39,7 +39,7 @@
"size": "Размер",
"duration": "Продолжительность"
},
- "empty": "Пустой",
+ "empty": "Пусто",
"error": {
"title": "Ошибка",
"message": "Ошибка загрузки истории"
diff --git a/public/locales/ru/modules/weather.json b/public/locales/ru/modules/weather.json
index 38b177c57..b4a62db76 100644
--- a/public/locales/ru/modules/weather.json
+++ b/public/locales/ru/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Погода",
- "description": "Узнайте текущую погоду в вашем регионе",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Отображение в градусах Фаренгейта"
},
@@ -14,19 +15,19 @@
"card": {
"weatherDescriptions": {
"clear": "Ясно",
- "mainlyClear": "В основном ясно",
+ "mainlyClear": "Преимущественно ясно",
"fog": "Туман",
- "drizzle": "Мелкий дождь",
+ "drizzle": "Небольшой дождь",
"freezingDrizzle": "Изморозь",
"rain": "Дождь",
"freezingRain": "Ледяной дождь",
"snowFall": "Снегопад",
- "snowGrains": "Снежные зерна",
- "rainShowers": "Ливневый дождь",
- "snowShowers": "Пурга",
+ "snowGrains": "Снежные зёрна",
+ "rainShowers": "Ливень",
+ "snowShowers": "Снегопад",
"thunderstorm": "Гроза",
"thunderstormWithHail": "Гроза с градом",
"unknown": "Неизвестно"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/settings/common.json b/public/locales/ru/settings/common.json
index c6fd9f5b5..df63d172c 100644
--- a/public/locales/ru/settings/common.json
+++ b/public/locales/ru/settings/common.json
@@ -2,14 +2,28 @@
"title": "Настройки",
"tooltip": "Настройки",
"tabs": {
- "common": "Общий",
- "customizations": "Персонализации"
+ "common": "Общие",
+ "customizations": "Оформление"
},
"tips": {
- "configTip": "Загрузите файл конфигурации, перетащив его на страницу!"
+ "configTip": ""
},
"credits": {
- "madeWithLove": "Сделано с ❤️ по @."
+ "madeWithLove": "Сделано с ❤️ от @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Увеличение сетки (занимает все пространство)",
+ "layout": {
+ "title": "",
+ "main": "",
+ "sidebar": "",
+ "cannotturnoff": "",
+ "dashboardlayout": "",
+ "enablersidebar": "",
+ "enablelsidebar": "",
+ "enablesearchbar": "",
+ "enabledocker": "",
+ "enableping": "",
+ "enablelsidebardesc": "",
+ "enablersidebardesc": ""
+ }
+}
diff --git a/public/locales/ru/settings/customization/page-appearance.json b/public/locales/ru/settings/customization/page-appearance.json
index 339539b2c..3876fec57 100644
--- a/public/locales/ru/settings/customization/page-appearance.json
+++ b/public/locales/ru/settings/customization/page-appearance.json
@@ -1,22 +1,24 @@
{
"pageTitle": {
- "label": "Название страницы",
- "placeholder": "Хомарр 🦞"
+ "label": "Название страницы"
+ },
+ "metaTitle": {
+ "label": ""
},
"logo": {
"label": "Логотип"
},
"favicon": {
- "label": "Фавикон"
+ "label": "Значок веб-страницы"
},
"background": {
- "label": "Справочная информация"
+ "label": "Фон"
},
"customCSS": {
"label": "Пользовательский CSS",
- "placeholder": "Пользовательский CSS будет выполняться в последнюю очередь"
+ "placeholder": ""
},
"buttons": {
- "submit": "Отправить"
+ "submit": "Подтвердить"
}
}
diff --git a/public/locales/ru/settings/general/config-changer.json b/public/locales/ru/settings/general/config-changer.json
index 3b1e12b53..b1a0e6eea 100644
--- a/public/locales/ru/settings/general/config-changer.json
+++ b/public/locales/ru/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Загрузчик конфигурации"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "Выберите имя новой конфигурации",
- "form": {
- "configName": {
- "label": "Имя конфигурации",
- "placeholder": "Имя вашей новой конфигурации"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Подтвердить"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Конфигурация сохранена",
- "message": "Конфигурация сохранена как {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Не удалось удалить конфигурацию",
"message": "Не удалось удалить конфигурацию"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Перетащите файлы сюда, чтобы загрузить конфигурацию. Поддерживается только JSON."
+ "title": "",
+ "text": ""
},
"reject": {
- "text": "Этот формат файла не поддерживается. Пожалуйста, загружайте только JSON."
+ "title": "",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/settings/general/module-enabler.json b/public/locales/ru/settings/general/module-enabler.json
index ddfa4ac43..9e26dfeeb 100644
--- a/public/locales/ru/settings/general/module-enabler.json
+++ b/public/locales/ru/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Усилитель модуля"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/ru/settings/general/search-engine.json b/public/locales/ru/settings/general/search-engine.json
index 3826fd131..ba72f58ba 100644
--- a/public/locales/ru/settings/general/search-engine.json
+++ b/public/locales/ru/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Поисковая система",
+ "configurationName": "",
"tips": {
- "generalTip": "Используйте префиксы !yt и !t перед вашим запросом для поиска на YouTube или для поиска торрента соответственно.",
+ "generalTip": "",
"placeholderTip": "%s можно использовать в качестве заполнителя для запроса."
},
"customEngine": {
+ "title": "",
"label": "URL запроса",
"placeholder": "URL пользовательского запроса"
},
"searchNewTab": {
"label": "Открыть результаты поиска в новой вкладке"
+ },
+ "searchEnabled": {
+ "label": ""
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/ru/settings/general/theme-selector.json b/public/locales/ru/settings/general/theme-selector.json
index 542b263b2..ef81ef433 100644
--- a/public/locales/ru/settings/general/theme-selector.json
+++ b/public/locales/ru/settings/general/theme-selector.json
@@ -1,3 +1,3 @@
{
- "label": "Переключитесь на режим {{theme}}"
+ "label": "Переключить на {{theme}} режим"
}
\ No newline at end of file
diff --git a/public/locales/ru/settings/general/widget-positions.json b/public/locales/ru/settings/general/widget-positions.json
index 44c3968fc..0967ef424 100644
--- a/public/locales/ru/settings/general/widget-positions.json
+++ b/public/locales/ru/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Расположите виджеты слева"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/sl/authentication/login.json b/public/locales/sl/authentication/login.json
index 66feacbf6..ed462c60b 100644
--- a/public/locales/sl/authentication/login.json
+++ b/public/locales/sl/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Dobrodošli nazaj!",
- "text": "Prosimo vnesite geslo",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Preverjamo vaše geslo..."
},
"correct": {
- "title": "Geslo je pravilno, preusmerjam..."
+ "title": ""
},
"wrong": {
- "title": "Geslo je napačno, poskusite znova."
+ "title": ""
}
}
}
diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json
index e7704e52b..a23a8f5e4 100644
--- a/public/locales/sl/common.json
+++ b/public/locales/sl/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Shrani"
+ "save": "Shrani",
+ "about": "O programu",
+ "cancel": "Prekliči",
+ "close": "Zapri",
+ "delete": "Izbriši",
+ "ok": "V redu",
+ "edit": "Uredi",
+ "version": "Različica",
+ "changePosition": "Spremeni položaj",
+ "remove": "Odstrani",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "Nastavitve",
+ "dangerZone": "Nevarno območje"
+ },
+ "secrets": {
+ "apiKey": "Api ključ",
+ "username": "Uporabniško ime",
+ "password": "Geslo"
},
"tip": "Nasvet: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minut",
"hours": "ur"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/sl/layout/add-service-app-shelf.json b/public/locales/sl/layout/add-service-app-shelf.json
index ffaa2f278..bad0a433a 100644
--- a/public/locales/sl/layout/add-service-app-shelf.json
+++ b/public/locales/sl/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Napredne nastavitve",
"form": {
- "httpStatusCodes": {
- "label": "HTTP statusne kode",
- "placeholder": "Izberite veljavne kode statusa",
- "clearButtonLabel": "Počisti izbiro",
- "nothingFound": "Brez rezultatov iskanja"
- },
"openServiceInNewTab": {
"label": "Odprite storitev v novem zavihku"
},
diff --git a/public/locales/sl/layout/element-selector/selector.json b/public/locales/sl/layout/element-selector/selector.json
new file mode 100644
index 000000000..2a4f14e0d
--- /dev/null
+++ b/public/locales/sl/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "",
+ "actionIcon": {
+ "tooltip": ""
+ }
+}
diff --git a/public/locales/sl/layout/header/actions/toggle-edit-mode.json b/public/locales/sl/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..56487b8b1
--- /dev/null
+++ b/public/locales/sl/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "",
+ "enabled": ""
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/sl/layout/mobile/drawer.json b/public/locales/sl/layout/mobile/drawer.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/sl/layout/mobile/drawer.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/sl/layout/modals/about.json b/public/locales/sl/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/sl/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/sl/layout/modals/add-app.json b/public/locales/sl/layout/modals/add-app.json
new file mode 100644
index 000000000..06a9ee408
--- /dev/null
+++ b/public/locales/sl/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "Omrežje",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/sl/layout/modals/change-position.json b/public/locales/sl/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/sl/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/sl/layout/screen-sizes.json b/public/locales/sl/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/sl/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/sl/layout/tools.json b/public/locales/sl/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/sl/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/sl/modules/calendar.json b/public/locales/sl/modules/calendar.json
index 0f31e2d4c..e9ac58a1b 100644
--- a/public/locales/sl/modules/calendar.json
+++ b/public/locales/sl/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Koledar",
- "description": "Koledarski modul za prikaz prihajajočih izdaj. Deluje z API vmesnikom od Sonarr in Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Začni teden z nedeljo"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/common.json b/public/locales/sl/modules/common.json
index 93851f8a5..c2a7719e6 100644
--- a/public/locales/sl/modules/common.json
+++ b/public/locales/sl/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Nastavitve"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/dashdot.json b/public/locales/sl/modules/dashdot.json
index cfb6af169..b6a86094d 100644
--- a/public/locales/sl/modules/dashdot.json
+++ b/public/locales/sl/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Modul za prikazovanje grafov iz vašega delujočega programa Dash.",
+ "description": "",
"settings": {
+ "title": "Nastavitve za pripomoček Dash",
"cpuMultiView": {
"label": "Pogled večjedrnih procesorjev"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/date.json b/public/locales/sl/modules/date.json
index b26ac578f..a5373d595 100644
--- a/public/locales/sl/modules/date.json
+++ b/public/locales/sl/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Datum",
- "description": "Prikaz trenutnega časa in datuma na kartici",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Prikaz polnega časa (24-urni)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/dlspeed.json b/public/locales/sl/modules/dlspeed.json
index f39b1d828..5cf32f2a8 100644
--- a/public/locales/sl/modules/dlspeed.json
+++ b/public/locales/sl/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Hitrost prenosa",
- "description": "Prikaži trenutno hitrost prenosa podprtih storitev"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/docker.json b/public/locales/sl/modules/docker.json
index 9f6491778..91e01aaa5 100644
--- a/public/locales/sl/modules/docker.json
+++ b/public/locales/sl/modules/docker.json
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Dodaj storitev",
- "message": "Dodajanje storitev v Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Ponovno zaženi"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Integracija Dockerja ni uspela",
- "message": "Ste pozabili namestiti Docker vtičnico?"
+ "message": ""
},
"unknownError": {
"title": "Prišlo je do napake"
},
"oneServiceAtATime": {
- "title": "Naenkrat dodajte samo eno storitev!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/overseerr.json b/public/locales/sl/modules/overseerr.json
index 451d366ad..a543ab236 100644
--- a/public/locales/sl/modules/overseerr.json
+++ b/public/locales/sl/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Omogoča iskanje in dodajanje medijev iz storitev Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Označite sezone, ki jih želite prenesti",
+ "caption": "",
"table": {
"header": {
"season": "Sezona",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/ping.json b/public/locales/sl/modules/ping.json
index 9ef65dc92..ff0b9c34e 100644
--- a/public/locales/sl/modules/ping.json
+++ b/public/locales/sl/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Omogoča preverjanje, ali je storitev vzpostavljena ali vrača določeno HTTP kodo statusa."
+ "description": ""
},
"states": {
"online": "Povezan {{response}}",
"offline": "Prekinjen {{response}}",
"loading": "Nalaganje..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/search.json b/public/locales/sl/modules/search.json
index 652cec58c..9abd0387e 100644
--- a/public/locales/sl/modules/search.json
+++ b/public/locales/sl/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Iskalna vrstica",
- "description": "Iskalna vrstica za iskanje po spletu, YouTubu, torrentih ali Overseerru"
+ "description": ""
},
"input": {
"placeholder": "Preišči splet..."
},
- "switched-to": "",
+ "switched-to": "Preklopljeno na",
"searchEngines": {
"search": {
- "name": "",
+ "name": "Splet",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "YouTube",
+ "description": "Poišči na YouTube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrenti",
+ "description": "Iskanje torrentov"
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "Iskalno vrstico lahko izberete z bližnjico ",
+ "switchedSearchEngine": "Preklopljen na iskanje s {{searchEngine}}"
+}
diff --git a/public/locales/sl/modules/torrents-status.json b/public/locales/sl/modules/torrents-status.json
index aab63c6bc..cd14e165b 100644
--- a/public/locales/sl/modules/torrents-status.json
+++ b/public/locales/sl/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Torrent",
- "description": "Prikaži trenutno hitrost prenosa podprtih storitev",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Skrijte dokončane torrente"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Ni bilo najdenih podprtih odjemalcev za prenos!",
- "text": "Dodajte storitev prenosa za ogled trenutnih prenosov"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Nalaganje..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/modules/usenet.json b/public/locales/sl/modules/usenet.json
index 4f30f78f2..7d60b9547 100644
--- a/public/locales/sl/modules/usenet.json
+++ b/public/locales/sl/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
+ "name": "Usenet",
"description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Ni bilo najdenih podprtih odjemalcev za prenos!",
- "text": "Dodajte storitev prenosa za ogled trenutnih prenosov"
+ "text": ""
}
}
},
diff --git a/public/locales/sl/modules/weather.json b/public/locales/sl/modules/weather.json
index 97baa3aa9..385342a6a 100644
--- a/public/locales/sl/modules/weather.json
+++ b/public/locales/sl/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Vreme",
- "description": "Iskanje trenutnega vremena na vaši lokaciji",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Prikaz v Fahrenheitu"
},
@@ -29,4 +30,4 @@
"unknown": "Neznano"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/settings/common.json b/public/locales/sl/settings/common.json
index dac1a8a69..ae9a3076a 100644
--- a/public/locales/sl/settings/common.json
+++ b/public/locales/sl/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Prilagoditve"
},
"tips": {
- "configTip": "Konfiguracijsko datoteko naložite tako, da jo povlečete in spustite na stran!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "Narejeno s ❤️ od @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Rastoča mreža (zavzema ves prostor)",
+ "layout": {
+ "title": "Postavitev nadzorne plošče",
+ "main": "Glavno",
+ "sidebar": "Stranska vrstica",
+ "cannotturnoff": "Ni mogoče izklopiti",
+ "dashboardlayout": "Postavitev nadzorne plošče",
+ "enablersidebar": "Omogočite desno stransko vrstico",
+ "enablelsidebar": "Omogočite levo stransko vrstico",
+ "enablesearchbar": "Omogočite iskalno vrstico",
+ "enabledocker": "Omogočite integracijo dockerja",
+ "enableping": "Omogočite pinganje",
+ "enablelsidebardesc": "Izbirno. Uporablja se lahko samo za aplikacije in integracije",
+ "enablersidebardesc": "Izbirno. Uporablja se lahko samo za aplikacije in integracije"
+ }
+}
diff --git a/public/locales/sl/settings/customization/page-appearance.json b/public/locales/sl/settings/customization/page-appearance.json
index 5d63587dc..6dd25bc2b 100644
--- a/public/locales/sl/settings/customization/page-appearance.json
+++ b/public/locales/sl/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Naslov strani",
- "placeholder": "Homarr 🦞"
+ "label": "Naslov strani"
+ },
+ "metaTitle": {
+ "label": ""
},
"logo": {
"label": "Logotip"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Po meri CSS",
- "placeholder": "CSS po meri se izvede kot zadnji"
+ "placeholder": ""
},
"buttons": {
"submit": "Pošlji"
diff --git a/public/locales/sl/settings/general/config-changer.json b/public/locales/sl/settings/general/config-changer.json
index 835bfb160..6af6195b2 100644
--- a/public/locales/sl/settings/general/config-changer.json
+++ b/public/locales/sl/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Nalagalnik konfiguracije"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "Izberite ime vaše nove konfiguracije",
- "form": {
- "configName": {
- "label": "Ime konfiguracije",
- "placeholder": "Vaše novo ime konfiguracije"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Potrdi"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Konfiguracija je shranjena",
- "message": "Konfiguracija shranjena kot {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Brisanje konfiguracije ni uspelo",
"message": "Brisanje konfiguracije ni uspelo"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Če želite naložiti konfiguracijo, povlecite datoteke sem. Podpora samo za JSON."
+ "title": "",
+ "text": ""
},
"reject": {
- "text": "Ta oblika datoteke ni podprta. Prenesite samo JSON."
+ "title": "",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/settings/general/module-enabler.json b/public/locales/sl/settings/general/module-enabler.json
index 48543dc34..9e26dfeeb 100644
--- a/public/locales/sl/settings/general/module-enabler.json
+++ b/public/locales/sl/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Omogočanje modulov"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/sl/settings/general/search-engine.json b/public/locales/sl/settings/general/search-engine.json
index 829340fec..94a9fd765 100644
--- a/public/locales/sl/settings/general/search-engine.json
+++ b/public/locales/sl/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Iskalnik",
+ "configurationName": "",
"tips": {
- "generalTip": "Za iskanje v YouTubu ali Torrentu uporabite predponi !yt in !t pred poizvedbo.",
+ "generalTip": "",
"placeholderTip": "%s lahko uporabite kot nadomestno ime za poizvedbo."
},
"customEngine": {
+ "title": "",
"label": "URL poizvedbe",
"placeholder": "URL poizvedbe po meri"
},
"searchNewTab": {
+ "label": "Odprite rezultate iskanja v novem zavihku"
+ },
+ "searchEnabled": {
"label": ""
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sl/settings/general/widget-positions.json b/public/locales/sl/settings/general/widget-positions.json
index d34ee5438..0967ef424 100644
--- a/public/locales/sl/settings/general/widget-positions.json
+++ b/public/locales/sl/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Prikaži gradnike na levi strani"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/sv/authentication/login.json b/public/locales/sv/authentication/login.json
index 5f3102cff..4ff931453 100644
--- a/public/locales/sv/authentication/login.json
+++ b/public/locales/sv/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Välkommen tillbaka!",
- "text": "Vänligen ange lösenordet",
+ "text": "Ange ditt lösenord",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Ditt lösenord kontrolleras..."
},
"correct": {
- "title": "Lösenord korrekt, omdirigerar dig..."
+ "title": "Inloggning lyckades, omdirigerar..."
},
"wrong": {
- "title": "Lösenordet är fel, försök igen."
+ "title": "Lösenordet du angav är felaktigt. Försök igen."
}
}
}
diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json
index 9fae26e10..c6a924e89 100644
--- a/public/locales/sv/common.json
+++ b/public/locales/sv/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Spara"
+ "save": "Spara",
+ "about": "Om",
+ "cancel": "Avbryt",
+ "close": "Stäng",
+ "delete": "Radera",
+ "ok": "OK",
+ "edit": "Redigera",
+ "version": "Version",
+ "changePosition": "Ändra position",
+ "remove": "Ta bort",
+ "removeConfirm": "Är du säker på att du vill ta bort {{item}} ?",
+ "sections": {
+ "settings": "Inställningar",
+ "dangerZone": "Farozon"
+ },
+ "secrets": {
+ "apiKey": "API-nyckel",
+ "username": "Användarnamn",
+ "password": "Lösenord"
},
"tip": "Tips: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "minuter",
"hours": "timmar"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/sv/layout/add-service-app-shelf.json b/public/locales/sv/layout/add-service-app-shelf.json
index fc9a29523..f53f1e228 100644
--- a/public/locales/sv/layout/add-service-app-shelf.json
+++ b/public/locales/sv/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Avancerade inställningar",
"form": {
- "httpStatusCodes": {
- "label": "HTTP-statuskoder",
- "placeholder": "Välj giltiga statuskoder",
- "clearButtonLabel": "Rensa markering",
- "nothingFound": "Ingenting hittades"
- },
"openServiceInNewTab": {
"label": "Öppna tjänsten i ny flik"
},
diff --git a/public/locales/sv/layout/element-selector/selector.json b/public/locales/sv/layout/element-selector/selector.json
new file mode 100644
index 000000000..2bf2d3538
--- /dev/null
+++ b/public/locales/sv/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Lägg till en ny ruta",
+ "text": "Rutor är det viktigaste elementet i Homarr. De används för att visa dina appar och annan information. Du kan lägga till så många rutor som du vill."
+ },
+ "widgetDescription": "Widgetar interagerar med dina appar, för att ge dig mer kontroll över dina applikationer. De kräver vanligtvis ytterligare konfiguration innan användning.",
+ "goBack": "Gå tillbaka till föregående steg",
+ "actionIcon": {
+ "tooltip": "Lägg till en ruta"
+ }
+}
diff --git a/public/locales/sv/layout/header/actions/toggle-edit-mode.json b/public/locales/sv/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..d605a70b4
--- /dev/null
+++ b/public/locales/sv/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "I redigeringsläge kan du justera rutor och konfigurera appar. Ändringar sparas inte förrän du avslutar redigeringsläget.",
+ "button": {
+ "disabled": "Gå till redigeringsläge",
+ "enabled": "Avsluta och spara"
+ },
+ "popover": {
+ "title": "Redigeringsläget är aktiverat för <1>{{size}}1> storlek",
+ "text": "Du kan justera och konfigurera dina appar nu. Ändringarna sparas inte förrän du lämnar redigeringsläget"
+ },
+ "screenSizes": {
+ "small": "liten",
+ "medium": "mellan",
+ "large": "stor"
+ }
+}
diff --git a/public/locales/sv/layout/mobile/drawer.json b/public/locales/sv/layout/mobile/drawer.json
new file mode 100644
index 000000000..135580d42
--- /dev/null
+++ b/public/locales/sv/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} sidofältet"
+}
diff --git a/public/locales/sv/layout/modals/about.json b/public/locales/sv/layout/modals/about.json
new file mode 100644
index 000000000..e35dbbfed
--- /dev/null
+++ b/public/locales/sv/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr är en stilren , modern instrumentpanel som ger dig tillgång till alla dina appar och tjänster. Med Homarr kan du få tillgång till och kontrollera allt på ett bekvämt ställe. Homarr integreras sömlöst med de appar du har lagt till, vilket ger dig värdefull information och ger dig fullständig kontroll. Installationen är enkel och Homarr stöder ett stort antal installationsmetoder.",
+ "i18n": "Laddade namnområden för I18n-översättningar",
+ "locales": "Konfigurerade I18n lokalspråk",
+ "contact": "Har du problem eller frågor? Kontakta oss!",
+ "addToDashboard": "Lägg till i instrumentpanel"
+}
diff --git a/public/locales/sv/layout/modals/add-app.json b/public/locales/sv/layout/modals/add-app.json
new file mode 100644
index 000000000..923c67f0d
--- /dev/null
+++ b/public/locales/sv/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Allmänt",
+ "behaviour": "Beteende",
+ "network": "Nätverk",
+ "appearance": "Utseende",
+ "integration": "Integration"
+ },
+ "general": {
+ "appname": {
+ "label": "Appnamn",
+ "description": "Används för att visa appen på instrumentpanelen."
+ },
+ "internalAddress": {
+ "label": "Intern adress",
+ "description": "Appens interna IP-adress."
+ },
+ "externalAddress": {
+ "label": "Extern adress",
+ "description": "URL som öppnas när du klickar på appen."
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Öppna i ny flik",
+ "description": "Öppna appen i en ny flik istället för den nuvarande."
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Statuskontroll",
+ "description": "Kontrollerar om din app är online med en enkel HTTP(S) begäran."
+ },
+ "statusCodes": {
+ "label": "HTTP-statuskoder",
+ "description": "HTTP-statuskoder som anses vara online."
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "Appikon",
+ "description": "Ikonen som kommer att visas på instrumentpanelen."
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Integrationskonfiguration",
+ "description": "Integrationskonfigurationen som kommer att användas för att ansluta till din app.",
+ "placeholder": "Välj en integration",
+ "defined": "Definierad",
+ "undefined": "Odefinierad",
+ "public": "Publik",
+ "private": "Privat",
+ "explanationPrivate": "En privat hemlighet skickas endast en gång till servern. När webbläsaren har uppdaterat sidan kommer den aldrig skickas igen.",
+ "explanationPublic": "En offentlig hemlighet kommer alltid att skickas till klienten och är tillgänglig över API. Det bör inte innehålla några konfidentiella värden som användarnamn, lösenord, tokens, certifikat och liknande!"
+ },
+ "secrets": {
+ "description": "För att uppdatera en hemlighet, ange ett värde och klicka på knappen Spara. För att ta bort en hemlighet, använd knappen rensa.",
+ "warning": "Dina autentiseringsuppgifter fungerar som åtkomst till dina integrationer och du bör aldrig dela dem med någon annan. Homarr-teamet kommer aldrig att be om autentiseringsuppgifter. Se till att lagra och hantera dina hemligheter på ett säkert sätt .",
+ "clear": "Rensa hemlighet",
+ "save": "Spara hemlighet",
+ "update": "Uppdatera hemlighet"
+ }
+ },
+ "validation": {
+ "popover": "Ditt formulär innehåller ogiltiga data. Därför kan det inte sparas. Vänligen lös alla problem och klicka på denna knapp igen för att spara dina ändringar"
+ }
+}
diff --git a/public/locales/sv/layout/modals/change-position.json b/public/locales/sv/layout/modals/change-position.json
new file mode 100644
index 000000000..5d1714b75
--- /dev/null
+++ b/public/locales/sv/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X axel position",
+ "width": "Bredd",
+ "height": "Höjd",
+ "yPosition": "Y axel position",
+ "zeroOrHigher": "0 eller högre",
+ "betweenXandY": "Mellan {{min}} och {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/sv/layout/screen-sizes.json b/public/locales/sv/layout/screen-sizes.json
new file mode 100644
index 000000000..3825e4341
--- /dev/null
+++ b/public/locales/sv/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "liten",
+ "medium": "mellan",
+ "large": "stor"
+ }
+}
diff --git a/public/locales/sv/layout/tools.json b/public/locales/sv/layout/tools.json
new file mode 100644
index 000000000..e2e2f0b6c
--- /dev/null
+++ b/public/locales/sv/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Du har för närvarande inga verktyg"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Sök efter ikoner...",
+ "searchLimitationTitle": "Sökningen är begränsad till {{max}} ikoner",
+ "searchLimitationMessage": "För att det ska gå snabbt och smidigt är sökningen begränsad till {{max}} ikoner. Använd sökrutan för att hitta fler ikoner"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/sv/modules/calendar.json b/public/locales/sv/modules/calendar.json
index eadf477a3..cea0dfff4 100644
--- a/public/locales/sv/modules/calendar.json
+++ b/public/locales/sv/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Kalender",
- "description": "En kalendermodul för att visa kommande releaser. Den interagerar med Sonarr- och Radarr-API:erna.",
+ "description": "Visar en kalender med kommande utgåvor, från integrationer som stöds.",
"settings": {
+ "title": "Inställningar för kalenderwidget",
"sundayStart": {
"label": "Börja veckan på söndag"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr releasetyp"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/common.json b/public/locales/sv/modules/common.json
index 99e8ff249..85ebdf33c 100644
--- a/public/locales/sv/modules/common.json
+++ b/public/locales/sv/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Inställningar"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": "En oanvänd parameter i konfigurationen har upptäckts {{key}}. Homarr kan inte tolka och använda denna parameter. För att undvika oväntat beteende bör du säkerhetskopiera din konfiguration och korrigera den."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/dashdot.json b/public/locales/sv/modules/dashdot.json
index 008745445..2ae7fa75a 100644
--- a/public/locales/sv/modules/dashdot.json
+++ b/public/locales/sv/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "En modul för att visa graferna från din Dash. instans.",
+ "description": "Visar graferna för en extern Dash.-instans i Homarr.",
"settings": {
+ "title": "Inställningar för Dash. widget",
"cpuMultiView": {
"label": "Flerkärnig CPU vy"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/date.json b/public/locales/sv/modules/date.json
index b99b6e859..1cf22b595 100644
--- a/public/locales/sv/modules/date.json
+++ b/public/locales/sv/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Datum",
- "description": "Visa aktuell tid och datum på ett kort",
+ "name": "Datum och tid",
+ "description": "Visar aktuellt datum och tid.",
"settings": {
+ "title": "Inställningar för datum och tid widget",
"display24HourFormat": {
"label": "Visa heltid (24-timmars)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/dlspeed.json b/public/locales/sv/modules/dlspeed.json
index d07d9d509..3ef35a452 100644
--- a/public/locales/sv/modules/dlspeed.json
+++ b/public/locales/sv/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Nedladdningshastighet ",
- "description": "Visa den aktuella nedladdningshastigheten för tjänster som stöds"
+ "description": "Visar nedladdnings- och uppladdningshastigheten för integrationer som stöds."
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/docker.json b/public/locales/sv/modules/docker.json
index 1cd48d13f..2e4b4dda5 100644
--- a/public/locales/sv/modules/docker.json
+++ b/public/locales/sv/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Låter dig enkelt hantera dina docker containers"
+ "description": "Låter dig enkelt se och hantera dina docker containers."
},
"search": {
"placeholder": "Sök efter container eller imagenamn"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Lägg till tjänst",
- "message": "Lägg till tjänst till Homarr"
+ "title": "Lägg till app",
+ "message": "Lägg till i Homarr"
},
"restart": {
"title": "Starta om"
@@ -74,10 +74,10 @@
"title": "Ett fel inträffade"
},
"oneServiceAtATime": {
- "title": "Lägg bara till en tjänst åt gången!"
+ "title": "Lägg bara till en app eller tjänst åt gången!"
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/overseerr.json b/public/locales/sv/modules/overseerr.json
index a105b8809..713e380b8 100644
--- a/public/locales/sv/modules/overseerr.json
+++ b/public/locales/sv/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Låter dig söka och lägga till media från Overseerr/Jellyseerr"
+ "description": "Låter dig söka och lägga till media från Overseerr eller Jellyseerr."
},
"popup": {
"item": {
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/ping.json b/public/locales/sv/modules/ping.json
index 871bc0f5b..6db1bb267 100644
--- a/public/locales/sv/modules/ping.json
+++ b/public/locales/sv/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Låter dig kontrollera om tjänsten är uppe eller returnerar en specifik HTTP-statuskod."
+ "description": "Visar en statusindikator beroende på HTTP-svarskoden för en given URL."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Laddar..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/search.json b/public/locales/sv/modules/search.json
index 0eaff0b7d..ae518f605 100644
--- a/public/locales/sv/modules/search.json
+++ b/public/locales/sv/modules/search.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Sökfält",
- "description": "Sökfältet för att söka på webben, Youtube, Torrents eller Overseerr"
+ "description": "Ett sökfält där du kan söka i din anpassade sökmotor, YouTube och integreringar som stöds."
},
"input": {
"placeholder": "Sök på webben..."
@@ -10,7 +10,7 @@
"searchEngines": {
"search": {
"name": "Webb",
- "description": "Sök med din sökmotor (definierad i inställningar)"
+ "description": "Sök..."
},
"youtube": {
"name": "YouTube",
@@ -22,9 +22,9 @@
},
"overseerr": {
"name": "Overseerr",
- "description": "Sök efter filmer och TV-program med Overseerr (modulen måste vara aktiverad)"
+ "description": "Sök efter filmer och TV-program i Overseerr"
}
},
"tip": "Du kan välja sökfältet med kortkommandot ",
"switchedSearchEngine": "Växlade till att söka med {{searchEngine}}"
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/torrents-status.json b/public/locales/sv/modules/torrents-status.json
index 619537ec0..b710e2c6d 100644
--- a/public/locales/sv/modules/torrents-status.json
+++ b/public/locales/sv/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Torrent",
- "description": "Visa den aktuella nedladdningshastigheten för tjänster som stöds",
+ "description": "Visar en lista med torrents från Torrent-klienter som stöds.",
"settings": {
- "hideComplete": {
- "label": "Dölj slutförda torrents"
+ "title": "Inställningar för Torrent widget",
+ "refreshInterval": {
+ "label": "Uppdateringsfrekvens (sekunder)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Visa slutförda torrents"
+ },
+ "displayStaleTorrents": {
+ "label": "Visa inaktuella torrents"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Inga nedladdningsklienter som stöds hittades!",
- "text": "Lägg till en nedladdningstjänst för att visa dina aktuella nedladdningar"
+ "title": "Inga Torrent-klienter som stöds hittades!",
+ "text": "Lägg till en Torrent-klient som stöds för att visa dina aktuella nedladdningar"
+ },
+ "generic": {
+ "title": "Ett oväntat fel uppstod",
+ "text": "Homarr kunde inte kommunicera med dina torrent-klienter. Vänligen kontrollera din konfiguration"
}
+ },
+ "loading": {
+ "title": "Laddar..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/modules/usenet.json b/public/locales/sv/modules/usenet.json
index 8ee92da1c..1e4bce64d 100644
--- a/public/locales/sv/modules/usenet.json
+++ b/public/locales/sv/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Låter dig se din usenet (Sabnzbd eller NZBGet) kö och historik, pausa och återuppta nedladdningar"
+ "description": "Låter dig visa och hantera din Usenet instans."
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Inga nedladdningsklienter som stöds hittades!",
- "text": "Lägg till en nedladdningstjänst för att visa dina aktuella nedladdningar"
+ "text": "Lägg till en nedladdningsklient för Usenet som stöds för att se dina aktuella nedladdningar"
}
}
},
diff --git a/public/locales/sv/modules/weather.json b/public/locales/sv/modules/weather.json
index 0abf49694..810253dd8 100644
--- a/public/locales/sv/modules/weather.json
+++ b/public/locales/sv/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Väder",
- "description": "Se det aktuella vädret på din plats",
+ "description": "Visar aktuell väderinformation för en bestämd plats.",
"settings": {
+ "title": "Inställningar för väderwidget",
"displayInFahrenheit": {
"label": "Visa i Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Okänd"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/settings/common.json b/public/locales/sv/settings/common.json
index e1aeac17f..747032b8a 100644
--- a/public/locales/sv/settings/common.json
+++ b/public/locales/sv/settings/common.json
@@ -11,5 +11,19 @@
"credits": {
"madeWithLove": "Gjort med ❤️ av @"
},
- "grow": "Växande rutnät (ta allt utrymme)"
-}
\ No newline at end of file
+ "grow": "Växande rutnät (ta allt utrymme)",
+ "layout": {
+ "title": "Instrumentpanelslayout",
+ "main": "Allmänt",
+ "sidebar": "Sidopanel",
+ "cannotturnoff": "Kan inte inaktiveras",
+ "dashboardlayout": "Instrumentpanelslayout",
+ "enablersidebar": "Aktivera höger sidopanel",
+ "enablelsidebar": "Aktivera vänster sidopanel",
+ "enablesearchbar": "Aktivera sökfält",
+ "enabledocker": "Aktivera dockerintegration",
+ "enableping": "Aktivera pings",
+ "enablelsidebardesc": "Valfritt. Kan endast användas för appar och integrationer",
+ "enablersidebardesc": "Valfritt. Kan endast användas för appar och integrationer"
+ }
+}
diff --git a/public/locales/sv/settings/customization/page-appearance.json b/public/locales/sv/settings/customization/page-appearance.json
index 04513596d..75c9ee3bf 100644
--- a/public/locales/sv/settings/customization/page-appearance.json
+++ b/public/locales/sv/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Sidotitel",
- "placeholder": "Homarr 🦞"
+ "label": "Sidotitel"
+ },
+ "metaTitle": {
+ "label": "Metatitel"
},
"logo": {
"label": "Logotyp"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Anpassad CSS",
- "placeholder": "Anpassad CSS kommer att utföras sist"
+ "placeholder": "Anpassad CSS tillämpas sist"
},
"buttons": {
"submit": "Skicka"
diff --git a/public/locales/sv/settings/general/config-changer.json b/public/locales/sv/settings/general/config-changer.json
index d0671304b..91b543cb6 100644
--- a/public/locales/sv/settings/general/config-changer.json
+++ b/public/locales/sv/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Konfigurationsladdare"
+ "label": "Konfigurationsväxlare",
+ "description": "{{configCount}} konfigurationer finns tillgängliga",
+ "loadingNew": "Laddar din konfiguration...",
+ "pleaseWait": "Vänta tills din nya konfiguration är laddad!"
},
"modal": {
- "title": "Välj namn på din nya konfigurering",
- "form": {
- "configName": {
- "label": "Konfigurationsnamn",
- "placeholder": "Ditt nya konfigurationsnamn"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": "Konfigurationens namn används redan"
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Bekräfta"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Konfigurationen sparad",
- "message": "Konfigurationen sparad som {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Radering av konfigurationen misslyckades",
"message": "Radering av konfigurationen misslyckades"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "Standardkonfigurationen kan inte tas bort",
+ "message": "Konfigurationen togs inte bort från filsystemet"
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Dra filer hit för att ladda upp en konfiguration. Endast stöd för JSON."
+ "title": "Uppladdning av konfiguration",
+ "text": "Dra filer hit för att ladda upp en konfiguration. Stöd för endast JSON-filer."
},
"reject": {
- "text": "Det här filformatet stöds inte. Ladda endast upp JSON."
+ "title": "Dra och släpp uppladdningen avvisades",
+ "text": "Det här filformatet stöds inte. Ladda bara upp JSON-filer."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/settings/general/module-enabler.json b/public/locales/sv/settings/general/module-enabler.json
index d74a481fc..9e26dfeeb 100644
--- a/public/locales/sv/settings/general/module-enabler.json
+++ b/public/locales/sv/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Modulaktiverare"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/sv/settings/general/search-engine.json b/public/locales/sv/settings/general/search-engine.json
index d602e80f8..8e72064cc 100644
--- a/public/locales/sv/settings/general/search-engine.json
+++ b/public/locales/sv/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Sökmotor",
+ "configurationName": "Sökmotorns konfiguration",
"tips": {
- "generalTip": "Använd prefixen !yt och !t framför din sökning för att söka på YouTube eller efter en Torrent.",
+ "generalTip": "Det finns flera prefix som du kan använda! Om du lägger till dessa före din fråga filtrerar du resultaten. !s (Webb), !t (Torrents), !y (YouTube) och !m (Media).",
"placeholderTip": "%s kan användas som platshållare för förfrågningen."
},
"customEngine": {
+ "title": "Anpassad sökmotor",
"label": "URL för förfrågan",
"placeholder": "Anpassad sök-URL"
},
"searchNewTab": {
"label": "Öppna sökresultaten i en ny flik"
+ },
+ "searchEnabled": {
+ "label": "Sökning aktiverad"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/sv/settings/general/widget-positions.json b/public/locales/sv/settings/general/widget-positions.json
index 02ed34b24..829c45b2c 100644
--- a/public/locales/sv/settings/general/widget-positions.json
+++ b/public/locales/sv/settings/general/widget-positions.json
@@ -1,3 +1,3 @@
{
"label": "Placera widgetar till vänster"
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/authentication/login.json b/public/locales/uk/authentication/login.json
index cfc12d549..aa074a55f 100644
--- a/public/locales/uk/authentication/login.json
+++ b/public/locales/uk/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "З поверненням!",
- "text": "Будь ласка, введіть пароль",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Ваш пароль перевіряється..."
},
"correct": {
- "title": "Пароль вірний, перенаправлення..."
+ "title": ""
},
"wrong": {
- "title": "Невірний пароль. Будь ласка, спробуйте ще раз."
+ "title": ""
}
}
}
diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json
index 8b53a5c55..9cc4ef05c 100644
--- a/public/locales/uk/common.json
+++ b/public/locales/uk/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Зберегти"
+ "save": "Зберегти",
+ "about": "Про програму",
+ "cancel": "Скасувати",
+ "close": "Закрити",
+ "delete": "Видалити",
+ "ok": "OK",
+ "edit": "Редагувати",
+ "version": "Версія",
+ "changePosition": "Змінити положення",
+ "remove": "Видалити",
+ "removeConfirm": "Ви впевнені, що хочете видалити {{item}} ?",
+ "sections": {
+ "settings": "Налаштування",
+ "dangerZone": "Небезпечна зона"
+ },
+ "secrets": {
+ "apiKey": "Ключ API",
+ "username": "Логін",
+ "password": "Пароль"
},
"tip": "Підказка: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "хвилин",
"hours": "годин"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/uk/layout/add-service-app-shelf.json b/public/locales/uk/layout/add-service-app-shelf.json
index 46a9216a9..8d9358ebf 100644
--- a/public/locales/uk/layout/add-service-app-shelf.json
+++ b/public/locales/uk/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Додаткові параметри",
"form": {
- "httpStatusCodes": {
- "label": "Коди статусу HTTP",
- "placeholder": "Виберіть правильні коди статусу",
- "clearButtonLabel": "Очистити вибране",
- "nothingFound": "Нічого не знайдено"
- },
"openServiceInNewTab": {
"label": "Відкрити сервіс у новій вкладці"
},
diff --git a/public/locales/uk/layout/element-selector/selector.json b/public/locales/uk/layout/element-selector/selector.json
new file mode 100644
index 000000000..c47c38ba7
--- /dev/null
+++ b/public/locales/uk/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Додати плитку",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "Повернутися на попередню сторінку",
+ "actionIcon": {
+ "tooltip": "Додати плитку"
+ }
+}
diff --git a/public/locales/uk/layout/header/actions/toggle-edit-mode.json b/public/locales/uk/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..22ac2c060
--- /dev/null
+++ b/public/locales/uk/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "Ввійти в режим редагування",
+ "enabled": "Зберегти та вийти"
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "малий",
+ "medium": "середній",
+ "large": "великий"
+ }
+}
diff --git a/public/locales/uk/layout/mobile/drawer.json b/public/locales/uk/layout/mobile/drawer.json
new file mode 100644
index 000000000..f7870b3d8
--- /dev/null
+++ b/public/locales/uk/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} бічна панель"
+}
diff --git a/public/locales/uk/layout/modals/about.json b/public/locales/uk/layout/modals/about.json
new file mode 100644
index 000000000..25ef14aec
--- /dev/null
+++ b/public/locales/uk/layout/modals/about.json
@@ -0,0 +1,6 @@
+{
+ "i18n": "Завантажено інтернаціональні переклади",
+ "locales": "Налаштовано інтернаціональні локалі",
+ "contact": "Виникли проблеми або питання? Зв'яжіться з нами!",
+ "addToDashboard": "Додати до панелі інструментів"
+}
diff --git a/public/locales/uk/layout/modals/add-app.json b/public/locales/uk/layout/modals/add-app.json
new file mode 100644
index 000000000..637cc7a40
--- /dev/null
+++ b/public/locales/uk/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "Загальне",
+ "behaviour": "Поведінка",
+ "network": "Мережа",
+ "appearance": "Вигляд",
+ "integration": "Інтеграція"
+ },
+ "general": {
+ "appname": {
+ "label": "Назва застосунку",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "Внутрішня адреса",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "Зовнішня адреса",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "Відкрити в новій вкладці",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "Перевірка стану",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "Код Статусу HTTP",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "Іконка програми",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "Налаштування інтеграції",
+ "description": "",
+ "placeholder": "Виберіть інтеграцію",
+ "defined": "Визначено",
+ "undefined": "Невизначено",
+ "public": "Публічний",
+ "private": "Приватний",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "Щоб оновити секрет, введіть значення і натисніть кнопку Зберегти. Щоб видалити секрет, використовуйте кнопку \"Очистити\".",
+ "warning": "",
+ "clear": "Очистити секрет",
+ "save": "Зберегти секрет",
+ "update": "Оновити секрет"
+ }
+ },
+ "validation": {
+ "popover": "Ваша форма містить неприпустимі дані тож не може бути збережена. Будь ласка, розв'яжіть усі проблеми та натисніть цю кнопку знову, щоб зберегти свої зміни"
+ }
+}
diff --git a/public/locales/uk/layout/modals/change-position.json b/public/locales/uk/layout/modals/change-position.json
new file mode 100644
index 000000000..4e9079512
--- /dev/null
+++ b/public/locales/uk/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "Положення осі X",
+ "width": "Ширина",
+ "height": "Висота",
+ "yPosition": "Положення осі Y",
+ "zeroOrHigher": "0 або вище",
+ "betweenXandY": "Між {{min}} та {{max}}"
+}
\ No newline at end of file
diff --git a/public/locales/uk/layout/screen-sizes.json b/public/locales/uk/layout/screen-sizes.json
new file mode 100644
index 000000000..cea849402
--- /dev/null
+++ b/public/locales/uk/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "малий",
+ "medium": "середній",
+ "large": "великий"
+ }
+}
diff --git a/public/locales/uk/layout/tools.json b/public/locales/uk/layout/tools.json
new file mode 100644
index 000000000..8ab3b6975
--- /dev/null
+++ b/public/locales/uk/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "Наразі у вас немає жодного інструменту"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "Пошук іконки...",
+ "searchLimitationTitle": "Пошук обмежений до {{max}} значків",
+ "searchLimitationMessage": "Для того, щоб зробити пошук швидким і швидким, пошук обмежений іконками {{max}}. Використовуйте поле пошуку, щоб знайти більше іконок"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/uk/modules/calendar.json b/public/locales/uk/modules/calendar.json
index 2092b9683..4d28455e9 100644
--- a/public/locales/uk/modules/calendar.json
+++ b/public/locales/uk/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Календар",
- "description": "Модуль календаря для відстежування майбутніх релізів. Він взаємодіє з API Sonarr і Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Почати тиждень у Неділю"
+ },
+ "radarrReleaseType": {
+ "label": "Radarr - тип релізів"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/common.json b/public/locales/uk/modules/common.json
index f48fb9d7c..1963ca573 100644
--- a/public/locales/uk/modules/common.json
+++ b/public/locales/uk/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Налаштування"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/dashdot.json b/public/locales/uk/modules/dashdot.json
index fa309ad35..878c47242 100644
--- a/public/locales/uk/modules/dashdot.json
+++ b/public/locales/uk/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Модуль для зображення графіків від вашого Dash. сервісу.",
+ "description": "",
"settings": {
+ "title": "Налаштування віджета Dash.",
"cpuMultiView": {
"label": "Процесор по ядрах"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/date.json b/public/locales/uk/modules/date.json
index 37a6f00d8..b317d7d6c 100644
--- a/public/locales/uk/modules/date.json
+++ b/public/locales/uk/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Дата",
- "description": "Показати поточний час і дату в картці",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Показувати повний час (24 години)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/dlspeed.json b/public/locales/uk/modules/dlspeed.json
index 1d898c614..5deac07e6 100644
--- a/public/locales/uk/modules/dlspeed.json
+++ b/public/locales/uk/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Швидкість завантаження",
- "description": "Показувати поточну швидкість завантаження для сервісів, які підтримуються"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/docker.json b/public/locales/uk/modules/docker.json
index f899c7689..20b3d3ce0 100644
--- a/public/locales/uk/modules/docker.json
+++ b/public/locales/uk/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Дозволяє вам легко керувати контейнерами докер"
+ "description": ""
},
"search": {
"placeholder": "Пошук по назві контейнера або образу"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Додати сервіс",
- "message": "Додати сервіс до Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Перезапустити"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Помилка інтеграції з Docker",
- "message": "Ви забули під'єднати docker сокет?"
+ "message": ""
},
"unknownError": {
"title": "Виникла помилка"
},
"oneServiceAtATime": {
- "title": "Будь ласка, лише один сервіс за один раз!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/overseerr.json b/public/locales/uk/modules/overseerr.json
index 9cda16bed..8162c79ed 100644
--- a/public/locales/uk/modules/overseerr.json
+++ b/public/locales/uk/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Дозволяє вам шукати та додавати медіа з Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Позначте сезони, які ви хочете завантажити",
+ "caption": "",
"table": {
"header": {
"season": "Сезон",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/ping.json b/public/locales/uk/modules/ping.json
index 878ff1c3c..90a7960d3 100644
--- a/public/locales/uk/modules/ping.json
+++ b/public/locales/uk/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Пінг",
- "description": "Дозволяє перевірити, чи працює сервіс або повертає певний код статусу HTTP."
+ "description": ""
},
"states": {
"online": "Онлайн {{response}}",
"offline": "Оффлайн {{response}}",
"loading": "Завантаження..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/search.json b/public/locales/uk/modules/search.json
index 70bff8526..bdec390c3 100644
--- a/public/locales/uk/modules/search.json
+++ b/public/locales/uk/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Панель пошуку",
- "description": "Панель для пошуку в Інтернеті, Youtube, Torrents або Overseerr"
+ "description": ""
},
"input": {
"placeholder": "Пошук в Інтернеті..."
},
- "switched-to": "",
+ "switched-to": "Змінити на",
"searchEngines": {
"search": {
- "name": "",
+ "name": "Веб",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "YouTube",
+ "description": "Пошук на YouTube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrent",
+ "description": "Пошук за торентами"
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "Викликати рядок пошуку можна за допомогою комбінації клавіш ",
+ "switchedSearchEngine": "Перейшли на пошук за допомогою {{searchEngine}}"
+}
diff --git a/public/locales/uk/modules/torrents-status.json b/public/locales/uk/modules/torrents-status.json
index 074976700..7a12c569c 100644
--- a/public/locales/uk/modules/torrents-status.json
+++ b/public/locales/uk/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
"name": "Торент",
- "description": "Показувати поточну швидкість завантаження для сервісів, які підтримуються",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Приховувати завершені"
+ "title": "",
+ "refreshInterval": {
+ "label": "Інтервал оновлення (у секундах)"
+ },
+ "displayCompletedTorrents": {
+ "label": "Відображати завершені торренти"
+ },
+ "displayStaleTorrents": {
+ "label": "Відображати застарілі торренти"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Не знайдено клієнтів для завантаження що підтримуються!",
- "text": "Додайте сервіс завантаження, щоб переглянути поточні завантаження"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "Сталась непередбачена помилка",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Завантаження..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/modules/usenet.json b/public/locales/uk/modules/usenet.json
index 055b0ad9e..baf14e5d5 100644
--- a/public/locales/uk/modules/usenet.json
+++ b/public/locales/uk/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Дозволяє вам бачити вашу чергу та історію в usenet (Sabnzbd або NZBGet), керувати завантаженнями"
+ "description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Не знайдено клієнтів для завантаження що підтримуються!",
- "text": "Додайте сервіс завантаження, щоб переглянути поточні завантаження"
+ "text": ""
}
}
},
diff --git a/public/locales/uk/modules/weather.json b/public/locales/uk/modules/weather.json
index c267f65e0..31cf29449 100644
--- a/public/locales/uk/modules/weather.json
+++ b/public/locales/uk/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Погода",
- "description": "Шукати поточну погоду у вашому місцеперебуванні",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Використовувати Фаренгейт"
},
@@ -29,4 +30,4 @@
"unknown": "Невідомо"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/settings/common.json b/public/locales/uk/settings/common.json
index cc6add962..440b73f6a 100644
--- a/public/locales/uk/settings/common.json
+++ b/public/locales/uk/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Персоналізація"
},
"tips": {
- "configTip": "Завантажте файл конфігурації, перетягуючи його на сторінку!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "Зроблено з ❤️ by @"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Виростити сітку (зайняти весь простір)",
+ "layout": {
+ "title": "Схема панелі інструментів",
+ "main": "Основне",
+ "sidebar": "Бокова панель",
+ "cannotturnoff": "Неможливо вимкнути",
+ "dashboardlayout": "Схема панелі інструментів",
+ "enablersidebar": "Увімкнути праву бічну панель",
+ "enablelsidebar": "Увімкнути ліву бічну панель",
+ "enablesearchbar": "Увімкнути панель пошуку",
+ "enabledocker": "Увімкнути інтеграцію з Docker",
+ "enableping": "Увімкнути пінг",
+ "enablelsidebardesc": "Необов'язково. Може використовуватися тільки для додатків та інтеграцій",
+ "enablersidebardesc": "Необов'язково. Може використовуватися тільки для додатків та інтеграцій"
+ }
+}
diff --git a/public/locales/uk/settings/customization/page-appearance.json b/public/locales/uk/settings/customization/page-appearance.json
index 85c0974f5..c089eb233 100644
--- a/public/locales/uk/settings/customization/page-appearance.json
+++ b/public/locales/uk/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Заголовок сторінки",
- "placeholder": "Homarr 🦞"
+ "label": "Заголовок сторінки"
+ },
+ "metaTitle": {
+ "label": "Мета-заголовок"
},
"logo": {
"label": "Логотип"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "Власний CSS",
- "placeholder": "Власний CSS буде оброблятися в останню чергу"
+ "placeholder": ""
},
"buttons": {
"submit": "Надіслати"
diff --git a/public/locales/uk/settings/general/config-changer.json b/public/locales/uk/settings/general/config-changer.json
index 0aa0248f1..d9d9294b9 100644
--- a/public/locales/uk/settings/general/config-changer.json
+++ b/public/locales/uk/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Завантаження налаштувань"
+ "label": "",
+ "description": "",
+ "loadingNew": "Завантаження конфігурації...",
+ "pleaseWait": ""
},
"modal": {
- "title": "Виберіть ім'я для нової конфігурації",
- "form": {
- "configName": {
- "label": "Ім'я конфігурації",
- "placeholder": "Нове ім'я вашої конфігурації"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Підтвердити"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Конфігурацію збережено",
- "message": "Конфігурація збережена як {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Помилка видалення конфігурації",
"message": "Помилка видалення конфігурації"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Перетягніть файли сюди для завантаження конфігурації. Підтримка лише для JSON."
+ "title": "Завантаження конфігурації",
+ "text": ""
},
"reject": {
- "text": "Цей формат файлу не підтримується. Будь ласка, лише завантажте JSON."
+ "title": "Відхилено завантаження за допомогою перетягування",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/settings/general/module-enabler.json b/public/locales/uk/settings/general/module-enabler.json
index a6415343f..9e26dfeeb 100644
--- a/public/locales/uk/settings/general/module-enabler.json
+++ b/public/locales/uk/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Використовувати модулі"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/uk/settings/general/search-engine.json b/public/locales/uk/settings/general/search-engine.json
index f4b27efd6..bdd2a6cbc 100644
--- a/public/locales/uk/settings/general/search-engine.json
+++ b/public/locales/uk/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Пошукова система",
+ "configurationName": "Налаштування пошукової системи",
"tips": {
- "generalTip": "Використовуйте префікси !yt і !t перед вашим запитом на YouTube або Торент відповідно.",
+ "generalTip": "",
"placeholderTip": "%s можна використовувати як заповнювач для запиту."
},
"customEngine": {
+ "title": "Оберіть пошукову систему",
"label": "URL-адреса запиту",
"placeholder": "Власна URL-адреса запиту"
},
"searchNewTab": {
"label": "Відкрити результати пошуку у новій вкладці"
+ },
+ "searchEnabled": {
+ "label": "Пошук увімкнено"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/uk/settings/general/widget-positions.json b/public/locales/uk/settings/general/widget-positions.json
index 71c4f67e7..0967ef424 100644
--- a/public/locales/uk/settings/general/widget-positions.json
+++ b/public/locales/uk/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Показувати віджети зліва"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/vi/authentication/login.json b/public/locales/vi/authentication/login.json
index 6db3fcb6c..4790ba53e 100644
--- a/public/locales/vi/authentication/login.json
+++ b/public/locales/vi/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "Chào mừng quay trở lại!",
- "text": "Vui lòng nhập mật khẩu",
+ "text": "",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "Mật khẩu của bạn đang được kiểm tra..."
},
"correct": {
- "title": "Mật khẩu chính xác, đang điều hướng..."
+ "title": ""
},
"wrong": {
- "title": "Sai mật khẩu, xin hãy thử lại."
+ "title": ""
}
}
}
diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json
index 06605cea9..53716f998 100644
--- a/public/locales/vi/common.json
+++ b/public/locales/vi/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "Lưu"
+ "save": "",
+ "about": "",
+ "cancel": "Hủy",
+ "close": "",
+ "delete": "Xóa",
+ "ok": "",
+ "edit": "Sửa",
+ "version": "",
+ "changePosition": "",
+ "remove": "Xóa",
+ "removeConfirm": "",
+ "sections": {
+ "settings": "Cài đặt",
+ "dangerZone": "Khu vực nguy hiểm"
+ },
+ "secrets": {
+ "apiKey": "",
+ "username": "Tên người dùng",
+ "password": "Mật khẩu"
},
"tip": "Mẹo: ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "phút",
"hours": "giờ"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/vi/layout/add-service-app-shelf.json b/public/locales/vi/layout/add-service-app-shelf.json
index 9b433e8a9..c5ab25b42 100644
--- a/public/locales/vi/layout/add-service-app-shelf.json
+++ b/public/locales/vi/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "Tùy chọn nâng cao",
"form": {
- "httpStatusCodes": {
- "label": "Mã trạng thái HTTP",
- "placeholder": "Chọn một mã trạng thái hợp lệ",
- "clearButtonLabel": "Xóa lựa chọn",
- "nothingFound": "Không tìm thấy"
- },
"openServiceInNewTab": {
"label": "Mở trong tab mới"
},
diff --git a/public/locales/vi/layout/element-selector/selector.json b/public/locales/vi/layout/element-selector/selector.json
new file mode 100644
index 000000000..c1409b3d4
--- /dev/null
+++ b/public/locales/vi/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "Thêm ô mới",
+ "text": ""
+ },
+ "widgetDescription": "",
+ "goBack": "Quay lại bước trước",
+ "actionIcon": {
+ "tooltip": "Thêm ô"
+ }
+}
diff --git a/public/locales/vi/layout/header/actions/toggle-edit-mode.json b/public/locales/vi/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..f400cb962
--- /dev/null
+++ b/public/locales/vi/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "",
+ "button": {
+ "disabled": "Vào chế độ chỉnh sửa",
+ "enabled": "Lưu và thoát"
+ },
+ "popover": {
+ "title": "",
+ "text": ""
+ },
+ "screenSizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/vi/layout/mobile/drawer.json b/public/locales/vi/layout/mobile/drawer.json
new file mode 100644
index 000000000..2171e09ac
--- /dev/null
+++ b/public/locales/vi/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} thanh bên"
+}
diff --git a/public/locales/vi/layout/modals/about.json b/public/locales/vi/layout/modals/about.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/public/locales/vi/layout/modals/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/vi/layout/modals/add-app.json b/public/locales/vi/layout/modals/add-app.json
new file mode 100644
index 000000000..1f187d74a
--- /dev/null
+++ b/public/locales/vi/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "",
+ "behaviour": "",
+ "network": "Mạng",
+ "appearance": "",
+ "integration": ""
+ },
+ "general": {
+ "appname": {
+ "label": "",
+ "description": ""
+ },
+ "internalAddress": {
+ "label": "",
+ "description": ""
+ },
+ "externalAddress": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "",
+ "description": ""
+ },
+ "statusCodes": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "",
+ "description": ""
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "",
+ "description": "",
+ "placeholder": "",
+ "defined": "",
+ "undefined": "",
+ "public": "",
+ "private": "",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "",
+ "warning": "",
+ "clear": "",
+ "save": "",
+ "update": ""
+ }
+ },
+ "validation": {
+ "popover": ""
+ }
+}
diff --git a/public/locales/vi/layout/modals/change-position.json b/public/locales/vi/layout/modals/change-position.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/public/locales/vi/layout/modals/change-position.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/locales/vi/layout/screen-sizes.json b/public/locales/vi/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/vi/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/vi/layout/tools.json b/public/locales/vi/layout/tools.json
new file mode 100644
index 000000000..ffbe07e69
--- /dev/null
+++ b/public/locales/vi/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": ""
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "",
+ "searchLimitationTitle": "",
+ "searchLimitationMessage": ""
+ }
+}
\ No newline at end of file
diff --git a/public/locales/vi/modules/calendar.json b/public/locales/vi/modules/calendar.json
index d1faa1671..0cb76aead 100644
--- a/public/locales/vi/modules/calendar.json
+++ b/public/locales/vi/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "Lịch",
- "description": "Một mô-đun lịch để hiển thị các lượt phát hành sắp tới. Nó tương tác với API của Sonarr và Radarr.",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "Đặt đầu tuần là Chủ Nhật"
+ },
+ "radarrReleaseType": {
+ "label": "Loại phát hành Radarr"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/common.json b/public/locales/vi/modules/common.json
index d25fd6088..014084f9d 100644
--- a/public/locales/vi/modules/common.json
+++ b/public/locales/vi/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "Cài đặt"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/dashdot.json b/public/locales/vi/modules/dashdot.json
index ca91e7fdf..138a7df1b 100644
--- a/public/locales/vi/modules/dashdot.json
+++ b/public/locales/vi/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "Một mô-đun để hiển thị biểu đồ cho quá trình Dash. của bạn.",
+ "description": "",
"settings": {
+ "title": "Cài đặt cho tiện ích Dash.",
"cpuMultiView": {
"label": "Chế độ hiện đa nhân CPU"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/date.json b/public/locales/vi/modules/date.json
index d7ee3ee79..1c8ef3c53 100644
--- a/public/locales/vi/modules/date.json
+++ b/public/locales/vi/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "Ngày giờ",
- "description": "Hiển thị ngày và giờ trong một thẻ",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "Dùng thời gian 24 giờ"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/dlspeed.json b/public/locales/vi/modules/dlspeed.json
index 1805ef315..53a646394 100644
--- a/public/locales/vi/modules/dlspeed.json
+++ b/public/locales/vi/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Tốc độ tải",
- "description": "Hiển thị tốc độ tải xuống của các dịch vụ được hỗ trợ"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/docker.json b/public/locales/vi/modules/docker.json
index cbd35728f..59b46b1fa 100644
--- a/public/locales/vi/modules/docker.json
+++ b/public/locales/vi/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "Cho phép bạn dễ dàng quản lý các khoang chứa docker"
+ "description": ""
},
"search": {
"placeholder": "Tìm kiếm bằng tên khoang chứa hoặc tên hình ảnh"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "Thêm dịch vụ",
- "message": "Thêm dịch vụ vào Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "Khởi động lại"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Tích hợp Docker thất bại",
- "message": "Có phải bạn quên gắn ổ cắm docker không?"
+ "message": ""
},
"unknownError": {
"title": "Có lỗi xảy ra"
},
"oneServiceAtATime": {
- "title": "Vui lòng chỉ thêm từng dịch vụ một!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/overseerr.json b/public/locales/vi/modules/overseerr.json
index 4634603d4..797c59496 100644
--- a/public/locales/vi/modules/overseerr.json
+++ b/public/locales/vi/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "Cho phép bạn tìm và thêm phương tiện truyền thông từ Overseerr/Jellyseerr"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "Đánh dấu các mùa bạn muốn tải về",
+ "caption": "",
"table": {
"header": {
"season": "Mùa",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/ping.json b/public/locales/vi/modules/ping.json
index 47071817a..5af9ed697 100644
--- a/public/locales/vi/modules/ping.json
+++ b/public/locales/vi/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "Giúp bạn kiểm tra nếu dịch vụ đang hoạt động hoặc trả lại một mã trạng thái HTTP cụ thể."
+ "description": ""
},
"states": {
"online": "Trực tuyến {{response}}",
"offline": "Ngoại tuyến {{response}}",
"loading": "Đang tải..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/search.json b/public/locales/vi/modules/search.json
index 4261a4378..7515274db 100644
--- a/public/locales/vi/modules/search.json
+++ b/public/locales/vi/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "Thanh tìm kiếm",
- "description": "Thanh tìm kiếm để tìm trên web, Youtube, torrent hoặc Overseerr"
+ "description": ""
},
"input": {
"placeholder": "Tìm kiếm trên web..."
},
- "switched-to": "",
+ "switched-to": "Đổi sang",
"searchEngines": {
"search": {
- "name": "",
+ "name": "Web",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "YouTube",
+ "description": "Tìm kiếm trên YouTube"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "Torrent",
+ "description": "Tìm kiếm torrent"
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "Bạn có thể chọn thanh tìm kiếm bằng phím tắt ",
+ "switchedSearchEngine": "Đã đổi sang tìm kiếm bằng {{searchEngine}}"
+}
diff --git a/public/locales/vi/modules/torrents-status.json b/public/locales/vi/modules/torrents-status.json
index 2da764d24..f6873e629 100644
--- a/public/locales/vi/modules/torrents-status.json
+++ b/public/locales/vi/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Torrent",
- "description": "Hiển thị tốc độ tải xuống hiện tại của các dịch vụ được hỗ trợ",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "Ẩn torrent đã hoàn thành"
+ "title": "",
+ "refreshInterval": {
+ "label": ""
+ },
+ "displayCompletedTorrents": {
+ "label": ""
+ },
+ "displayStaleTorrents": {
+ "label": ""
}
}
},
@@ -23,7 +30,7 @@
}
},
"lineChart": {
- "title": "Tốc độ tải xuống hiện tại",
+ "title": "Tốc độ tải hiện tại",
"download": "Tải xuống: {{download}}",
"upload": "Tải lên: {{upload}}",
"timeSpan": "{{seconds}} giây trước",
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "Không tìm thấy ứng dụng tải xuống được hỗ trợ nào!",
- "text": "Hãy thêm dịch vụ tải xuống để xem các bản tải xuống của bạn"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "Đang tải..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/modules/usenet.json b/public/locales/vi/modules/usenet.json
index a1c2a1487..cc0b9478b 100644
--- a/public/locales/vi/modules/usenet.json
+++ b/public/locales/vi/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
- "description": "Cho phép bạn xem lịch sử và hàng chờ usenet (Sabnzbd hoặc NZBGet), cũng như tạm dừng và tiếp tục tải xuống"
+ "description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Không tìm thấy ứng dụng tải xuống được hỗ trợ nào!",
- "text": "Hãy thêm dịch vụ tải xuống để xem các bản tải xuống của bạn"
+ "text": ""
}
}
},
diff --git a/public/locales/vi/modules/weather.json b/public/locales/vi/modules/weather.json
index d6175e6c6..58c80242c 100644
--- a/public/locales/vi/modules/weather.json
+++ b/public/locales/vi/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Thời tiết",
- "description": "Kiểm tra thời tiết hiện tại theo khu vực của bạn",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "Hiển thị bằng Fahrenheit"
},
@@ -29,4 +30,4 @@
"unknown": "Không xác định"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/settings/common.json b/public/locales/vi/settings/common.json
index 7f26fc342..af4b77ccb 100644
--- a/public/locales/vi/settings/common.json
+++ b/public/locales/vi/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "Cá nhân hoá"
},
"tips": {
- "configTip": "Tải tệp cấu hình của bạn lên chỉ bằng cách kéo và thả tệp vào trong trang!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "From @ with ❤️"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "Khung giãn (chiếm toàn bộ không gian)",
+ "layout": {
+ "title": "",
+ "main": "",
+ "sidebar": "",
+ "cannotturnoff": "",
+ "dashboardlayout": "",
+ "enablersidebar": "",
+ "enablelsidebar": "",
+ "enablesearchbar": "",
+ "enabledocker": "",
+ "enableping": "",
+ "enablelsidebardesc": "",
+ "enablersidebardesc": ""
+ }
+}
diff --git a/public/locales/vi/settings/customization/page-appearance.json b/public/locales/vi/settings/customization/page-appearance.json
index 7db328370..870fdd627 100644
--- a/public/locales/vi/settings/customization/page-appearance.json
+++ b/public/locales/vi/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "Tiêu đề trang",
- "placeholder": "Homarr 🦞"
+ "label": "Tiêu đề trang"
+ },
+ "metaTitle": {
+ "label": "Tiêu đề meta"
},
"logo": {
"label": "Logo"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "CSS tuỳ chỉnh",
- "placeholder": "CSS tùy chỉnh sẽ được áp dụng sau cùng"
+ "placeholder": ""
},
"buttons": {
"submit": "Gửi"
diff --git a/public/locales/vi/settings/general/config-changer.json b/public/locales/vi/settings/general/config-changer.json
index 2e4aa86c3..18118c77e 100644
--- a/public/locales/vi/settings/general/config-changer.json
+++ b/public/locales/vi/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "Trình nạp cấu hình"
+ "label": "",
+ "description": "",
+ "loadingNew": "",
+ "pleaseWait": ""
},
"modal": {
- "title": "Chọn tên cho cấu hình mới của bạn",
- "form": {
- "configName": {
- "label": "Tên cấu hình",
- "placeholder": "Tên cấu hình mới của bạn"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "Xác nhận"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "Đã lưu cấu hình",
- "message": "Đã lưu cấu hình thành {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "Xoá cấu hình thất bại",
"message": "Xoá cấu hình thất bại"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "Kéo thả tệp vào đây để tải cấu hình lên. Chỉ hỗ trợ JSON."
+ "title": "",
+ "text": ""
},
"reject": {
- "text": "Định dạng tệp này không được hỗ trợ. Vui lòng chỉ tải lên JSON."
+ "title": "",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/settings/general/module-enabler.json b/public/locales/vi/settings/general/module-enabler.json
index d223fdff9..9e26dfeeb 100644
--- a/public/locales/vi/settings/general/module-enabler.json
+++ b/public/locales/vi/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "Kích hoạt mô-đun"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/vi/settings/general/search-engine.json b/public/locales/vi/settings/general/search-engine.json
index 57b7eca2f..1e93e7d6e 100644
--- a/public/locales/vi/settings/general/search-engine.json
+++ b/public/locales/vi/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "Công cụ tìm kiếm",
+ "configurationName": "Thiết lập công cụ tìm kiếm",
"tips": {
- "generalTip": "Sử dụng các tiền tố !yt và !t trước truy vấn để tìm kiếm trên YouTube hoặc torrent.",
+ "generalTip": "",
"placeholderTip": "%s có thể được sử dụng làm phần giữ chỗ cho truy vấn."
},
"customEngine": {
+ "title": "Công cụ tìm kiếm tuỳ chỉnh",
"label": "URL truy vấn",
"placeholder": "URL truy vấn tuỳ chỉnh"
},
"searchNewTab": {
"label": "Mở kết quả tìm kiếm trong tab mới"
+ },
+ "searchEnabled": {
+ "label": "Đã bật tìm kiếm"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/vi/settings/general/widget-positions.json b/public/locales/vi/settings/general/widget-positions.json
index 4cdc1c02d..0967ef424 100644
--- a/public/locales/vi/settings/general/widget-positions.json
+++ b/public/locales/vi/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "Đặt tiện ích ở bên trái"
-}
\ No newline at end of file
+{}
diff --git a/public/locales/zh/authentication/login.json b/public/locales/zh/authentication/login.json
index dfd8bae39..65585a5ec 100644
--- a/public/locales/zh/authentication/login.json
+++ b/public/locales/zh/authentication/login.json
@@ -1,6 +1,6 @@
{
"title": "欢迎回来!",
- "text": "请输入密码",
+ "text": "请输入密码。",
"form": {
"fields": {
"password": {
@@ -18,10 +18,10 @@
"message": "正在检查你的密码..."
},
"correct": {
- "title": "密码正确,正在重定向..."
+ "title": "登录成功,正在跳转..."
},
"wrong": {
- "title": "密码错误,请重试。"
+ "title": "密码错误,请重新输入。"
}
}
}
diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json
index 480023b58..db36b5396 100644
--- a/public/locales/zh/common.json
+++ b/public/locales/zh/common.json
@@ -1,6 +1,23 @@
{
- "actions": {
- "save": "保存"
+ "save": "保存",
+ "about": "关于",
+ "cancel": "取消",
+ "close": "关闭",
+ "delete": "删除",
+ "ok": "确定",
+ "edit": "编辑",
+ "version": "版本",
+ "changePosition": "改变位置",
+ "remove": "移除",
+ "removeConfirm": "确定要删除 {{item}} 吗?",
+ "sections": {
+ "settings": "设置",
+ "dangerZone": "危险操作"
+ },
+ "secrets": {
+ "apiKey": "Api密钥",
+ "username": "用户名",
+ "password": "密码"
},
"tip": "提示。 ",
"time": {
@@ -8,4 +25,4 @@
"minutes": "分钟",
"hours": "小时"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/zh/layout/add-service-app-shelf.json b/public/locales/zh/layout/add-service-app-shelf.json
index 9c25c9e7c..b7762da09 100644
--- a/public/locales/zh/layout/add-service-app-shelf.json
+++ b/public/locales/zh/layout/add-service-app-shelf.json
@@ -113,12 +113,6 @@
"advancedOptions": {
"title": "高级选项",
"form": {
- "httpStatusCodes": {
- "label": "HTTP状态代码",
- "placeholder": "选择有效的状态代码",
- "clearButtonLabel": "清除选择项",
- "nothingFound": "没有找到"
- },
"openServiceInNewTab": {
"label": "在新标签中打开应用"
},
diff --git a/public/locales/zh/layout/element-selector/selector.json b/public/locales/zh/layout/element-selector/selector.json
new file mode 100644
index 000000000..6b861a95a
--- /dev/null
+++ b/public/locales/zh/layout/element-selector/selector.json
@@ -0,0 +1,11 @@
+{
+ "modal": {
+ "title": "添加新磁贴",
+ "text": "磁贴是Homarr的主要组成元素。它们被用来显示你的应用程序和其他信息。你可以根据需要增加任意数量的磁贴。"
+ },
+ "widgetDescription": "小组件与你的应用程序交互,提供更多的应用程序控制。在使用前通常需要额外的配置。",
+ "goBack": "上一步",
+ "actionIcon": {
+ "tooltip": "添加磁贴"
+ }
+}
diff --git a/public/locales/zh/layout/header/actions/toggle-edit-mode.json b/public/locales/zh/layout/header/actions/toggle-edit-mode.json
new file mode 100644
index 000000000..a0f191283
--- /dev/null
+++ b/public/locales/zh/layout/header/actions/toggle-edit-mode.json
@@ -0,0 +1,16 @@
+{
+ "description": "在编辑模式下,你可以调整磁贴和配置应用程序。在退出编辑模式之前不会保存更改。",
+ "button": {
+ "disabled": "进入编辑模式",
+ "enabled": "退出并保存"
+ },
+ "popover": {
+ "title": "",
+ "text": "现在你可以调整和配置你的应用程序,在退出编辑模式之前不会保存 更改 。"
+ },
+ "screenSizes": {
+ "small": "小",
+ "medium": "中",
+ "large": "大"
+ }
+}
diff --git a/public/locales/zh/layout/mobile/drawer.json b/public/locales/zh/layout/mobile/drawer.json
new file mode 100644
index 000000000..d06d8c4a9
--- /dev/null
+++ b/public/locales/zh/layout/mobile/drawer.json
@@ -0,0 +1,3 @@
+{
+ "title": "{{position}} 侧边栏"
+}
diff --git a/public/locales/zh/layout/modals/about.json b/public/locales/zh/layout/modals/about.json
new file mode 100644
index 000000000..76fdc070d
--- /dev/null
+++ b/public/locales/zh/layout/modals/about.json
@@ -0,0 +1,7 @@
+{
+ "description": "Homarr是一个 顺滑 、 现代化 的面板,它把你所有的应用和服务汇于指尖。有了Homarr,你可以在一个页面访问和控制一切。Homarr与你添加的应用无缝交互,为你提供有价值的信息并由你完全把控。安装Homarr轻松简单,并且支持多种部署方式。",
+ "i18n": "加载的I18n翻译命名空间",
+ "locales": "配置的I18n语言",
+ "contact": "遇到困难或问题?请与我们联系!",
+ "addToDashboard": "添加到面板"
+}
diff --git a/public/locales/zh/layout/modals/add-app.json b/public/locales/zh/layout/modals/add-app.json
new file mode 100644
index 000000000..751e27830
--- /dev/null
+++ b/public/locales/zh/layout/modals/add-app.json
@@ -0,0 +1,68 @@
+{
+ "tabs": {
+ "general": "一般",
+ "behaviour": "行为",
+ "network": "网络",
+ "appearance": "外观",
+ "integration": "融合"
+ },
+ "general": {
+ "appname": {
+ "label": "应用名称",
+ "description": "用于在面板上显示应用。"
+ },
+ "internalAddress": {
+ "label": "内部地址",
+ "description": "应用的内部IP地址。"
+ },
+ "externalAddress": {
+ "label": "外部地址",
+ "description": "点击应用时打开的URL。"
+ }
+ },
+ "behaviour": {
+ "isOpeningNewTab": {
+ "label": "在新标签中打开",
+ "description": "在新标签中打开应用,而不是当前标签。"
+ }
+ },
+ "network": {
+ "statusChecker": {
+ "label": "状态检测",
+ "description": "使用简单的HTTP(S) 请求检查你的应用是否在线。"
+ },
+ "statusCodes": {
+ "label": "HTTP状态码",
+ "description": "被视为在线的 HTTP 状态码。"
+ }
+ },
+ "appearance": {
+ "icon": {
+ "label": "应用图标",
+ "description": "将在面板上显示的图标。"
+ }
+ },
+ "integration": {
+ "type": {
+ "label": "集成配置",
+ "description": "将用于连接您的应用的集成配置。",
+ "placeholder": "选择一个整合",
+ "defined": "已定义",
+ "undefined": "未定义",
+ "public": "公开的",
+ "private": "私有的",
+ "explanationPrivate": "",
+ "explanationPublic": ""
+ },
+ "secrets": {
+ "description": "要更新一个秘密,输入一个值并点击保存按钮。要删除一个秘密,请使用清除按钮。",
+ "warning": "",
+ "clear": "清除密钥",
+ "save": "保存密钥",
+ "update": "更新密钥"
+ }
+ },
+ "validation": {
+ "popover": "你的表单包含无效数据,因此它不能被保存。请解决所有问题,并再次点击此按钮保存您的更改。"
+ }
+}
diff --git a/public/locales/zh/layout/modals/change-position.json b/public/locales/zh/layout/modals/change-position.json
new file mode 100644
index 000000000..3b1156da0
--- /dev/null
+++ b/public/locales/zh/layout/modals/change-position.json
@@ -0,0 +1,8 @@
+{
+ "xPosition": "X轴位置",
+ "width": "宽度",
+ "height": "高度",
+ "yPosition": "Y轴位置",
+ "zeroOrHigher": "0或更高",
+ "betweenXandY": "在 {{min}} 和 {{max}}之间"
+}
\ No newline at end of file
diff --git a/public/locales/zh/layout/screen-sizes.json b/public/locales/zh/layout/screen-sizes.json
new file mode 100644
index 000000000..24e49a689
--- /dev/null
+++ b/public/locales/zh/layout/screen-sizes.json
@@ -0,0 +1,11 @@
+{
+ "popover": {
+ "title": "",
+ "description": ""
+ },
+ "sizes": {
+ "small": "",
+ "medium": "",
+ "large": ""
+ }
+}
diff --git a/public/locales/zh/layout/tools.json b/public/locales/zh/layout/tools.json
new file mode 100644
index 000000000..99582c6d0
--- /dev/null
+++ b/public/locales/zh/layout/tools.json
@@ -0,0 +1,10 @@
+{
+ "fallback": {
+ "title": "你目前没有任何工具"
+ },
+ "iconPicker": {
+ "textInputPlaceholder": "搜索图标...",
+ "searchLimitationTitle": "搜索范围限于 {{max}} 图标",
+ "searchLimitationMessage": "为了保持迅速和快捷,搜索范围仅限于 {{max}} 图标。使用搜索框来寻找更多的图标"
+ }
+}
\ No newline at end of file
diff --git a/public/locales/zh/modules/calendar.json b/public/locales/zh/modules/calendar.json
index 76ea4aeca..0958fd3a6 100644
--- a/public/locales/zh/modules/calendar.json
+++ b/public/locales/zh/modules/calendar.json
@@ -1,11 +1,15 @@
{
"descriptor": {
"name": "日历",
- "description": "一个用于显示即将发布的日历模块。它与Sonarr和Radarr API进行交互。",
+ "description": "",
"settings": {
+ "title": "",
"sundayStart": {
"label": "使用周日作为一周的开始"
+ },
+ "radarrReleaseType": {
+ "label": ""
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/common.json b/public/locales/zh/modules/common.json
index 4c489bf5e..d95286e50 100644
--- a/public/locales/zh/modules/common.json
+++ b/public/locales/zh/modules/common.json
@@ -1,5 +1,10 @@
{
"settings": {
"label": "设置"
+ },
+ "errors": {
+ "unmappedOptions": {
+ "text": ""
+ }
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/dashdot.json b/public/locales/zh/modules/dashdot.json
index 33771d348..cf270aab1 100644
--- a/public/locales/zh/modules/dashdot.json
+++ b/public/locales/zh/modules/dashdot.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "Dash.",
- "description": "一个显示运行中的仪表板实例的图表模块。",
+ "description": "",
"settings": {
+ "title": "为Dash.Widget设置",
"cpuMultiView": {
"label": "CPU多核视图"
},
@@ -50,4 +51,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/date.json b/public/locales/zh/modules/date.json
index 14d9991da..ac5fc650b 100644
--- a/public/locales/zh/modules/date.json
+++ b/public/locales/zh/modules/date.json
@@ -1,11 +1,12 @@
{
"descriptor": {
- "name": "日期",
- "description": "在卡片中显示当前时间和日期",
+ "name": "",
+ "description": "",
"settings": {
+ "title": "",
"display24HourFormat": {
"label": "显示完整时间 (24小时)"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/dlspeed.json b/public/locales/zh/modules/dlspeed.json
index f6090e6da..7896827d4 100644
--- a/public/locales/zh/modules/dlspeed.json
+++ b/public/locales/zh/modules/dlspeed.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "下载速度",
- "description": "显示支持的服务的当前下载速度"
+ "description": ""
},
"card": {
"table": {
@@ -32,4 +32,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/docker.json b/public/locales/zh/modules/docker.json
index 103e91ecc..a12b4be2a 100644
--- a/public/locales/zh/modules/docker.json
+++ b/public/locales/zh/modules/docker.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
- "description": "轻松管理你的docker容器"
+ "description": ""
},
"search": {
"placeholder": "按容器或镜像名称搜索"
@@ -25,8 +25,8 @@
},
"actionBar": {
"addService": {
- "title": "添加应用",
- "message": "添加服务到 Homarr"
+ "title": "",
+ "message": ""
},
"restart": {
"title": "重新启动"
@@ -68,16 +68,16 @@
"errors": {
"integrationFailed": {
"title": "Docker整合失败",
- "message": "你忘了挂载docker socket吗?"
+ "message": ""
},
"unknownError": {
"title": "出现了一个错误"
},
"oneServiceAtATime": {
- "title": "请一次只添加一项服务!"
+ "title": ""
}
},
"actionIcon": {
"tooltip": "Docker"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/overseerr.json b/public/locales/zh/modules/overseerr.json
index 07e2ab118..e03a944d3 100644
--- a/public/locales/zh/modules/overseerr.json
+++ b/public/locales/zh/modules/overseerr.json
@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Overseerr",
- "description": "允许你从Overseerr/Jellyseerr搜索和添加媒体"
+ "description": ""
},
"popup": {
"item": {
@@ -18,7 +18,7 @@
}
},
"seasonSelector": {
- "caption": "勾选你想要下载的季数",
+ "caption": "",
"table": {
"header": {
"season": "季数",
@@ -27,4 +27,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/ping.json b/public/locales/zh/modules/ping.json
index 7eaf68d92..76932212c 100644
--- a/public/locales/zh/modules/ping.json
+++ b/public/locales/zh/modules/ping.json
@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
- "description": "允许你检查服务是否启动或返回一个特定的HTTP状态代码。"
+ "description": ""
},
"states": {
"online": "在线 {{response}}",
"offline": "离线 {{response}}",
"loading": "正在加载..."
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/search.json b/public/locales/zh/modules/search.json
index e445878c4..0c64d70fe 100644
--- a/public/locales/zh/modules/search.json
+++ b/public/locales/zh/modules/search.json
@@ -1,30 +1,30 @@
{
"descriptor": {
"name": "搜索栏",
- "description": "搜索栏可搜索网页、Youtube、Torrents或Overseerr"
+ "description": ""
},
"input": {
"placeholder": "在网上搜索..."
},
- "switched-to": "",
+ "switched-to": "换成了",
"searchEngines": {
"search": {
- "name": "",
+ "name": "网络",
"description": ""
},
"youtube": {
- "name": "",
- "description": ""
+ "name": "录像带",
+ "description": "在Youtube上搜索"
},
"torrents": {
- "name": "",
- "description": ""
+ "name": "火炬",
+ "description": "搜索山洪病毒"
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
- "tip": "",
- "switchedSearchEngine": ""
-}
\ No newline at end of file
+ "tip": "你可以用快捷键选择搜索栏 ",
+ "switchedSearchEngine": "改为用 {{searchEngine}}进行搜索"
+}
diff --git a/public/locales/zh/modules/torrents-status.json b/public/locales/zh/modules/torrents-status.json
index c5a686a90..6a95ac2a3 100644
--- a/public/locales/zh/modules/torrents-status.json
+++ b/public/locales/zh/modules/torrents-status.json
@@ -1,10 +1,17 @@
{
"descriptor": {
- "name": "Torrent",
- "description": "显示支持的服务的当前下载速度",
+ "name": "",
+ "description": "",
"settings": {
- "hideComplete": {
- "label": "隐藏已完成的种子"
+ "title": "",
+ "refreshInterval": {
+ "label": "刷新时间间隔(秒)。"
+ },
+ "displayCompletedTorrents": {
+ "label": "显示已完成的洪流"
+ },
+ "displayStaleTorrents": {
+ "label": "显示陈旧的山洪"
}
}
},
@@ -32,9 +39,16 @@
},
"errors": {
"noDownloadClients": {
- "title": "没有找到支持的下载客户端!",
- "text": "添加下载服务,查看你当前的下载量"
+ "title": "",
+ "text": ""
+ },
+ "generic": {
+ "title": "发生了一个意外的错误",
+ "text": ""
}
+ },
+ "loading": {
+ "title": "正在加载..."
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/modules/usenet.json b/public/locales/zh/modules/usenet.json
index 9c8f005cb..53b38eec3 100644
--- a/public/locales/zh/modules/usenet.json
+++ b/public/locales/zh/modules/usenet.json
@@ -1,13 +1,13 @@
{
"descriptor": {
- "name": "",
+ "name": "おそれについて",
"description": ""
},
"card": {
"errors": {
"noDownloadClients": {
"title": "没有找到支持的下载客户端!",
- "text": "添加下载服务,查看你当前的下载量"
+ "text": ""
}
}
},
diff --git a/public/locales/zh/modules/weather.json b/public/locales/zh/modules/weather.json
index 081245a36..18affa0b3 100644
--- a/public/locales/zh/modules/weather.json
+++ b/public/locales/zh/modules/weather.json
@@ -1,8 +1,9 @@
{
"descriptor": {
"name": "天气",
- "description": "查看你所在地区的当前天气",
+ "description": "",
"settings": {
+ "title": "",
"displayInFahrenheit": {
"label": "显示为华氏度"
},
@@ -29,4 +30,4 @@
"unknown": "未知"
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/settings/common.json b/public/locales/zh/settings/common.json
index b35c3ca9b..ecda7b70d 100644
--- a/public/locales/zh/settings/common.json
+++ b/public/locales/zh/settings/common.json
@@ -6,10 +6,24 @@
"customizations": "个性化"
},
"tips": {
- "configTip": "将你的配置文件拖放到页面上,就可以上传了!"
+ "configTip": ""
},
"credits": {
"madeWithLove": "用❤️创造,出品于"
},
- "grow": ""
-}
\ No newline at end of file
+ "grow": "增长的网格(占用所有空间)",
+ "layout": {
+ "title": "仪表板布局",
+ "main": "主要的",
+ "sidebar": "侧边栏",
+ "cannotturnoff": "不能关闭",
+ "dashboardlayout": "仪表板布局",
+ "enablersidebar": "启用右边的侧边栏",
+ "enablelsidebar": "启用左边的侧边栏",
+ "enablesearchbar": "启用搜索栏",
+ "enabledocker": "启用docker集成",
+ "enableping": "启用平移功能",
+ "enablelsidebardesc": "可选的。只能用于应用程序和集成",
+ "enablersidebardesc": "可选的。只能用于应用程序和集成"
+ }
+}
diff --git a/public/locales/zh/settings/customization/color-selector.json b/public/locales/zh/settings/customization/color-selector.json
index c079d3d6f..98603c298 100644
--- a/public/locales/zh/settings/customization/color-selector.json
+++ b/public/locales/zh/settings/customization/color-selector.json
@@ -1,3 +1,3 @@
{
- "suffix": "{{color}} 颜色"
+ "suffix": "{{color}} 色"
}
\ No newline at end of file
diff --git a/public/locales/zh/settings/customization/opacity-selector.json b/public/locales/zh/settings/customization/opacity-selector.json
index 8c97ca75b..d7341529e 100644
--- a/public/locales/zh/settings/customization/opacity-selector.json
+++ b/public/locales/zh/settings/customization/opacity-selector.json
@@ -1,3 +1,3 @@
{
- "label": "应用程序的不透明度"
+ "label": "应用不透明度"
}
\ No newline at end of file
diff --git a/public/locales/zh/settings/customization/page-appearance.json b/public/locales/zh/settings/customization/page-appearance.json
index 82f7da30f..06f0ec79e 100644
--- a/public/locales/zh/settings/customization/page-appearance.json
+++ b/public/locales/zh/settings/customization/page-appearance.json
@@ -1,7 +1,9 @@
{
"pageTitle": {
- "label": "页面标题",
- "placeholder": "Homarr 🦞"
+ "label": "页面标题"
+ },
+ "metaTitle": {
+ "label": "元标题"
},
"logo": {
"label": "徽标"
@@ -14,7 +16,7 @@
},
"customCSS": {
"label": "自定义CSS",
- "placeholder": "自定义CSS将被最后执行"
+ "placeholder": "自定义 CSS 将在最后应用"
},
"buttons": {
"submit": "提交"
diff --git a/public/locales/zh/settings/general/config-changer.json b/public/locales/zh/settings/general/config-changer.json
index 20adb5740..9a52fcdff 100644
--- a/public/locales/zh/settings/general/config-changer.json
+++ b/public/locales/zh/settings/general/config-changer.json
@@ -1,20 +1,45 @@
{
"configSelect": {
- "label": "配置加载器"
+ "label": "",
+ "description": "",
+ "loadingNew": "加载你的配置...",
+ "pleaseWait": ""
},
"modal": {
- "title": "选择你的新配置的名称",
- "form": {
- "configName": {
- "label": "配置名称",
- "placeholder": "你的新配置名称"
+ "copy": {
+ "title": "",
+ "form": {
+ "configName": {
+ "label": "",
+ "validation": {
+ "required": "",
+ "notUnique": ""
+ },
+ "placeholder": ""
+ },
+ "submitButton": ""
},
- "submitButton": "确认"
+ "events": {
+ "configSaved": {
+ "title": "",
+ "message": ""
+ },
+ "configCopied": {
+ "title": "",
+ "message": ""
+ },
+ "configNotCopied": {
+ "title": "",
+ "message": ""
+ }
+ }
},
- "events": {
- "configSaved": {
- "title": "配置已保存",
- "message": "配置保存为 {{configName}}"
+ "confirmDeletion": {
+ "title": "",
+ "warningText": "",
+ "text": "",
+ "buttons": {
+ "confirm": ""
}
}
},
@@ -30,6 +55,10 @@
"deleteFailed": {
"title": "配置删除失败",
"message": "配置删除失败"
+ },
+ "deleteFailedDefaultConfig": {
+ "title": "",
+ "message": ""
}
}
},
@@ -46,10 +75,12 @@
}
},
"accept": {
- "text": "在这里拖动文件来上传配置。只支持JSON。"
+ "title": "配置上传",
+ "text": ""
},
"reject": {
- "text": "不支持这种文件格式。请只上传JSON。"
+ "title": "拒绝拖拽式上传",
+ "text": ""
}
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/settings/general/module-enabler.json b/public/locales/zh/settings/general/module-enabler.json
index 10852fa26..9e26dfeeb 100644
--- a/public/locales/zh/settings/general/module-enabler.json
+++ b/public/locales/zh/settings/general/module-enabler.json
@@ -1,3 +1 @@
-{
- "title": "启用模块"
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/public/locales/zh/settings/general/search-engine.json b/public/locales/zh/settings/general/search-engine.json
index 25ec90673..3df9c1344 100644
--- a/public/locales/zh/settings/general/search-engine.json
+++ b/public/locales/zh/settings/general/search-engine.json
@@ -1,14 +1,19 @@
{
"title": "搜索引擎",
+ "configurationName": "搜索引擎设置",
"tips": {
- "generalTip": "在查询前使用前缀 !yt和 !t,可以在YouTube或Torrent中搜索。",
+ "generalTip": "",
"placeholderTip": "%s 可以作为查询的占位符。"
},
"customEngine": {
+ "title": "自定义搜索引擎",
"label": "查询网址",
"placeholder": "自定义查询网址"
},
"searchNewTab": {
"label": "在新选项卡中打开搜索结果页"
+ },
+ "searchEnabled": {
+ "label": "启用搜索"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/settings/general/widget-positions.json b/public/locales/zh/settings/general/widget-positions.json
index 60e015a6a..0967ef424 100644
--- a/public/locales/zh/settings/general/widget-positions.json
+++ b/public/locales/zh/settings/general/widget-positions.json
@@ -1,3 +1 @@
-{
- "label": "将小部件放在左边"
-}
\ No newline at end of file
+{}
diff --git a/src/components/About/AboutModal.tsx b/src/components/About/AboutModal.tsx
new file mode 100644
index 000000000..8d258ebd3
--- /dev/null
+++ b/src/components/About/AboutModal.tsx
@@ -0,0 +1,241 @@
+import {
+ ActionIcon,
+ Anchor,
+ Badge,
+ Button,
+ createStyles,
+ Divider,
+ Group,
+ HoverCard,
+ Modal,
+ Table,
+ Text,
+ Title,
+} from '@mantine/core';
+import {
+ IconBrandDiscord,
+ IconBrandGithub,
+ IconFile,
+ IconLanguage,
+ IconSchema,
+ IconVersions,
+ IconVocabulary,
+ IconWorldWww,
+} from '@tabler/icons';
+import { motion } from 'framer-motion';
+import { InitOptions } from 'i18next';
+import { i18n, Trans, useTranslation } from 'next-i18next';
+import Image from 'next/image';
+import { ReactNode } from 'react';
+import { CURRENT_VERSION } from '../../../data/constants';
+import { useConfigContext } from '../../config/provider';
+import { useConfigStore } from '../../config/store';
+import { usePrimaryGradient } from '../layout/useGradient';
+import Credits from '../Settings/Common/Credits';
+
+interface AboutModalProps {
+ opened: boolean;
+ closeModal: () => void;
+ newVersionAvailable?: string;
+}
+
+export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutModalProps) => {
+ const { classes } = useStyles();
+ const colorGradiant = usePrimaryGradient();
+ const informations = useInformationTableItems(newVersionAvailable);
+ const { t } = useTranslation(['common', 'layout/modals/about']);
+
+ return (
+ closeModal()}
+ opened={opened}
+ title={
+
+
+
+ {t('about')} Homarr
+
+
+ }
+ size="xl"
+ >
+
+
+
+
+
+
+ {informations.map((item, index) => (
+
+
+
+
+ {item.icon}
+
+ {t(item.label)}
+
+
+ {item.content}
+
+ ))}
+
+
+
+
+ {t('layout/modals/about:contact')}
+
+
+
+ }
+ variant="default"
+ >
+ GitHub
+
+ }
+ variant="default"
+ >
+ Documentation
+
+ }
+ variant="default"
+ >
+ Discord
+
+
+
+
+ );
+};
+
+interface InformationTableItem {
+ icon: ReactNode;
+ label: string;
+ content: ReactNode;
+}
+
+interface ExtendedInitOptions extends InitOptions {
+ locales: string[];
+}
+
+const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => {
+ // TODO: Fix this to not request. Pass it as a prop.
+ const colorGradiant = usePrimaryGradient();
+
+ const { configVersion } = useConfigContext();
+ const { configs } = useConfigStore();
+
+ let items: InformationTableItem[] = [];
+
+ if (i18n !== null) {
+ const usedI18nNamespaces = i18n.reportNamespaces.getUsedNamespaces();
+ const initOptions = i18n.options as ExtendedInitOptions;
+
+ items = [
+ ...items,
+ {
+ icon: ,
+ label: 'layout/modals/about:i18n',
+ content: (
+
+ {usedI18nNamespaces.length}
+
+ ),
+ },
+ {
+ icon: ,
+ label: 'layout/modals/about:locales',
+ content: (
+
+ {initOptions.locales.length}
+
+ ),
+ },
+ ];
+ }
+
+ items = [
+ {
+ icon: ,
+ label: 'Configuration schema version',
+ content: (
+
+ {configVersion}
+
+ ),
+ },
+ {
+ icon: ,
+ label: 'Available configurations',
+ content: (
+
+ {configs.length}
+
+ ),
+ },
+ {
+ icon: ,
+ label: 'version',
+ content: (
+
+
+ {CURRENT_VERSION}
+
+ {newVersionAvailable && (
+
+
+
+
+ new: {newVersionAvailable}
+
+
+
+
+ Version{' '}
+
+
+ {newVersionAvailable}
+
+ {' '}
+ is available ! Current version: {CURRENT_VERSION}
+
+
+ )}
+
+ ),
+ },
+ ...items,
+ ];
+
+ return items;
+};
+
+const useStyles = createStyles(() => ({
+ informationTableColumn: {
+ textAlign: 'right',
+ },
+ informationIcon: {
+ cursor: 'default',
+ },
+}));
diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx
deleted file mode 100644
index 01dd3da81..000000000
--- a/src/components/AppShelf/AddAppShelfItem.tsx
+++ /dev/null
@@ -1,490 +0,0 @@
-import {
- ActionIcon,
- Anchor,
- Button,
- Center,
- Group,
- Image,
- LoadingOverlay,
- Modal,
- MultiSelect,
- PasswordInput,
- Select,
- Space,
- Stack,
- Switch,
- Tabs,
- TextInput,
- Title,
- Tooltip,
-} from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { useDebouncedValue } from '@mantine/hooks';
-import { IconApps } from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-import { useEffect, useState } from 'react';
-import { v4 as uuidv4 } from 'uuid';
-import { useConfig } from '../../tools/state';
-import { tryMatchPort, ServiceTypeList, StatusCodes, Config } from '../../tools/types';
-import apiKeyPaths from './apiKeyPaths.json';
-import Tip from '../layout/Tip';
-
-export function AddItemShelfButton(props: any) {
- const { config, setConfig } = useConfig();
- const [opened, setOpened] = useState(false);
- const { t } = useTranslation('layout/add-service-app-shelf');
- return (
- <>
- {t('modal.title')}}
- opened={props.opened || opened}
- onClose={() => setOpened(false)}
- >
-
-
-
- setOpened(true)}
- >
-
-
-
- >
- );
-}
-
-function MatchIcon(name: string | undefined, form: any) {
- if (name === undefined || name === '') return null;
- fetch(
- `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name
- .replace(/\s+/g, '-')
- .toLowerCase()
- .replace(/^dash\.$/, 'dashdot')}.png`
- ).then((res) => {
- if (res.ok) {
- form.setFieldValue('icon', res.url);
- }
- });
-
- return false;
-}
-
-function MatchService(name: string, form: any) {
- const service = ServiceTypeList.find((s) => s.toLowerCase() === name.toLowerCase());
- if (service) {
- form.setFieldValue('type', service);
- }
-}
-
-const DEFAULT_ICON = '/imgs/favicon/favicon.png';
-
-interface AddAppShelfItemFormProps {
- setOpened: (b: boolean) => void;
- config: Config;
- setConfig: (config: Config) => void;
- // Any other props you want to pass to the form
- [key: string]: any;
-}
-
-export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
- const { setOpened, config, setConfig } = props;
- // Only get config and setConfig from useCOnfig if they are not present in props
- const [isLoading, setLoading] = useState(false);
- const { t } = useTranslation('layout/add-service-app-shelf');
-
- // Extract all the categories from the services in config
- const InitialCategories = config.services.reduce((acc, cur) => {
- if (cur.category && !acc.includes(cur.category)) {
- acc.push(cur.category);
- }
- return acc;
- }, [] as string[]);
- const [categories, setCategories] = useState(InitialCategories);
-
- const form = useForm({
- initialValues: {
- id: props.id ?? uuidv4(),
- type: props.type ?? 'Other',
- category: props.category ?? null,
- name: props.name ?? '',
- icon: props.icon ?? DEFAULT_ICON,
- url: props.url ?? '',
- apiKey: props.apiKey ?? undefined,
- username: props.username ?? undefined,
- password: props.password ?? undefined,
- openedUrl: props.openedUrl ?? undefined,
- ping: props.ping ?? true,
- status: props.status ?? ['200'],
- newTab: props.newTab ?? true,
- },
- validate: {
- apiKey: () => null,
- // Validate icon with a regex
- icon: (value: string) =>
- // Disable matching to allow any values
- null,
- // Validate url with a regex http/https
- url: (value: string) => {
- try {
- const _isValid = new URL(value);
- } catch (e) {
- return t('modal.form.validation.invalidUrl');
- }
- return null;
- },
- status: (value: string[]) => {
- if (!value.length) {
- return t('modal.form.validation.noStatusCodeSelected');
- }
- return null;
- },
- },
- });
-
- const [debounced, cancel] = useDebouncedValue(form.values.name, 250);
- useEffect(() => {
- if (
- form.values.name !== debounced ||
- form.values.icon !== DEFAULT_ICON ||
- form.values.type !== 'Other'
- ) {
- return;
- }
- MatchIcon(form.values.name, form);
- MatchService(form.values.name, form);
- tryMatchPort(form.values.name, form);
- }, [debounced]);
-
- // Try to set const hostname to new URL(form.values.url).hostname)
- // If it fails, set it to the form.values.url
- let hostname = form.values.url;
- try {
- hostname = new URL(form.values.url).origin;
- } catch (e) {
- // Do nothing
- }
-
- return (
- <>
-
-
-
-
- >
- );
-}
diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx
deleted file mode 100644
index ff2d118c8..000000000
--- a/src/components/AppShelf/AppShelf.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { useState } from 'react';
-import { Accordion, Grid, Stack, Title, useMantineColorScheme } from '@mantine/core';
-import {
- closestCenter,
- DndContext,
- DragOverlay,
- MouseSensor,
- TouchSensor,
- useSensor,
- useSensors,
-} from '@dnd-kit/core';
-
-import { arrayMove, SortableContext } from '@dnd-kit/sortable';
-import { useLocalStorage } from '@mantine/hooks';
-import { useTranslation } from 'next-i18next';
-import * as Modules from '../../modules';
-import { useConfig } from '../../tools/state';
-
-import { AppShelfItem, SortableItem } from './AppShelfItem';
-import { ModuleWrapper } from '../../modules/moduleWrapper';
-import { UsenetModule, TorrentsModule } from '../../modules';
-
-const AppShelf = (props: any) => {
- const { config, setConfig } = useConfig();
- // Extract all the categories from the services in config
- const categoryList = config.services.reduce((acc, cur) => {
- if (cur.category && !acc.includes(cur.category)) {
- acc.push(cur.category);
- }
- return acc;
- }, [] as string[]);
-
- const [toggledCategories, setToggledCategories] = useLocalStorage({
- key: 'app-shelf-toggled',
- // This is a bit of a hack to toggle the categories on the first load, return a string[] of the categories
- defaultValue: categoryList,
- });
- const [activeId, setActiveId] = useState(null);
- const { colorScheme } = useMantineColorScheme();
-
- const { t } = useTranslation('layout/app-shelf');
-
- const sensors = useSensors(
- useSensor(TouchSensor, {
- activationConstraint: {
- delay: 500,
- tolerance: 5,
- },
- }),
- useSensor(MouseSensor, {
- // Require the mouse to move by 10 pixels before activating
- activationConstraint: {
- delay: 500,
- tolerance: 5,
- },
- })
- );
-
- function handleDragStart(event: any) {
- const { active } = event;
-
- setActiveId(active.id);
- }
-
- function handleDragEnd(event: any) {
- const { active, over } = event;
-
- if (active.id !== over.id) {
- const newConfig = { ...config };
- const activeIndex = newConfig.services.findIndex((e) => e.id === active.id);
- const overIndex = newConfig.services.findIndex((e) => e.id === over.id);
- newConfig.services = arrayMove(newConfig.services, activeIndex, overIndex);
- setConfig(newConfig);
- }
-
- setActiveId(null);
- }
-
- const getItems = (filter?: string) => {
- // If filter is not set, return all the services without a category or a null category
- let filtered = config.services;
- const modules = Object.values(Modules).map((module) => module);
-
- if (!filter) {
- filtered = config.services.filter((e) => !e.category || e.category === null);
- }
- if (filter) {
- filtered = config.services.filter((e) => e.category === filter);
- }
- return (
-
-
-
- {filtered.map((service) => (
-
-
-
-
-
- ))}
-
-
-
- {activeId ? (
- e.id === activeId)} id={activeId} />
- ) : null}
-
-
- );
- };
-
- return (
-
- {
- setToggledCategories([...state]);
- }}
- >
- {categoryList.map((category, idx) => (
-
-
-
- {category}
-
-
- {getItems(category)}
-
- ))}
-
- {getItems()}
-
-
-
- );
-};
-
-export default AppShelf;
diff --git a/src/components/AppShelf/AppShelfItem.tsx b/src/components/AppShelf/AppShelfItem.tsx
deleted file mode 100644
index bbc51122c..000000000
--- a/src/components/AppShelf/AppShelfItem.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { useSortable } from '@dnd-kit/sortable';
-import { CSS } from '@dnd-kit/utilities';
-import {
- Anchor,
- AspectRatio,
- Card,
- Center,
- createStyles,
- Image,
- Text,
- useMantineColorScheme,
-} from '@mantine/core';
-import { motion } from 'framer-motion';
-import { useState } from 'react';
-import PingComponent from '../../modules/ping/PingModule';
-import { useConfig } from '../../tools/state';
-import { serviceItem } from '../../tools/types';
-import AppShelfMenu from './AppShelfMenu';
-
-const useStyles = createStyles((theme) => ({
- item: {
- transition: 'box-shadow 150ms ease, transform 100ms ease',
-
- '&:hover': {
- boxShadow: `${theme.shadows.md} !important`,
- transform: 'scale(1.05)',
- },
- [theme.fn.smallerThan('sm')]: {
- WebkitUserSelect: 'none',
- },
- },
-}));
-
-export function SortableItem(props: any) {
- const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
- id: props.id,
- });
-
- const style = {
- transform: CSS.Transform.toString(transform),
- transition,
- };
-
- return (
-
- {props.children}
-
- );
-}
-
-export function AppShelfItem(props: any) {
- const { service }: { service: serviceItem } = props;
- const [hovering, setHovering] = useState(false);
- const { config } = useConfig();
- const { colorScheme } = useMantineColorScheme();
- const { classes } = useStyles();
- return (
- {
- setHovering(true);
- }}
- onHoverEnd={() => {
- setHovering(false);
- }}
- >
-
-
-
-
- {service.name}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {service.ping !== false && }
-
-
-
-
- );
-}
diff --git a/src/components/AppShelf/AppShelfMenu.tsx b/src/components/AppShelf/AppShelfMenu.tsx
deleted file mode 100644
index 402867ac0..000000000
--- a/src/components/AppShelf/AppShelfMenu.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { ActionIcon, Menu, Modal, Text } from '@mantine/core';
-import { showNotification } from '@mantine/notifications';
-import { useState } from 'react';
-import { IconCheck as Check, IconEdit as Edit, IconMenu, IconTrash as Trash } from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { serviceItem } from '../../tools/types';
-import { AddAppShelfItemForm } from './AddAppShelfItem';
-import { useColorTheme } from '../../tools/color';
-
-export default function AppShelfMenu(props: any) {
- const { service }: { service: serviceItem } = props;
- const { config, setConfig } = useConfig();
- const { secondaryColor } = useColorTheme();
- const { t } = useTranslation('layout/app-shelf-menu');
- const [opened, setOpened] = useState(false);
- return (
- <>
- setOpened(false)}
- title={t('modal.title')}
- >
-
-
-
-
-
-
-
-
-
- {t('menu.labels.settings')}
- } onClick={() => setOpened(true)}>
- {t('menu.actions.edit')}
-
- {t('menu.labels.dangerZone')}
- {
- setConfig({
- ...config,
- services: config.services.filter((s) => s.id !== service.id),
- });
- showNotification({
- autoClose: 5000,
- title: (
-
- Service {service.name} removed successfully!
-
- ),
- color: 'green',
- icon: ,
- message: undefined,
- });
- }}
- icon={ }
- >
- {t('menu.actions.delete')}
-
-
-
- >
- );
-}
diff --git a/src/components/AppShelf/SmallServiceItem.tsx b/src/components/AppShelf/SmallServiceItem.tsx
deleted file mode 100644
index 98c8bbf53..000000000
--- a/src/components/AppShelf/SmallServiceItem.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Avatar, Group, Text } from '@mantine/core';
-
-interface smallServiceItem {
- label: string;
- icon?: string;
- url?: string;
-}
-
-export default function SmallServiceItem(props: any) {
- const { service }: { service: smallServiceItem } = props;
- // TODO : Use Next/link
- return (
-
- {service.icon && }
- {service.label}
-
- );
-}
diff --git a/src/components/AppShelf/apiKeyPaths.json b/src/components/AppShelf/apiKeyPaths.json
deleted file mode 100644
index 3ecd0f338..000000000
--- a/src/components/AppShelf/apiKeyPaths.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Jellyseerr": "settings",
- "Overseerr": "settings",
- "Sonarr": "settings/general",
- "Radarr": "settings/general",
- "Readarr": "settings/general",
- "Lidarr": "settings/general",
- "Sabnzbd": "sabnzbd/config/general"
-}
diff --git a/src/components/AppShelf/index.ts b/src/components/AppShelf/index.ts
deleted file mode 100644
index fd496bd5b..000000000
--- a/src/components/AppShelf/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as AppShelf } from './AppShelf';
-export * from './AppShelfItem';
diff --git a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx
deleted file mode 100644
index a167d0b94..000000000
--- a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import {
- createStyles,
- Switch,
- Group,
- useMantineColorScheme,
- Kbd,
- useMantineTheme,
-} from '@mantine/core';
-import { IconMoonStars, IconSun } from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-
-const useStyles = createStyles((theme) => ({
- root: {
- position: 'relative',
- '& *': {
- cursor: 'pointer',
- },
- },
-
- icon: {
- pointerEvents: 'none',
- position: 'absolute',
- zIndex: 1,
- top: 3,
- },
-
- iconLight: {
- left: 4,
- color: theme.white,
- },
-
- iconDark: {
- right: 4,
- color: theme.colors.gray[6],
- },
-}));
-
-export function ColorSchemeSwitch() {
- const { colorScheme, toggleColorScheme } = useMantineColorScheme();
- const { t } = useTranslation('settings/general/theme-selector');
- const theme = useMantineTheme();
- return (
-
- toggleColorScheme()}
- size="md"
- onLabel={ }
- offLabel={ }
- />
- {t('label', {
- theme: colorScheme === 'dark' ? 'light' : 'dark',
- })}
-
- Ctrl +J
-
-
- );
-}
diff --git a/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx b/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx
deleted file mode 100644
index 3fbe701ee..000000000
--- a/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Box, useMantineColorScheme } from '@mantine/core';
-import { IconSun as Sun, IconMoonStars as MoonStars } from '@tabler/icons';
-import { motion } from 'framer-motion';
-
-export function ColorSchemeToggle() {
- const { colorScheme, toggleColorScheme } = useMantineColorScheme();
-
- return (
-
- toggleColorScheme()}
- sx={(theme) => ({
- cursor: 'pointer',
- color: theme.colorScheme === 'dark' ? theme.colors.yellow[4] : theme.colors.blue[6],
- })}
- >
- {colorScheme === 'dark' ? : }
-
-
- );
-}
diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx
index 0e129bd2c..faa60f1f0 100644
--- a/src/components/Config/ConfigChanger.tsx
+++ b/src/components/Config/ConfigChanger.tsx
@@ -1,20 +1,36 @@
-import { Center, Loader, Select, Tooltip } from '@mantine/core';
+import { Center, Dialog, Loader, Notification, Select, Tooltip } from '@mantine/core';
+import { useToggle } from '@mantine/hooks';
+import { useQuery } from '@tanstack/react-query';
import { setCookie } from 'cookies-next';
import { useTranslation } from 'next-i18next';
-import { useEffect, useState } from 'react';
-import { useConfig } from '../../tools/state';
+import { useRouter } from 'next/router';
+import { useState } from 'react';
+import { useConfigContext } from '../../config/provider';
export default function ConfigChanger() {
- const { config, loadConfig, setConfig, getConfigs } = useConfig();
- const [configList, setConfigList] = useState([]);
- const [value, setValue] = useState(config.name);
- const { t } = useTranslation('settings/general/config-changer');
+ const router = useRouter();
+
+ const { t } = useTranslation('settings/general/config-changer');
+ const { name: configName, setConfigName } = useConfigContext();
+
+ const { data: configs, isLoading } = useConfigsQuery();
+ const [activeConfig, setActiveConfig] = useState(configName);
+ const [isRefreshing, toggle] = useToggle();
+
+ const onConfigChange = (value: string) => {
+ setCookie('config-name', value ?? 'default', {
+ maxAge: 60 * 60 * 24 * 30,
+ sameSite: 'strict',
+ });
+ setActiveConfig(value);
+ toggle();
+
+ router.push(`/${value}`);
+ setConfigName(value);
+ };
- useEffect(() => {
- getConfigs().then((configs) => setConfigList(configs));
- }, [config]);
// If configlist is empty, return a loading indicator
- if (configList.length === 0) {
+ if (isLoading || !configs || configs.length === 0 || !configName) {
return (
@@ -23,23 +39,36 @@ export default function ConfigChanger() {
);
}
- // return console.log(e)} value="1" />;
+
return (
- {
- loadConfig(e ?? 'default');
- setCookie('config-name', e ?? 'default', {
- maxAge: 60 * 60 * 24 * 30,
- sameSite: 'strict',
- });
- }}
- data={
- // If config list is empty, return the current config
- configList.length === 0 ? [config.name] : configList
- }
- />
+ <>
+
+ toggle()}
+ size="lg"
+ radius="md"
+ >
+
+ {t('configSelect.pleaseWait')}
+
+
+ >
);
}
+
+const useConfigsQuery = () =>
+ useQuery({
+ queryKey: ['config/get-all'],
+ queryFn: fetchConfigs,
+ });
+
+const fetchConfigs = async () => (await (await fetch('/api/configs')).json()) as string[];
diff --git a/src/components/Config/LoadConfig.tsx b/src/components/Config/LoadConfig.tsx
index a41f0e027..6f374bcd6 100644
--- a/src/components/Config/LoadConfig.tsx
+++ b/src/components/Config/LoadConfig.tsx
@@ -1,79 +1,97 @@
-import { Group, Text, useMantineTheme } from '@mantine/core';
-import { IconX as X, IconCheck as Check, IconX, IconPhoto, IconUpload } from '@tabler/icons';
-import { showNotification } from '@mantine/notifications';
-import { setCookie } from 'cookies-next';
+import { Group, Stack, Text, Title, useMantineTheme } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
+import { showNotification } from '@mantine/notifications';
+import { IconCheck as Check, IconPhoto, IconUpload, IconX, IconX as X } from '@tabler/icons';
+import Consola from 'consola';
+import { setCookie } from 'cookies-next';
import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
+import { useConfigStore } from '../../config/store';
+import { migrateConfig } from '../../tools/config/migrateConfig';
import { Config } from '../../tools/types';
-import { migrateToIdConfig } from '../../tools/migrate';
+import { ConfigType } from '../../types/config';
-export default function LoadConfigComponent(props: any) {
- const { setConfig } = useConfig();
+export const LoadConfigComponent = () => {
+ const { addConfig } = useConfigStore();
const theme = useMantineTheme();
const { t } = useTranslation('settings/general/config-changer');
return (
{
- files[0].text().then((e) => {
- try {
- JSON.parse(e) as Config;
- } catch (e) {
- showNotification({
- autoClose: 5000,
- title: {t('dropzone.notifications.invalidConfig.title')} ,
- color: 'red',
- icon: ,
- message: t('dropzone.notifications.invalidConfig.message'),
- });
- return;
- }
- const newConfig: Config = JSON.parse(e);
+ onDrop={async (files) => {
+ const fileName = files[0].name.replaceAll('.json', '');
+ const fileText = await files[0].text();
+
+ try {
+ JSON.parse(fileText) as ConfigType;
+ } catch (e) {
showNotification({
autoClose: 5000,
- radius: 'md',
- title: (
-
- {t('dropzone.notifications.loadedSuccessfully.title', {
- configName: newConfig.name,
- })}
-
- ),
- color: 'green',
- icon: ,
- message: undefined,
+ title: {t('dropzone.notifications.invalidConfig.title')} ,
+ color: 'red',
+ icon: ,
+ message: t('dropzone.notifications.invalidConfig.message'),
});
- setCookie('config-name', newConfig.name, {
- maxAge: 60 * 60 * 24 * 30,
- sameSite: 'strict',
- });
- const migratedConfig = migrateToIdConfig(newConfig);
- setConfig(migratedConfig);
+ return;
+ }
+
+ let newConfig: ConfigType = JSON.parse(fileText);
+
+ if (!newConfig.schemaVersion) {
+ Consola.warn(
+ 'a legacy configuration schema was deteced and migrated to the current schema'
+ );
+ const oldConfig = JSON.parse(fileText) as Config;
+ newConfig = migrateConfig(oldConfig);
+ }
+
+ await addConfig(fileName, newConfig, true);
+ showNotification({
+ autoClose: 5000,
+ radius: 'md',
+ title: (
+
+ {t('dropzone.notifications.loadedSuccessfully.title', {
+ configName: fileName,
+ })}
+
+ ),
+ color: 'green',
+ icon: ,
+ message: undefined,
+ });
+ setCookie('config-name', fileName, {
+ maxAge: 60 * 60 * 24 * 30,
+ sameSite: 'strict',
});
}}
accept={['application/json']}
>
-
+
- {t('dropzone.accept.text')}
-
+ {t('dropzone.accept.title')}
+
+ {t('dropzone.accept.text')}
+
+
-
+
- {t('dropzone.reject.text')}
-
+ {t('dropzone.reject.title')}
+
+ {t('dropzone.reject.text')}
+
+
@@ -81,4 +99,4 @@ export default function LoadConfigComponent(props: any) {
);
-}
+};
diff --git a/src/components/Config/SaveConfig.tsx b/src/components/Config/SaveConfig.tsx
deleted file mode 100644
index 327f50523..000000000
--- a/src/components/Config/SaveConfig.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { Button, Group, Modal, TextInput } from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { showNotification } from '@mantine/notifications';
-import axios from 'axios';
-import fileDownload from 'js-file-download';
-import { useState } from 'react';
-import { useTranslation } from 'next-i18next';
-import {
- IconCheck as Check,
- IconDownload as Download,
- IconPlus as Plus,
- IconTrash as Trash,
- IconX as X,
-} from '@tabler/icons';
-import { useConfig } from '../../tools/state';
-
-export default function SaveConfigComponent(props: any) {
- const [opened, setOpened] = useState(false);
- const { config, setConfig } = useConfig();
- const { t } = useTranslation('settings/general/config-changer');
- const form = useForm({
- initialValues: {
- configName: config.name,
- },
- });
- function onClick(e: any) {
- if (config) {
- fileDownload(JSON.stringify(config, null, '\t'), `${config.name}.json`);
- }
- }
- return (
-
- setOpened(false)} title={t('modal.title')}>
-
-
- } variant="outline" onClick={onClick}>
- {t('buttons.download')}
-
- }
- variant="outline"
- onClick={() => {
- axios
- .delete(`/api/configs/${config.name}`)
- .then(() => {
- showNotification({
- title: t('buttons.delete.notifications.deleted.title'),
- icon: ,
- color: 'green',
- autoClose: 1500,
- radius: 'md',
- message: t('buttons.delete.notifications.deleted.message'),
- });
- })
- .catch(() => {
- showNotification({
- title: t('buttons.delete.notifications.deleteFailed.title'),
- icon: ,
- color: 'red',
- autoClose: 1500,
- radius: 'md',
- message: t('buttons.delete.notifications.deleteFailed.message'),
- });
- });
- setConfig({ ...config, name: 'default' });
- }}
- >
- {t('buttons.delete.text')}
-
- } variant="outline" onClick={() => setOpened(true)}>
- {t('buttons.saveCopy')}
-
-
- );
-}
diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx
new file mode 100644
index 000000000..35acd4d39
--- /dev/null
+++ b/src/components/Dashboard/Dashboard.tsx
@@ -0,0 +1,16 @@
+import { MobileRibbons } from './Mobile/Ribbon/MobileRibbon';
+import { DashboardDetailView } from './Views/DetailView';
+import { DashboardEditView } from './Views/EditView';
+import { useEditModeStore } from './Views/useEditModeStore';
+
+export const Dashboard = () => {
+ const isEditMode = useEditModeStore((x) => x.enabled);
+
+ return (
+ <>
+ {/* The following elemens are splitted because gridstack doesn't reinitialize them when using same item. */}
+ {isEditMode ? : }
+
+ >
+ );
+};
diff --git a/src/components/Dashboard/Mobile/Ribbon/MobileRibbon.tsx b/src/components/Dashboard/Mobile/Ribbon/MobileRibbon.tsx
new file mode 100644
index 000000000..66b38249f
--- /dev/null
+++ b/src/components/Dashboard/Mobile/Ribbon/MobileRibbon.tsx
@@ -0,0 +1,87 @@
+import { ActionIcon, createStyles, Space } from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { IconChevronLeft, IconChevronRight } from '@tabler/icons';
+import { useConfigContext } from '../../../../config/provider';
+import { useScreenLargerThan } from '../../../../hooks/useScreenLargerThan';
+import { MobileRibbonSidebarDrawer } from './MobileRibbonSidebarDrawer';
+
+export const MobileRibbons = () => {
+ const { classes, cx } = useStyles();
+ const { config } = useConfigContext();
+ const [openedRight, rightSidebar] = useDisclosure(false);
+ const [openedLeft, leftSidebar] = useDisclosure(false);
+ const screenLargerThanMd = useScreenLargerThan('md');
+
+ if (screenLargerThanMd || !config) {
+ return <>>;
+ }
+
+ const layoutSettings = config.settings.customization.layout;
+
+ return (
+
+ {layoutSettings.enabledLeftSidebar ? (
+ <>
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+ {layoutSettings.enabledRightSidebar ? (
+ <>
+
+
+
+
+ >
+ ) : null}
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ root: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ pointerEvents: 'none',
+ },
+ button: {
+ height: 100,
+ width: 36,
+ pointerEvents: 'auto',
+ },
+ removeBorderLeft: {
+ borderTopLeftRadius: 0,
+ borderBottomLeftRadius: 0,
+ },
+ removeBorderRight: {
+ borderTopRightRadius: 0,
+ borderBottomRightRadius: 0,
+ },
+}));
diff --git a/src/components/Dashboard/Mobile/Ribbon/MobileRibbonSidebarDrawer.tsx b/src/components/Dashboard/Mobile/Ribbon/MobileRibbonSidebarDrawer.tsx
new file mode 100644
index 000000000..071af487d
--- /dev/null
+++ b/src/components/Dashboard/Mobile/Ribbon/MobileRibbonSidebarDrawer.tsx
@@ -0,0 +1,35 @@
+import { Drawer, Title } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { DashboardSidebar } from '../../Wrappers/Sidebar/Sidebar';
+
+interface MobileRibbonSidebarDrawerProps {
+ onClose: () => void;
+ opened: boolean;
+ location: 'left' | 'right';
+}
+
+export const MobileRibbonSidebarDrawer = ({
+ location,
+ ...props
+}: MobileRibbonSidebarDrawerProps) => {
+ const { t } = useTranslation('layout/mobile/drawer');
+ return (
+ {t('title', { position: location })}}
+ style={{
+ display: 'flex',
+ justifyContent: 'center',
+ }}
+ styles={{
+ title: {
+ width: '100%',
+ },
+ }}
+ {...props}
+ >
+
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx
new file mode 100644
index 000000000..dc18afe0e
--- /dev/null
+++ b/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx
@@ -0,0 +1,93 @@
+import { SelectItem } from '@mantine/core';
+import { closeModal, ContextModalProps } from '@mantine/modals';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { AppType } from '../../../../types/app';
+import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';
+import { ChangePositionModal } from './ChangePositionModal';
+
+type ChangeAppPositionModalInnerProps = {
+ app: AppType;
+};
+
+export const ChangeAppPositionModal = ({
+ id,
+ context,
+ innerProps,
+}: ContextModalProps) => {
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const shapeSize = useGridstackStore((x) => x.currentShapeSize);
+
+ if (!shapeSize) return null;
+
+ const handleSubmit = (x: number, y: number, width: number, height: number) => {
+ if (!configName) {
+ return;
+ }
+
+ updateConfig(
+ configName,
+ (previousConfig) => ({
+ ...previousConfig,
+ apps: [
+ ...previousConfig.apps.filter((x) => x.id !== innerProps.app.id),
+ {
+ ...innerProps.app,
+ shape: {
+ ...innerProps.app.shape,
+ [shapeSize]: { location: { x, y }, size: { width, height } },
+ },
+ },
+ ],
+ }),
+ true
+ );
+ context.closeModal(id);
+ };
+
+ const handleCancel = () => {
+ closeModal(id);
+ };
+
+ const widthData = useWidthData();
+ const heightData = useHeightData();
+
+ return (
+
+ );
+};
+
+const useHeightData = (): SelectItem[] => {
+ const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
+ const wrapperColumnCount = useWrapperColumnCount();
+
+ return Array.from(Array(11).keys()).map((n) => {
+ const index = n + 1;
+ return {
+ value: index.toString(),
+ label: `${Math.floor(index * (mainAreaWidth! / wrapperColumnCount!))}px`,
+ };
+ });
+};
+
+const useWidthData = (): SelectItem[] => {
+ const wrapperColumnCount = useWrapperColumnCount();
+ return Array.from(Array(wrapperColumnCount!).keys()).map((n) => {
+ const index = n + 1;
+ return {
+ value: index.toString(),
+ // eslint-disable-next-line no-mixed-operators
+ label: `${((100 / wrapperColumnCount!) * index).toFixed(2)}%`,
+ };
+ });
+};
diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx
new file mode 100644
index 000000000..f8e704b51
--- /dev/null
+++ b/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx
@@ -0,0 +1,124 @@
+import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../config/provider';
+
+interface ChangePositionModalProps {
+ initialX?: number;
+ initialY?: number;
+ initialWidth?: number;
+ initialHeight?: number;
+ widthData: SelectItem[];
+ heightData: SelectItem[];
+ onSubmit: (x: number, y: number, width: number, height: number) => void;
+ onCancel: () => void;
+}
+
+export const ChangePositionModal = ({
+ initialX,
+ initialY,
+ initialWidth,
+ initialHeight,
+ widthData,
+ heightData,
+ onCancel,
+ onSubmit,
+}: ChangePositionModalProps) => {
+ const { name: configName } = useConfigContext();
+
+ const form = useForm({
+ initialValues: {
+ x: initialX ?? null,
+ y: initialY ?? null,
+ width: initialWidth?.toString() ?? '',
+ height: initialHeight?.toString() ?? '',
+ },
+ validateInputOnChange: true,
+ validateInputOnBlur: true,
+ });
+
+ const handleSubmit = () => {
+ if (!configName) {
+ return;
+ }
+
+ const width = parseInt(form.values.width, 10);
+ const height = parseInt(form.values.height, 10);
+
+ if (!form.values.x || !form.values.y || Number.isNaN(width) || Number.isNaN(height)) return;
+
+ onSubmit(form.values.x, form.values.y, width, height);
+ };
+
+ const { t } = useTranslation(['layout/modals/change-position', 'common']);
+
+ return (
+
+ );
+};
+
+type FormType = {
+ x: number | null;
+ y: number | null;
+ width: string;
+ height: string;
+};
diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx
new file mode 100644
index 000000000..241503adb
--- /dev/null
+++ b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx
@@ -0,0 +1,102 @@
+import { SelectItem } from '@mantine/core';
+import { closeModal, ContextModalProps } from '@mantine/modals';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import widgets from '../../../../widgets';
+import { WidgetChangePositionModalInnerProps } from '../../Tiles/Widgets/WidgetsMenu';
+import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';
+import { ChangePositionModal } from './ChangePositionModal';
+
+export const ChangeWidgetPositionModal = ({
+ context,
+ id,
+ innerProps,
+}: ContextModalProps) => {
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const shapeSize = useGridstackStore((x) => x.currentShapeSize);
+
+ if (shapeSize === null) {
+ return null;
+ }
+
+ const handleSubmit = (x: number, y: number, width: number, height: number) => {
+ if (!configName) {
+ return;
+ }
+
+ updateConfig(
+ configName,
+ (prev) => {
+ const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
+ currentWidget!.shape[shapeSize] = {
+ location: {
+ x,
+ y,
+ },
+ size: {
+ height,
+ width,
+ },
+ };
+
+ return {
+ ...prev,
+ widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!],
+ };
+ },
+ true
+ );
+ context.closeModal(id);
+ };
+
+ const handleCancel = () => {
+ closeModal(id);
+ };
+
+ const widthData = useWidthData(innerProps.widgetId);
+ const heightData = useHeightData(innerProps.widgetId);
+
+ return (
+
+ );
+};
+
+const useWidthData = (integration: string): SelectItem[] => {
+ const wrapperColumnCount = useWrapperColumnCount();
+ const currentWidget = widgets[integration as keyof typeof widgets];
+ if (!currentWidget) return [];
+ const offset = currentWidget.gridstack.minWidth ?? 2;
+ const length =
+ (currentWidget.gridstack.maxWidth > wrapperColumnCount!
+ ? wrapperColumnCount!
+ : currentWidget.gridstack.maxWidth) - offset;
+ return Array.from({ length: length + 1 }, (_, i) => i + offset).map((n) => ({
+ value: n.toString(),
+ // eslint-disable-next-line no-mixed-operators
+ label: `${((100 / wrapperColumnCount!) * n).toFixed(2)}%`,
+ }));
+};
+
+const useHeightData = (integration: string): SelectItem[] => {
+ const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
+ const wrapperColumnCount = useWrapperColumnCount();
+
+ const currentWidget = widgets[integration as keyof typeof widgets];
+ if (!currentWidget) return [];
+ const offset = currentWidget.gridstack.minHeight ?? 2;
+ const length = (currentWidget.gridstack.maxHeight ?? 12) - offset;
+ return Array.from({ length }, (_, i) => i + offset).map((n) => ({
+ value: n.toString(),
+ label: `${(mainAreaWidth! / wrapperColumnCount!) * n}px`,
+ }));
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx
new file mode 100644
index 000000000..55ee1619a
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx
@@ -0,0 +1,238 @@
+import { Alert, Button, Group, Popover, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { useDisclosure } from '@mantine/hooks';
+import { ContextModalProps } from '@mantine/modals';
+import {
+ IconAccessPoint,
+ IconAdjustments,
+ IconAlertTriangle,
+ IconBrush,
+ IconClick,
+ IconPlug,
+} from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { AppType } from '../../../../types/app';
+import { useEditModeStore } from '../../Views/useEditModeStore';
+import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab';
+import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab';
+import { GeneralTab } from './Tabs/GeneralTab/GeneralTab';
+import { IntegrationTab } from './Tabs/IntegrationTab/IntegrationTab';
+import { NetworkTab } from './Tabs/NetworkTab/NetworkTab';
+import { DebouncedAppIcon } from './Tabs/Shared/DebouncedAppIcon';
+import { EditAppModalTab } from './Tabs/type';
+
+const appUrlRegex =
+ '(https?://(?:www.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9].[^\\s]{2,}|www.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9].[^\\s]{2,}|https?://(?:www.|(?!www))[a-zA-Z0-9]+.[^\\s]{2,}|www.[a-zA-Z0-9]+.[^\\s]{2,})';
+
+export const EditAppModal = ({
+ context,
+ id,
+ innerProps,
+}: ContextModalProps<{ app: AppType; allowAppNamePropagation: boolean }>) => {
+ const { t } = useTranslation(['layout/modals/add-app', 'common']);
+ const { name: configName, config } = useConfigContext();
+ const updateConfig = useConfigStore((store) => store.updateConfig);
+ const { enabled: isEditMode } = useEditModeStore();
+ const [allowAppNamePropagation, setAllowAppNamePropagation] = useState(
+ innerProps.allowAppNamePropagation
+ );
+
+ const form = useForm({
+ initialValues: innerProps.app,
+ validate: {
+ name: (name) => (!name ? 'Name is required' : null),
+ url: (url) => {
+ if (!url) {
+ return 'Url is required';
+ }
+
+ if (!url.match(appUrlRegex)) {
+ return 'Value is not a valid url';
+ }
+
+ return null;
+ },
+ appearance: {
+ iconUrl: (url: string) => {
+ if (url.length < 1) {
+ return 'This field is required';
+ }
+
+ return null;
+ },
+ },
+ behaviour: {
+ externalUrl: (url: string) => {
+ if (url === undefined || url.length < 1) {
+ return null;
+ }
+
+ if (!url.match(appUrlRegex)) {
+ return 'Uri override is not a valid uri';
+ }
+
+ return null;
+ },
+ },
+ },
+ validateInputOnChange: true,
+ });
+
+ const onSubmit = (values: AppType) => {
+ if (!configName) {
+ return;
+ }
+
+ updateConfig(
+ configName,
+ (previousConfig) => ({
+ ...previousConfig,
+ apps: [
+ ...previousConfig.apps.filter((x) => x.id !== values.id),
+ {
+ ...values,
+ },
+ ],
+ }),
+ true,
+ !isEditMode
+ );
+
+ // also close the parent modal
+ context.closeAll();
+ };
+
+ const [activeTab, setActiveTab] = useState('general');
+
+ const closeModal = () => {
+ context.closeModal(id);
+ };
+
+ const validationErrors = Object.keys(form.errors);
+
+ const ValidationErrorIndicator = ({ keys }: { keys: string[] }) => {
+ const relevantErrors = validationErrors.filter((x) => keys.includes(x));
+
+ return (
+
+
+
+ );
+ };
+
+ return (
+ <>
+ {configName === undefined ||
+ (config === undefined && (
+
+ There was an unexpected problem loading the configuration. Functionality might be
+ restricted. Please report this incident.
+
+ ))}
+
+
+
+
+ {form.values.name ?? 'New App'}
+
+
+
+
+ >
+ );
+};
+
+const SaveButton = ({ formIsValid }: { formIsValid: boolean }) => {
+ const [opened, { close, open }] = useDisclosure(false);
+ const { t } = useTranslation(['layout/modals/add-app', 'common']);
+
+ return (
+
+
+
+
+ {t('common:save')}
+
+
+
+ {t('validation.popover')}
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/AppereanceTab.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/AppereanceTab.tsx
new file mode 100644
index 000000000..8e46d0f51
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/AppereanceTab.tsx
@@ -0,0 +1,56 @@
+import { createStyles, Flex, Tabs, TextInput } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { AppType } from '../../../../../../types/app';
+import { DebouncedAppIcon } from '../Shared/DebouncedAppIcon';
+import { IconSelector } from './IconSelector/IconSelector';
+
+interface AppearanceTabProps {
+ form: UseFormReturnType AppType>;
+ disallowAppNameProgagation: () => void;
+ allowAppNamePropagation: boolean;
+}
+
+export const AppearanceTab = ({
+ form,
+ disallowAppNameProgagation,
+ allowAppNamePropagation,
+}: AppearanceTabProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+ const { classes } = useStyles();
+
+ return (
+
+
+ }
+ label={t('appearance.icon.label')}
+ description={t('appearance.icon.description')}
+ variant="default"
+ withAsterisk
+ required
+ {...form.getInputProps('appearance.iconUrl')}
+ />
+ {
+ form.setValues({
+ appearance: {
+ iconUrl: item.url,
+ },
+ });
+ disallowAppNameProgagation();
+ }}
+ allowAppNamePropagation={allowAppNamePropagation}
+ form={form}
+ />
+
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ textInput: {
+ flexGrow: 1,
+ },
+}));
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/IconSelector/IconSelector.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/IconSelector/IconSelector.tsx
new file mode 100644
index 000000000..8ec13cbb3
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/AppereanceTab/IconSelector/IconSelector.tsx
@@ -0,0 +1,142 @@
+/* eslint-disable @next/next/no-img-element */
+import {
+ ActionIcon,
+ Button,
+ createStyles,
+ Divider,
+ Flex,
+ Loader,
+ Popover,
+ ScrollArea,
+ Stack,
+ Text,
+ TextInput,
+ Title,
+} from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useDebouncedValue } from '@mantine/hooks';
+import { IconSearch, IconX } from '@tabler/icons';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'next-i18next';
+import { ICON_PICKER_SLICE_LIMIT } from '../../../../../../../../data/constants';
+import { IconSelectorItem } from '../../../../../../../types/iconSelector/iconSelectorItem';
+import { WalkxcodeRepositoryIcon } from '../../../../../../../types/iconSelector/repositories/walkxcodeIconRepository';
+import { AppType } from '../../../../../../../types/app';
+import { useRepositoryIconsQuery } from '../../../../../../../hooks/useRepositoryIconsQuery';
+
+interface IconSelectorProps {
+ form: UseFormReturnType AppType>;
+ onChange: (icon: IconSelectorItem) => void;
+ allowAppNamePropagation: boolean;
+}
+
+export const IconSelector = ({ onChange, allowAppNamePropagation, form }: IconSelectorProps) => {
+ const { t } = useTranslation('layout/tools');
+
+ const { data, isLoading } = useRepositoryIconsQuery({
+ url: 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png',
+ converter: (item) => ({
+ url: `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/${item.name}`,
+ fileName: item.name,
+ }),
+ });
+
+ const [searchTerm, setSearchTerm] = useState('');
+ const { classes } = useStyles();
+
+ const [debouncedValue] = useDebouncedValue(form.values.name, 500);
+
+ useEffect(() => {
+ if (allowAppNamePropagation !== true) {
+ return;
+ }
+
+ const matchingDebouncedIcon = data?.find(
+ (x) => replaceCharacters(x.fileName.split('.')[0]) === replaceCharacters(debouncedValue)
+ );
+
+ if (!matchingDebouncedIcon) {
+ return;
+ }
+
+ form.setFieldValue('appearance.iconUrl', matchingDebouncedIcon.url);
+ }, [debouncedValue]);
+
+ if (isLoading || !data) {
+ return ;
+ }
+
+ const replaceCharacters = (value: string) => value.toLowerCase().replaceAll('', '-');
+
+ const filteredItems = searchTerm
+ ? data.filter((x) => replaceCharacters(x.url).includes(replaceCharacters(searchTerm)))
+ : data;
+ const slicedFilteredItems = filteredItems.slice(0, ICON_PICKER_SLICE_LIMIT);
+ const isTruncated =
+ slicedFilteredItems.length > 0 && slicedFilteredItems.length !== filteredItems.length;
+
+ return (
+
+
+ }
+ >
+ Icon Picker
+
+
+
+
+ setSearchTerm(event.currentTarget.value)}
+ placeholder={t('iconPicker.textInputPlaceholder')}
+ variant="filled"
+ rightSection={
+ setSearchTerm('')}>
+
+
+ }
+ />
+
+
+
+ {slicedFilteredItems.map((item) => (
+ onChange(item)} size={40} p={3}>
+
+
+ ))}
+
+
+ {isTruncated && (
+
+
+
+ {t('iconPicker.searchLimitationTitle', { max: ICON_PICKER_SLICE_LIMIT })}
+
+
+ {t('iconPicker.searchLimitationMessage', { max: ICON_PICKER_SLICE_LIMIT })}
+
+
+ )}
+
+
+
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ flameIcon: {
+ margin: '0 auto',
+ },
+ icon: {
+ width: '100%',
+ height: '100%',
+ objectFit: 'contain',
+ },
+ actionIcon: {
+ alignSelf: 'end',
+ },
+}));
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/BehaviourTab/BehaviourTab.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/BehaviourTab/BehaviourTab.tsx
new file mode 100644
index 000000000..2f942927c
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/BehaviourTab/BehaviourTab.tsx
@@ -0,0 +1,22 @@
+import { Tabs, Switch } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { AppType } from '../../../../../../types/app';
+
+interface BehaviourTabProps {
+ form: UseFormReturnType AppType>;
+}
+
+export const BehaviourTab = ({ form }: BehaviourTabProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/GeneralTab/GeneralTab.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/GeneralTab/GeneralTab.tsx
new file mode 100644
index 000000000..73c8db39a
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/GeneralTab/GeneralTab.tsx
@@ -0,0 +1,49 @@
+import { Tabs, TextInput } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { IconClick, IconCursorText, IconLink } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { AppType } from '../../../../../../types/app';
+import { EditAppModalTab } from '../type';
+
+interface GeneralTabProps {
+ form: UseFormReturnType AppType>;
+ openTab: (tab: EditAppModalTab) => void;
+}
+
+export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+ return (
+
+ }
+ label={t('general.appname.label')}
+ description={t('general.appname.description')}
+ placeholder="My example app"
+ variant="default"
+ withAsterisk
+ {...form.getInputProps('name')}
+ />
+ }
+ label={t('general.internalAddress.label')}
+ description={t('general.internalAddress.description')}
+ placeholder="https://google.com"
+ variant="default"
+ withAsterisk
+ {...form.getInputProps('url')}
+ onChange={(e) => {
+ form.setFieldValue('behaviour.externalUrl', e.target.value);
+ form.setFieldValue('url', e.target.value);
+ }}
+ />
+ }
+ label={t('general.externalAddress.label')}
+ description={t('general.externalAddress.description')}
+ placeholder="https://homarr.mywebsite.com/"
+ variant="default"
+ {...form.getInputProps('behaviour.externalUrl')}
+ />
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/GenericSecretInput.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/GenericSecretInput.tsx
new file mode 100644
index 000000000..895dbf015
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/GenericSecretInput.tsx
@@ -0,0 +1,146 @@
+import {
+ Button,
+ Card,
+ createStyles,
+ Flex,
+ Grid,
+ Group,
+ PasswordInput,
+ Stack,
+ ThemeIcon,
+ Title,
+ Text,
+ Badge,
+ Tooltip,
+} from '@mantine/core';
+import { TablerIcon } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import { AppIntegrationPropertyAccessabilityType } from '../../../../../../../../types/app';
+
+interface GenericSecretInputProps {
+ label: string;
+ value: string;
+ setIcon: TablerIcon;
+ secretIsPresent: boolean;
+ type: AppIntegrationPropertyAccessabilityType;
+ onClickUpdateButton: (value: string | undefined) => void;
+}
+
+export const GenericSecretInput = ({
+ label,
+ value,
+ setIcon,
+ secretIsPresent,
+ type,
+ onClickUpdateButton,
+ ...props
+}: GenericSecretInputProps) => {
+ const { classes } = useStyles();
+
+ const Icon = setIcon;
+
+ const [displayUpdateField, setDisplayUpdateField] = useState(false);
+ const { t } = useTranslation(['layout/modals/add-app', 'common']);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {t(label)}
+
+
+
+ {secretIsPresent ? (
+
+ {t('integration.type.defined')}
+
+ ) : (
+
+ {t('integration.type.undefined')}
+
+ )}
+ {type === 'private' ? (
+
+
+ {t('integration.type.private')}
+
+
+ ) : (
+
+
+ {t('integration.type.public')}
+
+
+ )}
+
+
+
+ {type === 'private'
+ ? 'Private: Once saved, you cannot read out this value again'
+ : 'Public: Can be read out repeatedly'}
+
+
+
+
+
+
+ {
+ setDisplayUpdateField(false);
+ onClickUpdateButton(undefined);
+ }}
+ variant="subtle"
+ color="gray"
+ px="xl"
+ >
+ {t('integration.secrets.clear')}
+
+ {displayUpdateField === true ? (
+
+ ) : (
+ setDisplayUpdateField(true)} variant="light">
+ {t('integration.secrets.update')}
+
+ )}
+
+
+
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ subtitle: {
+ lineHeight: 1.1,
+ },
+ alignSelfCenter: {
+ alignSelf: 'center',
+ },
+ textTransformUnset: {
+ textTransform: 'inherit',
+ },
+}));
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/IntegrationSelector.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/IntegrationSelector.tsx
new file mode 100644
index 000000000..5f487f187
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/InputElements/IntegrationSelector.tsx
@@ -0,0 +1,160 @@
+/* eslint-disable @next/next/no-img-element */
+import { Group, Select, SelectItem, Text } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { forwardRef } from 'react';
+import {
+ IntegrationField,
+ integrationFieldDefinitions,
+ integrationFieldProperties,
+ AppIntegrationPropertyType,
+ AppIntegrationType,
+ AppType,
+} from '../../../../../../../../types/app';
+
+interface IntegrationSelectorProps {
+ form: UseFormReturnType AppType>;
+}
+
+export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+
+ const data: SelectItem[] = [
+ {
+ value: 'sabnzbd',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sabnzbd.png',
+ label: 'SABnzbd',
+ },
+ {
+ value: 'nzbGet',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/nzbget.png',
+ label: 'NZBGet',
+ },
+ {
+ value: 'deluge',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/deluge.png',
+ label: 'Deluge',
+ },
+ {
+ value: 'transmission',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/transmission.png',
+ label: 'Transmission',
+ },
+ {
+ value: 'qBittorrent',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/qbittorrent.png',
+ label: 'qBittorrent',
+ },
+ {
+ value: 'jellyseerr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyseerr.png',
+ label: 'Jellyseerr',
+ },
+ {
+ value: 'overseerr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/overseerr.png',
+ label: 'Overseerr',
+ },
+ {
+ value: 'sonarr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sonarr.png',
+ label: 'Sonarr',
+ },
+ {
+ value: 'radarr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/radarr.png',
+ label: 'Radarr',
+ },
+ {
+ value: 'lidarr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/lidarr.png',
+ label: 'Lidarr',
+ },
+ {
+ value: 'readarr',
+ image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png',
+ label: 'Readarr',
+ },
+ ].filter((x) => Object.keys(integrationFieldProperties).includes(x.value));
+
+ const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => {
+ if (!value) return [];
+ const integrationType = value as Exclude;
+ if (integrationType === null) {
+ return [];
+ }
+
+ const requiredProperties = Object.entries(integrationFieldDefinitions).filter(([k, v]) => {
+ const val = integrationFieldProperties[integrationType];
+ return val.includes(k as IntegrationField);
+ })!;
+ return requiredProperties.map(([k, value]) => ({
+ type: value.type,
+ field: k as IntegrationField,
+ value: undefined,
+ isDefined: false,
+ }));
+ };
+
+ const inputProps = form.getInputProps('integration.type');
+
+ return (
+
+ item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
+ item.description?.toLowerCase().includes(value.toLowerCase().trim())
+ }
+ icon={
+ form.values.integration?.type && (
+ x.value === form.values.integration?.type)?.image}
+ alt="integration"
+ width={20}
+ height={20}
+ />
+ )
+ }
+ onChange={(value) => {
+ form.setFieldValue('integration.properties', getNewProperties(value));
+ inputProps.onChange(value);
+ }}
+ withinPortal
+ {...inputProps}
+ />
+ );
+};
+
+interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
+ image: string;
+ description: string;
+ label: string;
+}
+
+const SelectItemComponent = forwardRef(
+ ({ image, label, description, ...others }: ItemProps, ref) => (
+
+
+
+
+
+ {label}
+ {description && (
+
+ {description}
+
+ )}
+
+
+
+ )
+);
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer.tsx
new file mode 100644
index 000000000..0d8d4bfd9
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer.tsx
@@ -0,0 +1,89 @@
+import { Stack } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { IconKey } from '@tabler/icons';
+import {
+ IntegrationField,
+ integrationFieldDefinitions,
+ integrationFieldProperties,
+ AppIntegrationPropertyType,
+ AppType,
+} from '../../../../../../../../types/app';
+import { GenericSecretInput } from '../InputElements/GenericSecretInput';
+
+interface IntegrationOptionsRendererProps {
+ form: UseFormReturnType AppType>;
+}
+
+export const IntegrationOptionsRenderer = ({ form }: IntegrationOptionsRendererProps) => {
+ const selectedIntegration = form.values.integration?.type;
+
+ if (!selectedIntegration) return null;
+
+ const displayedProperties = integrationFieldProperties[selectedIntegration];
+
+ return (
+
+ {displayedProperties.map((property, index) => {
+ const [_, definition] = Object.entries(integrationFieldDefinitions).find(
+ ([key]) => property === key
+ )!;
+
+ let indexInFormValue =
+ form.values.integration?.properties.findIndex((p) => p.field === property) ?? -1;
+ if (indexInFormValue === -1) {
+ const { type } = Object.entries(integrationFieldDefinitions).find(
+ ([k, v]) => k === property
+ )![1];
+ const newProperty: AppIntegrationPropertyType = {
+ type,
+ field: property as IntegrationField,
+ isDefined: false,
+ };
+ form.insertListItem('integration.properties', newProperty);
+ indexInFormValue = form.values.integration!.properties.length;
+ }
+ const formValue = form.values.integration?.properties[indexInFormValue];
+
+ const isPresent = formValue?.isDefined;
+ const accessabilityType = formValue?.type;
+
+ if (!definition) {
+ return (
+ {
+ form.setFieldValue(`integration.properties.${index}.value`, value);
+ form.setFieldValue(
+ `integration.properties.${index}.isDefined`,
+ value !== undefined
+ );
+ }}
+ key={`input-${property}`}
+ label={`${property} (potentionally unmapped)`}
+ secretIsPresent={isPresent}
+ setIcon={IconKey}
+ value={formValue.value}
+ type={accessabilityType}
+ {...form.getInputProps(`integration.properties.${index}.value`)}
+ />
+ );
+ }
+
+ return (
+ {
+ form.setFieldValue(`integration.properties.${index}.value`, value);
+ form.setFieldValue(`integration.properties.${index}.isDefined`, value !== undefined);
+ }}
+ key={`input-${definition.label}`}
+ label={definition.label}
+ value=""
+ secretIsPresent={isPresent}
+ setIcon={definition.icon}
+ type={accessabilityType}
+ {...form.getInputProps(`integration.properties.${index}.value`)}
+ />
+ );
+ })}
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/IntegrationTab.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/IntegrationTab.tsx
new file mode 100644
index 000000000..c57c69ced
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/IntegrationTab/IntegrationTab.tsx
@@ -0,0 +1,37 @@
+import { Alert, Divider, Tabs, Text } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { IconAlertTriangle } from '@tabler/icons';
+import { Trans, useTranslation } from 'next-i18next';
+import { AppType } from '../../../../../../types/app';
+import { IntegrationSelector } from './Components/InputElements/IntegrationSelector';
+import { IntegrationOptionsRenderer } from './Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer';
+
+interface IntegrationTabProps {
+ form: UseFormReturnType AppType>;
+}
+
+export const IntegrationTab = ({ form }: IntegrationTabProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+ const hasIntegrationSelected = form.values.integration?.type;
+
+ return (
+
+
+
+ {hasIntegrationSelected && (
+ <>
+
+
+ {t('integration.secrets.description')}
+
+
+ } color="yellow">
+
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/NetworkTab/NetworkTab.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/NetworkTab/NetworkTab.tsx
new file mode 100644
index 000000000..7845c77d7
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/NetworkTab/NetworkTab.tsx
@@ -0,0 +1,37 @@
+import { Tabs, Switch, MultiSelect } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { StatusCodes } from '../../../../../../tools/acceptableStatusCodes';
+import { AppType } from '../../../../../../types/app';
+
+interface NetworkTabProps {
+ form: UseFormReturnType AppType>;
+}
+
+export const NetworkTab = ({ form }: NetworkTabProps) => {
+ const { t } = useTranslation('layout/modals/add-app');
+ return (
+
+
+ {form.values.network.enabledStatusChecker && (
+
+ )}
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/Shared/DebouncedAppIcon.tsx b/src/components/Dashboard/Modals/EditAppModal/Tabs/Shared/DebouncedAppIcon.tsx
new file mode 100644
index 000000000..90e575fa1
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/Shared/DebouncedAppIcon.tsx
@@ -0,0 +1,59 @@
+// disabled due to too many dynamic targets for next image cache
+/* eslint-disable @next/next/no-img-element */
+import Image from 'next/image';
+import { createStyles, Loader } from '@mantine/core';
+import { UseFormReturnType } from '@mantine/form';
+import { useDebouncedValue } from '@mantine/hooks';
+import { AppType } from '../../../../../../types/app';
+
+interface DebouncedAppIconProps {
+ width: number;
+ height: number;
+ form: UseFormReturnType AppType>;
+ debouncedWaitPeriod?: number;
+}
+
+export const DebouncedAppIcon = ({
+ form,
+ width,
+ height,
+ debouncedWaitPeriod = 1000,
+}: DebouncedAppIconProps) => {
+ const { classes } = useStyles();
+ const [debouncedIconImageUrl] = useDebouncedValue(
+ form.values.appearance.iconUrl,
+ debouncedWaitPeriod
+ );
+
+ if (debouncedIconImageUrl !== form.values.appearance.iconUrl) {
+ return ;
+ }
+
+ if (debouncedIconImageUrl.length > 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ iconImage: {
+ objectFit: 'contain',
+ },
+}));
diff --git a/src/components/Dashboard/Modals/EditAppModal/Tabs/type.ts b/src/components/Dashboard/Modals/EditAppModal/Tabs/type.ts
new file mode 100644
index 000000000..a67947134
--- /dev/null
+++ b/src/components/Dashboard/Modals/EditAppModal/Tabs/type.ts
@@ -0,0 +1 @@
+export type EditAppModalTab = 'general' | 'behaviour' | 'network' | 'appereance' | 'integration';
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx
new file mode 100644
index 000000000..d746ae334
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx
@@ -0,0 +1,170 @@
+import { Group, Space, Stack, Text, UnstyledButton } from '@mantine/core';
+import { closeModal } from '@mantine/modals';
+import { showNotification } from '@mantine/notifications';
+import { IconBox, IconBoxAlignTop, IconStack } from '@tabler/icons';
+import { motion } from 'framer-motion';
+import { useTranslation } from 'next-i18next';
+import { ReactNode } from 'react';
+import { v4 as uuidv4 } from 'uuid';
+import { useConfigContext } from '../../../../../../config/provider';
+import { useConfigStore } from '../../../../../../config/store';
+import { openContextModalGeneric } from '../../../../../../tools/mantineModalManagerExtensions';
+import { AppType } from '../../../../../../types/app';
+import { CategoryEditModalInnerProps } from '../../../../Wrappers/Category/CategoryEditModal';
+import { useStyles } from '../Shared/styles';
+
+interface AvailableElementTypesProps {
+ modalId: string;
+ onOpenIntegrations: () => void;
+ onOpenStaticElements: () => void;
+}
+
+export const AvailableElementTypes = ({
+ modalId,
+ onOpenIntegrations: onOpenWidgets,
+ onOpenStaticElements,
+}: AvailableElementTypesProps) => {
+ const { t } = useTranslation('layout/element-selector/selector');
+ const { config, name: configName } = useConfigContext();
+ const { updateConfig } = useConfigStore();
+ const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
+
+ const onClickCreateCategory = async () => {
+ openContextModalGeneric({
+ modal: 'categoryEditModal',
+ title: 'Name of new category',
+ withCloseButton: false,
+ innerProps: {
+ category: {
+ id: uuidv4(),
+ name: 'New category',
+ position: 0, // doesn't matter, is being overwritten
+ },
+ onSuccess: async (category) => {
+ if (!configName) return;
+
+ await updateConfig(configName, (previousConfig) => ({
+ ...previousConfig,
+ wrappers: [
+ ...previousConfig.wrappers,
+ {
+ id: uuidv4(),
+ // Thank you ChatGPT ;)
+ position: previousConfig.categories.length + 1,
+ },
+ ],
+ categories: [
+ ...previousConfig.categories,
+ {
+ id: uuidv4(),
+ name: category.name,
+ position: previousConfig.categories.length + 1,
+ },
+ ],
+ })).then(() => {
+ closeModal(modalId);
+ showNotification({
+ title: 'Category created',
+ message: `The category ${category.name} has been created`,
+ color: 'teal',
+ });
+ });
+ },
+ },
+ });
+ };
+
+ return (
+ <>
+ {t('modal.text')}
+
+
+ }
+ onClick={() => {
+ openContextModalGeneric<{ app: AppType; allowAppNamePropagation: boolean }>({
+ modal: 'editApp',
+ innerProps: {
+ app: {
+ id: uuidv4(),
+ name: 'Your app',
+ url: 'https://homarr.dev',
+ appearance: {
+ iconUrl: '/imgs/logo/logo.png',
+ },
+ network: {
+ enabledStatusChecker: true,
+ okStatus: [200],
+ },
+ behaviour: {
+ isOpeningNewTab: true,
+ externalUrl: '',
+ },
+
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: getLowestWrapper()?.id ?? 'default',
+ },
+ },
+ shape: {},
+ integration: {
+ type: null,
+ properties: [],
+ },
+ },
+ allowAppNamePropagation: true,
+ },
+ size: 'xl',
+ });
+ }}
+ />
+ }
+ onClick={onOpenWidgets}
+ />
+ }
+ onClick={onClickCreateCategory}
+ />
+ {/* }
+ onClick={onOpenStaticElements}
+ />*/}
+
+ >
+ );
+};
+
+interface ElementItemProps {
+ icon: ReactNode;
+ name: string;
+ onClick: () => void;
+}
+
+const ElementItem = ({ name, icon, onClick }: ElementItemProps) => {
+ const { classes, cx } = useStyles();
+ return (
+
+
+
+ {icon}
+
+
+ {name}
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Shared/GenericElementType.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Shared/GenericElementType.tsx
new file mode 100644
index 000000000..07938aba2
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/Shared/GenericElementType.tsx
@@ -0,0 +1,61 @@
+import { Button, Card, Center, Grid, Stack, Text } from '@mantine/core';
+import { TablerIcon } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import Image from 'next/image';
+import React from 'react';
+import { useStyles } from './styles';
+
+interface GenericAvailableElementTypeProps {
+ name: string;
+ handleAddition: () => Promise;
+ description?: string;
+ image: string | TablerIcon;
+ disabled?: boolean;
+}
+
+export const GenericAvailableElementType = ({
+ name,
+ description,
+ image,
+ disabled,
+ handleAddition,
+}: GenericAvailableElementTypeProps) => {
+ const { classes } = useStyles();
+ const { t } = useTranslation('layout/modals/about');
+
+ const Icon =
+ typeof image === 'string' ? () => : image;
+
+ return (
+
+
+
+
+
+
+
+
+ {name}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {t('addToDashboard')}
+
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Shared/SelectorBackArrow.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Shared/SelectorBackArrow.tsx
new file mode 100644
index 000000000..575820366
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/Shared/SelectorBackArrow.tsx
@@ -0,0 +1,23 @@
+import { Button, Text } from '@mantine/core';
+import { IconArrowNarrowLeft } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+
+interface SelectorBackArrowProps {
+ onClickBack: () => void;
+}
+
+export function SelectorBackArrow({ onClickBack }: SelectorBackArrowProps) {
+ const { t } = useTranslation('layout/element-selector/selector');
+ return (
+ }
+ onClick={onClickBack}
+ styles={{ inner: { width: 'fit-content' } }}
+ fullWidth
+ variant="default"
+ mb="md"
+ >
+ {t('goBack')}
+
+ );
+}
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Shared/styles.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Shared/styles.tsx
new file mode 100644
index 000000000..41a8d263e
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/Shared/styles.tsx
@@ -0,0 +1,28 @@
+import { createStyles } from '@mantine/core';
+
+export const useStyles = createStyles((theme) => ({
+ styledButton: {
+ backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[9] : theme.colors.gray[2],
+ color: theme.colorScheme === 'dark' ? theme.colors.gray[0] : theme.colors.dark[9],
+ '&:hover': {
+ backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[8] : theme.colors.gray[3],
+ },
+ },
+ elementButton: {
+ width: '100%',
+ height: '100%',
+ borderRadius: theme.radius.sm,
+ },
+ elementStack: {
+ width: '100%',
+ },
+ elementName: {
+ whiteSpace: 'normal',
+ textAlign: 'center',
+ lineHeight: 1.2,
+ },
+ elementText: {
+ lineHeight: 1.2,
+ whiteSpace: 'normal',
+ },
+}));
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx b/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx
new file mode 100644
index 000000000..ef0da1757
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx
@@ -0,0 +1,32 @@
+import { Grid, Text } from '@mantine/core';
+import { IconCursorText } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { GenericAvailableElementType } from '../Shared/GenericElementType';
+import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
+
+interface AvailableStaticTypesProps {
+ onClickBack: () => void;
+}
+
+export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps) => {
+ const { t } = useTranslation('layout/element-selector/selector');
+ return (
+ <>
+
+
+
+ Static elements provide you additional control over your dashboard. They are static, because
+ they don't integrate with any apps and their content never changes.
+
+
+
+ {}}
+ />
+
+ >
+ );
+};
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx
new file mode 100644
index 000000000..c6192ab58
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx
@@ -0,0 +1,34 @@
+import { Grid, Text } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../../../config/provider';
+import widgets from '../../../../../../widgets';
+import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
+import { WidgetElementType } from './WidgetElementType';
+
+interface AvailableIntegrationElementsProps {
+ onClickBack: () => void;
+}
+
+export const AvailableIntegrationElements = ({
+ onClickBack,
+}: AvailableIntegrationElementsProps) => {
+ const { t } = useTranslation('layout/element-selector/selector');
+ const activeWidgets = useConfigContext().config?.widgets ?? [];
+ return (
+ <>
+
+
+
+ {t('widgetDescription')}
+
+
+
+ {Object.entries(widgets)
+ .filter(([widgetId]) => !activeWidgets.some((aw) => aw.id === widgetId))
+ .map(([k, v]) => (
+
+ ))}
+
+ >
+ );
+};
diff --git a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx
new file mode 100644
index 000000000..ce3a2b708
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx
@@ -0,0 +1,105 @@
+import { useModals } from '@mantine/modals';
+import { showNotification } from '@mantine/notifications';
+import { IconChecks, TablerIcon } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../../../config/provider';
+import { useConfigStore } from '../../../../../../config/store';
+import { IWidget, IWidgetDefinition } from '../../../../../../widgets/widgets';
+import { useEditModeStore } from '../../../../Views/useEditModeStore';
+import { GenericAvailableElementType } from '../Shared/GenericElementType';
+
+interface WidgetElementTypeProps {
+ id: string;
+ image: string | TablerIcon;
+ disabled?: boolean;
+ widget: IWidgetDefinition;
+}
+
+export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElementTypeProps) => {
+ const { closeModal } = useModals();
+ const { t } = useTranslation(`modules/${id}`);
+ const { name: configName, config } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const isEditMode = useEditModeStore((x) => x.enabled);
+
+ if (!configName) return null;
+
+ const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
+
+ const handleAddition = async () => {
+ updateConfig(
+ configName,
+ (prev) => ({
+ ...prev,
+ widgets: [
+ ...prev.widgets.filter((w) => w.id !== widget.id),
+ {
+ id: widget.id,
+ properties: Object.entries(widget.options).reduce((prev, [k, v]) => {
+ const newPrev = prev;
+ newPrev[k] = v.defaultValue;
+ return newPrev;
+ }, {} as IWidget['properties']),
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: getLowestWrapper()?.id ?? '',
+ },
+ },
+ shape: {
+ sm: {
+ location: {
+ x: 0,
+ y: 0,
+ },
+ size: {
+ width: widget.gridstack.minWidth,
+ height: widget.gridstack.minHeight,
+ },
+ },
+ md: {
+ location: {
+ x: 0,
+ y: 0,
+ },
+ size: {
+ width: widget.gridstack.minWidth,
+ height: widget.gridstack.minHeight,
+ },
+ },
+ lg: {
+ location: {
+ x: 0,
+ y: 0,
+ },
+ size: {
+ width: widget.gridstack.minWidth,
+ height: widget.gridstack.minHeight,
+ },
+ },
+ },
+ },
+ ],
+ }),
+ true,
+ !isEditMode
+ );
+ closeModal('selectElement');
+ showNotification({
+ title: t('descriptor.name'),
+ message: t('descriptor.description'),
+ icon: ,
+ color: 'teal',
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx b/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx
new file mode 100644
index 000000000..0f288c1ce
--- /dev/null
+++ b/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx
@@ -0,0 +1,28 @@
+import { ContextModalProps } from '@mantine/modals';
+import { useState } from 'react';
+import { AvailableIntegrationElements } from './Components/WidgetsTab/AvailableWidgetsTab';
+import { AvailableElementTypes } from './Components/Overview/AvailableElementsOverview';
+import { AvailableStaticTypes } from './Components/StaticElementsTab/AvailableStaticElementsTab';
+
+export const SelectElementModal = ({ context, id }: ContextModalProps) => {
+ const [activeTab, setActiveTab] = useState();
+
+ switch (activeTab) {
+ case undefined:
+ return (
+ setActiveTab('integrations')}
+ onOpenStaticElements={() => setActiveTab('static_elements')}
+ />
+ );
+ case 'integrations':
+ return setActiveTab(undefined)} />;
+ case 'static_elements':
+ return setActiveTab(undefined)} />;
+ default:
+ /* default to the main selection tab */
+ setActiveTab(undefined);
+ return <>>;
+ }
+};
diff --git a/src/components/Dashboard/Tiles/Apps/AppIcon.tsx b/src/components/Dashboard/Tiles/Apps/AppIcon.tsx
new file mode 100644
index 000000000..9775b9849
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Apps/AppIcon.tsx
@@ -0,0 +1,5 @@
+interface ServiceIconProps {
+ size: '100%' | number;
+}
+
+export const AppIcon = ({ size }: ServiceIconProps) => null;
diff --git a/src/components/Dashboard/Tiles/Apps/AppMenu.tsx b/src/components/Dashboard/Tiles/Apps/AppMenu.tsx
new file mode 100644
index 000000000..fea3a6e2c
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Apps/AppMenu.tsx
@@ -0,0 +1,64 @@
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
+import { AppType } from '../../../../types/app';
+import { GenericTileMenu } from '../GenericTileMenu';
+
+interface TileMenuProps {
+ app: AppType;
+}
+
+export const AppMenu = ({ app }: TileMenuProps) => {
+ const { config, name: configName } = useConfigContext();
+ const { updateConfig } = useConfigStore();
+
+ const handleClickEdit = () => {
+ openContextModalGeneric<{ app: AppType; allowAppNamePropagation: boolean }>({
+ modal: 'editApp',
+ size: 'xl',
+ innerProps: {
+ app,
+ allowAppNamePropagation: false,
+ },
+ styles: {
+ root: {
+ zIndex: 201,
+ },
+ },
+ });
+ };
+
+ const handleClickChangePosition = () => {
+ openContextModalGeneric({
+ modal: 'changeAppPositionModal',
+ innerProps: {
+ app,
+ },
+ styles: {
+ root: {
+ zIndex: 201,
+ },
+ },
+ });
+ };
+
+ const handleClickDelete = () => {
+ if (configName === undefined) {
+ return;
+ }
+
+ updateConfig(configName, (previousConfig) => ({
+ ...previousConfig,
+ apps: previousConfig.apps.filter((a) => a.id !== app.id),
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Dashboard/Tiles/Apps/AppPing.tsx b/src/components/Dashboard/Tiles/Apps/AppPing.tsx
new file mode 100644
index 000000000..3f2a8c925
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Apps/AppPing.tsx
@@ -0,0 +1,64 @@
+import { Indicator, Tooltip } from '@mantine/core';
+import { useQuery } from '@tanstack/react-query';
+import { motion } from 'framer-motion';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../config/provider';
+import { AppType } from '../../../../types/app';
+
+interface AppPingProps {
+ app: AppType;
+}
+
+export const AppPing = ({ app }: AppPingProps) => {
+ const { t } = useTranslation('modules/ping');
+ const { config } = useConfigContext();
+ const active =
+ (config?.settings.customization.layout.enabledPing && app.network.enabledStatusChecker) ??
+ false;
+ const { data, isLoading } = useQuery({
+ queryKey: [`ping/${app.id}`],
+ queryFn: async () => {
+ const response = await fetch(`/api/modules/ping?url=${encodeURI(app.url)}`);
+ const isOk = app.network.okStatus.includes(response.status);
+ return {
+ status: response.status,
+ state: isOk ? 'online' : 'down',
+ };
+ },
+ enabled: active,
+ });
+
+ const isOnline = data?.state === 'online';
+
+ if (!active) return null;
+
+ return (
+
+
+
+
+
+ );
+};
+
+type PingState = 'loading' | 'down' | 'online';
diff --git a/src/components/Dashboard/Tiles/Apps/AppTile.tsx b/src/components/Dashboard/Tiles/Apps/AppTile.tsx
new file mode 100644
index 000000000..0bd65decf
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Apps/AppTile.tsx
@@ -0,0 +1,103 @@
+import { Center, Text, UnstyledButton } from '@mantine/core';
+import { NextLink } from '@mantine/next';
+import { createStyles } from '@mantine/styles';
+import { motion } from 'framer-motion';
+import { AppType } from '../../../../types/app';
+import { useCardStyles } from '../../../layout/useCardStyles';
+import { useEditModeStore } from '../../Views/useEditModeStore';
+import { HomarrCardWrapper } from '../HomarrCardWrapper';
+import { BaseTileProps } from '../type';
+import { AppMenu } from './AppMenu';
+import { AppPing } from './AppPing';
+
+interface AppTileProps extends BaseTileProps {
+ app: AppType;
+}
+
+export const AppTile = ({ className, app }: AppTileProps) => {
+ const isEditMode = useEditModeStore((x) => x.enabled);
+
+ const { cx, classes } = useStyles();
+
+ const {
+ classes: { card: cardClass },
+ } = useCardStyles(false);
+
+ function Inner() {
+ return (
+ <>
+
+ {app.name}
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
+ >
+ );
+ }
+
+ return (
+
+
+ {!app.url || isEditMode ? (
+
+
+
+ ) : (
+ 0 ? app.behaviour.externalUrl : app.url}
+ target={app.behaviour.isOpeningNewTab ? '_blank' : '_self'}
+ className={cx(classes.button)}
+ >
+
+
+ )}
+
+
+ );
+};
+
+const useStyles = createStyles((theme, _params, getRef) => ({
+ image: {
+ ref: getRef('image'),
+ maxHeight: '90%',
+ maxWidth: '90%',
+ },
+ appName: {
+ ref: getRef('appName'),
+ },
+ button: {
+ paddingBottom: 10,
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: 4,
+ },
+}));
+
+export const appTileDefinition = {
+ component: AppTile,
+ minWidth: 1,
+ minHeight: 1,
+ maxWidth: 12,
+ maxHeight: 12,
+};
diff --git a/src/components/Dashboard/Tiles/EmptyTile.tsx b/src/components/Dashboard/Tiles/EmptyTile.tsx
new file mode 100644
index 000000000..9fdb26db7
--- /dev/null
+++ b/src/components/Dashboard/Tiles/EmptyTile.tsx
@@ -0,0 +1,6 @@
+import { HomarrCardWrapper } from './HomarrCardWrapper';
+import { BaseTileProps } from './type';
+
+export const EmptyTile = ({ className }: BaseTileProps) => (
+ Empty
+);
diff --git a/src/components/Dashboard/Tiles/GenericTileMenu.tsx b/src/components/Dashboard/Tiles/GenericTileMenu.tsx
new file mode 100644
index 000000000..fba87edfa
--- /dev/null
+++ b/src/components/Dashboard/Tiles/GenericTileMenu.tsx
@@ -0,0 +1,57 @@
+import { ActionIcon, Menu } from '@mantine/core';
+import { IconDots, IconLayoutKanban, IconPencil, IconTrash } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { useEditModeStore } from '../Views/useEditModeStore';
+
+interface GenericTileMenuProps {
+ handleClickEdit: () => void;
+ handleClickChangePosition: () => void;
+ handleClickDelete: () => void;
+ displayEdit: boolean;
+}
+
+export const GenericTileMenu = ({
+ handleClickEdit,
+ handleClickChangePosition,
+ handleClickDelete,
+ displayEdit,
+}: GenericTileMenuProps) => {
+ const { t } = useTranslation('common');
+ const isEditMode = useEditModeStore((x) => x.enabled);
+
+ if (!isEditMode) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+ {t('sections.settings')}
+ {displayEdit && (
+ } onClick={handleClickEdit}>
+ {t('edit')}
+
+ )}
+ }
+ onClick={handleClickChangePosition}
+ >
+ {t('changePosition')}
+
+ {t('sections.dangerZone')}
+ }
+ onClick={handleClickDelete}
+ >
+ {t('remove')}
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Tiles/HomarrCardWrapper.tsx b/src/components/Dashboard/Tiles/HomarrCardWrapper.tsx
new file mode 100644
index 000000000..8958667fc
--- /dev/null
+++ b/src/components/Dashboard/Tiles/HomarrCardWrapper.tsx
@@ -0,0 +1,28 @@
+import { Card, CardProps } from '@mantine/core';
+import { ReactNode } from 'react';
+import { useCardStyles } from '../../layout/useCardStyles';
+import { useEditModeStore } from '../Views/useEditModeStore';
+
+interface HomarrCardWrapperProps extends CardProps {
+ children: ReactNode;
+ isCategory?: boolean;
+}
+
+export const HomarrCardWrapper = ({ ...props }: HomarrCardWrapperProps) => {
+ const { isCategory = false, ...restProps } = props;
+ const {
+ cx,
+ classes: { card: cardClass },
+ } = useCardStyles(isCategory);
+ const isEditMode = useEditModeStore((x) => x.enabled);
+ return (
+
+ );
+};
diff --git a/src/components/Dashboard/Tiles/TileWrapper.tsx b/src/components/Dashboard/Tiles/TileWrapper.tsx
new file mode 100644
index 000000000..8d94abdd7
--- /dev/null
+++ b/src/components/Dashboard/Tiles/TileWrapper.tsx
@@ -0,0 +1,73 @@
+/* eslint-disable react/no-unknown-property */
+import { ReactNode, RefObject } from 'react';
+
+interface GridstackTileWrapperProps {
+ id: string;
+ type: 'app' | 'widget';
+ x?: number;
+ y?: number;
+ width?: number;
+ height?: number;
+ minWidth?: number;
+ minHeight?: number;
+ maxWidth?: number;
+ maxHeight?: number;
+ itemRef: RefObject;
+ children: ReactNode;
+}
+
+export const GridstackTileWrapper = ({
+ id,
+ type,
+ x,
+ y,
+ width,
+ height,
+ minWidth,
+ minHeight,
+ maxWidth,
+ maxHeight,
+ children,
+ itemRef,
+}: GridstackTileWrapperProps) => {
+ const locationProperties = useLocationProperties(x, y);
+ const normalizedWidth = width ?? minWidth;
+ const normalizedHeight = height ?? minHeight;
+
+ return (
+
+ {children}
+
+ );
+};
+
+const useLocationProperties = (x: number | undefined, y: number | undefined) => {
+ const isLocationDefined = x !== undefined && y !== undefined;
+
+ if (!isLocationDefined) {
+ return {
+ 'gs-auto-position': 'true',
+ };
+ }
+
+ return {
+ 'gs-x': x.toString(),
+ 'data-gridstack-x': x.toString(),
+ 'gs-y': y.toString(),
+ 'data-gridstack-y': y.toString(),
+ };
+};
diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx
new file mode 100644
index 000000000..73a8110d3
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx
@@ -0,0 +1,193 @@
+import {
+ Alert,
+ Button,
+ Group,
+ MultiSelect,
+ Stack,
+ Switch,
+ TextInput,
+ Text,
+ NumberInput,
+ Slider,
+ Select,
+} from '@mantine/core';
+import { ContextModalProps } from '@mantine/modals';
+import { IconAlertTriangle } from '@tabler/icons';
+import { Trans, useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import Widgets from '../../../../widgets';
+import type { IWidgetOptionValue } from '../../../../widgets/widgets';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { IWidget } from '../../../../widgets/widgets';
+import { useColorTheme } from '../../../../tools/color';
+
+export type WidgetEditModalInnerProps = {
+ widgetId: string;
+ options: IWidget['properties'];
+ widgetOptions: IWidget['properties'];
+};
+
+type IntegrationOptionsValueType = IWidget['properties'][string];
+
+export const WidgetsEditModal = ({
+ context,
+ id,
+ innerProps,
+}: ContextModalProps) => {
+ const { t } = useTranslation([`modules/${innerProps.widgetId}`, 'common']);
+ const [moduleProperties, setModuleProperties] = useState(innerProps.options);
+ // const items = Object.entries(moduleProperties ?? {}) as [string, IntegrationOptionsValueType][];
+ const items = Object.entries(innerProps.widgetOptions ?? {}) as [
+ string,
+ IntegrationOptionsValueType
+ ][];
+
+ // Find the Key in the "Widgets" Object that matches the widgetId
+ const currentWidgetDefinition = Widgets[innerProps.widgetId as keyof typeof Widgets];
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ if (!configName || !innerProps.options) return null;
+
+ const handleChange = (key: string, value: IntegrationOptionsValueType) => {
+ setModuleProperties((prev) => {
+ const copyOfPrev: any = { ...prev };
+ copyOfPrev[key] = value;
+ return copyOfPrev;
+ });
+ };
+
+ const handleSave = () => {
+ updateConfig(
+ configName,
+ (prev) => {
+ const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
+ currentWidget!.properties = moduleProperties;
+
+ return {
+ ...prev,
+ widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!],
+ };
+ },
+ true
+ );
+ context.closeModal(id);
+ };
+
+ return (
+
+ {items.map(([key, _], index) => {
+ const option = (currentWidgetDefinition as any).options[key] as IWidgetOptionValue;
+ const value = moduleProperties[key] ?? option.defaultValue;
+
+ if (!option) {
+ return (
+ } color="red">
+
+ , code: }}
+ />
+
+
+ );
+ }
+ return WidgetOptionTypeSwitch(option, index, t, key, value, handleChange);
+ })}
+
+ context.closeModal(id)} variant="light">
+ {t('common:cancel')}
+
+ {t('common:save')}
+
+
+ );
+};
+
+// Widget switch
+// Widget options are computed based on their type.
+// here you can define new types for options (along with editing the widgets.d.ts file)
+function WidgetOptionTypeSwitch(
+ option: IWidgetOptionValue,
+ index: number,
+ t: any,
+ key: string,
+ value: string | number | boolean | string[],
+ handleChange: (key: string, value: IntegrationOptionsValueType) => void
+) {
+ const { primaryColor, secondaryColor } = useColorTheme();
+ switch (option.type) {
+ case 'switch':
+ return (
+ handleChange(key, ev.currentTarget.checked)}
+ />
+ );
+ case 'text':
+ return (
+ handleChange(key, ev.currentTarget.value)}
+ />
+ );
+ case 'multi-select':
+ return (
+ handleChange(key, v)}
+ />
+ );
+ case 'select':
+ return (
+ handleChange(key, v ?? option.defaultValue)}
+ />
+ );
+ case 'number':
+ return (
+ handleChange(key, v!)}
+ />
+ );
+ case 'slider':
+ return (
+
+ handleChange(key, v)}
+ />
+
+ );
+ default:
+ return null;
+ }
+}
diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx
new file mode 100644
index 000000000..01b8411b2
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx
@@ -0,0 +1,83 @@
+import { Title } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
+import { IWidget } from '../../../../widgets/widgets';
+import { useWrapperColumnCount } from '../../Wrappers/gridstack/store';
+import { GenericTileMenu } from '../GenericTileMenu';
+import { WidgetEditModalInnerProps } from './WidgetsEditModal';
+import { WidgetsRemoveModalInnerProps } from './WidgetsRemoveModal';
+import WidgetsDefinitions from '../../../../widgets';
+
+export type WidgetChangePositionModalInnerProps = {
+ widgetId: string;
+ widget: IWidget;
+ wrapperColumnCount: number;
+};
+
+interface WidgetsMenuProps {
+ integration: string;
+ widget: IWidget | undefined;
+}
+
+export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
+ const { t } = useTranslation(`modules/${integration}`);
+ const wrapperColumnCount = useWrapperColumnCount();
+
+ if (!widget || !wrapperColumnCount) return null;
+ // Match widget.id with WidgetsDefinitions
+ // First get the keys
+ const keys = Object.keys(WidgetsDefinitions);
+ // Then find the key that matches the widget.id
+ const widgetDefinition = keys.find((key) => key === widget.id);
+ // Then get the widget definition
+ const widgetDefinitionObject =
+ WidgetsDefinitions[widgetDefinition as keyof typeof WidgetsDefinitions];
+
+ const handleDeleteClick = () => {
+ openContextModalGeneric({
+ modal: 'integrationRemove',
+ title: {t('common:remove')} ,
+ innerProps: {
+ widgetId: integration,
+ },
+ });
+ };
+
+ const handleChangeSizeClick = () => {
+ openContextModalGeneric({
+ modal: 'changeIntegrationPositionModal',
+ size: 'xl',
+ title: null,
+ innerProps: {
+ widgetId: integration,
+ widget,
+ wrapperColumnCount,
+ },
+ });
+ };
+
+ const handleEditClick = () => {
+ openContextModalGeneric({
+ modal: 'integrationOptions',
+ title: {t('descriptor.settings.title')} ,
+ innerProps: {
+ widgetId: integration,
+ options: widget.properties,
+ // Cast as the right type for the correct widget
+ widgetOptions: widgetDefinitionObject.options as any,
+ },
+ zIndex: 5,
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx
new file mode 100644
index 000000000..f92e34380
--- /dev/null
+++ b/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { Button, Group, Stack, Text } from '@mantine/core';
+import { ContextModalProps } from '@mantine/modals';
+import { Trans, useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+export type WidgetsRemoveModalInnerProps = {
+ widgetId: string;
+};
+
+export const WidgetsRemoveModal = ({
+ context,
+ id,
+ innerProps,
+}: ContextModalProps) => {
+ const { t } = useTranslation([`modules/${innerProps.widgetId}`, 'common']);
+ const { name: configName } = useConfigContext();
+ if (!configName) return null;
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const handleDeletion = () => {
+ updateConfig(
+ configName,
+ (prev) => ({
+ ...prev,
+ widgets: prev.widgets.filter((w) => w.id !== innerProps.widgetId),
+ }),
+ true
+ );
+ context.closeModal(id);
+ };
+
+ return (
+
+ ]}
+ values={{ item: innerProps.widgetId }}
+ />
+
+ context.closeModal(id)} variant="light">
+ {t('common:cancel')}
+
+ handleDeletion()}>{t('common:ok')}
+
+
+ );
+};
diff --git a/src/components/Dashboard/Tiles/type.ts b/src/components/Dashboard/Tiles/type.ts
new file mode 100644
index 000000000..e5e860af9
--- /dev/null
+++ b/src/components/Dashboard/Tiles/type.ts
@@ -0,0 +1,3 @@
+export interface BaseTileProps {
+ className?: string;
+}
diff --git a/src/components/Dashboard/Views/DashboardView.tsx b/src/components/Dashboard/Views/DashboardView.tsx
new file mode 100644
index 000000000..11f6cb0b4
--- /dev/null
+++ b/src/components/Dashboard/Views/DashboardView.tsx
@@ -0,0 +1,94 @@
+import { Center, Group, Loader, Stack } from '@mantine/core';
+import { useEffect, useMemo, useRef } from 'react';
+import { useConfigContext } from '../../../config/provider';
+import { useResize } from '../../../hooks/use-resize';
+import { useScreenLargerThan } from '../../../hooks/useScreenLargerThan';
+import { CategoryType } from '../../../types/category';
+import { WrapperType } from '../../../types/wrapper';
+import { DashboardCategory } from '../Wrappers/Category/Category';
+import { useGridstackStore } from '../Wrappers/gridstack/store';
+import { DashboardSidebar } from '../Wrappers/Sidebar/Sidebar';
+import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper';
+
+export const DashboardView = () => {
+ const wrappers = useWrapperItems();
+ const sidebarsVisible = useSidebarVisibility();
+ const { isReady, mainAreaRef } = usePrepareGridstack();
+
+ return (
+
+ {sidebarsVisible.isLoading ? (
+
+
+
+ ) : (
+ <>
+ {sidebarsVisible.left ? (
+
+ ) : null}
+
+
+ {!isReady
+ ? null
+ : wrappers.map((item) =>
+ item.type === 'category' ? (
+
+ ) : (
+
+ )
+ )}
+
+
+ {sidebarsVisible.right ? (
+
+ ) : null}
+ >
+ )}
+
+ );
+};
+
+const usePrepareGridstack = () => {
+ const mainAreaRef = useRef(null);
+ const { width } = useResize(mainAreaRef, []);
+ const setMainAreaWidth = useGridstackStore((x) => x.setMainAreaWidth);
+ const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
+
+ useEffect(() => {
+ if (width === 0) return;
+ setMainAreaWidth(width);
+ }, [width]);
+
+ return {
+ isReady: !!mainAreaWidth,
+ mainAreaRef,
+ };
+};
+
+const useSidebarVisibility = () => {
+ const layoutSettings = useConfigContext()?.config?.settings.customization.layout;
+ const screenLargerThanMd = useScreenLargerThan('md'); // For smaller screens mobile ribbons are displayed with drawers
+
+ const isScreenSizeUnknown = typeof screenLargerThanMd === 'undefined';
+
+ return {
+ right: layoutSettings?.enabledRightSidebar && screenLargerThanMd,
+ left: layoutSettings?.enabledLeftSidebar && screenLargerThanMd,
+ isLoading: isScreenSizeUnknown,
+ };
+};
+
+const useWrapperItems = () => {
+ const { config } = useConfigContext();
+
+ return useMemo(
+ () =>
+ config
+ ? [
+ ...config.categories.map((c) => ({ ...c, type: 'category' })),
+ ...config.wrappers.map((w) => ({ ...w, type: 'wrapper' })),
+ ].sort((a, b) => a.position - b.position)
+ : [],
+ [config?.categories, config?.wrappers]
+ );
+};
diff --git a/src/components/Dashboard/Views/DetailView.tsx b/src/components/Dashboard/Views/DetailView.tsx
new file mode 100644
index 000000000..e73c664bc
--- /dev/null
+++ b/src/components/Dashboard/Views/DetailView.tsx
@@ -0,0 +1,3 @@
+import { DashboardView } from './DashboardView';
+
+export const DashboardDetailView = () => ;
diff --git a/src/components/Dashboard/Views/EditView.tsx b/src/components/Dashboard/Views/EditView.tsx
new file mode 100644
index 000000000..11b387d81
--- /dev/null
+++ b/src/components/Dashboard/Views/EditView.tsx
@@ -0,0 +1,3 @@
+import { DashboardView } from './DashboardView';
+
+export const DashboardEditView = () => ;
diff --git a/src/components/Dashboard/Views/ViewToggleButton.tsx b/src/components/Dashboard/Views/ViewToggleButton.tsx
new file mode 100644
index 000000000..8be927a46
--- /dev/null
+++ b/src/components/Dashboard/Views/ViewToggleButton.tsx
@@ -0,0 +1,39 @@
+import { ActionIcon, Button, Text, Tooltip } from '@mantine/core';
+import { IconEdit, IconEditOff } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { useScreenLargerThan } from '../../../hooks/useScreenLargerThan';
+import { useEditModeStore } from './useEditModeStore';
+
+export const ViewToggleButton = () => {
+ const screenLargerThanMd = useScreenLargerThan('md');
+ const { enabled: isEditMode, toggleEditMode } = useEditModeStore();
+ const { t } = useTranslation('layout/header/actions/toggle-edit-mode');
+
+ return (
+ {t('description')}}>
+ {screenLargerThanMd ? (
+ : }
+ onClick={() => toggleEditMode()}
+ color={isEditMode ? 'red' : undefined}
+ radius="md"
+ >
+ {isEditMode ? t('button.enabled') : t('button.disabled')}
+
+ ) : (
+ toggleEditMode()}
+ variant="default"
+ radius="md"
+ size="xl"
+ color="blue"
+ >
+ {isEditMode ? : }
+
+ )}
+
+ );
+};
diff --git a/src/components/Dashboard/Views/useEditModeStore.ts b/src/components/Dashboard/Views/useEditModeStore.ts
new file mode 100644
index 000000000..5c9fbafc2
--- /dev/null
+++ b/src/components/Dashboard/Views/useEditModeStore.ts
@@ -0,0 +1,11 @@
+import create from 'zustand';
+
+interface EditModeState {
+ enabled: boolean;
+ toggleEditMode: () => void;
+}
+
+export const useEditModeStore = create((set) => ({
+ enabled: false,
+ toggleEditMode: () => set((state) => ({ enabled: !state.enabled })),
+}));
diff --git a/src/components/Dashboard/Wrappers/Category/Category.tsx b/src/components/Dashboard/Wrappers/Category/Category.tsx
new file mode 100644
index 000000000..9034a2e1a
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Category/Category.tsx
@@ -0,0 +1,32 @@
+import { Group, Title } from '@mantine/core';
+import { CategoryType } from '../../../../types/category';
+import { HomarrCardWrapper } from '../../Tiles/HomarrCardWrapper';
+import { useEditModeStore } from '../../Views/useEditModeStore';
+import { useGridstack } from '../gridstack/use-gridstack';
+import { WrapperContent } from '../WrapperContent';
+import { CategoryEditMenu } from './CategoryEditMenu';
+
+interface DashboardCategoryProps {
+ category: CategoryType;
+}
+
+export const DashboardCategory = ({ category }: DashboardCategoryProps) => {
+ const { refs, apps, widgets } = useGridstack('category', category.id);
+ const isEditMode = useEditModeStore((x) => x.enabled);
+
+ return (
+
+
+ {category.name}
+ {isEditMode ? : null}
+
+
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
new file mode 100644
index 000000000..24c89bc8f
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
@@ -0,0 +1,55 @@
+import { ActionIcon, Menu } from '@mantine/core';
+import {
+ IconDots,
+ IconTransitionTop,
+ IconTransitionBottom,
+ IconRowInsertTop,
+ IconRowInsertBottom,
+ IconEdit,
+ IconTrash,
+} from '@tabler/icons';
+import { useConfigContext } from '../../../../config/provider';
+import { CategoryType } from '../../../../types/category';
+import { useCategoryActions } from './useCategoryActions';
+
+interface CategoryEditMenuProps {
+ category: CategoryType;
+}
+
+export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
+ const { name: configName } = useConfigContext();
+ const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
+ useCategoryActions(configName, category);
+
+ return (
+
+
+
+
+
+
+
+ } onClick={edit}>
+ Edit
+
+ } onClick={remove}>
+ Remove
+
+ Change positon
+ } onClick={moveCategoryUp}>
+ Move up
+
+ } onClick={moveCategoryDown}>
+ Move down
+
+ Add category
+ } onClick={addCategoryAbove}>
+ Add category above
+
+ } onClick={addCategoryBelow}>
+ Add category below
+
+
+
+ );
+};
diff --git a/src/components/Dashboard/Wrappers/Category/CategoryEditModal.tsx b/src/components/Dashboard/Wrappers/Category/CategoryEditModal.tsx
new file mode 100644
index 000000000..0c23d45a6
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Category/CategoryEditModal.tsx
@@ -0,0 +1,53 @@
+import { Button, Group, TextInput } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { ContextModalProps } from '@mantine/modals';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { CategoryType } from '../../../../types/category';
+
+export type CategoryEditModalInnerProps = {
+ category: CategoryType;
+ onSuccess: (category: CategoryType) => Promise;
+};
+
+export const CategoryEditModal = ({
+ context,
+ innerProps,
+ id,
+}: ContextModalProps) => {
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const form = useForm({
+ initialValues: {
+ name: innerProps.category.name,
+ },
+ validate: {
+ name: (val: string) => (!val || val.trim().length === 0 ? 'Name is required' : null),
+ },
+ });
+
+ const handleSubmit = async (values: FormType) => {
+ innerProps.onSuccess({ ...innerProps.category, name: values.name });
+ context.closeModal(id);
+ };
+
+ const { t } = useTranslation('common');
+
+ return (
+
+ );
+};
+
+type FormType = {
+ name: string;
+};
diff --git a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx
new file mode 100644
index 000000000..777bdabf3
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx
@@ -0,0 +1,248 @@
+import { v4 as uuidv4 } from 'uuid';
+import { useConfigStore } from '../../../../config/store';
+import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
+import { CategoryType } from '../../../../types/category';
+import { WrapperType } from '../../../../types/wrapper';
+import { CategoryEditModalInnerProps } from './CategoryEditModal';
+
+export const useCategoryActions = (configName: string | undefined, category: CategoryType) => {
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ // creates a new category above the current
+ const addCategoryAbove = () => {
+ const abovePosition = category.position - 1;
+
+ openContextModalGeneric({
+ modal: 'categoryEditModal',
+ innerProps: {
+ category: {
+ id: uuidv4(),
+ name: 'New category',
+ position: abovePosition + 1,
+ },
+ onSuccess: async (category) => {
+ if (!configName) return;
+
+ const newWrapper: WrapperType = {
+ id: uuidv4(),
+ position: abovePosition + 2,
+ };
+
+ // Adding category and wrapper and moving other items down
+ updateConfig(
+ configName,
+ (previous) => {
+ const aboveWrappers = previous.wrappers.filter((x) => x.position <= abovePosition);
+ const aboveCategories = previous.categories.filter(
+ (x) => x.position <= abovePosition
+ );
+
+ const belowWrappers = previous.wrappers.filter((x) => x.position > abovePosition);
+ const belowCategories = previous.categories.filter((x) => x.position > abovePosition);
+
+ return {
+ ...previous,
+ categories: [
+ ...aboveCategories,
+ category,
+ // Move categories below down
+ ...belowCategories.map((x) => ({ ...x, position: x.position + 1 })),
+ ],
+ wrappers: [
+ ...aboveWrappers,
+ newWrapper,
+ // Move wrappers below down
+ ...belowWrappers.map((x) => ({ ...x, position: x.position + 1 })),
+ ],
+ };
+ },
+ true
+ );
+ },
+ },
+ });
+ };
+
+ // creates a new category below the current
+ const addCategoryBelow = () => {
+ const belowPosition = category.position + 1;
+
+ openContextModalGeneric({
+ modal: 'categoryEditModal',
+ innerProps: {
+ category: {
+ id: uuidv4(),
+ name: 'New category',
+ position: belowPosition + 1,
+ },
+ onSuccess: async (category) => {
+ if (!configName) return;
+
+ const newWrapper: WrapperType = {
+ id: uuidv4(),
+ position: belowPosition,
+ };
+
+ // Adding category and wrapper and moving other items down
+ updateConfig(
+ configName,
+ (previous) => {
+ const aboveWrappers = previous.wrappers.filter((x) => x.position < belowPosition);
+ const aboveCategories = previous.categories.filter((x) => x.position < belowPosition);
+
+ const belowWrappers = previous.wrappers.filter((x) => x.position >= belowPosition);
+ const belowCategories = previous.categories.filter(
+ (x) => x.position >= belowPosition
+ );
+
+ return {
+ ...previous,
+ categories: [
+ ...aboveCategories,
+ category,
+ // Move categories below down
+ ...belowCategories.map((x) => ({ ...x, position: x.position + 2 })),
+ ],
+ wrappers: [
+ ...aboveWrappers,
+ newWrapper,
+ // Move wrappers below down
+ ...belowWrappers.map((x) => ({ ...x, position: x.position + 2 })),
+ ],
+ };
+ },
+ true
+ );
+ },
+ },
+ });
+ };
+
+ const moveCategoryUp = () => {
+ if (!configName) return;
+
+ updateConfig(
+ configName,
+ (previous) => {
+ const currentItem = previous.categories.find((x) => x.id === category.id);
+ if (!currentItem) return previous;
+
+ const upperItem = previous.categories.find((x) => x.position === currentItem.position - 1);
+
+ if (!upperItem) return previous;
+
+ currentItem.position -= 1;
+ upperItem.position += 1;
+
+ return {
+ ...previous,
+ categories: [
+ ...previous.categories.filter((c) => ![currentItem.id, upperItem.id].includes(c.id)),
+ { ...upperItem },
+ { ...currentItem },
+ ],
+ };
+ },
+ true
+ );
+ };
+
+ const moveCategoryDown = () => {
+ if (!configName) return;
+
+ updateConfig(
+ configName,
+ (previous) => {
+ const currentItem = previous.categories.find((x) => x.id === category.id);
+ if (!currentItem) return previous;
+
+ const belowItem = previous.categories.find((x) => x.position === currentItem.position + 1);
+
+ if (!belowItem) return previous;
+
+ currentItem.position += 1;
+ belowItem.position -= 1;
+
+ return {
+ ...previous,
+ categories: [
+ ...previous.categories.filter((c) => ![currentItem.id, belowItem.id].includes(c.id)),
+ { ...currentItem },
+ { ...belowItem },
+ ],
+ };
+ },
+ true
+ );
+ };
+
+ // Removes the current category
+ const remove = () => {
+ if (!configName) return;
+ updateConfig(
+ configName,
+ (previous) => {
+ const currentItem = previous.categories.find((x) => x.id === category.id);
+ if (!currentItem) return previous;
+ // Find the main wrapper
+ const mainWrapper = previous.wrappers.find((x) => x.position === 1);
+
+ // Check that the app has an area.type or "category" and that the area.id is the current category
+ const appsToMove = previous.apps.filter(
+ (x) => x.area && x.area.type === 'category' && x.area.properties.id === currentItem.id
+ );
+ appsToMove.forEach((x) => {
+ // eslint-disable-next-line no-param-reassign
+ x.area = { type: 'wrapper', properties: { id: mainWrapper?.id ?? 'default' } };
+ });
+
+ const widgetsToMove = previous.widgets.filter(
+ (x) => x.area && x.area.type === 'category' && x.area.properties.id === currentItem.id
+ );
+
+ widgetsToMove.forEach((x) => {
+ // eslint-disable-next-line no-param-reassign
+ x.area = { type: 'wrapper', properties: { id: mainWrapper?.id ?? 'default' } };
+ });
+
+ return {
+ ...previous,
+ apps: previous.apps,
+ categories: previous.categories.filter((x) => x.id !== category.id),
+ wrappers: previous.wrappers.filter((x) => x.position !== currentItem.position),
+ };
+ },
+ true
+ );
+ };
+
+ const edit = async () => {
+ openContextModalGeneric({
+ modal: 'categoryEditModal',
+ withCloseButton: false,
+ innerProps: {
+ category,
+ onSuccess: async (category) => {
+ if (!configName) return;
+ await updateConfig(configName, (prev) => {
+ const currentCategory = prev.categories.find((c) => c.id === category.id);
+ if (!currentCategory) return prev;
+ return {
+ ...prev,
+ categories: [...prev.categories.filter((c) => c.id !== category.id), { ...category }],
+ };
+ });
+ },
+ },
+ });
+ };
+
+ return {
+ addCategoryAbove,
+ addCategoryBelow,
+ moveCategoryUp,
+ moveCategoryDown,
+ remove,
+ edit,
+ };
+};
diff --git a/src/components/Dashboard/Wrappers/Sidebar/Sidebar.tsx b/src/components/Dashboard/Wrappers/Sidebar/Sidebar.tsx
new file mode 100644
index 000000000..1603de044
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Sidebar/Sidebar.tsx
@@ -0,0 +1,59 @@
+import { Card } from '@mantine/core';
+import { RefObject } from 'react';
+import { useCardStyles } from '../../../layout/useCardStyles';
+import { useGridstack } from '../gridstack/use-gridstack';
+import { WrapperContent } from '../WrapperContent';
+
+interface DashboardSidebarProps extends DashboardSidebarInnerProps {
+ location: 'right' | 'left';
+ isGridstackReady: boolean;
+}
+
+export const DashboardSidebar = ({ location, isGridstackReady }: DashboardSidebarProps) => {
+ const {
+ cx,
+ classes: { card: cardClass },
+ } = useCardStyles(true);
+
+ return (
+
+ {isGridstackReady && }
+
+ );
+};
+
+interface DashboardSidebarInnerProps {
+ location: 'right' | 'left';
+}
+
+// Is Required because of the gridstack main area width.
+const SidebarInner = ({ location }: DashboardSidebarInnerProps) => {
+ const { refs, apps, widgets } = useGridstack('sidebar', location);
+
+ const minRow = useMinRowForFullHeight(refs.wrapper);
+ const {
+ cx,
+ classes: { card: cardClass },
+ } = useCardStyles(false);
+
+ return (
+
+
+
+ );
+};
+
+const useMinRowForFullHeight = (wrapperRef: RefObject) =>
+ wrapperRef.current ? Math.floor(wrapperRef.current!.offsetHeight / 128) : 2;
diff --git a/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx b/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx
new file mode 100644
index 000000000..bdc61c10a
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx
@@ -0,0 +1,29 @@
+import { WrapperType } from '../../../../types/wrapper';
+import { useEditModeStore } from '../../Views/useEditModeStore';
+import { useGridstack } from '../gridstack/use-gridstack';
+import { WrapperContent } from '../WrapperContent';
+
+interface DashboardWrapperProps {
+ wrapper: WrapperType;
+}
+
+export const DashboardWrapper = ({ wrapper }: DashboardWrapperProps) => {
+ const { refs, apps, widgets } = useGridstack('wrapper', wrapper.id);
+ const isEditMode = useEditModeStore((x) => x.enabled);
+ const defaultClasses = 'grid-stack grid-stack-wrapper min-row';
+
+ return (
+ 0 || widgets.length > 0 || isEditMode
+ ? defaultClasses
+ : `${defaultClasses} gridstack-empty-wrapper`
+ }
+ style={{ transitionDuration: '0s' }}
+ data-wrapper={wrapper.id}
+ ref={refs.wrapper}
+ >
+
+
+ );
+};
diff --git a/src/components/Dashboard/Wrappers/WrapperContent.tsx b/src/components/Dashboard/Wrappers/WrapperContent.tsx
new file mode 100644
index 000000000..5a864b6ab
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/WrapperContent.tsx
@@ -0,0 +1,68 @@
+import { GridStack } from 'fily-publish-gridstack';
+import { MutableRefObject, RefObject } from 'react';
+import { AppType } from '../../../types/app';
+import Widgets from '../../../widgets';
+import { IWidget, IWidgetDefinition } from '../../../widgets/widgets';
+import { WidgetWrapper } from '../../../widgets/WidgetWrapper';
+import { appTileDefinition } from '../Tiles/Apps/AppTile';
+import { GridstackTileWrapper } from '../Tiles/TileWrapper';
+import { useGridstackStore } from './gridstack/store';
+
+interface WrapperContentProps {
+ apps: AppType[];
+ widgets: IWidget[];
+ refs: {
+ wrapper: RefObject;
+ items: MutableRefObject>>;
+ gridstack: MutableRefObject;
+ };
+}
+
+export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
+ const shapeSize = useGridstackStore((x) => x.currentShapeSize);
+
+ if (!shapeSize) return null;
+
+ return (
+ <>
+ {apps?.map((app) => {
+ const { component: TileComponent, ...tile } = appTileDefinition;
+ return (
+
+
+
+ );
+ })}
+ {widgets.map((widget) => {
+ const definition = Widgets[widget.id as keyof typeof Widgets] as
+ | IWidgetDefinition
+ | undefined;
+ if (!definition) return null;
+
+ return (
+
+
+
+
+
+ );
+ })}
+ >
+ );
+}
diff --git a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts
new file mode 100644
index 000000000..6d0519c2b
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts
@@ -0,0 +1,110 @@
+import { GridItemHTMLElement, GridStack, GridStackNode } from 'fily-publish-gridstack';
+import { MutableRefObject, RefObject, useEffect } from 'react';
+import { AppType } from '../../../../types/app';
+import { ShapeType } from '../../../../types/shape';
+import { IWidget } from '../../../../widgets/widgets';
+
+export const initializeGridstack = (
+ areaType: 'wrapper' | 'category' | 'sidebar',
+ wrapperRef: RefObject,
+ gridRef: MutableRefObject,
+ itemRefs: MutableRefObject>>,
+ areaId: string,
+ items: AppType[],
+ widgets: IWidget[],
+ isEditMode: boolean,
+ wrapperColumnCount: number,
+ shapeSize: 'sm' | 'md' | 'lg',
+ tilesWithUnknownLocation: TileWithUnknownLocation[],
+ events: {
+ onChange: (changedNode: GridStackNode) => void;
+ onAdd: (addedNode: GridStackNode) => void;
+ }
+) => {
+ if (!wrapperRef.current) return;
+ // calculates the currently available count of columns
+ const columnCount = areaType === 'sidebar' ? 2 : wrapperColumnCount;
+ const minRow = areaType !== 'sidebar' ? 1 : Math.floor(wrapperRef.current.offsetHeight / 128);
+ // initialize gridstack
+ const newGrid = gridRef;
+ newGrid.current = GridStack.init(
+ {
+ column: columnCount,
+ margin: 10,
+ cellHeight: 128,
+ float: true,
+ alwaysShowResizeHandle: 'mobile',
+ acceptWidgets: true,
+ disableOneColumnMode: true,
+ staticGrid: !isEditMode,
+ minRow,
+ animate: false,
+ },
+ // selector of the gridstack item (it's eather category or wrapper)
+ `.grid-stack-${areaType}[data-${areaType}='${areaId}']`
+ );
+ const grid = newGrid.current;
+ // Must be used to update the column count after the initialization
+ grid.column(columnCount, 'none');
+
+ // Add listener for moving items around in a wrapper
+ grid.on('change', (_, el) => {
+ const nodes = el as GridStackNode[];
+ const firstNode = nodes.at(0);
+ if (!firstNode) return;
+ events.onChange(firstNode);
+ });
+
+ // Add listener for moving items in config from one wrapper to another
+ grid.on('added', (_, el) => {
+ const nodes = el as GridStackNode[];
+ const firstNode = nodes.at(0);
+ if (!firstNode) return;
+ events.onAdd(firstNode);
+ });
+
+ grid.batchUpdate();
+ grid.removeAll(false);
+ items.forEach(({ id, shape }) => {
+ const item = itemRefs.current[id]?.current;
+ setAttributesFromShape(item, shape[shapeSize]);
+ item && grid.makeWidget(item as HTMLDivElement);
+ if (!shape[shapeSize] && item) {
+ const gridItemElement = item as GridItemHTMLElement;
+ if (gridItemElement.gridstackNode) {
+ const { x, y, w, h } = gridItemElement.gridstackNode;
+ tilesWithUnknownLocation.push({ x, y, w, h, type: 'app', id });
+ }
+ }
+ });
+ widgets.forEach(({ id, shape }) => {
+ const item = itemRefs.current[id]?.current;
+ setAttributesFromShape(item, shape[shapeSize]);
+ item && grid.makeWidget(item as HTMLDivElement);
+ if (!shape[shapeSize] && item) {
+ const gridItemElement = item as GridItemHTMLElement;
+ if (gridItemElement.gridstackNode) {
+ const { x, y, w, h } = gridItemElement.gridstackNode;
+ tilesWithUnknownLocation.push({ x, y, w, h, type: 'widget', id });
+ }
+ }
+ });
+ grid.batchUpdate(false);
+};
+
+function setAttributesFromShape(ref: HTMLDivElement | null, sizedShape: ShapeType['lg']) {
+ if (!sizedShape || !ref) return;
+ ref.setAttribute('gs-x', sizedShape.location.x.toString());
+ ref.setAttribute('gs-y', sizedShape.location.y.toString());
+ ref.setAttribute('gs-w', sizedShape.size.width.toString());
+ ref.setAttribute('gs-h', sizedShape.size.height.toString());
+}
+
+export type TileWithUnknownLocation = {
+ x?: number;
+ y?: number;
+ w?: number;
+ h?: number;
+ type: 'app' | 'widget';
+ id: string;
+};
diff --git a/src/components/Dashboard/Wrappers/gridstack/store.tsx b/src/components/Dashboard/Wrappers/gridstack/store.tsx
new file mode 100644
index 000000000..6df8c2f75
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/gridstack/store.tsx
@@ -0,0 +1,44 @@
+import { useMantineTheme } from '@mantine/core';
+import create from 'zustand';
+
+export const useGridstackStore = create((set, get) => ({
+ mainAreaWidth: null,
+ currentShapeSize: null,
+ setMainAreaWidth: (w: number) =>
+ set((v) => ({ ...v, mainAreaWidth: w, currentShapeSize: getCurrentShapeSize(w) })),
+}));
+
+interface GridstackStoreType {
+ mainAreaWidth: null | number;
+ currentShapeSize: null | 'sm' | 'md' | 'lg';
+ setMainAreaWidth: (width: number) => void;
+}
+
+export const useNamedWrapperColumnCount = (): 'small' | 'medium' | 'large' | null => {
+ const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
+ const { sm, xl } = useMantineTheme().breakpoints;
+ if (!mainAreaWidth) return null;
+
+ if (mainAreaWidth >= xl) return 'large';
+
+ if (mainAreaWidth >= sm) return 'medium';
+
+ return 'small';
+};
+
+export const useWrapperColumnCount = () => {
+ switch (useNamedWrapperColumnCount()) {
+ case 'large':
+ return 12;
+ case 'medium':
+ return 6;
+ case 'small':
+ return 3;
+ default:
+ return null;
+ }
+};
+
+function getCurrentShapeSize(size: number) {
+ return size >= 1400 ? 'lg' : size >= 768 ? 'md' : 'sm';
+}
diff --git a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts
new file mode 100644
index 000000000..85a48da0e
--- /dev/null
+++ b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts
@@ -0,0 +1,321 @@
+import { GridStack, GridStackNode } from 'fily-publish-gridstack';
+import { createRef, MutableRefObject, RefObject, useEffect, useMemo, useRef } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { AppType } from '../../../../types/app';
+import { AreaType } from '../../../../types/area';
+import { IWidget } from '../../../../widgets/widgets';
+import { useEditModeStore } from '../../Views/useEditModeStore';
+import { initializeGridstack, TileWithUnknownLocation } from './init-gridstack';
+import { useGridstackStore, useWrapperColumnCount } from './store';
+
+interface UseGristackReturnType {
+ apps: AppType[];
+ widgets: IWidget[];
+ refs: {
+ wrapper: RefObject;
+ items: MutableRefObject>>;
+ gridstack: MutableRefObject;
+ };
+}
+
+export const useGridstack = (
+ areaType: 'wrapper' | 'category' | 'sidebar',
+ areaId: string
+): UseGristackReturnType => {
+ const isEditMode = useEditModeStore((x) => x.enabled);
+ const { config, configVersion, name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ // define reference for wrapper - is used to calculate the width of the wrapper
+ const wrapperRef = useRef(null);
+ // references to the diffrent items contained in the gridstack
+ const itemRefs = useRef>>({});
+ // reference of the gridstack object for modifications after initialization
+ const gridRef = useRef();
+ const wrapperColumnCount = useWrapperColumnCount();
+ const shapeSize = useGridstackStore((x) => x.currentShapeSize);
+ const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
+ // width of the wrapper (updating on page resize)
+ const root: HTMLHtmlElement = useMemo(() => document.querySelector(':root')!, []);
+
+ if (!mainAreaWidth || !shapeSize || !wrapperColumnCount) {
+ throw new Error('UseGridstack should not be executed before mainAreaWidth has been set!');
+ }
+
+ const items = useMemo(
+ () =>
+ config?.apps.filter(
+ (x) =>
+ x.area.type === areaType &&
+ (x.area.type === 'sidebar'
+ ? x.area.properties.location === areaId
+ : x.area.properties.id === areaId)
+ ) ?? [],
+ [configVersion, config?.apps.length]
+ );
+ const widgets = useMemo(() => {
+ if (!config) return [];
+ return config.widgets.filter(
+ (w) =>
+ w.area.type === areaType &&
+ (w.area.type === 'sidebar'
+ ? w.area.properties.location === areaId
+ : w.area.properties.id === areaId)
+ );
+ }, [configVersion, config?.widgets.length]);
+
+ // define items in itemRefs for easy access and reference to items
+ if (Object.keys(itemRefs.current).length !== items.length + (widgets ?? []).length) {
+ items.forEach(({ id }: { id: keyof typeof itemRefs.current }) => {
+ itemRefs.current[id] = itemRefs.current[id] || createRef();
+ });
+ (widgets ?? []).forEach(({ id }) => {
+ itemRefs.current[id] = itemRefs.current[id] || createRef();
+ });
+ }
+
+ useEffect(() => {
+ if (areaType === 'sidebar') return;
+ const widgetWidth = mainAreaWidth / wrapperColumnCount;
+ // widget width is used to define sizes of gridstack items within global.scss
+ root.style.setProperty('--gridstack-widget-width', widgetWidth.toString());
+ gridRef.current?.cellHeight(widgetWidth);
+ }, [mainAreaWidth, wrapperColumnCount, gridRef.current]);
+
+ useEffect(() => {
+ // column count is used to define count of columns of gridstack within global.scss
+ root.style.setProperty('--gridstack-column-count', wrapperColumnCount.toString());
+ }, [wrapperColumnCount]);
+
+ const onChange = isEditMode
+ ? (changedNode: GridStackNode) => {
+ if (!configName) return;
+
+ const itemType = changedNode.el?.getAttribute('data-type');
+ const itemId = changedNode.el?.getAttribute('data-id');
+ if (!itemType || !itemId) return;
+
+ // Updates the config and defines the new position of the item
+ updateConfig(configName, (previous) => {
+ const currentItem =
+ itemType === 'app'
+ ? previous.apps.find((x) => x.id === itemId)
+ : previous.widgets.find((x) => x.id === itemId);
+ if (!currentItem) return previous;
+
+ currentItem.shape[shapeSize] = {
+ location: {
+ x: changedNode.x!,
+ y: changedNode.y!,
+ },
+ size: {
+ width: changedNode.w!,
+ height: changedNode.h!,
+ },
+ };
+
+ if (itemType === 'app') {
+ return {
+ ...previous,
+ apps: [
+ ...previous.apps.filter((x) => x.id !== itemId),
+ { ...(currentItem as AppType) },
+ ],
+ };
+ }
+
+ return {
+ ...previous,
+ widgets: [
+ ...previous.widgets.filter((x) => x.id !== itemId),
+ { ...(currentItem as IWidget) },
+ ],
+ };
+ });
+ }
+ : () => {};
+
+ const onAdd = isEditMode
+ ? (addedNode: GridStackNode) => {
+ if (!configName) return;
+
+ const itemType = addedNode.el?.getAttribute('data-type');
+ const itemId = addedNode.el?.getAttribute('data-id');
+ if (!itemType || !itemId) return;
+
+ // Updates the config and defines the new position and wrapper of the item
+ updateConfig(
+ configName,
+ (previous) => {
+ const currentItem =
+ itemType === 'app'
+ ? previous.apps.find((x) => x.id === itemId)
+ : previous.widgets.find((x) => x.id === itemId);
+ if (!currentItem) return previous;
+
+ if (areaType === 'sidebar') {
+ currentItem.area = {
+ type: areaType,
+ properties: {
+ location: areaId as 'right' | 'left',
+ },
+ };
+ } else {
+ currentItem.area = {
+ type: areaType,
+ properties: {
+ id: areaId,
+ },
+ };
+ }
+
+ currentItem.shape[shapeSize] = {
+ location: {
+ x: addedNode.x!,
+ y: addedNode.y!,
+ },
+ size: {
+ width: addedNode.w!,
+ height: addedNode.h!,
+ },
+ };
+
+ if (itemType === 'app') {
+ return {
+ ...previous,
+ apps: [
+ ...previous.apps.filter((x) => x.id !== itemId),
+ { ...(currentItem as AppType) },
+ ],
+ };
+ }
+
+ return {
+ ...previous,
+ widgets: [
+ ...previous.widgets.filter((x) => x.id !== itemId),
+ { ...(currentItem as IWidget) },
+ ],
+ };
+ },
+ (prev, curr) => {
+ const isApp = itemType === 'app';
+
+ if (isApp) {
+ const currItem = curr.apps.find((x) => x.id === itemId);
+ const prevItem = prev.apps.find((x) => x.id === itemId);
+ if (!currItem || !prevItem) return false;
+
+ return (
+ currItem.area.type !== prevItem.area.type ||
+ Object.entries(currItem.area.properties).some(
+ ([key, value]) =>
+ prevItem.area.properties[key as keyof AreaType['properties']] !== value
+ )
+ );
+ }
+
+ const currItem = curr.widgets.find((x) => x.id === itemId);
+ const prevItem = prev.widgets.find((x) => x.id === itemId);
+ if (!currItem || !prevItem) return false;
+
+ return (
+ currItem.area.type !== prevItem.area.type ||
+ Object.entries(currItem.area.properties).some(
+ ([key, value]) =>
+ prevItem.area.properties[key as keyof AreaType['properties']] !== value
+ )
+ );
+ }
+ );
+ }
+ : () => {};
+
+ // initialize the gridstack
+ useEffect(() => {
+ const removeEventHandlers = () => {
+ gridRef.current?.off('change');
+ gridRef.current?.off('added');
+ };
+
+ const tilesWithUnknownLocation: TileWithUnknownLocation[] = [];
+ initializeGridstack(
+ areaType,
+ wrapperRef,
+ gridRef,
+ itemRefs,
+ areaId,
+ items,
+ widgets ?? [],
+ isEditMode,
+ wrapperColumnCount,
+ shapeSize,
+ tilesWithUnknownLocation,
+ {
+ onChange,
+ onAdd,
+ }
+ );
+ if (!configName) return removeEventHandlers;
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ apps: prev.apps.map((app) => {
+ const currentUnknownLocation = tilesWithUnknownLocation.find(
+ (x) => x.type === 'app' && x.id === app.id
+ );
+ if (!currentUnknownLocation) return app;
+
+ return {
+ ...app,
+ shape: {
+ ...app.shape,
+ [shapeSize]: {
+ location: {
+ x: currentUnknownLocation.x,
+ y: currentUnknownLocation.y,
+ },
+ size: {
+ width: currentUnknownLocation.w,
+ height: currentUnknownLocation.h,
+ },
+ },
+ },
+ };
+ }),
+ widgets: prev.widgets.map((widget) => {
+ const currentUnknownLocation = tilesWithUnknownLocation.find(
+ (x) => x.type === 'widget' && x.id === widget.id
+ );
+ if (!currentUnknownLocation) return widget;
+
+ return {
+ ...widget,
+ shape: {
+ ...widget.shape,
+ [shapeSize]: {
+ location: {
+ x: currentUnknownLocation.x,
+ y: currentUnknownLocation.y,
+ },
+ size: {
+ width: currentUnknownLocation.w,
+ height: currentUnknownLocation.h,
+ },
+ },
+ },
+ };
+ }),
+ }));
+ return removeEventHandlers;
+ }, [items, wrapperRef.current, widgets, wrapperColumnCount]);
+
+ return {
+ apps: items,
+ widgets: widgets ?? [],
+ refs: {
+ items: itemRefs,
+ wrapper: wrapperRef,
+ gridstack: gridRef,
+ },
+ };
+};
diff --git a/src/components/SearchNewTabSwitch/SearchNewTabSwitch.tsx b/src/components/SearchNewTabSwitch/SearchNewTabSwitch.tsx
deleted file mode 100644
index 00ebb88f4..000000000
--- a/src/components/SearchNewTabSwitch/SearchNewTabSwitch.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React, { useState } from 'react';
-import { createStyles, Switch, Group } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-
-const useStyles = createStyles((theme) => ({
- root: {
- position: 'relative',
- '& *': {
- cursor: 'pointer',
- },
- },
-
- icon: {
- pointerEvents: 'none',
- position: 'absolute',
- zIndex: 1,
- top: 3,
- },
-
- iconLight: {
- left: 4,
- color: theme.white,
- },
-
- iconDark: {
- right: 4,
- color: theme.colors.gray[6],
- },
-}));
-
-export function SearchNewTabSwitch() {
- const { config, setConfig } = useConfig();
- const { classes, cx } = useStyles();
- const defaultPosition = config?.settings?.searchNewTab ?? true;
- const [openInNewTab, setOpenInNewTab] = useState(defaultPosition);
- const { t } = useTranslation('settings/general/search-engine');
- const toggleOpenInNewTab = () => {
- setOpenInNewTab(!openInNewTab);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- searchNewTab: !openInNewTab,
- },
- });
- };
-
- return (
-
-
- toggleOpenInNewTab()} size="md" />
-
- {t('searchNewTab.label')}
-
- );
-}
diff --git a/src/components/Settings/AdvancedSettings.tsx b/src/components/Settings/AdvancedSettings.tsx
deleted file mode 100644
index 3d5b7bd96..000000000
--- a/src/components/Settings/AdvancedSettings.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { TextInput, Button, Stack, Textarea } from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { ColorSelector } from './ColorSelector';
-import { OpacitySelector } from './OpacitySelector';
-import { AppCardWidthSelector } from './AppCardWidthSelector';
-import { ShadeSelector } from './ShadeSelector';
-import { GrowthSelector } from './GrowthSelector';
-
-export default function TitleChanger() {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation('settings/customization/page-appearance');
-
- const form = useForm({
- initialValues: {
- title: config.settings.title,
- logo: config.settings.logo,
- favicon: config.settings.favicon,
- background: config.settings.background,
- customCSS: config.settings.customCSS,
- },
- });
-
- const saveChanges = (values: {
- title?: string;
- logo?: string;
- favicon?: string;
- background?: string;
- customCSS?: string;
- }) => {
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- title: values.title,
- logo: values.logo,
- favicon: values.favicon,
- background: values.background,
- customCSS: values.customCSS,
- },
- });
- };
-
- return (
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/Settings/AppCardWidthSelector.tsx b/src/components/Settings/AppCardWidthSelector.tsx
deleted file mode 100644
index cfeb51338..000000000
--- a/src/components/Settings/AppCardWidthSelector.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { Text, Slider, Stack } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-
-export function AppCardWidthSelector() {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation('settings/customization/app-width');
-
- const setappCardWidth = (appCardWidth: number) => {
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- appCardWidth,
- },
- });
- };
-
- return (
-
- {t('label')}
- setappCardWidth(value)}
- />
-
- );
-}
diff --git a/src/components/Settings/ColorSelector.tsx b/src/components/Settings/ColorSelector.tsx
deleted file mode 100644
index 817378e7e..000000000
--- a/src/components/Settings/ColorSelector.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { useState } from 'react';
-import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { useColorTheme } from '../../tools/color';
-
-interface ColorControlProps {
- type: string;
-}
-
-export function ColorSelector({ type }: ColorControlProps) {
- const { config, setConfig } = useConfig();
- const [opened, setOpened] = useState(false);
- const { primaryColor, secondaryColor, setPrimaryColor, setSecondaryColor } = useColorTheme();
- const { t } = useTranslation('settings/customization/color-selector');
-
- const theme = useMantineTheme();
- const colors = Object.keys(theme.colors).map((color) => ({
- swatch: theme.colors[color][6],
- color,
- }));
-
- const configColor = type === 'primary' ? primaryColor : secondaryColor;
-
- const setConfigColor = (color: string) => {
- if (type === 'primary') {
- setPrimaryColor(color);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- primaryColor: color,
- },
- });
- } else {
- setSecondaryColor(color);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- secondaryColor: color,
- },
- });
- }
- };
-
- const swatches = colors.map(({ color, swatch }) => (
-
- setConfigColor(color)}
- color={swatch}
- size={22}
- style={{ cursor: 'pointer' }}
- />
-
- ));
-
- return (
-
- setOpened(false)}
- position="left"
- withArrow
- >
-
- setOpened((o) => !o)}
- size={22}
- style={{ cursor: 'pointer' }}
- />
-
-
-
- {swatches}
-
-
-
-
- {t('suffix', {
- color: type[0].toUpperCase() + type.slice(1),
- })}
-
-
- );
-}
diff --git a/src/components/Settings/Common/CommonSettings.tsx b/src/components/Settings/Common/CommonSettings.tsx
new file mode 100644
index 000000000..e404bd9bb
--- /dev/null
+++ b/src/components/Settings/Common/CommonSettings.tsx
@@ -0,0 +1,31 @@
+import { ScrollArea, Space, Stack, Text } from '@mantine/core';
+import { useViewportSize } from '@mantine/hooks';
+import { useConfigContext } from '../../../config/provider';
+import ConfigChanger from '../../Config/ConfigChanger';
+import ConfigActions from './Config/ConfigActions';
+import LanguageSelect from './Language/LanguageSelect';
+import { SearchEngineSelector } from './SearchEngine/SearchEngineSelector';
+
+export default function CommonSettings() {
+ const { config } = useConfigContext();
+ const { height, width } = useViewportSize();
+
+ if (!config) {
+ return (
+
+ No active config
+
+ );
+ }
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Settings/Common/Config/ConfigActions.tsx b/src/components/Settings/Common/Config/ConfigActions.tsx
new file mode 100644
index 000000000..6ff6ac4db
--- /dev/null
+++ b/src/components/Settings/Common/Config/ConfigActions.tsx
@@ -0,0 +1,142 @@
+import {
+ ActionIcon,
+ Alert,
+ Center,
+ createStyles,
+ Flex,
+ Text,
+ useMantineTheme,
+} from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { openConfirmModal } from '@mantine/modals';
+import { showNotification } from '@mantine/notifications';
+import { IconAlertTriangle, IconCheck, IconCopy, IconDownload, IconTrash } from '@tabler/icons';
+import fileDownload from 'js-file-download';
+import { Trans, useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { useDeleteConfigMutation } from '../../../../tools/config/mutations/useDeleteConfigMutation';
+import Tip from '../../../layout/Tip';
+import { CreateConfigCopyModal } from './CreateCopyModal';
+
+export default function ConfigActions() {
+ const router = useRouter();
+ const { t } = useTranslation(['settings/general/config-changer', 'settings/common', 'common']);
+ const [createCopyModalOpened, createCopyModal] = useDisclosure(false);
+ const { config } = useConfigContext();
+ const { removeConfig } = useConfigStore();
+ const { mutateAsync } = useDeleteConfigMutation(config?.configProperties.name ?? 'default');
+
+ if (!config) return null;
+
+ const handleDownload = () => {
+ fileDownload(JSON.stringify(config, null, '\t'), `${config?.configProperties.name}.json`);
+ };
+
+ const handleDeletion = async () => {
+ openConfirmModal({
+ title: t('modal.confirmDeletion.title'),
+ children: (
+ <>
+ } mb="md">
+ , code: }}
+ />
+
+ {t('modal.confirmDeletion.text')}
+ >
+ ),
+ labels: {
+ confirm: (
+ , code: }}
+ />
+ ),
+ cancel: t('common:cancel'),
+ },
+ zIndex: 201,
+ onConfirm: async () => {
+ const response = await mutateAsync();
+
+ if (response.message) {
+ showNotification({
+ title: t('buttons.delete.notifications.deleteFailedDefaultConfig.title'),
+ message: t('buttons.delete.notifications.deleteFailedDefaultConfig.message'),
+ });
+ return;
+ }
+
+ showNotification({
+ title: t('buttons.delete.notifications.deleted.title'),
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('buttons.delete.notifications.deleted.message'),
+ });
+
+ removeConfig(config?.configProperties.name ?? 'default');
+
+ router.push('/');
+ },
+ });
+ };
+
+ const { classes } = useStyles();
+ const { colors } = useMantineTheme();
+
+ return (
+ <>
+
+
+
+
+ {t('buttons.download')}
+
+
+
+ {t('buttons.delete.text')}
+
+
+
+ {t('buttons.saveCopy')}
+
+
+
+
+ {t('settings/common:tips.configTip')}
+
+ >
+ );
+}
+
+const useStyles = createStyles(() => ({
+ actionIcon: {
+ width: 'auto',
+ height: 'auto',
+ maxWidth: 'auto',
+ maxHeight: 'auto',
+ flexGrow: 1,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ textAlign: 'center',
+ rowGap: 10,
+ padding: 10,
+ },
+}));
diff --git a/src/components/Settings/Common/Config/CreateCopyModal.tsx b/src/components/Settings/Common/Config/CreateCopyModal.tsx
new file mode 100644
index 000000000..580df42d0
--- /dev/null
+++ b/src/components/Settings/Common/Config/CreateCopyModal.tsx
@@ -0,0 +1,79 @@
+import { Button, Group, Modal, TextInput, Title } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { useCopyConfigMutation } from '../../../../tools/config/mutations/useCopyConfigMutation';
+
+interface CreateConfigCopyModalProps {
+ opened: boolean;
+ closeModal: () => void;
+ initialConfigName: string;
+}
+
+export const CreateConfigCopyModal = ({
+ opened,
+ closeModal,
+ initialConfigName,
+}: CreateConfigCopyModalProps) => {
+ const { configs } = useConfigStore();
+ const { t } = useTranslation(['settings/general/config-changer']);
+
+ const form = useForm({
+ initialValues: {
+ configName: initialConfigName,
+ },
+ validate: {
+ configName: (value) => {
+ if (!value) {
+ return t('modal.copy.form.configName.validation.required');
+ }
+
+ const configNames = configs.map((x) => x.value.configProperties.name);
+ if (configNames.includes(value)) {
+ return t('modal.copy.form.configName.validation.notUnique');
+ }
+
+ return undefined;
+ },
+ },
+ validateInputOnChange: true,
+ validateInputOnBlur: true,
+ });
+
+ const { mutateAsync } = useCopyConfigMutation(form.values.configName);
+
+ const handleClose = () => {
+ form.setFieldValue('configName', initialConfigName);
+ closeModal();
+ };
+
+ const handleSubmit = async (values: typeof form.values) => {
+ if (!form.isValid) return;
+
+ await mutateAsync();
+ closeModal();
+ };
+
+ return (
+ {t('modal.copy.title')}}
+ >
+
+
+
+
+ {t('modal.copy.form.submitButton')}
+
+
+
+
+ );
+};
diff --git a/src/components/Settings/Common/Credits.tsx b/src/components/Settings/Common/Credits.tsx
new file mode 100644
index 000000000..e3867d710
--- /dev/null
+++ b/src/components/Settings/Common/Credits.tsx
@@ -0,0 +1,27 @@
+import { Group, Anchor, Text } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+
+export default function Credits() {
+ const { t } = useTranslation('settings/common');
+
+ return (
+
+
+ {t('credits.madeWithLove')}
+
+ ajnart
+ {' '}
+ and you!
+
+
+ );
+}
diff --git a/src/components/Settings/LanguageSwitch.tsx b/src/components/Settings/Common/Language/LanguageSelect.tsx
similarity index 87%
rename from src/components/Settings/LanguageSwitch.tsx
rename to src/components/Settings/Common/Language/LanguageSelect.tsx
index 35f3b0d80..b21b2011d 100644
--- a/src/components/Settings/LanguageSwitch.tsx
+++ b/src/components/Settings/Common/Language/LanguageSelect.tsx
@@ -5,9 +5,9 @@ import { forwardRef, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { getCookie, setCookie } from 'cookies-next';
-import { getLanguageByCode, Language } from '../../tools/language';
+import { getLanguageByCode, Language } from '../../../../tools/language';
-export default function LanguageSwitch() {
+export default function LanguageSelect() {
const { t, i18n } = useTranslation('settings/general/internationalization');
const { changeLanguage } = i18n;
const configLocale = getCookie('config-locale');
@@ -77,8 +77,14 @@ export default function LanguageSwitch() {
filter={(value, item) => {
const selectItems = item as unknown as { value: string; language: Language };
return (
- selectItems.language.originalName.trim().includes(value.trim()) ||
- selectItems.language.translatedName.trim().includes(value.trim())
+ selectItems.language.originalName
+ .toLowerCase()
+ .trim()
+ .includes(value.toLowerCase().trim()) ||
+ selectItems.language.translatedName
+ .toLowerCase()
+ .trim()
+ .includes(value.toLowerCase().trim())
);
}}
styles={{
diff --git a/src/components/Settings/Common/SearchEngine/SearchEngineSelector.tsx b/src/components/Settings/Common/SearchEngine/SearchEngineSelector.tsx
new file mode 100644
index 000000000..c001184e2
--- /dev/null
+++ b/src/components/Settings/Common/SearchEngine/SearchEngineSelector.tsx
@@ -0,0 +1,134 @@
+import { Alert, Paper, SegmentedControl, Space, Stack, TextInput, Title } from '@mantine/core';
+import { IconInfoCircle } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import {
+ CommonSearchEngineCommonSettingsType,
+ SearchEngineCommonSettingsType,
+} from '../../../../types/settings';
+import { SearchNewTabSwitch } from './SearchNewTabSwitch';
+
+interface Props {
+ searchEngine: SearchEngineCommonSettingsType;
+}
+
+export const SearchEngineSelector = ({ searchEngine }: Props) => {
+ const { t } = useTranslation(['settings/general/search-engine']);
+ const { updateSearchEngineConfig } = useUpdateSearchEngineConfig();
+
+ const [engine, setEngine] = useState(searchEngine.type);
+ const [searchUrl, setSearchUrl] = useState(
+ searchEngine.type === 'custom' ? searchEngine.properties.template : searchUrls.google
+ );
+
+ const onEngineChange = (value: EngineType) => {
+ setEngine(value);
+ updateSearchEngineConfig(value, searchUrl);
+ };
+
+ const onSearchUrlChange: ChangeEventHandler = (ev) => {
+ const url = ev.currentTarget.value;
+ setSearchUrl(url);
+ updateSearchEngineConfig(engine, url);
+ };
+
+ return (
+
+
+ {t('title')}
+
+
+
+
+
+ {t('configurationName')}
+
+
+
+
+ {engine === 'custom' && (
+ <>
+
+
+ >
+ )}
+
+ } color="blue">
+ {t('tips.generalTip')}
+
+
+ );
+};
+
+const searchEngineOptions: { label: string; value: EngineType }[] = [
+ { label: 'Google', value: 'google' },
+ { label: 'DuckDuckGo', value: 'duckDuckGo' },
+ { label: 'Bing', value: 'bing' },
+ { label: 'Custom', value: 'custom' },
+];
+
+export const searchUrls: { [key in CommonSearchEngineCommonSettingsType['type']]: string } = {
+ google: 'https://google.com/search?q=',
+ duckDuckGo: 'https://duckduckgo.com/?q=',
+ bing: 'https://bing.com/search?q=',
+};
+
+type EngineType = SearchEngineCommonSettingsType['type'];
+
+const useUpdateSearchEngineConfig = () => {
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ if (!configName) {
+ return {
+ updateSearchEngineConfig: () => {},
+ };
+ }
+
+ const updateSearchEngineConfig = (engine: EngineType, searchUrl: string) => {
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ common: {
+ ...prev.settings.common,
+ searchEngine:
+ engine === 'custom'
+ ? {
+ type: engine,
+ properties: {
+ ...prev.settings.common.searchEngine.properties,
+ template: searchUrl,
+ },
+ }
+ : {
+ type: engine,
+ properties: {
+ openInNewTab: prev.settings.common.searchEngine.properties.openInNewTab,
+ enabled: prev.settings.common.searchEngine.properties.enabled,
+ },
+ },
+ },
+ },
+ }));
+ };
+
+ return {
+ updateSearchEngineConfig,
+ };
+};
diff --git a/src/components/Settings/Common/SearchEngine/SearchNewTabSwitch.tsx b/src/components/Settings/Common/SearchEngine/SearchNewTabSwitch.tsx
new file mode 100644
index 000000000..46ae6988a
--- /dev/null
+++ b/src/components/Settings/Common/SearchEngine/SearchNewTabSwitch.tsx
@@ -0,0 +1,44 @@
+import { Switch } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { SearchEngineCommonSettingsType } from '../../../../types/settings';
+
+interface SearchNewTabSwitchProps {
+ defaultValue: boolean | undefined;
+}
+
+export function SearchNewTabSwitch({ defaultValue }: SearchNewTabSwitchProps) {
+ const { t } = useTranslation('settings/general/search-engine');
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ const [openInNewTab, setOpenInNewTab] = useState(defaultValue ?? true);
+
+ if (!configName) return null;
+
+ const toggleOpenInNewTab = () => {
+ setOpenInNewTab(!openInNewTab);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ common: {
+ ...prev.settings.common,
+ searchEngine: {
+ ...prev.settings.common.searchEngine,
+ properties: {
+ ...prev.settings.common.searchEngine.properties,
+ openInNewTab: !openInNewTab,
+ },
+ } as SearchEngineCommonSettingsType,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/Settings/CommonSettings.tsx b/src/components/Settings/CommonSettings.tsx
deleted file mode 100644
index c7d10b3af..000000000
--- a/src/components/Settings/CommonSettings.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Text, SegmentedControl, TextInput, Stack } from '@mantine/core';
-import { useState } from 'react';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { SearchNewTabSwitch } from '../SearchNewTabSwitch/SearchNewTabSwitch';
-import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch';
-import { WidgetsPositionSwitch } from '../WidgetsPositionSwitch/WidgetsPositionSwitch';
-import ConfigChanger from '../Config/ConfigChanger';
-import SaveConfigComponent from '../Config/SaveConfig';
-import ModuleEnabler from './ModuleEnabler';
-import Tip from '../layout/Tip';
-import LanguageSwitch from './LanguageSwitch';
-
-export default function CommonSettings(args: any) {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation(['settings/general/search-engine', 'settings/common']);
-
- const matches = [
- { label: 'Google', value: 'https://google.com/search?q=' },
- { label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=' },
- { label: 'Bing', value: 'https://bing.com/search?q=' },
- { label: 'Custom', value: 'Custom' },
- ];
-
- const [customSearchUrl, setCustomSearchUrl] = useState(config.settings.searchUrl);
- const [searchUrl, setSearchUrl] = useState(
- matches.find((match) => match.value === config.settings.searchUrl)?.value ?? 'Custom'
- );
-
- return (
-
-
- {t('title')}
- {t('tips.generalTip')}
- {
- setSearchUrl(e);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- searchUrl: e,
- },
- });
- }
- }
- data={matches}
- />
- {searchUrl === 'Custom' && (
- <>
- {t('tips.placeholderTip')}
- {
- setCustomSearchUrl(event.currentTarget.value);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- searchUrl: event.currentTarget.value,
- },
- });
- }}
- />
- >
- )}
-
-
-
-
-
-
-
-
- {t('settings/common:tips.configTip')}
-
- );
-}
diff --git a/src/components/Settings/Credits.tsx b/src/components/Settings/Credits.tsx
deleted file mode 100644
index 641889879..000000000
--- a/src/components/Settings/Credits.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Group, ActionIcon, Anchor, Text } from '@mantine/core';
-import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-
-import { CURRENT_VERSION } from '../../../data/constants';
-
-export default function Credits(props: any) {
- const { t } = useTranslation('settings/common');
-
- return (
-
-
- component="a" href="https://github.com/ajnart/homarr" size="lg">
-
-
-
- {CURRENT_VERSION}
-
-
-
-
- {t('credits.madeWithLove')}
-
- ajnart
-
-
- component="a" href="https://discord.gg/aCsmEV5RgA" size="lg">
-
-
-
-
- );
-}
diff --git a/src/components/Settings/Customization/CustomizationSettings.tsx b/src/components/Settings/Customization/CustomizationSettings.tsx
new file mode 100644
index 000000000..8265ac4a6
--- /dev/null
+++ b/src/components/Settings/Customization/CustomizationSettings.tsx
@@ -0,0 +1,47 @@
+import { ScrollArea, Stack } from '@mantine/core';
+import { useViewportSize } from '@mantine/hooks';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../config/provider';
+import { useConfigStore } from '../../../config/store';
+import { LayoutSelector } from './Layout/LayoutSelector';
+import { BackgroundChanger } from './Meta/BackgroundChanger';
+import { FaviconChanger } from './Meta/FaviconChanger';
+import { LogoImageChanger } from './Meta/LogoImageChanger';
+import { MetaTitleChanger } from './Meta/MetaTitleChanger';
+import { PageTitleChanger } from './Meta/PageTitleChanger';
+import { ColorSelector } from './Theme/ColorSelector';
+import { CustomCssChanger } from './Theme/CustomCssChanger';
+import { OpacitySelector } from './Theme/OpacitySelector';
+import { ShadeSelector } from './Theme/ShadeSelector';
+
+export default function CustomizationSettings() {
+ const { config, name: configName } = useConfigContext();
+ const { t } = useTranslation('common');
+ const { height, width } = useViewportSize();
+
+ const { updateConfig } = useConfigStore();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Settings/Customization/Layout/LayoutSelector.tsx b/src/components/Settings/Customization/Layout/LayoutSelector.tsx
new file mode 100644
index 000000000..94e380827
--- /dev/null
+++ b/src/components/Settings/Customization/Layout/LayoutSelector.tsx
@@ -0,0 +1,172 @@
+import {
+ ActionIcon,
+ Checkbox,
+ createStyles,
+ Flex,
+ Group,
+ Paper,
+ Stack,
+ Text,
+ Title,
+ useMantineTheme,
+} from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { CustomizationSettingsType } from '../../../../types/settings';
+import { Logo } from '../../../layout/Logo';
+
+interface LayoutSelectorProps {
+ defaultLayout: CustomizationSettingsType['layout'] | undefined;
+}
+
+// TODO: add translations
+export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
+ const { classes } = useStyles();
+
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ const [leftSidebar, setLeftSidebar] = useState(defaultLayout?.enabledLeftSidebar ?? true);
+ const [rightSidebar, setRightSidebar] = useState(defaultLayout?.enabledRightSidebar ?? true);
+ const [docker, setDocker] = useState(defaultLayout?.enabledDocker ?? false);
+ const [ping, setPing] = useState(defaultLayout?.enabledPing ?? false);
+ const [searchBar, setSearchBar] = useState(defaultLayout?.enabledSearchbar ?? false);
+
+ const { colors, colorScheme } = useMantineTheme();
+ const { t } = useTranslation('settings/common');
+
+ if (!configName) return null;
+
+ const handleChange = (
+ key: keyof CustomizationSettingsType['layout'],
+ event: ChangeEvent,
+ setState: Dispatch>
+ ) => {
+ const value = event.target.checked;
+ setState(value);
+ updateConfig(
+ configName,
+ (prev) => {
+ const { layout } = prev.settings.customization;
+
+ layout[key] = value;
+
+ return {
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ layout,
+ },
+ },
+ };
+ },
+ true
+ );
+ };
+
+ return (
+
+ {t('layout.title')}
+
+
+
+
+
+ {searchBar ? (
+
+ ) : null}
+ {docker ? : null}
+
+
+
+
+
+ {leftSidebar && (
+
+
+ {t('layout.sidebar')}
+
+ Only for
+
+ apps &
+ integrations
+
+
+
+ )}
+
+
+ {t('layout.main')}
+
+ {t('layout.cannotturnoff')}
+
+
+
+ {rightSidebar && (
+
+
+ {t('layout.sidebar')}
+
+ Only for
+
+ apps &
+ integrations
+
+
+
+ )}
+
+
+
+ handleChange('enabledLeftSidebar', ev, setLeftSidebar)}
+ />
+ handleChange('enabledRightSidebar', ev, setRightSidebar)}
+ />
+ handleChange('enabledSearchbar', ev, setSearchBar)}
+ />
+ handleChange('enabledDocker', ev, setDocker)}
+ />
+ handleChange('enabledPing', ev, setPing)}
+ />
+
+
+ );
+};
+
+const useStyles = createStyles((theme) => ({
+ primaryWrapper: {
+ flexGrow: 2,
+ },
+ secondaryWrapper: {
+ flexGrow: 1,
+ maxWidth: 100,
+ },
+}));
diff --git a/src/components/Settings/Customization/Meta/BackgroundChanger.tsx b/src/components/Settings/Customization/Meta/BackgroundChanger.tsx
new file mode 100644
index 000000000..5c3efabd2
--- /dev/null
+++ b/src/components/Settings/Customization/Meta/BackgroundChanger.tsx
@@ -0,0 +1,43 @@
+import { TextInput } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface BackgroundChangerProps {
+ defaultValue: string | undefined;
+}
+
+export const BackgroundChanger = ({ defaultValue }: BackgroundChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [backgroundImageUrl, setBackgroundImageUrl] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const backgroundImageUrl = value.trim().length === 0 ? undefined : value;
+ setBackgroundImageUrl(backgroundImageUrl);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ backgroundImageUrl,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Meta/FaviconChanger.tsx b/src/components/Settings/Customization/Meta/FaviconChanger.tsx
new file mode 100644
index 000000000..7bb71f815
--- /dev/null
+++ b/src/components/Settings/Customization/Meta/FaviconChanger.tsx
@@ -0,0 +1,43 @@
+import { TextInput } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface FaviconChangerProps {
+ defaultValue: string | undefined;
+}
+
+export const FaviconChanger = ({ defaultValue }: FaviconChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [faviconUrl, setFaviconUrl] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const faviconUrl = value.trim().length === 0 ? undefined : value;
+ setFaviconUrl(faviconUrl);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ faviconUrl,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Meta/LogoImageChanger.tsx b/src/components/Settings/Customization/Meta/LogoImageChanger.tsx
new file mode 100644
index 000000000..0f45de7d5
--- /dev/null
+++ b/src/components/Settings/Customization/Meta/LogoImageChanger.tsx
@@ -0,0 +1,43 @@
+import { TextInput } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface LogoImageChangerProps {
+ defaultValue: string | undefined;
+}
+
+export const LogoImageChanger = ({ defaultValue }: LogoImageChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [logoImageSrc, setLogoImageSrc] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const logoImageSrc = value.trim().length === 0 ? undefined : value;
+ setLogoImageSrc(logoImageSrc);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ logoImageUrl: logoImageSrc,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Meta/MetaTitleChanger.tsx b/src/components/Settings/Customization/Meta/MetaTitleChanger.tsx
new file mode 100644
index 000000000..3e871a9b8
--- /dev/null
+++ b/src/components/Settings/Customization/Meta/MetaTitleChanger.tsx
@@ -0,0 +1,44 @@
+import { TextInput } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface MetaTitleChangerProps {
+ defaultValue: string | undefined;
+}
+
+// TODO: change to pageTitle
+export const MetaTitleChanger = ({ defaultValue }: MetaTitleChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [metaTitle, setMetaTitle] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const metaTitle = value.trim().length === 0 ? undefined : value;
+ setMetaTitle(metaTitle);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ metaTitle,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Meta/PageTitleChanger.tsx b/src/components/Settings/Customization/Meta/PageTitleChanger.tsx
new file mode 100644
index 000000000..c2febd0ed
--- /dev/null
+++ b/src/components/Settings/Customization/Meta/PageTitleChanger.tsx
@@ -0,0 +1,44 @@
+import { TextInput } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface PageTitleChangerProps {
+ defaultValue: string | undefined;
+}
+
+// TODO: change to dashboard title
+export const PageTitleChanger = ({ defaultValue }: PageTitleChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [pageTitle, setPageTitle] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const pageTitle = value.trim().length === 0 ? undefined : value;
+ setPageTitle(pageTitle);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ pageTitle,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Theme/ColorSelector.tsx b/src/components/Settings/Customization/Theme/ColorSelector.tsx
new file mode 100644
index 000000000..053a7d386
--- /dev/null
+++ b/src/components/Settings/Customization/Theme/ColorSelector.tsx
@@ -0,0 +1,104 @@
+import {
+ ColorSwatch,
+ Grid,
+ Group,
+ MantineTheme,
+ Popover,
+ Text,
+ useMantineTheme,
+} from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { useColorTheme } from '../../../../tools/color';
+
+interface ColorControlProps {
+ defaultValue: MantineTheme['primaryColor'] | undefined;
+ type: 'primary' | 'secondary';
+}
+
+export function ColorSelector({ type, defaultValue }: ColorControlProps) {
+ const { t } = useTranslation('settings/customization/color-selector');
+ const [color, setColor] = useState(defaultValue);
+ const [popoverOpened, popover] = useDisclosure(false);
+ const { setPrimaryColor, setSecondaryColor } = useColorTheme();
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ const theme = useMantineTheme();
+ const colors = Object.keys(theme.colors).map((color) => ({
+ swatch: theme.colors[color][6],
+ color,
+ }));
+
+ if (!color || !configName) return null;
+
+ const handleSelection = (color: MantineTheme['primaryColor']) => {
+ setColor(color);
+ if (type === 'primary') setPrimaryColor(color);
+ else setSecondaryColor(color);
+ updateConfig(configName, (prev) => {
+ const { colors } = prev.settings.customization;
+ colors[type] = color;
+ return {
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ colors,
+ },
+ },
+ };
+ });
+ };
+
+ const swatches = colors.map(({ color, swatch }) => (
+
+ handleSelection(color)}
+ color={swatch}
+ size={22}
+ style={{ cursor: 'pointer' }}
+ />
+
+ ));
+
+ return (
+
+
+
+
+
+
+
+ {swatches}
+
+
+
+
+ {t('suffix', {
+ color: type[0].toUpperCase() + type.slice(1),
+ })}
+
+
+ );
+}
diff --git a/src/components/Settings/Customization/Theme/CustomCssChanger.tsx b/src/components/Settings/Customization/Theme/CustomCssChanger.tsx
new file mode 100644
index 000000000..5b8db8de1
--- /dev/null
+++ b/src/components/Settings/Customization/Theme/CustomCssChanger.tsx
@@ -0,0 +1,44 @@
+import { Textarea } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { ChangeEventHandler, useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface CustomCssChangerProps {
+ defaultValue: string | undefined;
+}
+
+export const CustomCssChanger = ({ defaultValue }: CustomCssChangerProps) => {
+ const { t } = useTranslation('settings/customization/page-appearance');
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+ const { name: configName } = useConfigContext();
+ const [customCss, setCustomCss] = useState(defaultValue);
+
+ if (!configName) return null;
+
+ const handleChange: ChangeEventHandler = (ev) => {
+ const { value } = ev.currentTarget;
+ const customCss = value.trim().length === 0 ? undefined : value;
+ setCustomCss(customCss);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ customCss,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Settings/Customization/Theme/OpacitySelector.tsx b/src/components/Settings/Customization/Theme/OpacitySelector.tsx
new file mode 100644
index 000000000..f0eaf3bd1
--- /dev/null
+++ b/src/components/Settings/Customization/Theme/OpacitySelector.tsx
@@ -0,0 +1,60 @@
+import { Slider, Stack, Text } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+
+interface OpacitySelectorProps {
+ defaultValue: number | undefined;
+}
+
+export function OpacitySelector({ defaultValue }: OpacitySelectorProps) {
+ const [opacity, setOpacity] = useState(defaultValue || 100);
+ const { t } = useTranslation('settings/customization/opacity-selector');
+
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
+
+ if (!configName) return null;
+
+ const handleChange = (opacity: number) => {
+ setOpacity(opacity);
+ updateConfig(configName, (prev) => ({
+ ...prev,
+ settings: {
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ appOpacity: opacity,
+ },
+ },
+ }));
+ };
+
+ return (
+
+ {t('label')}
+
+
+ );
+}
+
+const MARKS = [
+ { value: 10, label: '10' },
+ { value: 20, label: '20' },
+ { value: 30, label: '30' },
+ { value: 40, label: '40' },
+ { value: 50, label: '50' },
+ { value: 60, label: '60' },
+ { value: 70, label: '70' },
+ { value: 80, label: '80' },
+ { value: 90, label: '90' },
+ { value: 100, label: '100' },
+];
diff --git a/src/components/Settings/ShadeSelector.tsx b/src/components/Settings/Customization/Theme/ShadeSelector.tsx
similarity index 53%
rename from src/components/Settings/ShadeSelector.tsx
rename to src/components/Settings/Customization/Theme/ShadeSelector.tsx
index a7dc85dfa..5611581ae 100644
--- a/src/components/Settings/ShadeSelector.tsx
+++ b/src/components/Settings/Customization/Theme/ShadeSelector.tsx
@@ -1,44 +1,57 @@
-import React, { useState } from 'react';
import {
ColorSwatch,
+ Grid,
Group,
+ MantineTheme,
Popover,
+ Stack,
Text,
useMantineTheme,
- MantineTheme,
- Stack,
- Grid,
} from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { useColorTheme } from '../../tools/color';
+import { useState } from 'react';
+import { useConfigContext } from '../../../../config/provider';
+import { useConfigStore } from '../../../../config/store';
+import { useColorTheme } from '../../../../tools/color';
-export function ShadeSelector() {
- const { config, setConfig } = useConfig();
- const [opened, setOpened] = useState(false);
+interface ShadeSelectorProps {
+ defaultValue: MantineTheme['primaryShade'] | undefined;
+}
+
+export function ShadeSelector({ defaultValue }: ShadeSelectorProps) {
const { t } = useTranslation('settings/customization/shade-selector');
+ const [shade, setShade] = useState(defaultValue);
+ const [popoverOpened, popover] = useDisclosure(false);
+ const { primaryColor, setPrimaryShade } = useColorTheme();
- const { primaryColor, secondaryColor, primaryShade, setPrimaryShade } = useColorTheme();
+ const { name: configName } = useConfigContext();
+ const updateConfig = useConfigStore((x) => x.updateConfig);
const theme = useMantineTheme();
const primaryShades = theme.colors[primaryColor].map((s, i) => ({
swatch: theme.colors[primaryColor][i],
shade: i as MantineTheme['primaryShade'],
}));
- const secondaryShades = theme.colors[secondaryColor].map((s, i) => ({
- swatch: theme.colors[secondaryColor][i],
- shade: i as MantineTheme['primaryShade'],
- }));
- const setConfigShade = (shade: MantineTheme['primaryShade']) => {
+ if (shade === undefined || !configName) return null;
+
+ const handleSelection = (shade: MantineTheme['primaryShade']) => {
setPrimaryShade(shade);
- setConfig({
- ...config,
+ setShade(shade);
+ updateConfig(configName, (prev) => ({
+ ...prev,
settings: {
- ...config.settings,
- primaryShade: shade,
+ ...prev.settings,
+ customization: {
+ ...prev.settings.customization,
+ colors: {
+ ...prev.settings.customization.colors,
+ shade,
+ },
+ },
},
- });
+ }));
};
const primarySwatches = primaryShades.map(({ swatch, shade }) => (
@@ -46,20 +59,7 @@ export function ShadeSelector() {
setConfigShade(shade)}
- color={swatch}
- size={22}
- style={{ cursor: 'pointer' }}
- />
-
- ));
-
- const secondarySwatches = secondaryShades.map(({ swatch, shade }) => (
-
- setConfigShade(shade)}
+ onClick={() => handleSelection(shade)}
color={swatch}
size={22}
style={{ cursor: 'pointer' }}
@@ -72,8 +72,8 @@ export function ShadeSelector() {
setOpened(false)}
+ opened={popoverOpened}
+ onClose={popover.close}
position="left"
withArrow
>
@@ -81,8 +81,8 @@ export function ShadeSelector() {
setOpened((o) => !o)}
+ color={theme.colors[primaryColor][Number(shade)]}
+ onClick={popover.toggle}
size={22}
style={{ display: 'block', cursor: 'pointer' }}
/>
@@ -91,7 +91,6 @@ export function ShadeSelector() {
{primarySwatches}
- {secondarySwatches}
diff --git a/src/components/Settings/GrowthSelector.tsx b/src/components/Settings/GrowthSelector.tsx
deleted file mode 100644
index 4eb5af861..000000000
--- a/src/components/Settings/GrowthSelector.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Switch } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useState } from 'react';
-import { useConfig } from '../../tools/state';
-
-export function GrowthSelector() {
- const { config, setConfig } = useConfig();
- const defaultPosition = config?.settings?.grow || false;
- const [growState, setGrowState] = useState(defaultPosition);
- const { t } = useTranslation('settings/common.json');
- const toggleGrowState = () => {
- setGrowState(!growState);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- grow: !growState,
- },
- });
- };
-
- return (
- toggleGrowState()}
- size="md"
- />
- );
-}
diff --git a/src/components/Settings/ModuleEnabler.tsx b/src/components/Settings/ModuleEnabler.tsx
deleted file mode 100644
index dc3820d35..000000000
--- a/src/components/Settings/ModuleEnabler.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Checkbox, HoverCard, SimpleGrid, Stack, Text, Title } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import * as Modules from '../../modules';
-import { IModule } from '../../modules/ModuleTypes';
-import { useConfig } from '../../tools/state';
-
-export default function ModuleEnabler(props: any) {
- const { t } = useTranslation('settings/general/module-enabler');
- const modules = Object.values(Modules).map((module) => module);
- return (
-
- {t('title')}
-
- {modules.map((module) => (
-
- ))}
-
-
- );
-}
-
-const ModuleToggle = ({ module }: { module: IModule }) => {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation(`modules/${module.id}`);
-
- return (
-
-
- {
- setConfig({
- ...config,
- modules: {
- ...config.modules,
- [module.id]: {
- ...config.modules?.[module.id],
- enabled: e.currentTarget.checked,
- },
- },
- });
- }}
- />
-
-
- {t('descriptor.name')}
- {t('descriptor.description')}
-
-
- );
-};
diff --git a/src/components/Settings/OpacitySelector.tsx b/src/components/Settings/OpacitySelector.tsx
deleted file mode 100644
index cfcf89109..000000000
--- a/src/components/Settings/OpacitySelector.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { Text, Slider, Stack } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-
-export function OpacitySelector() {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation('settings/customization/opacity-selector');
-
- const MARKS = [
- { value: 10, label: '10' },
- { value: 20, label: '20' },
- { value: 30, label: '30' },
- { value: 40, label: '40' },
- { value: 50, label: '50' },
- { value: 60, label: '60' },
- { value: 70, label: '70' },
- { value: 80, label: '80' },
- { value: 90, label: '90' },
- { value: 100, label: '100' },
- ];
-
- const setConfigOpacity = (opacity: number) => {
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- appOpacity: opacity,
- },
- });
- };
-
- return (
-
- {t('label')}
- setConfigOpacity(value)}
- />
-
- );
-}
diff --git a/src/components/Settings/SettingsDrawer.tsx b/src/components/Settings/SettingsDrawer.tsx
new file mode 100644
index 000000000..ab8a792b7
--- /dev/null
+++ b/src/components/Settings/SettingsDrawer.tsx
@@ -0,0 +1,61 @@
+import { Drawer, Tabs, Title } from '@mantine/core';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../config/provider';
+import { useConfigStore } from '../../config/store';
+
+import CommonSettings from './Common/CommonSettings';
+import CustomizationSettings from './Customization/CustomizationSettings';
+
+function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
+ const { t } = useTranslation('settings/common');
+
+ return (
+
+
+ {t('tabs.common')}
+ {t('tabs.customizations')}
+
+
+
+
+
+
+
+
+ );
+}
+
+interface SettingsDrawerProps {
+ opened: boolean;
+ closeDrawer: () => void;
+}
+
+export function SettingsDrawer({
+ opened,
+ closeDrawer,
+ newVersionAvailable,
+}: SettingsDrawerProps & { newVersionAvailable: string }) {
+ const { t } = useTranslation('settings/common');
+ const { config, name: configName } = useConfigContext();
+ const { updateConfig } = useConfigStore();
+
+ return (
+ {t('title')}}
+ opened={opened}
+ onClose={() => {
+ closeDrawer();
+ if (!configName || !config) {
+ return;
+ }
+
+ updateConfig(configName, (_) => config, false, true);
+ }}
+ >
+
+
+ );
+}
diff --git a/src/components/Settings/SettingsMenu.tsx b/src/components/Settings/SettingsMenu.tsx
deleted file mode 100644
index 903989a10..000000000
--- a/src/components/Settings/SettingsMenu.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/core';
-import { useHotkeys } from '@mantine/hooks';
-import { useState } from 'react';
-import { IconSettings } from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-
-import AdvancedSettings from './AdvancedSettings';
-import CommonSettings from './CommonSettings';
-import Credits from './Credits';
-
-function SettingsMenu(props: any) {
- const { t } = useTranslation('settings/common');
-
- return (
-
-
- {t('tabs.common')}
- {t('tabs.customizations')}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export function SettingsMenuButton(props: any) {
- useHotkeys([['ctrl+L', () => setOpened(!opened)]]);
- const { t } = useTranslation('settings/common');
-
- const [opened, setOpened] = useState(false);
-
- return (
- <>
- {t('title')}}
- opened={props.opened || opened}
- onClose={() => setOpened(false)}
- >
-
-
-
-
- setOpened(true)}
- >
-
-
-
- >
- );
-}
diff --git a/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx b/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx
deleted file mode 100644
index beee55b52..000000000
--- a/src/components/WidgetsPositionSwitch/WidgetsPositionSwitch.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import React, { useState } from 'react';
-import { createStyles, Switch, Group } from '@mantine/core';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-
-const useStyles = createStyles((theme) => ({
- root: {
- position: 'relative',
- '& *': {
- cursor: 'pointer',
- },
- },
-
- icon: {
- pointerEvents: 'none',
- position: 'absolute',
- zIndex: 1,
- top: 3,
- },
-
- iconLight: {
- left: 4,
- color: theme.white,
- },
-
- iconDark: {
- right: 4,
- color: theme.colors.gray[6],
- },
-}));
-
-export function WidgetsPositionSwitch() {
- const { config, setConfig } = useConfig();
- const { classes, cx } = useStyles();
- const defaultPosition = config?.settings?.widgetPosition || 'right';
- const [widgetPosition, setWidgetPosition] = useState(defaultPosition);
- const { t } = useTranslation('settings/general/widget-positions');
- const toggleWidgetPosition = () => {
- const position = widgetPosition === 'right' ? 'left' : 'right';
- setWidgetPosition(position);
- setConfig({
- ...config,
- settings: {
- ...config.settings,
- widgetPosition: position,
- },
- });
- };
-
- return (
- toggleWidgetPosition()}
- size="md"
- />
- );
-}
diff --git a/src/components/layout/Aside.tsx b/src/components/layout/Aside.tsx
deleted file mode 100644
index 6c4810572..000000000
--- a/src/components/layout/Aside.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Aside as MantineAside, createStyles } from '@mantine/core';
-import Widgets from './Widgets';
-
-const useStyles = createStyles((theme) => ({
- hide: {
- [theme.fn.smallerThan('xs')]: {
- display: 'none',
- },
- },
- burger: {
- [theme.fn.largerThan('sm')]: {
- display: 'none',
- },
- },
-}));
-
-export default function Aside(props: any) {
- const { classes, cx } = useStyles();
- return (
-
-
-
- );
-}
diff --git a/src/components/layout/Background.tsx b/src/components/layout/Background.tsx
index 741bf9389..74fff226b 100644
--- a/src/components/layout/Background.tsx
+++ b/src/components/layout/Background.tsx
@@ -1,15 +1,15 @@
import { Global } from '@mantine/core';
-import { useConfig } from '../../tools/state';
+import { useConfigContext } from '../../config/provider';
export function Background() {
- const { config } = useConfig();
+ const { config } = useConfigContext();
return (
({
- footer: {
- borderTop: `1px solid ${
- theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
- }`,
- },
-
- inner: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: `${theme.spacing.md}px ${theme.spacing.md}px`,
-
- [theme.fn.smallerThan('sm')]: {
- flexDirection: 'column',
- },
- },
-
- links: {
- [theme.fn.smallerThan('sm')]: {
- marginTop: theme.spacing.lg,
- marginBottom: theme.spacing.sm,
- },
- },
-}));
-
-interface FooterCenteredProps {
- links: { link: string; label: string }[];
-}
-
-export function Footer({ links }: FooterCenteredProps) {
- useEffect(() => {
- // Fetch Data here when component first mounted
- fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
- res.json().then((data) => {
- if (data.tag_name > CURRENT_VERSION) {
- showNotification({
- color: 'yellow',
- autoClose: false,
- title: 'New version available',
- icon: ,
- message: `Version ${data.tag_name} is available, update now!`,
- });
- } else if (data.tag_name < CURRENT_VERSION) {
- showNotification({
- color: 'orange',
- autoClose: 5000,
- title: 'You are using a development version',
- icon: ,
- message: 'This version of Homarr is still in development! Bugs are expected 🐛',
- });
- }
- });
- });
- }, []);
-
- return (
-
- );
-}
diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx
index 5e8f074e0..7f8a32615 100644
--- a/src/components/layout/Layout.tsx
+++ b/src/components/layout/Layout.tsx
@@ -1,48 +1,29 @@
import { AppShell, createStyles } from '@mantine/core';
-import { Header } from './header/Header';
-import { Footer } from './Footer';
-import Aside from './Aside';
-import Navbar from './Navbar';
-import { HeaderConfig } from './header/HeaderConfig';
+import { useConfigContext } from '../../config/provider';
import { Background } from './Background';
-import { useConfig } from '../../tools/state';
+import { Header } from './header/Header';
+import { Head } from './header/Meta/Head';
-const useStyles = createStyles((theme) => ({
- main: {},
- appShell: {
- // eslint-disable-next-line no-useless-computed-key
- ['@media screen and (display-mode: standalone)']: {
- '&': {
- paddingTop: '88px !important',
- },
- },
- },
-}));
+const useStyles = createStyles(() => ({}));
-export default function Layout({ children, style }: any) {
- const { classes, cx } = useStyles();
- const { config } = useConfig();
- const widgetPosition = config?.settings?.widgetPosition === 'left';
+export default function Layout({ children }: any) {
+ const { cx } = useStyles();
+ const { config } = useConfigContext();
return (
}
- navbar={widgetPosition ? : undefined}
- aside={widgetPosition ? undefined : }
- footer={}
+ styles={{
+ main: {
+ minHeight: 'calc(100vh - var(--mantine-header-height))',
+ },
+ }}
>
-
+
-
- {children}
-
-
+ {children}
+
);
}
diff --git a/src/components/layout/Logo.tsx b/src/components/layout/Logo.tsx
index 4800dc5e7..a943f5866 100644
--- a/src/components/layout/Logo.tsx
+++ b/src/components/layout/Logo.tsx
@@ -1,34 +1,31 @@
import { Group, Image, Text } from '@mantine/core';
-import { NextLink } from '@mantine/next';
-import * as React from 'react';
-import { useColorTheme } from '../../tools/color';
-import { useConfig } from '../../tools/state';
+import { useConfigContext } from '../../config/provider';
+import { usePrimaryGradient } from './useGradient';
-export function Logo({ style, withoutText }: any) {
- const { config } = useConfig();
- const { primaryColor, secondaryColor } = useColorTheme();
+interface LogoProps {
+ size?: 'md' | 'xs';
+ withoutText?: boolean;
+}
+
+export function Logo({ size = 'md', withoutText = false }: LogoProps) {
+ const { config } = useConfigContext();
+ const primaryGradient = usePrimaryGradient();
return (
-
+
{withoutText ? null : (
- {config.settings.title || 'Homarr'}
+ {config?.settings.customization.pageTitle || 'Homarr'}
)}
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
deleted file mode 100644
index 0bf44237b..000000000
--- a/src/components/layout/Navbar.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { createStyles, Navbar as MantineNavbar } from '@mantine/core';
-import Widgets from './Widgets';
-
-const useStyles = createStyles((theme) => ({
- hide: {
- [theme.fn.smallerThan('xs')]: {
- display: 'none',
- },
- },
- burger: {
- [theme.fn.largerThan('sm')]: {
- display: 'none',
- },
- },
-}));
-
-export default function Navbar() {
- const { classes, cx } = useStyles();
-
- return (
-
-
-
- );
-}
diff --git a/src/components/layout/Widgets.tsx b/src/components/layout/Widgets.tsx
deleted file mode 100644
index 70a6759ef..000000000
--- a/src/components/layout/Widgets.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Stack } from '@mantine/core';
-import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../../modules';
-import { DashdotModule } from '../../modules/dashdot';
-import { ModuleWrapper } from '../../modules/moduleWrapper';
-
-export default function Widgets(props: any) {
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx b/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx
new file mode 100644
index 000000000..c979b5ca8
--- /dev/null
+++ b/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx
@@ -0,0 +1,58 @@
+import { ActionIcon, Button, Tooltip } from '@mantine/core';
+import { openContextModal } from '@mantine/modals';
+import { IconApps } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+
+interface AddElementActionProps {
+ type: 'action-icon' | 'button';
+}
+
+export const AddElementAction = ({ type }: AddElementActionProps) => {
+ const { t } = useTranslation('layout/element-selector/selector');
+
+ switch (type) {
+ case 'button':
+ return (
+
+
+ openContextModal({
+ modal: 'selectElement',
+ title: t('modal.title'),
+ size: 'xl',
+ innerProps: {},
+ })
+ }
+ >
+
+
+
+ );
+ case 'action-icon':
+ return (
+
+
+ openContextModal({
+ modal: 'selectElement',
+ title: t('modal.title'),
+ size: 'xl',
+ innerProps: {},
+ })
+ }
+ >
+
+
+
+ );
+ default:
+ return null;
+ }
+};
diff --git a/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx b/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx
new file mode 100644
index 000000000..cd256f838
--- /dev/null
+++ b/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx
@@ -0,0 +1,121 @@
+import axios from 'axios';
+import Consola from 'consola';
+import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
+import { IconEditCircle, IconEditCircleOff } from '@tabler/icons';
+import { getCookie } from 'cookies-next';
+import { Trans, useTranslation } from 'next-i18next';
+import { hideNotification, showNotification } from '@mantine/notifications';
+import { useConfigContext } from '../../../../../config/provider';
+import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan';
+
+import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore';
+import { AddElementAction } from '../AddElementAction/AddElementAction';
+import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store';
+
+export const ToggleEditModeAction = () => {
+ const { enabled, toggleEditMode } = useEditModeStore();
+ const namedWrapperColumnCount = useNamedWrapperColumnCount();
+ const { t } = useTranslation('layout/header/actions/toggle-edit-mode');
+ const translatedSize = t(`screenSizes.${namedWrapperColumnCount}`);
+
+ const smallerThanSm = useScreenSmallerThan('sm');
+ const { config } = useConfigContext();
+
+ const toggleButtonClicked = () => {
+ toggleEditMode();
+ if (enabled || config === undefined || config?.schemaVersion === undefined) {
+ const configName = getCookie('config-name')?.toString() ?? 'default';
+ axios.put(`/api/configs/${configName}`, { ...config });
+ Consola.log('Saved config to server', configName);
+ hideNotification('toggle-edit-mode');
+ } else if (!enabled) {
+ showNotification({
+ styles: (theme) => ({
+ root: {
+ backgroundColor: theme.colors.orange[7],
+ borderColor: theme.colors.orange[7],
+
+ '&::before': { backgroundColor: theme.white },
+ },
+ title: { color: theme.white },
+ description: { color: theme.white },
+ closeButton: {
+ color: theme.white,
+ '&:hover': { backgroundColor: theme.colors.orange[7] },
+ },
+ }),
+ radius: 'md',
+ id: 'toggle-edit-mode',
+ autoClose: 10000,
+ title: (
+
+
+ ),
+ }}
+ />
+
+ ),
+ message: ,
+ });
+ } else {
+ hideNotification('toggle-edit-mode');
+ }
+ };
+
+ const ToggleButtonDesktop = () => (
+
+ toggleButtonClicked()}
+ radius="md"
+ variant="default"
+ style={{ height: 43 }}
+ >
+ {enabled ? : }
+
+
+ );
+
+ const ToggleActionIconMobile = () => (
+ toggleButtonClicked()}
+ variant="default"
+ radius="md"
+ size="xl"
+ color="blue"
+ >
+ {enabled ? : }
+
+ );
+
+ return (
+ <>
+ {smallerThanSm ? (
+ enabled ? (
+
+
+
+
+ ) : (
+
+ )
+ ) : enabled ? (
+
+
+ {enabled && }
+
+ ) : (
+
+ )}
+ >
+ );
+};
diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx
index 803ea7690..c70fa7128 100644
--- a/src/components/layout/header/Header.tsx
+++ b/src/components/layout/header/Header.tsx
@@ -1,38 +1,57 @@
-import { Group, Header as Head, useMantineColorScheme, useMantineTheme } from '@mantine/core';
-import { useViewportSize } from '@mantine/hooks';
-import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
-
-import DockerMenuButton from '../../../modules/docker/DockerModule';
-import { SettingsMenuButton } from '../../Settings/SettingsMenu';
+import { Box, createStyles, Group, Header as MantineHeader, Indicator } from '@mantine/core';
+import { useEffect, useState } from 'react';
+import { CURRENT_VERSION, REPO_URL } from '../../../../data/constants';
+import { useConfigContext } from '../../../config/provider';
import { Logo } from '../Logo';
-import { useConfig } from '../../../tools/state';
-import { SearchModuleComponent } from '../../../modules/search/SearchModule';
+import { useCardStyles } from '../useCardStyles';
+import DockerMenuButton from '../../../modules/Docker/DockerModule';
+import { ToggleEditModeAction } from './Actions/ToggleEditMode/ToggleEditMode';
+import { Search } from './Search';
+import { SettingsMenu } from './SettingsMenu';
+
+export const HeaderHeight = 64;
export function Header(props: any) {
- const { width } = useViewportSize();
- const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
- const { config } = useConfig();
- const { colorScheme } = useMantineColorScheme();
+ const { classes } = useStyles();
+ const { classes: cardClasses } = useCardStyles(false);
+
+ const { config } = useConfigContext();
+
+ const [newVersionAvailable, setNewVersionAvailable] = useState('');
+ useEffect(() => {
+ // Fetch Data here when component first mounted
+ fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
+ res.json().then((data) => {
+ if (data.tag_name > CURRENT_VERSION) {
+ setNewVersionAvailable(data.tag_name);
+ }
+ });
+ });
+ }, [CURRENT_VERSION]);
return (
-
+
- {width > MIN_WIDTH_MOBILE && }
-
-
+
+
+
+
+
+
-
-
+
+
+
-
+
);
}
+
+const useStyles = createStyles((theme) => ({
+ hide: {
+ [theme.fn.smallerThan('xs')]: {
+ display: 'none',
+ },
+ },
+}));
diff --git a/src/components/layout/header/HeaderConfig.tsx b/src/components/layout/header/HeaderConfig.tsx
deleted file mode 100644
index 8f873a9f0..000000000
--- a/src/components/layout/header/HeaderConfig.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/* eslint-disable react/no-invalid-html-attribute */
-import React from 'react';
-import Head from 'next/head';
-import { useConfig } from '../../../tools/state';
-import { SafariStatusBarStyle } from './safariStatusBarStyle';
-
-export function HeaderConfig(props: any) {
- const { config } = useConfig();
-
- return (
-
- {config.settings.title || 'Homarr 🦞'}
-
-
-
-
- {/* configure apple splash screen & touch icon */}
-
-
-
-
-
-
- );
-}
diff --git a/src/components/layout/header/Meta/Head.tsx b/src/components/layout/header/Meta/Head.tsx
new file mode 100644
index 000000000..a0cb45691
--- /dev/null
+++ b/src/components/layout/header/Meta/Head.tsx
@@ -0,0 +1,34 @@
+/* eslint-disable react/no-invalid-html-attribute */
+import React from 'react';
+import NextHead from 'next/head';
+import { SafariStatusBarStyle } from './SafariStatusBarStyle';
+import { useConfigContext } from '../../../../config/provider';
+
+export function Head() {
+ const { config } = useConfigContext();
+
+ return (
+
+ {config?.settings.customization.metaTitle || 'Homarr 🦞'}
+
+
+
+
+ {/* configure apple splash screen & touch icon */}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/layout/header/Meta/SafariStatusBarStyle.tsx b/src/components/layout/header/Meta/SafariStatusBarStyle.tsx
new file mode 100644
index 000000000..e712c871a
--- /dev/null
+++ b/src/components/layout/header/Meta/SafariStatusBarStyle.tsx
@@ -0,0 +1,12 @@
+import { useMantineTheme } from '@mantine/core';
+
+export const SafariStatusBarStyle = () => {
+ const { colorScheme } = useMantineTheme();
+ const isDark = colorScheme === 'dark';
+ return (
+
+ );
+};
diff --git a/src/modules/search/SearchModule.tsx b/src/components/layout/header/Search.tsx
similarity index 79%
rename from src/modules/search/SearchModule.tsx
rename to src/components/layout/header/Search.tsx
index 5c12b9672..5c28e7fcb 100644
--- a/src/modules/search/SearchModule.tsx
+++ b/src/components/layout/header/Search.tsx
@@ -8,26 +8,25 @@ import {
Menu,
Popover,
ScrollArea,
- TextInput,
Tooltip,
} from '@mantine/core';
-import { IconSearch, IconBrandYoutube, IconDownload, IconMovie } from '@tabler/icons';
-import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
-import { useTranslation } from 'next-i18next';
+import { IconBrandYoutube, IconDownload, IconMovie, IconSearch } from '@tabler/icons';
import axios from 'axios';
-import { IModule } from '../ModuleTypes';
-import { useConfig } from '../../tools/state';
-import { OverseerrModule } from '../overseerr';
-import Tip from '../../components/layout/Tip';
-import { OverseerrMediaDisplay } from '../common';
-import SmallServiceItem from '../../components/AppShelf/SmallServiceItem';
+import { useTranslation } from 'next-i18next';
+import React, { forwardRef, useEffect, useRef, useState } from 'react';
+import SmallAppItem from './SmallAppItem';
+import Tip from '../Tip';
+import { searchUrls } from '../../Settings/Common/SearchEngine/SearchEngineSelector';
+import { useConfigContext } from '../../../config/provider';
+import { OverseerrMediaDisplay } from '../../../modules/common';
+import { IModule } from '../../../modules/ModuleTypes';
export const SearchModule: IModule = {
title: 'Search',
icon: IconSearch,
- component: SearchModuleComponent,
+ component: Search,
id: 'search',
};
@@ -50,15 +49,24 @@ const useStyles = createStyles((theme) => ({
},
}));
-export function SearchModuleComponent() {
- const { config } = useConfig();
+export function Search() {
const { t } = useTranslation('modules/search');
+ const { config } = useConfigContext();
const [searchQuery, setSearchQuery] = useState('');
const [debounced, cancel] = useDebouncedValue(searchQuery, 250);
- const isOverseerrEnabled = config.modules?.[OverseerrModule.id]?.enabled ?? false;
- const OverseerrService = config.services.find(
- (service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
+
+ const isOverseerrEnabled = config?.apps.some(
+ (x) => x.integration.type === 'overseerr' || x.integration.type === 'jellyseerr'
);
+ const overseerrApp = config?.apps.find(
+ (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
+ );
+ const searchEngineSettings = config?.settings.common.searchEngine;
+ const searchEngineUrl = !searchEngineSettings
+ ? searchUrls.google
+ : searchEngineSettings.type === 'custom'
+ ? searchEngineSettings.properties.template
+ : searchUrls[searchEngineSettings.type];
const searchEnginesList: ItemProps[] = [
{
@@ -67,7 +75,7 @@ export function SearchModuleComponent() {
label: t('searchEngines.search.name'),
value: 'search',
description: t('searchEngines.search.description'),
- url: config.settings.searchUrl,
+ url: searchEngineUrl,
shortcut: 's',
},
{
@@ -90,42 +98,45 @@ export function SearchModuleComponent() {
},
{
icon: ,
- disabled: !(isOverseerrEnabled === true && OverseerrService !== undefined),
+ disabled: !(isOverseerrEnabled === true && overseerrApp !== undefined),
label: t('searchEngines.overseerr.name'),
value: 'overseerr',
description: t('searchEngines.overseerr.description'),
- url: `${OverseerrService?.url}search?query=`,
+ url: `${overseerrApp?.url}search?query=`,
shortcut: 'm',
},
];
const [selectedSearchEngine, setSearchEngine] = useState(searchEnginesList[0]);
- const matchingServices = config.services.filter((service) => {
- if (searchQuery === '' || searchQuery === undefined) {
- return false;
- }
- return service.name.toLowerCase().includes(searchQuery.toLowerCase());
- });
- const autocompleteData = matchingServices.map((service) => ({
- label: service.name,
- value: service.name,
- icon: service.icon,
- url: service.openedUrl ?? service.url,
+ const matchingApps =
+ config?.apps.filter((app) => {
+ if (searchQuery === '' || searchQuery === undefined) {
+ return false;
+ }
+ return app.name.toLowerCase().includes(searchQuery.toLowerCase());
+ }) ?? [];
+ const autocompleteData = matchingApps.map((app) => ({
+ label: app.name,
+ value: app.name,
+ icon: app.appearance.iconUrl,
+ url: app.behaviour.externalUrl ?? app.url,
}));
const AutoCompleteItem = forwardRef(
({ label, value, icon, url, ...others }: any, ref) => (
-
+
)
);
useEffect(() => {
// Refresh the default search engine every time the config for it changes #521
setSearchEngine(searchEnginesList[0]);
- }, [config.settings.searchUrl]);
+ }, [searchEngineUrl]);
const textInput = useRef(null);
- useHotkeys([['mod+K', () => textInput.current && textInput.current.focus()]]);
+ useHotkeys([['mod+K', () => textInput.current?.focus()]]);
const { classes } = useStyles();
- const openInNewTab = config.settings.searchNewTab ? '_blank' : '_self';
+ const openInNewTab = config?.settings.common.searchEngine.properties.openInNewTab
+ ? '_blank'
+ : '_self';
const [OverseerrResults, setOverseerrResults] = useState([]);
const [opened, setOpened] = useState(false);
@@ -137,7 +148,7 @@ export function SearchModuleComponent() {
}
}, [debounced]);
- const isModuleEnabled = config.modules?.[SearchModule.id]?.enabled ?? false;
+ const isModuleEnabled = config?.settings.customization.layout.enabledSearchbar;
if (!isModuleEnabled) {
return null;
}
diff --git a/src/components/layout/header/SettingsMenu.tsx b/src/components/layout/header/SettingsMenu.tsx
new file mode 100644
index 000000000..f6a381fc0
--- /dev/null
+++ b/src/components/layout/header/SettingsMenu.tsx
@@ -0,0 +1,55 @@
+import { Badge, Button, Menu } from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { AboutModal } from '../../About/AboutModal';
+import { SettingsDrawer } from '../../Settings/SettingsDrawer';
+import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch';
+
+export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
+ const [drawerOpened, drawer] = useDisclosure(false);
+ const { t } = useTranslation('common');
+ const [aboutModalOpened, aboutModal] = useDisclosure(false);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ } onClick={drawer.open}>
+ {t('sections.settings')}
+
+ }
+ rightSection={
+ newVersionAvailable && (
+
+ New
+
+ )
+ }
+ onClick={() => aboutModal.open()}
+ >
+ {t('about')}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/layout/header/SettingsMenu/ColorSchemeSwitch.tsx b/src/components/layout/header/SettingsMenu/ColorSchemeSwitch.tsx
new file mode 100644
index 000000000..434cb5a0a
--- /dev/null
+++ b/src/components/layout/header/SettingsMenu/ColorSchemeSwitch.tsx
@@ -0,0 +1,22 @@
+import { Menu, useMantineColorScheme } from '@mantine/core';
+import { IconMoonStars, IconSun } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+
+export const ColorSchemeSwitch = () => {
+ const { colorScheme, toggleColorScheme } = useMantineColorScheme();
+ const { t } = useTranslation('settings/general/theme-selector');
+
+ const Icon = colorScheme === 'dark' ? IconSun : IconMoonStars;
+
+ return (
+ }
+ onClick={() => toggleColorScheme()}
+ >
+ {t('label', {
+ theme: colorScheme === 'dark' ? 'light' : 'dark',
+ })}
+
+ );
+};
diff --git a/src/components/layout/header/SmallAppItem.tsx b/src/components/layout/header/SmallAppItem.tsx
new file mode 100644
index 000000000..02aeed486
--- /dev/null
+++ b/src/components/layout/header/SmallAppItem.tsx
@@ -0,0 +1,17 @@
+import { Avatar, Group, Text } from '@mantine/core';
+
+interface smallAppItem {
+ label: string;
+ icon?: string;
+ url?: string;
+}
+
+export default function SmallAppItem(props: any) {
+ const { app }: { app: smallAppItem } = props;
+ return (
+
+ {app.icon && }
+ {app.label}
+
+ );
+}
diff --git a/src/components/layout/header/safariStatusBarStyle.tsx b/src/components/layout/header/safariStatusBarStyle.tsx
deleted file mode 100644
index b7100ced0..000000000
--- a/src/components/layout/header/safariStatusBarStyle.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useMantineTheme } from '@mantine/core';
-
-export const SafariStatusBarStyle = () => {
- const colorScheme = useMantineTheme();
-
- const isDark = colorScheme.colorScheme === 'dark';
-
- if (isDark) {
- return ;
- }
-
- return ;
-};
diff --git a/src/components/layout/useCardStyles.ts b/src/components/layout/useCardStyles.ts
new file mode 100644
index 000000000..2c842e422
--- /dev/null
+++ b/src/components/layout/useCardStyles.ts
@@ -0,0 +1,35 @@
+import { createStyles } from '@mantine/core';
+import { useConfigContext } from '../../config/provider';
+
+export const useCardStyles = (isCategory: boolean) => {
+ const { config } = useConfigContext();
+ const appOpacity = config?.settings.customization.appOpacity;
+ return createStyles(({ colorScheme }, _params) => {
+ const opacity = (appOpacity || 100) / 100;
+
+ if (colorScheme === 'dark') {
+ if (isCategory) {
+ return {
+ card: {
+ backgroundColor: `rgba(32, 33, 35, ${opacity}) !important`,
+ borderColor: `rgba(37, 38, 43, ${opacity})`,
+ },
+ };
+ }
+
+ return {
+ card: {
+ backgroundColor: `rgba(37, 38, 43, ${opacity}) !important`,
+ borderColor: `rgba(37, 38, 43, ${opacity})`,
+ },
+ };
+ }
+
+ return {
+ card: {
+ backgroundColor: `rgba(255, 255, 255, ${opacity}) !important`,
+ borderColor: `rgba(233, 236, 239, ${opacity})`,
+ },
+ };
+ })();
+};
diff --git a/src/components/layout/useGradient.tsx b/src/components/layout/useGradient.tsx
new file mode 100644
index 000000000..13197673d
--- /dev/null
+++ b/src/components/layout/useGradient.tsx
@@ -0,0 +1,12 @@
+import { MantineGradient } from '@mantine/core';
+import { useColorTheme } from '../../tools/color';
+
+export const usePrimaryGradient = (): MantineGradient => {
+ const { primaryColor, secondaryColor } = useColorTheme();
+
+ return {
+ from: primaryColor,
+ to: secondaryColor,
+ deg: 145,
+ };
+};
diff --git a/src/config/init.ts b/src/config/init.ts
new file mode 100644
index 000000000..7255c7e37
--- /dev/null
+++ b/src/config/init.ts
@@ -0,0 +1,15 @@
+import { useEffect } from 'react';
+import { ConfigType } from '../types/config';
+import { useConfigContext } from './provider';
+import { useConfigStore } from './store';
+
+export const useInitConfig = (initialConfig: ConfigType) => {
+ const { setConfigName, increaseVersion } = useConfigContext();
+ const configName = initialConfig.configProperties?.name ?? 'default';
+ const initConfig = useConfigStore((x) => x.initConfig);
+
+ useEffect(() => {
+ setConfigName(configName);
+ initConfig(configName, initialConfig, increaseVersion);
+ }, [configName]);
+};
diff --git a/src/config/provider.tsx b/src/config/provider.tsx
new file mode 100644
index 000000000..3559287d9
--- /dev/null
+++ b/src/config/provider.tsx
@@ -0,0 +1,52 @@
+import { createContext, ReactNode, useContext, useEffect, useState } from 'react';
+import shallow from 'zustand/shallow';
+import { useColorTheme } from '../tools/color';
+import { ConfigType } from '../types/config';
+import { useConfigStore } from './store';
+
+export type ConfigContextType = {
+ config: ConfigType | undefined;
+ name: string | undefined;
+ configVersion: number | undefined;
+ increaseVersion: () => void;
+ setConfigName: (name: string) => void;
+};
+
+const ConfigContext = createContext({
+ name: 'unknown',
+ config: undefined,
+ configVersion: undefined,
+ increaseVersion: () => console.error('Provider not set'),
+ setConfigName: () => console.error('Provider not set'),
+});
+
+export const ConfigProvider = ({ children }: { children: ReactNode }) => {
+ const [configName, setConfigName] = useState();
+ const [configVersion, setConfigVersion] = useState(0);
+ const { configs } = useConfigStore((s) => ({ configs: s.configs }), shallow);
+ const { setPrimaryColor, setSecondaryColor, setPrimaryShade } = useColorTheme();
+
+ const currentConfig = configs.find((c) => c.value.configProperties.name === configName)?.value;
+
+ useEffect(() => {
+ setPrimaryColor(currentConfig?.settings.customization.colors.primary || 'red');
+ setSecondaryColor(currentConfig?.settings.customization.colors.secondary || 'orange');
+ setPrimaryShade(currentConfig?.settings.customization.colors.shade || 6);
+ }, [configName]);
+
+ return (
+ setConfigVersion((v) => v + 1),
+ setConfigName: (name: string) => setConfigName(name),
+ }}
+ >
+ {children}
+
+ );
+};
+
+export const useConfigContext = () => useContext(ConfigContext);
diff --git a/src/config/store.ts b/src/config/store.ts
new file mode 100644
index 000000000..a3a943030
--- /dev/null
+++ b/src/config/store.ts
@@ -0,0 +1,90 @@
+import axios from 'axios';
+import create from 'zustand';
+import { ConfigType } from '../types/config';
+
+export const useConfigStore = create((set, get) => ({
+ configs: [],
+ initConfig: (name, config, increaseVersion) => {
+ set((old) => ({
+ ...old,
+ configs: [
+ ...old.configs.filter((x) => x.value.configProperties?.name !== name),
+ { increaseVersion, value: config },
+ ],
+ }));
+ },
+ addConfig: async (name: string, config: ConfigType, shouldSaveConfigToFileSystem = true) => {
+ set((old) => ({
+ ...old,
+ configs: [
+ ...old.configs.filter((x) => x.value.configProperties.name !== name),
+ { value: config, increaseVersion: () => {} },
+ ],
+ }));
+
+ if (!shouldSaveConfigToFileSystem) {
+ return;
+ }
+ axios.put(`/api/configs/${name}`, { ...config });
+ },
+ removeConfig: (name: string) => {
+ set((old) => ({
+ ...old,
+ configs: old.configs.filter((x) => x.value.configProperties.name !== name),
+ }));
+ },
+ updateConfig: async (
+ name,
+ updateCallback: (previous: ConfigType) => ConfigType,
+ shouldRegenerateGridstack = false,
+ shouldSaveConfigToFileSystem = false
+ ) => {
+ const { configs } = get();
+ const currentConfig = configs.find((x) => x.value.configProperties.name === name);
+ if (!currentConfig) {
+ return;
+ }
+ // copies the value of currentConfig and creates a non reference object named previousConfig
+ const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value));
+
+ const updatedConfig = updateCallback(currentConfig.value);
+ set((old) => ({
+ ...old,
+ configs: [
+ ...old.configs.filter((x) => x.value.configProperties.name !== name),
+ { value: updatedConfig, increaseVersion: currentConfig.increaseVersion },
+ ],
+ }));
+
+ if (
+ (typeof shouldRegenerateGridstack === 'boolean' && shouldRegenerateGridstack) ||
+ (typeof shouldRegenerateGridstack === 'function' &&
+ shouldRegenerateGridstack(previousConfig, updatedConfig))
+ ) {
+ currentConfig.increaseVersion();
+ }
+
+ if (shouldSaveConfigToFileSystem) {
+ axios.put(`/api/configs/${name}`, { ...updatedConfig });
+ }
+ },
+}));
+
+interface UseConfigStoreType {
+ configs: { increaseVersion: () => void; value: ConfigType }[];
+ initConfig: (name: string, config: ConfigType, increaseVersion: () => void) => void;
+ addConfig: (
+ name: string,
+ config: ConfigType,
+ shouldSaveConfigToFileSystem: boolean
+ ) => Promise;
+ removeConfig: (name: string) => void;
+ updateConfig: (
+ name: string,
+ updateCallback: (previous: ConfigType) => ConfigType,
+ shouldRegenerateGridstack?:
+ | boolean
+ | ((previousConfig: ConfigType, currentConfig: ConfigType) => boolean),
+ shouldSaveConfigToFileSystem?: boolean
+ ) => Promise;
+}
diff --git a/src/hooks/use-resize.ts b/src/hooks/use-resize.ts
new file mode 100644
index 000000000..e6f632d36
--- /dev/null
+++ b/src/hooks/use-resize.ts
@@ -0,0 +1,28 @@
+import { MutableRefObject, useCallback, useEffect, useState } from 'react';
+
+export const useResize = (myRef: MutableRefObject, dependencies: any[]) => {
+ const [width, setWidth] = useState(0);
+ const [height, setHeight] = useState(0);
+
+ const handleResize = useCallback(() => {
+ if (!myRef.current) return;
+ setWidth(myRef.current.offsetWidth);
+ setHeight(myRef.current.offsetHeight);
+ }, [myRef]);
+
+ useEffect(() => {
+ window.addEventListener('load', handleResize);
+ window.addEventListener('resize', handleResize);
+
+ return () => {
+ window.removeEventListener('load', handleResize);
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [myRef, handleResize]);
+
+ useEffect(() => {
+ handleResize();
+ }, [myRef, dependencies]);
+
+ return { width, height };
+};
diff --git a/src/hooks/useRepositoryIconsQuery.ts b/src/hooks/useRepositoryIconsQuery.ts
new file mode 100644
index 000000000..0db02254b
--- /dev/null
+++ b/src/hooks/useRepositoryIconsQuery.ts
@@ -0,0 +1,27 @@
+import { useQuery } from '@tanstack/react-query';
+import { IconSelectorItem } from '../types/iconSelector/iconSelectorItem';
+
+export const useRepositoryIconsQuery = ({
+ url,
+ converter,
+}: {
+ url: string;
+ converter: (value: TRepositoryIcon) => IconSelectorItem;
+}) =>
+ useQuery({
+ queryKey: ['repository-icons', { url }],
+ queryFn: async () => fetchRepositoryIcons(url),
+ select(data) {
+ return data.map((x) => converter(x));
+ },
+ refetchOnWindowFocus: false,
+ });
+
+const fetchRepositoryIcons = async (
+ url: string
+): Promise => {
+ const response = await fetch(
+ 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png'
+ );
+ return response.json();
+};
diff --git a/src/hooks/useScreenLargerThan.ts b/src/hooks/useScreenLargerThan.ts
new file mode 100644
index 000000000..4cb70371c
--- /dev/null
+++ b/src/hooks/useScreenLargerThan.ts
@@ -0,0 +1,8 @@
+import { MantineSize, useMantineTheme } from '@mantine/core';
+import { useMediaQuery } from '@mantine/hooks';
+
+export const useScreenLargerThan = (size: MantineSize | number) => {
+ const { breakpoints } = useMantineTheme();
+ const pixelCount = typeof size === 'string' ? breakpoints[size] : size;
+ return useMediaQuery(`(min-width: ${pixelCount}px)`);
+};
diff --git a/src/hooks/useScreenSmallerThan.ts b/src/hooks/useScreenSmallerThan.ts
new file mode 100644
index 000000000..4667bb071
--- /dev/null
+++ b/src/hooks/useScreenSmallerThan.ts
@@ -0,0 +1,8 @@
+import { MantineSize, useMantineTheme } from '@mantine/core';
+import { useMediaQuery } from '@mantine/hooks';
+
+export const useScreenSmallerThan = (size: MantineSize | number) => {
+ const { breakpoints } = useMantineTheme();
+ const pixelCount = typeof size === 'string' ? breakpoints[size] : size;
+ return useMediaQuery(`(max-width: ${pixelCount}px)`);
+};
diff --git a/src/tools/hooks/useSetSafeInterval.tsx b/src/hooks/useSetSafeInterval.tsx
similarity index 100%
rename from src/tools/hooks/useSetSafeInterval.tsx
rename to src/hooks/useSetSafeInterval.tsx
diff --git a/src/tools/hooks/api.ts b/src/hooks/widgets/dashDot/api.ts
similarity index 77%
rename from src/tools/hooks/api.ts
rename to src/hooks/widgets/dashDot/api.ts
index ee4b5cf1d..34a290475 100644
--- a/src/tools/hooks/api.ts
+++ b/src/hooks/widgets/dashDot/api.ts
@@ -1,24 +1,24 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { Results } from 'sabnzbd-api';
-import {
+import type {
UsenetQueueRequestParams,
UsenetQueueResponse,
-} from '../../pages/api/modules/usenet/queue';
-import {
+} from '../../../pages/api/modules/usenet/queue';
+import type {
UsenetHistoryRequestParams,
UsenetHistoryResponse,
-} from '../../pages/api/modules/usenet/history';
-import { UsenetInfoRequestParams, UsenetInfoResponse } from '../../pages/api/modules/usenet';
-import { UsenetPauseRequestParams } from '../../pages/api/modules/usenet/pause';
-import { queryClient } from '../queryClient';
-import { UsenetResumeRequestParams } from '../../pages/api/modules/usenet/resume';
+} from '../../../pages/api/modules/usenet/history';
+import { UsenetInfoRequestParams, UsenetInfoResponse } from '../../../pages/api/modules/usenet';
+import { UsenetPauseRequestParams } from '../../../pages/api/modules/usenet/pause';
+import { queryClient } from '../../../tools/queryClient';
+import { UsenetResumeRequestParams } from '../../../pages/api/modules/usenet/resume';
const POLLING_INTERVAL = 2000;
export const useGetUsenetInfo = (params: UsenetInfoRequestParams) =>
useQuery(
- ['usenetInfo', params.serviceId],
+ ['usenetInfo', params.appId],
async () =>
(
await axios.get('/api/modules/usenet', {
@@ -29,7 +29,7 @@ export const useGetUsenetInfo = (params: UsenetInfoRequestParams) =>
refetchInterval: POLLING_INTERVAL,
keepPreviousData: true,
retry: 2,
- enabled: !!params.serviceId,
+ enabled: !!params.appId,
}
);
@@ -80,14 +80,14 @@ export const usePauseUsenetQueue = (params: UsenetPauseRequestParams) =>
).data,
{
async onMutate() {
- await queryClient.cancelQueries(['usenetInfo', params.serviceId]);
+ await queryClient.cancelQueries(['usenetInfo', params.appId]);
const previousInfo = queryClient.getQueryData([
'usenetInfo',
- params.serviceId,
+ params.appId,
]);
if (previousInfo) {
- queryClient.setQueryData(['usenetInfo', params.serviceId], {
+ queryClient.setQueryData(['usenetInfo', params.appId], {
...previousInfo,
paused: true,
});
@@ -98,13 +98,13 @@ export const usePauseUsenetQueue = (params: UsenetPauseRequestParams) =>
onError(err, _, context) {
if (context?.previousInfo) {
queryClient.setQueryData(
- ['usenetInfo', params.serviceId],
+ ['usenetInfo', params.appId],
context.previousInfo
);
}
},
onSettled() {
- queryClient.invalidateQueries(['usenetInfo', params.serviceId]);
+ queryClient.invalidateQueries(['usenetInfo', params.appId]);
},
}
);
@@ -124,14 +124,14 @@ export const useResumeUsenetQueue = (params: UsenetResumeRequestParams) =>
).data,
{
async onMutate() {
- await queryClient.cancelQueries(['usenetInfo', params.serviceId]);
+ await queryClient.cancelQueries(['usenetInfo', params.appId]);
const previousInfo = queryClient.getQueryData([
'usenetInfo',
- params.serviceId,
+ params.appId,
]);
if (previousInfo) {
- queryClient.setQueryData(['usenetInfo', params.serviceId], {
+ queryClient.setQueryData(['usenetInfo', params.appId], {
...previousInfo,
paused: false,
});
@@ -142,13 +142,13 @@ export const useResumeUsenetQueue = (params: UsenetResumeRequestParams) =>
onError(err, _, context) {
if (context?.previousInfo) {
queryClient.setQueryData(
- ['usenetInfo', params.serviceId],
+ ['usenetInfo', params.appId],
context.previousInfo
);
}
},
onSettled() {
- queryClient.invalidateQueries(['usenetInfo', params.serviceId]);
+ queryClient.invalidateQueries(['usenetInfo', params.appId]);
},
}
);
diff --git a/src/hooks/widgets/torrents/useGetTorrentData.tsx b/src/hooks/widgets/torrents/useGetTorrentData.tsx
new file mode 100644
index 000000000..b3136e03f
--- /dev/null
+++ b/src/hooks/widgets/torrents/useGetTorrentData.tsx
@@ -0,0 +1,29 @@
+import { NormalizedTorrent } from '@ctrl/shared-torrent';
+import { Query, useQuery } from '@tanstack/react-query';
+import axios from 'axios';
+
+const POLLING_INTERVAL = 2000;
+
+interface TorrentsDataRequestParams {
+ appId: string;
+ refreshInterval: number;
+}
+
+export const useGetTorrentData = (params: TorrentsDataRequestParams) =>
+ useQuery({
+ queryKey: ['torrentsData', params.appId],
+ queryFn: fetchData,
+ refetchOnWindowFocus: true,
+ refetchInterval(_: any, query: Query) {
+ if (query.state.fetchFailureCount < 3) {
+ return params.refreshInterval;
+ }
+ return false;
+ },
+ enabled: !!params.appId,
+ });
+
+const fetchData = async (): Promise => {
+ const response = await axios.post('/api/modules/torrents');
+ return response.data as NormalizedTorrent[];
+};
diff --git a/src/middleware.ts b/src/middleware.ts
index 9c45b2840..15b770e8b 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -11,6 +11,7 @@ export function middleware(req: NextRequest, ev: NextFetchEvent) {
(url.pathname.includes('/_next/') && !url.pathname.includes('/pages/')) ||
url.pathname === '/favicon.ico' ||
url.pathname === '/404' ||
+ url.pathname === '/migrate' ||
url.pathname.includes('pages/_app'));
if (!skipURL && !isCorrectPassword && process.env.PASSWORD) {
url.pathname = '/login';
diff --git a/src/modules/docker/ContainerActionBar.tsx b/src/modules/Docker/ContainerActionBar.tsx
similarity index 72%
rename from src/modules/docker/ContainerActionBar.tsx
rename to src/modules/Docker/ContainerActionBar.tsx
index 0dd5e9ff5..0d0a1f968 100644
--- a/src/modules/docker/ContainerActionBar.tsx
+++ b/src/modules/Docker/ContainerActionBar.tsx
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Button, Group } from '@mantine/core';
-import { closeModal, openModal } from '@mantine/modals';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconCheck,
@@ -16,9 +15,12 @@ import Dockerode from 'dockerode';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { TFunction } from 'react-i18next';
-import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
+import { v4 as uuidv4 } from 'uuid';
+import { useConfigContext } from '../../config/provider';
import { tryMatchService } from '../../tools/addToHomarr';
-import { useConfig } from '../../tools/state';
+import { openContextModalGeneric } from '../../tools/mantineModalManagerExtensions';
+import { AppType } from '../../types/app';
+import { appTileDefinition } from '../../components/Dashboard/Tiles/Apps/AppTile';
let t: TFunction<'modules/docker', undefined>;
@@ -69,7 +71,8 @@ export interface ContainerActionBarProps {
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
t = useTranslation('modules/docker').t;
const [isLoading, setisLoading] = useState(false);
- const { config, setConfig } = useConfig();
+ const { name: configName, config } = useConfigContext();
+ const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
return (
@@ -160,21 +163,42 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
radius="md"
disabled={selected.length === 0 || selected.length > 1}
onClick={() => {
- openModal({
+ const app = tryMatchService(selected.at(0)!);
+ const containerUrl = `http://localhost:${selected[0].Ports[0].PublicPort}`;
+ openContextModalGeneric<{ app: AppType; allowAppNamePropagation: boolean }>({
+ modal: 'editApp',
+ zIndex: 1000,
+ innerProps: {
+ app: {
+ id: uuidv4(),
+ name: app.name ? app.name : selected[0].Names[0].substring(1),
+ url: containerUrl,
+ appearance: {
+ iconUrl: app.icon ? app.icon : '/imgs/logo/logo.png',
+ },
+ network: {
+ enabledStatusChecker: true,
+ okStatus: [200],
+ },
+ behaviour: {
+ isOpeningNewTab: true,
+ externalUrl: '',
+ },
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: getLowestWrapper()?.id ?? 'default',
+ },
+ },
+ shape: {},
+ integration: {
+ type: null,
+ properties: [],
+ },
+ },
+ allowAppNamePropagation: true,
+ },
size: 'xl',
- modalId: selected.at(0)!.Id,
- radius: 'md',
- title: t('actionBar.addService.title'),
- zIndex: 500,
- children: (
- closeModal(selected.at(0)!.Id)}
- message={t('actionBar.addService.message')}
- {...tryMatchService(selected.at(0)!)}
- />
- ),
});
}}
>
diff --git a/src/modules/docker/ContainerState.tsx b/src/modules/Docker/ContainerState.tsx
similarity index 100%
rename from src/modules/docker/ContainerState.tsx
rename to src/modules/Docker/ContainerState.tsx
diff --git a/src/modules/docker/DockerModule.tsx b/src/modules/Docker/DockerModule.tsx
similarity index 78%
rename from src/modules/docker/DockerModule.tsx
rename to src/modules/Docker/DockerModule.tsx
index 61101723e..54704ec3c 100644
--- a/src/modules/docker/DockerModule.tsx
+++ b/src/modules/Docker/DockerModule.tsx
@@ -1,38 +1,31 @@
import { ActionIcon, Drawer, Text, Tooltip } from '@mantine/core';
-import axios from 'axios';
-import { useEffect, useState } from 'react';
-import Docker from 'dockerode';
-import { IconBrandDocker, IconX } from '@tabler/icons';
import { showNotification } from '@mantine/notifications';
+import { IconBrandDocker, IconX } from '@tabler/icons';
+import axios from 'axios';
+import Docker from 'dockerode';
import { useTranslation } from 'next-i18next';
+import { useEffect, useState } from 'react';
+import { useConfigContext } from '../../config/provider';
import ContainerActionBar from './ContainerActionBar';
import DockerTable from './DockerTable';
-import { useConfig } from '../../tools/state';
-import { IModule } from '../ModuleTypes';
-
-export const DockerModule: IModule = {
- title: 'Docker',
- icon: IconBrandDocker,
- component: DockerMenuButton,
- id: 'docker',
-};
export default function DockerMenuButton(props: any) {
const [opened, setOpened] = useState(false);
const [containers, setContainers] = useState([]);
const [selection, setSelection] = useState([]);
- const { config } = useConfig();
- const moduleEnabled = config.modules?.[DockerModule.id]?.enabled ?? false;
+ const { config } = useConfigContext();
+
+ const dockerEnabled = config?.settings.customization.layout.enabledDocker || false;
const { t } = useTranslation('modules/docker');
useEffect(() => {
reload();
- }, [config.modules]);
+ }, [config?.settings]);
function reload() {
- if (!moduleEnabled) {
+ if (!dockerEnabled) {
return;
}
setTimeout(() => {
@@ -56,18 +49,18 @@ export default function DockerMenuButton(props: any) {
});
}, 300);
}
- const exists = config.modules?.[DockerModule.id]?.enabled ?? false;
- if (!exists) {
+
+ if (!dockerEnabled) {
return null;
}
- // Check if the user has at least one container
- if (containers.length < 1) return null;
+
return (
<>
setOpened(false)}
padding="xl"
+ position="right"
size="full"
title={ }
>
diff --git a/src/modules/docker/DockerTable.tsx b/src/modules/Docker/DockerTable.tsx
similarity index 96%
rename from src/modules/docker/DockerTable.tsx
rename to src/modules/Docker/DockerTable.tsx
index 10196ac5c..2925e3990 100644
--- a/src/modules/docker/DockerTable.tsx
+++ b/src/modules/Docker/DockerTable.tsx
@@ -1,4 +1,13 @@
-import { Table, Checkbox, Group, Badge, createStyles, ScrollArea, TextInput, useMantineTheme } from '@mantine/core';
+import {
+ Table,
+ Checkbox,
+ Group,
+ Badge,
+ createStyles,
+ ScrollArea,
+ TextInput,
+ useMantineTheme,
+} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons';
import Dockerode from 'dockerode';
@@ -109,7 +118,6 @@ export default function DockerTable({
icon={ }
value={search}
onChange={handleSearchChange}
- disabled={usedContainers.length === 0}
/>
diff --git a/src/modules/ModuleTypes.d.ts b/src/modules/ModuleTypes.d.ts
index 937afc5a5..23d535b42 100644
--- a/src/modules/ModuleTypes.d.ts
+++ b/src/modules/ModuleTypes.d.ts
@@ -5,6 +5,7 @@
import { TablerIcon } from '@tabler/icons';
// Note: Maybe use context to keep track of the modules
+// TODO: Remove this old component and the entire file
export interface IModule {
id: string;
title: string;
diff --git a/src/modules/calendar/CalendarModule.tsx b/src/modules/calendar/CalendarModule.tsx
deleted file mode 100644
index bdebf072b..000000000
--- a/src/modules/calendar/CalendarModule.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-/* eslint-disable react/no-children-prop */
-import {
- Box,
- Divider,
- Indicator,
- Popover,
- ScrollArea,
- createStyles,
- useMantineTheme,
- Space,
-} from '@mantine/core';
-import React, { useEffect, useState } from 'react';
-import { Calendar } from '@mantine/dates';
-import { IconCalendar as CalendarIcon } from '@tabler/icons';
-import axios from 'axios';
-import { useDisclosure } from '@mantine/hooks';
-import { useConfig } from '../../tools/state';
-import { IModule } from '../ModuleTypes';
-import {
- SonarrMediaDisplay,
- RadarrMediaDisplay,
- LidarrMediaDisplay,
- ReadarrMediaDisplay,
-} from '../common';
-import { serviceItem } from '../../tools/types';
-import { useColorTheme } from '../../tools/color';
-
-export const CalendarModule: IModule = {
- title: 'Calendar',
- icon: CalendarIcon,
- component: CalendarComponent,
- options: {
- sundaystart: {
- name: 'descriptor.settings.sundayStart.label',
- value: false,
- },
- },
- id: 'calendar',
-};
-
-export default function CalendarComponent(props: any) {
- const { config } = useConfig();
- const theme = useMantineTheme();
- const { secondaryColor } = useColorTheme();
- const useStyles = createStyles((theme) => ({
- weekend: {
- color: `${secondaryColor} !important`,
- },
- }));
-
- const [sonarrMedias, setSonarrMedias] = useState([] as any);
- const [lidarrMedias, setLidarrMedias] = useState([] as any);
- const [radarrMedias, setRadarrMedias] = useState([] as any);
- const [readarrMedias, setReadarrMedias] = useState([] as any);
- const sonarrServices = config.services.filter((service) => service.type === 'Sonarr');
- const radarrServices = config.services.filter((service) => service.type === 'Radarr');
- const lidarrServices = config.services.filter((service) => service.type === 'Lidarr');
- const readarrServices = config.services.filter((service) => service.type === 'Readarr');
- const today = new Date();
-
- const { classes, cx } = useStyles();
-
- function getMedias(service: serviceItem | undefined, type: string) {
- if (!service || !service.apiKey) {
- return Promise.resolve({ data: [] });
- }
- return axios.post(`/api/modules/calendar?type=${type}`, { id: service.id });
- }
-
- useEffect(() => {
- // Create each Sonarr service and get the medias
- const currentSonarrMedias: any[] = [];
- Promise.all(
- sonarrServices.map((service) =>
- getMedias(service, 'sonarr')
- .then((res) => {
- currentSonarrMedias.push(...res.data);
- })
- .catch(() => {
- currentSonarrMedias.push([]);
- })
- )
- ).then(() => {
- setSonarrMedias(currentSonarrMedias);
- });
- const currentRadarrMedias: any[] = [];
- Promise.all(
- radarrServices.map((service) =>
- getMedias(service, 'radarr')
- .then((res) => {
- currentRadarrMedias.push(...res.data);
- })
- .catch(() => {
- currentRadarrMedias.push([]);
- })
- )
- ).then(() => {
- setRadarrMedias(currentRadarrMedias);
- });
- const currentLidarrMedias: any[] = [];
- Promise.all(
- lidarrServices.map((service) =>
- getMedias(service, 'lidarr')
- .then((res) => {
- currentLidarrMedias.push(...res.data);
- })
- .catch(() => {
- currentLidarrMedias.push([]);
- })
- )
- ).then(() => {
- setLidarrMedias(currentLidarrMedias);
- });
- const currentReadarrMedias: any[] = [];
- Promise.all(
- readarrServices.map((service) =>
- getMedias(service, 'readarr')
- .then((res) => {
- currentReadarrMedias.push(...res.data);
- })
- .catch(() => {
- currentReadarrMedias.push([]);
- })
- )
- ).then(() => {
- setReadarrMedias(currentReadarrMedias);
- });
- }, [config.services]);
-
- const weekStartsAtSunday =
- (config?.modules?.[CalendarModule.id]?.options?.sundaystart?.value as boolean) ?? false;
- return (
- {}}
- dayStyle={(date) =>
- date.getDay() === today.getDay() && date.getDate() === today.getDate()
- ? {
- backgroundColor:
- theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[0],
- margin: 1,
- }
- : {
- margin: 1,
- }
- }
- allowLevelChange={false}
- dayClassName={(date, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
- renderDay={(renderdate) => (
-
- )}
- />
- );
-}
-
-function DayComponent(props: any) {
- const {
- renderdate,
- sonarrmedias,
- radarrmedias,
- lidarrmedias,
- readarrmedias,
- }: { renderdate: Date; sonarrmedias: []; radarrmedias: []; lidarrmedias: []; readarrmedias: [] } =
- props;
- const [opened, { close, open }] = useDisclosure(false);
-
- const day = renderdate.getDate();
-
- const readarrFiltered = readarrmedias.filter((media: any) => {
- const date = new Date(media.releaseDate);
- return date.toDateString() === renderdate.toDateString();
- });
-
- const lidarrFiltered = lidarrmedias.filter((media: any) => {
- const date = new Date(media.releaseDate);
- return date.toDateString() === renderdate.toDateString();
- });
- const sonarrFiltered = sonarrmedias.filter((media: any) => {
- const date = new Date(media.airDateUtc);
- return date.toDateString() === renderdate.toDateString();
- });
- const radarrFiltered = radarrmedias.filter((media: any) => {
- const date = new Date(media.inCinemas);
- return date.toDateString() === renderdate.toDateString();
- });
- const totalFiltered = [
- ...readarrFiltered,
- ...lidarrFiltered,
- ...sonarrFiltered,
- ...radarrFiltered,
- ];
- if (totalFiltered.length === 0) {
- return {day}
;
- }
-
- return (
-
-
-
- {readarrFiltered.length > 0 && (
-
- )}
- {radarrFiltered.length > 0 && (
-
- )}
- {sonarrFiltered.length > 0 && (
-
- )}
- {lidarrFiltered.length > 0 && (
-
- )}
- {day}
-
-
-
- 1 ? totalFiltered.slice(0, 2).length * 150 : 220,
- width: 400,
- }}
- >
-
- {sonarrFiltered.map((media: any, index: number) => (
-
-
- {index < sonarrFiltered.length - 1 && }
-
- ))}
- {radarrFiltered.length > 0 && sonarrFiltered.length > 0 && (
-
- )}
- {radarrFiltered.map((media: any, index: number) => (
-
-
- {index < radarrFiltered.length - 1 && }
-
- ))}
- {sonarrFiltered.length > 0 && lidarrFiltered.length > 0 && (
-
- )}
- {lidarrFiltered.map((media: any, index: number) => (
-
-
- {index < lidarrFiltered.length - 1 && }
-
- ))}
- {lidarrFiltered.length > 0 && readarrFiltered.length > 0 && (
-
- )}
- {readarrFiltered.map((media: any, index: number) => (
-
-
- {index < readarrFiltered.length - 1 && }
-
- ))}
-
-
-
- );
-}
diff --git a/src/modules/calendar/index.ts b/src/modules/calendar/index.ts
deleted file mode 100644
index ccb16b383..000000000
--- a/src/modules/calendar/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { CalendarModule } from './CalendarModule';
diff --git a/src/modules/calendar/mediaExample.ts b/src/modules/calendar/mediaExample.ts
deleted file mode 100644
index acdbc0352..000000000
--- a/src/modules/calendar/mediaExample.ts
+++ /dev/null
@@ -1,408 +0,0 @@
-export const medias = [
- {
- title: 'Doctor Strange in the Multiverse of Madness',
- originalTitle: 'Doctor Strange in the Multiverse of Madness',
- originalLanguage: {
- id: 1,
- name: 'English',
- },
- alternateTitles: [
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 1,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Доктор Стрэндж 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 11,
- name: 'Russian',
- },
- id: 2,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doutor Estranho 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 3,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange v multivesmíre šialenstva',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 4,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange 2: El multiverso de la locura',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 3,
- name: 'Spanish',
- },
- id: 5,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doktor Strange Deliliğin Çoklu Evreninde',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 17,
- name: 'Turkish',
- },
- id: 6,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'মহাবিশ্বের পাগলামিতে অদ্ভুত চিকিৎসক',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 7,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'จอมเวทย์มหากาฬ ในมัลติเวิร์สมหาภัย',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 28,
- name: 'Thai',
- },
- id: 8,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: "Marvel Studios' Doctor Strange in the Multiverse of Madness",
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 9,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange en el Multiverso de la Locura de Marvel Studios',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 3,
- name: 'Spanish',
- },
- id: 10,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doktors Streindžs neprāta multivisumā',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 11,
- },
- ],
- secondaryYearSourceId: 0,
- sortTitle: 'doctor strange in multiverse madness',
- sizeOnDisk: 0,
- status: 'announced',
- overview:
- 'Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.',
- inCinemas: '2022-05-04T00:00:00Z',
- images: [
- {
- coverType: 'poster',
- url: 'https://image.tmdb.org/t/p/original/wRnbWt44nKjsFPrqSmwYki5vZtF.jpg',
- },
- {
- coverType: 'fanart',
- url: 'https://image.tmdb.org/t/p/original/ndCSoasjIZAMMDIuMxuGnNWu4DU.jpg',
- },
- ],
- website: 'https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness',
- year: 2022,
- hasFile: false,
- youTubeTrailerId: 'aWzlQ2N6qqg',
- studio: 'Marvel Studios',
- path: '/config/Doctor Strange in the Multiverse of Madness (2022)',
- qualityProfileId: 1,
- monitored: true,
- minimumAvailability: 'announced',
- isAvailable: true,
- folderName: '/config/Doctor Strange in the Multiverse of Madness (2022)',
- runtime: 126,
- cleanTitle: 'doctorstrangeinmultiversemadness',
- imdbId: 'tt9419884',
- tmdbId: 453395,
- titleSlug: '453395',
- certification: 'PG-13',
- genres: ['Fantasy', 'Action', 'Adventure'],
- tags: [],
- added: '2022-04-29T20:52:33Z',
- ratings: {
- tmdb: {
- votes: 0,
- value: 0,
- type: 'user',
- },
- },
- collection: {
- name: 'Doctor Strange Collection',
- tmdbId: 618529,
- images: [],
- },
- id: 1,
- },
- {
- title: 'Doctor Strange in the Multiverse of Madness',
- originalTitle: 'Doctor Strange in the Multiverse of Madness',
- originalLanguage: {
- id: 1,
- name: 'English',
- },
- alternateTitles: [
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 1,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Доктор Стрэндж 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 11,
- name: 'Russian',
- },
- id: 2,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doutor Estranho 2',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 3,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange v multivesmíre šialenstva',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 4,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange 2: El multiverso de la locura',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 3,
- name: 'Spanish',
- },
- id: 5,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doktor Strange Deliliğin Çoklu Evreninde',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 17,
- name: 'Turkish',
- },
- id: 6,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'মহাবিশ্বের পাগলামিতে অদ্ভুত চিকিৎসক',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 7,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'จอมเวทย์มหากาฬ ในมัลติเวิร์สมหาภัย',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 28,
- name: 'Thai',
- },
- id: 8,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: "Marvel Studios' Doctor Strange in the Multiverse of Madness",
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 9,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doctor Strange en el Multiverso de la Locura de Marvel Studios',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 3,
- name: 'Spanish',
- },
- id: 10,
- },
- {
- sourceType: 'tmdb',
- movieId: 1,
- title: 'Doktors Streindžs neprāta multivisumā',
- sourceId: 0,
- votes: 0,
- voteCount: 0,
- language: {
- id: 1,
- name: 'English',
- },
- id: 11,
- },
- ],
- secondaryYearSourceId: 0,
- sortTitle: 'doctor strange in multiverse madness',
- sizeOnDisk: 0,
- status: 'announced',
- overview:
- 'Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.',
- inCinemas: '2022-05-05T00:00:00Z',
- images: [
- {
- coverType: 'poster',
- url: 'https://image.tmdb.org/t/p/original/wRnbWt44nKjsFPrqSmwYki5vZtF.jpg',
- },
- {
- coverType: 'fanart',
- url: 'https://image.tmdb.org/t/p/original/ndCSoasjIZAMMDIuMxuGnNWu4DU.jpg',
- },
- ],
- website: 'https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness',
- year: 2022,
- hasFile: false,
- youTubeTrailerId: 'aWzlQ2N6qqg',
- studio: 'Marvel Studios',
- path: '/config/Doctor Strange in the Multiverse of Madness (2022)',
- qualityProfileId: 1,
- monitored: true,
- minimumAvailability: 'announced',
- isAvailable: true,
- folderName: '/config/Doctor Strange in the Multiverse of Madness (2022)',
- runtime: 126,
- cleanTitle: 'doctorstrangeinmultiversemadness',
- imdbId: 'tt9419884',
- tmdbId: 453395,
- titleSlug: '453395',
- certification: 'PG-13',
- genres: ['Fantasy', 'Action', 'Adventure'],
- tags: [],
- added: '2022-04-29T20:52:33Z',
- ratings: {
- tmdb: {
- votes: 0,
- value: 0,
- type: 'user',
- },
- },
- collection: {
- name: 'Doctor Strange Collection',
- tmdbId: 618529,
- images: [],
- },
- id: 2,
- },
-];
diff --git a/src/modules/common/MediaDisplay.tsx b/src/modules/common/MediaDisplay.tsx
index 5ea29fc9c..7c3631b18 100644
--- a/src/modules/common/MediaDisplay.tsx
+++ b/src/modules/common/MediaDisplay.tsx
@@ -2,9 +2,8 @@ import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core';
import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
+import { useConfigContext } from '../../config/provider';
import { useColorTheme } from '../../tools/color';
-import { useConfig } from '../../tools/state';
-import { serviceItem } from '../../tools/types';
import { RequestModal } from '../overseerr/RequestModal';
import { Result } from '../overseerr/SearchResult';
@@ -27,9 +26,15 @@ export interface IMedia {
export function OverseerrMediaDisplay(props: any) {
const { media }: { media: Result } = props;
- const { config } = useConfig();
- const service = config.services.find(
- (service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
+ const { config } = useConfigContext();
+
+ if (!config) {
+ return null;
+ }
+
+ const service = config.apps.find(
+ (service) =>
+ service.integration.type === 'overseerr' || service.integration.type === 'jellyseerr'
);
return (
@@ -45,9 +50,9 @@ export function OverseerrMediaDisplay(props: any) {
plexUrl: media.mediaInfo?.plexUrl ?? media.mediaInfo?.mediaUrl,
voteAverage: media.voteAverage?.toString(),
overseerrResult: media,
- overseerrId: `${service?.openedUrl ? service?.openedUrl : service?.url}/${
- media.mediaType
- }/${media.id}`,
+ overseerrId: `${
+ service?.behaviour.externalUrl ? service.behaviour.externalUrl : service?.url
+ }/${media.mediaType}/${media.id}`,
type: 'overseer',
}}
/>
@@ -56,16 +61,21 @@ export function OverseerrMediaDisplay(props: any) {
export function ReadarrMediaDisplay(props: any) {
const { media }: { media: any } = props;
- const { config } = useConfig();
+ const { config } = useConfigContext();
+
+ if (!config) {
+ return null;
+ }
+
// Find lidarr in services
- const readarr = config.services.find((service: serviceItem) => service.type === 'Readarr');
+ const readarr = config.apps.find((service) => service.integration.type === 'readarr');
// Find a poster CoverType
const poster = media.images.find((image: any) => image.coverType === 'cover');
if (!readarr) {
return null;
}
- const baseUrl = readarr.openedUrl
- ? new URL(readarr.openedUrl).origin
+ const baseUrl = readarr.behaviour.externalUrl
+ ? new URL(readarr.behaviour.externalUrl).origin
: new URL(readarr.url).origin;
// Remove '/' from the end of the lidarr url
const fullLink = poster ? `${baseUrl}${poster.url}` : undefined;
@@ -88,15 +98,22 @@ export function ReadarrMediaDisplay(props: any) {
export function LidarrMediaDisplay(props: any) {
const { media }: { media: any } = props;
- const { config } = useConfig();
+ const { config } = useConfigContext();
+
+ if (!config) {
+ return null;
+ }
+
// Find lidarr in services
- const lidarr = config.services.find((service: serviceItem) => service.type === 'Lidarr');
+ const lidarr = config.apps.find((service) => service.integration.type === 'lidarr');
// Find a poster CoverType
const poster = media.images.find((image: any) => image.coverType === 'cover');
if (!lidarr) {
return null;
}
- const baseUrl = lidarr.openedUrl ? new URL(lidarr.openedUrl).origin : new URL(lidarr.url).origin;
+ const baseUrl = lidarr.behaviour.externalUrl
+ ? new URL(lidarr.behaviour.externalUrl).origin
+ : new URL(lidarr.url).origin;
// Remove '/' from the end of the lidarr url
const fullLink = poster ? `${baseUrl}${poster.url}` : undefined;
// Return a movie poster containting the title and the description
@@ -163,14 +180,19 @@ export function MediaDisplay({ media }: { media: IMedia }) {
const { t } = useTranslation('modules/common-media-cards');
return (
-
+
{media.title}
-
+
{media.type === 'tvshow' && (
s{media.seasonNumber}e{media.episodeNumber} - {media.episodetitle}
diff --git a/src/modules/dashdot/DashdotModule.tsx b/src/modules/dashdot/DashdotModule.tsx
deleted file mode 100644
index 51badf882..000000000
--- a/src/modules/dashdot/DashdotModule.tsx
+++ /dev/null
@@ -1,275 +0,0 @@
-import { createStyles, Stack, Title, useMantineColorScheme, useMantineTheme } from '@mantine/core';
-import { IconCalendar as CalendarIcon } from '@tabler/icons';
-import axios from 'axios';
-import { useTranslation } from 'next-i18next';
-import { useEffect, useState } from 'react';
-import { useConfig } from '../../tools/state';
-import { serviceItem } from '../../tools/types';
-import { IModule } from '../ModuleTypes';
-
-const asModule = (t: T) => t;
-export const DashdotModule = asModule({
- title: 'Dash.',
- icon: CalendarIcon,
- component: DashdotComponent,
- options: {
- cpuMultiView: {
- name: 'descriptor.settings.cpuMultiView.label',
- value: false,
- },
- storageMultiView: {
- name: 'descriptor.settings.storageMultiView.label',
- value: false,
- },
- useCompactView: {
- name: 'descriptor.settings.useCompactView.label',
- value: false,
- },
- graphs: {
- name: 'descriptor.settings.graphs.label',
- value: ['CPU', 'RAM', 'Storage', 'Network'],
- options: ['CPU', 'RAM', 'Storage', 'Network', 'GPU'],
- },
- url: {
- name: 'descriptor.settings.url.label',
- value: '',
- },
- },
- id: 'dashdot',
-});
-
-const useStyles = createStyles((theme, _params, getRef) => ({
- heading: {
- marginTop: 0,
- marginBottom: 10,
- },
- table: {
- display: 'table',
- },
- tableRow: {
- display: 'table-row',
- },
- tableLabel: {
- display: 'table-cell',
- paddingRight: 10,
- },
- tableValue: {
- display: 'table-cell',
- whiteSpace: 'pre-wrap',
- paddingBottom: 5,
- },
- graphsContainer: {
- display: 'flex',
- flexDirection: 'row',
- flexWrap: 'wrap',
- rowGap: 10,
- columnGap: 10,
- },
- iframe: {
- flex: '1 0 auto',
- maxWidth: '100%',
- height: '140px',
- borderRadius: theme.radius.lg,
- border: 'none',
- colorScheme: 'none',
- },
- graphTitle: {
- ref: getRef('graphTitle'),
- position: 'absolute',
- right: 0,
- opacity: 0,
- transition: 'opacity .1s ease-in-out',
- pointerEvents: 'none',
- },
- graphStack: {
- [`&:hover .${getRef('graphTitle')}`]: {
- opacity: 0.5,
- },
- },
-}));
-
-const bpsPrettyPrint = (bits?: number) =>
- !bits
- ? '-'
- : bits > 1000 * 1000 * 1000
- ? `${(bits / 1000 / 1000 / 1000).toFixed(1)} Gb/s`
- : bits > 1000 * 1000
- ? `${(bits / 1000 / 1000).toFixed(1)} Mb/s`
- : bits > 1000
- ? `${(bits / 1000).toFixed(1)} Kb/s`
- : `${bits.toFixed(1)} b/s`;
-
-const bytePrettyPrint = (byte: number): string =>
- byte > 1024 * 1024 * 1024
- ? `${(byte / 1024 / 1024 / 1024).toFixed(1)} GiB`
- : byte > 1024 * 1024
- ? `${(byte / 1024 / 1024).toFixed(1)} MiB`
- : byte > 1024
- ? `${(byte / 1024).toFixed(1)} KiB`
- : `${byte.toFixed(1)} B`;
-
-const useJson = (targetUrl: string, url: string) => {
- const [data, setData] = useState();
-
- const doRequest = async () => {
- try {
- const resp = await axios.get('/api/modules/dashdot', { params: { url, base: targetUrl } });
-
- setData(resp.data);
- // eslint-disable-next-line no-empty
- } catch (e) {}
- };
-
- useEffect(() => {
- if (targetUrl) {
- doRequest();
- }
- }, [targetUrl]);
-
- return data;
-};
-
-export function DashdotComponent() {
- const { config } = useConfig();
- const theme = useMantineTheme();
- const { classes } = useStyles();
- const { colorScheme } = useMantineColorScheme();
-
- const dashConfig = config.modules?.[DashdotModule.id].options as typeof DashdotModule['options'];
- const isCompact = dashConfig?.useCompactView?.value ?? false;
- const dashdotService: serviceItem | undefined = config.services.filter(
- (service) => service.type === 'Dash.'
- )[0];
- const dashdotUrl = dashdotService?.url ?? dashConfig?.url?.value ?? '';
- const enabledGraphs = dashConfig?.graphs?.value ?? ['CPU', 'RAM', 'Storage', 'Network'];
- const cpuEnabled = enabledGraphs.includes('CPU');
- const storageEnabled = enabledGraphs.includes('Storage');
- const ramEnabled = enabledGraphs.includes('RAM');
- const networkEnabled = enabledGraphs.includes('Network');
- const gpuEnabled = enabledGraphs.includes('GPU');
-
- const info = useJson(dashdotUrl, '/info');
- const storageLoad = useJson(dashdotUrl, '/load/storage');
-
- const totalUsed =
- (storageLoad?.layout as any[])?.reduce((acc, curr) => (curr.load ?? 0) + acc, 0) ?? 0;
- const totalSize =
- (info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
-
- const { t } = useTranslation('modules/dashdot');
-
- const graphs = [
- {
- id: 'cpu',
- name: t('card.graphs.cpu.title'),
- enabled: cpuEnabled,
- params: {
- multiView: dashConfig?.cpuMultiView?.value ?? false,
- },
- },
- {
- id: 'storage',
- name: t('card.graphs.storage.title'),
- enabled: storageEnabled && !isCompact,
- params: {
- multiView: dashConfig?.storageMultiView?.value ?? false,
- },
- },
- {
- id: 'ram',
- name: t('card.graphs.memory.title'),
- enabled: ramEnabled,
- },
- {
- id: 'network',
- name: t('card.graphs.network.title'),
- enabled: networkEnabled,
- spanTwo: true,
- },
- {
- id: 'gpu',
- name: t('card.graphs.gpu.title'),
- enabled: gpuEnabled,
- spanTwo: true,
- },
- ].filter((g) => g.enabled);
-
- if (dashdotUrl === '') {
- return (
-
-
{t('card.title')}
-
{t('card.errors.noService')}
-
- );
- }
-
- return (
-
-
{t('card.title')}
-
- {!info ? (
-
{t('card.errors.noInformation')}
- ) : (
-
-
- {storageEnabled && isCompact && (
-
-
{t('card.graphs.storage.label')}
-
- {((100 * totalUsed) / (totalSize || 1)).toFixed(1)}%{'\n'}
- {bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)}
-
-
- )}
- {networkEnabled && (
-
-
{t('card.graphs.network.label')}
-
- {bpsPrettyPrint(info?.network?.speedUp)} {t('card.graphs.network.metrics.upload')}
- {'\n'}
- {bpsPrettyPrint(info?.network?.speedDown)}
- {t('card.graphs.network.metrics.download')}
-
-
- )}
-
-
- {graphs.map((graph) => (
-
-
- {graph.name}
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/src/modules/dashdot/index.ts b/src/modules/dashdot/index.ts
deleted file mode 100644
index 4d483ea4d..000000000
--- a/src/modules/dashdot/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DashdotModule } from './DashdotModule';
diff --git a/src/modules/date/DateModule.tsx b/src/modules/date/DateModule.tsx
deleted file mode 100644
index 3a57491fd..000000000
--- a/src/modules/date/DateModule.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Group, Text, Title } from '@mantine/core';
-import dayjs from 'dayjs';
-import { useEffect, useState } from 'react';
-import { IconClock as Clock } from '@tabler/icons';
-import { useConfig } from '../../tools/state';
-import { IModule } from '../ModuleTypes';
-import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
-
-export const DateModule: IModule = {
- title: 'Date',
- icon: Clock,
- component: DateComponent,
- options: {
- full: {
- name: 'descriptor.settings.display24HourFormat.label',
- value: true,
- },
- },
- id: 'date',
-};
-
-export default function DateComponent(props: any) {
- const [date, setDate] = useState(new Date());
- const setSafeInterval = useSetSafeInterval();
- const { config } = useConfig();
- const isFullTime = config?.modules?.[DateModule.id]?.options?.full?.value ?? true;
- const formatString = isFullTime ? 'HH:mm' : 'h:mm A';
- // Change date on minute change
- // Note: Using 10 000ms instead of 1000ms to chill a little :)
- useEffect(() => {
- setSafeInterval(() => {
- setDate(new Date());
- }, 1000 * 60);
- }, []);
-
- return (
-
- {dayjs(date).format(formatString)}
- {dayjs(date).format('dddd, MMMM D')}
-
- );
-}
diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts
deleted file mode 100644
index 62db5e0b0..000000000
--- a/src/modules/date/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DateModule } from './DateModule';
diff --git a/src/modules/docker/index.ts b/src/modules/docker/index.ts
deleted file mode 100644
index 8e0a617dc..000000000
--- a/src/modules/docker/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DockerModule } from './DockerModule';
diff --git a/src/modules/index.ts b/src/modules/index.ts
index f3b7292a7..3f69cd0de 100644
--- a/src/modules/index.ts
+++ b/src/modules/index.ts
@@ -1,10 +1,2 @@
-export * from './calendar';
-export * from './dashdot';
-export * from './date';
-export * from './torrents';
export * from './ping';
-export * from './search';
-export * from './weather';
-export * from './docker';
export * from './overseerr';
-export * from './usenet';
diff --git a/src/modules/moduleWrapper.tsx b/src/modules/moduleWrapper.tsx
deleted file mode 100644
index d62e6b02e..000000000
--- a/src/modules/moduleWrapper.tsx
+++ /dev/null
@@ -1,239 +0,0 @@
-import {
- ActionIcon,
- Button,
- Card,
- Group,
- Menu,
- MultiSelect,
- Switch,
- TextInput,
- useMantineColorScheme,
-} from '@mantine/core';
-import { IconAdjustments } from '@tabler/icons';
-import { motion } from 'framer-motion';
-import { useTranslation } from 'next-i18next';
-import { useState } from 'react';
-import { useConfig } from '../tools/state';
-import { IModule } from './ModuleTypes';
-
-function getItems(module: IModule) {
- const { config, setConfig } = useConfig();
- const { t } = useTranslation([`modules/${module.id}`, 'common']);
- const items: JSX.Element[] = [];
- if (module.options) {
- const keys = Object.keys(module.options);
- const values = Object.values(module.options);
- // Get the value and the name of the option
- const types = values.map((v) => typeof v.value);
- // Loop over all the types with a for each loop
- types.forEach((type, index) => {
- const optionName = `${module.id}.${keys[index]}`;
- const moduleInConfig = config.modules?.[module.id];
- if (type === 'object') {
- items.push(
- {
- setConfig({
- ...config,
- modules: {
- ...config.modules,
- [module.id]: {
- ...moduleInConfig,
- options: {
- ...moduleInConfig?.options,
- [keys[index]]: {
- ...moduleInConfig?.options?.[keys[index]],
- value,
- },
- },
- },
- },
- });
- }}
- />
- );
- }
- if (type === 'string') {
- items.push(
- {
- e.preventDefault();
- setConfig({
- ...config,
- modules: {
- ...config.modules,
- [module.id]: {
- ...config.modules[module.id],
- options: {
- ...config.modules[module.id].options,
- [keys[index]]: {
- ...config.modules[module.id].options?.[keys[index]],
- value: (e.target as any)[0].value,
- },
- },
- },
- },
- });
- }}
- >
-
- {}}
- />
-
- {t('actions.save', { ns: 'common' })}
-
-
- );
- }
- // TODO: Add support for other types
- if (type === 'boolean') {
- items.push(
- {
- setConfig({
- ...config,
- modules: {
- ...config.modules,
- [module.id]: {
- ...config.modules[module.id],
- options: {
- ...config.modules[module.id].options,
- [keys[index]]: {
- ...config.modules[module.id].options?.[keys[index]],
- value: e.currentTarget.checked,
- },
- },
- },
- },
- });
- }}
- label={t(values[index].name)}
- />
- );
- }
- });
- }
- return items;
-}
-
-export function ModuleWrapper(props: any) {
- const { module }: { module: IModule } = props;
- const { colorScheme } = useMantineColorScheme();
- const { config, setConfig } = useConfig();
- const enabledModules = config.modules ?? {};
- // Remove 'Module' from enabled modules titles
- const isShown = enabledModules[module.id]?.enabled ?? false;
- //TODO: fix the hover problem
- const [hovering, setHovering] = useState(false);
- const { t } = useTranslation('modules');
-
- if (!isShown) {
- return null;
- }
-
- return (
-
- {
- setHovering(true);
- }}
- onHoverEnd={() => {
- setHovering(false);
- }}
- >
-
-
-
-
- );
-}
-
-interface ModuleMenuProps {
- hovered: boolean;
- module: IModule;
-}
-
-export function ModuleMenu(props: ModuleMenuProps) {
- const { module, hovered } = props;
- const items: JSX.Element[] = getItems(module);
- const { t } = useTranslation('modules/common');
- return (
- <>
- {module.options && (
-
-
-
-
-
-
-
-
-
- {t('settings.label')}
- {items.map((item) => (
- {item}
- ))}
-
-
- )}
- >
- );
-}
diff --git a/src/modules/ping/PingModule.tsx b/src/modules/ping/PingModule.tsx
index a9a9344a8..e85088373 100644
--- a/src/modules/ping/PingModule.tsx
+++ b/src/modules/ping/PingModule.tsx
@@ -1,10 +1,10 @@
import { Indicator, Tooltip } from '@mantine/core';
+import { IconPlug as Plug } from '@tabler/icons';
import axios, { AxiosResponse } from 'axios';
import { motion } from 'framer-motion';
-import { useEffect, useState } from 'react';
-import { IconPlug as Plug } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
+import { useEffect, useState } from 'react';
+import { useConfigContext } from '../../config/provider';
import { IModule } from '../ModuleTypes';
export const PingModule: IModule = {
@@ -16,12 +16,12 @@ export const PingModule: IModule = {
export default function PingComponent(props: any) {
type State = 'loading' | 'down' | 'online';
- const { config } = useConfig();
+ const { config } = useConfigContext();
const { url }: { url: string } = props;
const [isOnline, setOnline] = useState('loading');
const [response, setResponse] = useState(500);
- const exists = config.modules?.[PingModule.id]?.enabled ?? false;
+ const exists = config?.settings.customization.layout.enabledPing || false;
const { t } = useTranslation('modules/ping');
@@ -54,7 +54,7 @@ export default function PingComponent(props: any) {
.catch((error) => {
statusCheck(error.response);
});
- }, [config.modules?.[PingModule.id]?.enabled]);
+ }, [config?.settings.customization.layout.enabledPing]);
if (!exists) {
return null;
}
@@ -62,7 +62,7 @@ export default function PingComponent(props: any) {
@@ -78,7 +78,7 @@ export default function PingComponent(props: any) {
}
>
{null}
diff --git a/src/modules/search/index.ts b/src/modules/search/index.ts
deleted file mode 100644
index eda41643a..000000000
--- a/src/modules/search/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { SearchModule } from './SearchModule';
diff --git a/src/modules/torrents/TorrentsModule.tsx b/src/modules/torrents/TorrentsModule.tsx
deleted file mode 100644
index cd010992c..000000000
--- a/src/modules/torrents/TorrentsModule.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import {
- Table,
- Text,
- Tooltip,
- Title,
- Group,
- Progress,
- Skeleton,
- ScrollArea,
- Center,
- Stack,
- useMantineTheme,
-} from '@mantine/core';
-import { IconDownload as Download } from '@tabler/icons';
-import { useEffect, useState } from 'react';
-import axios from 'axios';
-import { useElementSize } from '@mantine/hooks';
-import { showNotification } from '@mantine/notifications';
-import { NormalizedTorrent } from '@ctrl/shared-torrent';
-import { useTranslation } from 'next-i18next';
-import { IModule } from '../ModuleTypes';
-import { useConfig } from '../../tools/state';
-import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
-import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
-import { humanFileSize } from '../../tools/humanFileSize';
-
-export const TorrentsModule: IModule = {
- id: 'torrents-status',
- title: 'Torrent',
- icon: Download,
- component: TorrentsComponent,
- options: {
- hideComplete: {
- name: 'descriptor.settings.hideComplete.label',
- value: false,
- },
- },
-};
-
-export default function TorrentsComponent() {
- const { config } = useConfig();
- const downloadServices =
- config.services.filter(
- (service) =>
- service.type === 'qBittorrent' ||
- service.type === 'Transmission' ||
- service.type === 'Deluge'
- ) ?? [];
-
- const hideComplete: boolean =
- (config?.modules?.[TorrentsModule.id]?.options?.hideComplete?.value as boolean) ?? false;
- const [torrents, setTorrents] = useState([]);
- const setSafeInterval = useSetSafeInterval();
- const [isLoading, setIsLoading] = useState(true);
- const { ref, width, height } = useElementSize();
- const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
-
- const { t } = useTranslation(`modules/${TorrentsModule.id}`);
-
- useEffect(() => {
- setIsLoading(true);
- if (downloadServices.length === 0) return;
- const interval = setInterval(() => {
- // Send one request with each download service inside
- axios
- .post('/api/modules/torrents')
- .then((response) => {
- setTorrents(response.data);
- setIsLoading(false);
- })
- .catch((error) => {
- setTorrents([]);
- // eslint-disable-next-line no-console
- console.error('Error while fetching torrents', error.response.data);
- setIsLoading(false);
- showNotification({
- title: 'Error fetching torrents',
- autoClose: 1000,
- disallowClose: true,
- id: 'fail-torrent-downloads-module',
- color: 'red',
- message:
- 'Please check your config for any potential errors, check the console for more info',
- });
- clearInterval(interval);
- });
- }, 5000);
- }, []);
-
- if (downloadServices.length === 0) {
- return (
-
- {t('card.errors.noDownloadClients.title')}
-
- {t('card.errors.noDownloadClients.text')}
-
-
-
- );
- }
-
- if (isLoading) {
- return (
- <>
-
-
-
-
-
- >
- );
- }
- const ths = (
-
- {t('card.table.header.name')}
- {t('card.table.header.size')}
- {width > MIN_WIDTH_MOBILE && {t('card.table.header.download')} }
- {width > MIN_WIDTH_MOBILE && {t('card.table.header.upload')} }
- {width > MIN_WIDTH_MOBILE && {t('card.table.header.estimatedTimeOfArrival')} }
- {t('card.table.header.progress')}
-
- );
- // Convert Seconds to readable format.
- function calculateETA(givenSeconds: number) {
- // If its superior than one day return > 1 day
- if (givenSeconds > 86400) {
- return '> 1 day';
- }
- // Transform the givenSeconds into a readable format. e.g. 1h 2m 3s
- const hours = Math.floor(givenSeconds / 3600);
- const minutes = Math.floor((givenSeconds % 3600) / 60);
- const seconds = Math.floor(givenSeconds % 60);
- // Only show hours if it's greater than 0.
- const hoursString = hours > 0 ? `${hours}h ` : '';
- const minutesString = minutes > 0 ? `${minutes}m ` : '';
- const secondsString = seconds > 0 ? `${seconds}s` : '';
- return `${hoursString}${minutesString}${secondsString}`;
- }
- // Loop over qBittorrent torrents merging with deluge torrents
- const rows = torrents
- .filter((torrent) => !(torrent.progress === 1 && hideComplete))
- .map((torrent) => {
- const downloadSpeed = torrent.downloadSpeed / 1024 / 1024;
- const uploadSpeed = torrent.uploadSpeed / 1024 / 1024;
- const size = torrent.totalSelected;
- return (
-
-
-
-
- {torrent.name}
-
-
-
-
- {humanFileSize(size)}
-
- {width > MIN_WIDTH_MOBILE && (
-
- {downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}
-
- )}
- {width > MIN_WIDTH_MOBILE && (
-
- {uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}
-
- )}
- {width > MIN_WIDTH_MOBILE && (
-
- {torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}
-
- )}
-
- {(torrent.progress * 100).toFixed(1)}%
-
-
-
- );
- });
-
- return (
-
- {rows.length > 0 ? (
-
- ) : (
-
- {t('card.table.body.nothingFound')}
-
- )}
-
- );
-}
diff --git a/src/modules/torrents/index.ts b/src/modules/torrents/index.ts
deleted file mode 100644
index c5337e5fb..000000000
--- a/src/modules/torrents/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { TorrentsModule } from './TorrentsModule';
-export { TotalDownloadsModule } from './TotalDownloadsModule';
diff --git a/src/modules/usenet/UsenetModule.tsx b/src/modules/usenet/UsenetModule.tsx
deleted file mode 100644
index 6545889ee..000000000
--- a/src/modules/usenet/UsenetModule.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import {
- Badge,
- Button,
- Group,
- Select,
- Stack,
- Tabs,
- Text,
- Title,
- useMantineTheme,
-} from '@mantine/core';
-import { IconDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
-import { FunctionComponent, useEffect, useState } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import dayjs from 'dayjs';
-import duration from 'dayjs/plugin/duration';
-import { useElementSize } from '@mantine/hooks';
-import { IModule } from '../ModuleTypes';
-import { UsenetQueueList } from './UsenetQueueList';
-import { UsenetHistoryList } from './UsenetHistoryList';
-import { useGetServiceByType } from '../../tools/hooks/useGetServiceByType';
-import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api';
-import { humanFileSize } from '../../tools/humanFileSize';
-import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
-
-dayjs.extend(duration);
-
-export const UsenetComponent: FunctionComponent = () => {
- const downloadServices = useGetServiceByType('Sabnzbd', 'NZBGet');
-
- const { t } = useTranslation('modules/usenet');
-
- const [selectedServiceId, setSelectedService] = useState(downloadServices[0]?.id);
- const { data } = useGetUsenetInfo({ serviceId: selectedServiceId! });
-
- useEffect(() => {
- if (!selectedServiceId && downloadServices.length) {
- setSelectedService(downloadServices[0].id);
- }
- }, [downloadServices, selectedServiceId]);
-
- const { mutate: pause } = usePauseUsenetQueue({ serviceId: selectedServiceId! });
- const { mutate: resume } = useResumeUsenetQueue({ serviceId: selectedServiceId! });
-
- if (downloadServices.length === 0) {
- return (
-
- {t('card.errors.noDownloadClients.title')}
-
- {t('card.errors.noDownloadClients.text')}
-
-
-
- );
- }
-
- if (!selectedServiceId) {
- return null;
- }
-
- const { ref, width, height } = useElementSize();
- const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
-
- return (
-
-
- {t('tabs.queue')}
- {t('tabs.history')}
- {data && (
-
- {width > MIN_WIDTH_MOBILE && (
- <>
- {humanFileSize(data?.speed)}/s
-
- {t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)}
-
- >
- )}
-
- {data.paused ? (
- resume()} radius="xl" size="xs">
- {t('info.paused')}
-
- ) : (
- pause()} radius="xl" size="xs">
- {' '}
- {dayjs.duration(data.eta, 's').format('HH:mm')}
-
- )}
-
- )}
-
- {downloadServices.length > 1 && (
- ({ value: service.id, label: service.name }))}
- />
- )}
-
-
-
-
-
-
-
- );
-};
-
-export const UsenetModule: IModule = {
- id: 'usenet',
- title: 'Usenet',
- icon: IconDownload,
- component: UsenetComponent,
-};
-
-export default UsenetComponent;
diff --git a/src/modules/usenet/index.ts b/src/modules/usenet/index.ts
deleted file mode 100644
index 6ee3e560e..000000000
--- a/src/modules/usenet/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { UsenetModule } from './UsenetModule';
-export * from './types';
diff --git a/src/modules/weather/WeatherModule.tsx b/src/modules/weather/WeatherModule.tsx
deleted file mode 100644
index aec0e125f..000000000
--- a/src/modules/weather/WeatherModule.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { Group, Space, Title, Tooltip, Skeleton, Stack, Box } from '@mantine/core';
-import axios from 'axios';
-import { useEffect, useState } from 'react';
-import {
- IconArrowDownRight as ArrowDownRight,
- IconArrowUpRight as ArrowUpRight,
- IconCloud as Cloud,
- IconCloudFog as CloudFog,
- IconCloudRain as CloudRain,
- IconCloudSnow as CloudSnow,
- IconCloudStorm as CloudStorm,
- IconQuestionMark as QuestionMark,
- IconSnowflake as Snowflake,
- IconSun as Sun,
-} from '@tabler/icons';
-import { useTranslation } from 'next-i18next';
-import { useConfig } from '../../tools/state';
-import { IModule } from '../ModuleTypes';
-import { WeatherResponse } from './WeatherInterface';
-
-export const WeatherModule: IModule = {
- title: 'Weather',
- icon: Sun,
- component: WeatherComponent,
- options: {
- freedomunit: {
- name: 'descriptor.settings.displayInFahrenheit.label',
- value: false,
- },
- location: {
- name: 'descriptor.settings.location.label',
- value: 'Paris',
- },
- },
- id: 'weather',
-};
-
-// 0 Clear sky
-// 1, 2, 3 Mainly clear, partly cloudy, and overcast
-// 45, 48 Fog and depositing rime fog
-// 51, 53, 55 Drizzle: Light, moderate, and dense intensity
-// 56, 57 Freezing Drizzle: Light and dense intensity
-// 61, 63, 65 Rain: Slight, moderate and heavy intensity
-// 66, 67 Freezing Rain: Light and heavy intensity
-// 71, 73, 75 Snow fall: Slight, moderate, and heavy intensity
-// 77 Snow grains
-// 80, 81, 82 Rain showers: Slight, moderate, and violent
-// 85, 86Snow showers slight and heavy
-// 95 *Thunderstorm: Slight or moderate
-// 96, 99 *Thunderstorm with slight and heavy hail
-export function WeatherIcon(props: any) {
- const { t } = useTranslation('modules/weather');
-
- const { code } = props;
- let data: { icon: any; name: string };
- switch (code) {
- case 0: {
- data = { icon: Sun, name: t('card.weatherDescriptions.clear') };
- break;
- }
- case 1:
- case 2:
- case 3: {
- data = { icon: Cloud, name: t('card.weatherDescriptions.mainlyClear') };
- break;
- }
- case 45:
- case 48: {
- data = { icon: CloudFog, name: t('card.weatherDescriptions.fog') };
- break;
- }
- case 51:
- case 53:
- case 55: {
- data = { icon: Cloud, name: t('card.weatherDescriptions.drizzle') };
- break;
- }
- case 56:
- case 57: {
- data = {
- icon: Snowflake,
- name: t('card.weatherDescriptions.freezingDrizzle'),
- };
- break;
- }
- case 61:
- case 63:
- case 65: {
- data = { icon: CloudRain, name: t('card.weatherDescriptions.rain') };
- break;
- }
- case 66:
- case 67: {
- data = { icon: CloudRain, name: t('card.weatherDescriptions.freezingRain') };
- break;
- }
- case 71:
- case 73:
- case 75: {
- data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowFall') };
- break;
- }
- case 77: {
- data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowGrains') };
- break;
- }
- case 80:
- case 81:
- case 82: {
- data = { icon: CloudRain, name: t('card.weatherDescriptions.rainShowers') };
-
- break;
- }
- case 85:
- case 86: {
- data = { icon: CloudSnow, name: t('card.weatherDescriptions.snowShowers') };
- break;
- }
- case 95: {
- data = { icon: CloudStorm, name: t('card.weatherDescriptions.thunderstorm') };
- break;
- }
- case 96:
- case 99: {
- data = {
- icon: CloudStorm,
- name: t('card.weatherDescriptions.thunderstormWithHail'),
- };
- break;
- }
- default: {
- data = { icon: QuestionMark, name: t('card.weatherDescriptions.unknown') };
- }
- }
- return (
-
-
-
-
-
- );
-}
-
-export default function WeatherComponent(props: any) {
- // Get location from browser
- const { config } = useConfig();
- const [weather, setWeather] = useState({} as WeatherResponse);
- const cityInput: string =
- (config?.modules?.[WeatherModule.id]?.options?.location?.value as string) ?? 'Paris';
- const isFahrenheit: boolean =
- (config?.modules?.[WeatherModule.id]?.options?.freedomunit?.value as boolean) ?? false;
-
- useEffect(() => {
- axios
- .get(`https://geocoding-api.open-meteo.com/v1/search?name=${cityInput}`)
- .then((response) => {
- // Check if results exists
- const { latitude, longitude } = response.data.results
- ? response.data.results[0]
- : { latitude: 0, longitude: 0 };
- axios
- .get(
- `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon`
- )
- .then((res) => {
- setWeather(res.data);
- });
- });
- }, [cityInput]);
- if (!weather.current_weather) {
- return (
- <>
-
-
-
-
-
-
-
-
- >
- );
- }
- function usePerferedUnit(value: number): string {
- return isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`;
- }
- return (
-
- {usePerferedUnit(weather.current_weather.temperature)}
-
-
-
- {usePerferedUnit(weather.daily.temperature_2m_max[0])}
-
-
- {usePerferedUnit(weather.daily.temperature_2m_min[0])}
-
-
-
- );
-}
diff --git a/src/modules/weather/index.ts b/src/modules/weather/index.ts
deleted file mode 100644
index e8817394b..000000000
--- a/src/modules/weather/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { WeatherModule } from './WeatherModule';
diff --git a/src/pages/[slug].tsx b/src/pages/[slug].tsx
index a1ae53141..5a0876847 100644
--- a/src/pages/[slug].tsx
+++ b/src/pages/[slug].tsx
@@ -1,59 +1,66 @@
-import { getCookie } from 'cookies-next';
+import { setCookie } from 'cookies-next';
import fs from 'fs';
import { GetServerSidePropsContext } from 'next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import path from 'path';
-import { useEffect } from 'react';
-import AppShelf from '../components/AppShelf/AppShelf';
-import LoadConfigComponent from '../components/Config/LoadConfig';
+import { LoadConfigComponent } from '../components/Config/LoadConfig';
+import { Dashboard } from '../components/Dashboard/Dashboard';
import Layout from '../components/layout/Layout';
-import { getConfig } from '../tools/getConfig';
-import { useConfig } from '../tools/state';
+import { useInitConfig } from '../config/init';
+import { getFallbackConfig } from '../tools/config/getFallbackConfig';
+import { getFrontendConfig } from '../tools/config/getFrontendConfig';
+import { getServerSideTranslations } from '../tools/getServerSideTranslations';
import { dashboardNamespaces } from '../tools/translation-namespaces';
-import { Config } from '../tools/types';
+import { ConfigType } from '../types/config';
+import { DashboardServerSideProps } from '../types/dashboardPageType';
export async function getServerSideProps({
req,
res,
locale,
query,
-}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
- const configByUrl = query.slug;
- const configPath = path.join(process.cwd(), 'data/configs', `${configByUrl}.json`);
+}: GetServerSidePropsContext): Promise<{ props: DashboardServerSideProps }> {
+ const configName = query.slug as string;
+ const configPath = path.join(process.cwd(), 'data/configs', `${configName}.json`);
const configExists = fs.existsSync(configPath);
+
+ const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale);
+
if (!configExists) {
// Redirect to 404
res.writeHead(301, { Location: '/404' });
res.end();
return {
props: {
- config: {
- name: 'Default config',
- services: [],
- settings: {
- searchUrl: 'https://www.google.com/search?q=',
- },
- modules: {},
- },
+ config: getFallbackConfig() as unknown as ConfigType,
+ configName,
+ ...translations,
},
};
}
- const configLocale = getCookie('config-locale', { req, res });
- const targetLanguage = (configLocale ?? locale) as string;
- const translations = await serverSideTranslations(targetLanguage, dashboardNamespaces);
- return getConfig(configByUrl as string, translations);
+ const config = getFrontendConfig(configName as string);
+ setCookie('config-name', configName, {
+ req,
+ res,
+ maxAge: 60 * 60 * 24 * 30,
+ sameSite: 'strict',
+ });
+
+ return {
+ props: {
+ configName,
+ config,
+ ...translations,
+ },
+ };
}
-export default function HomePage(props: any) {
- const { config: initialConfig }: { config: Config } = props;
- const { setConfig } = useConfig();
- useEffect(() => {
- setConfig(initialConfig);
- }, [initialConfig]);
+export default function HomePage({ config: initialConfig }: DashboardServerSideProps) {
+ useInitConfig(initialConfig);
+
return (
-
+
);
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 4b49fd0bc..829fba3bd 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,18 +1,26 @@
-import { GetServerSidePropsContext } from 'next';
-import { useState } from 'react';
-import { AppProps } from 'next/app';
-import { getCookie } from 'cookies-next';
-import Head from 'next/head';
-import { MantineProvider, ColorScheme, ColorSchemeProvider, MantineTheme } from '@mantine/core';
-import { NotificationsProvider } from '@mantine/notifications';
+import { ColorScheme, ColorSchemeProvider, MantineProvider, MantineTheme } from '@mantine/core';
import { useColorScheme, useHotkeys, useLocalStorage } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
-import { appWithTranslation } from 'next-i18next';
+import { NotificationsProvider } from '@mantine/notifications';
import { QueryClientProvider } from '@tanstack/react-query';
-import { ConfigProvider } from '../tools/state';
-import { theme } from '../tools/theme';
+import { getCookie } from 'cookies-next';
+import { GetServerSidePropsContext } from 'next';
+import { appWithTranslation } from 'next-i18next';
+import { AppProps } from 'next/app';
+import Head from 'next/head';
+import { useState } from 'react';
+import { ChangeAppPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal';
+import { ChangeWidgetPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal';
+import { EditAppModal } from '../components/Dashboard/Modals/EditAppModal/EditAppModal';
+import { SelectElementModal } from '../components/Dashboard/Modals/SelectElement/SelectElementModal';
+import { WidgetsEditModal } from '../components/Dashboard/Tiles/Widgets/WidgetsEditModal';
+import { WidgetsRemoveModal } from '../components/Dashboard/Tiles/Widgets/WidgetsRemoveModal';
+import { CategoryEditModal } from '../components/Dashboard/Wrappers/Category/CategoryEditModal';
+import { ConfigProvider } from '../config/provider';
+import '../styles/global.scss';
import { ColorTheme } from '../tools/color';
import { queryClient } from '../tools/queryClient';
+import { theme } from '../tools/theme';
function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {
const { Component, pageProps } = props;
@@ -74,13 +82,23 @@ function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {
withGlobalStyles
withNormalizeCSS
>
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/src/pages/api/configs/[slug].ts b/src/pages/api/configs/[slug].ts
index 6f71ca257..414c74c5d 100644
--- a/src/pages/api/configs/[slug].ts
+++ b/src/pages/api/configs/[slug].ts
@@ -1,24 +1,78 @@
-import { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs';
import path from 'path';
+import Consola from 'consola';
+import { NextApiRequest, NextApiResponse } from 'next';
+import { BackendConfigType, ConfigType } from '../../../types/config';
+import { getConfig } from '../../../tools/config/getConfig';
function Put(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
const { slug } = req.query as { slug: string };
// Get the body of the request
- const { body }: { body: string } = req;
- if (!slug || !body) {
- res.status(400).json({
+ const { body: config }: { body: ConfigType } = req;
+ if (!slug || !config) {
+ Consola.warn('Rejected configuration update because either config or slug were undefined');
+ return res.status(400).json({
error: 'Wrong request',
});
}
- // Save the body in the /data/config folder with the slug as filename
- fs.writeFileSync(
- path.join('data/configs', `${slug}.json`),
- JSON.stringify(body, null, 2),
- 'utf8'
- );
+ Consola.info(`Saving updated configuration of '${slug}' config.`);
+
+ const previousConfig = getConfig(slug);
+
+ const newConfig: BackendConfigType = {
+ ...config,
+ apps: [
+ ...config.apps.map((app) => ({
+ ...app,
+ integration: {
+ ...app.integration,
+ properties: app.integration.properties.map((property) => {
+ if (property.type === 'public') {
+ return {
+ field: property.field,
+ type: property.type,
+ value: property.value,
+ };
+ }
+
+ const previousApp = previousConfig.apps.find(
+ (previousApp) => previousApp.id === app.id
+ );
+
+ const previousProperty = previousApp?.integration?.properties.find(
+ (previousProperty) => previousProperty.field === property.field
+ );
+
+ if (property.value !== undefined && property.value !== null) {
+ Consola.info(
+ 'Detected credential change of private secret. Value will be overwritten in configuration'
+ );
+ return {
+ field: property.field,
+ type: property.type,
+ value: property.value,
+ };
+ }
+
+ return {
+ field: property.field,
+ type: property.type,
+ value: previousProperty?.value,
+ };
+ }),
+ },
+ })),
+ ],
+ };
+
+ // Save the body in the /data/config folder with the slug as filename
+ const targetPath = path.join('data/configs', `${slug}.json`);
+ fs.writeFileSync(targetPath, JSON.stringify(newConfig, null, 2), 'utf8');
+
+ Consola.debug(`Config '${slug}' has been updated and flushed to '${targetPath}'.`);
+
return res.status(200).json({
message: 'Configuration saved with success',
});
@@ -32,16 +86,22 @@ function Get(req: NextApiRequest, res: NextApiResponse) {
message: 'Wrong request',
});
}
+
// Loop over all the files in the /data/configs directory
- const files = fs.readdirSync('data/configs');
+ // Get all the configs in the /data/configs folder
+ // All the files that end in ".json"
+ const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
+
// Strip the .json extension from the file name
const configs = files.map((file) => file.replace('.json', ''));
+
// If the target is not in the list of files, return an error
if (!configs.includes(slug)) {
return res.status(404).json({
message: 'Target not found',
});
}
+
// Return the content of the file
return res.status(200).json(fs.readFileSync(path.join('data/configs', `${slug}.json`), 'utf8'));
}
@@ -51,12 +111,15 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'PUT') {
return Put(req, res);
}
+
if (req.method === 'DELETE') {
return Delete(req, res);
}
+
if (req.method === 'GET') {
return Get(req, res);
}
+
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
@@ -67,22 +130,42 @@ function Delete(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
const { slug } = req.query as { slug: string };
if (!slug) {
+ Consola.error('Rejected config deletion request because config slug was not present');
return res.status(400).json({
message: 'Wrong request',
});
}
+
+ if (slug.toLowerCase() === 'default') {
+ Consola.error("Rejected config deletion because default configuration can't be deleted");
+ return res.status(403).json({
+ message: "Default config can't be deleted",
+ });
+ }
+
// Loop over all the files in the /data/configs directory
- const files = fs.readdirSync('data/configs');
- // Strip the .json extension from the file name
- const configs = files.map((file) => file.replace('.json', ''));
+ // Get all the configs in the /data/configs folder
+ // All the files that end in ".json"
+ const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
+ // Match one file if the configProperties.name is the same as the slug
+ const matchedFile = files.find((file) => {
+ const config = JSON.parse(fs.readFileSync(path.join('data/configs', file), 'utf8'));
+ return config.configProperties.name === slug;
+ });
+
// If the target is not in the list of files, return an error
- if (!configs.includes(slug)) {
+ if (!matchedFile) {
+ Consola.error(
+ `Rejected config deletion request because config name '${slug}' was not included in present configurations`
+ );
return res.status(404).json({
message: 'Target not found',
});
}
+
// Delete the file
- fs.unlinkSync(path.join('data/configs', `${slug}.json`));
+ fs.unlinkSync(path.join('data/configs', matchedFile));
+ Consola.info(`Successfully deleted configuration '${slug}' from your file system`);
return res.status(200).json({
message: 'Configuration deleted with success',
});
diff --git a/src/pages/api/configs/index.ts b/src/pages/api/configs/index.ts
index 78aabc4e8..f582b7855 100644
--- a/src/pages/api/configs/index.ts
+++ b/src/pages/api/configs/index.ts
@@ -2,8 +2,9 @@ import { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs';
function Get(req: NextApiRequest, res: NextApiResponse) {
- // Loop over all the files in the /data/configs directory
- const files = fs.readdirSync('data/configs');
+ // Get all the configs in the /data/configs folder
+ // All the files that end in ".json"
+ const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
// Strip the .json extension from the file name
const configs = files.map((file) => file.replace('.json', ''));
@@ -12,12 +13,6 @@ function Get(req: NextApiRequest, res: NextApiResponse) {
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a POST or a GET
- if (req.method === 'POST') {
- return res.status(405).json({
- statusCode: 405,
- message: 'Method not allowed',
- });
- }
if (req.method === 'GET') {
return Get(req, res);
}
diff --git a/src/pages/api/migrate.ts b/src/pages/api/migrate.ts
new file mode 100644
index 000000000..379b7c724
--- /dev/null
+++ b/src/pages/api/migrate.ts
@@ -0,0 +1,21 @@
+import { NextApiRequest, NextApiResponse } from 'next';
+import fs from 'fs';
+import { backendMigrateConfig } from '../../tools/config/backendMigrateConfig';
+
+export default async (req: NextApiRequest, res: NextApiResponse) => {
+ // Gets all the config files
+ const configs = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
+ // If there is no config, redirect to the index
+ configs.every((config) => {
+ const configData = JSON.parse(fs.readFileSync(`./data/configs/${config}`, 'utf8'));
+ if (!configData.schemaVersion) {
+ // Migrate the config
+ backendMigrateConfig(configData, config.replace('.json', ''));
+ }
+ return config;
+ });
+ return res.status(200).json({
+ success: true,
+ message: 'Configs migrated',
+ });
+};
diff --git a/src/pages/api/modules/calendar.ts b/src/pages/api/modules/calendar.ts
index 0bb41e545..daf9ec41a 100644
--- a/src/pages/api/modules/calendar.ts
+++ b/src/pages/api/modules/calendar.ts
@@ -1,83 +1,104 @@
import axios from 'axios';
-import { getCookie } from 'cookies-next';
+import Consola from 'consola';
import { NextApiRequest, NextApiResponse } from 'next';
-import { getConfig } from '../../../tools/getConfig';
-import { Config } from '../../../tools/types';
-
-async function Post(req: NextApiRequest, res: NextApiResponse) {
- // Parse req.body as a ServiceItem
- const { id } = req.body;
- const { type } = req.query;
- const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- // Find service with serviceId in config
- const service = config.services.find((service) => service.id === id);
- if (!service) {
- return res.status(500).json({
- statusCode: 500,
- message: 'Missing service',
- });
- }
-
- const nextMonth = new Date(new Date().setMonth(new Date().getMonth() + 2)).toISOString();
- const lastMonth = new Date(new Date().setMonth(new Date().getMonth() - 2)).toISOString();
- const TypeToUrl: { service: string; url: string }[] = [
- {
- service: 'sonarr',
- url: '/api/calendar',
- },
- {
- service: 'radarr',
- url: '/api/v3/calendar',
- },
- {
- service: 'lidarr',
- url: '/api/v1/calendar',
- },
- {
- service: 'readarr',
- url: '/api/v1/calendar',
- },
- ];
- if (!type) {
- return res.status(400).json({
- message: 'Missing required parameter in url: type',
- });
- }
- if (!service) {
- return res.status(400).json({
- message: 'Missing required parameter in body: service',
- });
- }
- // Match the type to the correct url
- const url = TypeToUrl.find((x) => x.service === type);
- if (!url) {
- return res.status(400).json({
- message: 'Invalid type',
- });
- }
- // Get the origin URL
- let { href: origin } = new URL(service.url);
- if (origin.endsWith('/')) {
- origin = origin.slice(0, -1);
- }
- const pined = `${origin}${url?.url}?apiKey=${service.apiKey}&end=${nextMonth}&start=${lastMonth}`;
- return axios
- .get(`${origin}${url?.url}?apiKey=${service.apiKey}&end=${nextMonth}&start=${lastMonth}`)
- .then((response) => res.status(200).json(response.data))
- .catch((e) => res.status(500).json(e));
- // // Make a request to the URL
- // const response = await axios.get(url);
- // // Return the response
-}
+import { getConfig } from '../../../tools/config/getConfig';
+import { AppIntegrationType } from '../../../types/app';
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a POST or a GET
- if (req.method === 'POST') {
- return Post(req, res);
+ if (req.method === 'GET') {
+ return Get(req, res);
}
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
};
+
+async function Get(req: NextApiRequest, res: NextApiResponse) {
+ // Parse req.body as a AppItem
+ const {
+ month: monthString,
+ year: yearString,
+ configName,
+ } = req.query as { month: string; year: string; configName: string };
+
+ const month = parseInt(monthString, 10);
+ const year = parseInt(yearString, 10);
+
+ if (Number.isNaN(month) || Number.isNaN(year) || !configName) {
+ return res.status(400).json({
+ statusCode: 400,
+ message: 'Missing required parameter in url: year, month or configName',
+ });
+ }
+
+ const config = getConfig(configName);
+
+ const mediaAppIntegrationTypes: AppIntegrationType['type'][] = [
+ 'sonarr',
+ 'radarr',
+ 'readarr',
+ 'lidarr',
+ ];
+ const mediaApps = config.apps.filter(
+ (app) => app.integration && mediaAppIntegrationTypes.includes(app.integration.type)
+ );
+
+ try {
+ const medias = await Promise.all(
+ await mediaApps.map(async (app) => {
+ const integration = app.integration!;
+ const endpoint = IntegrationTypeEndpointMap.get(integration.type);
+ if (!endpoint) {
+ return {
+ type: integration.type,
+ items: [],
+ };
+ }
+
+ // Get the origin URL
+ let { href: origin } = new URL(app.url);
+ if (origin.endsWith('/')) {
+ origin = origin.slice(0, -1);
+ }
+
+ const start = new Date(year, month - 1, 1); // First day of month
+ const end = new Date(year, month, 0); // Last day of month
+
+ const apiKey = integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) return { type: integration.type, items: [] };
+ return axios
+ .get(
+ `${origin}${endpoint}?apiKey=${apiKey}&end=${end.toISOString()}&start=${start.toISOString()}`
+ )
+ .then((x) => ({ type: integration.type, items: x.data as any[] }));
+ })
+ );
+
+ return res.status(200).json({
+ tvShows: medias.filter((m) => m.type === 'sonarr').flatMap((m) => m.items),
+ movies: medias.filter((m) => m.type === 'radarr').flatMap((m) => m.items),
+ books: medias.filter((m) => m.type === 'readarr').flatMap((m) => m.items),
+ musics: medias.filter((m) => m.type === 'lidarr').flatMap((m) => m.items),
+ totalCount: medias.reduce((p, c) => p + c.items.length, 0),
+ });
+ } catch (error) {
+ Consola.error(`Error while requesting media from your app. Check your configuration. ${error}`);
+
+ return res.status(500).json({
+ tvShows: [],
+ movies: [],
+ books: [],
+ musics: [],
+ totalCount: 0,
+ });
+ }
+}
+
+const IntegrationTypeEndpointMap = new Map([
+ ['sonarr', '/api/calendar'],
+ ['radarr', '/api/v3/calendar'],
+ ['lidarr', '/api/v1/calendar'],
+ ['readarr', '/api/v1/calendar'],
+]);
diff --git a/src/pages/api/modules/dashdot.ts b/src/pages/api/modules/dashdot.ts
deleted file mode 100644
index 8f9a990a5..000000000
--- a/src/pages/api/modules/dashdot.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import axios from 'axios';
-import { NextApiRequest, NextApiResponse } from 'next';
-
-async function Get(req: NextApiRequest, res: NextApiResponse) {
- // Extract url from req.query as string
- const { url, base } = req.query;
-
- // If no url is provided, return an error
- if (!url || !base) {
- return res.status(400).json({
- message: 'Missing required parameter in url',
- });
- }
- // Get the origin URL
- const response = await axios.get(url as string, { baseURL: base as string });
- // Return the response
- return res.status(200).json(response.data);
-}
-
-export default async (req: NextApiRequest, res: NextApiResponse) => {
- // Filter out if the reuqest is a POST or a GET
- if (req.method === 'GET') {
- return Get(req, res);
- }
- return res.status(405).json({
- statusCode: 405,
- message: 'Method not allowed',
- });
-};
diff --git a/src/pages/api/modules/dashdot/info.ts b/src/pages/api/modules/dashdot/info.ts
new file mode 100644
index 000000000..2a188d9b8
--- /dev/null
+++ b/src/pages/api/modules/dashdot/info.ts
@@ -0,0 +1,51 @@
+import axios from 'axios';
+import { NextApiRequest, NextApiResponse } from 'next';
+import { getConfig } from '../../../../tools/config/getConfig';
+import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
+
+async function Get(req: NextApiRequest, res: NextApiResponse) {
+ const { configName } = req.query;
+
+ if (!configName || typeof configName !== 'string') {
+ return res.status(400).json({
+ message: 'Missing required configName in url',
+ });
+ }
+
+ const config = getConfig(configName);
+
+ const dashDotWidget = config.widgets.find((x) => x.id === 'dashdot');
+
+ if (!dashDotWidget) {
+ return res.status(400).json({
+ message: 'There is no dashdot widget defined',
+ });
+ }
+
+ const dashDotUrl = (dashDotWidget as IDashDotTile).properties.url;
+
+ if (!dashDotUrl) {
+ return res.status(400).json({
+ message: 'Dashdot url must be defined in config',
+ });
+ }
+
+ // Get the origin URL
+ const url = dashDotUrl.endsWith('/')
+ ? dashDotUrl.substring(0, dashDotUrl.length - 1)
+ : dashDotUrl;
+ const response = await axios.get(`${url}/info`);
+ // Return the response
+ return res.status(200).json(response.data);
+}
+
+export default async (req: NextApiRequest, res: NextApiResponse) => {
+ // Filter out if the reuqest is a POST or a GET
+ if (req.method === 'GET') {
+ return Get(req, res);
+ }
+ return res.status(405).json({
+ statusCode: 405,
+ message: 'Method not allowed',
+ });
+};
diff --git a/src/pages/api/modules/dashdot/storage.ts b/src/pages/api/modules/dashdot/storage.ts
new file mode 100644
index 000000000..8f15a8fa8
--- /dev/null
+++ b/src/pages/api/modules/dashdot/storage.ts
@@ -0,0 +1,50 @@
+import axios from 'axios';
+import { NextApiRequest, NextApiResponse } from 'next';
+import { getConfig } from '../../../../tools/config/getConfig';
+import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
+
+async function Get(req: NextApiRequest, res: NextApiResponse) {
+ const { configName } = req.query;
+
+ if (!configName || typeof configName !== 'string') {
+ return res.status(400).json({
+ message: 'Missing required configName in url',
+ });
+ }
+
+ const config = getConfig(configName);
+ const dashDotWidget = config.widgets.find((x) => x.id === 'dashdot');
+
+ if (!dashDotWidget) {
+ return res.status(400).json({
+ message: 'There is no dashdot widget defined',
+ });
+ }
+
+ const dashDotUrl = (dashDotWidget as IDashDotTile).properties.url;
+
+ if (!dashDotUrl) {
+ return res.status(400).json({
+ message: 'Dashdot url must be defined in config',
+ });
+ }
+
+ // Get the origin URL
+ const url = dashDotUrl.endsWith('/')
+ ? dashDotUrl.substring(0, dashDotUrl.length - 1)
+ : dashDotUrl;
+ const response = await axios.get(`${url}/load/storage`);
+ // Return the response
+ return res.status(200).json(response.data);
+}
+
+export default async (req: NextApiRequest, res: NextApiResponse) => {
+ // Filter out if the reuqest is a POST or a GET
+ if (req.method === 'GET') {
+ return Get(req, res);
+ }
+ return res.status(405).json({
+ statusCode: 405,
+ message: 'Method not allowed',
+ });
+};
diff --git a/src/pages/api/modules/overseerr/[id].tsx b/src/pages/api/modules/overseerr/[id].tsx
index 74bddddc8..55d253400 100644
--- a/src/pages/api/modules/overseerr/[id].tsx
+++ b/src/pages/api/modules/overseerr/[id].tsx
@@ -2,17 +2,16 @@ import { NextApiRequest, NextApiResponse } from 'next';
import { getCookie } from 'cookies-next';
import axios from 'axios';
import Consola from 'consola';
-import { getConfig } from '../../../../tools/getConfig';
-import { Config } from '../../../../tools/types';
-import { MediaType } from '../../../../modules/overseerr/SearchResult';
+import { getConfig } from '../../../../tools/config/getConfig';
+import type { MediaType } from '../../../../modules/overseerr/SearchResult';
async function Get(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
const { id, type } = req.query as { id: string; type: string };
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const service = config.services.find(
- (service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
+ const config = getConfig(configName?.toString() ?? 'default');
+ const app = config.apps.find(
+ (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
);
if (!id) {
return res.status(400).json({ error: 'No id provided' });
@@ -20,18 +19,19 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
if (!type) {
return res.status(400).json({ error: 'No type provided' });
}
- if (!service?.apiKey) {
- return res.status(400).json({ error: 'No service found' });
+ const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ return res.status(400).json({ error: 'No apps found' });
}
- const serviceUrl = new URL(service.url);
+ const appUrl = new URL(app.url);
switch (type) {
case 'movie':
return axios
- .get(`${serviceUrl.origin}/api/v1/movie/${id}`, {
+ .get(`${appUrl.origin}/api/v1/movie/${id}`, {
headers: {
// Set X-Api-Key to the value of the API key
- 'X-Api-Key': service.apiKey,
+ 'X-Api-Key': apiKey,
},
})
.then((axiosres) => res.status(200).json(axiosres.data))
@@ -45,10 +45,10 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
case 'tv':
// Make request to the tv api
return axios
- .get(`${serviceUrl.origin}/api/v1/tv/${id}`, {
+ .get(`${appUrl.origin}/api/v1/tv/${id}`, {
headers: {
// Set X-Api-Key to the value of the API key
- 'X-Api-Key': service.apiKey,
+ 'X-Api-Key': apiKey,
},
})
.then((axiosres) => res.status(200).json(axiosres.data))
@@ -71,9 +71,9 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.query as { id: string };
const { seasons, type } = req.body as { seasons?: number[]; type: MediaType };
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const service = config.services.find(
- (service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
+ const config = getConfig(configName?.toString() ?? 'default');
+ const app = config.apps.find(
+ (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
);
if (!id) {
return res.status(400).json({ error: 'No id provided' });
@@ -81,13 +81,15 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
if (!type) {
return res.status(400).json({ error: 'No type provided' });
}
- if (!service?.apiKey) {
- return res.status(400).json({ error: 'No service found' });
+
+ const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ return res.status(400).json({ error: 'No app found' });
}
if (type === 'movie' && !seasons) {
return res.status(400).json({ error: 'No seasons provided' });
}
- const serviceUrl = new URL(service.url);
+ const appUrl = new URL(app.url);
Consola.info('Got an Overseerr request with these arguments', {
mediaType: type,
mediaId: id,
@@ -95,7 +97,7 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
});
return axios
.post(
- `${serviceUrl.origin}/api/v1/request`,
+ `${appUrl.origin}/api/v1/request`,
{
mediaType: type,
mediaId: Number(id),
@@ -104,7 +106,7 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
{
headers: {
// Set X-Api-Key to the value of the API key
- 'X-Api-Key': service.apiKey,
+ 'X-Api-Key': apiKey,
},
}
)
diff --git a/src/pages/api/modules/overseerr/index.ts b/src/pages/api/modules/overseerr/index.ts
index e45603453..321cf9afa 100644
--- a/src/pages/api/modules/overseerr/index.ts
+++ b/src/pages/api/modules/overseerr/index.ts
@@ -1,31 +1,32 @@
import axios from 'axios';
import { getCookie } from 'cookies-next';
import { NextApiRequest, NextApiResponse } from 'next';
-import { getConfig } from '../../../../tools/getConfig';
-import { Config } from '../../../../tools/types';
+import { getConfig } from '../../../../tools/config/getConfig';
async function Get(req: NextApiRequest, res: NextApiResponse) {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
+ const config = getConfig(configName?.toString() ?? 'default');
const { query } = req.query;
- const service = config.services.find(
- (service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
+ const app = config.apps.find(
+ (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
);
// If query is an empty string, return an empty array
if (query === '' || query === undefined) {
return res.status(200).json([]);
}
- if (!service || !query || service === undefined || !service.apiKey) {
+
+ const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!app || !query || !apiKey) {
return res.status(400).json({
error: 'Wrong request',
});
}
- const serviceUrl = new URL(service.url);
+ const appUrl = new URL(app.url);
const data = await axios
- .get(`${serviceUrl.origin}/api/v1/search?query=${query}`, {
+ .get(`${appUrl.origin}/api/v1/search?query=${query}`, {
headers: {
// Set X-Api-Key to the value of the API key
- 'X-Api-Key': service.apiKey,
+ 'X-Api-Key': apiKey,
},
})
.then((res) => res.data);
diff --git a/src/pages/api/modules/ping.ts b/src/pages/api/modules/ping.ts
index de5dcc79d..270ed49f8 100644
--- a/src/pages/api/modules/ping.ts
+++ b/src/pages/api/modules/ping.ts
@@ -3,19 +3,21 @@ import https from 'https';
import { NextApiRequest, NextApiResponse } from 'next';
async function Get(req: NextApiRequest, res: NextApiResponse) {
- // Parse req.body as a ServiceItem
+ // Parse req.body as a AppItem
const { url } = req.query;
const agent = new https.Agent({ rejectUnauthorized: false });
await axios
- .get(url as string, { httpsAgent: agent })
+ .get(url as string, { httpsAgent: agent, timeout: 2000 })
.then((response) => {
res.status(response.status).json(response.statusText);
})
.catch((error) => {
if (error.response) {
res.status(error.response.status).json(error.response.statusText);
+ } else if (error.code === 'ECONNABORTED') {
+ res.status(408).json('Request Timeout');
} else {
- res.status(500).json('Server Error');
+ res.status(error.response ? error.response.status : 500).json('Server Error');
}
});
// // Make a request to the URL
diff --git a/src/pages/api/modules/search.ts b/src/pages/api/modules/search.ts
deleted file mode 100644
index e5dfd1309..000000000
--- a/src/pages/api/modules/search.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import axios from 'axios';
-import { NextApiRequest, NextApiResponse } from 'next';
-
-async function Get(req: NextApiRequest, res: NextApiResponse) {
- const { q } = req.query;
- const response = await axios.get(`https://duckduckgo.com/ac/?q=${q}`);
- res.status(200).json(response.data);
-}
-
-export default async (req: NextApiRequest, res: NextApiResponse) => {
- // Filter out if the reuqest is a POST or a GET
- if (req.method === 'GET') {
- return Get(req, res);
- }
- return res.status(405).json({
- statusCode: 405,
- message: 'Method not allowed',
- });
-};
diff --git a/src/pages/api/modules/torrents.ts b/src/pages/api/modules/torrents.ts
index 736f8f672..81600cace 100644
--- a/src/pages/api/modules/torrents.ts
+++ b/src/pages/api/modules/torrents.ts
@@ -1,65 +1,76 @@
+import Consola from 'consola';
import { Deluge } from '@ctrl/deluge';
import { QBittorrent } from '@ctrl/qbittorrent';
import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { Transmission } from '@ctrl/transmission';
import { getCookie } from 'cookies-next';
import { NextApiRequest, NextApiResponse } from 'next';
-import { getConfig } from '../../../tools/getConfig';
-import { Config } from '../../../tools/types';
+import { getConfig } from '../../../tools/config/getConfig';
async function Post(req: NextApiRequest, res: NextApiResponse) {
- // Get the type of service from the request url
+ // Get the type of app from the request url
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const qBittorrentServices = config.services.filter((service) => service.type === 'qBittorrent');
- const delugeServices = config.services.filter((service) => service.type === 'Deluge');
- const transmissionServices = config.services.filter((service) => service.type === 'Transmission');
+ const config = getConfig(configName?.toString() ?? 'default');
+ const qBittorrentApp = config.apps.filter((app) => app.integration?.type === 'qBittorrent');
+ const delugeApp = config.apps.filter((app) => app.integration?.type === 'deluge');
+ const transmissionApp = config.apps.filter((app) => app.integration?.type === 'transmission');
const torrents: NormalizedTorrent[] = [];
- if (!qBittorrentServices && !delugeServices && !transmissionServices) {
+ if (!qBittorrentApp && !delugeApp && !transmissionApp) {
return res.status(500).json({
statusCode: 500,
- message: 'Missing services',
+ message: 'Missing apps',
});
}
+
try {
await Promise.all(
- qBittorrentServices.map((service) =>
+ qBittorrentApp.map((app) =>
new QBittorrent({
- baseUrl: service.url,
- username: service.username,
- password: service.password,
+ baseUrl: app.url,
+ username:
+ app.integration!.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ password:
+ app.integration!.properties.find((x) => x.field === 'password')?.value ?? undefined,
})
.getAllData()
.then((e) => torrents.push(...e.torrents))
)
);
await Promise.all(
- delugeServices.map((service) =>
- new Deluge({
- baseUrl: service.url,
- password: 'password' in service ? service.password : '',
+ delugeApp.map((app) => {
+ const password =
+ app.integration?.properties.find((x) => x.field === 'password')?.value ?? undefined;
+ const test = new Deluge({
+ baseUrl: app.url,
+ password,
})
.getAllData()
- .then((e) => torrents.push(...e.torrents))
- )
+ .then((e) => torrents.push(...e.torrents));
+ return test;
+ })
);
- // Map transmissionServices
+ // Map transmissionApps
await Promise.all(
- transmissionServices.map((service) =>
+ transmissionApp.map((app) =>
new Transmission({
- baseUrl: service.url,
- username: 'username' in service ? service.username : '',
- password: 'password' in service ? service.password : '',
+ baseUrl: app.url,
+ username:
+ app.integration!.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ password:
+ app.integration!.properties.find((x) => x.field === 'password')?.value ?? undefined,
})
.getAllData()
.then((e) => torrents.push(...e.torrents))
)
);
} catch (e: any) {
+ Consola.error('Error while communicating with your torrent applications:\n', e);
return res.status(401).json(e);
}
+
+ Consola.debug(`Retrieved ${torrents.length} from all download clients`);
return res.status(200).json(torrents);
}
diff --git a/src/pages/api/modules/usenet/history.ts b/src/pages/api/modules/usenet/history.ts
index 16a24f30f..7399d29b9 100644
--- a/src/pages/api/modules/usenet/history.ts
+++ b/src/pages/api/modules/usenet/history.ts
@@ -3,17 +3,15 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
-import { UsenetHistoryItem } from '../../../../modules';
-import { getConfig } from '../../../../tools/getConfig';
-import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
-import { Config } from '../../../../tools/types';
import { NzbgetHistoryItem } from './nzbget/types';
import { NzbgetClient } from './nzbget/nzbget-client';
+import { getConfig } from '../../../../tools/config/getConfig';
+import { UsenetHistoryItem } from '../../../../widgets/useNet/types';
dayjs.extend(duration);
export interface UsenetHistoryRequestParams {
- serviceId: string;
+ appId: string;
offset: number;
limit: number;
}
@@ -26,24 +24,24 @@ export interface UsenetHistoryResponse {
async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const { limit, offset, serviceId } = req.query as any as UsenetHistoryRequestParams;
+ const config = getConfig(configName?.toString() ?? 'default');
+ const { limit, offset, appId } = req.query as any as UsenetHistoryRequestParams;
- const service = getServiceById(config, serviceId);
+ const app = config.apps.find((x) => x.id === appId);
- if (!service) {
- throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
+ if (!app) {
+ throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetHistoryResponse;
- switch (service.type) {
- case 'NZBGet': {
- const url = new URL(service.url);
+ switch (app.integration?.type) {
+ case 'nzbGet': {
+ const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
- login: service.username,
- hash: service.password,
+ login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -76,14 +74,15 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
};
break;
}
- case 'Sabnzbd': {
- const { origin } = new URL(service.url);
+ case 'sabnzbd': {
+ const { origin } = new URL(app.url);
- if (!service.apiKey) {
- throw new Error(`API Key for service "${service.name}" is missing`);
+ const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ throw new Error(`API Key for app "${app.name}" is missing`);
}
- const history = await new Client(origin, service.apiKey).history(offset, limit);
+ const history = await new Client(origin, apiKey).history(offset, limit);
const items: UsenetHistoryItem[] = history.slots.map((slot) => ({
id: slot.nzo_id,
@@ -99,7 +98,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
- throw new Error(`Service type "${service.type}" unrecognized.`);
+ throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);
diff --git a/src/pages/api/modules/usenet/index.ts b/src/pages/api/modules/usenet/index.ts
index 0945ab86d..cd99c8ac9 100644
--- a/src/pages/api/modules/usenet/index.ts
+++ b/src/pages/api/modules/usenet/index.ts
@@ -3,16 +3,14 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
-import { getConfig } from '../../../../tools/getConfig';
-import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
-import { Config } from '../../../../tools/types';
-import { NzbgetStatus } from './nzbget/types';
+import { getConfig } from '../../../../tools/config/getConfig';
import { NzbgetClient } from './nzbget/nzbget-client';
+import { NzbgetStatus } from './nzbget/types';
dayjs.extend(duration);
export interface UsenetInfoRequestParams {
- serviceId: string;
+ appId: string;
}
export interface UsenetInfoResponse {
@@ -25,24 +23,24 @@ export interface UsenetInfoResponse {
async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const { serviceId } = req.query as any as UsenetInfoRequestParams;
+ const config = getConfig(configName?.toString() ?? 'default');
+ const { appId } = req.query as any as UsenetInfoRequestParams;
- const service = getServiceById(config, serviceId);
+ const app = config.apps.find((x) => x.id === appId);
- if (!service) {
- throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
+ if (!app) {
+ throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetInfoResponse;
- switch (service.type) {
- case 'NZBGet': {
- const url = new URL(service.url);
+ switch (app.integration?.type) {
+ case 'nzbGet': {
+ const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
- login: service.username,
- hash: service.password,
+ login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -71,14 +69,15 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
};
break;
}
- case 'Sabnzbd': {
- if (!service.apiKey) {
- throw new Error(`API Key for service "${service.name}" is missing`);
+ case 'sabnzbd': {
+ const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ throw new Error(`API Key for app "${app.name}" is missing`);
}
- const { origin } = new URL(service.url);
+ const { origin } = new URL(app.url);
- const queue = await new Client(origin, service.apiKey).queue(0, -1);
+ const queue = await new Client(origin, apiKey).queue(0, -1);
const [hours, minutes, seconds] = queue.timeleft.split(':');
const eta = dayjs.duration({
@@ -96,7 +95,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
- throw new Error(`Service type "${service.type}" unrecognized.`);
+ throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);
diff --git a/src/pages/api/modules/usenet/nzbget/nzbget-client.ts b/src/pages/api/modules/usenet/nzbget/nzbget-client.ts
index 1fd068dc4..ef614dd57 100644
--- a/src/pages/api/modules/usenet/nzbget/nzbget-client.ts
+++ b/src/pages/api/modules/usenet/nzbget/nzbget-client.ts
@@ -3,19 +3,19 @@ import { NzbgetClientOptions } from './types';
export function NzbgetClient(options: NzbgetClientOptions) {
if (!options?.host) {
- throw new Error('Cannot connect to NZBGet. Missing host in service config.');
+ throw new Error('Cannot connect to NZBGet. Missing host in app config.');
}
if (!options?.port) {
- throw new Error('Cannot connect to NZBGet. Missing port in service config.');
+ throw new Error('Cannot connect to NZBGet. Missing port in app config.');
}
if (!options?.login) {
- throw new Error('Cannot connect to NZBGet. Missing username in service config.');
+ throw new Error('Cannot connect to NZBGet. Missing username in app config.');
}
if (!options?.hash) {
- throw new Error('Cannot connect to NZBGet. Missing password in service config.');
+ throw new Error('Cannot connect to NZBGet. Missing password in app config.');
}
return new NZBGet(options);
diff --git a/src/pages/api/modules/usenet/pause.ts b/src/pages/api/modules/usenet/pause.ts
index 9ce84015d..534c71055 100644
--- a/src/pages/api/modules/usenet/pause.ts
+++ b/src/pages/api/modules/usenet/pause.ts
@@ -3,38 +3,36 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
-import { getConfig } from '../../../../tools/getConfig';
-import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
-import { Config } from '../../../../tools/types';
+import { getConfig } from '../../../../tools/config/getConfig';
import { NzbgetClient } from './nzbget/nzbget-client';
dayjs.extend(duration);
export interface UsenetPauseRequestParams {
- serviceId: string;
+ appId: string;
}
async function Post(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const { serviceId } = req.query as any as UsenetPauseRequestParams;
+ const config = getConfig(configName?.toString() ?? 'default');
+ const { appId } = req.query as any as UsenetPauseRequestParams;
- const service = getServiceById(config, serviceId);
+ const app = config.apps.find((x) => x.id === appId);
- if (!service) {
- throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
+ if (!app) {
+ throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let result;
- switch (service.type) {
- case 'NZBGet': {
- const url = new URL(service.url);
+ switch (app.integration?.type) {
+ case 'nzbGet': {
+ const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
- login: service.username,
- hash: service.password,
+ login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -50,18 +48,19 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
});
break;
}
- case 'Sabnzbd': {
- if (!service.apiKey) {
- throw new Error(`API Key for service "${service.name}" is missing`);
+ case 'sabnzbd': {
+ const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ throw new Error(`API Key for app "${app.name}" is missing`);
}
- const { origin } = new URL(service.url);
+ const { origin } = new URL(app.url);
- result = await new Client(origin, service.apiKey).queuePause();
+ result = await new Client(origin, apiKey).queuePause();
break;
}
default:
- throw new Error(`Service type "${service.type}" unrecognized.`);
+ throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(result);
diff --git a/src/pages/api/modules/usenet/queue.ts b/src/pages/api/modules/usenet/queue.ts
index 7d6041e14..c81a66b8a 100644
--- a/src/pages/api/modules/usenet/queue.ts
+++ b/src/pages/api/modules/usenet/queue.ts
@@ -3,17 +3,15 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
-import { UsenetQueueItem } from '../../../../modules';
-import { getConfig } from '../../../../tools/getConfig';
-import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
-import { Config } from '../../../../tools/types';
+import { getConfig } from '../../../../tools/config/getConfig';
+import { UsenetQueueItem } from '../../../../widgets/useNet/types';
import { NzbgetClient } from './nzbget/nzbget-client';
import { NzbgetQueueItem, NzbgetStatus } from './nzbget/types';
dayjs.extend(duration);
export interface UsenetQueueRequestParams {
- serviceId: string;
+ appId: string;
offset: number;
limit: number;
}
@@ -26,24 +24,24 @@ export interface UsenetQueueResponse {
async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const { limit, offset, serviceId } = req.query as any as UsenetQueueRequestParams;
+ const config = getConfig(configName?.toString() ?? 'default');
+ const { limit, offset, appId } = req.query as any as UsenetQueueRequestParams;
- const service = getServiceById(config, serviceId);
+ const app = config.apps.find((x) => x.id === appId);
- if (!service) {
- throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
+ if (!app) {
+ throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetQueueResponse;
- switch (service.type) {
- case 'NZBGet': {
- const url = new URL(service.url);
+ switch (app.integration?.type) {
+ case 'nzbGet': {
+ const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
- login: service.username,
- hash: service.password,
+ login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -92,13 +90,14 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
};
break;
}
- case 'Sabnzbd': {
- if (!service.apiKey) {
- throw new Error(`API Key for service "${service.name}" is missing`);
+ case 'sabnzbd': {
+ const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ throw new Error(`API Key for app "${app.name}" is missing`);
}
- const { origin } = new URL(service.url);
- const queue = await new Client(origin, service.apiKey).queue(offset, limit);
+ const { origin } = new URL(app.url);
+ const queue = await new Client(origin, apiKey).queue(offset, limit);
const items: UsenetQueueItem[] = queue.slots.map((slot) => {
const [hours, minutes, seconds] = slot.timeleft.split(':');
@@ -125,7 +124,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
- throw new Error(`Service type "${service.type}" unrecognized.`);
+ throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);
diff --git a/src/pages/api/modules/usenet/resume.ts b/src/pages/api/modules/usenet/resume.ts
index 444f00d34..90675d2ed 100644
--- a/src/pages/api/modules/usenet/resume.ts
+++ b/src/pages/api/modules/usenet/resume.ts
@@ -3,39 +3,37 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
-import { getConfig } from '../../../../tools/getConfig';
-import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
-import { Config } from '../../../../tools/types';
+import { getConfig } from '../../../../tools/config/getConfig';
import { NzbgetClient } from './nzbget/nzbget-client';
dayjs.extend(duration);
export interface UsenetResumeRequestParams {
- serviceId: string;
+ appId: string;
nzbId?: string;
}
async function Post(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
- const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
- const { serviceId } = req.query as any as UsenetResumeRequestParams;
+ const config = getConfig(configName?.toString() ?? 'default');
+ const { appId } = req.query as any as UsenetResumeRequestParams;
- const service = getServiceById(config, serviceId);
+ const app = config.apps.find((x) => x.id === appId);
- if (!service) {
- throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
+ if (!app) {
+ throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let result;
- switch (service.type) {
- case 'NZBGet': {
- const url = new URL(service.url);
+ switch (app.integration?.type) {
+ case 'nzbGet': {
+ const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
- login: service.username,
- hash: service.password,
+ login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
+ hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -51,18 +49,19 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
});
break;
}
- case 'Sabnzbd': {
- if (!service.apiKey) {
- throw new Error(`API Key for service "${service.name}" is missing`);
+ case 'sabnzbd': {
+ const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
+ if (!apiKey) {
+ throw new Error(`API Key for app "${app.name}" is missing`);
}
- const { origin } = new URL(service.url);
+ const { origin } = new URL(app.url);
- result = await new Client(origin, service.apiKey).queueResume();
+ result = await new Client(origin, apiKey).queueResume();
break;
}
default:
- throw new Error(`Service type "${service.type}" unrecognized.`);
+ throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(result);
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 4e57a6ae9..dd416158b 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,25 +1,41 @@
import { getCookie, setCookie } from 'cookies-next';
import { GetServerSidePropsContext } from 'next';
-import { useEffect } from 'react';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import AppShelf from '../components/AppShelf/AppShelf';
-import LoadConfigComponent from '../components/Config/LoadConfig';
-import { Config } from '../tools/types';
-import { useConfig } from '../tools/state';
-import { migrateToIdConfig } from '../tools/migrate';
-import { getConfig } from '../tools/getConfig';
-import { useColorTheme } from '../tools/color';
+import fs from 'fs';
+import { Dashboard } from '../components/Dashboard/Dashboard';
import Layout from '../components/layout/Layout';
+import { useInitConfig } from '../config/init';
+import { getFrontendConfig } from '../tools/config/getFrontendConfig';
+import { getServerSideTranslations } from '../tools/getServerSideTranslations';
import { dashboardNamespaces } from '../tools/translation-namespaces';
+import { DashboardServerSideProps } from '../types/dashboardPageType';
+import { LoadConfigComponent } from '../components/Config/LoadConfig';
export async function getServerSideProps({
req,
res,
locale,
-}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
+}: GetServerSidePropsContext): Promise<{ props: DashboardServerSideProps }> {
+ // Get all the configs in the /data/configs folder
+ // All the files that end in ".json"
+ const configs = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
+
+ if (
+ !configs.every(
+ (config) => JSON.parse(fs.readFileSync(`./data/configs/${config}`, 'utf8')).schemaVersion
+ )
+ ) {
+ // Replace the current page with the migrate page but don't redirect
+ // This is to prevent the user from seeing the redirect
+ res.writeHead(302, {
+ Location: '/migrate',
+ });
+ res.end();
+
+ return { props: {} as DashboardServerSideProps };
+ }
+
let configName = getCookie('config-name', { req, res });
- const configLocale = getCookie('config-locale', { req, res });
if (!configName) {
setCookie('config-name', 'default', {
req,
@@ -30,26 +46,21 @@ export async function getServerSideProps({
configName = 'default';
}
- const translations = await serverSideTranslations(
- (configLocale ?? locale) as string,
- dashboardNamespaces
- );
- return getConfig(configName as string, translations);
+ const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale);
+
+ const config = getFrontendConfig(configName as string);
+
+ return {
+ props: { configName: configName as string, config, ...translations },
+ };
}
-export default function HomePage(props: any) {
- const { config: initialConfig }: { config: Config } = props;
- const { setConfig } = useConfig();
- const { setPrimaryColor, setSecondaryColor } = useColorTheme();
- useEffect(() => {
- const migratedConfig = migrateToIdConfig(initialConfig);
- setPrimaryColor(migratedConfig.settings.primaryColor || 'red');
- setSecondaryColor(migratedConfig.settings.secondaryColor || 'orange');
- setConfig(migratedConfig);
- }, [initialConfig]);
+export default function HomePage({ config: initialConfig }: DashboardServerSideProps) {
+ useInitConfig(initialConfig);
+
return (
-
+
);
diff --git a/src/pages/login.tsx b/src/pages/login.tsx
index ef3eda1bd..7587bbde0 100644
--- a/src/pages/login.tsx
+++ b/src/pages/login.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { PasswordInput, Paper, Title, Text, Container, Group, Button } from '@mantine/core';
+import { PasswordInput, Paper, Title, Text, Container, Button } from '@mantine/core';
import { setCookie } from 'cookies-next';
import { showNotification, updateNotification } from '@mantine/notifications';
import axios from 'axios';
@@ -32,19 +32,6 @@ export default function AuthenticationTitle() {
justifyContent: 'center',
}}
>
-
- ({ fontFamily: `Greycliff CF, ${theme.fontFamily}`, fontWeight: 900 })}
- >
- {t('title')}
-
-
-
-
- {t('text')}
-
-
+ ({ fontFamily: `Greycliff CF, ${theme.fontFamily}`, fontWeight: 900 })}
+ >
+ {t('title')}
+
+
+
+ {t('text')}
+
{
setCookie('password', values.password, {
diff --git a/src/pages/migrate.tsx b/src/pages/migrate.tsx
new file mode 100644
index 000000000..428e7e564
--- /dev/null
+++ b/src/pages/migrate.tsx
@@ -0,0 +1,358 @@
+import fs from 'fs';
+import { GetServerSidePropsContext } from 'next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import React, { useEffect, useState } from 'react';
+
+import {
+ Alert,
+ Anchor,
+ AppShell,
+ Badge,
+ Box,
+ Button,
+ Container,
+ createStyles,
+ Group,
+ Header,
+ List,
+ Loader,
+ Paper,
+ Progress,
+ Space,
+ Stack,
+ Stepper,
+ Switch,
+ Text,
+ ThemeIcon,
+ Title,
+ useMantineColorScheme,
+ useMantineTheme,
+} from '@mantine/core';
+import {
+ IconAlertCircle,
+ IconBrandDiscord,
+ IconCheck,
+ IconCircleCheck,
+ IconMoonStars,
+ IconSun,
+} from '@tabler/icons';
+import { motion } from 'framer-motion';
+import axios from 'axios';
+import { Logo } from '../components/layout/Logo';
+import { usePrimaryGradient } from '../components/layout/useGradient';
+
+const useStyles = createStyles((theme) => ({
+ root: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,
+ },
+
+ label: {
+ textAlign: 'center',
+ color: theme.colors[theme.primaryColor][8],
+ fontWeight: 900,
+ fontSize: 110,
+ lineHeight: 1,
+ marginBottom: theme.spacing.xl * 1.5,
+
+ [theme.fn.smallerThan('sm')]: {
+ fontSize: 60,
+ },
+ },
+
+ title: {
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ textAlign: 'center',
+ fontWeight: 900,
+ fontSize: 38,
+
+ [theme.fn.smallerThan('sm')]: {
+ fontSize: 32,
+ },
+ },
+
+ card: {
+ position: 'relative',
+ overflow: 'visible',
+ padding: theme.spacing.xl,
+ },
+
+ icon: {
+ position: 'absolute',
+ top: -ICON_SIZE / 3,
+ left: `calc(50% - ${ICON_SIZE / 2}px)`,
+ },
+
+ description: {
+ maxWidth: 700,
+ margin: 'auto',
+ marginTop: theme.spacing.xl,
+ marginBottom: theme.spacing.xl * 1.5,
+ },
+}));
+
+export default function ServerError({ configs }: { configs: any }) {
+ const { classes } = useStyles();
+ const [active, setActive] = React.useState(0);
+ const gradient = usePrimaryGradient();
+ const [progress, setProgress] = React.useState(0);
+ const [isUpgrading, setIsUpgrading] = React.useState(false);
+ const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
+ const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
+
+ return (
+ ({
+ main: {
+ backgroundColor:
+ theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
+ },
+ })}
+ >
+
+
+
+
+
+
+ {/* Header content */}
+
+ }
+ styles={(theme) => ({
+ main: {
+ backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor })
+ .background,
+ },
+ })}
+ >
+
+
+
+
+
+ Homarr v0.11
+
+
+
+
+ {active === 0 && "Good to see you back! Let's get started"}
+ {active === 1 && progress !== 100 && 'Migrating your configs'}
+ {active === 1 && progress === 100 && 'Migration complete!'}
+
+
+
+
+
+ A few things have changed since the last time you used Homarr. We'll
+ help you migrate your old configuration to the new format. This process is automatic
+ and should take less than a minute. Then, you'll be able to use the new
+ features of Homarr!
+
+ }
+ title="Please make a backup of your configs!"
+ color="red"
+ radius="md"
+ variant="outline"
+ >
+ Please make sure to have a backup of your configs in case something goes wrong.{' '}
+ Not all settings can be migrated , so you'll have to re-do some
+ configuration yourself.
+
+
+ }
+ label="Step 2"
+ description="Migrating your configs"
+ >
+
+
+
+
+ Homarr v0.11 brings a lot of new features, if you are interested in learning
+ about them, please check out the{' '}
+
+ documentation page
+
+
+
+
+
+ That's it ! We hope you enjoy the new flexibility v0.11 brings. If you spot any
+ bugs make sure to report them as a{' '}
+
+ github issue
+ {' '}
+ or directly on the
+
+
+
+ discord !
+
+
+
+
+
+
+ }
+ onClick={active === 3 ? () => window.location.reload() : nextStep}
+ variant="filled"
+ disabled={active === 1 && progress < 100}
+ >
+ {active === 3 ? 'Finish' : 'Next'}
+
+
+
+
+
+ );
+}
+
+function SwitchToggle() {
+ const { colorScheme, toggleColorScheme } = useMantineColorScheme();
+ const theme = useMantineTheme();
+
+ return (
+ toggleColorScheme()}
+ size="lg"
+ onLabel={ }
+ offLabel={ }
+ />
+ );
+}
+
+export async function getServerSideProps({ req, res, locale }: GetServerSidePropsContext) {
+ // Get all the configs in the /data/configs folder
+ // All the files that end in ".json"
+ const configs = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
+
+ if (configs.length === 0) {
+ res.writeHead(302, {
+ Location: '/',
+ });
+ res.end();
+ return { props: {} };
+ }
+ // If all the configs are migrated (contains a schemaVersion), redirect to the index
+ if (
+ configs.every(
+ (config) => JSON.parse(fs.readFileSync(`./data/configs/${config}`, 'utf8')).schemaVersion
+ )
+ ) {
+ res.writeHead(302, {
+ Location: '/',
+ });
+ res.end();
+ return {
+ processed: true,
+ };
+ }
+ return {
+ props: {
+ configs: configs.map(
+ // Get all the file names in ./data/configs
+ (config) => config.replace('.json', '')
+ ),
+ ...(await serverSideTranslations(locale!, [])),
+ // Will be passed to the page component as props
+ },
+ };
+}
+
+const ICON_SIZE = 60;
+
+export function StatsCard({
+ configs,
+ progress,
+ setProgress,
+}: {
+ configs: string[];
+ progress: number;
+ setProgress: (progress: number) => void;
+}) {
+ const { classes } = useStyles();
+ const numberOfConfigs = configs.length;
+ // Update the progress every 100ms
+ const [treatedConfigs, setTreatedConfigs] = useState([]);
+ // Stop the progress at 100%
+ useEffect(() => {
+ const data = axios.post('/api/migrate').then((response) => {
+ setProgress(100);
+ });
+
+ const interval = setInterval(() => {
+ if (configs.length === 0) {
+ clearInterval(interval);
+ setProgress(100);
+ return;
+ }
+ // Add last element of configs to the treatedConfigs array
+ setTreatedConfigs((treatedConfigs) => [...treatedConfigs, configs[configs.length - 1]]);
+ // Remove last element of configs
+ configs.pop();
+ }, 500);
+ return () => clearInterval(interval);
+ }, [configs]);
+
+ return (
+
+
+
+ Progress
+
+
+ {(100 / (numberOfConfigs + 1)).toFixed(1)}%
+
+
+
+
+
+
+
+ }
+ >
+ {configs.map((config, index) => (
+ }>
+ {config ?? 'Unknown'}
+
+ ))}
+ {treatedConfigs.map((config, index) => (
+ {config ?? 'Unknown'}
+ ))}
+
+
+
+
+
+ {configs.length} configs left
+
+
+ );
+}
diff --git a/src/styles/global.scss b/src/styles/global.scss
new file mode 100644
index 000000000..d99f2f6b6
--- /dev/null
+++ b/src/styles/global.scss
@@ -0,0 +1,94 @@
+@import 'fily-publish-gridstack/dist/gridstack.min.css';
+
+:root {
+ --gridstack-widget-width: 64;
+ --gridstack-column-count: 12;
+}
+
+.grid-stack-placeholder > .placeholder-content {
+ background-color: rgb(248, 249, 250) !important;
+ border-radius: 12px;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+}
+
+@media (prefers-color-scheme: dark) {
+ .grid-stack-placeholder > .placeholder-content {
+ background-color: rgba(255, 255, 255, 0.05) !important;
+ }
+}
+
+// Styling for grid-stack main area
+@for $i from 1 to 13 {
+ .grid-stack>.grid-stack-item[gs-w="#{$i}"] { width: calc(100% / #{var(--gridstack-column-count)} * #{$i}) }
+ .grid-stack>.grid-stack-item[gs-min-w="#{$i}"] { min-width: calc(100% / #{var(--gridstack-column-count)} * #{$i}) }
+ .grid-stack>.grid-stack-item[gs-max-w="#{$i}"] { max-width: calc(100% / #{var(--gridstack-column-count)} * #{$i}) }
+}
+
+@for $i from 1 to 96 {
+ .grid-stack>.grid-stack-item[gs-h="#{$i}"] { height: calc(#{$i}px * #{var(--gridstack-widget-width)}) }
+ .grid-stack>.grid-stack-item[gs-min-h="#{$i}"] { min-height: calc(#{$i}px * #{var(--gridstack-widget-width)}) }
+ .grid-stack>.grid-stack-item[gs-max-h="#{$i}"] { max-height: calc(#{$i}px * #{var(--gridstack-widget-width)}) }
+}
+
+@for $i from 1 to 13 {
+ .grid-stack>.grid-stack-item[gs-x="#{$i}"] { left: calc(100% / #{var(--gridstack-column-count)} * #{$i}) }
+}
+
+
+@for $i from 1 to 96 {
+ .grid-stack>.grid-stack-item[gs-y="#{$i}"] { top: calc(#{$i}px * #{var(--gridstack-widget-width)}) }
+}
+
+.grid-stack>.grid-stack-item {
+ min-width: calc(percentage(1) * #{var(--gridstack-widget-width)});
+}
+
+// Styling for sidebar grid-stack elements
+@for $i from 1 to 3 {
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-w="#{$i}"] { width: 128px * $i }
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-min-w="#{$i}"] { min-width: 128px * $i }
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-max-w="#{$i}"] { max-width: 128px * $i }
+}
+
+@for $i from 1 to 96 {
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-h="#{$i}"] { height: 128px * $i }
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-min-h="#{$i}"] { min-height: 128px * $i }
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-max-h="#{$i}"] { max-height: 128px * $i }
+}
+
+@for $i from 1 to 13 {
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-x="#{$i}"] { left: 128px * $i }
+}
+
+
+@for $i from 1 to 96 {
+ .grid-stack.grid-stack-sidebar>.grid-stack-item[gs-y="#{$i}"] { top: 128px * $i }
+}
+
+.grid-stack.grid-stack-sidebar>.grid-stack-item {
+ min-width: 128px;
+}
+
+// General gridstack styling
+.grid-stack>.grid-stack-item>.grid-stack-item-content,
+.grid-stack>.grid-stack-item>.placeholder-content {
+ inset: 10px;
+}
+
+.grid-stack>.grid-stack-item>.ui-resizable-se {
+ bottom: 10px;
+ right: 10px;
+}
+
+.grid-stack > .grid-stack-item > .grid-stack-item-content {
+ overflow-y: auto;
+}
+
+.grid-stack.grid-stack-animate {
+ transition: none;
+}
+
+.gridstack-empty-wrapper {
+ height: 0px;
+ min-height: 0px !important;
+}
\ No newline at end of file
diff --git a/src/tools/acceptableStatusCodes.ts b/src/tools/acceptableStatusCodes.ts
new file mode 100644
index 000000000..3d942fd8d
--- /dev/null
+++ b/src/tools/acceptableStatusCodes.ts
@@ -0,0 +1,21 @@
+export const StatusCodes = [
+ { value: 200, label: '200 - OK', group: 'Sucessful responses' },
+ { value: 204, label: '204 - No Content', group: 'Sucessful responses' },
+ { value: 301, label: '301 - Moved Permanently', group: 'Redirection responses' },
+ { value: 302, label: '302 - Found / Moved Temporarily', group: 'Redirection responses' },
+ { value: 304, label: '304 - Not Modified', group: 'Redirection responses' },
+ { value: 307, label: '307 - Temporary Redirect', group: 'Redirection responses' },
+ { value: 308, label: '308 - Permanent Redirect', group: 'Redirection responses' },
+ { value: 400, label: '400 - Bad Request', group: 'Client error responses' },
+ { value: 401, label: '401 - Unauthorized', group: 'Client error responses' },
+ { value: 403, label: '403 - Forbidden', group: 'Client error responses' },
+ { value: 404, label: '404 - Not Found', group: 'Client error responses' },
+ { value: 405, label: '405 - Method Not Allowed', group: 'Client error responses' },
+ { value: 408, label: '408 - Request Timeout', group: 'Client error responses' },
+ { value: 410, label: '410 - Gone', group: 'Client error responses' },
+ { value: 429, label: '429 - Too Many Requests', group: 'Client error responses' },
+ { value: 500, label: '500 - Internal Server Error', group: 'Server error responses' },
+ { value: 502, label: '502 - Bad Gateway', group: 'Server error responses' },
+ { value: 503, label: '503 - Service Unavailable', group: 'Server error responses' },
+ { value: 504, label: '504 - Gateway Timeout Error', group: 'Server error responses' },
+];
diff --git a/src/tools/addToHomarr.ts b/src/tools/addToHomarr.ts
index bd3cc24dc..5f97bf49d 100644
--- a/src/tools/addToHomarr.ts
+++ b/src/tools/addToHomarr.ts
@@ -35,23 +35,3 @@ export function tryMatchService(container: Dockerode.ContainerInfo | undefined)
.toLowerCase()}.png`,
};
}
-
-export default async function addToHomarr(
- container: Dockerode.ContainerInfo,
- config: Config,
- setConfig: (newconfig: Config) => void
-) {
- setConfig({
- ...config,
- services: [
- ...config.services,
- {
- name: container.Names[0].substring(1),
- id: container.Id,
- type: tryMatchType(container.Image),
- url: `localhost:${container.Ports.at(0)?.PublicPort}`,
- icon: await MatchIcon(container.Names[0].substring(1)),
- },
- ],
- });
-}
diff --git a/src/tools/bytesHelper.ts b/src/tools/bytesHelper.ts
new file mode 100644
index 000000000..bcbe882d0
--- /dev/null
+++ b/src/tools/bytesHelper.ts
@@ -0,0 +1,26 @@
+export const bytes = {
+ toPerSecondString: (bytes?: number) => {
+ if (!bytes) return '-';
+ for (let i = 0; i < 4; i++) {
+ if (bytes >= 1000 && i !== 3) {
+ bytes /= 1000;
+ continue;
+ }
+
+ return `${bytes.toFixed(1)} ${perSecondUnits[i]}`;
+ }
+ },
+ toString: (bytes: number) => {
+ for (let i = 0; i < 4; i++) {
+ if (bytes >= 1024 && i !== 3) {
+ bytes /= 1024;
+ continue;
+ }
+
+ return `${bytes.toFixed(1)} ${units[i]}`;
+ }
+ },
+};
+
+const perSecondUnits = ['b/s', 'Kb/s', 'Mb/s', 'Gb/s'];
+const units = ['B', 'KiB', 'MiB', 'GiB'];
diff --git a/src/tools/calculateEta.ts b/src/tools/calculateEta.ts
new file mode 100644
index 000000000..ab7c355ba
--- /dev/null
+++ b/src/tools/calculateEta.ts
@@ -0,0 +1,15 @@
+export const calculateETA = (givenSeconds: number) => {
+ // If its superior than one day return > 1 day
+ if (givenSeconds > 86400) {
+ return '> 1 day';
+ }
+ // Transform the givenSeconds into a readable format. e.g. 1h 2m 3s
+ const hours = Math.floor(givenSeconds / 3600);
+ const minutes = Math.floor((givenSeconds % 3600) / 60);
+ const seconds = Math.floor(givenSeconds % 60);
+ // Only show hours if it's greater than 0.
+ const hoursString = hours > 0 ? `${hours}h ` : '';
+ const minutesString = minutes > 0 ? `${minutes}m ` : '';
+ const secondsString = seconds > 0 ? `${seconds}s` : '';
+ return `${hoursString}${minutesString}${secondsString}`;
+};
diff --git a/src/tools/config/backendMigrateConfig.ts b/src/tools/config/backendMigrateConfig.ts
new file mode 100644
index 000000000..dc311e5e3
--- /dev/null
+++ b/src/tools/config/backendMigrateConfig.ts
@@ -0,0 +1,18 @@
+import fs from 'fs';
+import { ConfigType } from '../../types/config';
+import { Config } from '../types';
+import { migrateConfig } from './migrateConfig';
+
+export function backendMigrateConfig(config: Config, name: string): ConfigType {
+ const migratedConfig = migrateConfig(config);
+
+ // Make a backup of the old file ./data/configs/${name}.json
+ // New name is ./data/configs/${name}.bak
+ fs.copyFileSync(`./data/configs/${name}.json`, `./data/configs/${name}.json.bak`);
+
+ // Overrite the file ./data/configs/${name}.json
+ // with the new config format
+ fs.writeFileSync(`./data/configs/${name}.json`, JSON.stringify(migratedConfig, null, 2));
+
+ return migratedConfig;
+}
diff --git a/src/tools/config/configExists.ts b/src/tools/config/configExists.ts
new file mode 100644
index 000000000..f6c05b523
--- /dev/null
+++ b/src/tools/config/configExists.ts
@@ -0,0 +1,7 @@
+import fs from 'fs';
+import { generateConfigPath } from './generateConfigPath';
+
+export const configExists = (name: string) => {
+ const path = generateConfigPath(name);
+ return fs.existsSync(path);
+};
diff --git a/src/tools/config/generateConfigPath.ts b/src/tools/config/generateConfigPath.ts
new file mode 100644
index 000000000..c1f2339a7
--- /dev/null
+++ b/src/tools/config/generateConfigPath.ts
@@ -0,0 +1,4 @@
+import path from 'path';
+
+export const generateConfigPath = (configName: string) =>
+ path.join(process.cwd(), 'data/configs', `${configName}.json`);
diff --git a/src/tools/config/getConfig.ts b/src/tools/config/getConfig.ts
new file mode 100644
index 000000000..c17ee3881
--- /dev/null
+++ b/src/tools/config/getConfig.ts
@@ -0,0 +1,20 @@
+import Consola from 'consola';
+import { BackendConfigType, ConfigType } from '../../types/config';
+import { backendMigrateConfig } from './backendMigrateConfig';
+import { configExists } from './configExists';
+import { getFallbackConfig } from './getFallbackConfig';
+import { readConfig } from './readConfig';
+
+export const getConfig = (name: string): BackendConfigType => {
+ if (!configExists(name)) return getFallbackConfig() as unknown as ConfigType;
+ // Else if config exists but contains no "schema_version" property
+ // then it is an old config file and we should try to migrate it
+ // to the new format.
+ const config = readConfig(name);
+ if (config.schemaVersion === undefined) {
+ Consola.log('Migrating config file...', config);
+ return backendMigrateConfig(config, name);
+ }
+
+ return config;
+};
diff --git a/src/tools/config/getFallbackConfig.ts b/src/tools/config/getFallbackConfig.ts
new file mode 100644
index 000000000..f6fd248ae
--- /dev/null
+++ b/src/tools/config/getFallbackConfig.ts
@@ -0,0 +1,8 @@
+import defaultConfig from '../../../data/configs/default.json';
+
+export const getFallbackConfig = (name?: string) => ({
+ ...defaultConfig,
+ configProperties: {
+ name: name ?? 'default',
+ },
+});
diff --git a/src/tools/config/getFrontendConfig.ts b/src/tools/config/getFrontendConfig.ts
new file mode 100644
index 000000000..c968c9b28
--- /dev/null
+++ b/src/tools/config/getFrontendConfig.ts
@@ -0,0 +1,27 @@
+import Consola from 'consola';
+
+import { ConfigType } from '../../types/config';
+import { getConfig } from './getConfig';
+
+export const getFrontendConfig = (name: string): ConfigType => {
+ const config = getConfig(name);
+
+ Consola.info(`Requested frontend content of configuration '${name}'`);
+
+ return {
+ ...config,
+ apps: config.apps.map((app) => ({
+ ...app,
+ integration: {
+ ...(app.integration ?? null),
+ type: app.integration?.type ?? null,
+ properties:
+ app.integration?.properties.map((property) => ({
+ ...property,
+ value: property.type === 'private' ? null : property.value,
+ isDefined: property.value !== null,
+ })) ?? [],
+ },
+ })),
+ };
+};
diff --git a/src/tools/config/migrateConfig.ts b/src/tools/config/migrateConfig.ts
new file mode 100644
index 000000000..595635a72
--- /dev/null
+++ b/src/tools/config/migrateConfig.ts
@@ -0,0 +1,484 @@
+import Consola from 'consola';
+import { v4 as uuidv4 } from 'uuid';
+import { AppIntegrationType, AppType, IntegrationType } from '../../types/app';
+import { AreaType } from '../../types/area';
+import { CategoryType } from '../../types/category';
+import { ConfigType } from '../../types/config';
+import { SearchEngineCommonSettingsType } from '../../types/settings';
+import { ITorrent } from '../../widgets/torrent/TorrentTile';
+import { ICalendarWidget } from '../../widgets/calendar/CalendarTile';
+import { IDashDotTile } from '../../widgets/dashDot/DashDotTile';
+import { IDateWidget } from '../../widgets/date/DateTile';
+import { ITorrentNetworkTraffic } from '../../widgets/torrentNetworkTraffic/TorrentNetworkTrafficTile';
+import { IUsenetWidget } from '../../widgets/useNet/UseNetTile';
+import { IWeatherWidget } from '../../widgets/weather/WeatherTile';
+import { IWidget } from '../../widgets/widgets';
+import { Config, serviceItem } from '../types';
+
+export function migrateConfig(config: Config): ConfigType {
+ const newConfig: ConfigType = {
+ schemaVersion: 1,
+ configProperties: {
+ name: config.name ?? 'default',
+ },
+ categories: [],
+ widgets: migrateModules(config),
+ apps: [],
+ settings: {
+ common: {
+ searchEngine: migrateSearchEngine(config),
+ defaultConfig: 'default',
+ },
+ customization: {
+ colors: {
+ primary: config.settings.primaryColor ?? 'red',
+ secondary: config.settings.secondaryColor ?? 'orange',
+ shade: config.settings.primaryShade ?? 7,
+ },
+ layout: {
+ enabledDocker: config.modules.docker?.enabled ?? false,
+ enabledLeftSidebar: false,
+ enabledPing: config.modules.ping?.enabled ?? false,
+ enabledRightSidebar: false,
+ enabledSearchbar: config.modules.search?.enabled ?? true,
+ },
+ },
+ },
+ wrappers: [
+ {
+ id: 'default',
+ position: 1,
+ },
+ ],
+ };
+
+ config.services.forEach((service) => {
+ const { category: categoryName } = service;
+
+ if (!categoryName) {
+ newConfig.apps.push(
+ migrateService(service, {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ })
+ );
+ return;
+ }
+
+ const category = getConfigAndCreateIfNotExsists(newConfig, categoryName);
+
+ if (!category) {
+ return;
+ }
+
+ newConfig.apps.push(
+ migrateService(service, { type: 'category', properties: { id: category.id } })
+ );
+ });
+
+ return newConfig;
+}
+
+const migrateSearchEngine = (config: Config): SearchEngineCommonSettingsType => {
+ switch (config.settings.searchUrl) {
+ case 'https://bing.com/search?q=':
+ return {
+ type: 'bing',
+ properties: {
+ enabled: true,
+ openInNewTab: true,
+ },
+ };
+ case 'https://google.com/search?q=':
+ return {
+ type: 'google',
+ properties: {
+ enabled: true,
+ openInNewTab: true,
+ },
+ };
+ case 'https://duckduckgo.com/?q=':
+ return {
+ type: 'duckDuckGo',
+ properties: {
+ enabled: true,
+ openInNewTab: true,
+ },
+ };
+ default:
+ return {
+ type: 'custom',
+ properties: {
+ enabled: true,
+ openInNewTab: true,
+ template: config.settings.searchUrl,
+ },
+ };
+ }
+};
+
+const getConfigAndCreateIfNotExsists = (
+ config: ConfigType,
+ categoryName: string
+): CategoryType | null => {
+ const foundCategory = config.categories.find((c) => c.name === categoryName);
+ if (foundCategory) {
+ return foundCategory;
+ }
+
+ const category: CategoryType = {
+ id: uuidv4(),
+ name: categoryName,
+ position: config.categories.length + 1, // sync up with index of categories
+ };
+
+ config.categories.push(category);
+
+ // sync up with categories
+ if (config.wrappers.length < config.categories.length) {
+ config.wrappers.push({
+ id: uuidv4(),
+ position: config.wrappers.length + 1, // sync up with index of categories
+ });
+ }
+
+ return category;
+};
+
+const migrateService = (oldService: serviceItem, areaType: AreaType): AppType => ({
+ id: uuidv4(),
+ name: oldService.name,
+ url: oldService.url,
+ behaviour: {
+ isOpeningNewTab: oldService.newTab ?? true,
+ externalUrl: oldService.openedUrl ?? '',
+ },
+ network: {
+ enabledStatusChecker: oldService.ping ?? true,
+ okStatus: oldService.status?.map((str) => parseInt(str, 10)) ?? [200],
+ },
+ appearance: {
+ iconUrl: migrateIcon(oldService.icon),
+ },
+ integration: migrateIntegration(oldService),
+ area: areaType,
+ shape: {},
+});
+
+const migrateModules = (config: Config): IWidget[] => {
+ const moduleKeys = Object.keys(config.modules);
+ return moduleKeys
+ .map((moduleKey): IWidget | null => {
+ const oldModule = config.modules[moduleKey];
+
+ if (!oldModule.enabled) {
+ return null;
+ }
+
+ switch (moduleKey.toLowerCase()) {
+ case 'torrent-status':
+ case 'Torrent':
+ return {
+ id: 'torrents-status',
+ properties: {
+ refreshInterval: 10,
+ displayCompletedTorrents: oldModule.options?.hideComplete?.value ?? false,
+ displayStaleTorrents: true,
+ },
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as ITorrent;
+ case 'weather':
+ return {
+ id: 'weather',
+ properties: {
+ displayInFahrenheit: oldModule.options?.freedomunit?.value ?? false,
+ location: oldModule.options?.location?.value ?? 'Paris',
+ },
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as IWeatherWidget;
+ case 'dashdot':
+ case 'Dash.': {
+ const oldDashDotService = config.services.find((service) => service.type === 'Dash.');
+ return {
+ id: 'dashdot',
+ properties: {
+ url: oldModule.options?.url?.value ?? oldDashDotService?.url ?? '',
+ cpuMultiView: oldModule.options?.cpuMultiView?.value ?? false,
+ storageMultiView: oldModule.options?.storageMultiView?.value ?? false,
+ useCompactView: oldModule.options?.useCompactView?.value ?? false,
+ graphs: oldModule.options?.graphs?.value ?? ['cpu', 'ram'],
+ },
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as IDashDotTile;
+ }
+ case 'date':
+ return {
+ id: 'date',
+ properties: {
+ display24HourFormat: oldModule.options?.full?.value ?? true,
+ },
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as IDateWidget;
+ case 'Download Speed' || 'dlspeed':
+ return {
+ id: 'dlspeed',
+ properties: {},
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as ITorrentNetworkTraffic;
+ case 'calendar':
+ return {
+ id: 'calendar',
+ properties: {},
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as ICalendarWidget;
+ case 'usenet':
+ return {
+ id: 'usenet',
+ properties: {},
+ area: {
+ type: 'wrapper',
+ properties: {
+ id: 'default',
+ },
+ },
+ shape: {},
+ } as IUsenetWidget;
+ default:
+ Consola.error(`Failed to map unknown module type ${moduleKey} to new type definitions.`);
+ return null;
+ }
+ })
+ .filter((x) => x !== null) as IWidget[];
+};
+
+const migrateIcon = (iconUrl: string) => {
+ if (iconUrl.startsWith('https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/')) {
+ const icon = iconUrl.split('/').at(-1);
+ Consola.warn(
+ `Detected legacy icon repository. Upgrading to replacement repository: ${iconUrl} -> ${icon}`
+ );
+ return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
+ }
+
+ return iconUrl;
+};
+
+const migrateIntegration = (oldService: serviceItem): AppIntegrationType => {
+ const logInformation = (newType: IntegrationType) => {
+ Consola.info(`Migrated integration ${oldService.type} to the new type ${newType}`);
+ };
+ switch (oldService.type) {
+ case 'Deluge':
+ logInformation('deluge');
+ return {
+ type: 'deluge',
+ properties: [
+ {
+ field: 'password',
+ isDefined: oldService.password !== undefined,
+ type: 'private',
+ value: oldService.password,
+ },
+ ],
+ };
+ case 'Jellyseerr':
+ logInformation('jellyseerr');
+ return {
+ type: 'jellyseerr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Overseerr':
+ logInformation('overseerr');
+ return {
+ type: 'overseerr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Lidarr':
+ logInformation('lidarr');
+ return {
+ type: 'lidarr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Radarr':
+ logInformation('radarr');
+ return {
+ type: 'radarr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Readarr':
+ logInformation('readarr');
+ return {
+ type: 'readarr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Sabnzbd':
+ logInformation('sabnzbd');
+ return {
+ type: 'sabnzbd',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'Sonarr':
+ logInformation('sonarr');
+ return {
+ type: 'sonarr',
+ properties: [
+ {
+ field: 'apiKey',
+ isDefined: oldService.apiKey !== undefined,
+ type: 'private',
+ value: oldService.apiKey,
+ },
+ ],
+ };
+ case 'NZBGet':
+ logInformation('nzbGet');
+ return {
+ type: 'nzbGet',
+ properties: [
+ {
+ field: 'username',
+ isDefined: oldService.username !== undefined,
+ type: 'private',
+ value: oldService.username,
+ },
+ {
+ field: 'password',
+ isDefined: oldService.password !== undefined,
+ type: 'private',
+ value: oldService.password,
+ },
+ ],
+ };
+ case 'qBittorrent':
+ logInformation('qBittorrent');
+ return {
+ type: 'qBittorrent',
+ properties: [
+ {
+ field: 'username',
+ isDefined: oldService.username !== undefined,
+ type: 'private',
+ value: oldService.username,
+ },
+ {
+ field: 'password',
+ isDefined: oldService.password !== undefined,
+ type: 'private',
+ value: oldService.password,
+ },
+ ],
+ };
+ case 'Transmission':
+ logInformation('transmission');
+ return {
+ type: 'transmission',
+ properties: [
+ {
+ field: 'username',
+ isDefined: oldService.username !== undefined,
+ type: 'private',
+ value: oldService.username,
+ },
+ {
+ field: 'password',
+ isDefined: oldService.password !== undefined,
+ type: 'private',
+ value: oldService.password,
+ },
+ ],
+ };
+ case 'Other':
+ return {
+ type: null,
+ properties: [],
+ };
+ default:
+ Consola.warn(
+ `Integration type of service ${oldService.name} could not be mapped to new integration type definition`
+ );
+ return {
+ type: null,
+ properties: [],
+ };
+ }
+};
diff --git a/src/tools/config/mutations/useCopyConfigMutation.tsx b/src/tools/config/mutations/useCopyConfigMutation.tsx
new file mode 100644
index 000000000..2aad78a68
--- /dev/null
+++ b/src/tools/config/mutations/useCopyConfigMutation.tsx
@@ -0,0 +1,54 @@
+import { showNotification } from '@mantine/notifications';
+import { IconCheck, IconX } from '@tabler/icons';
+import { useMutation } from '@tanstack/react-query';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../../config/provider';
+import { ConfigType } from '../../../types/config';
+
+export const useCopyConfigMutation = (configName: string) => {
+ const { config } = useConfigContext();
+ const { t } = useTranslation(['settings/general/config-changer']);
+
+ return useMutation({
+ mutationKey: ['configs/copy', { configName }],
+ mutationFn: () => fetchCopy(configName, config),
+ onSuccess() {
+ showNotification({
+ title: t('modal.events.configCopied.title'),
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('modal.events.configCopied.message', { configName }),
+ });
+ },
+ onError() {
+ showNotification({
+ title: t('modal.events.configNotCopied.title'),
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('modal.events.configNotCopied.message', { configName }),
+ });
+ },
+ });
+};
+
+const fetchCopy = async (configName: string, config: ConfigType | undefined) => {
+ if (!config) {
+ throw new Error('config is not defiend');
+ }
+
+ const copiedConfig = config;
+ copiedConfig.configProperties.name = configName;
+
+ const response = await fetch(`/api/configs/${configName}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(config),
+ });
+ return response.json();
+};
diff --git a/src/tools/config/mutations/useDeleteConfigMutation.tsx b/src/tools/config/mutations/useDeleteConfigMutation.tsx
new file mode 100644
index 000000000..6885d79e6
--- /dev/null
+++ b/src/tools/config/mutations/useDeleteConfigMutation.tsx
@@ -0,0 +1,26 @@
+import { showNotification } from '@mantine/notifications';
+import { IconCheck, IconX } from '@tabler/icons';
+import { useMutation } from '@tanstack/react-query';
+import { useTranslation } from 'next-i18next';
+
+export const useDeleteConfigMutation = (configName: string) => {
+ const { t } = useTranslation(['settings/general/config-changer']);
+
+ return useMutation({
+ mutationKey: ['configs/delete', { configName }],
+ mutationFn: () => fetchDeletion(configName),
+ onError() {
+ showNotification({
+ title: t('buttons.delete.notifications.deleteFailed.title'),
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('buttons.delete.notifications.deleteFailed.message'),
+ });
+ },
+ });
+};
+
+const fetchDeletion = async (configName: string) =>
+ (await fetch(`/api/configs/${configName}`, { method: 'DELETE' })).json();
diff --git a/src/tools/config/readConfig.ts b/src/tools/config/readConfig.ts
new file mode 100644
index 000000000..c038d88b0
--- /dev/null
+++ b/src/tools/config/readConfig.ts
@@ -0,0 +1,7 @@
+import fs from 'fs';
+import { generateConfigPath } from './generateConfigPath';
+
+export function readConfig(name: string) {
+ const path = generateConfigPath(name);
+ return JSON.parse(fs.readFileSync(path, 'utf8'));
+}
diff --git a/src/tools/getConfig.ts b/src/tools/getConfig.ts
index d2a4a9c08..2c7526a7b 100644
--- a/src/tools/getConfig.ts
+++ b/src/tools/getConfig.ts
@@ -1,29 +1,14 @@
import path from 'path';
import fs from 'fs';
+import { getFallbackConfig } from './config/getFallbackConfig';
+import { ConfigType } from '../types/config';
export function getConfig(name: string, props: any = undefined) {
// Check if the config file exists
const configPath = path.join(process.cwd(), 'data/configs', `${name}.json`);
if (!fs.existsSync(configPath)) {
- return {
- props: {
- configName: name,
- config: {
- name: name.toString(),
- services: [],
- settings: {
- searchUrl: 'https://www.google.com/search?q=',
- },
- modules: {
- 'Search Bar': {
- enabled: true,
- },
- },
- },
- },
- };
+ return getFallbackConfig() as unknown as ConfigType;
}
-
const config = fs.readFileSync(configPath, 'utf8');
// Print loaded config
return {
diff --git a/src/tools/getServerSideTranslations.ts b/src/tools/getServerSideTranslations.ts
new file mode 100644
index 000000000..5576fafc7
--- /dev/null
+++ b/src/tools/getServerSideTranslations.ts
@@ -0,0 +1,19 @@
+import { getCookie } from 'cookies-next';
+import { IncomingMessage, ServerResponse } from 'http';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+
+export const getServerSideTranslations = async (
+ req: IncomingMessage,
+ res: ServerResponse,
+ namespaces: string[],
+ requestLocale?: string
+) => {
+ const configLocale = getCookie('config-locale', { req, res });
+
+ const translations = await serverSideTranslations(
+ (configLocale ?? requestLocale ?? 'en') as string,
+ namespaces
+ );
+
+ return translations;
+};
diff --git a/src/tools/hooks/useGetServiceByType.ts b/src/tools/hooks/useGetServiceByType.ts
deleted file mode 100644
index 05d2dce10..000000000
--- a/src/tools/hooks/useGetServiceByType.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useConfig } from '../state';
-import { Config, ServiceType } from '../types';
-
-export const useGetServiceByType = (...serviceTypes: ServiceType[]) => {
- const { config } = useConfig();
-
- return getServiceByType(config, ...serviceTypes);
-};
-
-export const getServiceByType = (config: Config, ...serviceTypes: ServiceType[]) =>
- config.services.filter((s) => serviceTypes.includes(s.type));
-
-export const getServiceById = (config: Config, id: string) =>
- config.services.find((s) => s.id === id);
diff --git a/src/tools/isToday.ts b/src/tools/isToday.ts
new file mode 100644
index 000000000..8ca8441f0
--- /dev/null
+++ b/src/tools/isToday.ts
@@ -0,0 +1,8 @@
+export const isToday = (date: Date) => {
+ const today = new Date();
+ return (
+ today.getDate() === date.getDate() &&
+ today.getMonth() === date.getMonth() &&
+ date.getFullYear() === date.getFullYear()
+ );
+};
diff --git a/src/tools/mantineModalManagerExtensions.ts b/src/tools/mantineModalManagerExtensions.ts
new file mode 100644
index 000000000..3457f4568
--- /dev/null
+++ b/src/tools/mantineModalManagerExtensions.ts
@@ -0,0 +1,6 @@
+import { openContextModal } from '@mantine/modals';
+import { OpenContextModal } from '@mantine/modals/lib/context';
+
+export const openContextModalGeneric = >(
+ payload: OpenContextModal & { modal: string }
+) => openContextModal(payload);
diff --git a/src/tools/migrate.ts b/src/tools/migrate.ts
deleted file mode 100644
index b39818b07..000000000
--- a/src/tools/migrate.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { v4 as uuidv4 } from 'uuid';
-import { Config } from './types';
-
-export function migrateToIdConfig(config: Config): Config {
- // Set the config and add an ID to all the services that don't have one
- const services = config.services.map((service) => ({
- ...service,
- id: service.id ?? uuidv4(),
- }));
- return {
- ...config,
- services,
- };
-}
diff --git a/src/tools/percentage.ts b/src/tools/percentage.ts
new file mode 100644
index 000000000..eaf588c34
--- /dev/null
+++ b/src/tools/percentage.ts
@@ -0,0 +1,3 @@
+export const percentage = (partialValue: number, totalValue: number) => {
+ return ((100 * partialValue) / totalValue).toFixed(1);
+};
diff --git a/src/tools/state.tsx b/src/tools/state.tsx
deleted file mode 100644
index 21e4da1dc..000000000
--- a/src/tools/state.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-// src/context/state.js
-import { showNotification } from '@mantine/notifications';
-import axios from 'axios';
-import { createContext, ReactNode, useContext, useState } from 'react';
-import { IconCheck as Check, IconX as X } from '@tabler/icons';
-import { Config } from './types';
-
-type configContextType = {
- config: Config;
- setConfig: (newconfig: Config) => void;
- loadConfig: (name: string) => void;
- getConfigs: () => Promise;
-};
-
-const configContext = createContext({
- config: {
- name: 'default',
- services: [],
- settings: {
- searchUrl: 'https://google.com/search?q=',
- },
- modules: {},
- },
- setConfig: () => {},
- loadConfig: async (name: string) => {},
- getConfigs: async () => [],
-});
-
-export function useConfig() {
- const context = useContext(configContext);
- if (context === undefined) {
- throw new Error('useConfig must be used within a ConfigProvider');
- }
- return context;
-}
-
-type Props = {
- children: ReactNode;
-};
-
-export function ConfigProvider({ children }: Props) {
- const [config, setConfigInternal] = useState({
- name: 'default',
- services: [],
- settings: {
- searchUrl: 'https://www.google.com/search?q=',
- },
- modules: {},
- });
-
- async function loadConfig(configName: string) {
- try {
- const response = await axios.get(`/api/configs/${configName}`);
- setConfigInternal(JSON.parse(response.data));
- showNotification({
- title: 'Config',
- icon: ,
- color: 'green',
- autoClose: 1500,
- radius: 'md',
- message: `Loaded config : ${configName}`,
- });
- } catch (error) {
- showNotification({
- title: 'Config',
- icon: ,
- color: 'red',
- autoClose: 1500,
- radius: 'md',
- message: `Error loading config : ${configName}`,
- });
- }
- }
-
- function setConfig(newconfig: Config) {
- axios.put(`/api/configs/${newconfig.name}`, newconfig);
- setConfigInternal(newconfig);
- }
-
- async function getConfigs(): Promise {
- const response = await axios.get('/api/configs');
- return response.data;
- }
-
- const value = {
- config,
- setConfig,
- loadConfig,
- getConfigs,
- };
- return {children} ;
-}
diff --git a/src/tools/translation-namespaces.ts b/src/tools/translation-namespaces.ts
index e4f6c540a..facb3cbd4 100644
--- a/src/tools/translation-namespaces.ts
+++ b/src/tools/translation-namespaces.ts
@@ -1,13 +1,16 @@
export const dashboardNamespaces = [
'common',
- 'layout/app-shelf',
- 'layout/add-service-app-shelf',
- 'layout/app-shelf-menu',
+ 'layout/tools',
+ 'layout/element-selector/selector',
+ 'layout/modals/add-app',
+ 'layout/modals/change-position',
+ 'layout/modals/about',
+ 'layout/header/actions/toggle-edit-mode',
+ 'layout/mobile/drawer',
'settings/common',
'settings/general/theme-selector',
'settings/general/config-changer',
'settings/general/internationalization',
- 'settings/general/module-enabler',
'settings/general/search-engine',
'settings/general/widget-positions',
'settings/customization/color-selector',
diff --git a/src/tools/types.ts b/src/tools/types.ts
index 68d8d75e7..cb59c6f3a 100644
--- a/src/tools/types.ts
+++ b/src/tools/types.ts
@@ -35,28 +35,6 @@ interface ConfigModule {
};
}
-export const StatusCodes = [
- { value: '200', label: '200 - OK', group: 'Sucessful responses' },
- { value: '204', label: '204 - No Content', group: 'Sucessful responses' },
- { value: '301', label: '301 - Moved Permanently', group: 'Redirection responses' },
- { value: '302', label: '302 - Found / Moved Temporarily', group: 'Redirection responses' },
- { value: '304', label: '304 - Not Modified', group: 'Redirection responses' },
- { value: '307', label: '307 - Temporary Redirect', group: 'Redirection responses' },
- { value: '308', label: '308 - Permanent Redirect', group: 'Redirection responses' },
- { value: '400', label: '400 - Bad Request', group: 'Client error responses' },
- { value: '401', label: '401 - Unauthorized', group: 'Client error responses' },
- { value: '403', label: '403 - Forbidden', group: 'Client error responses' },
- { value: '404', label: '404 - Not Found', group: 'Client error responses' },
- { value: '405', label: '405 - Method Not Allowed', group: 'Client error responses' },
- { value: '408', label: '408 - Request Timeout', group: 'Client error responses' },
- { value: '410', label: '410 - Gone', group: 'Client error responses' },
- { value: '429', label: '429 - Too Many Requests', group: 'Client error responses' },
- { value: '500', label: '500 - Internal Server Error', group: 'Server error responses' },
- { value: '502', label: '502 - Bad Gateway', group: 'Server error responses' },
- { value: '503', label: '503 - Service Unavailable', group: 'Server error responses' },
- { value: '054', label: '504 - Gateway Timeout Error', group: 'Server error responses' },
-];
-
export const Targets = [
{ value: '_blank', label: 'New Tab' },
{ value: '_top', label: 'Same Window' },
@@ -124,6 +102,7 @@ export const portmap = [
{ name: 'nzbget', value: '6789' },
];
+//TODO: Fix this to be used in the docker add to homarr button
export const MatchingImages: {
image: string;
type: ServiceType;
diff --git a/src/types/app.ts b/src/types/app.ts
new file mode 100644
index 000000000..7edeadee9
--- /dev/null
+++ b/src/types/app.ts
@@ -0,0 +1,107 @@
+import { IconKey, IconPassword, IconUser, TablerIcon } from '@tabler/icons';
+import { TileBaseType } from './tile';
+
+export interface AppType extends TileBaseType {
+ id: string;
+ name: string;
+ url: string;
+ behaviour: AppBehaviourType;
+ network: AppNetworkType;
+ appearance: AppAppearanceType;
+ integration: AppIntegrationType;
+}
+
+export type ConfigAppType = Omit & {
+ integration?: ConfigAppIntegrationType | null;
+};
+
+interface AppBehaviourType {
+ externalUrl: string;
+ isOpeningNewTab: boolean;
+}
+
+interface AppNetworkType {
+ enabledStatusChecker: boolean;
+ okStatus: number[];
+}
+
+interface AppAppearanceType {
+ iconUrl: string;
+}
+
+export type IntegrationType =
+ | 'readarr'
+ | 'radarr'
+ | 'sonarr'
+ | 'lidarr'
+ | 'sabnzbd'
+ | 'jellyseerr'
+ | 'overseerr'
+ | 'deluge'
+ | 'qBittorrent'
+ | 'transmission'
+ | 'nzbGet';
+
+export type AppIntegrationType = {
+ type: IntegrationType | null;
+ properties: AppIntegrationPropertyType[];
+};
+
+export type ConfigAppIntegrationType = Omit & {
+ properties: ConfigAppIntegrationPropertyType[];
+};
+
+export type AppIntegrationPropertyType = {
+ type: AppIntegrationPropertyAccessabilityType;
+ field: IntegrationField;
+ value?: string | null;
+ isDefined: boolean;
+};
+
+export type AppIntegrationPropertyAccessabilityType = 'private' | 'public';
+
+type ConfigAppIntegrationPropertyType = Omit;
+
+export type IntegrationField = 'apiKey' | 'password' | 'username';
+
+export const integrationFieldProperties: {
+ [key in Exclude]: IntegrationField[];
+} = {
+ lidarr: ['apiKey'],
+ radarr: ['apiKey'],
+ sonarr: ['apiKey'],
+ sabnzbd: ['apiKey'],
+ readarr: ['apiKey'],
+ overseerr: ['apiKey'],
+ jellyseerr: ['apiKey'],
+ deluge: ['password'],
+ nzbGet: ['username', 'password'],
+ qBittorrent: ['username', 'password'],
+ transmission: ['username', 'password'],
+};
+
+export type IntegrationFieldDefinitionType = {
+ type: 'private' | 'public';
+ icon: TablerIcon;
+ label: string;
+};
+
+export const integrationFieldDefinitions: {
+ [key in IntegrationField]: IntegrationFieldDefinitionType;
+} = {
+ apiKey: {
+ type: 'private',
+ icon: IconKey,
+ label: 'common:secrets.apiKey',
+ },
+ username: {
+ type: 'public',
+ icon: IconUser,
+ label: 'common:secrets.username',
+ },
+ password: {
+ type: 'private',
+ icon: IconPassword,
+ label: 'common:secrets.password',
+ },
+};
diff --git a/src/types/area.ts b/src/types/area.ts
new file mode 100644
index 000000000..b236a3fbd
--- /dev/null
+++ b/src/types/area.ts
@@ -0,0 +1,22 @@
+export type AreaType = WrapperAreaType | CategoryAreaType | SidebarAreaType;
+
+interface WrapperAreaType {
+ type: 'wrapper';
+ properties: {
+ id: string;
+ };
+}
+
+interface CategoryAreaType {
+ type: 'category';
+ properties: {
+ id: string;
+ };
+}
+
+interface SidebarAreaType {
+ type: 'sidebar';
+ properties: {
+ location: 'right' | 'left';
+ };
+}
diff --git a/src/types/category.ts b/src/types/category.ts
new file mode 100644
index 000000000..86f91b9a8
--- /dev/null
+++ b/src/types/category.ts
@@ -0,0 +1,5 @@
+export interface CategoryType {
+ id: string;
+ position: number;
+ name: string;
+}
diff --git a/src/types/config.ts b/src/types/config.ts
new file mode 100644
index 000000000..53a8a3f73
--- /dev/null
+++ b/src/types/config.ts
@@ -0,0 +1,23 @@
+import { CategoryType } from './category';
+import { WrapperType } from './wrapper';
+import { ConfigAppType, AppType } from './app';
+import { SettingsType } from './settings';
+import { IWidget } from '../widgets/widgets';
+
+export interface ConfigType {
+ schemaVersion: number;
+ configProperties: ConfigPropertiesType;
+ categories: CategoryType[];
+ wrappers: WrapperType[];
+ apps: AppType[];
+ widgets: IWidget[];
+ settings: SettingsType;
+}
+
+export type BackendConfigType = Omit & {
+ apps: ConfigAppType[];
+};
+
+export interface ConfigPropertiesType {
+ name: string;
+}
diff --git a/src/types/dashboardPageType.ts b/src/types/dashboardPageType.ts
new file mode 100644
index 000000000..416c3b1bf
--- /dev/null
+++ b/src/types/dashboardPageType.ts
@@ -0,0 +1,10 @@
+import { SSRConfig } from 'next-i18next';
+import { ConfigType } from './config';
+
+export type DashboardServerSideProps = {
+ config: ConfigType;
+ // eslint-disable-next-line react/no-unused-prop-types
+ configName: string;
+ // eslint-disable-next-line react/no-unused-prop-types
+ _nextI18Next: SSRConfig['_nextI18Next'];
+};
diff --git a/src/types/iconSelector/iconSelectorItem.ts b/src/types/iconSelector/iconSelectorItem.ts
new file mode 100644
index 000000000..4056bc21c
--- /dev/null
+++ b/src/types/iconSelector/iconSelectorItem.ts
@@ -0,0 +1,4 @@
+export interface IconSelectorItem {
+ url: string;
+ fileName: string;
+}
diff --git a/src/types/iconSelector/repositories/walkxcodeIconRepository.ts b/src/types/iconSelector/repositories/walkxcodeIconRepository.ts
new file mode 100644
index 000000000..70aab46aa
--- /dev/null
+++ b/src/types/iconSelector/repositories/walkxcodeIconRepository.ts
@@ -0,0 +1,3 @@
+export interface WalkxcodeRepositoryIcon {
+ name: string;
+}
diff --git a/src/types/settings.ts b/src/types/settings.ts
new file mode 100644
index 000000000..3cf2642aa
--- /dev/null
+++ b/src/types/settings.ts
@@ -0,0 +1,61 @@
+import { MantineTheme } from '@mantine/core';
+
+export interface SettingsType {
+ common: CommonSettingsType;
+ customization: CustomizationSettingsType;
+}
+
+export interface CommonSettingsType {
+ searchEngine: SearchEngineCommonSettingsType;
+ defaultConfig: string;
+}
+
+export type SearchEngineCommonSettingsType =
+ | CommonSearchEngineCommonSettingsType
+ | CustomSearchEngineCommonSettingsType;
+
+export interface CommonSearchEngineCommonSettingsType extends BaseSearchEngineType {
+ type: 'google' | 'duckDuckGo' | 'bing';
+}
+
+interface CustomSearchEngineCommonSettingsType extends BaseSearchEngineType {
+ type: 'custom';
+ properties: {
+ template: string;
+ openInNewTab: boolean;
+ enabled: boolean;
+ };
+}
+
+interface BaseSearchEngineType {
+ properties: {
+ openInNewTab: boolean;
+ enabled: boolean;
+ };
+}
+
+export interface CustomizationSettingsType {
+ layout: LayoutCustomizationSettingsType;
+ pageTitle?: string;
+ metaTitle?: string;
+ logoImageUrl?: string;
+ faviconUrl?: string;
+ backgroundImageUrl?: string;
+ customCss?: string;
+ colors: ColorsCustomizationSettingsType;
+ appOpacity?: number;
+}
+
+interface LayoutCustomizationSettingsType {
+ enabledLeftSidebar: boolean;
+ enabledRightSidebar: boolean;
+ enabledDocker: boolean;
+ enabledPing: boolean;
+ enabledSearchbar: boolean;
+}
+
+interface ColorsCustomizationSettingsType {
+ primary?: MantineTheme['primaryColor'];
+ secondary?: MantineTheme['primaryColor'];
+ shade?: MantineTheme['primaryShade'];
+}
diff --git a/src/types/shape.ts b/src/types/shape.ts
new file mode 100644
index 000000000..a8a6e6823
--- /dev/null
+++ b/src/types/shape.ts
@@ -0,0 +1,16 @@
+export interface ShapeType {
+ lg?: SizedShapeType;
+ md?: SizedShapeType;
+ sm?: SizedShapeType;
+}
+
+export interface SizedShapeType {
+ location: {
+ x: number;
+ y: number;
+ };
+ size: {
+ width: number;
+ height: number;
+ };
+}
diff --git a/src/types/tile.ts b/src/types/tile.ts
new file mode 100644
index 000000000..7e879fd78
--- /dev/null
+++ b/src/types/tile.ts
@@ -0,0 +1,7 @@
+import { AreaType } from './area';
+import { ShapeType } from './shape';
+
+export interface TileBaseType {
+ area: AreaType;
+ shape: ShapeType;
+}
diff --git a/src/types/wrapper.ts b/src/types/wrapper.ts
new file mode 100644
index 000000000..7e27e6118
--- /dev/null
+++ b/src/types/wrapper.ts
@@ -0,0 +1,4 @@
+export interface WrapperType {
+ id: string;
+ position: number;
+}
diff --git a/src/widgets/WidgetWrapper.tsx b/src/widgets/WidgetWrapper.tsx
new file mode 100644
index 000000000..da0837307
--- /dev/null
+++ b/src/widgets/WidgetWrapper.tsx
@@ -0,0 +1,18 @@
+import { ReactNode } from 'react';
+import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
+import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
+import { IWidget } from './widgets';
+
+interface WidgetWrapperProps {
+ widgetId: string;
+ widget: IWidget;
+ className: string;
+ children: ReactNode;
+}
+
+export const WidgetWrapper = ({ widgetId, widget, className, children }: WidgetWrapperProps) => (
+
+
+ {children}
+
+);
diff --git a/src/widgets/calendar/CalendarDay.tsx b/src/widgets/calendar/CalendarDay.tsx
new file mode 100644
index 000000000..c8ddc2f9d
--- /dev/null
+++ b/src/widgets/calendar/CalendarDay.tsx
@@ -0,0 +1,64 @@
+import { Box, Indicator, IndicatorProps, Popover } from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import { MediaList } from './MediaList';
+import { MediasType } from './type';
+
+interface CalendarDayProps {
+ date: Date;
+ medias: MediasType;
+}
+
+export const CalendarDay = ({ date, medias }: CalendarDayProps) => {
+ const [opened, { close, open }] = useDisclosure(false);
+
+ if (medias.totalCount === 0) {
+ return {date.getDate()}
;
+ }
+
+ return (
+
+
+
+
+
+
+
+ {date.getDate()}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+interface DayIndicatorProps {
+ color: string;
+ medias: any[];
+ children: JSX.Element;
+ position: IndicatorProps['position'];
+}
+
+const DayIndicator = ({ color, medias, children, position }: DayIndicatorProps) => {
+ if (medias.length === 0) return children;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/widgets/calendar/CalendarTile.tsx b/src/widgets/calendar/CalendarTile.tsx
new file mode 100644
index 000000000..b2dd3bfb6
--- /dev/null
+++ b/src/widgets/calendar/CalendarTile.tsx
@@ -0,0 +1,139 @@
+import { createStyles, Group, MantineThemeColors, useMantineTheme } from '@mantine/core';
+import { Calendar } from '@mantine/dates';
+import { IconCalendarTime } from '@tabler/icons';
+import { useQuery } from '@tanstack/react-query';
+import { useState } from 'react';
+import { useConfigContext } from '../../config/provider';
+import { useColorTheme } from '../../tools/color';
+import { isToday } from '../../tools/isToday';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+import { CalendarDay } from './CalendarDay';
+import { MediasType } from './type';
+
+const definition = defineWidget({
+ id: 'calendar',
+ icon: IconCalendarTime,
+ options: {
+ sundayStart: {
+ type: 'switch',
+ defaultValue: false,
+ },
+ radarrReleaseType: {
+ type: 'select',
+ defaultValue: 'inCinemas',
+ data: [
+ { label: 'In Cinemas', value: 'inCinemas' },
+ { label: 'Physical', value: 'physicalRelease' },
+ { label: 'Digital', value: 'digitalRelease' },
+ ],
+ },
+ },
+ gridstack: {
+ minWidth: 2,
+ minHeight: 2,
+ maxWidth: 12,
+ maxHeight: 12,
+ },
+ component: CalendarTile,
+});
+
+export type ICalendarWidget = IWidget;
+
+interface CalendarTileProps {
+ widget: ICalendarWidget;
+}
+
+function CalendarTile({ widget }: CalendarTileProps) {
+ const { secondaryColor } = useColorTheme();
+ const { name: configName } = useConfigContext();
+ const { classes, cx } = useStyles(secondaryColor);
+ const { colorScheme, colors } = useMantineTheme();
+ const [month, setMonth] = useState(new Date());
+
+ const { data: medias } = useQuery({
+ queryKey: ['calendar/medias', { month: month.getMonth(), year: month.getFullYear() }],
+ queryFn: async () =>
+ (await (
+ await fetch(
+ `/api/modules/calendar?year=${month.getFullYear()}&month=${
+ month.getMonth() + 1
+ }&configName=${configName}`
+ )
+ ).json()) as MediasType,
+ });
+
+ return (
+
+ {}}
+ firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'}
+ dayStyle={(date) => ({
+ margin: 1,
+ backgroundColor: isToday(date)
+ ? colorScheme === 'dark'
+ ? colors.dark[5]
+ : colors.gray[0]
+ : undefined,
+ })}
+ styles={{
+ calendarHeader: {
+ marginRight: 40,
+ marginLeft: 40,
+ },
+ }}
+ allowLevelChange={false}
+ dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
+ renderDay={(date) => (
+
+ )}
+ />
+
+ );
+}
+
+const useStyles = createStyles((theme, secondaryColor: keyof MantineThemeColors) => ({
+ weekend: {
+ color: `${secondaryColor} !important`,
+ },
+}));
+
+const getReleasedMediasForDate = (
+ medias: MediasType | undefined,
+ date: Date,
+ widget: ICalendarWidget
+): MediasType => {
+ const radarrReleaseType = widget.properties.radarrReleaseType ?? 'inCinemas';
+
+ const books =
+ medias?.books.filter((b) => new Date(b.releaseDate).toDateString() === date.toDateString()) ??
+ [];
+ const movies =
+ medias?.movies.filter(
+ (m) => new Date(m[radarrReleaseType]).toDateString() === date.toDateString()
+ ) ?? [];
+ const musics =
+ medias?.musics.filter((m) => new Date(m.releaseDate).toDateString() === date.toDateString()) ??
+ [];
+ const tvShows =
+ medias?.tvShows.filter(
+ (tv) => new Date(tv.airDateUtc).toDateString() === date.toDateString()
+ ) ?? [];
+ const totalCount = medias ? books.length + movies.length + musics.length + tvShows.length : 0;
+
+ return {
+ books,
+ movies,
+ musics,
+ tvShows,
+ totalCount,
+ };
+};
+
+export default definition;
diff --git a/src/widgets/calendar/MediaList.tsx b/src/widgets/calendar/MediaList.tsx
new file mode 100644
index 000000000..6fee8be0f
--- /dev/null
+++ b/src/widgets/calendar/MediaList.tsx
@@ -0,0 +1,65 @@
+import { createStyles, Divider, ScrollArea } from '@mantine/core';
+import React from 'react';
+import {
+ LidarrMediaDisplay,
+ RadarrMediaDisplay,
+ ReadarrMediaDisplay,
+ SonarrMediaDisplay,
+} from '../../modules/common';
+import { MediasType } from './type';
+
+interface MediaListProps {
+ medias: MediasType;
+}
+
+export const MediaList = ({ medias }: MediaListProps) => {
+ const { classes } = useStyles();
+ const lastMediaType = getLastMediaType(medias);
+
+ return (
+
+ {mapMedias(medias.tvShows, SonarrMediaDisplay, lastMediaType === 'tv-show')}
+ {mapMedias(medias.movies, RadarrMediaDisplay, lastMediaType === 'movie')}
+ {mapMedias(medias.musics, LidarrMediaDisplay, lastMediaType === 'music')}
+ {mapMedias(medias.books, ReadarrMediaDisplay, lastMediaType === 'book')}
+
+ );
+};
+
+const mapMedias = (
+ medias: any[],
+ MediaComponent: (props: { media: any }) => JSX.Element | null,
+ containsLastItem: boolean
+) =>
+ medias.map((media, index) => (
+
+
+ {containsLastItem && index === medias.length - 1 ? null : }
+
+ ));
+
+const MediaDivider = () => ;
+
+const getLastMediaType = (medias: MediasType) => {
+ if (medias.books.length >= 1) return 'book';
+ if (medias.musics.length >= 1) return 'music';
+ if (medias.movies.length >= 1) return 'movie';
+ return 'tv-show';
+};
+
+const useStyles = createStyles(() => ({
+ scrollArea: {
+ width: 400,
+ },
+}));
diff --git a/src/widgets/calendar/type.ts b/src/widgets/calendar/type.ts
new file mode 100644
index 000000000..c9b3b6bb9
--- /dev/null
+++ b/src/widgets/calendar/type.ts
@@ -0,0 +1,7 @@
+export interface MediasType {
+ tvShows: any[]; // Sonarr
+ movies: any[]; // Radarr
+ musics: any[]; // Lidarr
+ books: any[]; // Readarr
+ totalCount: number;
+}
diff --git a/src/widgets/dashDot/DashDotCompactNetwork.tsx b/src/widgets/dashDot/DashDotCompactNetwork.tsx
new file mode 100644
index 000000000..b0c0f9f50
--- /dev/null
+++ b/src/widgets/dashDot/DashDotCompactNetwork.tsx
@@ -0,0 +1,45 @@
+import { Group, Stack, Text } from '@mantine/core';
+import { IconArrowNarrowDown, IconArrowNarrowUp } from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+import { bytes } from '../../tools/bytesHelper';
+
+interface DashDotCompactNetworkProps {
+ info: DashDotInfo;
+}
+
+export interface DashDotInfo {
+ storage: {
+ layout: { size: number }[];
+ };
+ network: {
+ speedUp: number;
+ speedDown: number;
+ };
+}
+
+export const DashDotCompactNetwork = ({ info }: DashDotCompactNetworkProps) => {
+ const { t } = useTranslation('modules/dashdot');
+
+ const upSpeed = bytes.toPerSecondString(info?.network?.speedUp);
+ const downSpeed = bytes.toPerSecondString(info?.network?.speedDown);
+
+ return (
+
+ {t('card.graphs.network.label')}
+
+
+
+ {upSpeed}
+
+
+
+
+
+ {downSpeed}
+
+
+
+
+
+ );
+};
diff --git a/src/widgets/dashDot/DashDotCompactStorage.tsx b/src/widgets/dashDot/DashDotCompactStorage.tsx
new file mode 100644
index 000000000..3a798a5ef
--- /dev/null
+++ b/src/widgets/dashDot/DashDotCompactStorage.tsx
@@ -0,0 +1,77 @@
+import { Group, Stack, Text } from '@mantine/core';
+import { useQuery } from '@tanstack/react-query';
+import axios from 'axios';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../config/provider';
+import { bytes } from '../../tools/bytesHelper';
+import { percentage } from '../../tools/percentage';
+import { DashDotInfo } from './DashDotCompactNetwork';
+
+interface DashDotCompactStorageProps {
+ info: DashDotInfo;
+}
+
+export const DashDotCompactStorage = ({ info }: DashDotCompactStorageProps) => {
+ const { t } = useTranslation('modules/dashdot');
+ const { data: storageLoad } = useDashDotStorage();
+
+ const totalUsed = calculateTotalLayoutSize({
+ layout: storageLoad?.layout ?? [],
+ key: 'load',
+ });
+ const totalSize = calculateTotalLayoutSize({
+ layout: info?.storage?.layout ?? [],
+ key: 'size',
+ });
+
+ return (
+
+ {t('card.graphs.storage.label')}
+
+
+ {percentage(totalUsed, totalSize)}%
+
+
+ {bytes.toString(totalUsed)} / {bytes.toString(totalSize)}
+
+
+
+ );
+};
+
+const calculateTotalLayoutSize = ({
+ layout,
+ key,
+}: CalculateTotalLayoutSizeProps) =>
+ layout.reduce((total, current) => total + (current[key] as number), 0);
+
+interface CalculateTotalLayoutSizeProps {
+ layout: TLayoutItem[];
+ key: keyof TLayoutItem;
+}
+
+const useDashDotStorage = () => {
+ const { name: configName, config } = useConfigContext();
+
+ return useQuery({
+ queryKey: [
+ 'dashdot/storage',
+ {
+ configName,
+ url: config?.widgets.find((x) => x.id === 'dashdot')?.properties.url,
+ },
+ ],
+ queryFn: () => fetchDashDotStorageLoad(configName),
+ });
+};
+
+async function fetchDashDotStorageLoad(configName: string | undefined) {
+ if (!configName) throw new Error('configName is undefined');
+ return (await (
+ await axios.get('/api/modules/dashdot/storage', { params: { configName } })
+ ).data) as DashDotStorageLoad;
+}
+
+interface DashDotStorageLoad {
+ layout: { load: number }[];
+}
diff --git a/src/widgets/dashDot/DashDotGraph.tsx b/src/widgets/dashDot/DashDotGraph.tsx
new file mode 100644
index 000000000..571a021c7
--- /dev/null
+++ b/src/widgets/dashDot/DashDotGraph.tsx
@@ -0,0 +1,77 @@
+import { createStyles, Stack, Title, useMantineTheme } from '@mantine/core';
+import { DashDotGraph as GraphType } from './types';
+
+interface DashDotGraphProps {
+ graph: GraphType;
+ isCompact: boolean;
+ dashDotUrl: string;
+}
+
+export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps) => {
+ const { classes } = useStyles();
+ return (
+
+
+ {graph.name}
+
+
+
+ );
+};
+
+const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => {
+ const { colorScheme, colors, radius } = useMantineTheme();
+ const surface = (colorScheme === 'dark' ? colors.dark[7] : colors.gray[0]).substring(1); // removes # from hex value
+
+ const graphId = graph.id === 'memory' ? 'ram' : graph.id;
+
+ return (
+ `${dashDotUrl}` +
+ '?singleGraphMode=true' +
+ `&graph=${graphId}` +
+ `&theme=${colorScheme}` +
+ `&surface=${surface}` +
+ `&gap=${isCompact ? 10 : 5}` +
+ `&innerRadius=${radius.lg}` +
+ `&multiView=${graph.isMultiView}`
+ );
+};
+
+export const useStyles = createStyles((theme, _params, getRef) => ({
+ iframe: {
+ flex: '1 0 auto',
+ maxWidth: '100%',
+ height: '140px',
+ borderRadius: theme.radius.lg,
+ border: 'none',
+ colorScheme: 'light', // fixes white borders around iframe
+ },
+ graphTitle: {
+ ref: getRef('graphTitle'),
+ position: 'absolute',
+ right: 0,
+ opacity: 0,
+ transition: 'opacity .1s ease-in-out',
+ pointerEvents: 'none',
+ marginTop: 10,
+ marginRight: 25,
+ },
+ graphStack: {
+ position: 'relative',
+ [`&:hover .${getRef('graphTitle')}`]: {
+ opacity: 0.5,
+ },
+ },
+}));
diff --git a/src/widgets/dashDot/DashDotTile.tsx b/src/widgets/dashDot/DashDotTile.tsx
new file mode 100644
index 000000000..89f83d353
--- /dev/null
+++ b/src/widgets/dashDot/DashDotTile.tsx
@@ -0,0 +1,159 @@
+import { createStyles, Group, Title } from '@mantine/core';
+import { useQuery } from '@tanstack/react-query';
+import axios from 'axios';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../config/provider';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+import { DashDotCompactNetwork, DashDotInfo } from './DashDotCompactNetwork';
+import { DashDotCompactStorage } from './DashDotCompactStorage';
+import { DashDotGraph } from './DashDotGraph';
+
+const definition = defineWidget({
+ id: 'dashdot',
+ icon: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dashdot.png',
+ options: {
+ cpuMultiView: {
+ type: 'switch',
+ defaultValue: false,
+ },
+ storageMultiView: {
+ type: 'switch',
+ defaultValue: false,
+ },
+ useCompactView: {
+ type: 'switch',
+ defaultValue: true,
+ },
+ graphs: {
+ type: 'multi-select',
+ defaultValue: ['cpu', 'memory'],
+ data: [
+ // ['cpu', 'memory', 'storage', 'network', 'gpu'], into { label, value }
+ { label: 'CPU', value: 'cpu' },
+ { label: 'Memory', value: 'memory' },
+ { label: 'Storage', value: 'storage' },
+ { label: 'Network', value: 'network' },
+ { label: 'GPU', value: 'gpu' },
+ ],
+ },
+ url: {
+ type: 'text',
+ defaultValue: '',
+ },
+ },
+ gridstack: {
+ minWidth: 2,
+ minHeight: 2,
+ maxWidth: 12,
+ maxHeight: 14,
+ },
+ component: DashDotTile,
+});
+
+export type IDashDotTile = IWidget;
+
+interface DashDotTileProps {
+ widget: IDashDotTile;
+}
+
+function DashDotTile({ widget }: DashDotTileProps) {
+ const { classes } = useDashDotTileStyles();
+ const { t } = useTranslation('modules/dashdot');
+
+ const dashDotUrl = widget.properties.url;
+
+ const { data: info } = useDashDotInfo({
+ dashDotUrl,
+ });
+
+ const graphs = widget?.properties.graphs.map((graph) => ({
+ id: graph,
+ name: t(`card.graphs.${graph}.title`),
+ twoSpan: ['network', 'gpu'].includes(graph),
+ isMultiView:
+ (graph === 'cpu' && widget.properties.cpuMultiView) ||
+ (graph === 'storage' && widget.properties.storageMultiView),
+ }));
+
+ const heading = (
+
+ {t('card.title')}
+
+ );
+
+ const isCompact = widget?.properties.useCompactView ?? false;
+
+ const isCompactStorageVisible = graphs?.some((g) => g.id === 'storage' && isCompact);
+
+ const isCompactNetworkVisible = graphs?.some((g) => g.id === 'network' && isCompact);
+
+ const displayedGraphs = graphs?.filter(
+ (g) => !isCompact || !['network', 'storage'].includes(g.id)
+ );
+
+ return (
+ <>
+ {heading}
+ {!info && {t('card.errors.noInformation')}
}
+ {info && (
+
+
+ {isCompactStorageVisible && }
+ {isCompactNetworkVisible && }
+
+
+ {displayedGraphs?.map((graph) => (
+
+ ))}
+
+
+ )}
+ >
+ );
+}
+
+const useDashDotInfo = ({ dashDotUrl }: { dashDotUrl: string }) => {
+ const { name: configName } = useConfigContext();
+ return useQuery({
+ refetchInterval: 50000,
+ queryKey: [
+ 'dashdot/info',
+ {
+ configName,
+ dashDotUrl,
+ },
+ ],
+ queryFn: () => fetchDashDotInfo(configName),
+ });
+};
+
+const fetchDashDotInfo = async (configName: string | undefined) => {
+ if (!configName) return {} as DashDotInfo;
+ return (await (
+ await axios.get('/api/modules/dashdot/info', { params: { configName } })
+ ).data) as DashDotInfo;
+};
+
+export const useDashDotTileStyles = createStyles(() => ({
+ graphsContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ rowGap: 10,
+ columnGap: 10,
+ },
+ graphsWrapper: {
+ '& > *:nth-child(odd):last-child': {
+ width: '100% !important',
+ maxWidth: '100% !important',
+ },
+ },
+}));
+
+export default definition;
diff --git a/src/widgets/dashDot/types.ts b/src/widgets/dashDot/types.ts
new file mode 100644
index 000000000..b11e4e520
--- /dev/null
+++ b/src/widgets/dashDot/types.ts
@@ -0,0 +1,6 @@
+export interface DashDotGraph {
+ id: string;
+ name: string;
+ twoSpan: boolean;
+ isMultiView: boolean | undefined;
+}
diff --git a/src/widgets/date/DateTile.tsx b/src/widgets/date/DateTile.tsx
new file mode 100644
index 000000000..b9f005873
--- /dev/null
+++ b/src/widgets/date/DateTile.tsx
@@ -0,0 +1,83 @@
+import { Stack, Text, Title } from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
+import { IconClock } from '@tabler/icons';
+import dayjs from 'dayjs';
+import { useEffect, useRef, useState } from 'react';
+import { useSetSafeInterval } from '../../hooks/useSetSafeInterval';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+
+const definition = defineWidget({
+ id: 'date',
+ icon: IconClock,
+ options: {
+ display24HourFormat: {
+ type: 'switch',
+ defaultValue: false,
+ },
+ },
+ gridstack: {
+ minWidth: 1,
+ minHeight: 1,
+ maxWidth: 12,
+ maxHeight: 12,
+ },
+ component: DateTile,
+});
+
+export type IDateWidget = IWidget;
+
+interface DateTileProps {
+ widget: IDateWidget;
+}
+
+function DateTile({ widget }: DateTileProps) {
+ const date = useDateState();
+ const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
+ const { width, height, ref } = useElementSize();
+
+ return (
+
+ {dayjs(date).format(formatString)}
+ {width > 200 && {dayjs(date).format('dddd, MMMM D')} }
+
+ );
+}
+
+/**
+ * State which updates when the minute is changing
+ * @returns current date updated every new minute
+ */
+const useDateState = () => {
+ const [date, setDate] = useState(new Date());
+ const setSafeInterval = useSetSafeInterval();
+ const timeoutRef = useRef(); // reference for initial timeout until first minute change
+ useEffect(() => {
+ timeoutRef.current = setTimeout(() => {
+ setDate(new Date());
+ // Starts intervall which update the date every minute
+ setSafeInterval(() => {
+ setDate(new Date());
+ }, 1000 * 60);
+ }, getMsUntilNextMinute());
+
+ return () => timeoutRef.current && clearTimeout(timeoutRef.current);
+ }, []);
+
+ return date;
+};
+
+// calculates the amount of milliseconds until next minute starts.
+const getMsUntilNextMinute = () => {
+ const now = new Date();
+ const nextMinute = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate(),
+ now.getHours(),
+ now.getMinutes() + 1
+ );
+ return nextMinute.getTime() - now.getTime();
+};
+
+export default definition;
diff --git a/src/widgets/helper.ts b/src/widgets/helper.ts
new file mode 100644
index 000000000..238fd88a4
--- /dev/null
+++ b/src/widgets/helper.ts
@@ -0,0 +1,8 @@
+// Method which allows to define the type verry specific and type checks all
+
+import { IWidgetDefinition } from './widgets';
+
+// The options of IWidgetDefinition are so heavily typed that it even used 'true' as type
+export const defineWidget = >(
+ options: TOptions
+) => options;
diff --git a/src/widgets/index.ts b/src/widgets/index.ts
new file mode 100644
index 000000000..bd8e0607f
--- /dev/null
+++ b/src/widgets/index.ts
@@ -0,0 +1,17 @@
+import date from './date/DateTile';
+import calendar from './calendar/CalendarTile';
+import dashdot from './dashDot/DashDotTile';
+import usenet from './useNet/UseNetTile';
+import weather from './weather/WeatherTile';
+import torrent from './torrent/TorrentTile';
+import torrentNetworkTraffic from './torrentNetworkTraffic/TorrentNetworkTrafficTile';
+
+export default {
+ calendar,
+ dashdot,
+ usenet,
+ weather,
+ 'torrents-status': torrent,
+ dlspeed: torrentNetworkTraffic,
+ date,
+};
diff --git a/src/widgets/torrent/TorrentQueueItem.tsx b/src/widgets/torrent/TorrentQueueItem.tsx
new file mode 100644
index 000000000..a6b8329f5
--- /dev/null
+++ b/src/widgets/torrent/TorrentQueueItem.tsx
@@ -0,0 +1,62 @@
+import { NormalizedTorrent } from '@ctrl/shared-torrent';
+import { Tooltip, Text, Progress, useMantineTheme } from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
+import { calculateETA } from '../../tools/calculateEta';
+import { humanFileSize } from '../../tools/humanFileSize';
+
+interface TorrentQueueItemProps {
+ torrent: NormalizedTorrent;
+}
+
+export const BitTorrrentQueueItem = ({ torrent }: TorrentQueueItemProps) => {
+ const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
+ const { width } = useElementSize();
+
+ const downloadSpeed = torrent.downloadSpeed / 1024 / 1024;
+ const uploadSpeed = torrent.uploadSpeed / 1024 / 1024;
+ const size = torrent.totalSelected;
+ return (
+
+
+
+
+ {torrent.name}
+
+
+
+
+ {humanFileSize(size)}
+
+ {width > MIN_WIDTH_MOBILE && (
+
+ {downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}
+
+ )}
+ {width > MIN_WIDTH_MOBILE && (
+
+ {uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}
+
+ )}
+ {width > MIN_WIDTH_MOBILE && (
+
+ {torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}
+
+ )}
+
+ {(torrent.progress * 100).toFixed(1)}%
+
+
+
+ );
+};
diff --git a/src/widgets/torrent/TorrentTile.tsx b/src/widgets/torrent/TorrentTile.tsx
new file mode 100644
index 000000000..89dd90b90
--- /dev/null
+++ b/src/widgets/torrent/TorrentTile.tsx
@@ -0,0 +1,182 @@
+import { NormalizedTorrent } from '@ctrl/shared-torrent';
+import {
+ Center,
+ Flex,
+ Group,
+ Loader,
+ ScrollArea,
+ Stack,
+ Table,
+ Text,
+ Title,
+ useMantineTheme,
+} from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
+import { IconFileDownload } from '@tabler/icons';
+import dayjs from 'dayjs';
+import duration from 'dayjs/plugin/duration';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import { useTranslation } from 'next-i18next';
+import { useEffect, useState } from 'react';
+import { useConfigContext } from '../../config/provider';
+import { useGetTorrentData } from '../../hooks/widgets/torrents/useGetTorrentData';
+import { AppIntegrationType } from '../../types/app';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+import { BitTorrrentQueueItem } from './TorrentQueueItem';
+
+dayjs.extend(duration);
+dayjs.extend(relativeTime);
+
+const downloadAppTypes: AppIntegrationType['type'][] = ['deluge', 'qBittorrent', 'transmission'];
+
+const definition = defineWidget({
+ id: 'torrents-status',
+ icon: IconFileDownload,
+ options: {
+ displayCompletedTorrents: {
+ type: 'switch',
+ defaultValue: true,
+ },
+ displayStaleTorrents: {
+ type: 'switch',
+ defaultValue: true,
+ },
+ refreshInterval: {
+ type: 'slider',
+ defaultValue: 10,
+ min: 1,
+ max: 60,
+ step: 1,
+ },
+ },
+ gridstack: {
+ minWidth: 2,
+ minHeight: 2,
+ maxWidth: 12,
+ maxHeight: 14,
+ },
+ component: TorrentTile,
+});
+
+export type ITorrent = IWidget;
+
+interface TorrentTileProps {
+ widget: ITorrent;
+}
+
+function TorrentTile({ widget }: TorrentTileProps) {
+ const { t } = useTranslation('modules/torrents-status');
+ const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
+ const { width } = useElementSize();
+
+ const { config } = useConfigContext();
+ const downloadApps =
+ config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ??
+ [];
+
+ const [selectedAppId, setSelectedApp] = useState(downloadApps[0]?.id);
+ const { data, isError, isInitialLoading, dataUpdatedAt } = useGetTorrentData({
+ appId: selectedAppId!,
+ refreshInterval: widget.properties.refreshInterval * 1000,
+ });
+
+ useEffect(() => {
+ if (!selectedAppId && downloadApps.length) {
+ setSelectedApp(downloadApps[0].id);
+ }
+ }, [downloadApps, selectedAppId]);
+
+ if (downloadApps.length === 0) {
+ return (
+
+ {t('card.errors.noDownloadClients.title')}
+
+ {t('card.errors.noDownloadClients.text')}
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ {t('card.errors.generic.title')}
+
+ {t('card.errors.generic.text')}
+
+
+ );
+ }
+
+ if (isInitialLoading) {
+ return (
+
+
+
+ {t('card.loading.title')}
+ Homarr is establishing a connection...
+
+
+ );
+ }
+
+ if (!data || data.length < 1) {
+ return (
+
+ {t('card.table.body.nothingFound')}
+
+ );
+ }
+
+ const filter = (torrent: NormalizedTorrent) => {
+ if (!widget.properties.displayCompletedTorrents && torrent.isCompleted) {
+ return false;
+ }
+
+ if (!widget.properties.displayStaleTorrents && !torrent.isCompleted && torrent.eta <= 0) {
+ return false;
+ }
+
+ return true;
+ };
+
+ const difference = new Date().getTime() - dataUpdatedAt;
+ const duration = dayjs.duration(difference, 'ms');
+ const humanizedDuration = duration.humanize();
+
+ return (
+
+
+
+
+
+ {t('card.table.header.name')}
+ {t('card.table.header.size')}
+ {width > MIN_WIDTH_MOBILE && {t('card.table.header.download')} }
+ {width > MIN_WIDTH_MOBILE && {t('card.table.header.upload')} }
+ {width > MIN_WIDTH_MOBILE && {t('card.table.header.estimatedTimeOfArrival')} }
+ {t('card.table.header.progress')}
+
+
+
+ {data.filter(filter).map((item: NormalizedTorrent, index: number) => (
+
+ ))}
+
+
+
+
+ Last updated {humanizedDuration} ago
+
+
+ );
+}
+
+export default definition;
diff --git a/src/modules/torrents/TotalDownloadsModule.tsx b/src/widgets/torrentNetworkTraffic/TorrentNetworkTrafficTile.tsx
similarity index 74%
rename from src/modules/torrents/TotalDownloadsModule.tsx
rename to src/widgets/torrentNetworkTraffic/TorrentNetworkTrafficTile.tsx
index 95f64852e..59a5e3ca6 100644
--- a/src/modules/torrents/TotalDownloadsModule.tsx
+++ b/src/widgets/torrentNetworkTraffic/TorrentNetworkTrafficTile.tsx
@@ -1,49 +1,58 @@
-import { Text, Title, Group, useMantineTheme, Box, Card, ColorSwatch, Stack } from '@mantine/core';
-import { IconDownload as Download } from '@tabler/icons';
-import { useEffect, useState } from 'react';
-import axios from 'axios';
import { NormalizedTorrent } from '@ctrl/shared-torrent';
-import { linearGradientDef } from '@nivo/core';
-import { useTranslation } from 'next-i18next';
-import { Datum, ResponsiveLine } from '@nivo/line';
+import { Text, Title, Group, useMantineTheme, Box, Card, ColorSwatch, Stack } from '@mantine/core';
import { useListState } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
-import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
-import { useConfig } from '../../tools/state';
+import { linearGradientDef } from '@nivo/core';
+import { Datum, ResponsiveLine } from '@nivo/line';
+import { IconArrowsUpDown } from '@tabler/icons';
+import axios from 'axios';
+import { useTranslation } from 'next-i18next';
+import { useEffect, useState } from 'react';
+import { useConfigContext } from '../../config/provider';
+import { useSetSafeInterval } from '../../hooks/useSetSafeInterval';
import { humanFileSize } from '../../tools/humanFileSize';
-import { IModule } from '../ModuleTypes';
-import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
-export const TotalDownloadsModule: IModule = {
- title: 'Download Speed',
- icon: Download,
- component: TotalDownloadsComponent,
+const definition = defineWidget({
id: 'dlspeed',
-};
+ icon: IconArrowsUpDown,
+ options: {},
-interface torrentHistory {
- x: number;
- up: number;
- down: number;
+ gridstack: {
+ minWidth: 2,
+ minHeight: 2,
+ maxWidth: 12,
+ maxHeight: 6,
+ },
+ component: TorrentNetworkTrafficTile,
+});
+
+export type ITorrentNetworkTraffic = IWidget;
+
+interface TorrentNetworkTrafficTileProps {
+ widget: ITorrentNetworkTraffic;
}
-export default function TotalDownloadsComponent() {
+function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTrafficTileProps) {
+ const { t } = useTranslation(`modules/${definition.id}`);
+ const { colors } = useMantineTheme();
const setSafeInterval = useSetSafeInterval();
- const { config } = useConfig();
- const downloadServices =
- config.services.filter(
- (service) =>
- service.type === 'qBittorrent' ||
- service.type === 'Transmission' ||
- service.type === 'Deluge'
- ) ?? [];
- const { t } = useTranslation(`modules/${TotalDownloadsModule.id}`);
+ const { configVersion, config } = useConfigContext();
- const [torrentHistory, torrentHistoryHandlers] = useListState([]);
+ const [torrentHistory, torrentHistoryHandlers] = useListState([]);
const [torrents, setTorrents] = useState([]);
+ const downloadServices =
+ config?.apps.filter(
+ (app) =>
+ app.integration.type === 'qBittorrent' ||
+ app.integration.type === 'transmission' ||
+ app.integration.type === 'deluge'
+ ) ?? [];
const totalDownloadSpeed = torrents.reduce((acc, torrent) => acc + torrent.downloadSpeed, 0);
const totalUploadSpeed = torrents.reduce((acc, torrent) => acc + torrent.uploadSpeed, 0);
+
useEffect(() => {
if (downloadServices.length === 0) return;
const interval = setSafeInterval(() => {
@@ -54,6 +63,7 @@ export default function TotalDownloadsComponent() {
setTorrents(response.data);
})
.catch((error) => {
+ if (error.status === 401) return;
setTorrents([]);
// eslint-disable-next-line no-console
console.error('Error while fetching torrents', error.response.data);
@@ -69,7 +79,7 @@ export default function TotalDownloadsComponent() {
clearInterval(interval);
});
}, 1000);
- }, [config.services]);
+ }, [configVersion]);
useEffect(() => {
torrentHistoryHandlers.append({
@@ -79,24 +89,6 @@ export default function TotalDownloadsComponent() {
});
}, [totalDownloadSpeed, totalUploadSpeed]);
- if (downloadServices.length === 0) {
- return (
-
- {t('card.errors.noDownloadClients.title')}
-
-
- {t('card.errors.noDownloadClients.text')}
-
-
- );
- }
-
- const theme = useMantineTheme();
- // Load the last 10 values from the history
const history = torrentHistory.slice(-10);
const chartDataUp = history.map((load, i) => ({
x: load.x,
@@ -112,13 +104,13 @@ export default function TotalDownloadsComponent() {
{t('card.lineChart.title')}
-
+
{t('card.lineChart.totalDownload', { download: humanFileSize(totalDownloadSpeed) })}
-
+
{t('card.lineChart.totalUpload', { upload: humanFileSize(totalUploadSpeed) })}
@@ -146,13 +138,13 @@ export default function TotalDownloadsComponent() {
-
+
{t('card.lineChart.download', { download: humanFileSize(Download) })}
-
+
{t('card.lineChart.upload', { upload: humanFileSize(Upload) })}
@@ -188,14 +180,17 @@ export default function TotalDownloadsComponent() {
]),
]}
fill={[{ match: '*', id: 'gradientA' }]}
- colors={[
- // Blue
- theme.colors.blue[5],
- // Green
- theme.colors.green[5],
- ]}
+ colors={[colors.blue[5], colors.green[5]]}
/>
);
}
+
+export default definition;
+
+interface TorrentHistory {
+ x: number;
+ up: number;
+ down: number;
+}
diff --git a/src/widgets/useNet/UseNetTile.tsx b/src/widgets/useNet/UseNetTile.tsx
new file mode 100644
index 000000000..832b8d2d7
--- /dev/null
+++ b/src/widgets/useNet/UseNetTile.tsx
@@ -0,0 +1,137 @@
+import {
+ Badge,
+ Button,
+ Group,
+ Select,
+ Stack,
+ Tabs,
+ Text,
+ Title,
+ useMantineTheme,
+} from '@mantine/core';
+import { IconFileDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
+import { useEffect, useState } from 'react';
+
+import { useElementSize } from '@mantine/hooks';
+import dayjs from 'dayjs';
+import duration from 'dayjs/plugin/duration';
+import { useTranslation } from 'next-i18next';
+import { useConfigContext } from '../../config/provider';
+import {
+ useGetUsenetInfo,
+ usePauseUsenetQueue,
+ useResumeUsenetQueue,
+} from '../../hooks/widgets/dashDot/api';
+import { humanFileSize } from '../../tools/humanFileSize';
+import { AppIntegrationType } from '../../types/app';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+import { UsenetHistoryList } from './UsenetHistoryList';
+import { UsenetQueueList } from './UsenetQueueList';
+
+dayjs.extend(duration);
+
+const downloadAppTypes: AppIntegrationType['type'][] = ['sabnzbd', 'nzbGet'];
+
+const definition = defineWidget({
+ id: 'usenet',
+ icon: IconFileDownload,
+ options: {},
+ component: UseNetTile,
+ gridstack: {
+ minWidth: 2,
+ minHeight: 3,
+ maxWidth: 12,
+ maxHeight: 12,
+ },
+});
+
+export type IUsenetWidget = IWidget;
+
+interface UseNetTileProps {
+ widget: IUsenetWidget;
+}
+
+function UseNetTile({ widget }: UseNetTileProps) {
+ const { t } = useTranslation('modules/usenet');
+ const { config } = useConfigContext();
+ const downloadApps =
+ config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ??
+ [];
+ const { ref, width, height } = useElementSize();
+ const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
+
+ const [selectedAppId, setSelectedApp] = useState(downloadApps[0]?.id);
+ const { data } = useGetUsenetInfo({ appId: selectedAppId! });
+
+ useEffect(() => {
+ if (!selectedAppId && downloadApps.length) {
+ setSelectedApp(downloadApps[0].id);
+ }
+ }, [downloadApps, selectedAppId]);
+
+ const { mutate: pause } = usePauseUsenetQueue({ appId: selectedAppId! });
+ const { mutate: resume } = useResumeUsenetQueue({ appId: selectedAppId! });
+
+ if (downloadApps.length === 0) {
+ return (
+
+ {t('card.errors.noDownloadClients.title')}
+
+ {t('card.errors.noDownloadClients.text')}
+
+
+ );
+ }
+
+ if (!selectedAppId) {
+ return null;
+ }
+
+ return (
+
+
+ {t('tabs.queue')}
+ {t('tabs.history')}
+ {data && (
+
+ {width > MIN_WIDTH_MOBILE && (
+ <>
+ {humanFileSize(data?.speed)}/s
+
+ {t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)}
+
+ >
+ )}
+
+ )}
+
+ {downloadApps.length > 1 && (
+ ({ value: app.id, label: app.name }))}
+ />
+ )}
+
+
+ {!data ? null : data.paused ? (
+ resume()} radius="xl" size="xs" fullWidth mt="sm">
+ {t('info.paused')}
+
+ ) : (
+ pause()} radius="xl" size="xs" fullWidth mt="sm">
+ {' '}
+ {dayjs.duration(data.eta, 's').format('HH:mm')}
+
+ )}
+
+
+
+
+
+ );
+}
+
+export default definition;
diff --git a/src/modules/usenet/UsenetHistoryList.tsx b/src/widgets/useNet/UsenetHistoryList.tsx
similarity index 76%
rename from src/modules/usenet/UsenetHistoryList.tsx
rename to src/widgets/useNet/UsenetHistoryList.tsx
index f886e5ad3..28ee10ccf 100644
--- a/src/modules/usenet/UsenetHistoryList.tsx
+++ b/src/widgets/useNet/UsenetHistoryList.tsx
@@ -5,37 +5,41 @@ import {
Group,
Pagination,
Skeleton,
+ Stack,
Table,
Text,
Title,
Tooltip,
} from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
import { IconAlertCircle } from '@tabler/icons';
import { AxiosError } from 'axios';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next';
import { FunctionComponent, useState } from 'react';
-import { useGetUsenetHistory } from '../../tools/hooks/api';
+import { useGetUsenetHistory } from '../../hooks/widgets/dashDot/api';
import { humanFileSize } from '../../tools/humanFileSize';
import { parseDuration } from '../../tools/parseDuration';
dayjs.extend(duration);
interface UsenetHistoryListProps {
- serviceId: string;
+ appId: string;
}
-const PAGE_SIZE = 10;
+const PAGE_SIZE = 13;
-export const UsenetHistoryList: FunctionComponent = ({ serviceId }) => {
+export const UsenetHistoryList: FunctionComponent = ({ appId }) => {
const [page, setPage] = useState(1);
const { t } = useTranslation(['modules/usenet', 'common']);
+ const { ref, width, height } = useElementSize();
+ const durationBreakpoint = 400;
const { data, isLoading, isError, error } = useGetUsenetHistory({
limit: PAGE_SIZE,
offset: (page - 1) * PAGE_SIZE,
- serviceId,
+ appId,
});
const totalPages = Math.ceil((data?.total || 1) / PAGE_SIZE);
@@ -77,18 +81,15 @@ export const UsenetHistoryList: FunctionComponent = ({ s
}
return (
- <>
-
-
-
-
-
-
+
+
{t('modules/usenet:history.header.name')}
- {t('modules/usenet:history.header.size')}
- {t('modules/usenet:history.header.duration')}
+ {t('modules/usenet:history.header.size')}
+ {durationBreakpoint < width ? (
+ {t('modules/usenet:history.header.duration')}
+ ) : null}
@@ -111,15 +112,18 @@ export const UsenetHistoryList: FunctionComponent = ({ s
{humanFileSize(history.size)}
-
- {parseDuration(history.time, t)}
-
+ {durationBreakpoint < width ? (
+
+ {parseDuration(history.time, t)}
+
+ ) : null}
))}
{totalPages > 1 && (
= ({ s
onChange={setPage}
/>
)}
- >
+
);
};
diff --git a/src/modules/usenet/UsenetQueueList.tsx b/src/widgets/useNet/UsenetQueueList.tsx
similarity index 66%
rename from src/modules/usenet/UsenetQueueList.tsx
rename to src/widgets/useNet/UsenetQueueList.tsx
index a000dbd20..5fec4f842 100644
--- a/src/modules/usenet/UsenetQueueList.tsx
+++ b/src/widgets/useNet/UsenetQueueList.tsx
@@ -1,44 +1,52 @@
import {
ActionIcon,
Alert,
+ Button,
Center,
Code,
Group,
Pagination,
Progress,
+ ScrollArea,
Skeleton,
+ Stack,
Table,
Text,
Title,
Tooltip,
useMantineTheme,
} from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
import { IconAlertCircle, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
import { AxiosError } from 'axios';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next';
import { FunctionComponent, useState } from 'react';
-import { useGetUsenetDownloads } from '../../tools/hooks/api';
+import { useGetUsenetDownloads } from '../../hooks/widgets/dashDot/api';
import { humanFileSize } from '../../tools/humanFileSize';
dayjs.extend(duration);
interface UsenetQueueListProps {
- serviceId: string;
+ appId: string;
}
-const PAGE_SIZE = 10;
+const PAGE_SIZE = 13;
-export const UsenetQueueList: FunctionComponent = ({ serviceId }) => {
+export const UsenetQueueList: FunctionComponent = ({ appId }) => {
const theme = useMantineTheme();
const { t } = useTranslation('modules/usenet');
+ const progressbarBreakpoint = theme.breakpoints.xs;
+ const progressBreakpoint = 400;
+ const sizeBreakpoint = 300;
+ const { ref, width } = useElementSize();
const [page, setPage] = useState(1);
const { data, isLoading, isError, error } = useGetUsenetDownloads({
limit: PAGE_SIZE,
offset: (page - 1) * PAGE_SIZE,
- serviceId,
+ appId,
});
const totalPages = Math.ceil((data?.total || 1) / PAGE_SIZE);
@@ -79,16 +87,23 @@ export const UsenetQueueList: FunctionComponent = ({ servi
);
}
+ // TODO: Set ScollArea dynamic height based on the widget size
return (
- <>
-
+
+
-
+
{t('queue.header.name')}
- {t('queue.header.size')}
- {t('queue.header.eta')}
- {t('queue.header.progress')}
+ {sizeBreakpoint < width ? (
+ {t('queue.header.size')}
+ ) : null}
+ {t('queue.header.eta')}
+ {progressBreakpoint < width ? (
+ width ? 100 : 200 }}>
+ {t('queue.header.progress')}
+
+ ) : null}
@@ -124,9 +139,11 @@ export const UsenetQueueList: FunctionComponent = ({ servi
-
- {humanFileSize(nzb.size)}
-
+ {sizeBreakpoint < width ? (
+
+ {humanFileSize(nzb.size)}
+
+ ) : null}
{nzb.eta <= 0 ? (
@@ -136,32 +153,36 @@ export const UsenetQueueList: FunctionComponent = ({ servi
{dayjs.duration(nzb.eta, 's').format('H:mm:ss')}
)}
-
-
- {nzb.progress.toFixed(1)}%
-
- 0 ? theme.primaryColor : 'lightgrey'}
- value={nzb.progress}
- size="lg"
- style={{ width: '100%' }}
- />
-
+ {progressBreakpoint < width ? (
+
+
+ {nzb.progress.toFixed(1)}%
+
+ {width > progressbarBreakpoint ? (
+ 0 ? theme.primaryColor : 'lightgrey'}
+ value={nzb.progress}
+ size="lg"
+ style={{ width: '100%' }}
+ />
+ ) : null}
+
+ ) : null}
))}
{totalPages > 1 && (
)}
- >
+
);
};
diff --git a/src/modules/usenet/types.ts b/src/widgets/useNet/types.ts
similarity index 100%
rename from src/modules/usenet/types.ts
rename to src/widgets/useNet/types.ts
diff --git a/src/widgets/weather/WeatherIcon.tsx b/src/widgets/weather/WeatherIcon.tsx
new file mode 100644
index 000000000..2a845b48e
--- /dev/null
+++ b/src/widgets/weather/WeatherIcon.tsx
@@ -0,0 +1,73 @@
+import { Box, Tooltip } from '@mantine/core';
+import {
+ IconCloud,
+ IconCloudFog,
+ IconCloudRain,
+ IconCloudSnow,
+ IconCloudStorm,
+ IconQuestionMark,
+ IconSnowflake,
+ IconSun,
+ TablerIcon,
+} from '@tabler/icons';
+import { useTranslation } from 'next-i18next';
+
+interface WeatherIconProps {
+ code: number;
+}
+
+/**
+ * Icon which should be displayed when specific code is defined
+ * @param code weather code from api
+ * @returns weather tile component
+ */
+export const WeatherIcon = ({ code }: WeatherIconProps) => {
+ const { t } = useTranslation('modules/weather');
+
+ const { icon: Icon, name } =
+ weatherDefinitions.find((wd) => wd.codes.includes(code)) ?? unknownWeather;
+
+ return (
+
+
+
+
+
+ );
+};
+
+type WeatherDefinitionType = { icon: TablerIcon; name: string; codes: number[] };
+
+// 0 Clear sky
+// 1, 2, 3 Mainly clear, partly cloudy, and overcast
+// 45, 48 Fog and depositing rime fog
+// 51, 53, 55 Drizzle: Light, moderate, and dense intensity
+// 56, 57 Freezing Drizzle: Light and dense intensity
+// 61, 63, 65 Rain: Slight, moderate and heavy intensity
+// 66, 67 Freezing Rain: Light and heavy intensity
+// 71, 73, 75 Snow fall: Slight, moderate, and heavy intensity
+// 77 Snow grains
+// 80, 81, 82 Rain showers: Slight, moderate, and violent
+// 85, 86Snow showers slight and heavy
+// 95 *Thunderstorm: Slight or moderate
+// 96, 99 *Thunderstorm with slight and heavy hail
+const weatherDefinitions: WeatherDefinitionType[] = [
+ { icon: IconSun, name: 'clear', codes: [0] },
+ { icon: IconCloud, name: 'mainlyClear', codes: [1, 2, 3] },
+ { icon: IconCloudFog, name: 'fog', codes: [45, 48] },
+ { icon: IconCloud, name: 'drizzle', codes: [51, 53, 55] },
+ { icon: IconSnowflake, name: 'freezingDrizzle', codes: [56, 57] },
+ { icon: IconCloudRain, name: 'rain', codes: [61, 63, 65] },
+ { icon: IconCloudRain, name: 'freezingRain', codes: [66, 67] },
+ { icon: IconCloudSnow, name: 'snowFall', codes: [71, 73, 75] },
+ { icon: IconCloudSnow, name: 'snowGrains', codes: [77] },
+ { icon: IconCloudRain, name: 'rainShowers', codes: [80, 81, 82] },
+ { icon: IconCloudSnow, name: 'snowShowers', codes: [85, 86] },
+ { icon: IconCloudStorm, name: 'thunderstorm', codes: [95] },
+ { icon: IconCloudStorm, name: 'thunderstormWithHail', codes: [96, 99] },
+];
+
+const unknownWeather: Omit = {
+ icon: IconQuestionMark,
+ name: 'unknown',
+};
diff --git a/src/widgets/weather/WeatherTile.tsx b/src/widgets/weather/WeatherTile.tsx
new file mode 100644
index 000000000..ad8efde4d
--- /dev/null
+++ b/src/widgets/weather/WeatherTile.tsx
@@ -0,0 +1,109 @@
+import { Center, Group, Skeleton, Stack, Text, Title } from '@mantine/core';
+import { useElementSize } from '@mantine/hooks';
+import { IconArrowDownRight, IconArrowUpRight, IconCloudRain } from '@tabler/icons';
+import { defineWidget } from '../helper';
+import { IWidget } from '../widgets';
+import { useWeatherForCity } from './useWeatherForCity';
+import { WeatherIcon } from './WeatherIcon';
+
+const definition = defineWidget({
+ id: 'weather',
+ icon: IconCloudRain,
+ options: {
+ displayInFahrenheit: {
+ type: 'switch',
+ defaultValue: false,
+ },
+ location: {
+ type: 'text',
+ defaultValue: 'Paris',
+ },
+ },
+ gridstack: {
+ minWidth: 1,
+ minHeight: 1,
+ maxWidth: 12,
+ maxHeight: 12,
+ },
+ component: WeatherTile,
+});
+
+export type IWeatherWidget = IWidget;
+
+interface WeatherTileProps {
+ widget: IWeatherWidget;
+}
+
+function WeatherTile({ widget }: WeatherTileProps) {
+ const { data: weather, isLoading, isError } = useWeatherForCity(widget.properties.location);
+ const { width, height, ref } = useElementSize();
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ An error occured
+
+ );
+ }
+
+ // TODO: add widgetWrapper that is generic and uses the definition
+ return (
+
+
+
+
+ {getPerferedUnit(
+ weather!.current_weather.temperature,
+ widget.properties.displayInFahrenheit
+ )}
+
+
+ {width > 200 && (
+
+
+ {getPerferedUnit(
+ weather!.daily.temperature_2m_max[0],
+ widget.properties.displayInFahrenheit
+ )}
+
+ {getPerferedUnit(
+ weather!.daily.temperature_2m_min[0],
+ widget.properties.displayInFahrenheit
+ )}
+
+ )}
+
+ );
+}
+
+const getPerferedUnit = (value: number, isFahrenheit = false): string =>
+ isFahrenheit ? `${(value * (9 / 5) + 32).toFixed(1)}°F` : `${value.toFixed(1)}°C`;
+
+export default definition;
diff --git a/src/modules/weather/WeatherInterface.ts b/src/widgets/weather/types.ts
similarity index 100%
rename from src/modules/weather/WeatherInterface.ts
rename to src/widgets/weather/types.ts
diff --git a/src/widgets/weather/useWeatherForCity.ts b/src/widgets/weather/useWeatherForCity.ts
new file mode 100644
index 000000000..a785c92eb
--- /dev/null
+++ b/src/widgets/weather/useWeatherForCity.ts
@@ -0,0 +1,53 @@
+import { useQuery } from '@tanstack/react-query';
+import { WeatherResponse } from './types';
+
+/**
+ * Requests the weather of the specified city
+ * @param cityName name of the city where the weather should be requested
+ * @returns weather of specified city
+ */
+export const useWeatherForCity = (cityName: string) => {
+ const {
+ data: city,
+ isLoading,
+ isError,
+ } = useQuery({ queryKey: ['weatherCity', { cityName }], queryFn: () => fetchCity(cityName) });
+ const weatherQuery = useQuery({
+ queryKey: ['weather', { cityName }],
+ queryFn: () => fetchWeather(city?.results[0]),
+ enabled: !!city,
+ refetchInterval: 1000 * 60 * 5, // requests the weather every 5 minutes
+ });
+
+ return {
+ ...weatherQuery,
+ isLoading: weatherQuery.isLoading || isLoading,
+ isError: weatherQuery.isError || isError,
+ };
+};
+
+/**
+ * Requests the coordinates of a city
+ * @param cityName name of city
+ * @returns list with all coordinates for citites with specified name
+ */
+const fetchCity = async (cityName: string) => {
+ const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${cityName}`);
+ return (await res.json()) as { results: Coordinates[] };
+};
+
+/**
+ * Requests the weather of specific coordinates
+ * @param coordinates of the location the weather should be fetched
+ * @returns weather of specified coordinates
+ */
+const fetchWeather = async (coordinates?: Coordinates) => {
+ if (!coordinates) return;
+ const { longitude, latitude } = coordinates;
+ const res = await fetch(
+ `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon`
+ );
+ return (await res.json()) as WeatherResponse;
+};
+
+type Coordinates = { latitude: number; longitude: number };
diff --git a/src/widgets/widgets.ts b/src/widgets/widgets.ts
new file mode 100644
index 000000000..2a084de3d
--- /dev/null
+++ b/src/widgets/widgets.ts
@@ -0,0 +1,101 @@
+import { TablerIcon } from '@tabler/icons';
+import React from 'react';
+import { AreaType } from '../types/area';
+import { ShapeType } from '../types/shape';
+
+// Type of widgets which are safed to config
+export type IWidget = {
+ id: TKey;
+ properties: {
+ [key in keyof TDefinition['options']]: MakeLessSpecific<
+ TDefinition['options'][key]['defaultValue']
+ >;
+ };
+ area: AreaType;
+ shape: ShapeType;
+};
+
+// Makes the type less specific
+// For example when the type true is used as input the result is boolean
+// By not using this type the definition would always be { property: true }
+type MakeLessSpecific = TInput extends boolean
+ ? boolean
+ : TInput extends number
+ ? number
+ : TInput extends string[]
+ ? string[]
+ : TInput extends string
+ ? string
+ : never;
+
+// Types of options that can be specified for the widget edit modal
+export type IWidgetOptionValue =
+ | IMultiSelectOptionValue
+ | ISwitchOptionValue
+ | ITextInputOptionValue
+ | ISliderInputOptionValue
+ | ISelectOptionValue
+ | INumberInputOptionValue;
+
+// Interface for data type
+interface DataType {
+ label: string;
+ value: string;
+}
+
+// will show a multi-select with specified data
+export type IMultiSelectOptionValue = {
+ type: 'multi-select';
+ defaultValue: string[];
+ data: DataType[];
+};
+
+// will show a multi-select with specified data
+export type ISelectOptionValue = {
+ type: 'select';
+ defaultValue: string;
+ data: DataType[];
+};
+
+// will show a switch
+export type ISwitchOptionValue = {
+ type: 'switch';
+ defaultValue: boolean;
+};
+
+// will show a text-input
+export type ITextInputOptionValue = {
+ type: 'text';
+ defaultValue: string;
+};
+
+// will show a number-input
+export type INumberInputOptionValue = {
+ type: 'number';
+ defaultValue: number;
+};
+
+// will show a slider-input
+export type ISliderInputOptionValue = {
+ type: 'slider';
+ defaultValue: number;
+ min: number;
+ max: number;
+ step: number;
+};
+
+// is used to type the widget definitions which will be used to display all widgets
+export type IWidgetDefinition = {
+ id: TKey;
+ icon: TablerIcon | string;
+ options: {
+ [key: string]: IWidgetOptionValue;
+ };
+ gridstack: {
+ minWidth: number;
+ minHeight: number;
+ maxWidth: number;
+ maxHeight: number;
+ };
+ component: React.ComponentType;
+};
diff --git a/tsconfig.json b/tsconfig.json
index 97fd02c6c..4cc7a24b0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,7 +9,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
- "forceConsistentCasingInFileNames": true,
+ "forceConsistentCasingInFileNames": false,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
diff --git a/yarn.lock b/yarn.lock
index 87dd94f19..08cd3cf7f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -24,58 +24,58 @@ __metadata:
languageName: node
linkType: hard
-"@babel/compat-data@npm:^7.18.8":
- version: 7.18.8
- resolution: "@babel/compat-data@npm:7.18.8"
- checksum: 3096aafad74936477ebdd039bcf342fba84eb3100e608f3360850fb63e1efa1c66037c4824f814d62f439ab47d25164439343a6e92e9b4357024fdf571505eb9
+"@babel/compat-data@npm:^7.20.0":
+ version: 7.20.5
+ resolution: "@babel/compat-data@npm:7.20.5"
+ checksum: 523790c43ef6388fae91d1ca9acf1ab0e1b22208dcd39c0e5e7a6adf0b48a133f1831be8d5931a72ecd48860f3e3fb777cb89840794abd8647a5c8e5cfab484e
languageName: node
linkType: hard
"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3":
- version: 7.18.9
- resolution: "@babel/core@npm:7.18.9"
+ version: 7.20.5
+ resolution: "@babel/core@npm:7.20.5"
dependencies:
"@ampproject/remapping": ^2.1.0
"@babel/code-frame": ^7.18.6
- "@babel/generator": ^7.18.9
- "@babel/helper-compilation-targets": ^7.18.9
- "@babel/helper-module-transforms": ^7.18.9
- "@babel/helpers": ^7.18.9
- "@babel/parser": ^7.18.9
- "@babel/template": ^7.18.6
- "@babel/traverse": ^7.18.9
- "@babel/types": ^7.18.9
+ "@babel/generator": ^7.20.5
+ "@babel/helper-compilation-targets": ^7.20.0
+ "@babel/helper-module-transforms": ^7.20.2
+ "@babel/helpers": ^7.20.5
+ "@babel/parser": ^7.20.5
+ "@babel/template": ^7.18.10
+ "@babel/traverse": ^7.20.5
+ "@babel/types": ^7.20.5
convert-source-map: ^1.7.0
debug: ^4.1.0
gensync: ^1.0.0-beta.2
json5: ^2.2.1
semver: ^6.3.0
- checksum: 64b9088b03fdf659b334864ef93bed85d60c17b27fcbd72970f8eb9e0d3266ffa5a1926960f648f2db36b0bafec615f947ea5117d200599a0661b9f0a9cdf323
+ checksum: 9547f1e6364bc58c3621e3b17ec17f0d034ff159e5a520091d9381608d40af3be4042dd27c20ad7d3e938422d75850ac56a3758d6801d65df701557af4bd244b
languageName: node
linkType: hard
-"@babel/generator@npm:^7.18.9, @babel/generator@npm:^7.7.2":
- version: 7.18.9
- resolution: "@babel/generator@npm:7.18.9"
+"@babel/generator@npm:^7.20.5, @babel/generator@npm:^7.7.2":
+ version: 7.20.5
+ resolution: "@babel/generator@npm:7.20.5"
dependencies:
- "@babel/types": ^7.18.9
+ "@babel/types": ^7.20.5
"@jridgewell/gen-mapping": ^0.3.2
jsesc: ^2.5.1
- checksum: 1c271e0c6f33e59f7845d88a1b0b9b0dce88164e80dec9274a716efa54c260e405e9462b160843e73f45382bf5b24d8e160e0121207e480c29b30e2ed0eb16d4
+ checksum: 31c10d1e122f08cf755a24bd6f5d197f47eceba03f1133759687d00ab72d210e60ba4011da42f368b6e9fa85cbfda7dc4adb9889c2c20cc5c34bb2d57c1deab7
languageName: node
linkType: hard
-"@babel/helper-compilation-targets@npm:^7.18.9":
- version: 7.18.9
- resolution: "@babel/helper-compilation-targets@npm:7.18.9"
+"@babel/helper-compilation-targets@npm:^7.20.0":
+ version: 7.20.0
+ resolution: "@babel/helper-compilation-targets@npm:7.20.0"
dependencies:
- "@babel/compat-data": ^7.18.8
+ "@babel/compat-data": ^7.20.0
"@babel/helper-validator-option": ^7.18.6
- browserslist: ^4.20.2
+ browserslist: ^4.21.3
semver: ^6.3.0
peerDependencies:
"@babel/core": ^7.0.0
- checksum: 2a9d71e124e098a9f45de4527ddd1982349d231827d341e00da9dfb967e260ecc7662c8b62abee4a010fb34d5f07a8d2155c974e0bc1928144cee5644910621d
+ checksum: bc183f2109648849c8fde0b3c5cf08adf2f7ad6dc617b546fd20f34c8ef574ee5ee293c8d1bd0ed0221212e8f5907cdc2c42097870f1dcc769a654107d82c95b
languageName: node
linkType: hard
@@ -86,13 +86,13 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-function-name@npm:^7.18.9":
- version: 7.18.9
- resolution: "@babel/helper-function-name@npm:7.18.9"
+"@babel/helper-function-name@npm:^7.19.0":
+ version: 7.19.0
+ resolution: "@babel/helper-function-name@npm:7.19.0"
dependencies:
- "@babel/template": ^7.18.6
- "@babel/types": ^7.18.9
- checksum: d04c44e0272f887c0c868651be7fc3c5690531bea10936f00d4cca3f6d5db65e76dfb49e8d553c42ae1fe1eba61ccce9f3d93ba2df50a66408c8d4c3cc61cf0c
+ "@babel/template": ^7.18.10
+ "@babel/types": ^7.19.0
+ checksum: eac1f5db428ba546270c2b8d750c24eb528b8fcfe50c81de2e0bdebf0e20f24bec688d4331533b782e4a907fad435244621ca2193cfcf80a86731299840e0f6e
languageName: node
linkType: hard
@@ -114,35 +114,35 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-module-transforms@npm:^7.18.9":
- version: 7.18.9
- resolution: "@babel/helper-module-transforms@npm:7.18.9"
+"@babel/helper-module-transforms@npm:^7.20.2":
+ version: 7.20.2
+ resolution: "@babel/helper-module-transforms@npm:7.20.2"
dependencies:
"@babel/helper-environment-visitor": ^7.18.9
"@babel/helper-module-imports": ^7.18.6
- "@babel/helper-simple-access": ^7.18.6
+ "@babel/helper-simple-access": ^7.20.2
"@babel/helper-split-export-declaration": ^7.18.6
- "@babel/helper-validator-identifier": ^7.18.6
- "@babel/template": ^7.18.6
- "@babel/traverse": ^7.18.9
- "@babel/types": ^7.18.9
- checksum: af08c60ea239ff3d40eda542fceaab69de17e713f131e80ead08c975ba7a47dd55d439cb48cfb14ae7ec96704a10c989ff5a5240e52a39101cb44a49467ce058
+ "@babel/helper-validator-identifier": ^7.19.1
+ "@babel/template": ^7.18.10
+ "@babel/traverse": ^7.20.1
+ "@babel/types": ^7.20.2
+ checksum: 33a60ca115f6fce2c9d98e2a2e5649498aa7b23e2ae3c18745d7a021487708fc311458c33542f299387a0da168afccba94116e077f2cce49ae9e5ab83399e8a2
languageName: node
linkType: hard
-"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.8.0":
- version: 7.18.9
- resolution: "@babel/helper-plugin-utils@npm:7.18.9"
- checksum: ebae876cd60f1fe238c7210986093845fa5c4cad5feeda843ea4d780bf068256717650376d3af2a5e760f2ed6a35c065ae144f99c47da3e54aa6cba99d8804e0
+"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.8.0":
+ version: 7.20.2
+ resolution: "@babel/helper-plugin-utils@npm:7.20.2"
+ checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b
languageName: node
linkType: hard
-"@babel/helper-simple-access@npm:^7.18.6":
- version: 7.18.6
- resolution: "@babel/helper-simple-access@npm:7.18.6"
+"@babel/helper-simple-access@npm:^7.20.2":
+ version: 7.20.2
+ resolution: "@babel/helper-simple-access@npm:7.20.2"
dependencies:
- "@babel/types": ^7.18.6
- checksum: 37cd36eef199e0517845763c1e6ff6ea5e7876d6d707a6f59c9267c547a50aa0e84260ba9285d49acfaf2cfa0a74a772d92967f32ac1024c961517d40b6c16a5
+ "@babel/types": ^7.20.2
+ checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1
languageName: node
linkType: hard
@@ -155,10 +155,17 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-validator-identifier@npm:^7.18.6":
- version: 7.18.6
- resolution: "@babel/helper-validator-identifier@npm:7.18.6"
- checksum: e295254d616bbe26e48c196a198476ab4d42a73b90478c9842536cf910ead887f5af6b5c4df544d3052a25ccb3614866fa808dc1e3a5a4291acd444e243c0648
+"@babel/helper-string-parser@npm:^7.19.4":
+ version: 7.19.4
+ resolution: "@babel/helper-string-parser@npm:7.19.4"
+ checksum: b2f8a3920b30dfac81ec282ac4ad9598ea170648f8254b10f475abe6d944808fb006aab325d3eb5a8ad3bea8dfa888cfa6ef471050dae5748497c110ec060943
+ languageName: node
+ linkType: hard
+
+"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1":
+ version: 7.19.1
+ resolution: "@babel/helper-validator-identifier@npm:7.19.1"
+ checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a
languageName: node
linkType: hard
@@ -169,14 +176,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helpers@npm:^7.18.9":
- version: 7.18.9
- resolution: "@babel/helpers@npm:7.18.9"
+"@babel/helpers@npm:^7.20.5":
+ version: 7.20.6
+ resolution: "@babel/helpers@npm:7.20.6"
dependencies:
- "@babel/template": ^7.18.6
- "@babel/traverse": ^7.18.9
- "@babel/types": ^7.18.9
- checksum: d0bd8255d36bfc65dc52ce75f7fea778c70287da2d64981db4c84fbdf9581409ecbd6433deff1c81da3a5acf26d7e4c364b3a4445efacf88f4f48e77c5b34d8d
+ "@babel/template": ^7.18.10
+ "@babel/traverse": ^7.20.5
+ "@babel/types": ^7.20.5
+ checksum: f03ec6eb2bf8dc7cdfe2569ee421fd9ba6c7bac6c862d90b608ccdd80281ebe858bc56ca175fc92b3ac50f63126b66bbd5ec86f9f361729289a20054518f1ac5
languageName: node
linkType: hard
@@ -191,12 +198,12 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.18.6, @babel/parser@npm:^7.18.9":
- version: 7.18.9
- resolution: "@babel/parser@npm:7.18.9"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.18.10, @babel/parser@npm:^7.20.5":
+ version: 7.20.5
+ resolution: "@babel/parser@npm:7.20.5"
bin:
parser: ./bin/babel-parser.js
- checksum: 81a966b334e3ef397e883c64026265a5ae0ad435a86f52a84f60a5ee1efc0738c1f42c55e0dc5f191cc6a83ba0c61350433eee417bf1dff160ca5f3cfde244c6
+ checksum: e8d514ce0aa74d56725bd102919a49fa367afef9cd8208cf52f670f54b061c4672f51b4b7980058ab1f5fe73615fe4dc90720ab47bbcebae07ad08d667eda318
languageName: node
linkType: hard
@@ -344,71 +351,79 @@ __metadata:
linkType: hard
"@babel/plugin-syntax-typescript@npm:^7.7.2":
- version: 7.18.6
- resolution: "@babel/plugin-syntax-typescript@npm:7.18.6"
+ version: 7.20.0
+ resolution: "@babel/plugin-syntax-typescript@npm:7.20.0"
dependencies:
- "@babel/helper-plugin-utils": ^7.18.6
+ "@babel/helper-plugin-utils": ^7.19.0
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 2cde73725ec51118ebf410bf02d78781c03fa4d3185993fcc9d253b97443381b621c44810084c5dd68b92eb8bdfae0e5b163e91b32bebbb33852383d1815c05d
+ checksum: 6189c0b5c32ba3c9a80a42338bd50719d783b20ef29b853d4f03929e971913d3cefd80184e924ae98ad6db09080be8fe6f1ffde9a6db8972523234f0274d36f7
languageName: node
linkType: hard
"@babel/runtime-corejs3@npm:^7.10.2":
- version: 7.18.9
- resolution: "@babel/runtime-corejs3@npm:7.18.9"
+ version: 7.20.6
+ resolution: "@babel/runtime-corejs3@npm:7.20.6"
dependencies:
- core-js-pure: ^3.20.2
- regenerator-runtime: ^0.13.4
- checksum: 249158b660ac996fa4f4b0d1ab5810db060af40fac4d0bb5da23f55539a151313ae254aa64afc2ab7000d95167824e21a689f74bc24b36fd0f5ca030d522133d
+ core-js-pure: ^3.25.1
+ regenerator-runtime: ^0.13.11
+ checksum: d533d432216509426c4f9dad56db2fe453112b7d738433111944372fba4abd0b07bee3261f19a218530b435de46592121b2a6a57b98c0c7c3452d552ba009c3e
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7":
- version: 7.18.9
- resolution: "@babel/runtime@npm:7.18.9"
+"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.19.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7":
+ version: 7.20.6
+ resolution: "@babel/runtime@npm:7.20.6"
dependencies:
- regenerator-runtime: ^0.13.4
- checksum: 36dd736baba7164e82b3cc9d43e081f0cb2d05ff867ad39cac515d99546cee75b7f782018b02a3dcf5f2ef3d27f319faa68965fdfec49d4912c60c6002353a2e
+ regenerator-runtime: ^0.13.11
+ checksum: 42a8504db21031b1859fbc0f52d698a3d2f5ada9519eb76c6f96a7e657d8d555732a18fe71ef428a67cc9fc81ca0d3562fb7afdc70549c5fec343190cbaa9b03
languageName: node
linkType: hard
-"@babel/template@npm:^7.18.6, @babel/template@npm:^7.3.3":
- version: 7.18.6
- resolution: "@babel/template@npm:7.18.6"
+"@babel/template@npm:^7.18.10, @babel/template@npm:^7.3.3":
+ version: 7.18.10
+ resolution: "@babel/template@npm:7.18.10"
dependencies:
"@babel/code-frame": ^7.18.6
- "@babel/parser": ^7.18.6
- "@babel/types": ^7.18.6
- checksum: cb02ed804b7b1938dbecef4e01562013b80681843dd391933315b3dd9880820def3b5b1bff6320d6e4c6a1d63d1d5799630d658ec6b0369c5505e7e4029c38fb
+ "@babel/parser": ^7.18.10
+ "@babel/types": ^7.18.10
+ checksum: 93a6aa094af5f355a72bd55f67fa1828a046c70e46f01b1606e6118fa1802b6df535ca06be83cc5a5e834022be95c7b714f0a268b5f20af984465a71e28f1473
languageName: node
linkType: hard
-"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.7.2":
- version: 7.18.9
- resolution: "@babel/traverse@npm:7.18.9"
+"@babel/traverse@npm:^7.20.1, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.7.2":
+ version: 7.20.5
+ resolution: "@babel/traverse@npm:7.20.5"
dependencies:
"@babel/code-frame": ^7.18.6
- "@babel/generator": ^7.18.9
+ "@babel/generator": ^7.20.5
"@babel/helper-environment-visitor": ^7.18.9
- "@babel/helper-function-name": ^7.18.9
+ "@babel/helper-function-name": ^7.19.0
"@babel/helper-hoist-variables": ^7.18.6
"@babel/helper-split-export-declaration": ^7.18.6
- "@babel/parser": ^7.18.9
- "@babel/types": ^7.18.9
+ "@babel/parser": ^7.20.5
+ "@babel/types": ^7.20.5
debug: ^4.1.0
globals: ^11.1.0
- checksum: 0445a51952ea1664a5719d9b1f8bf04be6f1933bcf54915fecc544c844a5dad2ac56f3b555723bbf741ef680d7fd64f6a5d69cfd08d518a4089c79a734270162
+ checksum: c7fed468614aab1cf762dda5df26e2cfcd2b1b448c9d3321ac44786c4ee773fb0e10357e6593c3c6a648ae2e0be6d90462d855998dc10e3abae84de99291e008
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3":
- version: 7.18.9
- resolution: "@babel/types@npm:7.18.9"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3":
+ version: 7.20.5
+ resolution: "@babel/types@npm:7.20.5"
dependencies:
- "@babel/helper-validator-identifier": ^7.18.6
+ "@babel/helper-string-parser": ^7.19.4
+ "@babel/helper-validator-identifier": ^7.19.1
to-fast-properties: ^2.0.0
- checksum: f0e0147267895fd8a5b82133e711ce7ce99941f3ce63647e0e3b00656a7afe48a8aa48edbae27543b701794d2b29a562a08f51f88f41df401abce7c3acc5e13a
+ checksum: 773f0a1ad9f6ca5c5beaf751d1d8d81b9130de87689d1321fc911d73c3b1167326d66f0ae086a27fb5bfc8b4ee3ffebf1339be50d3b4d8015719692468c31f2d
+ languageName: node
+ linkType: hard
+
+"@balena/dockerignore@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "@balena/dockerignore@npm:1.0.2"
+ checksum: 0d39f8fbcfd1a983a44bced54508471ab81aaaa40e2c62b46a9f97eac9d6b265790799f16919216db486331dedaacdde6ecbd6b7abe285d39bc50de111991699
languageName: node
linkType: hard
@@ -420,20 +435,20 @@ __metadata:
linkType: hard
"@ctrl/deluge@npm:^4.1.0":
- version: 4.1.0
- resolution: "@ctrl/deluge@npm:4.1.0"
+ version: 4.3.0
+ resolution: "@ctrl/deluge@npm:4.3.0"
dependencies:
"@ctrl/magnet-link": ^3.1.1
- "@ctrl/shared-torrent": ^4.1.1
- "@ctrl/url-join": ^2.0.0
- formdata-node: ^4.3.2
- got: ^12.1.0
- tough-cookie: ^4.0.0
- checksum: a17f974e1b98a9086e1036604a86d3e14b5cf9c8d0fd997357dd4522dc296f0ef92e2697231f97f7211c0224e35256af966f722b6b316a363533328908cd8d5e
+ "@ctrl/shared-torrent": ^4.3.1
+ "@ctrl/url-join": ^2.0.2
+ formdata-node: ^5.0.0
+ got: ^12.5.0
+ tough-cookie: ^4.1.2
+ checksum: d194c77286fc003b26b09ca4d40a2ca6e524868ce7eeac7ece7e22a192e55199ac0a557a36a507d3c652bd590b60f3d78e80238076a2417cd0727327dabc23c1
languageName: node
linkType: hard
-"@ctrl/magnet-link@npm:^3.1.0, @ctrl/magnet-link@npm:^3.1.1":
+"@ctrl/magnet-link@npm:^3.1.1":
version: 3.1.1
resolution: "@ctrl/magnet-link@npm:3.1.1"
dependencies:
@@ -457,12 +472,12 @@ __metadata:
languageName: node
linkType: hard
-"@ctrl/shared-torrent@npm:^4.1.1":
- version: 4.1.1
- resolution: "@ctrl/shared-torrent@npm:4.1.1"
+"@ctrl/shared-torrent@npm:^4.1.1, @ctrl/shared-torrent@npm:^4.3.1":
+ version: 4.3.2
+ resolution: "@ctrl/shared-torrent@npm:4.3.2"
dependencies:
- got: ^12.1.0
- checksum: 1273c9088a920eed5afca945b11e83a6b64d4268ad0b09e916e7e2214ea8092b998ab16525885f8f24af2c75893e3fd7d4542e7e9d6dfe4688da57e47c31b165
+ got: ^12.5.2
+ checksum: bd3ce95884d4c880df53c4b7a397cac84fc2ef6e02b6c5d818bbd3cc9cc4bcbe4e32c8da3a83ae6e40e5b4ac531ebd9d618579dcf5c6f48fa62cace43f223100
languageName: node
linkType: hard
@@ -476,25 +491,25 @@ __metadata:
linkType: hard
"@ctrl/transmission@npm:^4.1.1":
- version: 4.1.1
- resolution: "@ctrl/transmission@npm:4.1.1"
+ version: 4.3.0
+ resolution: "@ctrl/transmission@npm:4.3.0"
dependencies:
- "@ctrl/magnet-link": ^3.1.0
- "@ctrl/shared-torrent": ^4.1.1
- "@ctrl/url-join": ^2.0.0
- got: ^12.1.0
- checksum: 218ed4c00f70c46c90cd2a5e90f8390beee06a2cf7d76c2445ad2bcfb89ad1e6ea9cf237a7b3aa990fdf81fc9b9d4aa9900fa21e041457e8bb177dbd0b319b0a
+ "@ctrl/magnet-link": ^3.1.1
+ "@ctrl/shared-torrent": ^4.3.1
+ "@ctrl/url-join": ^2.0.2
+ got: ^12.5.0
+ checksum: 0432ab2d2ead0d4fd6d5078769e87f94024cab5e400084bc43f0472cfa6b8f93b9e5f560721386f5682ae299b8f1fd519a78743d1c026911ead4a19e88ef2cfc
languageName: node
linkType: hard
"@ctrl/ts-base32@npm:^2.1.1":
- version: 2.1.1
- resolution: "@ctrl/ts-base32@npm:2.1.1"
- checksum: 4b10fdb37d9893db157affc217d677bc12a1c051a519aab806ab30182847ead2f04d388e35ed1c3db2d6f5ea87891f394f3212ea1ca27909ec7183a0aeac90f1
+ version: 2.1.2
+ resolution: "@ctrl/ts-base32@npm:2.1.2"
+ checksum: 0fdb81836be455606e8ce9e859f6f69428373ec16d3ae18e04aac73ffc547e10ca9ace46d1287e022d9b5962a6dde9703674872461aa9f422321c1dbe0f0a62c
languageName: node
linkType: hard
-"@ctrl/url-join@npm:^2.0.0, @ctrl/url-join@npm:^2.0.2":
+"@ctrl/url-join@npm:^2.0.2":
version: 2.0.2
resolution: "@ctrl/url-join@npm:2.0.2"
checksum: 12407e934055db8e04987371944e8814a54ed6e60e3206944bf5ba94282edab5ece6e29a9c3744237fcdeba235067f660a99f9068dafc881cffc73a2d4f3544e
@@ -513,16 +528,16 @@ __metadata:
linkType: hard
"@dnd-kit/core@npm:^6.0.5":
- version: 6.0.5
- resolution: "@dnd-kit/core@npm:6.0.5"
+ version: 6.0.6
+ resolution: "@dnd-kit/core@npm:6.0.6"
dependencies:
"@dnd-kit/accessibility": ^3.0.0
- "@dnd-kit/utilities": ^3.2.0
+ "@dnd-kit/utilities": ^3.2.1
tslib: ^2.0.0
peerDependencies:
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: 09061e741bea9d2ba3987d01bb760c354fe29ca27996e74da3774e68c4783cf09ad21087703b5ffb036d3c2f86d16bea3c9f24466c2f266c8698f1e76c29d386
+ checksum: 2945bd5438630748fa3c11390afd9919eb9836cb64bf4c4c3afae0ccb99d32e1df56a387e59cb16800ec0ae047222fb9e40ecb9363ec4cca375c2b81d4e9784d
languageName: node
linkType: hard
@@ -539,14 +554,14 @@ __metadata:
languageName: node
linkType: hard
-"@dnd-kit/utilities@npm:^3.2.0":
- version: 3.2.0
- resolution: "@dnd-kit/utilities@npm:3.2.0"
+"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1":
+ version: 3.2.1
+ resolution: "@dnd-kit/utilities@npm:3.2.1"
dependencies:
tslib: ^2.0.0
peerDependencies:
react: ">=16.8.0"
- checksum: 575e554992c5ff26622854b889b6288580f308e1125620a4aae55d0a113d47f05fdb7c6e2023ce51c58d5a206091fc1deac868222dd5f9c35c80c3e2f99db9af
+ checksum: 038fd5cc1328bf4c9dca17cd48046e5a687bbf9d904c7197f851aab869ab52d9dee2734b2e255256fd6158245acd00063a23deed962c7673c0fadfbf061f04ca
languageName: node
linkType: hard
@@ -706,36 +721,36 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/eslintrc@npm:^1.3.0":
- version: 1.3.0
- resolution: "@eslint/eslintrc@npm:1.3.0"
+"@eslint/eslintrc@npm:^1.3.3":
+ version: 1.3.3
+ resolution: "@eslint/eslintrc@npm:1.3.3"
dependencies:
ajv: ^6.12.4
debug: ^4.3.2
- espree: ^9.3.2
+ espree: ^9.4.0
globals: ^13.15.0
ignore: ^5.2.0
import-fresh: ^3.2.1
js-yaml: ^4.1.0
minimatch: ^3.1.2
strip-json-comments: ^3.1.1
- checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515
+ checksum: f03e9d6727efd3e0719da2051ea80c0c73d20e28c171121527dbb868cd34232ca9c1d0525a66e517a404afea26624b1e47895b6a92474678418c2f50c9566694
languageName: node
linkType: hard
-"@floating-ui/core@npm:^1.0.1":
- version: 1.0.1
- resolution: "@floating-ui/core@npm:1.0.1"
- checksum: c8a5f1a491788e5bebfe747e9372df2c7cbee0d8790ddf95e25149ac91ccf1a2cca8f768029826cfd3d687617c1d0f3241b97f1648bdf2a28d421f39e79c2eee
- languageName: node
- linkType: hard
-
-"@floating-ui/dom@npm:^1.0.0":
+"@floating-ui/core@npm:^1.0.4":
version: 1.0.4
- resolution: "@floating-ui/dom@npm:1.0.4"
+ resolution: "@floating-ui/core@npm:1.0.4"
+ checksum: 6362a625ad0dba7cfd197fc4b6f620eed95147d38684d7347287569897098862c6ba1173f3758d76d22e7739112c57dc836a569c582e78f5807c26ef41df4989
+ languageName: node
+ linkType: hard
+
+"@floating-ui/dom@npm:^1.0.5":
+ version: 1.0.9
+ resolution: "@floating-ui/dom@npm:1.0.9"
dependencies:
- "@floating-ui/core": ^1.0.1
- checksum: f038ad74c8c0d4e3668705396c9955018ec4fce5de4a3ebc0d2317fa10a0dbae3ff6c9d331423014ab7558864977a83a1664be2800cb1a1d57cd35647a3d4907
+ "@floating-ui/core": ^1.0.4
+ checksum: 188ce7fe6d4e98c492dbe88e3f9d36bf8fcd341506c061c80ead742d81ec818b3149510d013e32dcc1adf70c3bc39e4b77b132a624a522d70dee6b5e4032727d
languageName: node
linkType: hard
@@ -753,14 +768,14 @@ __metadata:
linkType: hard
"@floating-ui/react-dom@npm:^1.0.0":
- version: 1.0.0
- resolution: "@floating-ui/react-dom@npm:1.0.0"
+ version: 1.0.1
+ resolution: "@floating-ui/react-dom@npm:1.0.1"
dependencies:
- "@floating-ui/dom": ^1.0.0
+ "@floating-ui/dom": ^1.0.5
peerDependencies:
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: d79e3c8570b0eaef5e968cd9c69de2c6bb372a5316e4e69a1cd12dae81771863ef430e81b74a0c21cefba663a2f971d8540c7294d7fb3b9807e32ba2a00c1e76
+ checksum: 74b80fd46f2f301f444af458e5bbdb0ef0321786aca15dd892c45ce4077a500dfc84c117cb1669360ecc1378346eff77886f1b0dd9b1adbb30e72f894fe27dfb
languageName: node
linkType: hard
@@ -771,14 +786,21 @@ __metadata:
languageName: node
linkType: hard
-"@humanwhocodes/config-array@npm:^0.9.2":
- version: 0.9.5
- resolution: "@humanwhocodes/config-array@npm:0.9.5"
+"@humanwhocodes/config-array@npm:^0.11.6":
+ version: 0.11.7
+ resolution: "@humanwhocodes/config-array@npm:0.11.7"
dependencies:
"@humanwhocodes/object-schema": ^1.2.1
debug: ^4.1.1
- minimatch: ^3.0.4
- checksum: 8ba6281bc0590f6c6eadeefc14244b5a3e3f5903445aadd1a32099ed80e753037674026ce1b3c945ab93561bea5eb29e3c5bff67060e230c295595ba517a3492
+ minimatch: ^3.0.5
+ checksum: cf506dc45d9488af7fbf108ea6ac2151ba1a25e6d2b94b9b4fc36d2c1e4099b89ff560296dbfa13947e44604d4ca4a90d97a4fb167370bf8dd01a6ca2b6d83ac
+ languageName: node
+ linkType: hard
+
+"@humanwhocodes/module-importer@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "@humanwhocodes/module-importer@npm:1.0.1"
+ checksum: 0fd22007db8034a2cdf2c764b140d37d9020bbfce8a49d3ec5c05290e77d4b0263b1b972b752df8c89e5eaa94073408f2b7d977aed131faf6cf396ebb5d7fb61
languageName: node
linkType: hard
@@ -1061,7 +1083,7 @@ __metadata:
languageName: node
linkType: hard
-"@jridgewell/resolve-uri@npm:^3.0.3":
+"@jridgewell/resolve-uri@npm:3.1.0":
version: 3.1.0
resolution: "@jridgewell/resolve-uri@npm:3.1.0"
checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267
@@ -1075,7 +1097,7 @@ __metadata:
languageName: node
linkType: hard
-"@jridgewell/sourcemap-codec@npm:^1.4.10":
+"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10":
version: 1.4.14
resolution: "@jridgewell/sourcemap-codec@npm:1.4.14"
checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97
@@ -1083,172 +1105,172 @@ __metadata:
linkType: hard
"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.9":
- version: 0.3.14
- resolution: "@jridgewell/trace-mapping@npm:0.3.14"
+ version: 0.3.17
+ resolution: "@jridgewell/trace-mapping@npm:0.3.17"
dependencies:
- "@jridgewell/resolve-uri": ^3.0.3
- "@jridgewell/sourcemap-codec": ^1.4.10
- checksum: b9537b9630ffb631aef9651a085fe361881cde1772cd482c257fe3c78c8fd5388d681f504a9c9fe1081b1c05e8f75edf55ee10fdb58d92bbaa8dbf6a7bd6b18c
+ "@jridgewell/resolve-uri": 3.1.0
+ "@jridgewell/sourcemap-codec": 1.4.14
+ checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339
languageName: node
linkType: hard
-"@mantine/carousel@npm:^5.1.0":
- version: 5.1.0
- resolution: "@mantine/carousel@npm:5.1.0"
+"@mantine/carousel@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/carousel@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.1.0
+ "@mantine/utils": 5.9.3
peerDependencies:
- "@mantine/core": 5.1.0
- "@mantine/hooks": 5.1.0
- embla-carousel-react: 7.0.0
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
+ embla-carousel-react: ^7.0.0
react: ">=16.8.0"
- checksum: 0aa6beda2c1c406ea1cf07ece919999a7591b838579c1a0b90f88d1491d75f0871418deb700976791e14d870543f7594ea28569af13b330444ed8c810ab47ac3
+ checksum: 4c64a170d3dacfe94c2bfdd40f21f2eeb48e91df4fcfe4b06c0990a19ec787d36bb9c7eaf83632aead451e1f45e438144a484e50d5ccd5a3314e61c9169d5a2e
languageName: node
linkType: hard
-"@mantine/core@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/core@npm:5.7.2"
+"@mantine/core@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/core@npm:5.9.3"
dependencies:
"@floating-ui/react-dom-interactions": ^0.10.1
- "@mantine/styles": 5.7.2
- "@mantine/utils": 5.7.2
- "@radix-ui/react-scroll-area": 1.0.0
+ "@mantine/styles": 5.9.3
+ "@mantine/utils": 5.9.3
+ "@radix-ui/react-scroll-area": 1.0.2
react-textarea-autosize: 8.3.4
peerDependencies:
- "@mantine/hooks": 5.7.2
+ "@mantine/hooks": 5.9.3
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: 54a5460d4367179d4016068e6cdfdd6d6776921ff8e0c24926881564655ec0255588a842e166fb754685bf31fb485160a1759d07e0435a5620307b771dce65c3
+ checksum: 8a84074c5af3607034fc8b31a73b04281e7b62e421395d884e392e74b966b616d9f01211ec35d7d491effaa04e0a02f7c69cd2b70813ebc2cb21e34d31afd0f2
languageName: node
linkType: hard
-"@mantine/dates@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/dates@npm:5.7.2"
+"@mantine/dates@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/dates@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.7.2
+ "@mantine/utils": 5.9.3
peerDependencies:
- "@mantine/core": 5.7.2
- "@mantine/hooks": 5.7.2
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
dayjs: ">=1.0.0"
react: ">=16.8.0"
- checksum: 76362eba6e51ab95e2343482203598a17b62aad510620cc5bf7f3a703b808c0ded61e995c70df80d09a1c36e15f76acc66dd589b9e3b0d32f1bc5246c705be31
+ checksum: fc7c8d19ab10c1d997a882debae74f21fa3a2a59ee834b901713e5ddd9feba7197f61fb68a9a27794071d83d7690564fc45793a6966eebf5f55dff368f837aee
languageName: node
linkType: hard
-"@mantine/dropzone@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/dropzone@npm:5.7.2"
+"@mantine/dropzone@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/dropzone@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.7.2
+ "@mantine/utils": 5.9.3
react-dropzone: 14.2.3
peerDependencies:
- "@mantine/core": 5.7.2
- "@mantine/hooks": 5.7.2
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: 604c0adb49ee4939c739f19ec86ff1dbc27d59b46fc13f3ab2ff5cebd7cd6eae39936f54760db362d95a70a0b9b2c89af558a2acbd26ef1fa36b20a67803be5f
+ checksum: 4409c1a302ff7a964045cdd1c5aa17db6d6c0554a67d85f900424a4503f19022488eaf76a83c51e0d76c3c2477498e606bf9c3b2b7897480cdfdd6de9b2038cb
languageName: node
linkType: hard
-"@mantine/form@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/form@npm:5.7.2"
+"@mantine/form@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/form@npm:5.9.3"
dependencies:
fast-deep-equal: ^3.1.3
klona: ^2.0.5
peerDependencies:
react: ">=16.8.0"
- checksum: 34d236411a0fbe32c194a2f5157246854446380b41e970f341703137d1422a6c43ae307175dd4c0d2c0512541e42aa1636ecd56d5550d6714c51f1f4890a592c
+ checksum: e531b059c2a80a4286c62b722b17df1c13f7e6a76341692cdf8c9fa6b25e45555a5998185364d362320a83bed35a6f824c6a30cc94bc44d277140ac62a263a5a
languageName: node
linkType: hard
-"@mantine/hooks@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/hooks@npm:5.7.2"
+"@mantine/hooks@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/hooks@npm:5.9.3"
peerDependencies:
react: ">=16.8.0"
- checksum: 85977a0f3968be3ab5556e79cd378f210be3ffa0b4b3c59e995848ec05d8a8c9c340d2bfcbd1900fe05495a8c2a977653233f793a304984f5dc1f626d4d6b386
+ checksum: 53ceb36bad2b5eeeca5ce612116cd2089ea31cfc93b7d0280c4917b90592dea8d9c584e08d2eb907553060750810b7601170d1f3221f0504b847a68f28854624
languageName: node
linkType: hard
-"@mantine/modals@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/modals@npm:5.7.2"
+"@mantine/modals@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/modals@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.7.2
+ "@mantine/utils": 5.9.3
peerDependencies:
- "@mantine/core": 5.7.2
- "@mantine/hooks": 5.7.2
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: f233d6aa976e519bde789534801395c1f9d3e84c14893b992ffef4e3ebcde5aa4b2b0360d4b33b98681992a72ce13fdc0f7591afd80bb58accb3c63260a07fa8
+ checksum: 8bad3c4542a937e529c5ebe4161ccf43c7b45288ef5024789c534c4e4dda36e67e9820f563eccda7aa47375002423c0d6c4b99ddbd4a0d1fb72bb8af09ce39b8
languageName: node
linkType: hard
-"@mantine/next@npm:^5.2.3":
- version: 5.2.3
- resolution: "@mantine/next@npm:5.2.3"
+"@mantine/next@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/next@npm:5.9.3"
dependencies:
- "@mantine/ssr": 5.2.3
- "@mantine/styles": 5.2.3
+ "@mantine/ssr": 5.9.3
+ "@mantine/styles": 5.9.3
peerDependencies:
next: "*"
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: bd77c1f8c4a2605ab3a16436b33685b93fb5c91bd82984868fbce43c4566b3a28c00e80adc5f9a454ada6614e5f1c8aa948f64871eab534468881ff47d86bbd7
+ checksum: 336cdf9732f64202498b33f725bdf51e27eb3b322e8176b7d7427fb595689e26049d3ebb775ba65882619f4048ed1a8bdca17893bdd560ab65c76551de016f21
languageName: node
linkType: hard
-"@mantine/notifications@npm:^5.7.2":
- version: 5.7.2
- resolution: "@mantine/notifications@npm:5.7.2"
+"@mantine/notifications@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/notifications@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.7.2
+ "@mantine/utils": 5.9.3
react-transition-group: 4.4.2
peerDependencies:
- "@mantine/core": 5.7.2
- "@mantine/hooks": 5.7.2
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: db1a8e343bda01dec80a9eb336daaa183ea73b8a8eed20cf80a5ef15b620f79488041dd492a68bd7460c0d9b2432a2f71ccc049bc44894c4c8837c5c078a5c3e
+ checksum: de92616f88b79271080f02edaaa92c9e76a85e5dad2831c747cbde312e23336351ee12f4179bb57b8a54d628644d4f8a7843b8358179bf5f657c2a995e5a1c6f
languageName: node
linkType: hard
-"@mantine/prism@npm:^5.0.0":
- version: 5.0.0
- resolution: "@mantine/prism@npm:5.0.0"
+"@mantine/prism@npm:^5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/prism@npm:5.9.3"
dependencies:
- "@mantine/utils": 5.0.0
+ "@mantine/utils": 5.9.3
prism-react-renderer: ^1.2.1
peerDependencies:
- "@mantine/core": 5.0.0
- "@mantine/hooks": 5.0.0
+ "@mantine/core": 5.9.3
+ "@mantine/hooks": 5.9.3
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: 2d0420424b0f0b32bb299cb15a856e97edb941fdc0bfba93508e9ce71e99a50a37a5d24951f48d04d0d10754bd4834d1247345456c2f34876f4fe3e106b773c1
+ checksum: 5d5b66a13ff2260cc150f7252eb0a8c581e062df347ebb842ddcdb0bbddc255373861e9e824697aaf6414dc0062b849db7d2dd9088a20719a420e24bc1a8ef7c
languageName: node
linkType: hard
-"@mantine/ssr@npm:5.2.3":
- version: 5.2.3
- resolution: "@mantine/ssr@npm:5.2.3"
+"@mantine/ssr@npm:5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/ssr@npm:5.9.3"
dependencies:
- "@mantine/styles": 5.2.3
+ "@mantine/styles": 5.9.3
html-react-parser: 1.4.12
peerDependencies:
"@emotion/react": ">=11.9.0"
"@emotion/server": ">=11.4.0"
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: f9212233489b3d88198756646db6013fe1e9dce62619254431c5f029a3ff61da057993daecf10ffafe55f14f75ac78f85d0efbdacfff50f34df492bb13e81413
+ checksum: 44cc9f8d1ae122c563251be2e50f175dd61b85dda5354d68a66870ebd49b21b593b061d79245954d02fd8ad736e3cf6590e4dff5a177ec74d5498401178ce800
languageName: node
linkType: hard
-"@mantine/styles@npm:5.2.3":
- version: 5.2.3
- resolution: "@mantine/styles@npm:5.2.3"
+"@mantine/styles@npm:5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/styles@npm:5.9.3"
dependencies:
clsx: 1.1.1
csstype: 3.0.9
@@ -1256,60 +1278,28 @@ __metadata:
"@emotion/react": ">=11.9.0"
react: ">=16.8.0"
react-dom: ">=16.8.0"
- checksum: 39e9e3c7462386da8106260787e0537b767e54b8b01b5077b3e371c9026ad78228e8bea801d4d3968faca6254e509bdd3ca783eba8a62be0d011f585bdf6019e
+ checksum: 8ba6db16ecc62d5c5128b4e72b6ce8358b4dd3ef89e53b7deaa510d1d7289641b39b1cff2551c00d66791b41e33e6e5d8f213a37d4d10b006fa7329e3c0e31d5
languageName: node
linkType: hard
-"@mantine/styles@npm:5.7.2":
- version: 5.7.2
- resolution: "@mantine/styles@npm:5.7.2"
- dependencies:
- clsx: 1.1.1
- csstype: 3.0.9
- peerDependencies:
- "@emotion/react": ">=11.9.0"
- react: ">=16.8.0"
- react-dom: ">=16.8.0"
- checksum: d440f92b830e232b4826e0ff8002f4821d5eee0f379d8e9e5cea9346ca550719b89aa850d5d06f0feebd6150172a398136391285d57e7174180bfa8294bbba65
- languageName: node
- linkType: hard
-
-"@mantine/utils@npm:5.0.0":
- version: 5.0.0
- resolution: "@mantine/utils@npm:5.0.0"
+"@mantine/utils@npm:5.9.3":
+ version: 5.9.3
+ resolution: "@mantine/utils@npm:5.9.3"
peerDependencies:
react: ">=16.8.0"
- checksum: 7d167dd40e02e6f2cb830ede1aad091af7bf9eb42fa1b47a08451286e98d3fa1a88f49a3e179d7969be450f70fc3d02f9851b8655d310499fa6bc431978ed232
- languageName: node
- linkType: hard
-
-"@mantine/utils@npm:5.1.0":
- version: 5.1.0
- resolution: "@mantine/utils@npm:5.1.0"
- peerDependencies:
- react: ">=16.8.0"
- checksum: f6d2dd28f97d9e2d09eea3db7d1f2e0a82c451bd7a0c80f9a38c421c7003052b822917d4128a055e39c5822c6de61ecb53728aa86ab726bf4dcda911ab47f650
- languageName: node
- linkType: hard
-
-"@mantine/utils@npm:5.7.2":
- version: 5.7.2
- resolution: "@mantine/utils@npm:5.7.2"
- peerDependencies:
- react: ">=16.8.0"
- checksum: 35ce46a03a24f8f2b649b833b725120e50e639a66a3a9b399a10f5c4083446a4338ececd8b1d6f0897743cb1bc6d6757c7a530c515f032c0ab23b4f8e9d04b2a
+ checksum: 5cdb34ce05213636f396fa85d0a03fcd13a24e5e87a85e87719a213745ca5cd98d9e143630d9b8d3b5029de3a9694df16ee6c28c3d668f8f6012815600f42b44
languageName: node
linkType: hard
"@motionone/animation@npm:^10.12.0":
- version: 10.13.1
- resolution: "@motionone/animation@npm:10.13.1"
+ version: 10.15.1
+ resolution: "@motionone/animation@npm:10.15.1"
dependencies:
- "@motionone/easing": ^10.13.1
- "@motionone/types": ^10.13.0
- "@motionone/utils": ^10.13.1
+ "@motionone/easing": ^10.15.1
+ "@motionone/types": ^10.15.1
+ "@motionone/utils": ^10.15.1
tslib: ^2.3.1
- checksum: bf3f95f1c100a18e170fd55fa3d16c6f674fe02dd0f344e78a8ae7e6ffac3510612e9f0b7a5a73fae3bb91b7c3d4e287f605e4feeb8a92538c3588d3ab8a4a70
+ checksum: 75b7a1e6c47c27073a578eb5559ea0a6e7075862c72e1eb1598403c8c2725f596a95b0369514c9e72f3c7439a9845c468b85a14d4e500df48e09d01b0739d4a7
languageName: node
linkType: hard
@@ -1327,51 +1317,51 @@ __metadata:
languageName: node
linkType: hard
-"@motionone/easing@npm:^10.13.1":
- version: 10.13.1
- resolution: "@motionone/easing@npm:10.13.1"
+"@motionone/easing@npm:^10.15.1":
+ version: 10.15.1
+ resolution: "@motionone/easing@npm:10.15.1"
dependencies:
- "@motionone/utils": ^10.13.1
+ "@motionone/utils": ^10.15.1
tslib: ^2.3.1
- checksum: 04d3c2d1458795d207067a8341ac23b5037b11d9e7160f91bb953438551255d072012dd22aaed678d0f88792143ad16c9566e131003ec047ef7938529a27b485
+ checksum: cf7cfcf9917525d892334c58282425aafc69d9ab9004c190bfa7cf91317a680e8143f227adc79557424e7f26cdf8478dcbb2ae467e744cebc58195d6f0b8153a
languageName: node
linkType: hard
"@motionone/generators@npm:^10.12.0":
- version: 10.13.1
- resolution: "@motionone/generators@npm:10.13.1"
+ version: 10.15.1
+ resolution: "@motionone/generators@npm:10.15.1"
dependencies:
- "@motionone/types": ^10.13.0
- "@motionone/utils": ^10.13.1
+ "@motionone/types": ^10.15.1
+ "@motionone/utils": ^10.15.1
tslib: ^2.3.1
- checksum: d4b91d0352c00275644c5e33bf031545212f94821aa6f7fdc26fa92f054138c76ff89c77a3b10ca167b447e4cf7c019a9628688c9635a21528da2ea260724fbc
+ checksum: 0eb6797a64d536bb5c26628343d6594a2ebc45c3c447b8ce442b4ac3a41be847b860ac009bda7968fc7d339d2ee49b18bfe36306c5dd99cf17c7d84c82de93f3
languageName: node
linkType: hard
-"@motionone/types@npm:^10.12.0, @motionone/types@npm:^10.13.0":
- version: 10.13.0
- resolution: "@motionone/types@npm:10.13.0"
- checksum: 4c0a4593562f3c8fa30660a3b796ec012d592029137fc35f3029b34e69e5c364efa24c2016dd66b21db580d0a9d4107730b30f55496b416b2ed9dbe437865eab
+"@motionone/types@npm:^10.12.0, @motionone/types@npm:^10.15.1":
+ version: 10.15.1
+ resolution: "@motionone/types@npm:10.15.1"
+ checksum: 98091f7dca257508d94d1080678c433da39a814e8e58aaa742212bf6c2a5b5e2120a6251a06e3ea522219ce6d1b6eb6aa2cab224b803fe52789033d8398ef0aa
languageName: node
linkType: hard
-"@motionone/utils@npm:^10.12.0, @motionone/utils@npm:^10.13.1":
- version: 10.13.1
- resolution: "@motionone/utils@npm:10.13.1"
+"@motionone/utils@npm:^10.12.0, @motionone/utils@npm:^10.15.1":
+ version: 10.15.1
+ resolution: "@motionone/utils@npm:10.15.1"
dependencies:
- "@motionone/types": ^10.13.0
+ "@motionone/types": ^10.15.1
hey-listen: ^1.0.8
tslib: ^2.3.1
- checksum: 2ec2de91d07f7bd1dcb157d96c0c0f7565d1e8c6ac9adec0ce33811a321fea72d45a4c51833d2c3e432c26b3904e17e3296d553ad87b4b6705d6fba93cd22aca
+ checksum: 6ef13cd6637ec87c340e5536f849f8c40d30cc90139a3856d11cd70d78e3740f8815b0e63564fefd23c05a060da7a0ea5395390549606ed8801a7b832b74e04e
languageName: node
linkType: hard
"@next/bundle-analyzer@npm:^12.1.4":
- version: 12.2.3
- resolution: "@next/bundle-analyzer@npm:12.2.3"
+ version: 12.3.4
+ resolution: "@next/bundle-analyzer@npm:12.3.4"
dependencies:
webpack-bundle-analyzer: 4.3.0
- checksum: 86745354ac90ac72d203d0d1051114f90cc7de7b2e64ea1f7129621fd1d9fde36fe00901747b4c56708777023b08b936ded1aaafaf40f1709c3ad3a8c04f1f07
+ checksum: 611cc07194a5cdd4aa0d1db5bae2de807cb2388d2c623f8d7ab8d581f8d01ec1510bd73ae3977334f7957278540ec0e2b2ebd48f9476235bd7b65055c560dc44
languageName: node
linkType: hard
@@ -1383,11 +1373,11 @@ __metadata:
linkType: hard
"@next/eslint-plugin-next@npm:^12.1.4":
- version: 12.2.3
- resolution: "@next/eslint-plugin-next@npm:12.2.3"
+ version: 12.3.4
+ resolution: "@next/eslint-plugin-next@npm:12.3.4"
dependencies:
glob: 7.1.7
- checksum: aba5344c477b1a3d361159bbb46812a470f23d7e2ab3d7892ab372c3caad33e6e9c3c7abce45597571a52680eefc1ef451aecac67f469f2062ed78f37b80a3e8
+ checksum: e4ae97062f3efe8f70904cf0da296ab501a2924423273352d01b18d8ffff1eb2e9a65c47dd6f9cfa0d696eada272486a3f519b2786918d0a9ab735b93f5ce4b3
languageName: node
linkType: hard
@@ -1646,7 +1636,7 @@ __metadata:
languageName: node
linkType: hard
-"@nodelib/fs.walk@npm:^1.2.3":
+"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8":
version: 1.2.8
resolution: "@nodelib/fs.walk@npm:1.2.8"
dependencies:
@@ -1657,22 +1647,22 @@ __metadata:
linkType: hard
"@npmcli/fs@npm:^2.1.0":
- version: 2.1.1
- resolution: "@npmcli/fs@npm:2.1.1"
+ version: 2.1.2
+ resolution: "@npmcli/fs@npm:2.1.2"
dependencies:
"@gar/promisify": ^1.1.3
semver: ^7.3.5
- checksum: 4944a0545d38d3e6e29780eeb3cd4be6059c1e9627509d2c9ced635c53b852d28b37cdc615a2adf815b51ab8673adb6507e370401a20a7e90c8a6dc4fac02389
+ checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225
languageName: node
linkType: hard
"@npmcli/move-file@npm:^2.0.0":
- version: 2.0.0
- resolution: "@npmcli/move-file@npm:2.0.0"
+ version: 2.0.1
+ resolution: "@npmcli/move-file@npm:2.0.1"
dependencies:
mkdirp: ^1.0.4
rimraf: ^3.0.2
- checksum: 1388777b507b0c592d53f41b9d182e1a8de7763bc625fc07999b8edbc22325f074e5b3ec90af79c89d6987fdb2325bc66d59f483258543c14a43661621f841b0
+ checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380
languageName: node
linkType: hard
@@ -1748,22 +1738,22 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-primitive@npm:1.0.0":
- version: 1.0.0
- resolution: "@radix-ui/react-primitive@npm:1.0.0"
+"@radix-ui/react-primitive@npm:1.0.1":
+ version: 1.0.1
+ resolution: "@radix-ui/react-primitive@npm:1.0.1"
dependencies:
"@babel/runtime": ^7.13.10
- "@radix-ui/react-slot": 1.0.0
+ "@radix-ui/react-slot": 1.0.1
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
- checksum: fb3fe8c8c5a57995716cce4d7e9039e474c09ba5d714994419ad4940bc954da670f1188813cc931f189b23d9bd5a67adf7087bf44fe1d4272b4a334a3514d38b
+ checksum: 1cc86b72f926be4a42122e7e456e965de0906f16b0dc244b8448bac05905f208598c984a0dd40026f654b4a71d0235335d48a18e377b07b0ec6c6917576a8080
languageName: node
linkType: hard
-"@radix-ui/react-scroll-area@npm:1.0.0":
- version: 1.0.0
- resolution: "@radix-ui/react-scroll-area@npm:1.0.0"
+"@radix-ui/react-scroll-area@npm:1.0.2":
+ version: 1.0.2
+ resolution: "@radix-ui/react-scroll-area@npm:1.0.2"
dependencies:
"@babel/runtime": ^7.13.10
"@radix-ui/number": 1.0.0
@@ -1772,25 +1762,25 @@ __metadata:
"@radix-ui/react-context": 1.0.0
"@radix-ui/react-direction": 1.0.0
"@radix-ui/react-presence": 1.0.0
- "@radix-ui/react-primitive": 1.0.0
+ "@radix-ui/react-primitive": 1.0.1
"@radix-ui/react-use-callback-ref": 1.0.0
"@radix-ui/react-use-layout-effect": 1.0.0
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
- checksum: 0b7756f1df19ac13c570941461794668d0fb15fd576f37a698a6d0a39f48b728db751455f8de202e5ccb8750c3d9f31a02ada7b79d9f064347aa96e780d8f104
+ checksum: c59062c3321fb92b526f2b63be2636569c4a33fa81bd5c5109b13516932cebaf680a5cd318263d37e7e6e4ca62aa45521c34447478fd2acde3b2639ae53252d7
languageName: node
linkType: hard
-"@radix-ui/react-slot@npm:1.0.0":
- version: 1.0.0
- resolution: "@radix-ui/react-slot@npm:1.0.0"
+"@radix-ui/react-slot@npm:1.0.1":
+ version: 1.0.1
+ resolution: "@radix-ui/react-slot@npm:1.0.1"
dependencies:
"@babel/runtime": ^7.13.10
"@radix-ui/react-compose-refs": 1.0.0
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
- checksum: 60c0190ebdca21785b4f8b58a0c52717600c98953fc49da9580870519c60f52d5cf873dffa05446f4bb539066326ccec0827f4ca252b02ec4ff1a4ae203f59d7
+ checksum: a20693f8ce532bd6cbff12ba543dfcf90d451f22923bd60b57dc9e639f6e53348915e182002b33444feb6ab753434e78e2a54085bf7092aadda4418f0423763f
languageName: node
linkType: hard
@@ -1883,25 +1873,32 @@ __metadata:
linkType: hard
"@sinclair/typebox@npm:^0.24.1":
- version: 0.24.20
- resolution: "@sinclair/typebox@npm:0.24.20"
- checksum: bb2e95ab60236ebbcaf3c0735b01a8ce6bea068bb1214a8016f8fea7bc2027d69b08437998425d93a3ac38ded3dbe8c64e218e635c09282cb3dd5d5a64269076
+ version: 0.24.51
+ resolution: "@sinclair/typebox@npm:0.24.51"
+ checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0
languageName: node
linkType: hard
-"@sindresorhus/is@npm:^4.0.0, @sindresorhus/is@npm:^4.6.0":
+"@sindresorhus/is@npm:^4.0.0":
version: 4.6.0
resolution: "@sindresorhus/is@npm:4.6.0"
checksum: 83839f13da2c29d55c97abc3bc2c55b250d33a0447554997a85c539e058e57b8da092da396e252b11ec24a0279a0bed1f537fa26302209327060643e327f81d2
languageName: node
linkType: hard
+"@sindresorhus/is@npm:^5.2.0":
+ version: 5.3.0
+ resolution: "@sindresorhus/is@npm:5.3.0"
+ checksum: b31cebabcdece3d5322de2a4dbc8c0f004e04147a00f2606787bcaf5655ad4b1954f6727fc6914c524009b2b9a2cc01c42835b55f651ce69fd2a0083b60bb852
+ languageName: node
+ linkType: hard
+
"@sinonjs/commons@npm:^1.7.0":
- version: 1.8.3
- resolution: "@sinonjs/commons@npm:1.8.3"
+ version: 1.8.6
+ resolution: "@sinonjs/commons@npm:1.8.6"
dependencies:
type-detect: 4.0.8
- checksum: 6159726db5ce6bf9f2297f8427f7ca5b3dff45b31e5cee23496f1fa6ef0bb4eab878b23fb2c5e6446381f6a66aba4968ef2fc255c1180d753d4b8c271636a2e5
+ checksum: 7d3f8c1e85f30cd4e83594fc19b7a657f14d49eb8d95a30095631ce15e906c869e0eff96c5b93dffea7490c00418b07f54582ba49c6560feb2a8c34c0b16832d
languageName: node
linkType: hard
@@ -1941,9 +1938,9 @@ __metadata:
languageName: node
linkType: hard
-"@tabler/icons@npm:^1.78.0":
- version: 1.78.0
- resolution: "@tabler/icons@npm:1.78.0"
+"@tabler/icons@npm:^1.106.0":
+ version: 1.116.1
+ resolution: "@tabler/icons@npm:1.116.1"
peerDependencies:
react: ^16.x || 17.x || 18.x
react-dom: ^16.x || 17.x || 18.x
@@ -1952,23 +1949,22 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: f3789c4681fc7a3520585522afd8306d18ab3ec49077687b956c837f0d088b5c2a68c1bc90ff44f3369e9a5b3f4b4501f1a50133e8dff93e0947cdd53aceefea
+ checksum: 0e310e2334f69937deb77dab64042dbd16d8f3f6a1e5fb3ee0dac05b01d0260a7ade20297e55b91c6728eb6899a35817e1d471001ebbd2d5d18e6a23e63059c4
languageName: node
linkType: hard
-"@tanstack/query-core@npm:^4.0.0-beta.1":
- version: 4.2.1
- resolution: "@tanstack/query-core@npm:4.2.1"
- checksum: f71854969e02de6c2cfbe25e8b11e275b61e1297a902e0d5c4beac580a87db99555c1c21d536d838ce5e0664bc49da7b60a3c6b8de334c7004c5005fe2a48030
+"@tanstack/query-core@npm:4.19.1":
+ version: 4.19.1
+ resolution: "@tanstack/query-core@npm:4.19.1"
+ checksum: a54a613d5b7abe2bba352fa26228bee0589eb8116bf482760dbe39474202b08cfc3899100e478f0bd1ae1a7bd60dcda0634c04bec56a98346763d32bc7720b54
languageName: node
linkType: hard
"@tanstack/react-query@npm:^4.2.1":
- version: 4.2.1
- resolution: "@tanstack/react-query@npm:4.2.1"
+ version: 4.19.1
+ resolution: "@tanstack/react-query@npm:4.19.1"
dependencies:
- "@tanstack/query-core": ^4.0.0-beta.1
- "@types/use-sync-external-store": ^0.0.3
+ "@tanstack/query-core": 4.19.1
use-sync-external-store: ^1.2.0
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -1979,7 +1975,7 @@ __metadata:
optional: true
react-native:
optional: true
- checksum: bbf3a808645c26c649971dc182bb9a7ed7a1d89f6456b60685c6081b8be6ae84ae83b39c7eacb96c4f3b6677ca001d8114037329951987b7a8d65de53b28c862
+ checksum: cce8b51c5cbaff007efb2fc54c5e03a2191e363626226903c345ae9baf0d48a36fc322383e24aff4f12250e672da67d3ceaa9819bbb16ace98a5c7bdff098a61
languageName: node
linkType: hard
@@ -1991,15 +1987,15 @@ __metadata:
linkType: hard
"@types/babel__core@npm:^7.1.14":
- version: 7.1.19
- resolution: "@types/babel__core@npm:7.1.19"
+ version: 7.1.20
+ resolution: "@types/babel__core@npm:7.1.20"
dependencies:
"@babel/parser": ^7.1.0
"@babel/types": ^7.0.0
"@types/babel__generator": "*"
"@types/babel__template": "*"
"@types/babel__traverse": "*"
- checksum: 8c9fa87a1c2224cbec251683a58bebb0d74c497118034166aaa0491a4e2627998a6621fc71f8a60ffd27d9c0c52097defedf7637adc6618d0331c15adb302338
+ checksum: a09c4f0456552547a5b8a5a009a3daec4d362f622168f8e08bda0ded2da0a65ab0b1642e23c433b3616721f5701dc34a996c5bde5baeaea53eda98f438043f2c
languageName: node
linkType: hard
@@ -2023,23 +2019,23 @@ __metadata:
linkType: hard
"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6":
- version: 7.17.1
- resolution: "@types/babel__traverse@npm:7.17.1"
+ version: 7.18.3
+ resolution: "@types/babel__traverse@npm:7.18.3"
dependencies:
"@babel/types": ^7.3.0
- checksum: 8992d8c1eaaf1c793e9184b930767883446939d2744c40ea4e9591086e79b631189dc519931ed8864f1e016742a189703c217db59b800aca84870b865009d8b4
+ checksum: d20953338b2f012ab7750932ece0a78e7d1645b0a6ff42d49be90f55e9998085da1374a9786a7da252df89555c6586695ba4d1d4b4e88ab2b9f306bcd35e00d3
languageName: node
linkType: hard
-"@types/cacheable-request@npm:^6.0.1, @types/cacheable-request@npm:^6.0.2":
- version: 6.0.2
- resolution: "@types/cacheable-request@npm:6.0.2"
+"@types/cacheable-request@npm:^6.0.1":
+ version: 6.0.3
+ resolution: "@types/cacheable-request@npm:6.0.3"
dependencies:
"@types/http-cache-semantics": "*"
- "@types/keyv": "*"
+ "@types/keyv": ^3.1.4
"@types/node": "*"
- "@types/responselike": "*"
- checksum: 667d25808dbf46fe104d6f029e0281ff56058d50c7c1b9182774b3e38bb9c1124f56e4c367ba54f92dbde2d1cc573f26eb0e9748710b2822bc0fd1e5498859c6
+ "@types/responselike": ^1.0.0
+ checksum: d9b26403fe65ce6b0cb3720b7030104c352bcb37e4fac2a7089a25a97de59c355fa08940658751f2f347a8512aa9d18fdb66ab3ade835975b2f454f2d5befbd9
languageName: node
linkType: hard
@@ -2061,12 +2057,12 @@ __metadata:
linkType: hard
"@types/dockerode@npm:^3.3.9":
- version: 3.3.9
- resolution: "@types/dockerode@npm:3.3.9"
+ version: 3.3.14
+ resolution: "@types/dockerode@npm:3.3.14"
dependencies:
"@types/docker-modem": "*"
"@types/node": "*"
- checksum: 3d03c68addb37c50e9557fff17171d26423aa18e544cb24e4caa81ebcec39ccc1cafed7adbfb8f4220d8ed23028d231717826bb77a786d425885c4f4cc37536d
+ checksum: 6a8472622861fb0c97908963a8236d6e439c9feefc845b866326272ad0e5c33f537206fb4633424c5e3c8ec63559cb14bc737e2f88f0fcdf11496a9162d5b139
languageName: node
linkType: hard
@@ -2089,7 +2085,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/http-cache-semantics@npm:*":
+"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.1":
version: 4.0.1
resolution: "@types/http-cache-semantics@npm:4.0.1"
checksum: 1048aacf627829f0d5f00184e16548205cd9f964bf0841c29b36bc504509230c40bc57c39778703a1c965a6f5b416ae2cbf4c1d4589c889d2838dd9dbfccf6e9
@@ -2121,13 +2117,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/json-buffer@npm:~3.0.0":
- version: 3.0.0
- resolution: "@types/json-buffer@npm:3.0.0"
- checksum: 6b0a371dd603f0eec9d00874574bae195382570e832560dadf2193ee0d1062b8e0694bbae9798bc758632361c227b1e3b19e3bd914043b498640470a2da38b77
- languageName: node
- linkType: hard
-
"@types/json-schema@npm:^7.0.9":
version: 7.0.11
resolution: "@types/json-schema@npm:7.0.11"
@@ -2142,7 +2131,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/keyv@npm:*":
+"@types/keyv@npm:^3.1.4":
version: 3.1.4
resolution: "@types/keyv@npm:3.1.4"
dependencies:
@@ -2152,9 +2141,9 @@ __metadata:
linkType: hard
"@types/node@npm:*":
- version: 18.0.6
- resolution: "@types/node@npm:18.0.6"
- checksum: 780f8885a6b6eb12f4c0246617747fdc37a451931b3c01ce8148d356c0903b705dcb16cc6a914de63d48b0dc1b002c7a3dfae681f580e1761aa551d3cd996813
+ version: 18.11.12
+ resolution: "@types/node@npm:18.11.12"
+ checksum: 6c67f0998a9ad8ef0f357fcc0d72841c579576c746cfbac67c9d0229e1cd219d3b96297c2caaeaa4084ba7e9ad112b412e64af4066e522faa0be4ef9ad0a613a
languageName: node
linkType: hard
@@ -2166,9 +2155,9 @@ __metadata:
linkType: hard
"@types/node@npm:^16.10.2":
- version: 16.11.45
- resolution: "@types/node@npm:16.11.45"
- checksum: 57d61c951024f66d796e71e4a972faef266007398cd4e93a195822fea2d5deb41d0615f394a99ece89772b145ff057321d138c7e3442455dc7d785ff67cebde3
+ version: 16.18.7
+ resolution: "@types/node@npm:16.18.7"
+ checksum: a7fbc536d433c08adebcd788c2b2b28b4bec9b4cc493d3ed5fd98097bda7760893bacc06f97f45514a7f013f4fc952cbb3068166b6e9e7eebfe08e8e813f8f1c
languageName: node
linkType: hard
@@ -2179,10 +2168,17 @@ __metadata:
languageName: node
linkType: hard
+"@types/ping@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "@types/ping@npm:0.4.1"
+ checksum: 9b94837fe66df70558c5a42b0e0c8371b4950ab56b96c42c8df809ff2cf52477dd0a7e01d2e6b38af8bb6683b3dcb54587960b96b4b1f3d40fdb529aea348ad0
+ languageName: node
+ linkType: hard
+
"@types/prettier@npm:^2.1.5":
- version: 2.6.3
- resolution: "@types/prettier@npm:2.6.3"
- checksum: e1836699ca189fff6d2a73dc22e028b6a6f693ed1180d5998ac29fa197caf8f85aa92cb38db642e4a370e616b451cb5722ad2395dab11c78e025a1455f37d1f0
+ version: 2.7.1
+ resolution: "@types/prettier@npm:2.7.1"
+ checksum: 5e3f58e229d6c73b5f5cae2e8f96c1c4a5b5805f83459e17a045ba8e96152b1d38e86b63e3172fb159dac923388699660862b75b2d37e54220805f0e691e26f1
languageName: node
linkType: hard
@@ -2203,7 +2199,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/responselike@npm:*, @types/responselike@npm:^1.0.0":
+"@types/responselike@npm:^1.0.0":
version: 1.0.0
resolution: "@types/responselike@npm:1.0.0"
dependencies:
@@ -2212,12 +2208,19 @@ __metadata:
languageName: node
linkType: hard
+"@types/semver@npm:^7.3.12":
+ version: 7.3.13
+ resolution: "@types/semver@npm:7.3.13"
+ checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0
+ languageName: node
+ linkType: hard
+
"@types/ssh2@npm:*":
- version: 1.11.5
- resolution: "@types/ssh2@npm:1.11.5"
+ version: 1.11.6
+ resolution: "@types/ssh2@npm:1.11.6"
dependencies:
"@types/node": "*"
- checksum: ee7caae274e5f8e3c9d96aca94248142d39c9147e47f9700cec43aa9c680c75b66d970941ec34cfc622c00d87a1a983d0d92bb2e4a34b70e8d10213347446496
+ checksum: 4812694de5444802f7386da7882da9072a6d63b5b634eb59fdca39e8f7ad4964bcefe7844d375d594cb979a0804e24aea273098bdbb84e7e23a9d3b5698d3936
languageName: node
linkType: hard
@@ -2228,13 +2231,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/use-sync-external-store@npm:^0.0.3":
- version: 0.0.3
- resolution: "@types/use-sync-external-store@npm:0.0.3"
- checksum: 161ddb8eec5dbe7279ac971531217e9af6b99f7783213566d2b502e2e2378ea19cf5e5ea4595039d730aa79d3d35c6567d48599f69773a02ffcff1776ec2a44e
- languageName: node
- linkType: hard
-
"@types/uuid@npm:^8.3.4":
version: 8.3.4
resolution: "@types/uuid@npm:8.3.4"
@@ -2250,24 +2246,24 @@ __metadata:
linkType: hard
"@types/yargs@npm:^17.0.8":
- version: 17.0.10
- resolution: "@types/yargs@npm:17.0.10"
+ version: 17.0.17
+ resolution: "@types/yargs@npm:17.0.17"
dependencies:
"@types/yargs-parser": "*"
- checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8
+ checksum: 4f1b5149e794c8960652b4339dc96655e53b48fdbe1b847fcb0db95e72e760724a4c5c616a7fccb3c0ae8beb0e57e06e37391d492e916e3ff179aafeb890e426
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/eslint-plugin@npm:5.30.7"
+ version: 5.46.0
+ resolution: "@typescript-eslint/eslint-plugin@npm:5.46.0"
dependencies:
- "@typescript-eslint/scope-manager": 5.30.7
- "@typescript-eslint/type-utils": 5.30.7
- "@typescript-eslint/utils": 5.30.7
+ "@typescript-eslint/scope-manager": 5.46.0
+ "@typescript-eslint/type-utils": 5.46.0
+ "@typescript-eslint/utils": 5.46.0
debug: ^4.3.4
- functional-red-black-tree: ^1.0.1
ignore: ^5.2.0
+ natural-compare-lite: ^1.4.0
regexpp: ^3.2.0
semver: ^7.3.7
tsutils: ^3.21.0
@@ -2277,42 +2273,43 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: d42af514f5817732646b5601030699687b4ef619ba7983754a4173bf908f6c6030324038e3733b88342ec6ace07af61aa946d677da6a6266931275bd2afc9fc2
+ checksum: 5b7dde66a3db3d3009c9da5c1357d72c19b94d75474d8f51b6ac765962aa181bf8fe88fcca02a70faaceb2ed5739f790313d1d521d77149cab94ab5e3a987cf3
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/parser@npm:5.30.7"
+ version: 5.46.0
+ resolution: "@typescript-eslint/parser@npm:5.46.0"
dependencies:
- "@typescript-eslint/scope-manager": 5.30.7
- "@typescript-eslint/types": 5.30.7
- "@typescript-eslint/typescript-estree": 5.30.7
+ "@typescript-eslint/scope-manager": 5.46.0
+ "@typescript-eslint/types": 5.46.0
+ "@typescript-eslint/typescript-estree": 5.46.0
debug: ^4.3.4
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
typescript:
optional: true
- checksum: f0b2da3cfd047d241f0bd3065a36afe008214aa9e8cd05e9f92d8b0e4b9ec19d3651d0e4a3995b8cb34b553cccb4b0d02d18c0cfbe11f53acd85923dd68366d5
+ checksum: 34e2e8fb35050ac119ed2696293ecd9a1b40c3ccc915d06b8d48880c6656e8f40665b969807b76058eb9198227481653f1d1465a89a4ac6a98bc7ab9850ada1f
languageName: node
linkType: hard
-"@typescript-eslint/scope-manager@npm:5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/scope-manager@npm:5.30.7"
+"@typescript-eslint/scope-manager@npm:5.46.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/scope-manager@npm:5.46.0"
dependencies:
- "@typescript-eslint/types": 5.30.7
- "@typescript-eslint/visitor-keys": 5.30.7
- checksum: 434ce7a13a8f3bffae2af2b7fe19bab6e490c78114584212519f50cd1b91fbdcddc8ad93bdb3cacdc8cecca5a8c5d2eb606557e66bd3fcd9d3040846846c22ff
+ "@typescript-eslint/types": 5.46.0
+ "@typescript-eslint/visitor-keys": 5.46.0
+ checksum: 10d992a6f9bbe747a8fb5f8b02732e3a289d2667e32ea835de825efe7a841688e683ae1d15d250df84c2ada313b9a3f41c9c9b5ae924c3d9b90d73f23241ae7f
languageName: node
linkType: hard
-"@typescript-eslint/type-utils@npm:5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/type-utils@npm:5.30.7"
+"@typescript-eslint/type-utils@npm:5.46.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/type-utils@npm:5.46.0"
dependencies:
- "@typescript-eslint/utils": 5.30.7
+ "@typescript-eslint/typescript-estree": 5.46.0
+ "@typescript-eslint/utils": 5.46.0
debug: ^4.3.4
tsutils: ^3.21.0
peerDependencies:
@@ -2320,23 +2317,23 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: e7a8d4ec973355c0fe5bad4c317a55940e41d24b1c33b0bf40e8bb268d784f6584a8048fc84ebdb7287849a2c70e2b36365067cba7815de849cd41a1d7653167
+ checksum: 96feae3b67b78bc74b916bb7c3c654c13c6f000fc1f6945709764010fb6644e7cbf7749faa6ab0562255197b49fbf7c28c9a5b558378fa74e2cd38aeddc459cb
languageName: node
linkType: hard
-"@typescript-eslint/types@npm:5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/types@npm:5.30.7"
- checksum: 2f6345bf0e2e9f392c1f62a5f96c630d4565574230a000508d923444229e51c1a05e07cef042935ca30f4f35755dbf3871b8b9da808911f578d63e6a4b897b79
+"@typescript-eslint/types@npm:5.46.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/types@npm:5.46.0"
+ checksum: 162e2e7841369598d3018f315545e85b79e57cc2f9033770397dbbdab0c80d72a8ee791710d10570b70624b5f343f7f8fc7ffabaec62f708cd7bfc5b432ad595
languageName: node
linkType: hard
-"@typescript-eslint/typescript-estree@npm:5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/typescript-estree@npm:5.30.7"
+"@typescript-eslint/typescript-estree@npm:5.46.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/typescript-estree@npm:5.46.0"
dependencies:
- "@typescript-eslint/types": 5.30.7
- "@typescript-eslint/visitor-keys": 5.30.7
+ "@typescript-eslint/types": 5.46.0
+ "@typescript-eslint/visitor-keys": 5.46.0
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
@@ -2345,37 +2342,39 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: 7cff83a9b9c91a89bcbb677d539b7122b2a423a66f575364858b4635d7e53a25b9329cd20a5adfb732758a41d1c6801d4bfa3eb798a192f351aafb11eedc58b6
+ checksum: 645f9fd65836019073fd7af5bfa0e4c3d47303deda0917c4d8fb6ed6790bebaaeb7020a7a104b96c5fda707eb20e248c38fc711cdc83439775c563ef9f169746
languageName: node
linkType: hard
-"@typescript-eslint/utils@npm:5.30.7, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.13.0":
- version: 5.30.7
- resolution: "@typescript-eslint/utils@npm:5.30.7"
+"@typescript-eslint/utils@npm:5.46.0, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.13.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/utils@npm:5.46.0"
dependencies:
"@types/json-schema": ^7.0.9
- "@typescript-eslint/scope-manager": 5.30.7
- "@typescript-eslint/types": 5.30.7
- "@typescript-eslint/typescript-estree": 5.30.7
+ "@types/semver": ^7.3.12
+ "@typescript-eslint/scope-manager": 5.46.0
+ "@typescript-eslint/types": 5.46.0
+ "@typescript-eslint/typescript-estree": 5.46.0
eslint-scope: ^5.1.1
eslint-utils: ^3.0.0
+ semver: ^7.3.7
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
- checksum: 77b0baf069f70290214294d74fdf7c46a7ddeab322ef53f65766b0c8e59f0e6f8074beb19233be34faca5beb390ac1b932dd1c983337355674c4437b4b1e2b44
+ checksum: f4962a7d34f55c44b238df550d45aacbc7b8fcba7d172fd96ee17eb5ce964481e3e68376205598146f4f3aa69df7627686a9bd16022b51246d6e9fe94ffb2bb8
languageName: node
linkType: hard
-"@typescript-eslint/visitor-keys@npm:5.30.7":
- version: 5.30.7
- resolution: "@typescript-eslint/visitor-keys@npm:5.30.7"
+"@typescript-eslint/visitor-keys@npm:5.46.0":
+ version: 5.46.0
+ resolution: "@typescript-eslint/visitor-keys@npm:5.46.0"
dependencies:
- "@typescript-eslint/types": 5.30.7
+ "@typescript-eslint/types": 5.46.0
eslint-visitor-keys: ^3.3.0
- checksum: f322972aeda3143d4c24826436357937131f7fbad102d48cfa6dfca70ac245f93b20cf7beb5f1809bda4fe8f454676a6cabf8f73e39af6724076f2b2c213ee80
+ checksum: 83ec2514b2469db395f006576c934bd60b21e74e2e67c183f8d9249954119c56074de286ce8c55d42e20b06d0083d4665f3baf0eed720712203796be488f0944
languageName: node
linkType: hard
-"abbrev@npm:1":
+"abbrev@npm:^1.0.0":
version: 1.1.1
resolution: "abbrev@npm:1.1.1"
checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17
@@ -2398,19 +2397,12 @@ __metadata:
languageName: node
linkType: hard
-"acorn@npm:^8.0.4, acorn@npm:^8.7.1":
- version: 8.8.0
- resolution: "acorn@npm:8.8.0"
+"acorn@npm:^8.0.4, acorn@npm:^8.8.0":
+ version: 8.8.1
+ resolution: "acorn@npm:8.8.1"
bin:
acorn: bin/acorn
- checksum: 7270ca82b242eafe5687a11fea6e088c960af712683756abf0791b68855ea9cace3057bd5e998ffcef50c944810c1e0ca1da526d02b32110e13c722aa959afdc
- languageName: node
- linkType: hard
-
-"add@npm:^2.0.6":
- version: 2.0.6
- resolution: "add@npm:2.0.6"
- checksum: e2d23d40494565dfed4acd65e478570c444db5ac6c053551ed429c39ea0f2c99d83df63e7befec936df601827d2254d06a2fb6f7dcfd2022e810b25eab818b8c
+ checksum: 4079b67283b94935157698831967642f24a075c52ce3feaaaafe095776dfbe15d86a1b33b1e53860fc0d062ed6c83f4284a5c87c85b9ad51853a01173da6097f
languageName: node
linkType: hard
@@ -2497,13 +2489,13 @@ __metadata:
languageName: node
linkType: hard
-"anymatch@npm:^3.0.3":
- version: 3.1.2
- resolution: "anymatch@npm:3.1.2"
+"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2":
+ version: 3.1.3
+ resolution: "anymatch@npm:3.1.3"
dependencies:
normalize-path: ^3.0.0
picomatch: ^2.0.4
- checksum: 985163db2292fac9e5a1e072bf99f1b5baccf196e4de25a0b0b81865ebddeb3b3eb4480734ef0a2ac8c002845396b91aa89121f5b84f93981a4658164a9ec6e9
+ checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2
languageName: node
linkType: hard
@@ -2515,12 +2507,12 @@ __metadata:
linkType: hard
"are-we-there-yet@npm:^3.0.0":
- version: 3.0.0
- resolution: "are-we-there-yet@npm:3.0.0"
+ version: 3.0.1
+ resolution: "are-we-there-yet@npm:3.0.1"
dependencies:
delegates: ^1.0.0
readable-stream: ^3.6.0
- checksum: 348edfdd931b0b50868b55402c01c3f64df1d4c229ab6f063539a5025fd6c5f5bb8a0cab409bbed8d75d34762d22aa91b7c20b4204eb8177063158d9ba792981
+ checksum: 52590c24860fa7173bedeb69a4c05fb573473e860197f618b9a28432ee4379049336727ae3a1f9c4cb083114601c1140cee578376164d0e651217a9843f9fe83
languageName: node
linkType: hard
@@ -2541,11 +2533,17 @@ __metadata:
linkType: hard
"aria-hidden@npm:^1.1.3":
- version: 1.1.3
- resolution: "aria-hidden@npm:1.1.3"
+ version: 1.2.2
+ resolution: "aria-hidden@npm:1.2.2"
dependencies:
- tslib: ^1.0.0
- checksum: 2d40a328246baac7ae0b243ebe0cbef53c836c5f78c9212e9c1ff93f3aee185bd9aa51773e161e0025722d691c9d5f125070f6175a7074c4a57778ddc30d9e74
+ tslib: ^2.0.0
+ peerDependencies:
+ "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0
+ react: ^16.9.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: ee1a3688db5491eeb87b73eea624614f24ef62a74cf9e47bc8229dde1ff7457f7e4a26425cadc0d3efd89380305e6fb4a4e505bccdee16beaa4686014861d7b1
languageName: node
linkType: hard
@@ -2559,16 +2557,16 @@ __metadata:
languageName: node
linkType: hard
-"array-includes@npm:^3.1.4, array-includes@npm:^3.1.5":
- version: 3.1.5
- resolution: "array-includes@npm:3.1.5"
+"array-includes@npm:^3.1.4, array-includes@npm:^3.1.5, array-includes@npm:^3.1.6":
+ version: 3.1.6
+ resolution: "array-includes@npm:3.1.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
- es-abstract: ^1.19.5
- get-intrinsic: ^1.1.1
+ es-abstract: ^1.20.4
+ get-intrinsic: ^1.1.3
is-string: ^1.0.7
- checksum: f6f24d834179604656b7bec3e047251d5cc87e9e87fab7c175c61af48e80e75acd296017abcde21fb52292ab6a2a449ab2ee37213ee48c8709f004d75983f9c5
+ checksum: f22f8cd8ba8a6448d91eebdc69f04e4e55085d09232b5216ee2d476dab3ef59984e8d1889e662c6a0ed939dcb1b57fd05b2c0209c3370942fc41b752c82a2ca5
languageName: node
linkType: hard
@@ -2580,26 +2578,39 @@ __metadata:
linkType: hard
"array.prototype.flat@npm:^1.2.5":
- version: 1.3.0
- resolution: "array.prototype.flat@npm:1.3.0"
+ version: 1.3.1
+ resolution: "array.prototype.flat@npm:1.3.1"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.2
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
es-shim-unscopables: ^1.0.0
- checksum: 2a652b3e8dc0bebb6117e42a5ab5738af0203a14c27341d7bb2431467bdb4b348e2c5dc555dfcda8af0a5e4075c400b85311ded73861c87290a71a17c3e0a257
+ checksum: 5a8415949df79bf6e01afd7e8839bbde5a3581300e8ad5d8449dea52639e9e59b26a467665622783697917b43bf39940a6e621877c7dd9b3d1c1f97484b9b88b
languageName: node
linkType: hard
-"array.prototype.flatmap@npm:^1.3.0":
- version: 1.3.0
- resolution: "array.prototype.flatmap@npm:1.3.0"
+"array.prototype.flatmap@npm:^1.3.1":
+ version: 1.3.1
+ resolution: "array.prototype.flatmap@npm:1.3.1"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.2
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
es-shim-unscopables: ^1.0.0
- checksum: 818538f39409c4045d874be85df0dbd195e1446b14d22f95bdcfefea44ae77db44e42dcd89a559254ec5a7c8b338cfc986cc6d641e3472f9a5326b21eb2976a2
+ checksum: 8c1c43a4995f12cf12523436da28515184c753807b3f0bc2ca6c075f71c470b099e2090cc67dba8e5280958fea401c1d0c59e1db0143272aef6cd1103921a987
+ languageName: node
+ linkType: hard
+
+"array.prototype.tosorted@npm:^1.1.1":
+ version: 1.1.1
+ resolution: "array.prototype.tosorted@npm:1.1.1"
+ dependencies:
+ call-bind: ^1.0.2
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
+ es-shim-unscopables: ^1.0.0
+ get-intrinsic: ^1.1.3
+ checksum: 7923324a67e70a2fc0a6e40237405d92395e45ebd76f5cb89c2a5cf1e66b47aca6baacd0cd628ffd88830b90d47fff268071493d09c9ae123645613dac2c2ca3
languageName: node
linkType: hard
@@ -2634,9 +2645,9 @@ __metadata:
linkType: hard
"axe-core@npm:^4.4.3":
- version: 4.4.3
- resolution: "axe-core@npm:4.4.3"
- checksum: c3ea000d9ace3ba0bc747c8feafc24b0de62a0f7d93021d0f77b19c73fca15341843510f6170da563d51535d6cfb7a46c5fc0ea36170549dbb44b170208450a2
+ version: 4.5.2
+ resolution: "axe-core@npm:4.5.2"
+ checksum: 4068f183b2ef1db7e5a75606032c238781abfaa34ab4c23177e17f7dff8cc83f175e887b52689d20d88d2d4f001cbf632bd98925850026fe1d9abc739cabcf16
languageName: node
linkType: hard
@@ -2767,6 +2778,13 @@ __metadata:
languageName: node
linkType: hard
+"binary-extensions@npm:^2.0.0":
+ version: 2.2.0
+ resolution: "binary-extensions@npm:2.2.0"
+ checksum: ccd267956c58d2315f5d3ea6757cf09863c5fc703e50fbeb13a7dc849b812ef76e3cf9ca8f35a0c48498776a7478d7b4a0418e1e2b8cb9cb9731f2922aaad7f8
+ languageName: node
+ linkType: hard
+
"bl@npm:^4.0.3":
version: 4.1.0
resolution: "bl@npm:4.1.0"
@@ -2797,7 +2815,7 @@ __metadata:
languageName: node
linkType: hard
-"braces@npm:^3.0.2":
+"braces@npm:^3.0.2, braces@npm:~3.0.2":
version: 3.0.2
resolution: "braces@npm:3.0.2"
dependencies:
@@ -2806,17 +2824,17 @@ __metadata:
languageName: node
linkType: hard
-"browserslist@npm:^4.20.2":
- version: 4.21.2
- resolution: "browserslist@npm:4.21.2"
+"browserslist@npm:^4.21.3":
+ version: 4.21.4
+ resolution: "browserslist@npm:4.21.4"
dependencies:
- caniuse-lite: ^1.0.30001366
- electron-to-chromium: ^1.4.188
+ caniuse-lite: ^1.0.30001400
+ electron-to-chromium: ^1.4.251
node-releases: ^2.0.6
- update-browserslist-db: ^1.0.4
+ update-browserslist-db: ^1.0.9
bin:
browserslist: cli.js
- checksum: 30fe59f8b065f99665ea63819d29c797660f7975857c290f61f570403abed4d7039ca15b6fd21e39a57b87e1a9262f94676114040766fc0da6ccc11faf9fc377
+ checksum: 4af3793704dbb4615bcd29059ab472344dc7961c8680aa6c4bb84f05340e14038d06a5aead58724eae69455b8fade8b8c69f1638016e87e5578969d74c078b79
languageName: node
linkType: hard
@@ -2861,8 +2879,8 @@ __metadata:
linkType: hard
"cacache@npm:^16.1.0":
- version: 16.1.1
- resolution: "cacache@npm:16.1.1"
+ version: 16.1.3
+ resolution: "cacache@npm:16.1.3"
dependencies:
"@npmcli/fs": ^2.1.0
"@npmcli/move-file": ^2.0.0
@@ -2881,8 +2899,8 @@ __metadata:
rimraf: ^3.0.2
ssri: ^9.0.0
tar: ^6.1.11
- unique-filename: ^1.1.1
- checksum: 488524617008b793f0249b0c4ea2c330c710ca997921376e15650cc2415a8054491ae2dee9f01382c2015602c0641f3f977faf2fa7361aa33d2637dcfb03907a
+ unique-filename: ^2.0.0
+ checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6
languageName: node
linkType: hard
@@ -2893,10 +2911,25 @@ __metadata:
languageName: node
linkType: hard
-"cacheable-lookup@npm:^6.0.4":
- version: 6.0.4
- resolution: "cacheable-lookup@npm:6.0.4"
- checksum: 7aea70f5ea081aed12bf54fc165b9f80b580b0d210c85d55cc8fed2beaa9027fd321c1939c65dad945fe9eb207cea45442e01a48b5aa57542e125b716f022b6d
+"cacheable-lookup@npm:^7.0.0":
+ version: 7.0.0
+ resolution: "cacheable-lookup@npm:7.0.0"
+ checksum: 9e2856763fc0a7347ab34d704c010440b819d4bb5e3593b664381b7433e942dd22e67ee5581f12256f908e79b82d30b86ebbacf40a081bfe10ee93fbfbc2d6a9
+ languageName: node
+ linkType: hard
+
+"cacheable-request@npm:^10.2.1":
+ version: 10.2.3
+ resolution: "cacheable-request@npm:10.2.3"
+ dependencies:
+ "@types/http-cache-semantics": ^4.0.1
+ get-stream: ^6.0.1
+ http-cache-semantics: ^4.1.0
+ keyv: ^4.5.2
+ mimic-response: ^4.0.0
+ normalize-url: ^8.0.0
+ responselike: ^3.0.0
+ checksum: f54c57c40e54b2d23fabb9e3af94a11d9c7cd837627b143b9a2921c9ce25f0ae5da537b4a76006a086351423e6db05962066ae22af3362978569977acf0fde72
languageName: node
linkType: hard
@@ -2946,10 +2979,10 @@ __metadata:
languageName: node
linkType: hard
-"caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001366":
- version: 1.0.30001369
- resolution: "caniuse-lite@npm:1.0.30001369"
- checksum: f3f0eadc0685b399c0cad1116991d4c73d9fa10517ab72c3fb1c5e78fd5a019002f94a6d03b57a530009c69106400bdfa7dc0bddb01569428df67b9c5e411542
+"caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001400":
+ version: 1.0.30001439
+ resolution: "caniuse-lite@npm:1.0.30001439"
+ checksum: 3912dd536c9735713ca85e47721988bbcefb881ddb4886b0b9923fa984247fd22cba032cf268e57d158af0e8a2ae2eae042ae01942a1d6d7849fa9fa5d62fb82
languageName: node
linkType: hard
@@ -2981,6 +3014,25 @@ __metadata:
languageName: node
linkType: hard
+"chokidar@npm:>=3.0.0 <4.0.0":
+ version: 3.5.3
+ resolution: "chokidar@npm:3.5.3"
+ dependencies:
+ anymatch: ~3.1.2
+ braces: ~3.0.2
+ fsevents: ~2.3.2
+ glob-parent: ~5.1.2
+ is-binary-path: ~2.1.0
+ is-glob: ~4.0.1
+ normalize-path: ~3.0.0
+ readdirp: ~3.6.0
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ checksum: b49fcde40176ba007ff361b198a2d35df60d9bb2a5aab228279eb810feae9294a6b4649ab15981304447afe1e6ffbf4788ad5db77235dc770ab777c6e771980c
+ languageName: node
+ linkType: hard
+
"chownr@npm:^1.1.1":
version: 1.1.4
resolution: "chownr@npm:1.1.4"
@@ -2996,9 +3048,9 @@ __metadata:
linkType: hard
"ci-info@npm:^3.2.0":
- version: 3.3.2
- resolution: "ci-info@npm:3.3.2"
- checksum: fd81f1edd2d3b0f6cb077b2e84365136d87b9db8c055928c1ad69da8a76c2c2f19cba8ea51b90238302157ca927f91f92b653e933f2398dde4867500f08d6e62
+ version: 3.7.0
+ resolution: "ci-info@npm:3.7.0"
+ checksum: 6e5df0250382ff3732703b36b958d2d892dd3c481f9671666f96c2ab7888be744bc4dca81395be958dcb828502d94f18fa9aa8901c5a3c9923cda212df02724c
languageName: node
linkType: hard
@@ -3016,14 +3068,14 @@ __metadata:
languageName: node
linkType: hard
-"cliui@npm:^7.0.2":
- version: 7.0.4
- resolution: "cliui@npm:7.0.4"
+"cliui@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "cliui@npm:8.0.1"
dependencies:
string-width: ^4.2.0
- strip-ansi: ^6.0.0
+ strip-ansi: ^6.0.1
wrap-ansi: ^7.0.0
- checksum: ce2e8f578a4813806788ac399b9e866297740eecd4ad1823c27fd344d78b22c5f8597d548adbcc46f0573e43e21e751f39446c5a5e804a12aace402b7a315d7f
+ checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56
languageName: node
linkType: hard
@@ -3134,16 +3186,6 @@ __metadata:
languageName: node
linkType: hard
-"compress-brotli@npm:^1.3.8":
- version: 1.3.8
- resolution: "compress-brotli@npm:1.3.8"
- dependencies:
- "@types/json-buffer": ~3.0.0
- json-buffer: ~3.0.1
- checksum: de7589d692d40eb362f6c91070b5e51bc10b05a89eabb4a7c76c1aa21b625756f8c101c6999e4df0c4dc6199c5ca2e1353573bfdcca5615810f27485394162a5
- languageName: node
- linkType: hard
-
"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
@@ -3173,11 +3215,9 @@ __metadata:
linkType: hard
"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0":
- version: 1.8.0
- resolution: "convert-source-map@npm:1.8.0"
- dependencies:
- safe-buffer: ~5.1.1
- checksum: 985d974a2d33e1a2543ada51c93e1ba2f73eaed608dc39f229afc78f71dcc4c8b7d7c684aa647e3c6a3a204027444d69e53e169ce94e8d1fa8d7dee80c9c8fed
+ version: 1.9.0
+ resolution: "convert-source-map@npm:1.9.0"
+ checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8
languageName: node
linkType: hard
@@ -3199,17 +3239,17 @@ __metadata:
languageName: node
linkType: hard
-"core-js-pure@npm:^3.20.2":
- version: 3.23.5
- resolution: "core-js-pure@npm:3.23.5"
- checksum: a46edce5bdde29eda993c1f31086a08ba85e22fb1b3f77c9422e25ccc9e92fb9a1f3e7de5d13c9b807bdfeb0700b00bd5224375429cf44fd7ed5f46cc7d3e4ac
+"core-js-pure@npm:^3.25.1":
+ version: 3.26.1
+ resolution: "core-js-pure@npm:3.26.1"
+ checksum: d88c40e5e29e413c11d1ef991a8d5b6a63f00bd94707af0f649d3fc18b3524108b202f4ae75ce77620a1557d1ba340bc3362b4f25d590eccc37cf80fc75f7cd4
languageName: node
linkType: hard
"core-js@npm:^3":
- version: 3.24.1
- resolution: "core-js@npm:3.24.1"
- checksum: 6fb5bf0fd9e9f3e69d95616dd03332fea6758a715d2628c108b5faf17b48b0f580e90c4febb0a523c4665b0991a810de16289f86187fe79d70cc722dbd3edf0e
+ version: 3.26.1
+ resolution: "core-js@npm:3.26.1"
+ checksum: 0a01149f51ff1e9f41d1ea49cc4c9222047949ea597189ede7c4cf8cde3b097766b9c7615acc77c86fe65b4002f20b638a133dfba7b41dba830d707aeeed45ad
languageName: node
linkType: hard
@@ -3221,15 +3261,15 @@ __metadata:
linkType: hard
"cosmiconfig@npm:^7.0.0":
- version: 7.0.1
- resolution: "cosmiconfig@npm:7.0.1"
+ version: 7.1.0
+ resolution: "cosmiconfig@npm:7.1.0"
dependencies:
"@types/parse-json": ^4.0.0
import-fresh: ^3.2.1
parse-json: ^5.0.0
path-type: ^4.0.0
yaml: ^1.10.0
- checksum: 4be63e7117955fd88333d7460e4c466a90f556df6ef34efd59034d2463484e339666c41f02b523d574a797ec61f4a91918c5b89a316db2ea2f834e0d2d09465b
+ checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f
languageName: node
linkType: hard
@@ -3279,9 +3319,9 @@ __metadata:
linkType: hard
"csstype@npm:^3.0.2":
- version: 3.1.0
- resolution: "csstype@npm:3.1.0"
- checksum: 644e986cefab86525f0b674a06889cfdbb1f117e5b7d1ce0fc55b0423ecc58807a1ea42ecc75c4f18999d14fc42d1d255f84662a45003a52bb5840e977eb2ffd
+ version: 3.1.1
+ resolution: "csstype@npm:3.1.1"
+ checksum: 1f7b4f5fdd955b7444b18ebdddf3f5c699159f13e9cf8ac9027ae4a60ae226aef9bbb14a6e12ca7dba3358b007cee6354b116e720262867c398de6c955ea451d
languageName: node
linkType: hard
@@ -3405,9 +3445,9 @@ __metadata:
linkType: hard
"dayjs@npm:^1.11.6":
- version: 1.11.6
- resolution: "dayjs@npm:1.11.6"
- checksum: 18bdfd927009b68eab08dca578e421d4a581cefcbe9337f54c5d9e0d941ffb6b221c4b2c1cab15cdd9d419940e768ac4c984531461a90bbe1c158b75fe160580
+ version: 1.11.7
+ resolution: "dayjs@npm:1.11.7"
+ checksum: 5003a7c1dd9ed51385beb658231c3548700b82d3548c0cfbe549d85f2d08e90e972510282b7506941452c58d32136d6362f009c77ca55381a09c704e9f177ebb
languageName: node
linkType: hard
@@ -3554,24 +3594,25 @@ __metadata:
linkType: hard
"docker-modem@npm:^3.0.0":
- version: 3.0.5
- resolution: "docker-modem@npm:3.0.5"
+ version: 3.0.6
+ resolution: "docker-modem@npm:3.0.6"
dependencies:
debug: ^4.1.1
readable-stream: ^3.5.0
split-ca: ^1.0.1
- ssh2: ^1.4.0
- checksum: 79027f8e719a77031790af628f9aa1d72607ec3769149de3a4b683930f2e4d113ae0e3a7345b32ff3b2289f886879f4fcf216afb17908178ba00f9661c4e0dd6
+ ssh2: ^1.11.0
+ checksum: f80abc8ddf4d6026ba460bf66c8e039ef8e41a6705086a0770ce1b7cabd91bcd4681c32a6531b79dab23ceea680a3aae363bee29e8089b55a8eb775abfb6b67d
languageName: node
linkType: hard
"dockerode@npm:^3.3.2":
- version: 3.3.2
- resolution: "dockerode@npm:3.3.2"
+ version: 3.3.4
+ resolution: "dockerode@npm:3.3.4"
dependencies:
+ "@balena/dockerignore": ^1.0.2
docker-modem: ^3.0.0
tar-fs: ~2.0.1
- checksum: 69b60547ed2e6156e6ec1df16fccea9150c935ee0b0517723b4d05a5d840a01d4cd945341390d24b7fa301383be64145d563d9319be56d487a5bcbf9f872ee59
+ checksum: 6cb4b9d1c42feb3acfa77daf103b070cc412351dc7dc0a1553cc774ccd1be1a1412a87f8aa13c3155c63ec5c61a7cadc833b4248c4d8342814bbf708f795b952
languageName: node
linkType: hard
@@ -3657,28 +3698,28 @@ __metadata:
languageName: node
linkType: hard
-"electron-to-chromium@npm:^1.4.188":
- version: 1.4.199
- resolution: "electron-to-chromium@npm:1.4.199"
- checksum: d029a04cd765400bfa245c17e4895e15fcab3fd5c4dff7bfe1ceae9316a06fb4695b7078a50cfd04e0ca77ae27897520e4a8a332c13f7c2fdb2ee4a4b4593199
+"electron-to-chromium@npm:^1.4.251":
+ version: 1.4.284
+ resolution: "electron-to-chromium@npm:1.4.284"
+ checksum: be496e9dca6509dbdbb54dc32146fc99f8eb716d28a7ee8ccd3eba0066561df36fc51418d8bd7cf5a5891810bf56c0def3418e74248f51ea4a843d423603d10a
languageName: node
linkType: hard
"embla-carousel-react@npm:^7.0.0":
- version: 7.0.0
- resolution: "embla-carousel-react@npm:7.0.0"
+ version: 7.0.5
+ resolution: "embla-carousel-react@npm:7.0.5"
dependencies:
- embla-carousel: 7.0.0
+ embla-carousel: 7.0.5
peerDependencies:
- react: ^18.1.0
- checksum: d44b93901fb6a5be2236ce86115d7132a91f3e1943dc4d2cb0bccf045173008e947a35ebf0e345b90dc33ba06d8a0011913d45e2dbfd6cc47d58953bab96486e
+ react: ^16.8.0 || ^17.0.1 || ^18.0.0
+ checksum: 9d57f04b1fa22d3b8c5c82c4bf5b7522ec29a1888324cdfe3ec2b69c3440591c430ad741ee5d9e1fcb020bd0e567b61d7a5d599f36c711b4636468d2d8725b07
languageName: node
linkType: hard
-"embla-carousel@npm:7.0.0":
- version: 7.0.0
- resolution: "embla-carousel@npm:7.0.0"
- checksum: e662d18caf4371c04673372bf0e9144aec4b97629bbf48eb623e938ef27bd9f8de0d0f5b344d67bfdc777cce011518b7e833ec74773338292701c7d7efacb779
+"embla-carousel@npm:7.0.5":
+ version: 7.0.5
+ resolution: "embla-carousel@npm:7.0.5"
+ checksum: 59928624126f4537642c7391d8aacb617ff021e2bf290c2d54693feaa45a6aa04a08f213c98f76696dca67d1d3e681cba3c15bea279e7c336028019e5bdb39ef
languageName: node
linkType: hard
@@ -3758,34 +3799,36 @@ __metadata:
languageName: node
linkType: hard
-"es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5":
- version: 1.20.1
- resolution: "es-abstract@npm:1.20.1"
+"es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4":
+ version: 1.20.5
+ resolution: "es-abstract@npm:1.20.5"
dependencies:
call-bind: ^1.0.2
es-to-primitive: ^1.2.1
function-bind: ^1.1.1
function.prototype.name: ^1.1.5
- get-intrinsic: ^1.1.1
+ get-intrinsic: ^1.1.3
get-symbol-description: ^1.0.0
+ gopd: ^1.0.1
has: ^1.0.3
has-property-descriptors: ^1.0.0
has-symbols: ^1.0.3
internal-slot: ^1.0.3
- is-callable: ^1.2.4
+ is-callable: ^1.2.7
is-negative-zero: ^2.0.2
is-regex: ^1.1.4
is-shared-array-buffer: ^1.0.2
is-string: ^1.0.7
is-weakref: ^1.0.2
- object-inspect: ^1.12.0
+ object-inspect: ^1.12.2
object-keys: ^1.1.1
- object.assign: ^4.1.2
+ object.assign: ^4.1.4
regexp.prototype.flags: ^1.4.3
- string.prototype.trimend: ^1.0.5
- string.prototype.trimstart: ^1.0.5
+ safe-regex-test: ^1.0.0
+ string.prototype.trimend: ^1.0.6
+ string.prototype.trimstart: ^1.0.6
unbox-primitive: ^1.0.2
- checksum: 28da27ae0ed9c76df7ee8ef5c278df79dcfdb554415faf7068bb7c58f8ba8e2a16bfb59e586844be6429ab4c302ca7748979d48442224cb1140b051866d74b7f
+ checksum: 00564779ddaf7fb977ab5aa2b8ea2cbd4fa2335ad5368f788bd0bb094c86bc1790335dd9c3e30374bb0af2fa54c724fb4e0c73659dcfe8e427355a56f2b65946
languageName: node
linkType: hard
@@ -3911,12 +3954,14 @@ __metadata:
linkType: hard
"eslint-module-utils@npm:^2.7.3":
- version: 2.7.3
- resolution: "eslint-module-utils@npm:2.7.3"
+ version: 2.7.4
+ resolution: "eslint-module-utils@npm:2.7.4"
dependencies:
debug: ^3.2.7
- find-up: ^2.1.0
- checksum: 77048263f309167a1e6a1e1b896bfb5ddd1d3859b2e2abbd9c32c432aee13d610d46e6820b1ca81b37fba437cf423a404bc6649be64ace9148a3062d1886a678
+ peerDependenciesMeta:
+ eslint:
+ optional: true
+ checksum: 5da13645daff145a5c922896b258f8bba560722c3767254e458d894ff5fbb505d6dfd945bffa932a5b0ae06714da2379bd41011c4c20d2d59cc83e23895360f7
languageName: node
linkType: hard
@@ -3944,8 +3989,8 @@ __metadata:
linkType: hard
"eslint-plugin-jest@npm:^26.6.0":
- version: 26.6.0
- resolution: "eslint-plugin-jest@npm:26.6.0"
+ version: 26.9.0
+ resolution: "eslint-plugin-jest@npm:26.9.0"
dependencies:
"@typescript-eslint/utils": ^5.10.0
peerDependencies:
@@ -3956,7 +4001,7 @@ __metadata:
optional: true
jest:
optional: true
- checksum: 5dd60820d5618175e7203b077788476a6f697316b53d77c4bb7037b32073f3d5d539a72dec910eb3f8eedc97c3b28600ba35c5d3bf8c687ade765bb2d0dc77d2
+ checksum: 6d5fd5c95368f1ca2640389aeb7ce703d6202493c3ec6bdedb4eaca37233710508b0c75829e727765a16fd27029a466d34202bc7f2811c752038ccbbce224400
languageName: node
linkType: hard
@@ -3993,37 +4038,38 @@ __metadata:
linkType: hard
"eslint-plugin-react@npm:^7.30.1":
- version: 7.30.1
- resolution: "eslint-plugin-react@npm:7.30.1"
+ version: 7.31.11
+ resolution: "eslint-plugin-react@npm:7.31.11"
dependencies:
- array-includes: ^3.1.5
- array.prototype.flatmap: ^1.3.0
+ array-includes: ^3.1.6
+ array.prototype.flatmap: ^1.3.1
+ array.prototype.tosorted: ^1.1.1
doctrine: ^2.1.0
estraverse: ^5.3.0
jsx-ast-utils: ^2.4.1 || ^3.0.0
minimatch: ^3.1.2
- object.entries: ^1.1.5
- object.fromentries: ^2.0.5
- object.hasown: ^1.1.1
- object.values: ^1.1.5
+ object.entries: ^1.1.6
+ object.fromentries: ^2.0.6
+ object.hasown: ^1.1.2
+ object.values: ^1.1.6
prop-types: ^15.8.1
resolve: ^2.0.0-next.3
semver: ^6.3.0
- string.prototype.matchall: ^4.0.7
+ string.prototype.matchall: ^4.0.8
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
- checksum: 553fb9ece6beb7c14cf6f84670c786c8ac978c2918421994dcc4edd2385302022e5d5ac4a39fafdb35954e29cecddefed61758040c3c530cafcf651f674a9d51
+ checksum: a3d612f6647bef33cf2a67c81a6b37b42c075300ed079cffecf5fb475c0d6ab855c1de340d1cbf361a0126429fb906dda597527235d2d12c4404453dbc712fc6
languageName: node
linkType: hard
"eslint-plugin-testing-library@npm:^5.5.1":
- version: 5.5.1
- resolution: "eslint-plugin-testing-library@npm:5.5.1"
+ version: 5.9.1
+ resolution: "eslint-plugin-testing-library@npm:5.9.1"
dependencies:
"@typescript-eslint/utils": ^5.13.0
peerDependencies:
eslint: ^7.5.0 || ^8.0.0
- checksum: 558994da12e6a9ff0c4f71c2e63a23746b6323d171062032843591e0fca6ce3811f979cf82e11db003c8b4f1d9842cb75301bfaa9e88d1a399b11ea6686aadcc
+ checksum: d09f9486945807e9587d52b6979117bc41b750df741567381a06219671096afb318696a0e0db63e253e150fead40e77ef9653ee00f1dda83fc8920e3b3c47107
languageName: node
linkType: hard
@@ -4095,11 +4141,13 @@ __metadata:
linkType: hard
"eslint@npm:^8.20.0":
- version: 8.20.0
- resolution: "eslint@npm:8.20.0"
+ version: 8.29.0
+ resolution: "eslint@npm:8.29.0"
dependencies:
- "@eslint/eslintrc": ^1.3.0
- "@humanwhocodes/config-array": ^0.9.2
+ "@eslint/eslintrc": ^1.3.3
+ "@humanwhocodes/config-array": ^0.11.6
+ "@humanwhocodes/module-importer": ^1.0.1
+ "@nodelib/fs.walk": ^1.2.8
ajv: ^6.10.0
chalk: ^4.0.0
cross-spawn: ^7.0.2
@@ -4109,18 +4157,21 @@ __metadata:
eslint-scope: ^7.1.1
eslint-utils: ^3.0.0
eslint-visitor-keys: ^3.3.0
- espree: ^9.3.2
+ espree: ^9.4.0
esquery: ^1.4.0
esutils: ^2.0.2
fast-deep-equal: ^3.1.3
file-entry-cache: ^6.0.1
- functional-red-black-tree: ^1.0.1
- glob-parent: ^6.0.1
+ find-up: ^5.0.0
+ glob-parent: ^6.0.2
globals: ^13.15.0
+ grapheme-splitter: ^1.0.4
ignore: ^5.2.0
import-fresh: ^3.0.0
imurmurhash: ^0.1.4
is-glob: ^4.0.0
+ is-path-inside: ^3.0.3
+ js-sdsl: ^4.1.4
js-yaml: ^4.1.0
json-stable-stringify-without-jsonify: ^1.0.1
levn: ^0.4.1
@@ -4132,21 +4183,20 @@ __metadata:
strip-ansi: ^6.0.1
strip-json-comments: ^3.1.0
text-table: ^0.2.0
- v8-compile-cache: ^2.0.3
bin:
eslint: bin/eslint.js
- checksum: a31adf390d71d916925586bc8467b48f620e93dd0416bc1e897d99265af88b48d4eba3985b5ff4653ae5cc46311a360d373574002277e159bb38a4363abf9228
+ checksum: e05204b05907b82d910983995cb946e0ba62ca514eb2b6791c43f623333b143564a2eee0139909d31c10935c21877d815b1f76dd674a59cb91c471064325c4ab
languageName: node
linkType: hard
-"espree@npm:^9.3.2":
- version: 9.3.2
- resolution: "espree@npm:9.3.2"
+"espree@npm:^9.4.0":
+ version: 9.4.1
+ resolution: "espree@npm:9.4.1"
dependencies:
- acorn: ^8.7.1
+ acorn: ^8.8.0
acorn-jsx: ^5.3.2
eslint-visitor-keys: ^3.3.0
- checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30
+ checksum: 4d266b0cf81c7dfe69e542c7df0f246e78d29f5b04dda36e514eb4c7af117ee6cfbd3280e560571ed82ff6c9c3f0003c05b82583fc7a94006db7497c4fe4270e
languageName: node
linkType: hard
@@ -4251,15 +4301,15 @@ __metadata:
linkType: hard
"fast-glob@npm:^3.2.9":
- version: 3.2.11
- resolution: "fast-glob@npm:3.2.11"
+ version: 3.2.12
+ resolution: "fast-glob@npm:3.2.12"
dependencies:
"@nodelib/fs.stat": ^2.0.2
"@nodelib/fs.walk": ^1.2.3
glob-parent: ^5.1.2
merge2: ^1.3.0
micromatch: ^4.0.4
- checksum: f473105324a7780a20c06de842e15ddbb41d3cb7e71d1e4fe6e8373204f22245d54f5ab9e2061e6a1c613047345954d29b022e0e76f5c28b1df9858179a0e6d7
+ checksum: 0b1990f6ce831c7e28c4d505edcdaad8e27e88ab9fa65eedadb730438cfc7cde4910d6c975d6b7b8dc8a73da4773702ebcfcd6e3518e73938bb1383badfe01c2
languageName: node
linkType: hard
@@ -4278,20 +4328,20 @@ __metadata:
linkType: hard
"fastq@npm:^1.6.0":
- version: 1.13.0
- resolution: "fastq@npm:1.13.0"
+ version: 1.14.0
+ resolution: "fastq@npm:1.14.0"
dependencies:
reusify: ^1.0.4
- checksum: 32cf15c29afe622af187d12fc9cd93e160a0cb7c31a3bb6ace86b7dea3b28e7b72acde89c882663f307b2184e14782c6c664fa315973c03626c7d4bff070bb0b
+ checksum: da2c05ec1446ef77b8ba2b76619c90d483404f5087e71e77469fbee797280a1f4ef26a63be15b2377198bc20d09fdf25c7d6e1e492a1e568a29dfdd9bcb7538c
languageName: node
linkType: hard
"fb-watchman@npm:^2.0.0":
- version: 2.0.1
- resolution: "fb-watchman@npm:2.0.1"
+ version: 2.0.2
+ resolution: "fb-watchman@npm:2.0.2"
dependencies:
bser: 2.1.1
- checksum: 8510230778ab3a51c27dffb1b76ef2c24fab672a42742d3c0a45c2e9d1e5f20210b1fbca33486088da4a9a3958bde96b5aec0a63aac9894b4e9df65c88b2cbd6
+ checksum: b15a124cef28916fe07b400eb87cbc73ca082c142abf7ca8e8de6af43eca79ca7bd13eb4d4d48240b3bd3136eaac40d16e42d6edf87a8e5d1dd8070626860c78
languageName: node
linkType: hard
@@ -4322,6 +4372,13 @@ __metadata:
languageName: node
linkType: hard
+"fily-publish-gridstack@npm:^0.0.13":
+ version: 0.0.13
+ resolution: "fily-publish-gridstack@npm:0.0.13"
+ checksum: e025e32982397e0cd8d37f022a39911172579bec984a7cf05c10ee06b9b8a51471820c74b95f2dc9bde97af98e305527545955ad4ca6cc89b830c864f91bb755
+ languageName: node
+ linkType: hard
+
"find-root@npm:^1.1.0":
version: 1.1.0
resolution: "find-root@npm:1.1.0"
@@ -4329,15 +4386,6 @@ __metadata:
languageName: node
linkType: hard
-"find-up@npm:^2.1.0":
- version: 2.1.0
- resolution: "find-up@npm:2.1.0"
- dependencies:
- locate-path: ^2.0.0
- checksum: 43284fe4da09f89011f08e3c32cd38401e786b19226ea440b75386c1b12a4cb738c94969808d53a84f564ede22f732c8409e3cfc3f7fb5b5c32378ad0bbf28bd
- languageName: node
- linkType: hard
-
"find-up@npm:^4.0.0, find-up@npm:^4.1.0":
version: 4.1.0
resolution: "find-up@npm:4.1.0"
@@ -4348,6 +4396,16 @@ __metadata:
languageName: node
linkType: hard
+"find-up@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "find-up@npm:5.0.0"
+ dependencies:
+ locate-path: ^6.0.0
+ path-exists: ^4.0.0
+ checksum: 07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095
+ languageName: node
+ linkType: hard
+
"flat-cache@npm:^3.0.4":
version: 3.0.4
resolution: "flat-cache@npm:3.0.4"
@@ -4359,26 +4417,26 @@ __metadata:
linkType: hard
"flatted@npm:^3.1.0":
- version: 3.2.6
- resolution: "flatted@npm:3.2.6"
- checksum: 33b87aa88dfa40ca6ee31d7df61712bbbad3d3c05c132c23e59b9b61d34631b337a18ff2b8dc5553acdc871ec72b741e485f78969cf006124a3f57174de29a0e
+ version: 3.2.7
+ resolution: "flatted@npm:3.2.7"
+ checksum: 427633049d55bdb80201c68f7eb1cbd533e03eac541f97d3aecab8c5526f12a20ccecaeede08b57503e772c769e7f8680b37e8d482d1e5f8d7e2194687f9ea35
languageName: node
linkType: hard
"follow-redirects@npm:^1.14.9":
- version: 1.15.1
- resolution: "follow-redirects@npm:1.15.1"
+ version: 1.15.2
+ resolution: "follow-redirects@npm:1.15.2"
peerDependenciesMeta:
debug:
optional: true
- checksum: 6aa4e3e3cdfa3b9314801a1cd192ba756a53479d9d8cca65bf4db3a3e8834e62139245cd2f9566147c8dfe2efff1700d3e6aefd103de4004a7b99985e71dd533
+ checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190
languageName: node
linkType: hard
-"form-data-encoder@npm:1.7.1":
- version: 1.7.1
- resolution: "form-data-encoder@npm:1.7.1"
- checksum: a2a360d5588a70d323c12a140c3db23a503a38f0a5d141af1efad579dde9f9fff2e49e5f31f378cb4631518c1ab4a826452c92f0d2869e954b6b2d77b05613e1
+"form-data-encoder@npm:^2.1.2":
+ version: 2.1.4
+ resolution: "form-data-encoder@npm:2.1.4"
+ checksum: e0b3e5950fb69b3f32c273944620f9861f1933df9d3e42066e038e26dfb343d0f4465de9f27e0ead1a09d9df20bc2eed06a63c2ca2f8f00949e7202bae9e29dd
languageName: node
linkType: hard
@@ -4393,13 +4451,23 @@ __metadata:
languageName: node
linkType: hard
-"formdata-node@npm:^4.3.2, formdata-node@npm:^4.3.3":
- version: 4.3.3
- resolution: "formdata-node@npm:4.3.3"
+"formdata-node@npm:^4.3.3":
+ version: 4.4.1
+ resolution: "formdata-node@npm:4.4.1"
dependencies:
node-domexception: 1.0.0
- web-streams-polyfill: 4.0.0-beta.1
- checksum: ad5f627e694a977edb1986c4b351c863196ee409e460c644adcb242bfc78ca9faa4877cba8113a71cecc0f0e21320d3c3d0acac95445817e014001ce147ba362
+ web-streams-polyfill: 4.0.0-beta.3
+ checksum: d91d4f667cfed74827fc281594102c0dabddd03c9f8b426fc97123eedbf73f5060ee43205d89284d6854e2fc5827e030cd352ef68b93beda8decc2d72128c576
+ languageName: node
+ linkType: hard
+
+"formdata-node@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "formdata-node@npm:5.0.0"
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 4.0.0-beta.3
+ checksum: 384126102c5c541e1203a372f23417a1bbd7b2d866da6c15f2d397bf3475f08ed01b7f940eabb0ca6edfc370ae5bc8d09d3f89a285529016750757a6c2f98bd2
languageName: node
linkType: hard
@@ -4456,7 +4524,7 @@ __metadata:
languageName: node
linkType: hard
-"fsevents@npm:^2.3.2":
+"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2":
version: 2.3.2
resolution: "fsevents@npm:2.3.2"
dependencies:
@@ -4466,7 +4534,7 @@ __metadata:
languageName: node
linkType: hard
-"fsevents@patch:fsevents@^2.3.2#~builtin":
+"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin":
version: 2.3.2
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=18f3a7"
dependencies:
@@ -4494,13 +4562,6 @@ __metadata:
languageName: node
linkType: hard
-"functional-red-black-tree@npm:^1.0.1":
- version: 1.0.1
- resolution: "functional-red-black-tree@npm:1.0.1"
- checksum: ca6c170f37640e2d94297da8bb4bf27a1d12bea3e00e6a3e007fd7aa32e37e000f5772acf941b4e4f3cf1c95c3752033d0c509af157ad8f526e7f00723b9eb9f
- languageName: node
- linkType: hard
-
"functions-have-names@npm:^1.2.2":
version: 1.2.3
resolution: "functions-have-names@npm:1.2.3"
@@ -4538,14 +4599,14 @@ __metadata:
languageName: node
linkType: hard
-"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
- version: 1.1.2
- resolution: "get-intrinsic@npm:1.1.2"
+"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3":
+ version: 1.1.3
+ resolution: "get-intrinsic@npm:1.1.3"
dependencies:
function-bind: ^1.1.1
has: ^1.0.3
has-symbols: ^1.0.3
- checksum: 252f45491f2ba88ebf5b38018020c7cc3279de54b1d67ffb70c0cdf1dfa8ab31cd56467b5d117a8b4275b7a4dde91f86766b163a17a850f036528a7b2faafb2b
+ checksum: 152d79e87251d536cf880ba75cfc3d6c6c50e12b3a64e1ea960e73a3752b47c69f46034456eae1b0894359ce3bc64c55c186f2811f8a788b75b638b06fab228a
languageName: node
linkType: hard
@@ -4589,7 +4650,7 @@ __metadata:
languageName: node
linkType: hard
-"glob-parent@npm:^5.1.2":
+"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
version: 5.1.2
resolution: "glob-parent@npm:5.1.2"
dependencies:
@@ -4598,7 +4659,7 @@ __metadata:
languageName: node
linkType: hard
-"glob-parent@npm:^6.0.1":
+"glob-parent@npm:^6.0.2":
version: 6.0.2
resolution: "glob-parent@npm:6.0.2"
dependencies:
@@ -4656,11 +4717,11 @@ __metadata:
linkType: hard
"globals@npm:^13.15.0":
- version: 13.17.0
- resolution: "globals@npm:13.17.0"
+ version: 13.18.0
+ resolution: "globals@npm:13.18.0"
dependencies:
type-fest: ^0.20.2
- checksum: fbaf4112e59b92c9f5575e85ce65e9e17c0b82711196ec5f58beb08599bbd92fd72703d6dfc9b080381fd35b644e1b11dcf25b38cc2341ec21df942594cbc8ce
+ checksum: 9fdaa74cfd5d4ac91319662f512c29b11d1d2deb9c8a20d3998097671deba83d195f20730b2345887de3ddab958a6fa68952feed9ae836ee4594a82ace62fdb4
languageName: node
linkType: hard
@@ -4678,9 +4739,18 @@ __metadata:
languageName: node
linkType: hard
+"gopd@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "gopd@npm:1.0.1"
+ dependencies:
+ get-intrinsic: ^1.1.3
+ checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6
+ languageName: node
+ linkType: hard
+
"got@npm:^11.8.2":
- version: 11.8.5
- resolution: "got@npm:11.8.5"
+ version: 11.8.6
+ resolution: "got@npm:11.8.6"
dependencies:
"@sindresorhus/is": ^4.0.0
"@szmarczak/http-timer": ^4.0.5
@@ -4693,28 +4763,26 @@ __metadata:
lowercase-keys: ^2.0.0
p-cancelable: ^2.0.0
responselike: ^2.0.0
- checksum: 2de8a1bbda4e9b6b2b72b2d2100bc055a59adc1740529e631f61feb44a8b9a1f9f8590941ed9da9df0090b6d6d0ed8ffee94cd9ac086ec3409b392b33440f7d2
+ checksum: bbc783578a8d5030c8164ef7f57ce41b5ad7db2ed13371e1944bef157eeca5a7475530e07c0aaa71610d7085474d0d96222c9f4268d41db333a17e39b463f45d
languageName: node
linkType: hard
-"got@npm:^12.1.0":
- version: 12.1.0
- resolution: "got@npm:12.1.0"
+"got@npm:^12.1.0, got@npm:^12.5.0, got@npm:^12.5.2":
+ version: 12.5.3
+ resolution: "got@npm:12.5.3"
dependencies:
- "@sindresorhus/is": ^4.6.0
+ "@sindresorhus/is": ^5.2.0
"@szmarczak/http-timer": ^5.0.1
- "@types/cacheable-request": ^6.0.2
- "@types/responselike": ^1.0.0
- cacheable-lookup: ^6.0.4
- cacheable-request: ^7.0.2
+ cacheable-lookup: ^7.0.0
+ cacheable-request: ^10.2.1
decompress-response: ^6.0.0
- form-data-encoder: 1.7.1
+ form-data-encoder: ^2.1.2
get-stream: ^6.0.1
http2-wrapper: ^2.1.10
lowercase-keys: ^3.0.0
p-cancelable: ^3.0.0
- responselike: ^2.0.0
- checksum: 1cc9af6ca511338a7f1bbb0943999e6ac324ea3c7d826066c02e530b4ac41147b1a4cadad21b28c3938de82185ac99c33d64a3a4560c6e0b0b125191ba6ee619
+ responselike: ^3.0.0
+ checksum: e35ea3ccdb5f2c36d0bb9648a6a87300d017900ce2e647ad95f54a6fb674a82fe7d53b2c838542d15a9fa25290cc5361d6f82cadac3e94b2e91d93b5670cf304
languageName: node
linkType: hard
@@ -4725,6 +4793,13 @@ __metadata:
languageName: node
linkType: hard
+"grapheme-splitter@npm:^1.0.4":
+ version: 1.0.4
+ resolution: "grapheme-splitter@npm:1.0.4"
+ checksum: 0c22ec54dee1b05cd480f78cf14f732cb5b108edc073572c4ec205df4cd63f30f8db8025afc5debc8835a8ddeacf648a1c7992fe3dcd6ad38f9a476d84906620
+ languageName: node
+ linkType: hard
+
"gzip-size@npm:^6.0.0":
version: 6.0.0
resolution: "gzip-size@npm:6.0.0"
@@ -4764,7 +4839,7 @@ __metadata:
languageName: node
linkType: hard
-"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3":
+"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3":
version: 1.0.3
resolution: "has-symbols@npm:1.0.3"
checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410
@@ -4825,29 +4900,29 @@ __metadata:
"@dnd-kit/utilities": ^3.2.0
"@emotion/react": ^11.10.5
"@emotion/server": ^11.10.0
- "@mantine/carousel": ^5.1.0
- "@mantine/core": ^5.7.2
- "@mantine/dates": ^5.7.2
- "@mantine/dropzone": ^5.7.2
- "@mantine/form": ^5.7.2
- "@mantine/hooks": ^5.7.2
- "@mantine/modals": ^5.7.2
- "@mantine/next": ^5.2.3
- "@mantine/notifications": ^5.7.2
- "@mantine/prism": ^5.0.0
+ "@mantine/carousel": ^5.9.3
+ "@mantine/core": ^5.9.3
+ "@mantine/dates": ^5.9.3
+ "@mantine/dropzone": ^5.9.3
+ "@mantine/form": ^5.9.3
+ "@mantine/hooks": ^5.9.3
+ "@mantine/modals": ^5.9.3
+ "@mantine/next": ^5.9.3
+ "@mantine/notifications": ^5.9.3
+ "@mantine/prism": ^5.9.3
"@next/bundle-analyzer": ^12.1.4
"@next/eslint-plugin-next": ^12.1.4
"@nivo/core": ^0.79.0
"@nivo/line": ^0.79.1
- "@tabler/icons": ^1.78.0
+ "@tabler/icons": ^1.106.0
"@tanstack/react-query": ^4.2.1
"@types/dockerode": ^3.3.9
"@types/node": 17.0.1
+ "@types/ping": ^0.4.1
"@types/react": 17.0.1
"@types/uuid": ^8.3.4
"@typescript-eslint/eslint-plugin": ^5.30.7
"@typescript-eslint/parser": ^5.30.7
- add: ^2.0.6
axios: ^0.27.2
consola: ^2.15.3
cookies-next: ^2.1.1
@@ -4865,6 +4940,7 @@ __metadata:
eslint-plugin-react-hooks: ^4.6.0
eslint-plugin-testing-library: ^5.5.1
eslint-plugin-unused-imports: ^2.0.0
+ fily-publish-gridstack: ^0.0.13
framer-motion: ^6.5.1
i18next: ^21.9.1
i18next-browser-languagedetector: ^6.1.5
@@ -4874,16 +4950,19 @@ __metadata:
next: 12.2.0
next-i18next: ^11.3.0
nzbget-api: ^0.0.3
+ ping: ^0.4.2
prettier: ^2.7.1
prism-react-renderer: ^1.3.5
react: ^18.2.0
react-dom: ^18.2.0
sabnzbd-api: ^1.5.0
+ sass: ^1.56.1
sharp: ^0.30.7
systeminformation: ^5.12.1
typescript: ^4.7.4
uuid: ^8.3.2
yarn: ^1.22.19
+ zustand: ^4.1.4
languageName: unknown
linkType: soft
@@ -4983,12 +5062,12 @@ __metadata:
linkType: hard
"http2-wrapper@npm:^2.1.10":
- version: 2.1.11
- resolution: "http2-wrapper@npm:2.1.11"
+ version: 2.2.0
+ resolution: "http2-wrapper@npm:2.2.0"
dependencies:
quick-lru: ^5.1.1
resolve-alpn: ^1.2.0
- checksum: 5da05aa2c77226ac9cc82c616383f59c8f31b79897b02ecbe44b09714be1fca1f21bb184e672a669ca2830eefea4edac5f07e71c00cb5a8c5afec8e5a20cfaf7
+ checksum: 6fd20e5cb6a58151715b3581e06a62a47df943187d2d1f69e538a50cccb7175dd334ecfde7900a37d18f3e13a1a199518a2c211f39860e81e9a16210c199cfaa
languageName: node
linkType: hard
@@ -5019,36 +5098,36 @@ __metadata:
linkType: hard
"i18next-browser-languagedetector@npm:^6.1.5":
- version: 6.1.5
- resolution: "i18next-browser-languagedetector@npm:6.1.5"
+ version: 6.1.8
+ resolution: "i18next-browser-languagedetector@npm:6.1.8"
dependencies:
- "@babel/runtime": ^7.18.9
- checksum: af3a785454dfb7e3595215c3738062b82173601a788b0a7c6fe78b6c50f0f46cd89508300f694c324d4a07b19743d2410be50aa6e6494967a75aedb17ad5033f
+ "@babel/runtime": ^7.19.0
+ checksum: dd84d3c9cb693a70662436b06f5554898815df33b7641249a64876c74c38960f11ef17b4d7f49ee2da7262abe0f3ae73abe7f3a3b435a344d0d07e4ca7cb24c6
languageName: node
linkType: hard
"i18next-fs-backend@npm:^1.1.4":
- version: 1.1.5
- resolution: "i18next-fs-backend@npm:1.1.5"
- checksum: 71f6c4b0ff071676d69f1668675a68f2d72e1836dafcc8014123523bb584a78b0e4fccd16f83d7f37755b58d1dfcb4d6ad36c60b261833b509ccf20313419d9e
+ version: 1.2.0
+ resolution: "i18next-fs-backend@npm:1.2.0"
+ checksum: da74d20f2b007f8e34eaf442fa91ad12aaff3b9891e066c6addd6d111b37e370c62370dfbc656730ab2f8afd988f2e7ea1c48301ebb19ccb716fb5965600eddf
languageName: node
linkType: hard
"i18next-http-backend@npm:^1.4.1":
- version: 1.4.1
- resolution: "i18next-http-backend@npm:1.4.1"
+ version: 1.4.5
+ resolution: "i18next-http-backend@npm:1.4.5"
dependencies:
cross-fetch: 3.1.5
- checksum: 1ed4c68c458cc5e7c60af3b641223b9f1b49b6e7ded0fb908cf034ddf62de401db9bb8bb0f6be0634c53ceeee0fec7e03e7171b0dea2cbebca5bbcee6da46e2f
+ checksum: 1978a9d7970cc711e96133553e5f3815cf16c3e2f8db7982036f8c913c5a64eb20953e85e0ab48a88ad3c754f51184b67a778655ed65aeaae46430cdc1f673da
languageName: node
linkType: hard
"i18next@npm:^21.8.13, i18next@npm:^21.9.1":
- version: 21.9.1
- resolution: "i18next@npm:21.9.1"
+ version: 21.10.0
+ resolution: "i18next@npm:21.10.0"
dependencies:
"@babel/runtime": ^7.17.2
- checksum: 1bc59c61fbb27385841f76436c7dd60e9f42a3fb326797db44a65dd165c489420e549b5370e3de75b85f8d61239f4869fc9fbcf63deae5f40ee606bc04916e6d
+ checksum: f997985e2d4d15a62a0936a82ff6420b97f3f971e776fe685bdd50b4de0cb4dc2198bc75efe6b152844794ebd5040d8060d6d152506a687affad534834836d81
languageName: node
linkType: hard
@@ -5069,9 +5148,16 @@ __metadata:
linkType: hard
"ignore@npm:^5.2.0":
- version: 5.2.0
- resolution: "ignore@npm:5.2.0"
- checksum: 6b1f926792d614f64c6c83da3a1f9c83f6196c2839aa41e1e32dd7b8d174cef2e329d75caabb62cb61ce9dc432f75e67d07d122a037312db7caa73166a1bdb77
+ version: 5.2.1
+ resolution: "ignore@npm:5.2.1"
+ checksum: 7251d00cba49fe88c4f3565fadeb4aa726ba38294a9a79ffed542edc47bafd989d4b2ccf65700c5b1b26a1e91dfc7218fb23017937c79216025d5caeec0ee9d5
+ languageName: node
+ linkType: hard
+
+"immutable@npm:^4.0.0":
+ version: 4.1.0
+ resolution: "immutable@npm:4.1.0"
+ checksum: b9bc1f14fb18eb382d48339c064b24a1f97ae4cf43102e0906c0a6e186a27afcd18b55ca4a0b63c98eefb58143e2b5ebc7755a5fb4da4a7ad84b7a6096ac5b13
languageName: node
linkType: hard
@@ -5197,6 +5283,15 @@ __metadata:
languageName: node
linkType: hard
+"is-binary-path@npm:~2.1.0":
+ version: 2.1.0
+ resolution: "is-binary-path@npm:2.1.0"
+ dependencies:
+ binary-extensions: ^2.0.0
+ checksum: 84192eb88cff70d320426f35ecd63c3d6d495da9d805b19bc65b518984b7c0760280e57dbf119b7e9be6b161784a5a673ab2c6abe83abb5198a432232ad5b35c
+ languageName: node
+ linkType: hard
+
"is-boolean-object@npm:^1.1.0":
version: 1.1.2
resolution: "is-boolean-object@npm:1.1.2"
@@ -5207,19 +5302,19 @@ __metadata:
languageName: node
linkType: hard
-"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4":
- version: 1.2.4
- resolution: "is-callable@npm:1.2.4"
- checksum: 1a28d57dc435797dae04b173b65d6d1e77d4f16276e9eff973f994eadcfdc30a017e6a597f092752a083c1103cceb56c91e3dadc6692fedb9898dfaba701575f
+"is-callable@npm:^1.1.4, is-callable@npm:^1.2.7":
+ version: 1.2.7
+ resolution: "is-callable@npm:1.2.7"
+ checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac
languageName: node
linkType: hard
"is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0":
- version: 2.9.0
- resolution: "is-core-module@npm:2.9.0"
+ version: 2.11.0
+ resolution: "is-core-module@npm:2.11.0"
dependencies:
has: ^1.0.3
- checksum: b27034318b4b462f1c8f1dfb1b32baecd651d891a4e2d1922135daeff4141dfced2b82b07aef83ef54275c4a3526aa38da859223664d0868ca24182badb784ce
+ checksum: f96fd490c6b48eb4f6d10ba815c6ef13f410b0ba6f7eb8577af51697de523e5f2cd9de1c441b51d27251bf0e4aebc936545e33a5d26d5d51f28d25698d4a8bab
languageName: node
linkType: hard
@@ -5253,7 +5348,7 @@ __metadata:
languageName: node
linkType: hard
-"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3":
+"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
version: 4.0.3
resolution: "is-glob@npm:4.0.3"
dependencies:
@@ -5292,6 +5387,13 @@ __metadata:
languageName: node
linkType: hard
+"is-path-inside@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "is-path-inside@npm:3.0.3"
+ checksum: abd50f06186a052b349c15e55b182326f1936c89a78bf6c8f2b707412517c097ce04bc49a0ca221787bc44e1049f51f09a2ffb63d22899051988d3a618ba13e9
+ languageName: node
+ linkType: hard
+
"is-regex@npm:^1.1.4":
version: 1.1.4
resolution: "is-regex@npm:1.1.4"
@@ -5374,15 +5476,15 @@ __metadata:
linkType: hard
"istanbul-lib-instrument@npm:^5.0.4, istanbul-lib-instrument@npm:^5.1.0":
- version: 5.2.0
- resolution: "istanbul-lib-instrument@npm:5.2.0"
+ version: 5.2.1
+ resolution: "istanbul-lib-instrument@npm:5.2.1"
dependencies:
"@babel/core": ^7.12.3
"@babel/parser": ^7.14.7
"@istanbuljs/schema": ^0.1.2
istanbul-lib-coverage: ^3.2.0
semver: ^6.3.0
- checksum: 7c242ed782b6bf7b655656576afae8b6bd23dcc020e5fdc1472cca3dfb6ddb196a478385206d0df5219b9babf46ac4f21fea5d8ea9a431848b6cca6007012353
+ checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272
languageName: node
linkType: hard
@@ -5648,14 +5750,14 @@ __metadata:
linkType: hard
"jest-pnp-resolver@npm:^1.2.2":
- version: 1.2.2
- resolution: "jest-pnp-resolver@npm:1.2.2"
+ version: 1.2.3
+ resolution: "jest-pnp-resolver@npm:1.2.3"
peerDependencies:
jest-resolve: "*"
peerDependenciesMeta:
jest-resolve:
optional: true
- checksum: bd85dcc0e76e0eb0c3d56382ec140f08d25ff4068cda9d0e360bb78fb176cb726d0beab82dc0e8694cafd09f55fee7622b8bcb240afa5fad301f4ed3eebb4f47
+ checksum: db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2
languageName: node
linkType: hard
@@ -5864,6 +5966,13 @@ __metadata:
languageName: node
linkType: hard
+"js-sdsl@npm:^4.1.4":
+ version: 4.2.0
+ resolution: "js-sdsl@npm:4.2.0"
+ checksum: 2cd0885f7212afb355929d72ca105cb37de7e95ad6031e6a32619eaefa46735a7d0fb682641a0ba666e1519cb138fe76abc1eea8a34e224140c9d94c995171f1
+ languageName: node
+ linkType: hard
+
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
@@ -5903,7 +6012,7 @@ __metadata:
languageName: node
linkType: hard
-"json-buffer@npm:3.0.1, json-buffer@npm:~3.0.1":
+"json-buffer@npm:3.0.1":
version: 3.0.1
resolution: "json-buffer@npm:3.0.1"
checksum: 9026b03edc2847eefa2e37646c579300a1f3a4586cfb62bf857832b60c852042d0d6ae55d1afb8926163fa54c2b01d83ae24705f34990348bdac6273a29d4581
@@ -5952,22 +6061,21 @@ __metadata:
linkType: hard
"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.2":
- version: 3.3.2
- resolution: "jsx-ast-utils@npm:3.3.2"
+ version: 3.3.3
+ resolution: "jsx-ast-utils@npm:3.3.3"
dependencies:
array-includes: ^3.1.5
- object.assign: ^4.1.2
- checksum: 61d4596d44480afc03ae0a7ebb272aa6603dc4c3645805dea0fc8d9f0693542cd0959f3ba7c0c9b16c13dd5a900c7c4310108bada273132a8355efe3fed22064
+ object.assign: ^4.1.3
+ checksum: a2ed78cac49a0f0c4be8b1eafe3c5257a1411341d8e7f1ac740debae003de04e5f6372bfcfbd9d082e954ffd99aac85bcda85b7c6bc11609992483f4cdc0f745
languageName: node
linkType: hard
-"keyv@npm:^4.0.0":
- version: 4.3.3
- resolution: "keyv@npm:4.3.3"
+"keyv@npm:^4.0.0, keyv@npm:^4.5.2":
+ version: 4.5.2
+ resolution: "keyv@npm:4.5.2"
dependencies:
- compress-brotli: ^1.3.8
json-buffer: 3.0.1
- checksum: bcc946eeec3407fb3b42d831ce985357162113c5f07a8c45c12ede39704ba2d99be4c3dded76d2d2d2a2366627e42440bdde24393216164156928399949c12a1
+ checksum: 13ad58303acd2261c0d4831b4658451603fd159e61daea2121fcb15feb623e75ee328cded0572da9ca76b7b3ceaf8e614f1806c6b3af5db73c9c35a345259651
languageName: node
linkType: hard
@@ -5985,7 +6093,7 @@ __metadata:
languageName: node
linkType: hard
-"language-subtag-registry@npm:~0.3.2":
+"language-subtag-registry@npm:^0.3.20":
version: 0.3.22
resolution: "language-subtag-registry@npm:0.3.22"
checksum: 8ab70a7e0e055fe977ac16ea4c261faec7205ac43db5e806f72e5b59606939a3b972c4bd1e10e323b35d6ffa97c3e1c4c99f6553069dad2dfdd22020fa3eb56a
@@ -5993,11 +6101,11 @@ __metadata:
linkType: hard
"language-tags@npm:^1.0.5":
- version: 1.0.5
- resolution: "language-tags@npm:1.0.5"
+ version: 1.0.6
+ resolution: "language-tags@npm:1.0.6"
dependencies:
- language-subtag-registry: ~0.3.2
- checksum: c81b5d8b9f5f9cfd06ee71ada6ddfe1cf83044dd5eeefcd1e420ad491944da8957688db4a0a9bc562df4afdc2783425cbbdfd152c01d93179cf86888903123cf
+ language-subtag-registry: ^0.3.20
+ checksum: dc2927f7ce8f108ffd1d02ae0284b78ff6b4e03e631642794fa79d554d77b653f3f64cd1fb83acc9f3746ef7c18d43241b97feb712c05cc26e25aacd68f7a006
languageName: node
linkType: hard
@@ -6025,16 +6133,6 @@ __metadata:
languageName: node
linkType: hard
-"locate-path@npm:^2.0.0":
- version: 2.0.0
- resolution: "locate-path@npm:2.0.0"
- dependencies:
- p-locate: ^2.0.0
- path-exists: ^3.0.0
- checksum: 02d581edbbbb0fa292e28d96b7de36b5b62c2fa8b5a7e82638ebb33afa74284acf022d3b1e9ae10e3ffb7658fbc49163fcd5e76e7d1baaa7801c3e05a81da755
- languageName: node
- linkType: hard
-
"locate-path@npm:^5.0.0":
version: 5.0.0
resolution: "locate-path@npm:5.0.0"
@@ -6044,6 +6142,15 @@ __metadata:
languageName: node
linkType: hard
+"locate-path@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "locate-path@npm:6.0.0"
+ dependencies:
+ p-locate: ^5.0.0
+ checksum: 72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a
+ languageName: node
+ linkType: hard
+
"lodash.merge@npm:^4.6.2":
version: 4.6.2
resolution: "lodash.merge@npm:4.6.2"
@@ -6093,9 +6200,9 @@ __metadata:
linkType: hard
"lru-cache@npm:^7.7.1":
- version: 7.13.1
- resolution: "lru-cache@npm:7.13.1"
- checksum: f53c7dd098a7afd6342b23f7182629edff206c7665de79445a7f5455440e768a4d1c6ec52e1a16175580c71535c9437dfb6f6bc22ca1a0e4a7454a97cde87329
+ version: 7.14.1
+ resolution: "lru-cache@npm:7.14.1"
+ checksum: d72c6713c6a6d86836a7a6523b3f1ac6764768cca47ec99341c3e76db06aacd4764620e5e2cda719a36848785a52a70e531822dc2b33fb071fa709683746c104
languageName: node
linkType: hard
@@ -6109,8 +6216,8 @@ __metadata:
linkType: hard
"make-fetch-happen@npm:^10.0.3":
- version: 10.2.0
- resolution: "make-fetch-happen@npm:10.2.0"
+ version: 10.2.1
+ resolution: "make-fetch-happen@npm:10.2.1"
dependencies:
agentkeepalive: ^4.2.1
cacache: ^16.1.0
@@ -6128,7 +6235,7 @@ __metadata:
promise-retry: ^2.0.1
socks-proxy-agent: ^7.0.0
ssri: ^9.0.0
- checksum: 2f6c294179972f56fab40fd8618f07841e06550692bb78f6da16e7afaa9dca78c345b08cf44a77a8907ef3948e4dc77e93eb7492b8381f1217d7ac057a7522f8
+ checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c
languageName: node
linkType: hard
@@ -6202,7 +6309,14 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
+"mimic-response@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "mimic-response@npm:4.0.0"
+ checksum: 33b804cc961efe206efdb1fca6a22540decdcfce6c14eb5c0c50e5ae9022267ab22ce8f5568b1f7247ba67500fe20d523d81e0e9f009b321ccd9d472e78d1850
+ languageName: node
+ linkType: hard
+
+"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
dependencies:
@@ -6212,18 +6326,18 @@ __metadata:
linkType: hard
"minimatch@npm:^5.0.1":
- version: 5.1.0
- resolution: "minimatch@npm:5.1.0"
+ version: 5.1.1
+ resolution: "minimatch@npm:5.1.1"
dependencies:
brace-expansion: ^2.0.1
- checksum: 15ce53d31a06361e8b7a629501b5c75491bc2b59712d53e802b1987121d91b433d73fcc5be92974fde66b2b51d8fb28d75a9ae900d249feb792bb1ba2a4f0a90
+ checksum: 215edd0978320a3354188f84a537d45841f2449af4df4379f79b9b777e71aa4f5722cc9d1717eabd2a70d38ef76ab7b708d24d83ea6a6c909dfd8833de98b437
languageName: node
linkType: hard
"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6, minimist@npm:~1.2.5":
- version: 1.2.6
- resolution: "minimist@npm:1.2.6"
- checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb
+ version: 1.2.7
+ resolution: "minimist@npm:1.2.7"
+ checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec
languageName: node
linkType: hard
@@ -6237,8 +6351,8 @@ __metadata:
linkType: hard
"minipass-fetch@npm:^2.0.3":
- version: 2.1.0
- resolution: "minipass-fetch@npm:2.1.0"
+ version: 2.1.2
+ resolution: "minipass-fetch@npm:2.1.2"
dependencies:
encoding: ^0.1.13
minipass: ^3.1.6
@@ -6247,7 +6361,7 @@ __metadata:
dependenciesMeta:
encoding:
optional: true
- checksum: 1334732859a3f7959ed22589bafd9c40384b885aebb5932328071c33f86b3eb181d54c86919675d1825ab5f1c8e4f328878c863873258d113c29d79a4b0c9c9f
+ checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91
languageName: node
linkType: hard
@@ -6279,11 +6393,20 @@ __metadata:
linkType: hard
"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6":
- version: 3.3.4
- resolution: "minipass@npm:3.3.4"
+ version: 3.3.6
+ resolution: "minipass@npm:3.3.6"
dependencies:
yallist: ^4.0.0
- checksum: 5d95a7738c54852ba78d484141e850c792e062666a2d0c681a5ac1021275beb7e1acb077e59f9523ff1defb80901aea4e30fac10ded9a20a25d819a42916ef1b
+ checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48
+ languageName: node
+ linkType: hard
+
+"minipass@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "minipass@npm:4.0.0"
+ dependencies:
+ yallist: ^4.0.0
+ checksum: 7a609afbf394abfcf9c48e6c90226f471676c8f2a67f07f6838871afb03215ede431d1433feffe1b855455bcb13ef0eb89162841b9796109d6fed8d89790f381
languageName: node
linkType: hard
@@ -6352,11 +6475,11 @@ __metadata:
linkType: hard
"nan@npm:^2.15.0, nan@npm:^2.16.0":
- version: 2.16.0
- resolution: "nan@npm:2.16.0"
+ version: 2.17.0
+ resolution: "nan@npm:2.17.0"
dependencies:
node-gyp: latest
- checksum: cb16937273ea55b01ea47df244094c12297ce6b29b36e845d349f1f7c268b8d7c5abd126a102c5678a1e1afd0d36bba35ea0cc959e364928ce60561c9306064a
+ checksum: ec609aeaf7e68b76592a3ba96b372aa7f5df5b056c1e37410b0f1deefbab5a57a922061e2c5b369bae9c7c6b5e6eecf4ad2dac8833a1a7d3a751e0a7c7f849ed
languageName: node
linkType: hard
@@ -6376,6 +6499,13 @@ __metadata:
languageName: node
linkType: hard
+"natural-compare-lite@npm:^1.4.0":
+ version: 1.4.0
+ resolution: "natural-compare-lite@npm:1.4.0"
+ checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225
+ languageName: node
+ linkType: hard
+
"natural-compare@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare@npm:1.4.0"
@@ -6478,11 +6608,11 @@ __metadata:
linkType: hard
"node-abi@npm:^3.3.0":
- version: 3.24.0
- resolution: "node-abi@npm:3.24.0"
+ version: 3.30.0
+ resolution: "node-abi@npm:3.30.0"
dependencies:
semver: ^7.3.5
- checksum: d90ab48802497b2203800cac71018668e99c246435395ca4f67afcabf689e7e81568ed36e8036bae79a052b63ea5707375bece6ca0a1d2e2b99bfafde7a5c9b2
+ checksum: f285efcea312e52d8763cfad7d434b31c11586e5efdf9f239c214a582557777453a8358d338442f02490d6c5289b0fc0eeed3056a740a3ebe6c79334af3b1739
languageName: node
linkType: hard
@@ -6517,14 +6647,14 @@ __metadata:
linkType: hard
"node-gyp@npm:latest":
- version: 9.1.0
- resolution: "node-gyp@npm:9.1.0"
+ version: 9.3.0
+ resolution: "node-gyp@npm:9.3.0"
dependencies:
env-paths: ^2.2.0
glob: ^7.1.4
graceful-fs: ^4.2.6
make-fetch-happen: ^10.0.3
- nopt: ^5.0.0
+ nopt: ^6.0.0
npmlog: ^6.0.0
rimraf: ^3.0.2
semver: ^7.3.5
@@ -6532,7 +6662,7 @@ __metadata:
which: ^2.0.2
bin:
node-gyp: bin/node-gyp.js
- checksum: 1437fa4a879b5b9010604128e8da8609b57c66034262087539ee04a8b764b8436af2be01bab66f8fc729a3adba2dcc21b10a32b9f552696c3fa8cd657d134fc4
+ checksum: 589ddd3ed967724ef425f9624bfa47cf73022640ab3eba6d556e92cdc4ddef33b63fce3a467c93b995a3f61df92eafd3c3d1e8dbe4a2c00c383334487dea99c3
languageName: node
linkType: hard
@@ -6557,18 +6687,18 @@ __metadata:
languageName: node
linkType: hard
-"nopt@npm:^5.0.0":
- version: 5.0.0
- resolution: "nopt@npm:5.0.0"
+"nopt@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "nopt@npm:6.0.0"
dependencies:
- abbrev: 1
+ abbrev: ^1.0.0
bin:
nopt: bin/nopt.js
- checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f
+ checksum: 82149371f8be0c4b9ec2f863cc6509a7fd0fa729929c009f3a58e4eb0c9e4cae9920e8f1f8eb46e7d032fec8fb01bede7f0f41a67eb3553b7b8e14fa53de1dac
languageName: node
linkType: hard
-"normalize-path@npm:^3.0.0":
+"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0":
version: 3.0.0
resolution: "normalize-path@npm:3.0.0"
checksum: 88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20
@@ -6582,6 +6712,13 @@ __metadata:
languageName: node
linkType: hard
+"normalize-url@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "normalize-url@npm:8.0.0"
+ checksum: 24c20b75ebfd526d8453084692720b49d111c63c0911f1b7447427829597841eef5a8ba3f6bb93d6654007b991c1f5cd85da2c907800e439e2e2ec6c2abd0fc0
+ languageName: node
+ linkType: hard
+
"npm-run-path@npm:^4.0.1":
version: 4.0.1
resolution: "npm-run-path@npm:4.0.1"
@@ -6621,7 +6758,7 @@ __metadata:
languageName: node
linkType: hard
-"object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0":
+"object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0":
version: 1.12.2
resolution: "object-inspect@npm:1.12.2"
checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2
@@ -6642,58 +6779,58 @@ __metadata:
languageName: node
linkType: hard
-"object.assign@npm:^4.1.2":
- version: 4.1.2
- resolution: "object.assign@npm:4.1.2"
+"object.assign@npm:^4.1.2, object.assign@npm:^4.1.3, object.assign@npm:^4.1.4":
+ version: 4.1.4
+ resolution: "object.assign@npm:4.1.4"
dependencies:
- call-bind: ^1.0.0
- define-properties: ^1.1.3
- has-symbols: ^1.0.1
+ call-bind: ^1.0.2
+ define-properties: ^1.1.4
+ has-symbols: ^1.0.3
object-keys: ^1.1.1
- checksum: d621d832ed7b16ac74027adb87196804a500d80d9aca536fccb7ba48d33a7e9306a75f94c1d29cbfa324bc091bfc530bc24789568efdaee6a47fcfa298993814
+ checksum: 76cab513a5999acbfe0ff355f15a6a125e71805fcf53de4e9d4e082e1989bdb81d1e329291e1e4e0ae7719f0e4ef80e88fb2d367ae60500d79d25a6224ac8864
languageName: node
linkType: hard
-"object.entries@npm:^1.1.5":
- version: 1.1.5
- resolution: "object.entries@npm:1.1.5"
+"object.entries@npm:^1.1.5, object.entries@npm:^1.1.6":
+ version: 1.1.6
+ resolution: "object.entries@npm:1.1.6"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.1
- checksum: d658696f74fd222060d8428d2a9fda2ce736b700cb06f6bdf4a16a1892d145afb746f453502b2fa55d1dca8ead6f14ddbcf66c545df45adadea757a6c4cd86c7
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
+ checksum: 0f8c47517e6a9a980241eafe3b73de11e59511883173c2b93d67424a008e47e11b77c80e431ad1d8a806f6108b225a1cab9223e53e555776c612a24297117d28
languageName: node
linkType: hard
-"object.fromentries@npm:^2.0.5":
- version: 2.0.5
- resolution: "object.fromentries@npm:2.0.5"
+"object.fromentries@npm:^2.0.6":
+ version: 2.0.6
+ resolution: "object.fromentries@npm:2.0.6"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.1
- checksum: 61a0b565ded97b76df9e30b569729866e1824cce902f98e90bb106e84f378aea20163366f66dc75c9000e2aad2ed0caf65c6f530cb2abc4c0c0f6c982102db4b
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
+ checksum: 453c6d694180c0c30df451b60eaf27a5b9bca3fb43c37908fd2b78af895803dc631242bcf05582173afa40d8d0e9c96e16e8874b39471aa53f3ac1f98a085d85
languageName: node
linkType: hard
-"object.hasown@npm:^1.1.1":
- version: 1.1.1
- resolution: "object.hasown@npm:1.1.1"
+"object.hasown@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "object.hasown@npm:1.1.2"
dependencies:
define-properties: ^1.1.4
- es-abstract: ^1.19.5
- checksum: d8ed4907ce57f48b93e3b53c418fd6787bf226a51e8d698c91e39b78e80fe5b124cb6282f6a9d5be21cf9e2c7829ab10206dcc6112b7748860eefe641880c793
+ es-abstract: ^1.20.4
+ checksum: b936572536db0cdf38eb30afd2f1026a8b6f2cc5d2c4497c9d9bbb01eaf3e980dead4fd07580cfdd098e6383e5a9db8212d3ea0c6bdd2b5e68c60aa7e3b45566
languageName: node
linkType: hard
-"object.values@npm:^1.1.5":
- version: 1.1.5
- resolution: "object.values@npm:1.1.5"
+"object.values@npm:^1.1.5, object.values@npm:^1.1.6":
+ version: 1.1.6
+ resolution: "object.values@npm:1.1.6"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.1
- checksum: 0f17e99741ebfbd0fa55ce942f6184743d3070c61bd39221afc929c8422c4907618c8da694c6915bc04a83ab3224260c779ba37fc07bb668bdc5f33b66a902a4
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
+ checksum: f6fff9fd817c24cfd8107f50fb33061d81cd11bacc4e3dbb3852e9ff7692fde4dbce823d4333ea27cd9637ef1b6690df5fbb61f1ed314fa2959598dc3ae23d8e
languageName: node
linkType: hard
@@ -6752,15 +6889,6 @@ __metadata:
languageName: node
linkType: hard
-"p-limit@npm:^1.1.0":
- version: 1.3.0
- resolution: "p-limit@npm:1.3.0"
- dependencies:
- p-try: ^1.0.0
- checksum: 281c1c0b8c82e1ac9f81acd72a2e35d402bf572e09721ce5520164e9de07d8274451378a3470707179ad13240535558f4b277f02405ad752e08c7d5b0d54fbfd
- languageName: node
- linkType: hard
-
"p-limit@npm:^2.2.0":
version: 2.3.0
resolution: "p-limit@npm:2.3.0"
@@ -6770,7 +6898,7 @@ __metadata:
languageName: node
linkType: hard
-"p-limit@npm:^3.1.0":
+"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0":
version: 3.1.0
resolution: "p-limit@npm:3.1.0"
dependencies:
@@ -6779,15 +6907,6 @@ __metadata:
languageName: node
linkType: hard
-"p-locate@npm:^2.0.0":
- version: 2.0.0
- resolution: "p-locate@npm:2.0.0"
- dependencies:
- p-limit: ^1.1.0
- checksum: e2dceb9b49b96d5513d90f715780f6f4972f46987dc32a0e18bc6c3fc74a1a5d73ec5f81b1398af5e58b99ea1ad03fd41e9181c01fa81b4af2833958696e3081
- languageName: node
- linkType: hard
-
"p-locate@npm:^4.1.0":
version: 4.1.0
resolution: "p-locate@npm:4.1.0"
@@ -6797,6 +6916,15 @@ __metadata:
languageName: node
linkType: hard
+"p-locate@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "p-locate@npm:5.0.0"
+ dependencies:
+ p-limit: ^3.0.2
+ checksum: 1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3
+ languageName: node
+ linkType: hard
+
"p-map@npm:^4.0.0":
version: 4.0.0
resolution: "p-map@npm:4.0.0"
@@ -6806,13 +6934,6 @@ __metadata:
languageName: node
linkType: hard
-"p-try@npm:^1.0.0":
- version: 1.0.0
- resolution: "p-try@npm:1.0.0"
- checksum: 3b5303f77eb7722144154288bfd96f799f8ff3e2b2b39330efe38db5dd359e4fb27012464cd85cb0a76e9b7edd1b443568cb3192c22e7cffc34989df0bafd605
- languageName: node
- linkType: hard
-
"p-try@npm:^2.0.0":
version: 2.2.0
resolution: "p-try@npm:2.2.0"
@@ -6841,13 +6962,6 @@ __metadata:
languageName: node
linkType: hard
-"path-exists@npm:^3.0.0":
- version: 3.0.0
- resolution: "path-exists@npm:3.0.0"
- checksum: 96e92643aa34b4b28d0de1cd2eba52a1c5313a90c6542d03f62750d82480e20bfa62bc865d5cfc6165f5fcd5aeb0851043c40a39be5989646f223300021bae0a
- languageName: node
- linkType: hard
-
"path-exists@npm:^4.0.0":
version: 4.0.0
resolution: "path-exists@npm:4.0.0"
@@ -6890,13 +7004,23 @@ __metadata:
languageName: node
linkType: hard
-"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1":
+"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1":
version: 2.3.1
resolution: "picomatch@npm:2.3.1"
checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf
languageName: node
linkType: hard
+"ping@npm:^0.4.2":
+ version: 0.4.2
+ resolution: "ping@npm:0.4.2"
+ dependencies:
+ q: 1.x
+ underscore: ^1.12.0
+ checksum: 43992c76fb3294734248753f2028d9fab3b919dbfae79a5ea6df7e81fc2d6d555dd0b195d6c3dbc5c89aa9dba1cd8eb58d5ecedad103ecfee64df516e5f3665b
+ languageName: node
+ linkType: hard
+
"pirates@npm:^4.0.4":
version: 4.0.5
resolution: "pirates@npm:4.0.5"
@@ -6966,11 +7090,11 @@ __metadata:
linkType: hard
"prettier@npm:^2.7.1":
- version: 2.7.1
- resolution: "prettier@npm:2.7.1"
+ version: 2.8.1
+ resolution: "prettier@npm:2.8.1"
bin:
prettier: bin-prettier.js
- checksum: 55a4409182260866ab31284d929b3cb961e5fdb91fe0d2e099dac92eaecec890f36e524b4c19e6ceae839c99c6d7195817579cdffc8e2c80da0cb794463a748b
+ checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc
languageName: node
linkType: hard
@@ -7064,13 +7188,20 @@ __metadata:
languageName: node
linkType: hard
-"q@npm:^1.4.1":
+"q@npm:1.x, q@npm:^1.4.1":
version: 1.5.1
resolution: "q@npm:1.5.1"
checksum: 147baa93c805bc1200ed698bdf9c72e9e42c05f96d007e33a558b5fdfd63e5ea130e99313f28efc1783e90e6bdb4e48b67a36fcc026b7b09202437ae88a1fb12
languageName: node
linkType: hard
+"querystringify@npm:^2.1.1":
+ version: 2.2.0
+ resolution: "querystringify@npm:2.2.0"
+ checksum: 5641ea231bad7ef6d64d9998faca95611ed4b11c2591a8cae741e178a974f6a8e0ebde008475259abe1621cb15e692404e6b6626e927f7b849d5c09392604b15
+ languageName: node
+ linkType: hard
+
"queue-microtask@npm:^1.2.2":
version: 1.2.3
resolution: "queue-microtask@npm:1.2.3"
@@ -7125,8 +7256,8 @@ __metadata:
linkType: hard
"react-i18next@npm:^11.18.0":
- version: 11.18.4
- resolution: "react-i18next@npm:11.18.4"
+ version: 11.18.6
+ resolution: "react-i18next@npm:11.18.6"
dependencies:
"@babel/runtime": ^7.14.5
html-parse-stringify: ^3.0.1
@@ -7138,7 +7269,7 @@ __metadata:
optional: true
react-native:
optional: true
- checksum: c64546e22447410cb09020156d86a35cc672f5e34899155456068a7caedfb70dacd70e3619e671ebc831b11d0ca95c1de1c56897842fd1de401a87b86d040beb
+ checksum: 624c0a0313fac4e0d18560b83c99a8bd0a83abc02e5db8d01984e0643ac409d178668aa3a4720d01f7a0d9520d38598dcbff801d6f69a970bae67461de6cd852
languageName: node
linkType: hard
@@ -7245,14 +7376,23 @@ __metadata:
languageName: node
linkType: hard
-"regenerator-runtime@npm:^0.13.4":
- version: 0.13.9
- resolution: "regenerator-runtime@npm:0.13.9"
- checksum: 65ed455fe5afd799e2897baf691ca21c2772e1a969d19bb0c4695757c2d96249eb74ee3553ea34a91062b2a676beedf630b4c1551cc6299afb937be1426ec55e
+"readdirp@npm:~3.6.0":
+ version: 3.6.0
+ resolution: "readdirp@npm:3.6.0"
+ dependencies:
+ picomatch: ^2.2.1
+ checksum: 1ced032e6e45670b6d7352d71d21ce7edf7b9b928494dcaba6f11fba63180d9da6cd7061ebc34175ffda6ff529f481818c962952004d273178acd70f7059b320
languageName: node
linkType: hard
-"regexp.prototype.flags@npm:^1.4.1, regexp.prototype.flags@npm:^1.4.3":
+"regenerator-runtime@npm:^0.13.11":
+ version: 0.13.11
+ resolution: "regenerator-runtime@npm:0.13.11"
+ checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4
+ languageName: node
+ linkType: hard
+
+"regexp.prototype.flags@npm:^1.4.3":
version: 1.4.3
resolution: "regexp.prototype.flags@npm:1.4.3"
dependencies:
@@ -7277,6 +7417,13 @@ __metadata:
languageName: node
linkType: hard
+"requires-port@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "requires-port@npm:1.0.0"
+ checksum: eee0e303adffb69be55d1a214e415cf42b7441ae858c76dfc5353148644f6fd6e698926fc4643f510d5c126d12a705e7c8ed7e38061113bdf37547ab356797ff
+ languageName: node
+ linkType: hard
+
"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0":
version: 1.2.1
resolution: "resolve-alpn@npm:1.2.1"
@@ -7375,6 +7522,15 @@ __metadata:
languageName: node
linkType: hard
+"responselike@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "responselike@npm:3.0.0"
+ dependencies:
+ lowercase-keys: ^3.0.0
+ checksum: e0cc9be30df4f415d6d83cdede3c5c887cd4a73e7cc1708bcaab1d50a28d15acb68460ac5b02bcc55a42f3d493729c8856427dcf6e57e6e128ad05cba4cfb95e
+ languageName: node
+ linkType: hard
+
"retry@npm:^0.12.0":
version: 0.12.0
resolution: "retry@npm:0.12.0"
@@ -7433,6 +7589,17 @@ __metadata:
languageName: node
linkType: hard
+"safe-regex-test@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "safe-regex-test@npm:1.0.0"
+ dependencies:
+ call-bind: ^1.0.2
+ get-intrinsic: ^1.1.3
+ is-regex: ^1.1.4
+ checksum: bc566d8beb8b43c01b94e67de3f070fd2781685e835959bbbaaec91cc53381145ca91f69bd837ce6ec244817afa0a5e974fc4e40a2957f0aca68ac3add1ddd34
+ languageName: node
+ linkType: hard
+
"safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0":
version: 2.1.2
resolution: "safer-buffer@npm:2.1.2"
@@ -7440,6 +7607,19 @@ __metadata:
languageName: node
linkType: hard
+"sass@npm:^1.56.1":
+ version: 1.56.2
+ resolution: "sass@npm:1.56.2"
+ dependencies:
+ chokidar: ">=3.0.0 <4.0.0"
+ immutable: ^4.0.0
+ source-map-js: ">=0.6.2 <2.0.0"
+ bin:
+ sass: sass.js
+ checksum: 7b1f524d04bc42df3bac6dc5201ff7475635b7df9a1390430ed5bd58b6a73ea1ae58b83ccea8da293cb77b85b4c848faf5f2779ca4b91b9303948c251d0ddca4
+ languageName: node
+ linkType: hard
+
"scheduler@npm:^0.23.0":
version: 0.23.0
resolution: "scheduler@npm:0.23.0"
@@ -7459,13 +7639,13 @@ __metadata:
linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.3.7":
- version: 7.3.7
- resolution: "semver@npm:7.3.7"
+ version: 7.3.8
+ resolution: "semver@npm:7.3.8"
dependencies:
lru-cache: ^6.0.0
bin:
semver: bin/semver.js
- checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232
+ checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1
languageName: node
linkType: hard
@@ -7598,16 +7778,16 @@ __metadata:
linkType: hard
"socks@npm:^2.6.2":
- version: 2.7.0
- resolution: "socks@npm:2.7.0"
+ version: 2.7.1
+ resolution: "socks@npm:2.7.1"
dependencies:
ip: ^2.0.0
smart-buffer: ^4.2.0
- checksum: 0b5d94e2b3c11e7937b40fc5dac1e80d8b92a330e68c51f1d271ce6980c70adca42a3f8cd47c4a5769956bada074823b53374f2dc5f2ea5c2121b222dec6eadf
+ checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748
languageName: node
linkType: hard
-"source-map-js@npm:^1.0.1":
+"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1":
version: 1.0.2
resolution: "source-map-js@npm:1.0.2"
checksum: c049a7fc4deb9a7e9b481ae3d424cc793cb4845daa690bc5a05d428bf41bf231ced49b4cf0c9e77f9d42fdb3d20d6187619fc586605f5eabe995a316da8d377c
@@ -7652,7 +7832,7 @@ __metadata:
languageName: node
linkType: hard
-"ssh2@npm:^1.4.0":
+"ssh2@npm:^1.11.0":
version: 1.11.0
resolution: "ssh2@npm:1.11.0"
dependencies:
@@ -7679,11 +7859,11 @@ __metadata:
linkType: hard
"stack-utils@npm:^2.0.3":
- version: 2.0.5
- resolution: "stack-utils@npm:2.0.5"
+ version: 2.0.6
+ resolution: "stack-utils@npm:2.0.6"
dependencies:
escape-string-regexp: ^2.0.0
- checksum: 76b69da0f5b48a34a0f93c98ee2a96544d2c4ca2557f7eef5ddb961d3bdc33870b46f498a84a7c4f4ffb781df639840e7ebf6639164ed4da5e1aeb659615b9c7
+ checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7
languageName: node
linkType: hard
@@ -7708,41 +7888,41 @@ __metadata:
languageName: node
linkType: hard
-"string.prototype.matchall@npm:^4.0.7":
- version: 4.0.7
- resolution: "string.prototype.matchall@npm:4.0.7"
+"string.prototype.matchall@npm:^4.0.8":
+ version: 4.0.8
+ resolution: "string.prototype.matchall@npm:4.0.8"
dependencies:
call-bind: ^1.0.2
- define-properties: ^1.1.3
- es-abstract: ^1.19.1
- get-intrinsic: ^1.1.1
+ define-properties: ^1.1.4
+ es-abstract: ^1.20.4
+ get-intrinsic: ^1.1.3
has-symbols: ^1.0.3
internal-slot: ^1.0.3
- regexp.prototype.flags: ^1.4.1
+ regexp.prototype.flags: ^1.4.3
side-channel: ^1.0.4
- checksum: fc09f3ccbfb325de0472bcc87a6be0598a7499e0b4a31db5789676155b15754a4cc4bb83924f15fc9ed48934dac7366ee52c8b9bd160bed6fd072c93b489e75c
+ checksum: 952da3a818de42ad1c10b576140a5e05b4de7b34b8d9dbf00c3ac8c1293e9c0f533613a39c5cda53e0a8221f2e710bc2150e730b1c2278d60004a8a35726efb6
languageName: node
linkType: hard
-"string.prototype.trimend@npm:^1.0.5":
- version: 1.0.5
- resolution: "string.prototype.trimend@npm:1.0.5"
+"string.prototype.trimend@npm:^1.0.6":
+ version: 1.0.6
+ resolution: "string.prototype.trimend@npm:1.0.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
- es-abstract: ^1.19.5
- checksum: d44f543833112f57224e79182debadc9f4f3bf9d48a0414d6f0cbd2a86f2b3e8c0ca1f95c3f8e5b32ae83e91554d79d932fc746b411895f03f93d89ed3dfb6bc
+ es-abstract: ^1.20.4
+ checksum: 0fdc34645a639bd35179b5a08227a353b88dc089adf438f46be8a7c197fc3f22f8514c1c9be4629b3cd29c281582730a8cbbad6466c60f76b5f99cf2addb132e
languageName: node
linkType: hard
-"string.prototype.trimstart@npm:^1.0.5":
- version: 1.0.5
- resolution: "string.prototype.trimstart@npm:1.0.5"
+"string.prototype.trimstart@npm:^1.0.6":
+ version: 1.0.6
+ resolution: "string.prototype.trimstart@npm:1.0.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
- es-abstract: ^1.19.5
- checksum: a4857c5399ad709d159a77371eeaa8f9cc284469a0b5e1bfe405de16f1fd4166a8ea6f4180e55032f348d1b679b1599fd4301fbc7a8b72bdb3e795e43f7b1048
+ es-abstract: ^1.20.4
+ checksum: 89080feef416621e6ef1279588994305477a7a91648d9436490d56010a1f7adc39167cddac7ce0b9884b8cdbef086987c4dcb2960209f2af8bac0d23ceff4f41
languageName: node
linkType: hard
@@ -7892,12 +8072,12 @@ __metadata:
linkType: hard
"supports-hyperlinks@npm:^2.0.0":
- version: 2.2.0
- resolution: "supports-hyperlinks@npm:2.2.0"
+ version: 2.3.0
+ resolution: "supports-hyperlinks@npm:2.3.0"
dependencies:
has-flag: ^4.0.0
supports-color: ^7.0.0
- checksum: aef04fb41f4a67f1bc128f7c3e88a81b6cf2794c800fccf137006efe5bafde281da3e42e72bf9206c2fcf42e6438f37e3a820a389214d0a88613ca1f2d36076a
+ checksum: 9ee0de3c8ce919d453511b2b1588a8205bd429d98af94a01df87411391010fe22ca463f268c84b2ce2abad019dfff8452aa02806eeb5c905a8d7ad5c4f4c52b8
languageName: node
linkType: hard
@@ -7909,11 +8089,11 @@ __metadata:
linkType: hard
"systeminformation@npm:^5.12.1":
- version: 5.12.1
- resolution: "systeminformation@npm:5.12.1"
+ version: 5.16.5
+ resolution: "systeminformation@npm:5.16.5"
bin:
systeminformation: lib/cli.js
- checksum: 2ccffa0c11e623b0233465204685a7ed59b22bd47a6dd5ed20b0c32e83a27074fcd7b4b00f17b9a1b88477c799edec31d8b4a64dd0c78649185ca252e8df234f
+ checksum: 33cf64eca686358db8d38b17939f33656197678a37f401b916d7509c1d84e9f5389c29570bf94dc1a97e274617e6ff90dc36515a98541aca6f72703200bb718a
conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android)
languageName: node
linkType: hard
@@ -7956,16 +8136,16 @@ __metadata:
linkType: hard
"tar@npm:^6.1.11, tar@npm:^6.1.2":
- version: 6.1.11
- resolution: "tar@npm:6.1.11"
+ version: 6.1.13
+ resolution: "tar@npm:6.1.13"
dependencies:
chownr: ^2.0.0
fs-minipass: ^2.0.0
- minipass: ^3.0.0
+ minipass: ^4.0.0
minizlib: ^2.1.1
mkdirp: ^1.0.3
yallist: ^4.0.0
- checksum: a04c07bb9e2d8f46776517d4618f2406fb977a74d914ad98b264fc3db0fe8224da5bec11e5f8902c5b9bcb8ace22d95fbe3c7b36b8593b7dfc8391a25898f32f
+ checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c
languageName: node
linkType: hard
@@ -8044,14 +8224,15 @@ __metadata:
languageName: node
linkType: hard
-"tough-cookie@npm:^4.0.0":
- version: 4.0.0
- resolution: "tough-cookie@npm:4.0.0"
+"tough-cookie@npm:^4.0.0, tough-cookie@npm:^4.1.2":
+ version: 4.1.2
+ resolution: "tough-cookie@npm:4.1.2"
dependencies:
psl: ^1.1.33
punycode: ^2.1.1
- universalify: ^0.1.2
- checksum: 0891b37eb7d17faa3479d47f0dce2e3007f2583094ad272f2670d120fbcc3df3b0b0a631ba96ecad49f9e2297d93ff8995ce0d3292d08dd7eabe162f5b224d69
+ universalify: ^0.2.0
+ url-parse: ^1.5.3
+ checksum: a7359e9a3e875121a84d6ba40cc184dec5784af84f67f3a56d1d2ae39b87c0e004e6ba7c7331f9622a7d2c88609032473488b28fe9f59a1fec115674589de39a
languageName: node
linkType: hard
@@ -8074,7 +8255,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^1.0.0, tslib@npm:^1.8.1":
+"tslib@npm:^1.8.1":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd
@@ -8082,9 +8263,9 @@ __metadata:
linkType: hard
"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0":
- version: 2.4.0
- resolution: "tslib@npm:2.4.0"
- checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113
+ version: 2.4.1
+ resolution: "tslib@npm:2.4.1"
+ checksum: 19480d6e0313292bd6505d4efe096a6b31c70e21cf08b5febf4da62e95c265c8f571f7b36fcc3d1a17e068032f59c269fab3459d6cd3ed6949eafecf64315fca
languageName: node
linkType: hard
@@ -8146,22 +8327,22 @@ __metadata:
linkType: hard
"typescript@npm:^4.7.4":
- version: 4.7.4
- resolution: "typescript@npm:4.7.4"
+ version: 4.9.4
+ resolution: "typescript@npm:4.9.4"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 5750181b1cd7e6482c4195825547e70f944114fb47e58e4aa7553e62f11b3f3173766aef9c281783edfd881f7b8299cf35e3ca8caebe73d8464528c907a164df
+ checksum: e782fb9e0031cb258a80000f6c13530288c6d63f1177ed43f770533fdc15740d271554cdae86701c1dd2c83b082cea808b07e97fd68b38a172a83dbf9e0d0ef9
languageName: node
linkType: hard
"typescript@patch:typescript@^4.7.4#~builtin":
- version: 4.7.4
- resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin::version=4.7.4&hash=7ad353"
+ version: 4.9.4
+ resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=7ad353"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 9096d8f6c16cb80ef3bf96fcbbd055bf1c4a43bd14f3b7be45a9fbe7ada46ec977f604d5feed3263b4f2aa7d4c7477ce5f9cd87de0d6feedec69a983f3a4f93e
+ checksum: 37f6e2c3c5e2aa5934b85b0fddbf32eeac8b1bacf3a5b51d01946936d03f5377fe86255d4e5a4ae628fd0cd553386355ad362c57f13b4635064400f3e8e05b9d
languageName: node
linkType: hard
@@ -8177,34 +8358,41 @@ __metadata:
languageName: node
linkType: hard
-"unique-filename@npm:^1.1.1":
- version: 1.1.1
- resolution: "unique-filename@npm:1.1.1"
- dependencies:
- unique-slug: ^2.0.0
- checksum: cf4998c9228cc7647ba7814e255dec51be43673903897b1786eff2ac2d670f54d4d733357eb08dea969aa5e6875d0e1bd391d668fbdb5a179744e7c7551a6f80
+"underscore@npm:^1.12.0":
+ version: 1.13.6
+ resolution: "underscore@npm:1.13.6"
+ checksum: d5cedd14a9d0d91dd38c1ce6169e4455bb931f0aaf354108e47bd46d3f2da7464d49b2171a5cf786d61963204a42d01ea1332a903b7342ad428deaafaf70ec36
languageName: node
linkType: hard
-"unique-slug@npm:^2.0.0":
- version: 2.0.2
- resolution: "unique-slug@npm:2.0.2"
+"unique-filename@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "unique-filename@npm:2.0.1"
+ dependencies:
+ unique-slug: ^3.0.0
+ checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f
+ languageName: node
+ linkType: hard
+
+"unique-slug@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "unique-slug@npm:3.0.0"
dependencies:
imurmurhash: ^0.1.4
- checksum: 5b6876a645da08d505dedb970d1571f6cebdf87044cb6b740c8dbb24f0d6e1dc8bdbf46825fd09f994d7cf50760e6f6e063cfa197d51c5902c00a861702eb75a
+ checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c
languageName: node
linkType: hard
-"universalify@npm:^0.1.2":
- version: 0.1.2
- resolution: "universalify@npm:0.1.2"
- checksum: 40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff
+"universalify@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "universalify@npm:0.2.0"
+ checksum: e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5
languageName: node
linkType: hard
-"update-browserslist-db@npm:^1.0.4":
- version: 1.0.5
- resolution: "update-browserslist-db@npm:1.0.5"
+"update-browserslist-db@npm:^1.0.9":
+ version: 1.0.10
+ resolution: "update-browserslist-db@npm:1.0.10"
dependencies:
escalade: ^3.1.1
picocolors: ^1.0.0
@@ -8212,7 +8400,7 @@ __metadata:
browserslist: ">= 4.21.0"
bin:
browserslist-lint: cli.js
- checksum: 7e425fe5dbbebdccf72a84ce70ec47fc74dce561d28f47bc2b84a1c2b84179a862c2261b18ab66a5e73e261c7e2ef9e11c6129112989d4d52e8f75a56bb923f8
+ checksum: 12db73b4f63029ac407b153732e7cd69a1ea8206c9100b482b7d12859cd3cd0bc59c602d7ae31e652706189f1acb90d42c53ab24a5ba563ed13aebdddc5561a0
languageName: node
linkType: hard
@@ -8225,6 +8413,16 @@ __metadata:
languageName: node
linkType: hard
+"url-parse@npm:^1.5.3":
+ version: 1.5.10
+ resolution: "url-parse@npm:1.5.10"
+ dependencies:
+ querystringify: ^2.1.1
+ requires-port: ^1.0.0
+ checksum: fbdba6b1d83336aca2216bbdc38ba658d9cfb8fc7f665eb8b17852de638ff7d1a162c198a8e4ed66001ddbf6c9888d41e4798912c62b4fd777a31657989f7bdf
+ languageName: node
+ linkType: hard
+
"use-composed-ref@npm:^1.3.0":
version: 1.3.0
resolution: "use-composed-ref@npm:1.3.0"
@@ -8269,7 +8467,7 @@ __metadata:
languageName: node
linkType: hard
-"use-sync-external-store@npm:^1.2.0":
+"use-sync-external-store@npm:1.2.0, use-sync-external-store@npm:^1.2.0":
version: 1.2.0
resolution: "use-sync-external-store@npm:1.2.0"
peerDependencies:
@@ -8294,13 +8492,6 @@ __metadata:
languageName: node
linkType: hard
-"v8-compile-cache@npm:^2.0.3":
- version: 2.3.0
- resolution: "v8-compile-cache@npm:2.3.0"
- checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e
- languageName: node
- linkType: hard
-
"v8-to-istanbul@npm:^9.0.1":
version: 9.0.1
resolution: "v8-to-istanbul@npm:9.0.1"
@@ -8328,10 +8519,10 @@ __metadata:
languageName: node
linkType: hard
-"web-streams-polyfill@npm:4.0.0-beta.1":
- version: 4.0.0-beta.1
- resolution: "web-streams-polyfill@npm:4.0.0-beta.1"
- checksum: 94c21d3aba1c26e5942bb210ffd60c6990cbc750d34bdf548ed93ed845f0a6eac89cdae1dd0195afaba15fbcf4aaca9e397ee40fa4d1f2c191d04d43717bd065
+"web-streams-polyfill@npm:4.0.0-beta.3":
+ version: 4.0.0-beta.3
+ resolution: "web-streams-polyfill@npm:4.0.0-beta.3"
+ checksum: dfec1fbf52b9140e4183a941e380487b6c3d5d3838dd1259be81506c1c9f2abfcf5aeb670aeeecfd9dff4271a6d8fef931b193c7bedfb42542a3b05ff36c0d16
languageName: node
linkType: hard
@@ -8430,12 +8621,12 @@ __metadata:
linkType: hard
"write-file-atomic@npm:^4.0.1":
- version: 4.0.1
- resolution: "write-file-atomic@npm:4.0.1"
+ version: 4.0.2
+ resolution: "write-file-atomic@npm:4.0.2"
dependencies:
imurmurhash: ^0.1.4
signal-exit: ^3.0.7
- checksum: 8f780232533ca6223c63c9b9c01c4386ca8c625ebe5017a9ed17d037aec19462ae17109e0aa155bff5966ee4ae7a27b67a99f55caf3f32ffd84155e9da3929fc
+ checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c
languageName: node
linkType: hard
@@ -8491,25 +8682,25 @@ __metadata:
languageName: node
linkType: hard
-"yargs-parser@npm:^21.0.0":
- version: 21.0.1
- resolution: "yargs-parser@npm:21.0.1"
- checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a
+"yargs-parser@npm:^21.1.1":
+ version: 21.1.1
+ resolution: "yargs-parser@npm:21.1.1"
+ checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c
languageName: node
linkType: hard
"yargs@npm:^17.3.1":
- version: 17.5.1
- resolution: "yargs@npm:17.5.1"
+ version: 17.6.2
+ resolution: "yargs@npm:17.6.2"
dependencies:
- cliui: ^7.0.2
+ cliui: ^8.0.1
escalade: ^3.1.1
get-caller-file: ^2.0.5
require-directory: ^2.1.1
string-width: ^4.2.3
y18n: ^5.0.5
- yargs-parser: ^21.0.0
- checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde
+ yargs-parser: ^21.1.1
+ checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643
languageName: node
linkType: hard
@@ -8529,3 +8720,20 @@ __metadata:
checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700
languageName: node
linkType: hard
+
+"zustand@npm:^4.1.4":
+ version: 4.1.5
+ resolution: "zustand@npm:4.1.5"
+ dependencies:
+ use-sync-external-store: 1.2.0
+ peerDependencies:
+ immer: ">=9.0"
+ react: ">=16.8"
+ peerDependenciesMeta:
+ immer:
+ optional: true
+ react:
+ optional: true
+ checksum: 13190ee8e8a797c5347b525a7c392be62b2addacdd9645dd20d37ea053f96c7c7067c099c6201e98ebb8d54991f2e04e241cc323f9a25b841d44f0ae048e3afc
+ languageName: node
+ linkType: hard