Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51bbc89f52 | ||
|
|
cbd0cf3dee | ||
|
|
c3461677e8 | ||
|
|
5168cba8e4 | ||
|
|
a4aa2aea90 | ||
|
|
10feaa37ce | ||
|
|
84f1e78701 | ||
|
|
97bc092aea | ||
|
|
d9f0983f0d | ||
|
|
6847b31060 | ||
|
|
52d18dc132 | ||
|
|
5489590ebd | ||
|
|
cd8c726c52 | ||
|
|
e179b28bc0 | ||
|
|
3218cd2333 | ||
|
|
8d5d258905 | ||
|
|
d2ac99765b | ||
|
|
00d979931b | ||
|
|
6fb32a6061 | ||
|
|
1451c3c41e | ||
|
|
498acdcdc1 | ||
|
|
afdf9d9c0f | ||
|
|
f2ff90b6e2 | ||
|
|
fad7207efe | ||
|
|
683e817493 | ||
|
|
de98156d84 | ||
|
|
7685438790 | ||
|
|
1a8e40e1f9 | ||
|
|
1c32bb73bb | ||
|
|
acd09c8c02 | ||
|
|
d7b2298d14 | ||
|
|
20a2ea8968 | ||
|
|
7e700b2732 | ||
|
|
adad43f9fe | ||
|
|
950636e81d | ||
|
|
a88f1c3cba | ||
|
|
09fdb167a7 | ||
|
|
72cdba79cb | ||
|
|
82fcdae7e5 | ||
|
|
7c4ad40198 | ||
|
|
2457f9e4a7 | ||
|
|
dc43a015c4 |
No files matched your search
@@ -34,6 +34,10 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
# The following is an example on how to use the node-postgres driver:
|
||||
# DB_DRIVER='node-postgres'
|
||||
# DB_URL='postgres://user:password@host:port/database'
|
||||
|
||||
# The below path can be used to store trusted certificates, it is not required and can be left empty.
|
||||
# If it is empty, it will default to `/appdata/trusted-certificates` in production.
|
||||
# If it is used, please use the full path to the directory where the certificates are stored.
|
||||
|
||||
@@ -31,6 +31,10 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.34.0
|
||||
- 1.33.0
|
||||
- 1.32.0
|
||||
- 1.31.0
|
||||
- 1.30.1
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
validate-pull-request-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,82 @@
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
trigger-docs-release:
|
||||
name: Trigger Documentation Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
private_key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||
app_id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||
installation_retrieval_mode: repository
|
||||
installation_retrieval_payload: homarr-labs/documentation
|
||||
- name: Trigger documentation release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
SOURCE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/homarr-labs/documentation/dispatches \
|
||||
-d @- <<EOF
|
||||
{
|
||||
"event_type": "trigger-release",
|
||||
"client_payload": {
|
||||
"tag": "${SOURCE_TAG}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
update-bug-report-template:
|
||||
name: Update Bug Report Template
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: pnpm run scripts:update-bug-report-template
|
||||
env:
|
||||
NEXT_VERSION: ${{ github.event.release.tag_name }}
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git config --global user.name "Releases Homarr"
|
||||
git add .
|
||||
git commit -m "chore: update bug report template"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-bug-report-template
|
||||
base: dev
|
||||
title: "chore: update bug report template"
|
||||
delete-branch: true
|
||||
body: |
|
||||
This PR automatically updates the bug report template of Homarr in the .github/ISSUE_TEMPLATE/bug_report.yml
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Enable auto-merge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
|
||||
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="db:migration:postgresql:generate" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="db:migration:postgresql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,3 +1,40 @@
|
||||
## [1.35.0](https://github.com/homarr-labs/homarr/compare/v1.34.0...v1.35.0) (2025-08-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **app-widget:** show description in widget ([#3876](https://github.com/homarr-labs/homarr/issues/3876)) ([c346167](https://github.com/homarr-labs/homarr/commit/c3461677e82f52bd43bd3c4ecc867b4a6ae2bee8))
|
||||
* **db:** support postgresql database ([#3643](https://github.com/homarr-labs/homarr/issues/3643)) ([5168cba](https://github.com/homarr-labs/homarr/commit/5168cba8e4ccfb8a1c348c80706b2e7a987bb152))
|
||||
* **env:** improve support for instances without internet connection ([#3915](https://github.com/homarr-labs/homarr/issues/3915)) ([10feaa3](https://github.com/homarr-labs/homarr/commit/10feaa37ce1a3ecd28246db9b2f1b0414d0a75e6))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** document generation not working with nextjs ([#3920](https://github.com/homarr-labs/homarr/issues/3920)) ([97bc092](https://github.com/homarr-labs/homarr/commit/97bc092aea72dce19f5d1626054ecae69cf01aac))
|
||||
* **security:** deepsource issues ([#3919](https://github.com/homarr-labs/homarr/issues/3919)) ([84f1e78](https://github.com/homarr-labs/homarr/commit/84f1e787013468cab7f2f0e2bcdd1f012d4e0747))
|
||||
* system resources widget opacity support for transparent backgrounds ([#3835](https://github.com/homarr-labs/homarr/issues/3835)) ([a4aa2ae](https://github.com/homarr-labs/homarr/commit/a4aa2aea904081ff49e9cfc173c35770a019c587))
|
||||
|
||||
## [1.34.0](https://github.com/homarr-labs/homarr/compare/v1.33.0...v1.34.0) (2025-08-22)
|
||||
|
||||
### Features
|
||||
|
||||
* **app-widget:** add layout option ([#3875](https://github.com/homarr-labs/homarr/issues/3875)) ([acd09c8](https://github.com/homarr-labs/homarr/commit/acd09c8c02c13daac6e5b2432bb72640f075cec6))
|
||||
* **app-widget:** multiline description ([#3873](https://github.com/homarr-labs/homarr/issues/3873)) ([950636e](https://github.com/homarr-labs/homarr/commit/950636e81d4a934183f27694829dd3af05e5373f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency drizzle-zod to ^0.8.3 ([#3866](https://github.com/homarr-labs/homarr/issues/3866)) ([dc43a01](https://github.com/homarr-labs/homarr/commit/dc43a015c43d13ae01a0f32be654c756f41eb352))
|
||||
* **deps:** update dependency mantine-form-zod-resolver to ^1.3.0 ([#3868](https://github.com/homarr-labs/homarr/issues/3868)) ([72cdba7](https://github.com/homarr-labs/homarr/commit/72cdba79cbbaa03f35c90dd71a3703bacbe57558))
|
||||
* **deps:** update dependency trpc-to-openapi to ^3.0.1 ([#3761](https://github.com/homarr-labs/homarr/issues/3761)) ([2457f9e](https://github.com/homarr-labs/homarr/commit/2457f9e4a754221e8674a95d721ffc77573a4633))
|
||||
* **deps:** update dependency typescript-eslint to ^8.40.0 ([#3884](https://github.com/homarr-labs/homarr/issues/3884)) ([1a8e40e](https://github.com/homarr-labs/homarr/commit/1a8e40e1f9dbd4dbcf4ec0c54aef3297bc4cea7f))
|
||||
* **deps:** update dependency undici to v7.14.0 ([#3879](https://github.com/homarr-labs/homarr/issues/3879)) ([d7b2298](https://github.com/homarr-labs/homarr/commit/d7b2298d1488ef8d38558864cf359fc23bc5bb26))
|
||||
* **deps:** update dependency zod to ^4.0.17 ([#3867](https://github.com/homarr-labs/homarr/issues/3867)) ([82fcdae](https://github.com/homarr-labs/homarr/commit/82fcdae7e5cbc326f0051beb56c3a3805623e2a0))
|
||||
* **deps:** update dependency zod-validation-error to v4 ([#3670](https://github.com/homarr-labs/homarr/issues/3670)) ([09fdb16](https://github.com/homarr-labs/homarr/commit/09fdb167a76db2caf3f9aa253b516ee28baba3dd))
|
||||
* **deps:** update mantine monorepo to ^8.2.5 ([#3874](https://github.com/homarr-labs/homarr/issues/3874)) ([7e700b2](https://github.com/homarr-labs/homarr/commit/7e700b2732e2f0abff13d79191addb04097ca9bc))
|
||||
* **deps:** update nextjs monorepo to v15.4.7 ([#3886](https://github.com/homarr-labs/homarr/issues/3886)) ([683e817](https://github.com/homarr-labs/homarr/commit/683e817493eb1b391751fe36b59d9b95a760324c))
|
||||
* **deps:** update nextjs monorepo to v15.5.0 ([#3896](https://github.com/homarr-labs/homarr/issues/3896)) ([1451c3c](https://github.com/homarr-labs/homarr/commit/1451c3c41e08945eb31d025d187ff466c5104a05))
|
||||
* **deps:** update tanstack-query monorepo to ^5.85.4 ([#3889](https://github.com/homarr-labs/homarr/issues/3889)) ([7685438](https://github.com/homarr-labs/homarr/commit/76854387903b48b3af7be466972cf7b9c165acc5))
|
||||
* **deps:** update tanstack-query monorepo to ^5.85.5 ([#3890](https://github.com/homarr-labs/homarr/issues/3890)) ([fad7207](https://github.com/homarr-labs/homarr/commit/fad7207efe553111334b21739717b9813dea0b56))
|
||||
* **nextcloud:** integration ignores trusted certificates ([#3893](https://github.com/homarr-labs/homarr/issues/3893)) ([498acdc](https://github.com/homarr-labs/homarr/commit/498acdcdc190de457dc95e5b13f43e1b2c31f624))
|
||||
|
||||
## [1.33.0](https://github.com/homarr-labs/homarr/compare/v1.32.0...v1.33.0) (2025-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
+4
-4
@@ -1,18 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
This policy is relevant if you found potential vulnerabilities in an audit.
|
||||
We consider something as a vulnerability if it...
|
||||
|
||||
1. puts users or user data at risk
|
||||
2. enables third parties to gain control or access (e.g. [RATs](https://en.wikipedia.org/wiki/Remote_desktop_software#RAT), [privilege escalation](https://en.wikipedia.org/wiki/Privilege_escalation), ...)
|
||||
3. abuses the system in an unintended way (e.g. crypto mining, proxy, ...)
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| >1.0.0 | :white_check_mark: |
|
||||
| <1.0.0 | :x: |
|
||||
We only fix security issues in the [latest stable version](https://github.com/homarr-labs/homarr/releases/latest). Meaning security issues in prior versions will not be fixed and users have to upgrade to the latest version to receive them.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We use [GitHub's system for reporting vulnerabilities](https://docs.github.com/en/enterprise-cloud@latest/code-security/security-advisories/working-with-repository-security-advisories/creating-a-repository-security-advisory).
|
||||
Click [**here to report an advisory**](https://github.com/homarr-labs/homarr/security/advisories/new). Our team will get notified and will get back to you within 1-6 business days.
|
||||
|
||||
|
||||
+11
-11
@@ -50,17 +50,17 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.2.4",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/dropzone": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/modals": "^8.2.4",
|
||||
"@mantine/tiptap": "^8.2.4",
|
||||
"@mantine/colors-generator": "^8.2.5",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/dropzone": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"@mantine/modals": "^8.2.5",
|
||||
"@mantine/tiptap": "^8.2.5",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query-devtools": "^5.85.3",
|
||||
"@tanstack/react-query-next-experimental": "^5.85.3",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@tanstack/react-query-devtools": "^5.85.5",
|
||||
"@tanstack/react-query-next-experimental": "^5.85.5",
|
||||
"@trpc/client": "^11.4.4",
|
||||
"@trpc/next": "^11.4.4",
|
||||
"@trpc/react-query": "^11.4.4",
|
||||
@@ -76,7 +76,7 @@
|
||||
"glob": "^11.0.3",
|
||||
"jotai": "^2.13.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.1",
|
||||
@@ -87,7 +87,7 @@
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Fragment } from "react";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||
@@ -98,7 +99,12 @@ const AppCard = async ({ app }: AppCardProps) => {
|
||||
</Text>
|
||||
{app.description && (
|
||||
<Text size="sm" c="gray.6" lineClamp={4}>
|
||||
{app.description}
|
||||
{app.description.split("\n").map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
{app.href && (
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"dotenv": "^17.2.1",
|
||||
"fastify": "^5.5.0",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.13.0"
|
||||
"undici": "7.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -26,5 +26,29 @@ services:
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
|
||||
postgresql:
|
||||
image: postgres
|
||||
restart: always
|
||||
# set shared memory limit when using docker compose
|
||||
shm_size: 128mb
|
||||
# or set shared memory limit when deploy via swarm stack
|
||||
#volumes:
|
||||
# - type: tmpfs
|
||||
# target: /dev/shm
|
||||
# tmpfs:
|
||||
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||
environment:
|
||||
POSTGRES_PASSWORD: homarr
|
||||
POSTGRES_USER: homarr
|
||||
POSTGRES_DB: homarrdb
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- postgresql_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
# if already run PostgreSQL, change port number to use container's service
|
||||
# - 2345:5432
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
mysql_data:
|
||||
postgresql_data:
|
||||
+28
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.33.0",
|
||||
"version": "1.35.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -9,6 +9,8 @@
|
||||
"cli": "pnpm with-env tsx packages/cli/index.ts",
|
||||
"db:migration:mysql:generate": "pnpm -F db migration:mysql:generate",
|
||||
"db:migration:mysql:run": "pnpm -F db migration:mysql:run",
|
||||
"db:migration:postgresql:generate": "pnpm -F db migration:postgresql:generate",
|
||||
"db:migration:postgresql:run": "pnpm -F db migration:postgresql:run",
|
||||
"db:migration:sqlite:generate": "pnpm -F db migration:sqlite:generate",
|
||||
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
||||
"db:push": "pnpm -F db push:sqlite",
|
||||
@@ -22,13 +24,14 @@
|
||||
"lint:ws": "pnpm dlx sherif@latest",
|
||||
"package:new": "turbo gen init",
|
||||
"release": "semantic-release",
|
||||
"scripts:update-bug-report-template": "tsx ./scripts/update-bug-report-template.mts",
|
||||
"scripts:update-readme-integrations": "tsx ./scripts/update-integration-list.mts",
|
||||
"start": "concurrently \"pnpm with-env node apps/tasks/tasks.cjs\" \"pnpm with-env node apps/websocket/wssServer.cjs\" \"pnpm -F nextjs start\"",
|
||||
"test": "cross-env NODE_ENV=development CI=true vitest run --exclude e2e --coverage.enabled ",
|
||||
"test:e2e": "cross-env NODE_ENV=development CI=true vitest e2e",
|
||||
"test:ui": "cross-env NODE_ENV=development CI=true vitest --exclude e2e --ui --coverage.enabled",
|
||||
"typecheck": "turbo typecheck",
|
||||
"with-env": "dotenv -e .env --",
|
||||
"scripts:update-readme-integrations": "tsx ./scripts/update-integration-list.mts"
|
||||
"with-env": "dotenv -e .env --"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"devDependencies": {
|
||||
@@ -41,7 +44,7 @@
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@testcontainers/redis": "^11.5.1",
|
||||
"@turbo/gen": "^2.5.6",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
@@ -55,7 +58,7 @@
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"engines": {
|
||||
"node": ">=22.18.0"
|
||||
},
|
||||
@@ -72,10 +75,28 @@
|
||||
"tree-sitter-json"
|
||||
],
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.13.0"
|
||||
"@babel/helpers@<7.26.10": ">=7.26.10",
|
||||
"@babel/runtime@<7.26.10": ">=7.26.10",
|
||||
"axios@>=1.0.0 <1.8.2": ">=1.8.2",
|
||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=2.0.2",
|
||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=1.1.12",
|
||||
"esbuild@<=0.24.2": ">=0.25.0",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
||||
"hono@<4.6.5": ">=4.6.5",
|
||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||
"nanoid@>=4.0.0 <5.0.9": ">=5.0.9",
|
||||
"prismjs@<1.30.0": ">=1.30.0",
|
||||
"proxmox-api>undici": "7.14.0",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.22.4",
|
||||
"sha.js@<=2.4.11": ">=2.4.12",
|
||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.0.9",
|
||||
"tar-fs@>=2.0.0 <2.1.3": ">=2.1.3",
|
||||
"tmp@<=0.2.3": ">=0.2.4",
|
||||
"vite@>=5.0.0 <=5.4.18": ">=5.4.19"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch"
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||
"trpc-to-openapi": "patches/trpc-to-openapi.patch"
|
||||
},
|
||||
"allowUnusedPatches": true,
|
||||
"ignoredBuiltDependencies": [
|
||||
|
||||
@@ -42,18 +42,18 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@trpc/client": "^11.4.4",
|
||||
"@trpc/react-query": "^11.4.4",
|
||||
"@trpc/server": "^11.4.4",
|
||||
"@trpc/tanstack-react-query": "^11.4.4",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^3.0.0",
|
||||
"zod": "^4.0.14"
|
||||
"trpc-to-openapi": "^3.0.1",
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.9",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"undici": "7.13.0"
|
||||
"undici": "7.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createEnv } from "@homarr/core/infrastructure/env";
|
||||
import { createBooleanSchema, createEnv } from "@homarr/core/infrastructure/env";
|
||||
|
||||
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
|
||||
|
||||
@@ -23,9 +23,11 @@ export const env = createEnv({
|
||||
.regex(/^[0-9a-fA-F]{64}$/, {
|
||||
message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`,
|
||||
}),
|
||||
NO_EXTERNAL_CONNECTION: createBooleanSchema(false),
|
||||
},
|
||||
runtimeEnv: {
|
||||
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NO_EXTERNAL_CONNECTION: process.env.NO_EXTERNAL_CONNECTION,
|
||||
},
|
||||
});
|
||||
@@ -30,12 +30,12 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"undici": "7.13.0",
|
||||
"zod": "^4.0.14",
|
||||
"zod-validation-error": "^3.5.3"
|
||||
"undici": "7.14.0",
|
||||
"zod": "^4.0.17",
|
||||
"zod-validation-error": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { FetchError } from "node-fetch";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { RequestError } from "../request-error";
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import type { ResponseError } from "../response-error";
|
||||
import { matchErrorCode } from "./fetch-http-error-handler";
|
||||
import { HttpErrorHandler } from "./http-error-handler";
|
||||
|
||||
/**
|
||||
* node-fetch was a defacto standard to use fetch in nodejs.
|
||||
*
|
||||
* It is for example used within the cross-fetch package which is used in tsdav.
|
||||
*/
|
||||
export class NodeFetchHttpErrorHandler extends HttpErrorHandler {
|
||||
constructor(private type = "node-fetch") {
|
||||
super();
|
||||
}
|
||||
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
if (!(error instanceof FetchError)) return undefined;
|
||||
if (error.code === undefined) return undefined;
|
||||
|
||||
logger.debug(`Received ${this.type} request error`, {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
});
|
||||
|
||||
const requestErrorInput = matchErrorCode(error.code);
|
||||
if (!requestErrorInput) return undefined;
|
||||
|
||||
return new RequestError(requestErrorInput, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Response errors do not exist for fetch as it does not throw errors for non successful responses.
|
||||
*/
|
||||
handleResponseError(_: unknown): ResponseError | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import { logger } from "@homarr/log";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import { ResponseError } from "../response-error";
|
||||
import { FetchHttpErrorHandler } from "./fetch-http-error-handler";
|
||||
import { HttpErrorHandler } from "./http-error-handler";
|
||||
import { NodeFetchHttpErrorHandler } from "./node-fetch-http-error-handler";
|
||||
|
||||
export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
||||
handleRequestError(error: unknown): AnyRequestError | undefined {
|
||||
return new FetchHttpErrorHandler("tsdav").handleRequestError(error);
|
||||
return new NodeFetchHttpErrorHandler("tsdav").handleRequestError(error);
|
||||
}
|
||||
|
||||
handleResponseError(error: unknown): ResponseError | undefined {
|
||||
@@ -16,7 +16,7 @@ export class TsdavHttpErrorHandler extends HttpErrorHandler {
|
||||
// https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86
|
||||
if (error.message !== "Invalid credentials") return undefined;
|
||||
|
||||
logger.debug("Received Tsdav response error", {
|
||||
logger.debug("Received tsdav response error", {
|
||||
status: 401,
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"ioredis": "5.7.0",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@trpc/client": "^11.4.4",
|
||||
"@trpc/server": "^11.4.4",
|
||||
"@trpc/tanstack-react-query": "^11.4.4",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.1.1",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
@@ -9,6 +10,7 @@ export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
preventManualExecution: true,
|
||||
}).withCallback(async () => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) return;
|
||||
const analyticSetting = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
if (!analyticSetting.enableGeneral) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createId, splitToNChunks, Stopwatch } from "@homarr/common";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { db, handleTransactionsAsync, inArray, sql } from "@homarr/db";
|
||||
@@ -12,6 +13,8 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
expectedMaximumDurationInMillis: 10 * 1000,
|
||||
}).withCallback(async () => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) return;
|
||||
|
||||
logger.info("Updating icon repository cache...");
|
||||
const stopWatch = new Stopwatch();
|
||||
const repositoryIconGroups = await fetchIconsAsync();
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { InferInsertModel } from "drizzle-orm";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
|
||||
import { env } from "./env";
|
||||
import * as schema from "./schema";
|
||||
|
||||
@@ -10,6 +10,14 @@ type TableKey = {
|
||||
[K in keyof typeof schema]: (typeof schema)[K] extends { _: { brand: "Table" } } ? K : never;
|
||||
}[keyof typeof schema];
|
||||
|
||||
export function isMysql(): boolean {
|
||||
return env.DB_DRIVER === "mysql2";
|
||||
}
|
||||
|
||||
export function isPostgresql(): boolean {
|
||||
return env.DB_DRIVER === "node-postgres";
|
||||
}
|
||||
|
||||
export const createDbInsertCollectionForTransaction = <TTableKey extends TableKey>(
|
||||
tablesInInsertOrder: TTableKey[],
|
||||
) => {
|
||||
@@ -35,8 +43,10 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
|
||||
}
|
||||
});
|
||||
},
|
||||
insertAllAsync: async (db: HomarrDatabaseMysql) => {
|
||||
await db.transaction(async (transaction) => {
|
||||
// We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors
|
||||
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => {
|
||||
const innerDb = db as HomarrDatabaseMysql;
|
||||
await innerDb.transaction(async (transaction) => {
|
||||
for (const [key, values] of objectEntries(context)) {
|
||||
if (values.length >= 1) {
|
||||
// Below is actually the mysqlSchema when the driver is mysql
|
||||
@@ -56,12 +66,18 @@ export const createDbInsertCollectionWithoutTransaction = <TTableKey extends Tab
|
||||
return {
|
||||
...collection,
|
||||
insertAllAsync: async (db: HomarrDatabase) => {
|
||||
if (env.DB_DRIVER !== "mysql2") {
|
||||
insertAll(db);
|
||||
return;
|
||||
switch (env.DB_DRIVER) {
|
||||
case "mysql2":
|
||||
case "node-postgres":
|
||||
// For mysql2 and node-postgres, we can use the async insertAllAsync method
|
||||
await insertAllAsync(db as unknown as HomarrDatabaseMysql | HomarrDatabasePostgresql);
|
||||
return;
|
||||
default:
|
||||
// For better-sqlite3, we need to use the synchronous insertAll method
|
||||
// default assumes better-sqlite3. It's original implementation.
|
||||
insertAll(db);
|
||||
break;
|
||||
}
|
||||
|
||||
await insertAllAsync(db as unknown as HomarrDatabaseMysql);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Config } from "drizzle-kit";
|
||||
|
||||
import { env } from "../env";
|
||||
|
||||
export default {
|
||||
dialect: "postgresql",
|
||||
schema: "./schema",
|
||||
casing: "snake_case",
|
||||
|
||||
dbCredentials: env.DB_URL
|
||||
? { url: env.DB_URL }
|
||||
: {
|
||||
host: env.DB_HOST,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
database: env.DB_NAME,
|
||||
},
|
||||
out: "./migrations/postgresql",
|
||||
} satisfies Config;
|
||||
+38
-1
@@ -5,17 +5,22 @@ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
|
||||
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
|
||||
import type { MySql2Database } from "drizzle-orm/mysql2";
|
||||
import { drizzle as drizzleMysql } from "drizzle-orm/mysql2";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
|
||||
import type { Pool as MysqlConnectionPool } from "mysql2";
|
||||
import mysql from "mysql2";
|
||||
import { Pool as PostgresPool } from "pg";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { env } from "./env";
|
||||
import * as mysqlSchema from "./schema/mysql";
|
||||
import * as pgSchema from "./schema/postgresql";
|
||||
import * as sqliteSchema from "./schema/sqlite";
|
||||
|
||||
export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
|
||||
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
|
||||
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
|
||||
|
||||
const init = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
@@ -24,6 +29,9 @@ const init = () => {
|
||||
case "mysql2":
|
||||
initMySQL2();
|
||||
break;
|
||||
case "node-postgres":
|
||||
initNodePostgres();
|
||||
break;
|
||||
default:
|
||||
initBetterSqlite();
|
||||
break;
|
||||
@@ -31,7 +39,7 @@ const init = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export let connection: BetterSqlite3Connection | MysqlConnectionPool;
|
||||
export let connection: BetterSqlite3Connection | MysqlConnectionPool | PostgresPool;
|
||||
export let database: HomarrDatabase;
|
||||
|
||||
class WinstonDrizzleLogger implements Logger {
|
||||
@@ -73,4 +81,33 @@ const initMySQL2 = () => {
|
||||
}) as unknown as HomarrDatabase;
|
||||
};
|
||||
|
||||
const initNodePostgres = () => {
|
||||
if (!env.DB_HOST) {
|
||||
connection = new PostgresPool({
|
||||
connectionString: env.DB_URL,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
} else {
|
||||
connection = new PostgresPool({
|
||||
host: env.DB_HOST,
|
||||
database: env.DB_NAME,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
}
|
||||
|
||||
database = drizzlePg({
|
||||
logger: new WinstonDrizzleLogger(),
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: connection,
|
||||
}) as unknown as HomarrDatabase;
|
||||
};
|
||||
|
||||
init();
|
||||
+3
-2
@@ -6,6 +6,7 @@ import { createEnv } from "@homarr/core/infrastructure/env";
|
||||
const drivers = {
|
||||
betterSqlite3: "better-sqlite3",
|
||||
mysql2: "mysql2",
|
||||
nodePostgres: "node-postgres",
|
||||
} as const;
|
||||
|
||||
const isDriver = (driver: (typeof drivers)[keyof typeof drivers]) => process.env.DB_DRIVER === driver;
|
||||
@@ -21,7 +22,7 @@ export const env = createEnv({
|
||||
*/
|
||||
server: {
|
||||
DB_DRIVER: z
|
||||
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2)], {
|
||||
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], {
|
||||
message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`,
|
||||
})
|
||||
.default(drivers.betterSqlite3),
|
||||
@@ -42,7 +43,7 @@ export const env = createEnv({
|
||||
.regex(/\d+/)
|
||||
.transform(Number)
|
||||
.refine((number) => number >= 1)
|
||||
.default(3306),
|
||||
.default(isDriver(drivers.mysql2) ? 3306 : 5432),
|
||||
DB_USER: z.string(),
|
||||
DB_PASSWORD: z.string(),
|
||||
DB_NAME: z.string(),
|
||||
|
||||
@@ -7,6 +7,6 @@ export * from "drizzle-orm";
|
||||
export const db = database;
|
||||
|
||||
export type Database = typeof db;
|
||||
export type { HomarrDatabaseMysql } from "./driver";
|
||||
export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
|
||||
|
||||
export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions";
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { eq } from "../..";
|
||||
import type { Database } from "../..";
|
||||
import { items } from "../../schema";
|
||||
|
||||
/**
|
||||
* To support showing the description in the widget itself we replaced
|
||||
* the tooltip show option with display mode.
|
||||
*/
|
||||
export async function migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync(db: Database) {
|
||||
const existingAppItems = await db.query.items.findMany({
|
||||
where: (table, { eq }) => eq(table.kind, "app"),
|
||||
});
|
||||
|
||||
const itemsToUpdate = existingAppItems
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
options: SuperJSON.parse<{ showDescriptionTooltip?: boolean }>(item.options),
|
||||
}))
|
||||
.filter((item) => item.options.showDescriptionTooltip !== undefined);
|
||||
|
||||
console.log(
|
||||
`Migrating app items with showDescriptionTooltip to descriptionDisplayMode count=${itemsToUpdate.length}`,
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
itemsToUpdate.map(async (item) => {
|
||||
const { showDescriptionTooltip, ...options } = item.options;
|
||||
await db
|
||||
.update(items)
|
||||
.set({
|
||||
options: SuperJSON.stringify({
|
||||
...options,
|
||||
descriptionDisplayMode: showDescriptionTooltip ? "tooltip" : "hidden",
|
||||
}),
|
||||
})
|
||||
.where(eq(items.id, item.id));
|
||||
}),
|
||||
);
|
||||
|
||||
console.log(`Migrated app items with showDescriptionTooltip to descriptionDisplayMode count=${itemsToUpdate.length}`);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Database } from "../..";
|
||||
import { migrateReleaseWidgetProviderToOptionsAsync } from "./0000_release_widget_provider_to_options";
|
||||
import { migrateOpnsenseCredentialsAsync } from "./0001_opnsense_credentials";
|
||||
import { migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync } from "./0002_app_widget_show_description_tooltip_to_display_mode";
|
||||
|
||||
export const applyCustomMigrationsAsync = async (db: Database) => {
|
||||
await migrateReleaseWidgetProviderToOptionsAsync(db);
|
||||
await migrateOpnsenseCredentialsAsync(db);
|
||||
await migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync(db);
|
||||
};
|
||||
@@ -0,0 +1,322 @@
|
||||
CREATE TABLE "account" (
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"provider" varchar(64) NOT NULL,
|
||||
"provider_account_id" varchar(64) NOT NULL,
|
||||
"refresh_token" text,
|
||||
"access_token" text,
|
||||
"expires_at" integer,
|
||||
"token_type" text,
|
||||
"scope" text,
|
||||
"id_token" text,
|
||||
"session_state" text,
|
||||
CONSTRAINT "account_provider_provider_account_id_pk" PRIMARY KEY("provider","provider_account_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "apiKey" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"api_key" text NOT NULL,
|
||||
"salt" text NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "app" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"icon_url" text NOT NULL,
|
||||
"href" text,
|
||||
"ping_url" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "boardGroupPermission" (
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "boardGroupPermission_board_id_group_id_permission_pk" PRIMARY KEY("board_id","group_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "boardUserPermission" (
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "boardUserPermission_board_id_user_id_permission_pk" PRIMARY KEY("board_id","user_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "board" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(256) NOT NULL,
|
||||
"is_public" boolean DEFAULT false NOT NULL,
|
||||
"creator_id" varchar(64),
|
||||
"page_title" text,
|
||||
"meta_title" text,
|
||||
"logo_image_url" text,
|
||||
"favicon_image_url" text,
|
||||
"background_image_url" text,
|
||||
"background_image_attachment" text DEFAULT 'fixed' NOT NULL,
|
||||
"background_image_repeat" text DEFAULT 'no-repeat' NOT NULL,
|
||||
"background_image_size" text DEFAULT 'cover' NOT NULL,
|
||||
"primary_color" text DEFAULT '#fa5252' NOT NULL,
|
||||
"secondary_color" text DEFAULT '#fd7e14' NOT NULL,
|
||||
"opacity" integer DEFAULT 100 NOT NULL,
|
||||
"custom_css" text,
|
||||
"icon_color" text,
|
||||
"item_radius" text DEFAULT 'lg' NOT NULL,
|
||||
"disable_status" boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT "board_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cron_job_configuration" (
|
||||
"name" varchar(256) PRIMARY KEY NOT NULL,
|
||||
"cron_expression" varchar(32) NOT NULL,
|
||||
"is_enabled" boolean DEFAULT true NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "groupMember" (
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "groupMember_group_id_user_id_pk" PRIMARY KEY("group_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "groupPermission" (
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "group" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(64) NOT NULL,
|
||||
"owner_id" varchar(64),
|
||||
"home_board_id" varchar(64),
|
||||
"mobile_home_board_id" varchar(64),
|
||||
"position" smallint NOT NULL,
|
||||
CONSTRAINT "group_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "iconRepository" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"slug" varchar(150) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "icon" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(250) NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"checksum" text NOT NULL,
|
||||
"icon_repository_id" varchar(64) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationGroupPermissions" (
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "integration_group_permission__pk" PRIMARY KEY("integration_id","group_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integration_item" (
|
||||
"item_id" varchar(64) NOT NULL,
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "integration_item_item_id_integration_id_pk" PRIMARY KEY("item_id","integration_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationSecret" (
|
||||
"kind" varchar(16) NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"updated_at" timestamp NOT NULL,
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "integrationSecret_integration_id_kind_pk" PRIMARY KEY("integration_id","kind")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationUserPermission" (
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "integrationUserPermission_integration_id_user_id_permission_pk" PRIMARY KEY("integration_id","user_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integration" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"kind" varchar(128) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "invite" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"token" varchar(512) NOT NULL,
|
||||
"expiration_date" timestamp NOT NULL,
|
||||
"creator_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "invite_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "item_layout" (
|
||||
"item_id" varchar(64) NOT NULL,
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"layout_id" varchar(64) NOT NULL,
|
||||
"x_offset" integer NOT NULL,
|
||||
"y_offset" integer NOT NULL,
|
||||
"width" integer NOT NULL,
|
||||
"height" integer NOT NULL,
|
||||
CONSTRAINT "item_layout_item_id_section_id_layout_id_pk" PRIMARY KEY("item_id","section_id","layout_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "item" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"options" text DEFAULT '{"json": {}}' NOT NULL,
|
||||
"advanced_options" text DEFAULT '{"json": {}}' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "layout" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(32) NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"column_count" smallint NOT NULL,
|
||||
"breakpoint" smallint DEFAULT 0 NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "media" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(512) NOT NULL,
|
||||
"content" "bytea" NOT NULL,
|
||||
"content_type" text NOT NULL,
|
||||
"size" integer NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"creator_id" varchar(64)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "onboarding" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"step" varchar(64) NOT NULL,
|
||||
"previous_step" varchar(64)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "search_engine" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"icon_url" text NOT NULL,
|
||||
"name" varchar(64) NOT NULL,
|
||||
"short" varchar(8) NOT NULL,
|
||||
"description" text,
|
||||
"url_template" text,
|
||||
"type" varchar(64) DEFAULT 'generic' NOT NULL,
|
||||
"integration_id" varchar(64),
|
||||
CONSTRAINT "search_engine_short_unique" UNIQUE("short")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section_collapse_state" (
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"collapsed" boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT "section_collapse_state_user_id_section_id_pk" PRIMARY KEY("user_id","section_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section_layout" (
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"layout_id" varchar(64) NOT NULL,
|
||||
"parent_section_id" varchar(64),
|
||||
"x_offset" integer NOT NULL,
|
||||
"y_offset" integer NOT NULL,
|
||||
"width" integer NOT NULL,
|
||||
"height" integer NOT NULL,
|
||||
CONSTRAINT "section_layout_section_id_layout_id_pk" PRIMARY KEY("section_id","layout_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"x_offset" integer,
|
||||
"y_offset" integer,
|
||||
"name" text,
|
||||
"options" text DEFAULT '{"json": {}}'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "serverSetting" (
|
||||
"setting_key" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"value" text DEFAULT '{"json": {}}' NOT NULL,
|
||||
CONSTRAINT "serverSetting_settingKey_unique" UNIQUE("setting_key")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "session" (
|
||||
"session_token" varchar(512) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"expires" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "trusted_certificate_hostname" (
|
||||
"hostname" varchar(256) NOT NULL,
|
||||
"thumbprint" varchar(128) NOT NULL,
|
||||
"certificate" text NOT NULL,
|
||||
CONSTRAINT "trusted_certificate_hostname_hostname_thumbprint_pk" PRIMARY KEY("hostname","thumbprint")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text,
|
||||
"email" text,
|
||||
"email_verified" timestamp,
|
||||
"image" text,
|
||||
"password" text,
|
||||
"salt" text,
|
||||
"provider" varchar(64) DEFAULT 'credentials' NOT NULL,
|
||||
"home_board_id" varchar(64),
|
||||
"mobile_home_board_id" varchar(64),
|
||||
"default_search_engine_id" varchar(64),
|
||||
"open_search_in_new_tab" boolean DEFAULT false NOT NULL,
|
||||
"color_scheme" varchar(5) DEFAULT 'dark' NOT NULL,
|
||||
"first_day_of_week" smallint DEFAULT 1 NOT NULL,
|
||||
"ping_icons_enabled" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "verificationToken" (
|
||||
"identifier" varchar(64) NOT NULL,
|
||||
"token" varchar(512) NOT NULL,
|
||||
"expires" timestamp NOT NULL,
|
||||
CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "apiKey" ADD CONSTRAINT "apiKey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardGroupPermission" ADD CONSTRAINT "boardGroupPermission_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardGroupPermission" ADD CONSTRAINT "boardGroupPermission_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardUserPermission" ADD CONSTRAINT "boardUserPermission_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardUserPermission" ADD CONSTRAINT "boardUserPermission_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "board" ADD CONSTRAINT "board_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupMember" ADD CONSTRAINT "groupMember_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupMember" ADD CONSTRAINT "groupMember_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupPermission" ADD CONSTRAINT "groupPermission_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_home_board_id_board_id_fk" FOREIGN KEY ("home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_mobile_home_board_id_board_id_fk" FOREIGN KEY ("mobile_home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "icon" ADD CONSTRAINT "icon_icon_repository_id_iconRepository_id_fk" FOREIGN KEY ("icon_repository_id") REFERENCES "public"."iconRepository"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationGroupPermissions" ADD CONSTRAINT "integrationGroupPermissions_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationGroupPermissions" ADD CONSTRAINT "integrationGroupPermissions_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integration_item" ADD CONSTRAINT "integration_item_item_id_item_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."item"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integration_item" ADD CONSTRAINT "integration_item_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationSecret" ADD CONSTRAINT "integrationSecret_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationUserPermission" ADD CONSTRAINT "integrationUserPermission_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationUserPermission" ADD CONSTRAINT "integrationUserPermission_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "invite" ADD CONSTRAINT "invite_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_item_id_item_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."item"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_layout_id_layout_id_fk" FOREIGN KEY ("layout_id") REFERENCES "public"."layout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item" ADD CONSTRAINT "item_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "layout" ADD CONSTRAINT "layout_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "media" ADD CONSTRAINT "media_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "search_engine" ADD CONSTRAINT "search_engine_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_collapse_state" ADD CONSTRAINT "section_collapse_state_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_collapse_state" ADD CONSTRAINT "section_collapse_state_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_layout_id_layout_id_fk" FOREIGN KEY ("layout_id") REFERENCES "public"."layout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_parent_section_id_section_id_fk" FOREIGN KEY ("parent_section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section" ADD CONSTRAINT "section_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_home_board_id_board_id_fk" FOREIGN KEY ("home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_mobile_home_board_id_board_id_fk" FOREIGN KEY ("mobile_home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_default_search_engine_id_search_engine_id_fk" FOREIGN KEY ("default_search_engine_id") REFERENCES "public"."search_engine"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "integration_secret__kind_idx" ON "integrationSecret" USING btree ("kind");--> statement-breakpoint
|
||||
CREATE INDEX "integration_secret__updated_at_idx" ON "integrationSecret" USING btree ("updated_at");--> statement-breakpoint
|
||||
CREATE INDEX "integration__kind_idx" ON "integration" USING btree ("kind");--> statement-breakpoint
|
||||
CREATE INDEX "user_id_idx" ON "session" USING btree ("user_id");
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1754853510707,
|
||||
"tag": "0000_initial",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
|
||||
import type { Database } from "../..";
|
||||
import { env } from "../../env";
|
||||
import * as pgSchema from "../../schema/postgresql";
|
||||
import { applyCustomMigrationsAsync } from "../custom";
|
||||
import { seedDataAsync } from "../seed";
|
||||
|
||||
const migrationsFolder = process.argv[2] ?? ".";
|
||||
|
||||
const migrateAsync = async () => {
|
||||
const pool = new Pool(
|
||||
env.DB_URL
|
||||
? { connectionString: env.DB_URL }
|
||||
: {
|
||||
host: env.DB_HOST,
|
||||
database: env.DB_NAME,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
},
|
||||
);
|
||||
|
||||
const db = drizzle({
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: pool,
|
||||
});
|
||||
|
||||
await migrate(db, { migrationsFolder });
|
||||
await seedDataAsync(db as unknown as Database);
|
||||
await applyCustomMigrationsAsync(db as unknown as Database);
|
||||
};
|
||||
|
||||
migrateAsync()
|
||||
.then(() => {
|
||||
console.log("Migration complete");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Migration failed", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -15,9 +15,8 @@ import {
|
||||
insertServerSettingByKeyAsync,
|
||||
updateServerSettingByKeyAsync,
|
||||
} from "../queries/server-setting";
|
||||
import { integrations, onboarding, searchEngines } from "../schema";
|
||||
import { groups, integrations, onboarding, searchEngines } from "../schema";
|
||||
import type { Integration } from "../schema";
|
||||
import { groups } from "../schema/mysql";
|
||||
|
||||
export const seedDataAsync = async (db: Database) => {
|
||||
await seedEveryoneGroupAsync(db);
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run build:sqlite && pnpm run build:mysql",
|
||||
"build": "pnpm run build:sqlite && pnpm run build:mysql && pnpm run build:postgresql",
|
||||
"build:mysql": "esbuild migrations/mysql/migrate.ts --bundle --platform=node --outfile=migrations/mysql/migrate.cjs",
|
||||
"build:postgresql": "esbuild migrations/postgresql/migrate.ts --bundle --platform=node --outfile=migrations/postgresql/migrate.cjs",
|
||||
"build:sqlite": "esbuild migrations/sqlite/migrate.ts --bundle --platform=node --outfile=migrations/sqlite/migrate.cjs",
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
@@ -26,10 +27,14 @@
|
||||
"migration:mysql:drop": "pnpm with-env drizzle-kit drop --config ./configs/mysql.config.ts",
|
||||
"migration:mysql:generate": "pnpm with-env drizzle-kit generate --config ./configs/mysql.config.ts",
|
||||
"migration:mysql:run": "pnpm with-env drizzle-kit migrate --config ./configs/mysql.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"migration:postgresql:drop": "pnpm with-env drizzle-kit drop --config ./configs/postgresql.config.ts",
|
||||
"migration:postgresql:generate": "pnpm with-env drizzle-kit generate --config ./configs/postgresql.config.ts",
|
||||
"migration:postgresql:run": "pnpm with-env drizzle-kit migrate --config ./configs/postgresql.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"migration:sqlite:drop": "pnpm with-env drizzle-kit drop --config ./configs/sqlite.config.ts",
|
||||
"migration:sqlite:generate": "pnpm with-env drizzle-kit generate --config ./configs/sqlite.config.ts",
|
||||
"migration:sqlite:run": "pnpm with-env drizzle-kit migrate --config ./configs/sqlite.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"push:mysql": "pnpm with-env drizzle-kit push --config ./configs/mysql.config.ts",
|
||||
"push:postgresql": "pnpm with-env drizzle-kit push --config ./configs/postgresql.config.ts",
|
||||
"push:sqlite": "pnpm with-env drizzle-kit push --config ./configs/sqlite.config.ts",
|
||||
"seed": "pnpm with-env tsx ./migrations/run-seed.ts",
|
||||
"studio": "pnpm with-env drizzle-kit studio --config ./configs/sqlite.config.ts",
|
||||
@@ -44,15 +49,17 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^11.5.1",
|
||||
"@testcontainers/postgresql": "^11.4.0",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.4",
|
||||
"drizzle-zod": "^0.8.2",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.14.3",
|
||||
"pg": "^8.16.3",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -60,6 +67,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.15.4",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "^9.33.0",
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
import { env } from "../env";
|
||||
import * as mysqlSchema from "./mysql";
|
||||
import * as pgSchema from "./postgresql";
|
||||
import * as sqliteSchema from "./sqlite";
|
||||
|
||||
export type PostgreSqlSchema = typeof pgSchema;
|
||||
export type MySqlSchema = typeof mysqlSchema;
|
||||
type Schema = typeof sqliteSchema;
|
||||
|
||||
const schema = process.env.DB_DRIVER === "mysql2" ? (mysqlSchema as unknown as Schema) : sqliteSchema;
|
||||
const schema =
|
||||
env.DB_DRIVER === "mysql2"
|
||||
? (mysqlSchema as unknown as Schema)
|
||||
: env.DB_DRIVER === "node-postgres"
|
||||
? (pgSchema as unknown as Schema)
|
||||
: sqliteSchema;
|
||||
|
||||
// Sadly we can't use export * from here as we have multiple possible exports
|
||||
export const {
|
||||
|
||||
@@ -528,6 +528,7 @@ export const userRelations = relations(users, ({ one, many }) => ({
|
||||
groups: many(groupMembers),
|
||||
ownedGroups: many(groups),
|
||||
invites: many(invites),
|
||||
medias: many(medias),
|
||||
defaultSearchEngine: one(searchEngines, {
|
||||
fields: [users.defaultSearchEngineId],
|
||||
references: [searchEngines.id],
|
||||
|
||||
@@ -0,0 +1,775 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import type { MantineSize } from "@mantine/core";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { relations } from "drizzle-orm";
|
||||
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
boolean,
|
||||
customType,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
smallint,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import {
|
||||
backgroundImageAttachments,
|
||||
backgroundImageRepeats,
|
||||
backgroundImageSizes,
|
||||
emptySuperJSON,
|
||||
} from "@homarr/definitions";
|
||||
import type {
|
||||
BackgroundImageAttachment,
|
||||
BackgroundImageRepeat,
|
||||
BackgroundImageSize,
|
||||
BoardPermission,
|
||||
ColorScheme,
|
||||
GroupPermissionKey,
|
||||
IntegrationKind,
|
||||
IntegrationPermission,
|
||||
IntegrationSecretKind,
|
||||
OnboardingStep,
|
||||
SearchEngineType,
|
||||
SectionKind,
|
||||
SupportedAuthProvider,
|
||||
WidgetKind,
|
||||
} from "@homarr/definitions";
|
||||
|
||||
const customBlob = customType<{ data: Buffer }>({
|
||||
dataType() {
|
||||
return "bytea";
|
||||
},
|
||||
});
|
||||
|
||||
export const apiKeys = pgTable("apiKey", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
apiKey: text().notNull(),
|
||||
salt: text().notNull(),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references((): AnyPgColumn => users.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const users = pgTable("user", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text(),
|
||||
email: text(),
|
||||
emailVerified: timestamp(),
|
||||
image: text(),
|
||||
password: text(),
|
||||
salt: text(),
|
||||
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
||||
homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
mobileHomeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
defaultSearchEngineId: varchar({ length: 64 }).references(() => searchEngines.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
openSearchInNewTab: boolean().default(false).notNull(),
|
||||
colorScheme: varchar({ length: 5 }).$type<ColorScheme>().default("dark").notNull(),
|
||||
firstDayOfWeek: smallint().$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||
pingIconsEnabled: boolean().default(false).notNull(),
|
||||
});
|
||||
|
||||
export const accounts = pgTable(
|
||||
"account",
|
||||
{
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text().$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: varchar({ length: 64 }).notNull(),
|
||||
providerAccountId: varchar({ length: 64 }).notNull(),
|
||||
refresh_token: text(),
|
||||
access_token: text(),
|
||||
expires_at: integer(),
|
||||
token_type: text(),
|
||||
scope: text(),
|
||||
id_token: text(),
|
||||
session_state: text(),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [account.provider, account.providerAccountId],
|
||||
}),
|
||||
userIdIdx: index("userId_idx").on(account.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sessions = pgTable(
|
||||
"session",
|
||||
{
|
||||
sessionToken: varchar({ length: 512 }).notNull().primaryKey(),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp().notNull(),
|
||||
},
|
||||
(session) => ({
|
||||
userIdIdx: index("user_id_idx").on(session.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: varchar({ length: 64 }).notNull(),
|
||||
token: varchar({ length: 512 }).notNull(),
|
||||
expires: timestamp().notNull(),
|
||||
},
|
||||
(verificationToken) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [verificationToken.identifier, verificationToken.token],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const groupMembers = pgTable(
|
||||
"groupMember",
|
||||
{
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(groupMember) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [groupMember.groupId, groupMember.userId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const groups = pgTable("group", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 64 }).unique().notNull(),
|
||||
ownerId: varchar({ length: 64 }).references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
homeBoardId: varchar({ length: 64 }).references(() => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
mobileHomeBoardId: varchar({ length: 64 }).references(() => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
position: smallint().notNull(),
|
||||
});
|
||||
|
||||
export const groupPermissions = pgTable("groupPermission", {
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: text().$type<GroupPermissionKey>().notNull(),
|
||||
});
|
||||
|
||||
export const invites = pgTable("invite", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
token: varchar({ length: 512 }).notNull().unique(),
|
||||
expirationDate: timestamp().notNull(),
|
||||
creatorId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const medias = pgTable("media", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 512 }).notNull(),
|
||||
content: customBlob().notNull(),
|
||||
contentType: text().notNull(),
|
||||
size: integer().notNull(),
|
||||
createdAt: timestamp({ mode: "date" }).notNull().defaultNow(),
|
||||
creatorId: varchar({ length: 64 }).references(() => users.id, { onDelete: "set null" }),
|
||||
});
|
||||
|
||||
export const integrations = pgTable(
|
||||
"integration",
|
||||
{
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text().notNull(),
|
||||
url: text().notNull(),
|
||||
kind: varchar({ length: 128 }).$type<IntegrationKind>().notNull(),
|
||||
},
|
||||
(integrations) => ({
|
||||
kindIdx: index("integration__kind_idx").on(integrations.kind),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationSecrets = pgTable(
|
||||
"integrationSecret",
|
||||
{
|
||||
kind: varchar({ length: 16 }).$type<IntegrationSecretKind>().notNull(),
|
||||
value: text().$type<`${string}.${string}`>().notNull(),
|
||||
updatedAt: timestamp()
|
||||
.$onUpdateFn(() => new Date())
|
||||
.notNull(),
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(integrationSecret) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [integrationSecret.integrationId, integrationSecret.kind],
|
||||
}),
|
||||
kindIdx: index("integration_secret__kind_idx").on(integrationSecret.kind),
|
||||
updatedAtIdx: index("integration_secret__updated_at_idx").on(integrationSecret.updatedAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationUserPermissions = pgTable(
|
||||
"integrationUserPermission",
|
||||
{
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<IntegrationPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.integrationId, table.userId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationGroupPermissions = pgTable(
|
||||
"integrationGroupPermissions",
|
||||
{
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<IntegrationPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.integrationId, table.groupId, table.permission],
|
||||
name: "integration_group_permission__pk",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const boards = pgTable("board", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 256 }).unique().notNull(),
|
||||
isPublic: boolean().default(false).notNull(),
|
||||
creatorId: varchar({ length: 64 }).references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
pageTitle: text(),
|
||||
metaTitle: text(),
|
||||
logoImageUrl: text(),
|
||||
faviconImageUrl: text(),
|
||||
backgroundImageUrl: text(),
|
||||
backgroundImageAttachment: text()
|
||||
.$type<BackgroundImageAttachment>()
|
||||
.default(backgroundImageAttachments.defaultValue)
|
||||
.notNull(),
|
||||
backgroundImageRepeat: text().$type<BackgroundImageRepeat>().default(backgroundImageRepeats.defaultValue).notNull(),
|
||||
backgroundImageSize: text().$type<BackgroundImageSize>().default(backgroundImageSizes.defaultValue).notNull(),
|
||||
primaryColor: text().default("#fa5252").notNull(),
|
||||
secondaryColor: text().default("#fd7e14").notNull(),
|
||||
opacity: integer().default(100).notNull(),
|
||||
customCss: text(),
|
||||
iconColor: text(),
|
||||
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
||||
disableStatus: boolean().default(false).notNull(),
|
||||
});
|
||||
|
||||
export const boardUserPermissions = pgTable(
|
||||
"boardUserPermission",
|
||||
{
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<BoardPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.boardId, table.userId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const boardGroupPermissions = pgTable(
|
||||
"boardGroupPermission",
|
||||
{
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<BoardPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.boardId, table.groupId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const layouts = pgTable("layout", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
columnCount: smallint().notNull(),
|
||||
breakpoint: smallint().notNull().default(0),
|
||||
});
|
||||
|
||||
export const itemLayouts = pgTable(
|
||||
"item_layout",
|
||||
{
|
||||
itemId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => items.id, { onDelete: "cascade" }),
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
layoutId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => layouts.id, { onDelete: "cascade" }),
|
||||
xOffset: integer().notNull(),
|
||||
yOffset: integer().notNull(),
|
||||
width: integer().notNull(),
|
||||
height: integer().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.itemId, table.sectionId, table.layoutId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sectionLayouts = pgTable(
|
||||
"section_layout",
|
||||
{
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
layoutId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => layouts.id, { onDelete: "cascade" }),
|
||||
parentSectionId: varchar({ length: 64 }).references((): AnyPgColumn => sections.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
xOffset: integer().notNull(),
|
||||
yOffset: integer().notNull(),
|
||||
width: integer().notNull(),
|
||||
height: integer().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.sectionId, table.layoutId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sections = pgTable("section", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
kind: text().$type<SectionKind>().notNull(),
|
||||
xOffset: integer(),
|
||||
yOffset: integer(),
|
||||
name: text(),
|
||||
options: text().default(emptySuperJSON),
|
||||
});
|
||||
|
||||
export const sectionCollapseStates = pgTable(
|
||||
"section_collapse_state",
|
||||
{
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
collapsed: boolean().default(false).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.userId, table.sectionId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const items = pgTable("item", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
kind: text().$type<WidgetKind>().notNull(),
|
||||
options: text().default(emptySuperJSON).notNull(),
|
||||
advancedOptions: text().default(emptySuperJSON).notNull(),
|
||||
});
|
||||
|
||||
export const apps = pgTable("app", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text().notNull(),
|
||||
description: text(),
|
||||
iconUrl: text().notNull(),
|
||||
href: text(),
|
||||
pingUrl: text(),
|
||||
});
|
||||
|
||||
export const integrationItems = pgTable(
|
||||
"integration_item",
|
||||
{
|
||||
itemId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => items.id, { onDelete: "cascade" }),
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.itemId, table.integrationId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const icons = pgTable("icon", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 250 }).notNull(),
|
||||
url: text().notNull(),
|
||||
checksum: text().notNull(),
|
||||
iconRepositoryId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => iconRepositories.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const iconRepositories = pgTable("iconRepository", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
slug: varchar({ length: 150 }).notNull(),
|
||||
});
|
||||
|
||||
export const serverSettings = pgTable("serverSetting", {
|
||||
settingKey: varchar({ length: 64 }).notNull().unique().primaryKey(),
|
||||
value: text().default(emptySuperJSON).notNull(),
|
||||
});
|
||||
|
||||
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [apiKeys.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const searchEngines = pgTable("search_engine", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
iconUrl: text().notNull(),
|
||||
name: varchar({ length: 64 }).notNull(),
|
||||
short: varchar({ length: 8 }).unique().notNull(),
|
||||
description: text(),
|
||||
urlTemplate: text(),
|
||||
type: varchar({ length: 64 }).$type<SearchEngineType>().notNull().default("generic"),
|
||||
integrationId: varchar({ length: 64 }).references(() => integrations.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const onboarding = pgTable("onboarding", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
step: varchar({ length: 64 }).$type<OnboardingStep>().notNull(),
|
||||
previousStep: varchar({ length: 64 }).$type<OnboardingStep>(),
|
||||
});
|
||||
|
||||
export const trustedCertificateHostnames = pgTable(
|
||||
"trusted_certificate_hostname",
|
||||
{
|
||||
hostname: varchar({ length: 256 }).notNull(),
|
||||
thumbprint: varchar({ length: 128 }).notNull(),
|
||||
certificate: text().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.hostname, table.thumbprint],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const cronJobConfigurations = pgTable("cron_job_configuration", {
|
||||
name: varchar({ length: 256 }).notNull().primaryKey(),
|
||||
cronExpression: varchar({ length: 32 }).notNull(),
|
||||
isEnabled: boolean().default(true).notNull(),
|
||||
});
|
||||
|
||||
export const accountRelations = relations(accounts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [accounts.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const userRelations = relations(users, ({ one, many }) => ({
|
||||
accounts: many(accounts),
|
||||
boards: many(boards),
|
||||
boardPermissions: many(boardUserPermissions),
|
||||
groups: many(groupMembers),
|
||||
ownedGroups: many(groups),
|
||||
invites: many(invites),
|
||||
medias: many(medias),
|
||||
defaultSearchEngine: one(searchEngines, {
|
||||
fields: [users.defaultSearchEngineId],
|
||||
references: [searchEngines.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const mediaRelations = relations(medias, ({ one }) => ({
|
||||
creator: one(users, {
|
||||
fields: [medias.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const iconRelations = relations(icons, ({ one }) => ({
|
||||
repository: one(iconRepositories, {
|
||||
fields: [icons.iconRepositoryId],
|
||||
references: [iconRepositories.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const iconRepositoryRelations = relations(iconRepositories, ({ many }) => ({
|
||||
icons: many(icons),
|
||||
}));
|
||||
|
||||
export const inviteRelations = relations(invites, ({ one }) => ({
|
||||
creator: one(users, {
|
||||
fields: [invites.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sessionRelations = relations(sessions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [sessions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupMemberRelations = relations(groupMembers, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [groupMembers.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [groupMembers.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupRelations = relations(groups, ({ one, many }) => ({
|
||||
permissions: many(groupPermissions),
|
||||
boardPermissions: many(boardGroupPermissions),
|
||||
members: many(groupMembers),
|
||||
owner: one(users, {
|
||||
fields: [groups.ownerId],
|
||||
references: [users.id],
|
||||
}),
|
||||
homeBoard: one(boards, {
|
||||
fields: [groups.homeBoardId],
|
||||
references: [boards.id],
|
||||
relationName: "groupRelations__board__homeBoardId",
|
||||
}),
|
||||
mobileHomeBoard: one(boards, {
|
||||
fields: [groups.mobileHomeBoardId],
|
||||
references: [boards.id],
|
||||
relationName: "groupRelations__board__mobileHomeBoardId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupPermissionRelations = relations(groupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [groupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardUserPermissionRelations = relations(boardUserPermissions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [boardUserPermissions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [boardUserPermissions.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardGroupPermissionRelations = relations(boardGroupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [boardGroupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [boardGroupPermissions.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationRelations = relations(integrations, ({ many }) => ({
|
||||
secrets: many(integrationSecrets),
|
||||
items: many(integrationItems),
|
||||
userPermissions: many(integrationUserPermissions),
|
||||
groupPermissions: many(integrationGroupPermissions),
|
||||
}));
|
||||
|
||||
export const integrationUserPermissionRelations = relations(integrationUserPermissions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [integrationUserPermissions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
integration: one(integrations, {
|
||||
fields: [integrationUserPermissions.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationGroupPermissionRelations = relations(integrationGroupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [integrationGroupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
integration: one(integrations, {
|
||||
fields: [integrationGroupPermissions.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationSecretRelations = relations(integrationSecrets, ({ one }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [integrationSecrets.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardRelations = relations(boards, ({ many, one }) => ({
|
||||
sections: many(sections),
|
||||
items: many(items),
|
||||
creator: one(users, {
|
||||
fields: [boards.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
userPermissions: many(boardUserPermissions),
|
||||
groupPermissions: many(boardGroupPermissions),
|
||||
layouts: many(layouts),
|
||||
groupHomes: many(groups, {
|
||||
relationName: "groupRelations__board__homeBoardId",
|
||||
}),
|
||||
mobileHomeBoard: many(groups, {
|
||||
relationName: "groupRelations__board__mobileHomeBoardId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionRelations = relations(sections, ({ many, one }) => ({
|
||||
board: one(boards, {
|
||||
fields: [sections.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
collapseStates: many(sectionCollapseStates),
|
||||
layouts: many(sectionLayouts, {
|
||||
relationName: "sectionLayoutRelations__section__sectionId",
|
||||
}),
|
||||
children: many(sectionLayouts, {
|
||||
relationName: "sectionLayoutRelations__section__parentSectionId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionCollapseStateRelations = relations(sectionCollapseStates, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [sectionCollapseStates.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
section: one(sections, {
|
||||
fields: [sectionCollapseStates.sectionId],
|
||||
references: [sections.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const itemRelations = relations(items, ({ one, many }) => ({
|
||||
integrations: many(integrationItems),
|
||||
layouts: many(itemLayouts),
|
||||
board: one(boards, {
|
||||
fields: [items.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationItemRelations = relations(integrationItems, ({ one }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [integrationItems.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
item: one(items, {
|
||||
fields: [integrationItems.itemId],
|
||||
references: [items.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const searchEngineRelations = relations(searchEngines, ({ one, many }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [searchEngines.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
usersWithDefault: many(users),
|
||||
}));
|
||||
|
||||
export const itemLayoutRelations = relations(itemLayouts, ({ one }) => ({
|
||||
item: one(items, {
|
||||
fields: [itemLayouts.itemId],
|
||||
references: [items.id],
|
||||
}),
|
||||
section: one(sections, {
|
||||
fields: [itemLayouts.sectionId],
|
||||
references: [sections.id],
|
||||
}),
|
||||
layout: one(layouts, {
|
||||
fields: [itemLayouts.layoutId],
|
||||
references: [layouts.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionLayoutRelations = relations(sectionLayouts, ({ one }) => ({
|
||||
section: one(sections, {
|
||||
fields: [sectionLayouts.sectionId],
|
||||
references: [sections.id],
|
||||
relationName: "sectionLayoutRelations__section__sectionId",
|
||||
}),
|
||||
layout: one(layouts, {
|
||||
fields: [sectionLayouts.layoutId],
|
||||
references: [layouts.id],
|
||||
}),
|
||||
parentSection: one(sections, {
|
||||
fields: [sectionLayouts.parentSectionId],
|
||||
references: [sections.id],
|
||||
relationName: "sectionLayoutRelations__section__parentSectionId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const layoutRelations = relations(layouts, ({ one, many }) => ({
|
||||
items: many(itemLayouts),
|
||||
sections: many(sectionLayouts),
|
||||
board: one(boards, {
|
||||
fields: [layouts.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
@@ -0,0 +1,46 @@
|
||||
import path from "path";
|
||||
import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
import { describe, test } from "vitest";
|
||||
|
||||
import * as pgSchema from "../schema/postgresql";
|
||||
|
||||
describe("PostgreSql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
const container = new PostgreSqlContainer("postgres:latest");
|
||||
const postgreSqlContainer = await container.start();
|
||||
|
||||
const pool = new Pool({
|
||||
user: postgreSqlContainer.getUsername(),
|
||||
database: postgreSqlContainer.getDatabase(),
|
||||
password: postgreSqlContainer.getPassword(),
|
||||
port: postgreSqlContainer.getPort(),
|
||||
host: postgreSqlContainer.getHost(),
|
||||
keepAlive: true,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
|
||||
const database = drizzle({
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: pool,
|
||||
});
|
||||
|
||||
// Run migrations and check if it works
|
||||
await migrate(database, {
|
||||
migrationsFolder: path.join(__dirname, "..", "migrations", "postgresql"),
|
||||
});
|
||||
|
||||
// Check if users table exists
|
||||
await database.query.users.findMany();
|
||||
|
||||
// Close the pool to release resources
|
||||
await pool.end();
|
||||
// Stop the container
|
||||
await postgreSqlContainer.stop();
|
||||
}, 40_000);
|
||||
});
|
||||
@@ -1,15 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { Column, InferSelectModel } from "drizzle-orm";
|
||||
import type { ForeignKey as MysqlForeignKey, MySqlTableWithColumns } from "drizzle-orm/mysql-core";
|
||||
import type { PgTableWithColumns, ForeignKey as PostgresqlForeignKey } from "drizzle-orm/pg-core";
|
||||
import type { ForeignKey as SqliteForeignKey, SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
|
||||
import { expect, expectTypeOf, test } from "vitest";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import * as mysqlSchema from "../schema/mysql";
|
||||
import * as postgresqlSchema from "../schema/postgresql";
|
||||
import * as sqliteSchema from "../schema/sqlite";
|
||||
|
||||
// We need the following two types as there is currently no support for Buffer in mysql and
|
||||
// We need the following three types as there is currently no support for Buffer in mysql & pg and
|
||||
// so we use a custom type which results in the config beeing different
|
||||
type FixedMysqlConfig = {
|
||||
[key in keyof MysqlConfig]: {
|
||||
@@ -22,6 +24,22 @@ type FixedMysqlConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
type FixedPostgresqlConfig = {
|
||||
[key in keyof PostgreisqlConfig]: {
|
||||
[column in keyof PostgreisqlConfig[key]]: {
|
||||
[property in Exclude<
|
||||
keyof PostgreisqlConfig[key][column],
|
||||
"dataType" | "data"
|
||||
>]: PostgreisqlConfig[key][column][property];
|
||||
} & {
|
||||
dataType: PostgreisqlConfig[key][column]["data"] extends Buffer
|
||||
? "buffer"
|
||||
: PostgreisqlConfig[key][column]["dataType"];
|
||||
data: PostgreisqlConfig[key][column]["data"] extends Buffer ? Buffer : PostgreisqlConfig[key][column]["data"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type FixedSqliteConfig = {
|
||||
[key in keyof SqliteConfig]: {
|
||||
[column in keyof SqliteConfig[key]]: {
|
||||
@@ -117,6 +135,91 @@ test("schemas should match", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("schemas should match for postgresql", () => {
|
||||
expectTypeOf<SqliteTables>().toEqualTypeOf<PostgresqlTables>();
|
||||
expectTypeOf<PostgresqlTables>().toEqualTypeOf<SqliteTables>();
|
||||
expectTypeOf<FixedSqliteConfig>().toEqualTypeOf<FixedPostgresqlConfig>();
|
||||
expectTypeOf<FixedPostgresqlConfig>().toEqualTypeOf<FixedSqliteConfig>();
|
||||
|
||||
objectEntries(sqliteSchema).forEach(([tableName, sqliteTable]) => {
|
||||
// keys of sqliteSchema and postgresqlSchema are the same, so we can safely use tableName as key
|
||||
// skipcq: JS-E1007
|
||||
const postgresqlTable = postgresqlSchema[tableName];
|
||||
Object.entries(sqliteTable).forEach(([columnName, sqliteColumn]: [string, object]) => {
|
||||
if (!("isUnique" in sqliteColumn)) return;
|
||||
if (!("uniqueName" in sqliteColumn)) return;
|
||||
if (!("primary" in sqliteColumn)) return;
|
||||
|
||||
const postgresqlColumn = postgresqlTable[columnName as keyof typeof postgresqlTable] as object;
|
||||
if (!("isUnique" in postgresqlColumn)) return;
|
||||
if (!("uniqueName" in postgresqlColumn)) return;
|
||||
if (!("primary" in postgresqlColumn)) return;
|
||||
|
||||
expect(
|
||||
sqliteColumn.isUnique,
|
||||
`expect unique of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.isUnique);
|
||||
expect(
|
||||
sqliteColumn.uniqueName,
|
||||
`expect uniqueName of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.uniqueName);
|
||||
expect(
|
||||
sqliteColumn.primary,
|
||||
`expect primary of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.primary);
|
||||
});
|
||||
|
||||
const sqliteForeignKeys = sqliteTable[Symbol.for("drizzle:SQLiteInlineForeignKeys") as keyof typeof sqliteTable] as
|
||||
| SqliteForeignKey[]
|
||||
| undefined;
|
||||
const postgresqlForeignKeys = postgresqlTable[
|
||||
Symbol.for("drizzle:PgInlineForeignKeys") as keyof typeof postgresqlTable
|
||||
] as PostgresqlForeignKey[] | undefined;
|
||||
if (!sqliteForeignKeys && !postgresqlForeignKeys) return;
|
||||
|
||||
expect(postgresqlForeignKeys, `postgresql foreign key for ${tableName} to be defined`).toBeDefined();
|
||||
expect(sqliteForeignKeys, `sqlite foreign key for ${tableName} to be defined`).toBeDefined();
|
||||
|
||||
expect(
|
||||
sqliteForeignKeys!.length,
|
||||
`expect number of foreign keys in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKeys?.length);
|
||||
|
||||
sqliteForeignKeys?.forEach((sqliteForeignKey) => {
|
||||
sqliteForeignKey.getName();
|
||||
const postgresqlForeignKey = postgresqlForeignKeys!.find((key) => key.getName() === sqliteForeignKey.getName());
|
||||
expect(
|
||||
postgresqlForeignKey,
|
||||
`expect foreign key ${sqliteForeignKey.getName()} to be defined in postgresql schema`,
|
||||
).toBeDefined();
|
||||
|
||||
// In PostgreSql, onDelete is "no action" by default, so it is treated as undefined to match Sqlite.
|
||||
expect(
|
||||
sqliteForeignKey.onDelete,
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) onDelete to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKey!.onDelete === "no action" ? undefined : postgresqlForeignKey!.onDelete);
|
||||
|
||||
// In PostgreSql, onUpdate is "no action" by default, so it is treated as undefined to match Sqlite.
|
||||
expect(
|
||||
sqliteForeignKey.onUpdate,
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) onUpdate to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKey!.onUpdate === "no action" ? undefined : postgresqlForeignKey!.onUpdate);
|
||||
|
||||
sqliteForeignKey.reference().foreignColumns.forEach((column) => {
|
||||
expect(
|
||||
postgresqlForeignKey!.reference().foreignColumns.map((column) => column.name),
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) columns to be the same for both schemas`,
|
||||
).toContainEqual(column.name);
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.keys(sqliteForeignKey.reference().foreignTable),
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) table to be the same for both schemas`,
|
||||
).toEqual(Object.keys(postgresqlForeignKey!.reference().foreignTable).filter((key) => key !== "enableRLS"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type SqliteTables = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof sqliteSchema]: (typeof sqliteSchema)[K] extends SQLiteTableWithColumns<any>
|
||||
@@ -130,6 +233,13 @@ type MysqlTables = {
|
||||
: never;
|
||||
};
|
||||
|
||||
type PostgresqlTables = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof postgresqlSchema]: (typeof postgresqlSchema)[K] extends PgTableWithColumns<any>
|
||||
? InferSelectModel<(typeof postgresqlSchema)[K]>
|
||||
: never;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type InferColumnConfig<T extends Column<any, object>> =
|
||||
T extends Column<infer C, object> ? Omit<C, "columnType" | "enumValues" | "driverParam"> : never;
|
||||
@@ -155,3 +265,14 @@ type MysqlConfig = {
|
||||
}
|
||||
: never;
|
||||
};
|
||||
|
||||
type PostgreisqlConfig = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof postgresqlSchema]: (typeof postgresqlSchema)[K] extends PgTableWithColumns<any>
|
||||
? {
|
||||
[C in keyof (typeof postgresqlSchema)[K]["_"]["config"]["columns"]]: InferColumnConfig<
|
||||
(typeof postgresqlSchema)[K]["_"]["config"]["columns"][C]
|
||||
>;
|
||||
}
|
||||
: never;
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
import { isMysql, isPostgresql } from "./collection";
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
|
||||
import { env } from "./env";
|
||||
import * as mysqlSchema from "./schema/mysql";
|
||||
|
||||
type MysqlSchema = typeof mysqlSchema;
|
||||
import type { MySqlSchema } from "./schema";
|
||||
import * as schema from "./schema";
|
||||
|
||||
interface HandleTransactionInput {
|
||||
handleAsync: (db: HomarrDatabaseMysql, schema: MysqlSchema) => Promise<void>;
|
||||
handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>;
|
||||
handleSync: (db: HomarrDatabase) => void;
|
||||
}
|
||||
|
||||
@@ -15,10 +14,10 @@ interface HandleTransactionInput {
|
||||
* But it can also generally be used when dealing with different database drivers.
|
||||
*/
|
||||
export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => {
|
||||
if (env.DB_DRIVER !== "mysql2") {
|
||||
if (isMysql() || isPostgresql()) {
|
||||
// Schema type is always the correct one based on env variables
|
||||
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
|
||||
} else {
|
||||
input.handleSync(db);
|
||||
return;
|
||||
}
|
||||
|
||||
await input.handleAsync(db as unknown as HomarrDatabaseMysql, mysqlSchema);
|
||||
};
|
||||
@@ -25,7 +25,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^8.2.4",
|
||||
"mantine-form-zod-resolver": "^1.2.1",
|
||||
"zod": "^4.0.14"
|
||||
"@mantine/form": "^8.2.5",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"react": "19.1.1",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -97,7 +97,13 @@ export const AppForm = ({
|
||||
<Stack>
|
||||
<TextInput {...form.getInputProps("name")} withAsterisk label={t("app.field.name.label")} />
|
||||
<IconPicker {...form.getInputProps("iconUrl")} />
|
||||
<Textarea {...form.getInputProps("description")} label={t("app.field.description.label")} />
|
||||
<Textarea
|
||||
{...form.getInputProps("description")}
|
||||
label={t("app.field.description.label")}
|
||||
autosize
|
||||
minRows={2}
|
||||
resize="vertical"
|
||||
/>
|
||||
<TextInput {...form.getInputProps("href")} label={t("app.field.url.label")} />
|
||||
|
||||
<Checkbox
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
"octokit": "^5.0.3",
|
||||
"proxmox-api": "1.1.1",
|
||||
"tsdav": "^2.1.5",
|
||||
"undici": "7.13.0",
|
||||
"undici": "7.14.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Agent } from "https";
|
||||
import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
|
||||
import * as ical from "node-ical";
|
||||
import { DAVClient } from "tsdav";
|
||||
import type { Dispatcher, RequestInit as UndiciFetchRequestInit } from "undici";
|
||||
|
||||
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
||||
import { createHttpsAgentAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
@@ -16,7 +17,7 @@ import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
||||
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
||||
export class NextcloudIntegration extends Integration implements ICalendarIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const client = await this.createCalendarClientAsync(input.dispatcher);
|
||||
const client = await this.createCalendarClientAsync(await createHttpsAgentAsync(input.options));
|
||||
await client.login();
|
||||
|
||||
return { success: true };
|
||||
@@ -80,7 +81,7 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
||||
});
|
||||
}
|
||||
|
||||
private async createCalendarClientAsync(dispatcher?: Dispatcher) {
|
||||
private async createCalendarClientAsync(agent?: Agent) {
|
||||
return new DAVClient({
|
||||
serverUrl: this.integration.url,
|
||||
credentials: {
|
||||
@@ -90,9 +91,10 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
||||
authMethod: "Basic",
|
||||
defaultAccountType: "caldav",
|
||||
fetchOptions: {
|
||||
// We can use the undici options as the global fetch is used instead of the polyfilled.
|
||||
dispatcher: dispatcher ?? (await createCertificateAgentAsync()),
|
||||
} satisfies UndiciFetchRequestInit as RequestInit,
|
||||
// tsdav is using cross-fetch which uses node-fetch for nodejs environments.
|
||||
// There is an agent property that is the same type as the http(s) agents of nodejs
|
||||
agent: agent ?? (await createHttpsAgentAsync()),
|
||||
} satisfies NodeFetchRequestInit as RequestInit,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"winston": "3.17.0",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"react": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.2.4",
|
||||
"@mantine/notifications": "^8.2.5",
|
||||
"@tabler/icons-react": "^3.34.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^4.0.14",
|
||||
"zod": "^4.0.17",
|
||||
"zod-form-data": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -37,8 +37,9 @@ export const mapApp = (
|
||||
appId: appsMap.get(app.id)?.id!,
|
||||
openInNewTab: app.behaviour.isOpeningNewTab,
|
||||
pingEnabled: app.network.enabledStatusChecker,
|
||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
||||
showTitle: app.appearance.appNameStatus === "normal",
|
||||
layout: app.appearance.positionAppName,
|
||||
descriptionDisplayMode: app.behaviour.tooltipDescription !== "" ? "tooltip" : "hidden",
|
||||
} satisfies WidgetComponentProps<"app">["options"]),
|
||||
layouts: boardSizes.map((size) => {
|
||||
const shapeForSize = app.shape[size];
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"octokit": "^5.0.3",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.13.0"
|
||||
"undici": "7.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { logger } from "@homarr/log";
|
||||
import { createChannelWithLatestAndEvents } from "@homarr/redis";
|
||||
import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler";
|
||||
@@ -13,6 +14,11 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
queryKey: "homarr-update-checker",
|
||||
cacheDuration: dayjs.duration(1, "hour"),
|
||||
async requestAsync(_) {
|
||||
if (env.NO_EXTERNAL_CONNECTION)
|
||||
return {
|
||||
availableUpdates: [],
|
||||
};
|
||||
|
||||
const octokit = new Octokit({
|
||||
request: {
|
||||
fetch: fetchWithTimeout,
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.2.4",
|
||||
"next": "15.4.6",
|
||||
"@mantine/dates": "^8.2.5",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/spotlight": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"@mantine/spotlight": "^8.2.5",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"jotai": "^2.13.1",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"next-intl": "4.3.4",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "测试版",
|
||||
"error": "错误",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "启用状态检查"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "每 {interval, plural, =1 {分钟} other {# 分钟}}",
|
||||
"hours": "每 {interval, plural, =1 {小时} other {# 小时}}",
|
||||
"midnight": "每日午夜",
|
||||
"weeklyMonday": "每周一"
|
||||
"weeklyMonday": "每周一",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "{jobName} 任务设置"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": "计划时间间隔"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"apps": "Aplikace",
|
||||
"boards": "Plochy",
|
||||
"integrations": "Integrace",
|
||||
"credentialUsers": ""
|
||||
"credentialUsers": "Uživatelské oprávnění"
|
||||
}
|
||||
},
|
||||
"tokenModal": {
|
||||
@@ -52,7 +52,7 @@
|
||||
"field": {
|
||||
"token": {
|
||||
"label": "Token",
|
||||
"description": ""
|
||||
"description": "Zadejte zobrazený importní token z předchozí instance homarr"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Chyba",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Nástroje",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Fejl",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Aktiver statustjek"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "Hvert {interval, plural, one {}=1 {minut} other {# minutter}}",
|
||||
"hours": "Hvert {interval, plural, one {}=1 {time} other {# timer}}",
|
||||
"midnight": "Hver dag ved midnat",
|
||||
"weeklyMonday": "Hver uge om mandagen"
|
||||
"weeklyMonday": "Hver uge om mandagen",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Opgave indstillinger for {jobName}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": "Planlæg interval"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Værktøjer",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Fehler",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Statusüberprüfung aktivieren"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Werkzeuge",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Fehler",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Statusüberprüfung aktivieren"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "Jeweils {interval, plural, =1 {Minute} other {# Minuten}}",
|
||||
"hours": "Jeweils {interval, plural, =1 {Stunde} other {# Stunden}}",
|
||||
"midnight": "Jeden Tag um Mitternacht",
|
||||
"weeklyMonday": "Jede Woche am Montag"
|
||||
"weeklyMonday": "Jede Woche am Montag",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen der Aufgabe für {jobName}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": "Intervall des Zeitplans"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Werkzeuge",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Σφάλμα",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Εργαλεία",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Alpha",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1266,11 +1266,26 @@
|
||||
"showTitle": {
|
||||
"label": "Show app name"
|
||||
},
|
||||
"showDescriptionTooltip": {
|
||||
"label": "Show description tooltip"
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Enable status check"
|
||||
},
|
||||
"layout": {
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": "Horizontal",
|
||||
"row-reverse": "Horizontal (reversed)",
|
||||
"column": "Vertical",
|
||||
"column-reverse": "Vertical (reversed)"
|
||||
}
|
||||
},
|
||||
"descriptionDisplayMode": {
|
||||
"label": "Description display mode",
|
||||
"description": "Choose how to display the app description",
|
||||
"option": {
|
||||
"normal": "Within widget",
|
||||
"tooltip": "As tooltip",
|
||||
"hidden": "Hidden"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Bêta",
|
||||
"error": "Erreur",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Activer la vérification du statut"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "Chaque {interval, plural, one {}=1 {minute} other {# minutes}}",
|
||||
"hours": "Chaque {interval, plural, one {}=1 {heure} other {# heures}}",
|
||||
"midnight": "Chaque jour à minuit",
|
||||
"weeklyMonday": "Chaque semaine le lundi"
|
||||
"weeklyMonday": "Chaque semaine le lundi",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres de la tâche pour {jobName}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": "Intervalle de planification"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Outils",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "בטא",
|
||||
"error": "שגיאה",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "אפשר בדיקת מצב"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "כל {interval, plural, =1 {minute} other {# minutes}}",
|
||||
"hours": "כל {interval, plural, =1 {hour} other {# hours}}",
|
||||
"midnight": "כל יום בחצות",
|
||||
"weeklyMonday": "כל שבוע ביום שני"
|
||||
"weeklyMonday": "כל שבוע ביום שני",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "הגדרות משימה עבור {jobName}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": "מרווח זמן לתזמון"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "כלים",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "דוקר"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Pogreška",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Alati",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Béta",
|
||||
"error": "Hiba",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Egyszerű ping engedélyezése"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Eszközök",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Errore",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Strumenti",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -946,12 +946,12 @@
|
||||
"newLabel": "新規トピック"
|
||||
},
|
||||
"opnsenseApiKey": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "API キー (キー)",
|
||||
"newLabel": "新しいAPIキー(キー)"
|
||||
},
|
||||
"opnsenseApiSecret": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "API キー (シークレット)",
|
||||
"newLabel": "新しい API キー (シークレット)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "成功",
|
||||
"beta": "Beta",
|
||||
"error": "エラー",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "ステータスチェックを有効にする"
|
||||
},
|
||||
"layout": {
|
||||
"label": "レイアウト",
|
||||
"option": {
|
||||
"row": "水平方向",
|
||||
"row-reverse": "水平方向(反転)",
|
||||
"column": "垂直方向",
|
||||
"column-reverse": "垂直方向(反転)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -2266,7 +2276,7 @@
|
||||
"label": "詳細を表示"
|
||||
},
|
||||
"showOnlyIcon": {
|
||||
"label": ""
|
||||
"label": "アイコンのみ表示"
|
||||
},
|
||||
"topReleases": {
|
||||
"label": "トップリリース",
|
||||
@@ -2285,8 +2295,8 @@
|
||||
"listAlreadyImportedImages": "すでにインポートされたイメージのリスト",
|
||||
"allImagesAlreadyImported": "すでにインポートされた全てのイメージ",
|
||||
"onlyAdminCanImport": "管理者のみが Docker からインポートできます",
|
||||
"selectAll": "",
|
||||
"deselectAll": ""
|
||||
"selectAll": "すべてを選択",
|
||||
"deselectAll": "選択をすべて解除"
|
||||
},
|
||||
"provider": {
|
||||
"label": "プロバイダー"
|
||||
@@ -2348,7 +2358,7 @@
|
||||
"starsCount": "スター数",
|
||||
"forksCount": "フォーク数",
|
||||
"issuesCount": "未解決の課題数",
|
||||
"markViewed": "",
|
||||
"markViewed": "閲覧済みにする",
|
||||
"openProjectPage": "プロジェクトページを開く",
|
||||
"openReleasePage": "リリースページを開く",
|
||||
"releaseDescription": "リリースの説明",
|
||||
@@ -2422,31 +2432,31 @@
|
||||
}
|
||||
},
|
||||
"firewall": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "ファイアウォールの監視",
|
||||
"description": "ファイアウォールの概要を表示する",
|
||||
"tab": {
|
||||
"system": "",
|
||||
"interfaces": ""
|
||||
"system": "システム",
|
||||
"interfaces": "インターフェース"
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "ファイアウォールからデータを取得できません"
|
||||
},
|
||||
"option": {
|
||||
"interfaces": ""
|
||||
"interfaces": "表示するネットワークインターフェイス"
|
||||
},
|
||||
"widget": {
|
||||
"fwname": "",
|
||||
"version": "",
|
||||
"versiontitle": "",
|
||||
"cputitle": "",
|
||||
"memorytitle": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"fwname": "名称",
|
||||
"version": "バージョン",
|
||||
"versiontitle": "バージョン",
|
||||
"cputitle": "CPU 使用率",
|
||||
"memorytitle": "メモリ使用率",
|
||||
"cpu": "CPU",
|
||||
"memory": "メモリ",
|
||||
"interfaces": {
|
||||
"name": "",
|
||||
"trans": "",
|
||||
"recv": "",
|
||||
"title": ""
|
||||
"name": "名称",
|
||||
"trans": "送信",
|
||||
"recv": "受信",
|
||||
"title": "ネットワークインターフェース"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2457,15 +2467,15 @@
|
||||
"option": {}
|
||||
},
|
||||
"systemResources": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "システムリソース",
|
||||
"description": "CPU、メモリ、ディスクなどのシステムハードウェアの使用",
|
||||
"option": {},
|
||||
"card": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"network": "",
|
||||
"up": "",
|
||||
"down": ""
|
||||
"cpu": "CPU",
|
||||
"memory": "MEM",
|
||||
"network": "NET",
|
||||
"up": "UP",
|
||||
"down": "DOWN"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3243,16 +3253,16 @@
|
||||
"label": "Docker コンテナ"
|
||||
},
|
||||
"firewallCpu": {
|
||||
"label": ""
|
||||
"label": "ファイアウォールの CPU"
|
||||
},
|
||||
"firewallMemory": {
|
||||
"label": ""
|
||||
"label": "ファイアウォールのメモリ"
|
||||
},
|
||||
"firewallVersion": {
|
||||
"label": ""
|
||||
"label": "ファイアウォールのバージョン"
|
||||
},
|
||||
"firewallInterfaces": {
|
||||
"label": ""
|
||||
"label": "ファイアウォールのインターフェース"
|
||||
}
|
||||
},
|
||||
"interval": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "{interval, plural, =1 {分} other {# 分}}周期",
|
||||
"hours": "{interval, plural, =1 {時間} other {# 時間}}周期",
|
||||
"midnight": "毎日深夜",
|
||||
"weeklyMonday": "毎週月曜日"
|
||||
"weeklyMonday": "毎週月曜日",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": "間隔が正常に更新されました"
|
||||
},
|
||||
"error": {
|
||||
"message": "間隔の更新に失敗しました"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "{jobName} の タスク設定"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "名称"
|
||||
},
|
||||
"interval": {
|
||||
"label": "スケジュール間隔"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": "最終実行"
|
||||
},
|
||||
"actions": {
|
||||
"label": "アクション"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": "{count} のタスクを検索..."
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": "更新"
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": "タスクは正常に更新されました"
|
||||
},
|
||||
"error": {
|
||||
"message": "タスクの更新に失敗しました"
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": "タスクが正常にトリガーされました"
|
||||
},
|
||||
"error": {
|
||||
"message": "タスクのトリガーに失敗しました"
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": "正常にタスクが有効になりました"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": "正常にタスクが無効になりました"
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": "タスクの状態の切り替えに失敗しました"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "ツール",
|
||||
"tasks": {
|
||||
"label": "タスク"
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -2,40 +2,40 @@
|
||||
"init": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "Homarr에 오신 것을 환영합니다",
|
||||
"subtitle": "Homarr 인스턴스 설정을 시작합니다.",
|
||||
"description": "시작하기 전에, Homarr 인스턴스를 어떻게 설정할지 선택해주세요.",
|
||||
"action": {
|
||||
"scratch": "",
|
||||
"importOldmarr": ""
|
||||
"scratch": "처음부터 시작하기",
|
||||
"importOldmarr": "1.0 이전의 Homarr에서 불러오기"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "데이터 불러오기",
|
||||
"subtitle": "기존 Homarr 인스턴스에서 데이터를 불러올 수 있습니다.",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"title": "zip 파일 드롭 또는 클릭해서 찾아보기",
|
||||
"description": ""
|
||||
},
|
||||
"fileInfo": {
|
||||
"action": {
|
||||
"change": ""
|
||||
"change": "파일 변경"
|
||||
}
|
||||
},
|
||||
"importSettings": {
|
||||
"title": "",
|
||||
"title": "설정 불러오기",
|
||||
"description": ""
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "",
|
||||
"title": "{count}개의 보드 찾음",
|
||||
"description": "",
|
||||
"action": {
|
||||
"selectAll": "",
|
||||
"unselectAll": ""
|
||||
"selectAll": "모두 선택",
|
||||
"unselectAll": "모두 선택 해제"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"title": "",
|
||||
"title": "불러오기 요약",
|
||||
"description": "",
|
||||
"action": {
|
||||
"import": ""
|
||||
@@ -57,69 +57,69 @@
|
||||
},
|
||||
"notification": {
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "유효하지 않은 토큰",
|
||||
"message": "입력한 토큰이 유효하지 않습니다"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "관리자",
|
||||
"subtitle": "관리자의 계정 정보를 설정해주세요.",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "계정 생성됨",
|
||||
"message": "계정이 성공적으로 생성되었습니다"
|
||||
},
|
||||
"error": {
|
||||
"title": ""
|
||||
"title": "계정 생성 실패"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"title": "외부 그룹",
|
||||
"subtitle": "",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "그룹 이름",
|
||||
"description": "이름은 외부 제공자의 관리자 그룹과 일치해야 합니다"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "설정",
|
||||
"subtitle": ""
|
||||
"subtitle": "서버 설정하기."
|
||||
},
|
||||
"finish": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"title": "설정 완료",
|
||||
"subtitle": "모든 준비가 끝났습니다!",
|
||||
"description": "설정 과정을 성공적으로 완료했습니다. 이제 Homarr을 사용할 수 있습니다. 다음 작업을 선택해주세요:",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": ""
|
||||
"goToBoard": "{name} 보드로 가기",
|
||||
"createBoard": "첫 번째 보드 만들기",
|
||||
"inviteUser": "다른 사용자 초대하기",
|
||||
"docs": "문서 읽기"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backToStart": ""
|
||||
"backToStart": "시작으로 돌아가기"
|
||||
},
|
||||
"user": {
|
||||
"title": "사용자",
|
||||
"name": "사용자",
|
||||
"page": {
|
||||
"login": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "내 계정에 로그인",
|
||||
"subtitle": "돌아오신 것을 환영합니다! 계정 정보를 입력해주세요"
|
||||
},
|
||||
"invite": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": ""
|
||||
"title": "Homarr 참여하기",
|
||||
"subtitle": "Homarr에 오신 것을 환영합니다! 계정을 생성해주세요",
|
||||
"description": "{username}가 초대했습니다"
|
||||
},
|
||||
"init": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "새로운 Homarr 설치",
|
||||
"subtitle": "초기 관리자 계정을 생성해주세요"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
@@ -281,47 +281,47 @@
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "프로필을 업데이트할 수 없습니다"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"label": "",
|
||||
"label": "사용자 영원히 삭제",
|
||||
"description": "",
|
||||
"confirm": ""
|
||||
},
|
||||
"select": {
|
||||
"label": "",
|
||||
"notFound": ""
|
||||
"label": "사용자 선택",
|
||||
"notFound": "사용자 찾을 수 없음"
|
||||
},
|
||||
"transfer": {
|
||||
"label": ""
|
||||
"label": "새로운 소유자 선택하기"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"name": "",
|
||||
"search": "",
|
||||
"title": "그룹",
|
||||
"name": "그룹",
|
||||
"search": "그룹 찾기",
|
||||
"field": {
|
||||
"name": "이름",
|
||||
"members": "",
|
||||
"members": "멤버",
|
||||
"homeBoard": {
|
||||
"label": "",
|
||||
"label": "홈 보드",
|
||||
"description": ""
|
||||
},
|
||||
"mobileBoard": {
|
||||
"label": "",
|
||||
"label": "모바일 보드",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"admin": {
|
||||
"title": "",
|
||||
"title": "관리자",
|
||||
"item": {
|
||||
"admin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "관리자",
|
||||
"description": "이 권한을 가진 멤버는 모든 기능과 설정에 대한 접근을 할 수 있습니다"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -329,11 +329,11 @@
|
||||
"title": "앱",
|
||||
"item": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "앱 만들기",
|
||||
"description": "멤버가 앱을 만들 수 있도록 허락하기"
|
||||
},
|
||||
"use-all": {
|
||||
"label": "",
|
||||
"label": "모든 앱 사용",
|
||||
"description": ""
|
||||
},
|
||||
"modify-all": {
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "오류",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "도구",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Įrankiai",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Kļūda",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Rīki",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Fout",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Statuscontrole inschakelen"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Gereedschappen",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Feil",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Aktiver enkel ping"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Verktøy",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Wersja beta",
|
||||
"error": "Błąd",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Włącz prosty ping"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Narzędzia",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Erro",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Ferramentas",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Eroare",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Unelte",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Бета",
|
||||
"error": "Ошибка",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Включить простую проверку доступности"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Инструменты",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Beta",
|
||||
"error": "Chyba",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Povoliť jednoduchý ping"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Nástroje",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Napaka",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Orodja",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Fel",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "Varje dag vid midnatt",
|
||||
"weeklyMonday": "Varje vecka på måndag"
|
||||
"weeklyMonday": "Varje vecka på måndag",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Verktyg",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "Başarılı",
|
||||
"beta": "Test",
|
||||
"error": "Hata",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "Durum denetimini etkinleştir"
|
||||
},
|
||||
"layout": {
|
||||
"label": "Düzen",
|
||||
"option": {
|
||||
"row": "Yatay",
|
||||
"row-reverse": "Yatay (ters)",
|
||||
"column": "Dikey",
|
||||
"column-reverse": "Dikey (ters)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "Her {interval, plural, =1 {dakika} other {# dakikada bir}}",
|
||||
"hours": "Her {interval, plural, =1 {saat} other {# saatte bir}}",
|
||||
"midnight": "Her gün gece yarısı",
|
||||
"weeklyMonday": "Her hafta pazartesi günü"
|
||||
"weeklyMonday": "Her hafta pazartesi günü",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": "Aralık başarıyla güncellendi"
|
||||
},
|
||||
"error": {
|
||||
"message": "Aralık güncellenemedi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "{jobName} için görev ayarları"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": "İsim"
|
||||
},
|
||||
"interval": {
|
||||
"label": "Zamanlama aralığı"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": "Son Çalıştırma"
|
||||
},
|
||||
"actions": {
|
||||
"label": "Eylemler"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": "{count} görevi ara..."
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": "Yenile"
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": "Görevler başarıyla yenilendi"
|
||||
},
|
||||
"error": {
|
||||
"message": "Görevler yenilenemedi"
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": "Görev başarıyla tetiklendi"
|
||||
},
|
||||
"error": {
|
||||
"message": "Görev tetiklenemedi"
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": "Görev başarıyla etkinleştirildi"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": "Görev başarıyla devre dışı bırakıldı"
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": "Görev durumu değiştirilemedi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Araçlar",
|
||||
"tasks": {
|
||||
"label": "Görevler"
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "Бета-версія",
|
||||
"error": "Помилка",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Інструменти",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Докер"
|
||||
},
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "Chào mừng đến với Homarr",
|
||||
"subtitle": "Hãy bắt đầu cài đặt phiên bản Homarr của bạn.",
|
||||
"description": "Để bắt đầu, hãy chọn cách bạn muốn cài đặt phiên bản Homarr của mình.",
|
||||
"subtitle": "Hãy bắt đầu cài đặt ứng dụng Homarr của bạn.",
|
||||
"description": "Để bắt đầu, hãy chọn cách bạn muốn cài đặt ứng dụng Homarr của mình.",
|
||||
"action": {
|
||||
"scratch": "Bắt đầu từ đầu",
|
||||
"importOldmarr": "Nhập từ Homarr trước phiên bản 1.0"
|
||||
"importOldmarr": "Nhập dữ liệu từ Homarr phiên bản trước 1.0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Nhập dữ liệu",
|
||||
"subtitle": "Bạn có thể nhập dữ liệu từ một phiên bản Homarr có sẵn.",
|
||||
"dropzone": {
|
||||
"title": "Kéo tệp tin zip vào đây hoặc nhấp để chọn",
|
||||
"title": "Kéo tệp tin zip vào đây hoặc nhấp chuột để chọn",
|
||||
"description": "Tệp tin zip đã tải lên sẽ được xử lý và bạn sẽ có thể chọn những gì bạn muốn nhập"
|
||||
},
|
||||
"fileInfo": {
|
||||
@@ -92,13 +92,13 @@
|
||||
},
|
||||
"finish": {
|
||||
"title": "Hoàn tất cài đặt",
|
||||
"subtitle": "Đã sẵn sàng để tiếp tục!",
|
||||
"subtitle": "Bạn đã sẵn sàng để tiếp tục!",
|
||||
"description": "",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": "Hãy đọc tài liệu này"
|
||||
"docs": "Đọc tài liệu hướng dẫn"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -156,7 +156,7 @@
|
||||
"label": "Công cụ tìm kiếm mặc định"
|
||||
},
|
||||
"openSearchInNewTab": {
|
||||
"label": ""
|
||||
"label": "Mở kết quả tìm kiếm trong thẻ mới"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -169,7 +169,7 @@
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Đăng nhập thành công",
|
||||
"message": "Bạn đã đăng nhập"
|
||||
"message": "Bạn đã được đăng nhập"
|
||||
},
|
||||
"error": {
|
||||
"title": "Đăng nhập không thành công",
|
||||
@@ -177,7 +177,7 @@
|
||||
}
|
||||
},
|
||||
"forgotPassword": {
|
||||
"label": "Quên mật khẩu?",
|
||||
"label": "Bạn đã quên mật khẩu?",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
@@ -189,8 +189,8 @@
|
||||
"message": "Vui lòng đăng nhập để tiếp tục"
|
||||
},
|
||||
"error": {
|
||||
"title": "Tạo tài khoản thất bại",
|
||||
"message": "Tài khoản của bạn không thể được tạo"
|
||||
"title": "Tạo tài khoản không thành công",
|
||||
"message": "Không thể tạo tài khoản bạn được"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -287,7 +287,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"label": "Xóa người dùng vĩnh viễn",
|
||||
"description": "Xóa người dùng này bao gồm cài đặt của họ. Sẽ không xóa các bảng. Người dùng sẽ không được thông báo.",
|
||||
"description": "Xóa người dùng này và những cài đặt cá nhân của họ. Hành động này sẽ không xóa các bảng. Người dùng bị xóa sẽ không nhận thông báo.",
|
||||
"confirm": "Bạn có chắc chắn muốn xóa tài khoản {username} cùng với các cài đặt?"
|
||||
},
|
||||
"select": {
|
||||
@@ -409,8 +409,8 @@
|
||||
"title": "Khác",
|
||||
"item": {
|
||||
"view-logs": {
|
||||
"label": "Xem nhật ký",
|
||||
"description": "Cho phép thành viên xem nhật ký"
|
||||
"label": "Xem nhật ký máy",
|
||||
"description": "Cho phép thành viên xem nhật ký máy"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -419,11 +419,11 @@
|
||||
"item": {
|
||||
"create": {
|
||||
"label": "Tạo công cụ tìm kiếm",
|
||||
"description": ""
|
||||
"description": "Cho phép thành viên nhóm này tạo công cụ tìm kiếm"
|
||||
},
|
||||
"modify-all": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Sửa đổi tất cả công cụ tìm kiếm",
|
||||
"description": "Cho phép thành viên nhóm này sửa đổi tất cả công cụ tìm kiếm"
|
||||
},
|
||||
"full-all": {
|
||||
"label": "",
|
||||
@@ -441,23 +441,23 @@
|
||||
},
|
||||
"action": {
|
||||
"create": {
|
||||
"label": "",
|
||||
"label": "Nhóm mới",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Đã tạo nhóm thành công"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Nhóm này không thể tạo được"
|
||||
}
|
||||
}
|
||||
},
|
||||
"transfer": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"confirm": "",
|
||||
"label": "Chuyển quyền sở hữu",
|
||||
"description": "Chuyển quyền sở hữu nhóm này cho một người dùng khác",
|
||||
"confirm": "Bạn chắc chắn muốn chuyển quyền sở hữu nhóm {name} cho {username}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Đã chuyển thành công quyền sở hữu nhóm {group} cho {username}"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
@@ -465,19 +465,19 @@
|
||||
}
|
||||
},
|
||||
"addMember": {
|
||||
"label": "Thêm thành viên"
|
||||
"label": "Thêm thành viên vào nhóm"
|
||||
},
|
||||
"removeMember": {
|
||||
"label": "Xóa thành viên",
|
||||
"label": "Xóa thành viên khỏi nhóm",
|
||||
"confirm": ""
|
||||
},
|
||||
"delete": {
|
||||
"label": "Xóa nhóm",
|
||||
"description": "",
|
||||
"confirm": "",
|
||||
"confirm": "Bạn chắc chắn muốn xóa nhóm {name}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Đã xóa nhóm {name} thành công"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
@@ -537,7 +537,7 @@
|
||||
},
|
||||
"defaultGroup": {
|
||||
"name": "Nhóm mặc định",
|
||||
"description": ""
|
||||
"description": "{name} - Tất cả những người dùng đã đăng nhập"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
@@ -546,7 +546,7 @@
|
||||
"list": {
|
||||
"title": "Ứng dụng",
|
||||
"noResults": {
|
||||
"title": "",
|
||||
"title": "Chưa có ứng dụng nào cả",
|
||||
"action": "Tạo ứng dụng của bạn trước"
|
||||
}
|
||||
},
|
||||
@@ -596,7 +596,7 @@
|
||||
"label": "Tên"
|
||||
},
|
||||
"description": {
|
||||
"label": "Thông tin chi tiết"
|
||||
"label": "Mô tả"
|
||||
},
|
||||
"url": {
|
||||
"label": "Url"
|
||||
@@ -612,14 +612,14 @@
|
||||
"select": {
|
||||
"label": "Chọn ứng dụng",
|
||||
"notFound": "Không tìm thấy ứng dụng",
|
||||
"search": "",
|
||||
"search": "Tìm một ứng dụng",
|
||||
"noResults": "Không có kết quả",
|
||||
"action": "Chọn {app}",
|
||||
"title": ""
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Tạo ứng dụng mới",
|
||||
"description": "Tạo ứng dụng mới",
|
||||
"action": ""
|
||||
},
|
||||
"add": ""
|
||||
@@ -887,7 +887,7 @@
|
||||
"message": ""
|
||||
},
|
||||
"invalidJson": {
|
||||
"title": "",
|
||||
"title": "JSON không hợp lệ",
|
||||
"message": ""
|
||||
},
|
||||
"wrongPath": {
|
||||
@@ -943,7 +943,7 @@
|
||||
},
|
||||
"topic": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"newLabel": "Chủ đề mới"
|
||||
},
|
||||
"opnsenseApiKey": {
|
||||
"label": "",
|
||||
@@ -1003,12 +1003,13 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "",
|
||||
"error": "Lỗi",
|
||||
"action": {
|
||||
"add": "Thêm",
|
||||
"apply": "Áp dụng",
|
||||
"backToOverview": "",
|
||||
"backToOverview": "Trở về mục tổng quan",
|
||||
"create": "Tạo nên",
|
||||
"createAnother": "",
|
||||
"edit": "Sửa",
|
||||
@@ -1082,7 +1083,7 @@
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"switchToDarkMode": "",
|
||||
"switchToLightMode": "",
|
||||
"switchToLightMode": "Đổi qua chế độ nền sáng",
|
||||
"management": "",
|
||||
"preferences": "Cá nhân hoá",
|
||||
"logout": "",
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -2846,7 +2856,7 @@
|
||||
"boards": "Bảng",
|
||||
"apps": "Ứng dụng",
|
||||
"integrations": "",
|
||||
"searchEngies": "",
|
||||
"searchEngies": "Công cụ tìm kiếm",
|
||||
"medias": "",
|
||||
"users": {
|
||||
"label": "Người dùng",
|
||||
@@ -2966,7 +2976,7 @@
|
||||
"mobile": ""
|
||||
}
|
||||
},
|
||||
"search": "",
|
||||
"search": "Tìm",
|
||||
"firstDayOfWeek": "Ngày đầu tiên trong tuần",
|
||||
"accessibility": "Trợ năng"
|
||||
}
|
||||
@@ -3067,7 +3077,7 @@
|
||||
},
|
||||
"members": {
|
||||
"title": "",
|
||||
"search": "",
|
||||
"search": "Tìm một thành viên",
|
||||
"notFound": ""
|
||||
},
|
||||
"permissions": {
|
||||
@@ -3148,9 +3158,9 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "",
|
||||
"title": "Tìm",
|
||||
"defaultSearchEngine": {
|
||||
"label": "",
|
||||
"label": "Công cụ tìm kiếm mặc định cho toàn hệ thống",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "Công cụ",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3826,7 +3895,7 @@
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "",
|
||||
"nothingFound": "",
|
||||
"nothingFound": "Không tìm được gì cả",
|
||||
"error": {
|
||||
"fetch": ""
|
||||
},
|
||||
|
||||
@@ -1003,6 +1003,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"beta": "測試版",
|
||||
"error": "錯誤",
|
||||
"action": {
|
||||
@@ -1270,6 +1271,15 @@
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": "啟用簡單的 Ping"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3260,14 +3270,70 @@
|
||||
"minutes": "",
|
||||
"hours": "",
|
||||
"midnight": "",
|
||||
"weeklyMonday": ""
|
||||
"weeklyMonday": "",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
},
|
||||
"interval": {
|
||||
"label": ""
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3776,6 +3842,9 @@
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
},
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/dates": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/dates": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"svgson": "^5.3.1"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^4.0.14",
|
||||
"zod": "^4.0.17",
|
||||
"zod-form-data": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -48,9 +48,9 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^8.2.4",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/charts": "^8.2.5",
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@mantine/hooks": "^8.2.5",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@tiptap/extension-color": "2.26.1",
|
||||
"@tiptap/extension-highlight": "2.26.1",
|
||||
@@ -70,15 +70,15 @@
|
||||
"@tiptap/starter-kit": "^2.26.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-form-zod-resolver": "^1.2.1",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.6",
|
||||
"next": "15.5.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.4",
|
||||
"video.js": "^8.23.4",
|
||||
"zod": "^4.0.14"
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { Select } from "@mantine/core";
|
||||
import { Group, Select } from "@mantine/core";
|
||||
import { IconCheck } from "@tabler/icons-react";
|
||||
|
||||
import { translateIfNecessary } from "@homarr/translation";
|
||||
import type { stringOrTranslation } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import type { TablerIcon } from "@homarr/ui";
|
||||
|
||||
import type { CommonWidgetInputProps } from "./common";
|
||||
import { useWidgetInputTranslation } from "./common";
|
||||
@@ -12,6 +14,7 @@ import { useFormContext } from "./form";
|
||||
|
||||
export type SelectOption =
|
||||
| {
|
||||
icon?: TablerIcon;
|
||||
value: string;
|
||||
label: stringOrTranslation;
|
||||
}
|
||||
@@ -23,10 +26,19 @@ export type inferSelectOptionValue<TOption extends SelectOption> = TOption exten
|
||||
? TValue
|
||||
: TOption;
|
||||
|
||||
const getIconFor = (options: SelectOption[], value: string) => {
|
||||
const current = options.find((option) => (typeof option === "string" ? option : option.value) === value);
|
||||
if (!current) return null;
|
||||
if (typeof current === "string") return null;
|
||||
return current.icon;
|
||||
};
|
||||
|
||||
export const WidgetSelectInput = ({ property, kind, options }: CommonWidgetInputProps<"select">) => {
|
||||
const t = useI18n();
|
||||
const tWidget = useWidgetInputTranslation(kind, property);
|
||||
const form = useFormContext();
|
||||
const inputProps = form.getInputProps(`options.${property}`);
|
||||
const CurrentIcon = getIconFor(options.options, inputProps.value as string);
|
||||
|
||||
return (
|
||||
<Select
|
||||
@@ -39,9 +51,29 @@ export const WidgetSelectInput = ({ property, kind, options }: CommonWidgetInput
|
||||
label: translateIfNecessary(t, option.label) ?? option.value,
|
||||
},
|
||||
)}
|
||||
leftSection={CurrentIcon && <CurrentIcon size={16} stroke={1.5} />}
|
||||
renderOption={({ option, checked }) => {
|
||||
const Icon = getIconFor(options.options, option.value);
|
||||
|
||||
return (
|
||||
<Group flex="1" gap="xs">
|
||||
{Icon && <Icon color="currentColor" opacity={0.6} size={18} stroke={1.5} />}
|
||||
{option.label}
|
||||
{checked && (
|
||||
<IconCheck
|
||||
style={{ marginInlineStart: "auto" }}
|
||||
color="currentColor"
|
||||
opacity={0.6}
|
||||
size={18}
|
||||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
description={options.withDescription ? tWidget("description") : undefined}
|
||||
searchable={options.searchable}
|
||||
{...form.getInputProps(`options.${property}`)}
|
||||
{...inputProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { Suspense } from "react";
|
||||
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||
import { Fragment, Suspense } from "react";
|
||||
import { Flex, rem, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||
import { IconLoader } from "@tabler/icons-react";
|
||||
import combineClasses from "clsx";
|
||||
|
||||
@@ -56,7 +56,8 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
[app, options.openInNewTab],
|
||||
);
|
||||
|
||||
const tinyText = height < 100 || width < 100;
|
||||
const isTiny = height < 100 || width < 100;
|
||||
const isColumnLayout = options.layout.startsWith("column");
|
||||
|
||||
return (
|
||||
<AppLink
|
||||
@@ -65,25 +66,55 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
enabled={Boolean(app.href) && !isEditMode}
|
||||
>
|
||||
<Tooltip.Floating
|
||||
label={app.description}
|
||||
label={app.description?.split("\n").map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
position="right-start"
|
||||
multiline
|
||||
disabled={!options.showDescriptionTooltip || !app.description}
|
||||
disabled={options.descriptionDisplayMode !== "tooltip" || !app.description || isEditMode}
|
||||
styles={{ tooltip: { maxWidth: 300 } }}
|
||||
>
|
||||
<Flex
|
||||
p={isTiny ? 4 : "sm"}
|
||||
className={combineClasses("app-flex-wrapper", app.name, app.id, app.href && classes.appWithUrl)}
|
||||
h="100%"
|
||||
w="100%"
|
||||
direction="column"
|
||||
direction={options.layout}
|
||||
justify="center"
|
||||
align="center"
|
||||
gap={isColumnLayout ? 0 : "sm"}
|
||||
>
|
||||
{options.showTitle && (
|
||||
<Text className="app-title" fw={700} size={tinyText ? "8px" : "sm"} ta="center">
|
||||
{app.name}
|
||||
</Text>
|
||||
)}
|
||||
<Stack gap={0}>
|
||||
{options.showTitle && (
|
||||
<Text
|
||||
className="app-title"
|
||||
fw={700}
|
||||
size={isTiny ? rem(8) : "sm"}
|
||||
ta={isColumnLayout ? "center" : undefined}
|
||||
>
|
||||
{app.name}
|
||||
</Text>
|
||||
)}
|
||||
{options.descriptionDisplayMode === "normal" && (
|
||||
<Text
|
||||
className="app-description"
|
||||
size={isTiny ? rem(8) : "sm"}
|
||||
ta={isColumnLayout ? "center" : undefined}
|
||||
c="dimmed"
|
||||
lineClamp={4}
|
||||
>
|
||||
{app.description?.split("\n").map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
<MaskedOrNormalImage
|
||||
imageUrl={app.iconUrl}
|
||||
hasColor={board.iconColor !== null}
|
||||
@@ -92,6 +123,8 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
minWidth: "20%",
|
||||
maxWidth: isColumnLayout ? undefined : "50%",
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { IconApps, IconDeviceDesktopX } from "@tabler/icons-react";
|
||||
import {
|
||||
IconApps,
|
||||
IconDeviceDesktopX,
|
||||
IconEyeOff,
|
||||
IconLayoutBottombarExpand,
|
||||
IconLayoutNavbarExpand,
|
||||
IconLayoutSidebarLeftExpand,
|
||||
IconLayoutSidebarRightExpand,
|
||||
IconTextScan2,
|
||||
IconTooltip,
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
@@ -11,7 +21,68 @@ export const { definition, componentLoader } = createWidgetDefinition("app", {
|
||||
appId: factory.app(),
|
||||
openInNewTab: factory.switch({ defaultValue: true }),
|
||||
showTitle: factory.switch({ defaultValue: true }),
|
||||
showDescriptionTooltip: factory.switch({ defaultValue: false }),
|
||||
descriptionDisplayMode: factory.select({
|
||||
options: [
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.descriptionDisplayMode.option.normal");
|
||||
},
|
||||
value: "normal",
|
||||
icon: IconTextScan2,
|
||||
},
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.descriptionDisplayMode.option.tooltip");
|
||||
},
|
||||
value: "tooltip",
|
||||
icon: IconTooltip,
|
||||
},
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.descriptionDisplayMode.option.hidden");
|
||||
},
|
||||
value: "hidden",
|
||||
icon: IconEyeOff,
|
||||
},
|
||||
],
|
||||
defaultValue: "hidden",
|
||||
searchable: true,
|
||||
withDescription: true,
|
||||
}),
|
||||
layout: factory.select({
|
||||
options: [
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.layout.option.column");
|
||||
},
|
||||
value: "column",
|
||||
icon: IconLayoutNavbarExpand,
|
||||
},
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.layout.option.column-reverse");
|
||||
},
|
||||
value: "column-reverse",
|
||||
icon: IconLayoutBottombarExpand,
|
||||
},
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.layout.option.row");
|
||||
},
|
||||
value: "row",
|
||||
icon: IconLayoutSidebarLeftExpand,
|
||||
},
|
||||
{
|
||||
label(t) {
|
||||
return t("widget.app.option.layout.option.row-reverse");
|
||||
},
|
||||
value: "row-reverse",
|
||||
icon: IconLayoutSidebarRightExpand,
|
||||
},
|
||||
],
|
||||
defaultValue: "column",
|
||||
searchable: true,
|
||||
}),
|
||||
pingEnabled: factory.switch({ defaultValue: settings.enableStatusByDefault }),
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ export const CommonChart = ({
|
||||
pos={"relative"}
|
||||
style={{ overflow: "visible" }}
|
||||
p={0}
|
||||
bg={data.length <= 1 ? backgroundColor : undefined}
|
||||
bg={backgroundColor}
|
||||
radius={board.itemRadius}
|
||||
>
|
||||
{data.length > 1 && height > 40 && !hovered && (
|
||||
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user