chore(release): automatic release v1.38.0
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -33,6 +33,7 @@ body:
|
|||||||
options:
|
options:
|
||||||
# The below comment is used to insert a new version with on-release.yml
|
# The below comment is used to insert a new version with on-release.yml
|
||||||
#NEXT_VERSION#
|
#NEXT_VERSION#
|
||||||
|
- 1.37.0
|
||||||
- 1.36.1
|
- 1.36.1
|
||||||
- 1.36.0
|
- 1.36.0
|
||||||
- 1.35.1
|
- 1.35.1
|
||||||
|
|||||||
10
.github/workflows/automatic-approval.yml
vendored
10
.github/workflows/automatic-approval.yml
vendored
@@ -3,6 +3,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
approve-automatic-prs:
|
approve-automatic-prs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -12,10 +14,12 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||||
|
permission-pull-requests: write # required to approve pull request
|
||||||
|
|
||||||
- name: Install GitHub CLI
|
- name: Install GitHub CLI
|
||||||
run: sudo apt-get install -y gh
|
run: sudo apt-get install -y gh
|
||||||
- name: Approve automatic PRs
|
- name: Approve automatic PRs
|
||||||
|
|||||||
11
.github/workflows/crowdin-schedule-download.yml
vendored
11
.github/workflows/crowdin-schedule-download.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *" # every day at midnight
|
- cron: "0 0 * * *" # every day at midnight
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # required for code checkout
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
download-crowdin-translations:
|
download-crowdin-translations:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -15,10 +18,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.CROWDIN_APP_ID }}
|
app-id: ${{ secrets.CROWDIN_APP_ID }}
|
||||||
|
permission-contents: write # required to commit to crowdin branch
|
||||||
|
permission-pull-requests: write # required to create pull request
|
||||||
|
|
||||||
- name: Download Crowdin translations
|
- name: Download Crowdin translations
|
||||||
id: crowdin-download
|
id: crowdin-download
|
||||||
|
|||||||
13
.github/workflows/deployment-docker-image.yml
vendored
13
.github/workflows/deployment-docker-image.yml
vendored
@@ -49,18 +49,23 @@ jobs:
|
|||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||||
|
permission-contents: write # required to commit package.json & changelog changes, merge them to dev and publish the release
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
if: env.SKIP_RELEASE == 'false'
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
with:
|
with:
|
||||||
node-version: 22.19.0
|
node-version: 22.19.0
|
||||||
|
cache: "pnpm"
|
||||||
- run: npm i -g pnpm
|
- run: npm i -g pnpm
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
15
.github/workflows/deployment-weekly-release.yml
vendored
15
.github/workflows/deployment-weekly-release.yml
vendored
@@ -49,10 +49,11 @@ jobs:
|
|||||||
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
|
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainApprovalToken
|
id: obtainApprovalToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||||
|
permission-pull-requests: write
|
||||||
- name: Approve PR
|
- name: Approve PR
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||||
@@ -60,10 +61,12 @@ jobs:
|
|||||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainMergeToken
|
id: obtainMergeToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||||
|
permission-contents: write # write to main branch (due to merge)
|
||||||
|
permission-pull-requests: write # merge pull request
|
||||||
- id: automerge
|
- id: automerge
|
||||||
if: ${{ steps.semver.outputs.bump != 'major' }}
|
if: ${{ steps.semver.outputs.bump != 'major' }}
|
||||||
name: automerge
|
name: automerge
|
||||||
|
|||||||
21
.github/workflows/on-release.yml
vendored
21
.github/workflows/on-release.yml
vendored
@@ -11,12 +11,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||||
app_id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||||
installation_retrieval_mode: repository
|
owner: homarr-labs
|
||||||
installation_retrieval_payload: homarr-labs/documentation
|
repositories: |
|
||||||
|
documentation
|
||||||
|
permission-contents: write # required to dispatch repository workflow
|
||||||
- name: Trigger documentation release
|
- name: Trigger documentation release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
@@ -40,10 +42,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||||
|
permission-contents: write # required to commit to branch
|
||||||
|
permission-pull-requests: write # required to create pr & enable automerge
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -57,6 +61,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||||
git config --global user.name "Releases Homarr"
|
git config --global user.name "Releases Homarr"
|
||||||
|
git checkout -b update-bug-report-template
|
||||||
git add .
|
git add .
|
||||||
git commit -m "chore: update bug report template"
|
git commit -m "chore: update bug report template"
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ permissions:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore: "renovate/*"
|
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
skip-stability-days:
|
skip-stability-days:
|
||||||
|
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
|
||||||
name: Skip Stability Days
|
name: Skip Stability Days
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
15
.github/workflows/update-contributors.yml
vendored
15
.github/workflows/update-contributors.yml
vendored
@@ -9,9 +9,6 @@ env:
|
|||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
|
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-contributors:
|
update-contributors:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -21,20 +18,24 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||||
|
permission-contents: write # required to commit to branch
|
||||||
|
permission-pull-requests: write # required to create pr & enable automerge
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Run update script
|
- name: Run update script
|
||||||
run: node ./scripts/update-contributors.mjs
|
run: node ./scripts/update-contributors.mjs
|
||||||
|
|||||||
10
.github/workflows/update-integration-list.yml
vendored
10
.github/workflows/update-integration-list.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: Update integration list
|
name: Update integration list
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { }
|
workflow_dispatch: {}
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- packages/definitions/src/integration.ts
|
- packages/definitions/src/integration.ts
|
||||||
@@ -20,10 +20,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||||
|
permission-contents: write # required to commit to branch
|
||||||
|
permission-pull-requests: write # required to create pr & enable automerge
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -50,17 +50,17 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/colors-generator": "^8.2.8",
|
"@mantine/colors-generator": "^8.3.1",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/dropzone": "^8.2.8",
|
"@mantine/dropzone": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@mantine/modals": "^8.2.8",
|
"@mantine/modals": "^8.3.1",
|
||||||
"@mantine/tiptap": "^8.2.8",
|
"@mantine/tiptap": "^8.3.1",
|
||||||
"@million/lint": "1.0.14",
|
"@million/lint": "1.0.14",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"@tanstack/react-query": "^5.87.1",
|
"@tanstack/react-query": "^5.87.4",
|
||||||
"@tanstack/react-query-devtools": "^5.87.1",
|
"@tanstack/react-query-devtools": "^5.87.4",
|
||||||
"@tanstack/react-query-next-experimental": "^5.87.1",
|
"@tanstack/react-query-next-experimental": "^5.87.4",
|
||||||
"@trpc/client": "^11.5.1",
|
"@trpc/client": "^11.5.1",
|
||||||
"@trpc/next": "^11.5.1",
|
"@trpc/next": "^11.5.1",
|
||||||
"@trpc/react-query": "^11.5.1",
|
"@trpc/react-query": "^11.5.1",
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"flag-icons": "^7.5.0",
|
"flag-icons": "^7.5.0",
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"jotai": "^2.13.1",
|
"jotai": "^2.14.0",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
@@ -85,18 +85,18 @@
|
|||||||
"react-simple-code-editor": "^0.14.1",
|
"react-simple-code-editor": "^0.14.1",
|
||||||
"sass": "^1.92.1",
|
"sass": "^1.92.1",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"swagger-ui-react": "^5.28.1",
|
"swagger-ui-react": "^5.29.0",
|
||||||
"use-deep-compare-effect": "^1.8.1",
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/chroma-js": "3.1.1",
|
"@types/chroma-js": "3.1.1",
|
||||||
"@types/node": "^22.18.1",
|
"@types/node": "^22.18.3",
|
||||||
"@types/prismjs": "^1.26.5",
|
"@types/prismjs": "^1.26.5",
|
||||||
"@types/react": "19.1.12",
|
"@types/react": "19.1.13",
|
||||||
"@types/react-dom": "19.1.9",
|
"@types/react-dom": "19.1.9",
|
||||||
"@types/swagger-ui-react": "^5.18.0",
|
"@types/swagger-ui-react": "^5.18.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
IconCode,
|
IconCode,
|
||||||
IconGrid3x3,
|
IconGrid3x3,
|
||||||
IconKey,
|
IconKey,
|
||||||
|
IconLink,
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconPassword,
|
IconPassword,
|
||||||
IconPasswordUser,
|
IconPasswordUser,
|
||||||
@@ -21,6 +22,7 @@ export const integrationSecretIcons = {
|
|||||||
tokenId: IconGrid3x3,
|
tokenId: IconGrid3x3,
|
||||||
personalAccessToken: IconPasswordUser,
|
personalAccessToken: IconPasswordUser,
|
||||||
topic: IconMessage,
|
topic: IconMessage,
|
||||||
|
url: IconLink,
|
||||||
opnsenseApiKey: IconKey,
|
opnsenseApiKey: IconKey,
|
||||||
opnsenseApiSecret: IconPassword,
|
opnsenseApiSecret: IconPassword,
|
||||||
githubAppId: IconCode,
|
githubAppId: IconCode,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
|||||||
integration.secrets.every((secret) => secretKinds.includes(secret.kind)),
|
integration.secrets.every((secret) => secretKinds.includes(secret.kind)),
|
||||||
) ?? getDefaultSecretKinds(integration.kind);
|
) ?? getDefaultSecretKinds(integration.kind);
|
||||||
|
|
||||||
|
const hasUrlSecret = secretsKinds.includes("url");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
|
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -50,10 +52,14 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
|||||||
const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret]));
|
const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret]));
|
||||||
|
|
||||||
const handleSubmitAsync = async (values: FormType) => {
|
const handleSubmitAsync = async (values: FormType) => {
|
||||||
|
const url = hasUrlSecret
|
||||||
|
? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin
|
||||||
|
: values.url;
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
id: integration.id,
|
id: integration.id,
|
||||||
...values,
|
...values,
|
||||||
|
url,
|
||||||
secrets: values.secrets.map((secret) => ({
|
secrets: values.secrets.map((secret) => ({
|
||||||
kind: secret.kind,
|
kind: secret.kind,
|
||||||
value: secret.value === "" ? null : secret.value,
|
value: secret.value === "" ? null : secret.value,
|
||||||
@@ -92,7 +98,9 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<TextInput withAsterisk label={t("integration.field.name.label")} {...form.getInputProps("name")} />
|
<TextInput withAsterisk label={t("integration.field.name.label")} {...form.getInputProps("name")} />
|
||||||
|
|
||||||
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
|
{hasUrlSecret ? null : (
|
||||||
|
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Fieldset legend={t("integration.secrets.title")}>
|
<Fieldset legend={t("integration.secrets.title")}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
|||||||
@@ -55,12 +55,19 @@ const formSchema = integrationCreateSchema.omit({ kind: true }).and(
|
|||||||
export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => {
|
export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const secretKinds = getAllSecretKindOptions(searchParams.kind);
|
const secretKinds = getAllSecretKindOptions(searchParams.kind);
|
||||||
|
const hasUrlSecret = secretKinds.some((kinds) => kinds.includes("url"));
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
let url = searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "";
|
||||||
|
if (hasUrlSecret) {
|
||||||
|
// Placeholder Url, replaced with origin of the secret Url on submit
|
||||||
|
url = "http://localhost";
|
||||||
|
}
|
||||||
const form = useZodForm(formSchema, {
|
const form = useZodForm(formSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: searchParams.name ?? getIntegrationName(searchParams.kind),
|
name: searchParams.name ?? getIntegrationName(searchParams.kind),
|
||||||
url: searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "",
|
url,
|
||||||
secrets: secretKinds[0].map((kind) => ({
|
secrets: secretKinds[0].map((kind) => ({
|
||||||
kind,
|
kind,
|
||||||
value: "",
|
value: "",
|
||||||
@@ -83,10 +90,14 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
|||||||
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
|
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
|
||||||
|
|
||||||
const handleSubmitAsync = async (values: FormType) => {
|
const handleSubmitAsync = async (values: FormType) => {
|
||||||
|
const url = hasUrlSecret
|
||||||
|
? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin
|
||||||
|
: values.url;
|
||||||
await createIntegrationAsync(
|
await createIntegrationAsync(
|
||||||
{
|
{
|
||||||
kind: searchParams.kind,
|
kind: searchParams.kind,
|
||||||
...values,
|
...values,
|
||||||
|
url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
async onSuccess(data) {
|
async onSuccess(data) {
|
||||||
@@ -114,10 +125,10 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
|||||||
await createAppAsync(
|
await createAppAsync(
|
||||||
{
|
{
|
||||||
name: values.name,
|
name: values.name,
|
||||||
href: hasCustomHref ? values.appHref : values.url,
|
href: hasCustomHref ? values.appHref : url,
|
||||||
iconUrl: getIconUrl(searchParams.kind),
|
iconUrl: getIconUrl(searchParams.kind),
|
||||||
description: null,
|
description: null,
|
||||||
pingUrl: values.url,
|
pingUrl: url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
async onSettled() {
|
async onSettled() {
|
||||||
@@ -149,7 +160,9 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
|||||||
<Stack>
|
<Stack>
|
||||||
<TextInput withAsterisk label={t("integration.field.name.label")} autoFocus {...form.getInputProps("name")} />
|
<TextInput withAsterisk label={t("integration.field.name.label")} autoFocus {...form.getInputProps("name")} />
|
||||||
|
|
||||||
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
|
{hasUrlSecret ? null : (
|
||||||
|
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Fieldset legend={t("integration.secrets.title")}>
|
<Fieldset legend={t("integration.secrets.title")}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
|||||||
import { IconPowerOff } from "@homarr/ui/icons";
|
import { IconPowerOff } from "@homarr/ui/icons";
|
||||||
|
|
||||||
const cronExpressions = [
|
const cronExpressions = [
|
||||||
|
{
|
||||||
|
value: "*/1 * * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 1 }),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: "*/5 * * * * *",
|
value: "*/5 * * * * *",
|
||||||
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
|
||||||
|
|||||||
@@ -41,13 +41,13 @@
|
|||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"fastify": "^5.6.0",
|
"fastify": "^5.6.0",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"undici": "7.15.0"
|
"undici": "7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "^22.18.1",
|
"@types/node": "^22.18.3",
|
||||||
"dotenv-cli": "^10.0.0",
|
"dotenv-cli": "^10.0.0",
|
||||||
"esbuild": "^0.25.9",
|
"esbuild": "^0.25.9",
|
||||||
"eslint": "^9.35.0",
|
"eslint": "^9.35.0",
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 🖌️ Highly customizable with an extensive drag and drop grid system
|
- 🖌️ Highly customizable with an extensive drag and drop grid system
|
||||||
@@ -132,6 +134,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
<a href="https://homarr.dev/docs/integrations/ical" target="_blank" rel="noreferrer noopener">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg" alt="iCal" width="90" height="90" />
|
||||||
|
<br/>
|
||||||
|
<p align="center">iCal</p>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -144,15 +153,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Jellyseerr</p>
|
<p align="center">Jellyseerr</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/lidarr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/lidarr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg" alt="Lidarr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg" alt="Lidarr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">Lidarr</p>
|
<p align="center">Lidarr</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/linux-server-io" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/linux-server-io" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg" alt="LinuxServer.io" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg" alt="LinuxServer.io" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -193,15 +202,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">OpenMediaVault</p>
|
<p align="center">OpenMediaVault</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/opnsense" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/opnsense" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg" alt="OPNsense" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg" alt="OPNsense" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">OPNsense</p>
|
<p align="center">OPNsense</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/overseerr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/overseerr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg" alt="Overseerr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg" alt="Overseerr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -242,15 +251,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">qBittorrent</p>
|
<p align="center">qBittorrent</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/quay" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/quay" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png" alt="Quay" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png" alt="Quay" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">Quay</p>
|
<p align="center">Quay</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/radarr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/radarr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg" alt="Radarr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg" alt="Radarr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -291,15 +300,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Transmission</p>
|
<p align="center">Transmission</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/truenas" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/truenas" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg" alt="TrueNAS" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg" alt="TrueNAS" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">TrueNAS</p>
|
<p align="center">TrueNAS</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png" alt="Unifi Controller" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png" alt="Unifi Controller" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
BIN
docs/img/screenshot.png
Normal file
BIN
docs/img/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
13
package.json
13
package.json
@@ -39,7 +39,7 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"@semantic-release/github": "^11.0.5",
|
"@semantic-release/github": "^11.0.6",
|
||||||
"@semantic-release/npm": "^12.0.2",
|
"@semantic-release/npm": "^12.0.2",
|
||||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||||
"@testcontainers/redis": "^11.5.1",
|
"@testcontainers/redis": "^11.5.1",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||||
"cross-env": "^10.0.0",
|
"cross-env": "^10.0.0",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^27.0.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"semantic-release": "^24.2.8",
|
"semantic-release": "^24.2.8",
|
||||||
"testcontainers": "^11.5.1",
|
"testcontainers": "^11.5.1",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.15.1",
|
"packageManager": "pnpm@10.16.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.19.0"
|
"node": ">=22.19.0"
|
||||||
},
|
},
|
||||||
@@ -77,16 +77,17 @@
|
|||||||
"overrides": {
|
"overrides": {
|
||||||
"@babel/helpers@<7.26.10": ">=7.28.4",
|
"@babel/helpers@<7.26.10": ">=7.28.4",
|
||||||
"@babel/runtime@<7.26.10": ">=7.28.4",
|
"@babel/runtime@<7.26.10": ">=7.28.4",
|
||||||
"axios@>=1.0.0 <1.8.2": ">=1.12.1",
|
"axios@>=1.0.0 <1.8.2": ">=1.12.2",
|
||||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
|
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
|
||||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||||
"esbuild@<=0.24.2": ">=0.25.9",
|
"esbuild@<=0.24.2": ">=0.25.9",
|
||||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
||||||
"hono@<4.6.5": ">=4.9.6",
|
"hono@<4.6.5": ">=4.9.7",
|
||||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.5",
|
"nanoid@>=4.0.0 <5.0.9": ">=5.1.5",
|
||||||
"prismjs@<1.30.0": ">=1.30.0",
|
"prismjs@<1.30.0": ">=1.30.0",
|
||||||
"proxmox-api>undici": "7.15.0",
|
"proxmox-api>undici": "7.16.0",
|
||||||
|
"react-is": "^19.1.1",
|
||||||
"rollup@>=4.0.0 <4.22.4": ">=4.50.1",
|
"rollup@>=4.0.0 <4.22.4": ">=4.50.1",
|
||||||
"sha.js@<=2.4.11": ">=2.4.12",
|
"sha.js@<=2.4.11": ">=2.4.12",
|
||||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.0",
|
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.0",
|
||||||
|
|||||||
@@ -42,18 +42,18 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@kubernetes/client-node": "^1.3.0",
|
"@kubernetes/client-node": "^1.3.0",
|
||||||
"@tanstack/react-query": "^5.87.1",
|
"@tanstack/react-query": "^5.87.4",
|
||||||
"@trpc/client": "^11.5.1",
|
"@trpc/client": "^11.5.1",
|
||||||
"@trpc/react-query": "^11.5.1",
|
"@trpc/react-query": "^11.5.1",
|
||||||
"@trpc/server": "^11.5.1",
|
"@trpc/server": "^11.5.1",
|
||||||
"@trpc/tanstack-react-query": "^11.5.1",
|
"@trpc/tanstack-react-query": "^11.5.1",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"trpc-to-openapi": "^3.0.1",
|
"trpc-to-openapi": "^3.0.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"ldapts": "8.0.9",
|
"ldapts": "8.0.9",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"next-auth": "5.0.0-beta.29",
|
"next-auth": "5.0.0-beta.29",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"undici": "7.15.0"
|
"undici": "7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"dns-caching": "^0.2.5",
|
"dns-caching": "^0.2.5",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"octokit": "^5.0.3",
|
"octokit": "^5.0.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"undici": "7.15.0",
|
"undici": "7.16.0",
|
||||||
"zod": "^4.1.5",
|
"zod": "^4.1.8",
|
||||||
"zod-validation-error": "^4.0.1"
|
"zod-validation-error": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ export class LoggingAgent extends Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
|
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
|
||||||
const url = new URL(`${options.origin as string}${options.path}`);
|
const path = options.path
|
||||||
|
.split("/")
|
||||||
|
.map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment))
|
||||||
|
.join("/");
|
||||||
|
const url = new URL(`${options.origin as string}${path}`);
|
||||||
|
|
||||||
// The below code should prevent sensitive data from being logged as
|
// The below code should prevent sensitive data from being logged as
|
||||||
// some integrations use query parameters for auth
|
// some integrations use query parameters for auth
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ describe("LoggingAgent should log all requests", () => {
|
|||||||
["/?one=a1&two=b2&three=c3", `/?one=${REDACTED}&two=${REDACTED}&three=${REDACTED}`],
|
["/?one=a1&two=b2&three=c3", `/?one=${REDACTED}&two=${REDACTED}&three=${REDACTED}`],
|
||||||
["/?numberWith13Chars=1234567890123", `/?numberWith13Chars=${REDACTED}`],
|
["/?numberWith13Chars=1234567890123", `/?numberWith13Chars=${REDACTED}`],
|
||||||
[`/?stringWith13Chars=${"a".repeat(13)}`, `/?stringWith13Chars=${REDACTED}`],
|
[`/?stringWith13Chars=${"a".repeat(13)}`, `/?stringWith13Chars=${REDACTED}`],
|
||||||
|
[`/${"a".repeat(32)}/?param=123`, `/${REDACTED}/?param=123`],
|
||||||
])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => {
|
])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const infoLogSpy = vi.spyOn(logger, "debug");
|
const infoLogSpy = vi.spyOn(logger, "debug");
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.8",
|
||||||
"ioredis": "5.7.0",
|
"ioredis": "5.7.0",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const createRedisClient = () =>
|
|||||||
...defaultRedisOptions,
|
...defaultRedisOptions,
|
||||||
host: redisEnv.HOST,
|
host: redisEnv.HOST,
|
||||||
port: redisEnv.PORT,
|
port: redisEnv.PORT,
|
||||||
|
db: redisEnv.DATABASE_INDEX,
|
||||||
tls: redisEnv.TLS_CA
|
tls: redisEnv.TLS_CA
|
||||||
? {
|
? {
|
||||||
ca: redisEnv.TLS_CA,
|
ca: redisEnv.TLS_CA,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const redisEnv = createEnv({
|
|||||||
TLS_CA: z.string().optional(),
|
TLS_CA: z.string().optional(),
|
||||||
USERNAME: z.string().optional(),
|
USERNAME: z.string().optional(),
|
||||||
PASSWORD: z.string().optional(),
|
PASSWORD: z.string().optional(),
|
||||||
|
DATABASE_INDEX: z.coerce.number().optional(),
|
||||||
},
|
},
|
||||||
runtimeEnv: runtimeEnvWithPrefix("REDIS_"),
|
runtimeEnv: runtimeEnvWithPrefix("REDIS_"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,20 +29,20 @@
|
|||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@tanstack/react-query": "^5.87.1",
|
"@tanstack/react-query": "^5.87.4",
|
||||||
"@trpc/client": "^11.5.1",
|
"@trpc/client": "^11.5.1",
|
||||||
"@trpc/server": "^11.5.1",
|
"@trpc/server": "^11.5.1",
|
||||||
"@trpc/tanstack-react-query": "^11.5.1",
|
"@trpc/tanstack-react-query": "^11.5.1",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/react": "19.1.12",
|
"@types/react": "19.1.13",
|
||||||
"eslint": "^9.35.0",
|
"eslint": "^9.35.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@testcontainers/mysql": "^11.5.1",
|
"@testcontainers/mysql": "^11.5.1",
|
||||||
"@testcontainers/postgresql": "^11.5.1",
|
"@testcontainers/postgresql": "^11.5.1",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"drizzle-kit": "^0.31.4",
|
"drizzle-kit": "^0.31.4",
|
||||||
"drizzle-orm": "^0.44.5",
|
"drizzle-orm": "^0.44.5",
|
||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "^0.8.3",
|
||||||
"mysql2": "3.14.4",
|
"mysql2": "3.14.5",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"superjson": "2.2.2"
|
"superjson": "2.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"fast-xml-parser": "^5.2.5",
|
"fast-xml-parser": "^5.2.5",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/integrations/sonarr"
|
| "/docs/integrations/sonarr"
|
||||||
| "/docs/integrations/tdarr"
|
| "/docs/integrations/tdarr"
|
||||||
| "/docs/integrations/transmission"
|
| "/docs/integrations/transmission"
|
||||||
|
| "/docs/integrations/truenas"
|
||||||
| "/docs/integrations/unifi-controller"
|
| "/docs/integrations/unifi-controller"
|
||||||
| "/docs/management/api"
|
| "/docs/management/api"
|
||||||
| "/docs/management/apps"
|
| "/docs/management/apps"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const integrationSecretKindObject = {
|
|||||||
topic: { isPublic: true, multiline: false },
|
topic: { isPublic: true, multiline: false },
|
||||||
opnsenseApiKey: { isPublic: false, multiline: false },
|
opnsenseApiKey: { isPublic: false, multiline: false },
|
||||||
opnsenseApiSecret: { isPublic: false, multiline: false },
|
opnsenseApiSecret: { isPublic: false, multiline: false },
|
||||||
|
url: { isPublic: false, multiline: false },
|
||||||
privateKey: { isPublic: false, multiline: true },
|
privateKey: { isPublic: false, multiline: true },
|
||||||
githubAppId: { isPublic: true, multiline: false },
|
githubAppId: { isPublic: true, multiline: false },
|
||||||
githubInstallationId: { isPublic: true, multiline: false },
|
githubInstallationId: { isPublic: true, multiline: false },
|
||||||
@@ -283,6 +284,13 @@ export const integrationDefs = {
|
|||||||
category: ["notifications"],
|
category: ["notifications"],
|
||||||
documentationUrl: createDocumentationLink("/docs/integrations/ntfy"),
|
documentationUrl: createDocumentationLink("/docs/integrations/ntfy"),
|
||||||
},
|
},
|
||||||
|
ical: {
|
||||||
|
name: "iCal",
|
||||||
|
secretKinds: [["url"]],
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg",
|
||||||
|
category: ["calendar"],
|
||||||
|
documentationUrl: createDocumentationLink("/docs/integrations/ical"),
|
||||||
|
},
|
||||||
truenas: {
|
truenas: {
|
||||||
name: "TrueNAS",
|
name: "TrueNAS",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"dockerode": "^4.0.7"
|
"dockerode": "^4.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/form": "^8.2.8",
|
"@mantine/form": "^8.3.1",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"@homarr/notifications": "workspace:^0.1.0",
|
"@homarr/notifications": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -41,14 +41,15 @@
|
|||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@jellyfin/sdk": "^0.11.0",
|
"@jellyfin/sdk": "^0.11.0",
|
||||||
"@octokit/auth-app": "^8.1.0",
|
"@octokit/auth-app": "^8.1.0",
|
||||||
|
"ical.js": "^2.2.1",
|
||||||
"maria2": "^0.4.1",
|
"maria2": "^0.4.1",
|
||||||
"node-ical": "^0.20.1",
|
"node-ical": "^0.20.1",
|
||||||
"octokit": "^5.0.3",
|
"octokit": "^5.0.3",
|
||||||
"proxmox-api": "1.1.1",
|
"proxmox-api": "1.1.1",
|
||||||
"tsdav": "^2.1.5",
|
"tsdav": "^2.1.5",
|
||||||
"undici": "7.15.0",
|
"undici": "7.16.0",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { GitHubContainerRegistryIntegration } from "../github-container-registry
|
|||||||
import { GithubIntegration } from "../github/github-integration";
|
import { GithubIntegration } from "../github/github-integration";
|
||||||
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
||||||
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
||||||
|
import { ICalIntegration } from "../ical/ical-integration";
|
||||||
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
||||||
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
|
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
|
||||||
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
|
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
|
||||||
@@ -112,6 +113,7 @@ export const integrationCreators = {
|
|||||||
codeberg: CodebergIntegration,
|
codeberg: CodebergIntegration,
|
||||||
linuxServerIO: LinuxServerIOIntegration,
|
linuxServerIO: LinuxServerIOIntegration,
|
||||||
gitHubContainerRegistry: GitHubContainerRegistryIntegration,
|
gitHubContainerRegistry: GitHubContainerRegistryIntegration,
|
||||||
|
ical: ICalIntegration,
|
||||||
quay: QuayIntegration,
|
quay: QuayIntegration,
|
||||||
ntfy: NTFYIntegration,
|
ntfy: NTFYIntegration,
|
||||||
mock: MockIntegration,
|
mock: MockIntegration,
|
||||||
|
|||||||
67
packages/integrations/src/ical/ical-integration.ts
Normal file
67
packages/integrations/src/ical/ical-integration.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import ICAL from "ical.js";
|
||||||
|
|
||||||
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||||
|
|
||||||
|
import type { IntegrationTestingInput } from "../base/integration";
|
||||||
|
import { Integration } from "../base/integration";
|
||||||
|
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||||
|
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||||
|
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
|
||||||
|
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
||||||
|
|
||||||
|
export class ICalIntegration extends Integration implements ICalendarIntegration {
|
||||||
|
async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||||
|
const response = await fetchWithTrustedCertificatesAsync(super.getSecretValue("url"));
|
||||||
|
const result = await response.text();
|
||||||
|
const jcal = ICAL.parse(result) as unknown[];
|
||||||
|
const comp = new ICAL.Component(jcal);
|
||||||
|
|
||||||
|
return comp.getAllSubcomponents("vevent").reduce((prev, vevent) => {
|
||||||
|
const event = new ICAL.Event(vevent);
|
||||||
|
const startDate = event.startDate.toJSDate();
|
||||||
|
const endDate = event.endDate.toJSDate();
|
||||||
|
|
||||||
|
if (startDate > end) return prev;
|
||||||
|
if (endDate < start) return prev;
|
||||||
|
|
||||||
|
return prev.concat({
|
||||||
|
title: event.summary,
|
||||||
|
subTitle: null,
|
||||||
|
description: event.description,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
image: null,
|
||||||
|
location: event.location,
|
||||||
|
indicatorColor: "red",
|
||||||
|
links: [],
|
||||||
|
});
|
||||||
|
}, [] as CalendarEvent[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||||
|
const response = await input.fetchAsync(super.getSecretValue("url"));
|
||||||
|
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||||
|
|
||||||
|
const result = await response.text();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const jcal = ICAL.parse(result);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
const comp = new ICAL.Component(jcal);
|
||||||
|
return comp.getAllSubcomponents("vevent").length > 0
|
||||||
|
? { success: true }
|
||||||
|
: TestConnectionError.ParseResult({
|
||||||
|
name: "Calendar parse error",
|
||||||
|
message: "No events found",
|
||||||
|
cause: new Error("No events found"),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return TestConnectionError.ParseResult({
|
||||||
|
name: "Calendar parse error",
|
||||||
|
message: "Failed to parse calendar",
|
||||||
|
cause: error as Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ export { PlexIntegration } from "./plex/plex-integration";
|
|||||||
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
|
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
|
||||||
export { TrueNasIntegration } from "./truenas/truenas-integration";
|
export { TrueNasIntegration } from "./truenas/truenas-integration";
|
||||||
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
|
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
|
||||||
|
export { ICalIntegration } from "./ical/ical-integration";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type { IntegrationInput } from "./base/integration";
|
export type { IntegrationInput } from "./base/integration";
|
||||||
|
|||||||
@@ -1,24 +1,41 @@
|
|||||||
export const radarrReleaseTypes = ["inCinemas", "digitalRelease", "physicalRelease"] as const;
|
export const radarrReleaseTypes = ["inCinemas", "digitalRelease", "physicalRelease"] as const;
|
||||||
export type RadarrReleaseType = (typeof radarrReleaseTypes)[number];
|
export type RadarrReleaseType = (typeof radarrReleaseTypes)[number];
|
||||||
|
|
||||||
export interface CalendarEvent {
|
export interface RadarrMetadata {
|
||||||
name: string;
|
type: "radarr";
|
||||||
subName: string;
|
releaseType: RadarrReleaseType;
|
||||||
date: Date;
|
}
|
||||||
dates?: { type: RadarrReleaseType; date: Date }[];
|
|
||||||
description?: string;
|
export type CalendarMetadata = RadarrMetadata;
|
||||||
thumbnail?: string;
|
|
||||||
mediaInformation?: {
|
export interface CalendarLink {
|
||||||
type: "audio" | "video" | "tv" | "movie";
|
name: string;
|
||||||
seasonNumber?: number;
|
isDark: boolean;
|
||||||
episodeNumber?: number;
|
href: string;
|
||||||
};
|
color?: string;
|
||||||
links: {
|
logo?: string;
|
||||||
href: string;
|
}
|
||||||
name: string;
|
|
||||||
color: string | undefined;
|
export interface CalendarImageBadge {
|
||||||
notificationColor?: string | undefined;
|
content: string;
|
||||||
isDark: boolean | undefined;
|
color: string;
|
||||||
logo: string;
|
}
|
||||||
}[];
|
|
||||||
|
export interface CalendarImage {
|
||||||
|
src: string;
|
||||||
|
badge?: CalendarImageBadge;
|
||||||
|
aspectRatio?: { width: number; height: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarEvent {
|
||||||
|
title: string;
|
||||||
|
subTitle: string | null;
|
||||||
|
description: string | null;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date | null;
|
||||||
|
image: CalendarImage | null;
|
||||||
|
location: string | null;
|
||||||
|
metadata?: CalendarMetadata;
|
||||||
|
indicatorColor: string;
|
||||||
|
links: CalendarLink[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
|
|||||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||||
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
||||||
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||||
|
|
||||||
export class LidarrIntegration extends Integration implements ICalendarIntegration {
|
export class LidarrIntegration extends Integration implements ICalendarIntegration {
|
||||||
@@ -44,22 +44,28 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
const lidarrCalendarEvents = await z.array(lidarrCalendarEventSchema).parseAsync(await response.json());
|
const lidarrCalendarEvents = await z.array(lidarrCalendarEventSchema).parseAsync(await response.json());
|
||||||
|
|
||||||
return lidarrCalendarEvents.map((lidarrCalendarEvent): CalendarEvent => {
|
return lidarrCalendarEvents.map((lidarrCalendarEvent): CalendarEvent => {
|
||||||
|
const imageSrc = this.chooseBestImage(lidarrCalendarEvent);
|
||||||
return {
|
return {
|
||||||
name: lidarrCalendarEvent.title,
|
title: lidarrCalendarEvent.title,
|
||||||
subName: lidarrCalendarEvent.artist.artistName,
|
subTitle: lidarrCalendarEvent.artist.artistName,
|
||||||
description: lidarrCalendarEvent.overview,
|
description: lidarrCalendarEvent.overview ?? null,
|
||||||
thumbnail: this.chooseBestImageAsURL(lidarrCalendarEvent),
|
startDate: lidarrCalendarEvent.releaseDate,
|
||||||
date: lidarrCalendarEvent.releaseDate,
|
endDate: null,
|
||||||
mediaInformation: {
|
image: imageSrc
|
||||||
type: "audio",
|
? {
|
||||||
},
|
src: imageSrc.remoteUrl,
|
||||||
|
aspectRatio: { width: 7, height: 12 },
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
location: null,
|
||||||
|
indicatorColor: "cyan",
|
||||||
links: this.getLinksForLidarrCalendarEvent(lidarrCalendarEvent),
|
links: this.getLinksForLidarrCalendarEvent(lidarrCalendarEvent),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLinksForLidarrCalendarEvent = (event: z.infer<typeof lidarrCalendarEventSchema>) => {
|
private getLinksForLidarrCalendarEvent = (event: z.infer<typeof lidarrCalendarEventSchema>) => {
|
||||||
const links: CalendarEvent["links"] = [];
|
const links: CalendarLink[] = [];
|
||||||
|
|
||||||
for (const link of event.artist.links) {
|
for (const link of event.artist.links) {
|
||||||
switch (link.name) {
|
switch (link.name) {
|
||||||
@@ -70,7 +76,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
color: "#f5c518",
|
color: "#f5c518",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/vgmdb.svg",
|
logo: "/images/apps/vgmdb.svg",
|
||||||
notificationColor: "cyan",
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "imdb":
|
case "imdb":
|
||||||
@@ -80,7 +85,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
color: "#f5c518",
|
color: "#f5c518",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/imdb.png",
|
logo: "/images/apps/imdb.png",
|
||||||
notificationColor: "cyan",
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "last":
|
case "last":
|
||||||
@@ -90,7 +94,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
color: "#cf222a",
|
color: "#cf222a",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/lastfm.svg",
|
logo: "/images/apps/lastfm.svg",
|
||||||
notificationColor: "cyan",
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||||
import type { AtLeastOneOf } from "@homarr/common/types";
|
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
import { Integration } from "../../base/integration";
|
|
||||||
import type { IntegrationTestingInput } from "../../base/integration";
|
import type { IntegrationTestingInput } from "../../base/integration";
|
||||||
|
import { Integration } from "../../base/integration";
|
||||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||||
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
||||||
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||||
import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types";
|
import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types";
|
||||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||||
|
|
||||||
@@ -34,33 +33,44 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
});
|
});
|
||||||
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
||||||
|
|
||||||
return radarrCalendarEvents.map((radarrCalendarEvent): CalendarEvent => {
|
return radarrCalendarEvents.flatMap((radarrCalendarEvent): CalendarEvent[] => {
|
||||||
const dates = radarrReleaseTypes
|
const imageSrc = this.chooseBestImageAsURL(radarrCalendarEvent);
|
||||||
.map((type) => (radarrCalendarEvent[type] ? { type, date: radarrCalendarEvent[type] } : undefined))
|
|
||||||
.filter((date) => date) as AtLeastOneOf<Exclude<CalendarEvent["dates"], undefined>[number]>;
|
return radarrReleaseTypes
|
||||||
return {
|
.map((releaseType) => ({ type: releaseType, date: radarrCalendarEvent[releaseType] }))
|
||||||
name: radarrCalendarEvent.title,
|
.filter((item) => item.date !== undefined)
|
||||||
subName: radarrCalendarEvent.originalTitle,
|
.map((item) => ({
|
||||||
description: radarrCalendarEvent.overview,
|
title: radarrCalendarEvent.title,
|
||||||
thumbnail: this.chooseBestImageAsURL(radarrCalendarEvent),
|
subTitle: radarrCalendarEvent.originalTitle,
|
||||||
date: dates[0].date,
|
description: radarrCalendarEvent.overview ?? null,
|
||||||
dates,
|
// Check is done above in the filter
|
||||||
mediaInformation: {
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
type: "movie",
|
startDate: item.date!,
|
||||||
},
|
endDate: null,
|
||||||
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
image: imageSrc
|
||||||
};
|
? {
|
||||||
|
src: imageSrc,
|
||||||
|
aspectRatio: { width: 7, height: 12 },
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
location: null,
|
||||||
|
metadata: {
|
||||||
|
type: "radarr",
|
||||||
|
releaseType: item.type,
|
||||||
|
},
|
||||||
|
indicatorColor: "yellow",
|
||||||
|
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
|
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
|
||||||
const links: CalendarEvent["links"] = [
|
const links: CalendarLink[] = [
|
||||||
{
|
{
|
||||||
href: this.url(`/movie/${event.titleSlug}`).toString(),
|
href: this.url(`/movie/${event.titleSlug}`).toString(),
|
||||||
name: "Radarr",
|
name: "Radarr",
|
||||||
logo: "/images/apps/radarr.svg",
|
logo: "/images/apps/radarr.svg",
|
||||||
color: undefined,
|
color: undefined,
|
||||||
notificationColor: "yellow",
|
|
||||||
isDark: true,
|
isDark: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
|
|||||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||||
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
||||||
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||||
|
|
||||||
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
|
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
|
||||||
@@ -50,15 +50,22 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
|
|||||||
const readarrCalendarEvents = await z.array(readarrCalendarEventSchema).parseAsync(await response.json());
|
const readarrCalendarEvents = await z.array(readarrCalendarEventSchema).parseAsync(await response.json());
|
||||||
|
|
||||||
return readarrCalendarEvents.map((readarrCalendarEvent): CalendarEvent => {
|
return readarrCalendarEvents.map((readarrCalendarEvent): CalendarEvent => {
|
||||||
|
const imageSrc = this.chooseBestImageAsURL(readarrCalendarEvent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: readarrCalendarEvent.title,
|
title: readarrCalendarEvent.title,
|
||||||
subName: readarrCalendarEvent.author.authorName,
|
subTitle: readarrCalendarEvent.author.authorName,
|
||||||
description: readarrCalendarEvent.overview,
|
description: readarrCalendarEvent.overview ?? null,
|
||||||
thumbnail: this.chooseBestImageAsURL(readarrCalendarEvent),
|
startDate: readarrCalendarEvent.releaseDate,
|
||||||
date: readarrCalendarEvent.releaseDate,
|
endDate: null,
|
||||||
mediaInformation: {
|
image: imageSrc
|
||||||
type: "audio",
|
? {
|
||||||
},
|
src: imageSrc,
|
||||||
|
aspectRatio: { width: 7, height: 12 },
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
location: null,
|
||||||
|
indicatorColor: "#f5c518",
|
||||||
links: this.getLinksForReadarrCalendarEvent(readarrCalendarEvent),
|
links: this.getLinksForReadarrCalendarEvent(readarrCalendarEvent),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -72,9 +79,8 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
|
|||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/readarr.svg",
|
logo: "/images/apps/readarr.svg",
|
||||||
name: "Readarr",
|
name: "Readarr",
|
||||||
notificationColor: "#f5c518",
|
|
||||||
},
|
},
|
||||||
] satisfies CalendarEvent["links"];
|
] satisfies CalendarLink[];
|
||||||
};
|
};
|
||||||
|
|
||||||
private chooseBestImage = (
|
private chooseBestImage = (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
|
|||||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||||
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
|
||||||
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||||
|
|
||||||
export class SonarrIntegration extends Integration implements ICalendarIntegration {
|
export class SonarrIntegration extends Integration implements ICalendarIntegration {
|
||||||
@@ -33,33 +33,36 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
|||||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const sonarCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
|
const sonarrCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
|
||||||
|
|
||||||
return sonarCalendarEvents.map(
|
return sonarrCalendarEvents.map((event): CalendarEvent => {
|
||||||
(sonarCalendarEvent): CalendarEvent => ({
|
const imageSrc = this.chooseBestImageAsURL(event);
|
||||||
name: sonarCalendarEvent.title,
|
return {
|
||||||
subName: sonarCalendarEvent.series.title,
|
title: event.title,
|
||||||
description: sonarCalendarEvent.series.overview,
|
subTitle: event.series.title,
|
||||||
thumbnail: this.chooseBestImageAsURL(sonarCalendarEvent),
|
description: event.series.overview ?? null,
|
||||||
date: sonarCalendarEvent.airDateUtc,
|
startDate: event.airDateUtc,
|
||||||
mediaInformation: {
|
endDate: null,
|
||||||
type: "tv",
|
image: imageSrc
|
||||||
episodeNumber: sonarCalendarEvent.episodeNumber,
|
? {
|
||||||
seasonNumber: sonarCalendarEvent.seasonNumber,
|
src: imageSrc,
|
||||||
},
|
aspectRatio: { width: 7, height: 12 },
|
||||||
links: this.getLinksForSonarCalendarEvent(sonarCalendarEvent),
|
}
|
||||||
}),
|
: null,
|
||||||
);
|
location: null,
|
||||||
|
indicatorColor: "blue",
|
||||||
|
links: this.getLinksForSonarrCalendarEvent(event),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLinksForSonarCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
|
private getLinksForSonarrCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
|
||||||
const links: CalendarEvent["links"] = [
|
const links: CalendarLink[] = [
|
||||||
{
|
{
|
||||||
href: this.url(`/series/${event.series.titleSlug}`).toString(),
|
href: this.url(`/series/${event.series.titleSlug}`).toString(),
|
||||||
name: "Sonarr",
|
name: "Sonarr",
|
||||||
logo: "/images/apps/sonarr.svg",
|
logo: "/images/apps/sonarr.svg",
|
||||||
color: undefined,
|
color: undefined,
|
||||||
notificationColor: "blue",
|
|
||||||
isDark: true,
|
isDark: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,32 +8,46 @@ export class CalendarMockService implements ICalendarIntegration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const homarrMeetup = (start: Date, end: Date): CalendarEvent => ({
|
const homarrMeetup = (start: Date, end: Date): CalendarEvent => {
|
||||||
name: "Homarr Meetup",
|
const startDate = randomDateBetween(start, end);
|
||||||
subName: "",
|
const endDate = new Date(startDate.getTime() + 2 * 60 * 60 * 1000); // 2 hours later
|
||||||
description: "Yearly meetup of the Homarr community",
|
return {
|
||||||
date: randomDateBetween(start, end),
|
title: "Homarr Meetup",
|
||||||
links: [
|
subTitle: "",
|
||||||
{
|
description: "Yearly meetup of the Homarr community",
|
||||||
href: "https://homarr.dev",
|
startDate,
|
||||||
name: "Homarr",
|
endDate,
|
||||||
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
image: null,
|
||||||
color: "#000000",
|
location: "Mountains",
|
||||||
notificationColor: "#fa5252",
|
indicatorColor: "#fa5252",
|
||||||
isDark: true,
|
links: [
|
||||||
},
|
{
|
||||||
],
|
href: "https://homarr.dev",
|
||||||
});
|
name: "Homarr",
|
||||||
|
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||||
|
color: "#000000",
|
||||||
|
isDark: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
|
const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||||
name: "Titanic",
|
title: "Titanic",
|
||||||
subName: "A classic movie",
|
subTitle: "A classic movie",
|
||||||
description: "A tragic love story set on the ill-fated RMS Titanic.",
|
description: "A tragic love story set on the ill-fated RMS Titanic.",
|
||||||
date: randomDateBetween(start, end),
|
startDate: randomDateBetween(start, end),
|
||||||
thumbnail: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
|
endDate: null,
|
||||||
mediaInformation: {
|
image: {
|
||||||
type: "movie",
|
src: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
|
||||||
|
aspectRatio: { width: 7, height: 12 },
|
||||||
},
|
},
|
||||||
|
location: null,
|
||||||
|
metadata: {
|
||||||
|
type: "radarr",
|
||||||
|
releaseType: "inCinemas",
|
||||||
|
},
|
||||||
|
indicatorColor: "cyan",
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
href: "https://www.imdb.com/title/tt0120338/",
|
href: "https://www.imdb.com/title/tt0120338/",
|
||||||
@@ -41,22 +55,26 @@ const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
|
|||||||
color: "#f5c518",
|
color: "#f5c518",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/imdb.svg",
|
logo: "/images/apps/imdb.svg",
|
||||||
notificationColor: "cyan",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
|
const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||||
name: "The Mandalorian",
|
title: "The Mandalorian",
|
||||||
subName: "A Star Wars Series",
|
subTitle: "A Star Wars Series",
|
||||||
description: "A lone bounty hunter in the outer reaches of the galaxy.",
|
description: "A lone bounty hunter in the outer reaches of the galaxy.",
|
||||||
date: randomDateBetween(start, end),
|
startDate: randomDateBetween(start, end),
|
||||||
thumbnail: "https://image.tmdb.org/t/p/original/ztvm7C7hiUpS3CZRXFmJxljICzK.jpg",
|
endDate: null,
|
||||||
mediaInformation: {
|
image: {
|
||||||
type: "tv",
|
src: "https://image.tmdb.org/t/p/original/sWgBv7LV2PRoQgkxwlibdGXKz1S.jpg",
|
||||||
seasonNumber: 1,
|
aspectRatio: { width: 7, height: 12 },
|
||||||
episodeNumber: 1,
|
badge: {
|
||||||
|
content: "S1:E1",
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
location: null,
|
||||||
|
indicatorColor: "blue",
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
href: "https://www.imdb.com/title/tt8111088/",
|
href: "https://www.imdb.com/title/tt8111088/",
|
||||||
@@ -64,7 +82,6 @@ const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
|
|||||||
color: "#f5c518",
|
color: "#f5c518",
|
||||||
isDark: false,
|
isDark: false,
|
||||||
logo: "/images/apps/imdb.svg",
|
logo: "/images/apps/imdb.svg",
|
||||||
notificationColor: "blue",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,17 +63,20 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: veventObject.summary,
|
title: veventObject.summary,
|
||||||
date,
|
subTitle: null,
|
||||||
subName: "",
|
|
||||||
description: veventObject.description,
|
description: veventObject.description,
|
||||||
|
startDate: date,
|
||||||
|
endDate: veventObject.end,
|
||||||
|
image: null,
|
||||||
|
location: veventObject.location || null,
|
||||||
|
indicatorColor: "#ff8600",
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
href: url.toString(),
|
href: url.toString(),
|
||||||
name: "Nextcloud",
|
name: "Nextcloud",
|
||||||
logo: "/images/apps/nextcloud.svg",
|
logo: "/images/apps/nextcloud.svg",
|
||||||
color: undefined,
|
color: undefined,
|
||||||
notificationColor: "#ff8600",
|
|
||||||
isDark: true,
|
isDark: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"winston": "3.17.0",
|
"winston": "3.17.0",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -33,13 +33,13 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"react": "19.1.1"
|
"react": "19.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/notifications": "^8.2.8",
|
"@mantine/notifications": "^8.3.1",
|
||||||
"@tabler/icons-react": "^3.34.1"
|
"@tabler/icons-react": "^3.34.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -37,14 +37,14 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"adm-zip": "0.5.16",
|
"adm-zip": "0.5.16",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"zod": "^4.1.5",
|
"zod": "^4.1.8",
|
||||||
"zod-form-data": "^3.0.1"
|
"zod-form-data": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"octokit": "^5.0.3",
|
"octokit": "^5.0.3",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"undici": "7.15.0"
|
"undici": "7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/dates": "^8.2.8",
|
"@mantine/dates": "^8.3.1",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1"
|
"react-dom": "19.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,12 +33,12 @@
|
|||||||
"@homarr/settings": "workspace:^0.1.0",
|
"@homarr/settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@mantine/spotlight": "^8.2.8",
|
"@mantine/spotlight": "^8.3.1",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"jotai": "^2.13.1",
|
"jotai": "^2.14.0",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"use-deep-compare-effect": "^1.8.1"
|
"use-deep-compare-effect": "^1.8.1"
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"next-intl": "4.3.7",
|
"next-intl": "4.3.8",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1"
|
"react-dom": "19.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole 数据"
|
"label": "DNS Hole 数据"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "会话清理"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "更新检查"
|
"label": "更新检查"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Data"
|
"label": "DNS Hole Data"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Sessions Oprydning"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Opdaterings checker"
|
"label": "Opdaterings checker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Daten"
|
"label": "DNS Hole Daten"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Sitzung bereinigen"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Updateprüfer"
|
"label": "Updateprüfer"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Daten"
|
"label": "DNS Hole Daten"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Sitzung bereinigen"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Updateprüfer"
|
"label": "Updateprüfer"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -945,6 +945,10 @@
|
|||||||
"label": "Topic",
|
"label": "Topic",
|
||||||
"newLabel": "New topic"
|
"newLabel": "New topic"
|
||||||
},
|
},
|
||||||
|
"url": {
|
||||||
|
"label": "Url",
|
||||||
|
"newLabel": "New url"
|
||||||
|
},
|
||||||
"opnsenseApiKey": {
|
"opnsenseApiKey": {
|
||||||
"label": "API Key (Key)",
|
"label": "API Key (Key)",
|
||||||
"newLabel": "New API Key (Key)"
|
"newLabel": "New API Key (Key)"
|
||||||
@@ -1543,7 +1547,15 @@
|
|||||||
"width": "Width",
|
"width": "Width",
|
||||||
"height": "Height"
|
"height": "Height"
|
||||||
},
|
},
|
||||||
"placeholder": "Start writing your notes"
|
"placeholder": "Start writing your notes",
|
||||||
|
"dismiss": {
|
||||||
|
"title": "Dismiss changes?",
|
||||||
|
"message": "You have unsaved changes in your notebook. Are you sure you want to discard them?",
|
||||||
|
"action": {
|
||||||
|
"discard": "Discard changes",
|
||||||
|
"keepEditing": "Keep editing"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "iFrame",
|
"name": "iFrame",
|
||||||
@@ -2490,7 +2502,29 @@
|
|||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "System resources",
|
"name": "System resources",
|
||||||
"description": "CPU, Memory, Disk and other hardware usage of your system",
|
"description": "CPU, Memory, Disk and other hardware usage of your system",
|
||||||
"option": {},
|
"option": {
|
||||||
|
"hasShadow": {
|
||||||
|
"label": "Enable chart shading"
|
||||||
|
},
|
||||||
|
"visibleCharts": {
|
||||||
|
"label": "Visible charts",
|
||||||
|
"description": "Select the charts you want to be visible.",
|
||||||
|
"option": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"memory": "Memory",
|
||||||
|
"network": "Network"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelDisplayMode": {
|
||||||
|
"label": "Label display mode",
|
||||||
|
"option": {
|
||||||
|
"textWithIcon": "Show text with icon",
|
||||||
|
"text": "Show only text",
|
||||||
|
"icon": "Show only icon",
|
||||||
|
"hidden": "Hide label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"card": {
|
"card": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"memory": "MEM",
|
"memory": "MEM",
|
||||||
|
|||||||
@@ -954,16 +954,16 @@
|
|||||||
"newLabel": "Nueva clave API (Credencial)"
|
"newLabel": "Nueva clave API (Credencial)"
|
||||||
},
|
},
|
||||||
"githubAppId": {
|
"githubAppId": {
|
||||||
"label": "",
|
"label": "ID de aplicación",
|
||||||
"newLabel": ""
|
"newLabel": "Nuevo ID de aplicación"
|
||||||
},
|
},
|
||||||
"githubInstallationId": {
|
"githubInstallationId": {
|
||||||
"label": "",
|
"label": "ID de instalación",
|
||||||
"newLabel": ""
|
"newLabel": "Nuevo ID de instalación"
|
||||||
},
|
},
|
||||||
"privateKey": {
|
"privateKey": {
|
||||||
"label": "",
|
"label": "Clave privada",
|
||||||
"newLabel": ""
|
"newLabel": "Nueva clave privada"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -975,7 +975,7 @@
|
|||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"plural": "Imágenes",
|
"plural": "Imágenes",
|
||||||
"search": "Encuentra una imagen",
|
"search": "Buscar una imagen",
|
||||||
"field": {
|
"field": {
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"size": "Tamaño",
|
"size": "Tamaño",
|
||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "Datos de agujero DNS"
|
"label": "Datos de agujero DNS"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Limpieza de sesión"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Comprobador de actualización"
|
"label": "Comprobador de actualización"
|
||||||
},
|
},
|
||||||
@@ -3893,16 +3890,16 @@
|
|||||||
"label": "Credenciales"
|
"label": "Credenciales"
|
||||||
},
|
},
|
||||||
"volumes": {
|
"volumes": {
|
||||||
"label": ""
|
"label": "Volúmenes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
"label": ""
|
"label": "Registros"
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"label": "",
|
"label": "Certificados",
|
||||||
"hostnames": {
|
"hostnames": {
|
||||||
"label": ""
|
"label": "Nombres de host"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3915,28 +3912,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "",
|
"placeholder": "Busca lo que quieras",
|
||||||
"nothingFound": "",
|
"nothingFound": "Ningún resultado encontrado",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch": ""
|
"fetch": "Se produjo un error al recuperar los datos"
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"appIntegrationBoard": {
|
"appIntegrationBoard": {
|
||||||
"help": "",
|
"help": "Buscar aplicaciones, integraciones o tableros",
|
||||||
"group": {
|
"group": {
|
||||||
"app": {
|
"app": {
|
||||||
"title": "Aplicaciones",
|
"title": "Aplicaciones",
|
||||||
"children": {
|
"children": {
|
||||||
"action": {
|
"action": {
|
||||||
"open": {
|
"open": {
|
||||||
"label": ""
|
"label": "Abrir URL de aplicación"
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"label": ""
|
"label": "Editar aplicación"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Seleccionar una acción para la aplicación"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3945,122 +3942,122 @@
|
|||||||
"children": {
|
"children": {
|
||||||
"action": {
|
"action": {
|
||||||
"open": {
|
"open": {
|
||||||
"label": ""
|
"label": "Abrir tablero"
|
||||||
},
|
},
|
||||||
"homeBoard": {
|
"homeBoard": {
|
||||||
"label": ""
|
"label": "Establecer como tablero de inicio"
|
||||||
},
|
},
|
||||||
"mobileBoard": {
|
"mobileBoard": {
|
||||||
"label": ""
|
"label": "Establecer como tablero móvil"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"label": ""
|
"label": "Abrir ajustes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Seleccionar una acción para el tablero"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
"title": ""
|
"title": "Integraciones"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"help": "",
|
"help": "Activar modo comando",
|
||||||
"group": {
|
"group": {
|
||||||
"localCommand": {
|
"localCommand": {
|
||||||
"title": ""
|
"title": "Comandos locales"
|
||||||
},
|
},
|
||||||
"globalCommand": {
|
"globalCommand": {
|
||||||
"title": "",
|
"title": "Comandos globales",
|
||||||
"option": {
|
"option": {
|
||||||
"colorScheme": {
|
"colorScheme": {
|
||||||
"light": "",
|
"light": "Cambiar a modo claro",
|
||||||
"dark": ""
|
"dark": "Cambiar a modo oscuro"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"label": "",
|
"label": "Cambiar idioma",
|
||||||
"children": {
|
"children": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Selecciona tu idioma preferido"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"newBoard": {
|
"newBoard": {
|
||||||
"label": ""
|
"label": "Crear un nuevo tablero"
|
||||||
},
|
},
|
||||||
"importBoard": {
|
"importBoard": {
|
||||||
"label": ""
|
"label": "Importar un tablero"
|
||||||
},
|
},
|
||||||
"newApp": {
|
"newApp": {
|
||||||
"label": ""
|
"label": "Crear una nueva aplicación"
|
||||||
},
|
},
|
||||||
"newIntegration": {
|
"newIntegration": {
|
||||||
"label": "",
|
"label": "Crear una nueva integración",
|
||||||
"children": {
|
"children": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Selecciona el tipo de integración que deseas crear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"newUser": {
|
"newUser": {
|
||||||
"label": ""
|
"label": "Crear un nuevo usuario"
|
||||||
},
|
},
|
||||||
"newInvite": {
|
"newInvite": {
|
||||||
"label": ""
|
"label": "Crear una nueva invitación"
|
||||||
},
|
},
|
||||||
"newGroup": {
|
"newGroup": {
|
||||||
"label": ""
|
"label": "Crear un nuevo grupo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"requestMovie": "",
|
"requestMovie": "Solicitar película",
|
||||||
"requestSeries": "",
|
"requestSeries": "Solicitar serie",
|
||||||
"openIn": ""
|
"openIn": "Abrir en {kind}"
|
||||||
},
|
},
|
||||||
"external": {
|
"external": {
|
||||||
"help": "",
|
"help": "Usar un motor de búsqueda externo",
|
||||||
"group": {
|
"group": {
|
||||||
"searchEngine": {
|
"searchEngine": {
|
||||||
"title": "",
|
"title": "Motores de búsqueda",
|
||||||
"children": {
|
"children": {
|
||||||
"action": {
|
"action": {
|
||||||
"search": {
|
"search": {
|
||||||
"label": ""
|
"label": "Buscar con {name}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Selecciona una acción para el motor de búsqueda"
|
||||||
},
|
},
|
||||||
"searchResults": {
|
"searchResults": {
|
||||||
"title": ""
|
"title": "Selecciona un resultado de búsqueda para acciones"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"google": {
|
"google": {
|
||||||
"name": "",
|
"name": "Google",
|
||||||
"description": ""
|
"description": "Buscar en la web con Google"
|
||||||
},
|
},
|
||||||
"bing": {
|
"bing": {
|
||||||
"name": "",
|
"name": "Bing",
|
||||||
"description": ""
|
"description": "Buscar en la web con Bing"
|
||||||
},
|
},
|
||||||
"duckduckgo": {
|
"duckduckgo": {
|
||||||
"name": "",
|
"name": "DuckDuckGo",
|
||||||
"description": ""
|
"description": "Buscar en la web con DuckDuckGo"
|
||||||
},
|
},
|
||||||
"torrent": {
|
"torrent": {
|
||||||
"name": "",
|
"name": "Torrents",
|
||||||
"description": ""
|
"description": "Buscar torrents en torrentdownloads.pro"
|
||||||
},
|
},
|
||||||
"youTube": {
|
"youTube": {
|
||||||
"name": "",
|
"name": "YouTube",
|
||||||
"description": ""
|
"description": "Buscar vídeos en YouTube"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4069,7 +4066,7 @@
|
|||||||
"help": {
|
"help": {
|
||||||
"group": {
|
"group": {
|
||||||
"mode": {
|
"mode": {
|
||||||
"title": ""
|
"title": "Modos"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"title": "Ayuda",
|
"title": "Ayuda",
|
||||||
@@ -4078,7 +4075,7 @@
|
|||||||
"label": "Documentación"
|
"label": "Documentación"
|
||||||
},
|
},
|
||||||
"submitIssue": {
|
"submitIssue": {
|
||||||
"label": ""
|
"label": "Enviar una incidencia"
|
||||||
},
|
},
|
||||||
"discord": {
|
"discord": {
|
||||||
"label": "Comunidad de Discord"
|
"label": "Comunidad de Discord"
|
||||||
@@ -4090,81 +4087,81 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"group": {
|
"group": {
|
||||||
"search": {
|
"search": {
|
||||||
"title": "",
|
"title": "Buscar",
|
||||||
"option": {
|
"option": {
|
||||||
"other": {
|
"other": {
|
||||||
"label": ""
|
"label": "Buscar con otro motor de búsqueda"
|
||||||
},
|
},
|
||||||
"no-default": {
|
"no-default": {
|
||||||
"label": "",
|
"label": "Ningún motor de búsqueda por defecto",
|
||||||
"description": ""
|
"description": "Establece un motor de búsqueda predeterminado en las preferencias"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"label": ""
|
"label": "Buscar \"{query}\" con {name}"
|
||||||
},
|
},
|
||||||
"from-integration": {
|
"from-integration": {
|
||||||
"description": ""
|
"description": "Empieza a escribir para buscar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"local": {
|
"local": {
|
||||||
"title": ""
|
"title": "Resultados locales"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"help": "",
|
"help": "Buscar páginas",
|
||||||
"group": {
|
"group": {
|
||||||
"page": {
|
"page": {
|
||||||
"title": "",
|
"title": "Páginas",
|
||||||
"option": {
|
"option": {
|
||||||
"manageHome": {
|
"manageHome": {
|
||||||
"label": ""
|
"label": "Administrar página de inicio"
|
||||||
},
|
},
|
||||||
"manageBoard": {
|
"manageBoard": {
|
||||||
"label": ""
|
"label": "Administrar tableros"
|
||||||
},
|
},
|
||||||
"manageApp": {
|
"manageApp": {
|
||||||
"label": ""
|
"label": "Administrar aplicaciones"
|
||||||
},
|
},
|
||||||
"manageIntegration": {
|
"manageIntegration": {
|
||||||
"label": ""
|
"label": "Administrar integraciones"
|
||||||
},
|
},
|
||||||
"manageSearchEngine": {
|
"manageSearchEngine": {
|
||||||
"label": ""
|
"label": "Administrar motores de búsqueda"
|
||||||
},
|
},
|
||||||
"manageMedia": {
|
"manageMedia": {
|
||||||
"label": ""
|
"label": "Administrar imágenes"
|
||||||
},
|
},
|
||||||
"manageUser": {
|
"manageUser": {
|
||||||
"label": "Administrar usuarios"
|
"label": "Administrar usuarios"
|
||||||
},
|
},
|
||||||
"manageInvite": {
|
"manageInvite": {
|
||||||
"label": ""
|
"label": "Administrar invitaciones"
|
||||||
},
|
},
|
||||||
"manageGroup": {
|
"manageGroup": {
|
||||||
"label": ""
|
"label": "Administrar grupos"
|
||||||
},
|
},
|
||||||
"manageDocker": {
|
"manageDocker": {
|
||||||
"label": ""
|
"label": "Administrar docker"
|
||||||
},
|
},
|
||||||
"manageApi": {
|
"manageApi": {
|
||||||
"label": ""
|
"label": "Swagger API"
|
||||||
},
|
},
|
||||||
"manageLog": {
|
"manageLog": {
|
||||||
"label": ""
|
"label": "Ver registros"
|
||||||
},
|
},
|
||||||
"manageTask": {
|
"manageTask": {
|
||||||
"label": ""
|
"label": "Administrar tareas"
|
||||||
},
|
},
|
||||||
"manageSettings": {
|
"manageSettings": {
|
||||||
"label": ""
|
"label": "Ajustes globales"
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"label": "Acerca de"
|
"label": "Acerca de"
|
||||||
},
|
},
|
||||||
"homeBoard": {
|
"homeBoard": {
|
||||||
"label": ""
|
"label": "Tablero de inicio"
|
||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Tus preferencias"
|
"label": "Tus preferencias"
|
||||||
@@ -4174,37 +4171,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"userGroup": {
|
"userGroup": {
|
||||||
"help": "",
|
"help": "Buscar usuarios o grupos",
|
||||||
"group": {
|
"group": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Usuarios",
|
"title": "Usuarios",
|
||||||
"children": {
|
"children": {
|
||||||
"action": {
|
"action": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"label": ""
|
"label": "Mostrar detalles de usuario"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Selecciona una acción para el usuario"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"title": "",
|
"title": "Grupos",
|
||||||
"children": {
|
"children": {
|
||||||
"action": {
|
"action": {
|
||||||
"detail": {
|
"detail": {
|
||||||
"label": ""
|
"label": "Mostrar detalles del grupo"
|
||||||
},
|
},
|
||||||
"manageMember": {
|
"manageMember": {
|
||||||
"label": ""
|
"label": "Administrar usuarios"
|
||||||
},
|
},
|
||||||
"managePermission": {
|
"managePermission": {
|
||||||
"label": ""
|
"label": "Administrar permisos"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"title": ""
|
"title": "Selecciona una acción para el grupo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4212,72 +4209,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engine": {
|
"engine": {
|
||||||
"search": "",
|
"search": "Buscar un motor de búsqueda",
|
||||||
"field": {
|
"field": {
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Nombre"
|
"label": "Nombre"
|
||||||
},
|
},
|
||||||
"short": {
|
"short": {
|
||||||
"label": ""
|
"label": "Abreviatura"
|
||||||
},
|
},
|
||||||
"urlTemplate": {
|
"urlTemplate": {
|
||||||
"label": ""
|
"label": "Plantilla de búsqueda de URL"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"label": ""
|
"label": "Descripción"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "",
|
"title": "Motores de búsqueda",
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": "",
|
"title": "Todavía no hay motores de búsqueda",
|
||||||
"action": ""
|
"action": "Crea tu primer motor de búsqueda"
|
||||||
},
|
},
|
||||||
"interactive": ""
|
"interactive": "Interactivo, utiliza una integración"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "",
|
"title": "Nuevo motor de búsqueda",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Motor de búsqueda creado",
|
||||||
"message": ""
|
"message": "El motor de búsqueda fue creado con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "No se creó el motor de búsqueda",
|
||||||
"message": ""
|
"message": "El motor de búsqueda no pudo ser creado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"title": "",
|
"title": "Editar motor de búsqueda",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Cambios aplicados con éxito",
|
||||||
"message": ""
|
"message": "El motor de búsqueda se guardó con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "No se pudieron aplicar los cambios",
|
||||||
"message": ""
|
"message": "No se pudo guardar el motor de búsqueda"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"configControl": "",
|
"configControl": "Configuración",
|
||||||
"searchEngineType": {
|
"searchEngineType": {
|
||||||
"generic": "",
|
"generic": "Genérico",
|
||||||
"fromIntegration": ""
|
"fromIntegration": "Desde integración"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "",
|
"title": "Eliminar motor de búsqueda",
|
||||||
"message": "",
|
"message": "¿Estás seguro de que quieres eliminar el motor de búsqueda {name}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Motor de búsqueda eliminado",
|
||||||
"message": ""
|
"message": "El motor de búsqueda fue eliminado con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Motor de búsqueda no eliminado",
|
||||||
"message": ""
|
"message": "No se ha podido eliminar el motor de búsqueda"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4285,15 +4282,15 @@
|
|||||||
"media": {
|
"media": {
|
||||||
"request": {
|
"request": {
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "",
|
"title": "Solicitar \"{name}\"",
|
||||||
"table": {
|
"table": {
|
||||||
"header": {
|
"header": {
|
||||||
"season": "",
|
"season": "Temporada",
|
||||||
"episodes": ""
|
"episodes": "Episodios"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"send": ""
|
"send": "Enviar solicitud"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4303,89 +4300,89 @@
|
|||||||
"certificate": {
|
"certificate": {
|
||||||
"field": {
|
"field": {
|
||||||
"hostname": {
|
"hostname": {
|
||||||
"label": ""
|
"label": "Nombre de host"
|
||||||
},
|
},
|
||||||
"subject": {
|
"subject": {
|
||||||
"label": ""
|
"label": "Sujeto"
|
||||||
},
|
},
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"label": ""
|
"label": "Emisor"
|
||||||
},
|
},
|
||||||
"validFrom": {
|
"validFrom": {
|
||||||
"label": ""
|
"label": "Válido desde"
|
||||||
},
|
},
|
||||||
"validTo": {
|
"validTo": {
|
||||||
"label": ""
|
"label": "Válido hasta"
|
||||||
},
|
},
|
||||||
"serialNumber": {
|
"serialNumber": {
|
||||||
"label": ""
|
"label": "Número de serie"
|
||||||
},
|
},
|
||||||
"fingerprint": {
|
"fingerprint": {
|
||||||
"label": ""
|
"label": "Huella digital"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "",
|
"title": "Certificados de confianza",
|
||||||
"description": "",
|
"description": "Utilizado por Homarr para solicitar datos de las integraciones.",
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": ""
|
"title": "Aún no hay certificados"
|
||||||
},
|
},
|
||||||
"invalid": {
|
"invalid": {
|
||||||
"title": "Certificado no válido",
|
"title": "Certificado no válido",
|
||||||
"description": "Error al analizar el certificado"
|
"description": "Error al analizar el certificado"
|
||||||
},
|
},
|
||||||
"expires": "",
|
"expires": "Expira {when}",
|
||||||
"toHostnames": ""
|
"toHostnames": "Nombres de host de confianza"
|
||||||
},
|
},
|
||||||
"hostnames": {
|
"hostnames": {
|
||||||
"title": "",
|
"title": "Nombres de host de certificado de confianza",
|
||||||
"description": "",
|
"description": "Algunos certificados no permiten que el dominio específico que utiliza Homarr los solicite, debido a esto todos los nombres de host de confianza con sus huellas digitales de certificado se utilizan para eludir estas restricciones.",
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": ""
|
"title": "Aún no hay nombres de host"
|
||||||
},
|
},
|
||||||
"toCertificates": ""
|
"toCertificates": "Certificados"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"create": {
|
"create": {
|
||||||
"label": "",
|
"label": "Añadir certificado",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Certificado añadido",
|
||||||
"message": ""
|
"message": "El certificado fue añadido con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Error al añadir el certificado",
|
||||||
"message": ""
|
"message": "No se ha podido añadir el certificado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"label": "",
|
"label": "Eliminar certificado",
|
||||||
"confirm": "",
|
"confirm": "¿Estás seguro de que deseas eliminar el certificado?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Certificado eliminado",
|
||||||
"message": ""
|
"message": "El certificado fue eliminado con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Certificado no eliminado",
|
||||||
"message": ""
|
"message": "No se ha podido eliminar el certificado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"removeHostname": {
|
"removeHostname": {
|
||||||
"label": "",
|
"label": "Eliminar nombre de host de confianza",
|
||||||
"confirm": "",
|
"confirm": "¿Estás seguro de que quieres eliminar este nombre de host de confianza? Esto puede causar que algunas integraciones dejen de funcionar.",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Nombre de host eliminado",
|
||||||
"message": ""
|
"message": "El nombre de host fue eliminado con éxito"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Nombre de host no eliminado",
|
||||||
"message": ""
|
"message": "El nombre de host no pudo ser eliminado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4394,10 +4391,10 @@
|
|||||||
"log": {
|
"log": {
|
||||||
"level": {
|
"level": {
|
||||||
"option": {
|
"option": {
|
||||||
"debug": "",
|
"debug": "Depuración",
|
||||||
"info": "",
|
"info": "Información",
|
||||||
"warn": "",
|
"warn": "Advertencia",
|
||||||
"error": ""
|
"error": "Error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "Données du puit DNS"
|
"label": "Données du puit DNS"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Nettoyage de session"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Vérificateur de mise à jour"
|
"label": "Vérificateur de mise à jour"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "נתוני חור DNS"
|
"label": "נתוני חור DNS"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "ניקוי סשן"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "בודק עדכונים"
|
"label": "בודק עדכונים"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Holeデータ"
|
"label": "DNS Holeデータ"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "セッションのクリーンアップ"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "アップデートチェッカー"
|
"label": "アップデートチェッカー"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS-hole gegevens"
|
"label": "DNS-hole gegevens"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Sessie opruimen"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Update checker"
|
"label": "Update checker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Data"
|
"label": "DNS Hole Data"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Økt opprydding"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Oppdateringssjekk"
|
"label": "Oppdateringssjekk"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "Dane DNS Hole"
|
"label": "Dane DNS Hole"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Czyszczenie sesji"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Sprawdzanie aktualizacji"
|
"label": "Sprawdzanie aktualizacji"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "Данные DNS-фильтра"
|
"label": "Данные DNS-фильтра"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Очистка сессий"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Проверка обновлений"
|
"label": "Проверка обновлений"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "Údaje o dierach DNS"
|
"label": "Údaje o dierach DNS"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Čistenie relácie"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Kontrola aktualizácií"
|
"label": "Kontrola aktualizácií"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -954,16 +954,16 @@
|
|||||||
"newLabel": "Yeni API Anahtarı (Gizli Anahtar)"
|
"newLabel": "Yeni API Anahtarı (Gizli Anahtar)"
|
||||||
},
|
},
|
||||||
"githubAppId": {
|
"githubAppId": {
|
||||||
"label": "",
|
"label": "Uygulama Kimliği",
|
||||||
"newLabel": ""
|
"newLabel": "Yeni Uygulama Kimliği"
|
||||||
},
|
},
|
||||||
"githubInstallationId": {
|
"githubInstallationId": {
|
||||||
"label": "",
|
"label": "Kurulum Kimliği",
|
||||||
"newLabel": ""
|
"newLabel": "Yeni Kurulum Kimliği"
|
||||||
},
|
},
|
||||||
"privateKey": {
|
"privateKey": {
|
||||||
"label": "",
|
"label": "Özel Anahtar",
|
||||||
"newLabel": ""
|
"newLabel": "Yeni özel anahtar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2273,7 +2273,7 @@
|
|||||||
"label": "Gönderi sayısı sınırı"
|
"label": "Gönderi sayısı sınırı"
|
||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": ""
|
"label": "Açıklamayı gizle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Çözümleyici Verileri"
|
"label": "DNS Çözümleyici Verileri"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Oturum Temizleme"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Güncelleme denetleyicisi"
|
"label": "Güncelleme denetleyicisi"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": ""
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole 數據"
|
"label": "DNS Hole 數據"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "會話清理"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "更新檢查"
|
"label": "更新檢查"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/dates": "^8.2.8",
|
"@mantine/dates": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"svgson": "^5.3.1"
|
"svgson": "^5.3.1"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"zod": "^4.1.5",
|
"zod": "^4.1.8",
|
||||||
"zod-form-data": "^3.0.1"
|
"zod-form-data": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -48,9 +48,9 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/charts": "^8.2.8",
|
"@mantine/charts": "^8.3.1",
|
||||||
"@mantine/core": "^8.2.8",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"@tiptap/extension-color": "2.26.1",
|
"@tiptap/extension-color": "2.26.1",
|
||||||
"@tiptap/extension-highlight": "2.26.1",
|
"@tiptap/extension-highlight": "2.26.1",
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "15.5.2",
|
"next": "15.5.3",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"video.js": "^8.23.4",
|
"video.js": "^8.23.4",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconClock } from "@tabler/icons-react";
|
import { IconClock, IconPin } from "@tabler/icons-react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import { isNullOrWhitespace } from "@homarr/common";
|
||||||
import type { CalendarEvent } from "@homarr/integrations/types";
|
import type { CalendarEvent } from "@homarr/integrations/types";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
@@ -40,85 +41,108 @@ export const CalendarEventList = ({ events }: CalendarEventListProps) => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
{events.map((event, eventIndex) => (
|
{events.map((event, eventIndex) => (
|
||||||
<Group key={`event-${eventIndex}`} align={"stretch"} wrap="nowrap">
|
<Group key={`event-${eventIndex}`} align={"stretch"} wrap="nowrap">
|
||||||
<Box pos={"relative"} w={70} h={120}>
|
{event.image !== null && (
|
||||||
<Image
|
<Box pos="relative">
|
||||||
src={event.thumbnail}
|
<Image
|
||||||
w={70}
|
src={event.image.src}
|
||||||
h={120}
|
w={70}
|
||||||
radius={"sm"}
|
mah={150}
|
||||||
fallbackSrc={"https://placehold.co/400x600?text=No%20image"}
|
style={{
|
||||||
/>
|
aspectRatio: event.image.aspectRatio
|
||||||
{event.mediaInformation?.type === "tv" && (
|
? `${event.image.aspectRatio.width} / ${event.image.aspectRatio.height}`
|
||||||
<Badge
|
: "1/1",
|
||||||
pos={"absolute"}
|
}}
|
||||||
bottom={-6}
|
radius="sm"
|
||||||
left={"50%"}
|
fallbackSrc="https://placehold.co/400x400?text=No%20image"
|
||||||
w={"inherit"}
|
/>
|
||||||
className={classes.badge}
|
{event.image.badge !== undefined && (
|
||||||
>{`S${event.mediaInformation.seasonNumber} / E${event.mediaInformation.episodeNumber}`}</Badge>
|
<Badge pos="absolute" bottom={-6} left="50%" w="90%" className={classes.badge}>
|
||||||
)}
|
{event.image.badge.content}
|
||||||
</Box>
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Stack style={{ flexGrow: 1 }} gap={0}>
|
<Stack style={{ flexGrow: 1 }} gap={0}>
|
||||||
<Group justify={"space-between"} align={"start"} mb={"xs"} wrap="nowrap">
|
<Group justify="space-between" align="start" mb="xs" wrap="nowrap">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
{event.subName && (
|
{event.subTitle !== null && (
|
||||||
<Text lineClamp={1} size="sm">
|
<Text lineClamp={1} size="sm">
|
||||||
{event.subName}
|
{event.subTitle}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text fw={"bold"} lineClamp={1} size="sm">
|
<Text fw={"bold"} lineClamp={1} size="sm">
|
||||||
{event.name}
|
{event.title}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
{event.dates ? (
|
{event.metadata?.type === "radarr" && (
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
{t(
|
{t(`widget.calendar.option.releaseType.options.${event.metadata.releaseType}`)}
|
||||||
`widget.calendar.option.releaseType.options.${event.dates.find(({ date }) => event.date === date)?.type ?? "inCinemas"}`,
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
) : (
|
|
||||||
<Group gap={3} wrap="nowrap" align={"center"}>
|
|
||||||
<IconClock opacity={0.7} size={"1rem"} />
|
|
||||||
<Text c={"dimmed"} size={"sm"}>
|
|
||||||
{dayjs(event.date).format("HH:mm")}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Group gap={3} wrap="nowrap" align={"center"}>
|
||||||
|
<IconClock opacity={0.7} size={"1rem"} />
|
||||||
|
<Text c={"dimmed"} size={"sm"}>
|
||||||
|
{dayjs(event.startDate).format("HH:mm")}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{event.endDate !== null && (
|
||||||
|
<>
|
||||||
|
-{" "}
|
||||||
|
<Text c={"dimmed"} size={"sm"}>
|
||||||
|
{dayjs(event.endDate).format("HH:mm")}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
{event.description && (
|
|
||||||
|
{event.location !== null && (
|
||||||
|
<Group gap={4} mb={isNullOrWhitespace(event.description) ? 0 : "sm"}>
|
||||||
|
<IconPin opacity={0.7} size={"1rem"} />
|
||||||
|
<Text size={"xs"} c={"dimmed"} lineClamp={1}>
|
||||||
|
{event.location}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isNullOrWhitespace(event.description) && (
|
||||||
<Text size={"xs"} c={"dimmed"} lineClamp={2}>
|
<Text size={"xs"} c={"dimmed"} lineClamp={2}>
|
||||||
{event.description}
|
{event.description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{event.links.length > 0 && (
|
{event.links.length > 0 && (
|
||||||
<Group pt={5} gap={5} mt={"auto"} wrap="nowrap">
|
<Group pt={5} gap={5} mt={"auto"} wrap="nowrap">
|
||||||
{event.links.map((link) => (
|
{event.links
|
||||||
<Button
|
.filter((link) => link.href)
|
||||||
key={link.href}
|
.map((link) => (
|
||||||
component={"a"}
|
<Button
|
||||||
href={link.href.toString()}
|
key={link.href}
|
||||||
target={"_blank"}
|
component={"a"}
|
||||||
size={"xs"}
|
href={link.href.toString()}
|
||||||
radius={"xl"}
|
target={"_blank"}
|
||||||
variant={link.color ? undefined : "default"}
|
size={"xs"}
|
||||||
styles={{
|
radius={"xl"}
|
||||||
root: {
|
variant={link.color ? undefined : "default"}
|
||||||
backgroundColor: link.color,
|
styles={{
|
||||||
color: link.isDark && colorScheme === "dark" ? "white" : "black",
|
root: {
|
||||||
"&:hover": link.color
|
backgroundColor: link.color,
|
||||||
? {
|
color: link.isDark && colorScheme === "dark" ? "white" : "black",
|
||||||
backgroundColor: link.isDark ? lighten(link.color, 0.1) : darken(link.color, 0.1),
|
"&:hover": link.color
|
||||||
}
|
? {
|
||||||
: undefined,
|
backgroundColor: link.isDark ? lighten(link.color, 0.1) : darken(link.color, 0.1),
|
||||||
},
|
}
|
||||||
}}
|
: undefined,
|
||||||
leftSection={link.logo ? <Image src={link.logo} w={20} h={20} /> : undefined}
|
},
|
||||||
>
|
}}
|
||||||
<Text>{link.name}</Text>
|
leftSection={link.logo ? <Image src={link.logo} fit="contain" w={20} h={20} /> : undefined}
|
||||||
</Button>
|
>
|
||||||
))}
|
<Text>{link.name}</Text>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ interface NotificationIndicatorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps) => {
|
const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps) => {
|
||||||
const notificationEvents = [...new Set(events.map((event) => event.links[0]?.notificationColor))].filter(String);
|
const notificationEvents = [...new Set(events.map((event) => event.indicatorColor))].filter(String);
|
||||||
/* position bottom is lower when small to not be on top of number*/
|
/* position bottom is lower when small to not be on top of number*/
|
||||||
return (
|
return (
|
||||||
<Flex w="75%" pos={"absolute"} bottom={isSmall ? 4 : 10} left={"12.5%"} p={0} direction={"row"} justify={"center"}>
|
<Flex w="75%" pos={"absolute"} bottom={isSmall ? 4 : 10} left={"12.5%"} p={0} direction={"row"} justify={"center"}>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import dayjs from "dayjs";
|
|||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useRequiredBoard } from "@homarr/boards/context";
|
import { useRequiredBoard } from "@homarr/boards/context";
|
||||||
import type { CalendarEvent } from "@homarr/integrations/types";
|
|
||||||
import { useSettings } from "@homarr/settings";
|
import { useSettings } from "@homarr/settings";
|
||||||
|
|
||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
@@ -124,13 +123,11 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
|
|||||||
}}
|
}}
|
||||||
renderDay={(tileDate) => {
|
renderDay={(tileDate) => {
|
||||||
const eventsForDate = events
|
const eventsForDate = events
|
||||||
.map((event) => ({
|
.filter((event) => dayjs(event.startDate).isSame(tileDate, "day"))
|
||||||
...event,
|
.filter(
|
||||||
date: (event.dates?.filter(({ type }) => options.releaseType.includes(type)) ?? [event]).find(({ date }) =>
|
(event) => event.metadata?.type !== "radarr" || options.releaseType.includes(event.metadata.releaseType),
|
||||||
dayjs(date).isSame(tileDate, "day"),
|
)
|
||||||
)?.date,
|
.sort((eventA, eventB) => eventA.startDate.getTime() - eventB.startDate.getTime());
|
||||||
}))
|
|
||||||
.filter((event): event is CalendarEvent => Boolean(event.date));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarDay
|
<CalendarDay
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import "./notebook.css";
|
|||||||
import { useSession } from "@homarr/auth/client";
|
import { useSession } from "@homarr/auth/client";
|
||||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||||
import { useRequiredBoard } from "@homarr/boards/context";
|
import { useRequiredBoard } from "@homarr/boards/context";
|
||||||
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
|
|
||||||
const iconProps = {
|
const iconProps = {
|
||||||
size: 30,
|
size: 30,
|
||||||
@@ -240,9 +241,20 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
|
|||||||
return false;
|
return false;
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const handleEditCancel = useCallback(() => {
|
const handleEditCancel = useCallback(() => {
|
||||||
setIsEditing(handleEditCancelCallback);
|
openConfirmModal({
|
||||||
}, [setIsEditing, handleEditCancelCallback]);
|
title: t("widget.notebook.dismiss.title"),
|
||||||
|
children: t("widget.notebook.dismiss.message"),
|
||||||
|
labels: {
|
||||||
|
confirm: t("widget.notebook.dismiss.action.discard"),
|
||||||
|
cancel: t("widget.notebook.dismiss.action.keepEditing"),
|
||||||
|
},
|
||||||
|
onConfirm: () => {
|
||||||
|
setIsEditing(handleEditCancelCallback);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [setIsEditing, handleEditCancelCallback, openConfirmModal, t]);
|
||||||
|
|
||||||
const handleEditToggle = useCallback(() => {
|
const handleEditToggle = useCallback(() => {
|
||||||
setIsEditing(handleEditToggleCallback);
|
setIsEditing(handleEditToggleCallback);
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import { Box, Group, Paper, Stack, Text } from "@mantine/core";
|
import { Box, Group, Paper, Stack, Text } from "@mantine/core";
|
||||||
|
import { IconNetwork } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { humanFileSize } from "@homarr/common";
|
import { humanFileSize } from "@homarr/common";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
|
import type { LabelDisplayModeOption } from "..";
|
||||||
import { CommonChart } from "./common-chart";
|
import { CommonChart } from "./common-chart";
|
||||||
|
|
||||||
export const CombinedNetworkTrafficChart = ({
|
export const CombinedNetworkTrafficChart = ({
|
||||||
usageOverTime,
|
usageOverTime,
|
||||||
|
hasShadow,
|
||||||
|
labelDisplayMode,
|
||||||
}: {
|
}: {
|
||||||
usageOverTime: {
|
usageOverTime: {
|
||||||
up: number;
|
up: number;
|
||||||
down: number;
|
down: number;
|
||||||
}[];
|
}[];
|
||||||
|
hasShadow: boolean;
|
||||||
|
labelDisplayMode: LabelDisplayModeOption;
|
||||||
}) => {
|
}) => {
|
||||||
const chartData = usageOverTime.map((usage, index) => ({ index, up: usage.up, down: usage.down }));
|
const chartData = usageOverTime.map((usage, index) => ({ index, up: usage.up, down: usage.down }));
|
||||||
const t = useScopedI18n("widget.systemResources.card");
|
const t = useScopedI18n("widget.systemResources.card");
|
||||||
@@ -25,7 +31,10 @@ export const CombinedNetworkTrafficChart = ({
|
|||||||
{ name: "down", color: "yellow.5" },
|
{ name: "down", color: "yellow.5" },
|
||||||
]}
|
]}
|
||||||
title={t("network")}
|
title={t("network")}
|
||||||
|
icon={IconNetwork}
|
||||||
yAxisProps={{ domain: [0, "dataMax"] }}
|
yAxisProps={{ domain: [0, "dataMax"] }}
|
||||||
|
chartType={hasShadow ? "area" : "line"}
|
||||||
|
labelDisplayMode={labelDisplayMode}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|||||||
@@ -1,28 +1,38 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import type { LineChartSeries } from "@mantine/charts";
|
import type { ReactNode } from "react";
|
||||||
import { LineChart } from "@mantine/charts";
|
import type { AreaChartSeries } from "@mantine/charts";
|
||||||
|
import { AreaChart, LineChart } from "@mantine/charts";
|
||||||
import { Card, Center, Group, Loader, Stack, Text, useMantineColorScheme, useMantineTheme } from "@mantine/core";
|
import { Card, Center, Group, Loader, Stack, Text, useMantineColorScheme, useMantineTheme } from "@mantine/core";
|
||||||
import { useElementSize, useHover, useMergedRef } from "@mantine/hooks";
|
import { useElementSize, useHover, useMergedRef } from "@mantine/hooks";
|
||||||
import type { TooltipProps, YAxisProps } from "recharts";
|
import type { TooltipProps, YAxisProps } from "recharts";
|
||||||
|
|
||||||
import { useRequiredBoard } from "@homarr/boards/context";
|
import { useRequiredBoard } from "@homarr/boards/context";
|
||||||
|
import type { TablerIcon } from "@homarr/ui";
|
||||||
|
|
||||||
|
import type { LabelDisplayModeOption } from "..";
|
||||||
|
|
||||||
export const CommonChart = ({
|
export const CommonChart = ({
|
||||||
data,
|
data,
|
||||||
dataKey,
|
dataKey,
|
||||||
series,
|
series,
|
||||||
title,
|
title,
|
||||||
|
icon: Icon,
|
||||||
|
labelDisplayMode,
|
||||||
tooltipProps,
|
tooltipProps,
|
||||||
yAxisProps,
|
yAxisProps,
|
||||||
lastValue,
|
lastValue,
|
||||||
|
chartType = "line",
|
||||||
}: {
|
}: {
|
||||||
data: Record<string, any>[];
|
data: Record<string, any>[];
|
||||||
dataKey: string;
|
dataKey: string;
|
||||||
series: LineChartSeries[];
|
series: AreaChartSeries[];
|
||||||
title: string;
|
title: ReactNode;
|
||||||
|
icon: TablerIcon;
|
||||||
|
labelDisplayMode: LabelDisplayModeOption;
|
||||||
tooltipProps?: TooltipProps<number, any>;
|
tooltipProps?: TooltipProps<number, any>;
|
||||||
yAxisProps?: Omit<YAxisProps, "ref">;
|
yAxisProps?: Omit<YAxisProps, "ref">;
|
||||||
lastValue?: string;
|
lastValue?: string;
|
||||||
|
chartType?: "line" | "area";
|
||||||
}) => {
|
}) => {
|
||||||
const { ref: elementSizeRef, height } = useElementSize();
|
const { ref: elementSizeRef, height } = useElementSize();
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
@@ -35,6 +45,10 @@ export const CommonChart = ({
|
|||||||
const backgroundColor =
|
const backgroundColor =
|
||||||
scheme.colorScheme === "dark" ? `rgba(57, 57, 57, ${opacity})` : `rgba(246, 247, 248, ${opacity})`;
|
scheme.colorScheme === "dark" ? `rgba(57, 57, 57, ${opacity})` : `rgba(246, 247, 248, ${opacity})`;
|
||||||
|
|
||||||
|
const ChartComponent = chartType === "line" ? LineChart : AreaChart;
|
||||||
|
const showIcon = labelDisplayMode === "icon" || labelDisplayMode === "textWithIcon";
|
||||||
|
const showText = labelDisplayMode === "text" || labelDisplayMode === "textWithIcon";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -55,10 +69,14 @@ export const CommonChart = ({
|
|||||||
gap={5}
|
gap={5}
|
||||||
wrap={"nowrap"}
|
wrap={"nowrap"}
|
||||||
style={{ zIndex: 2, pointerEvents: "none" }}
|
style={{ zIndex: 2, pointerEvents: "none" }}
|
||||||
|
align="center"
|
||||||
>
|
>
|
||||||
<Text c={"dimmed"} size={height > 100 ? "md" : "xs"} fw={"bold"}>
|
{showIcon && <Icon color={"var(--mantine-color-dimmed)"} size={height > 100 ? 20 : 14} stroke={1.5} />}
|
||||||
{title}
|
{showText && (
|
||||||
</Text>
|
<Text c={"dimmed"} size={height > 100 ? "md" : "xs"} fw={"bold"}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
{lastValue && (
|
{lastValue && (
|
||||||
<Text c={"dimmed"} size={height > 100 ? "md" : "xs"} lineClamp={1}>
|
<Text c={"dimmed"} size={height > 100 ? "md" : "xs"} lineClamp={1}>
|
||||||
{lastValue}
|
{lastValue}
|
||||||
@@ -73,7 +91,7 @@ export const CommonChart = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<LineChart
|
<ChartComponent
|
||||||
data={data}
|
data={data}
|
||||||
dataKey={dataKey}
|
dataKey={dataKey}
|
||||||
h={"100%"}
|
h={"100%"}
|
||||||
@@ -90,6 +108,7 @@ export const CommonChart = ({
|
|||||||
tooltipProps={tooltipProps}
|
tooltipProps={tooltipProps}
|
||||||
withTooltip={height >= 64}
|
withTooltip={height >= 64}
|
||||||
yAxisProps={yAxisProps}
|
yAxisProps={yAxisProps}
|
||||||
|
fillOpacity={chartType === "area" ? 0.3 : undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import { Paper, Text } from "@mantine/core";
|
import { Paper, Text } from "@mantine/core";
|
||||||
|
import { IconCpu } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
|
import type { LabelDisplayModeOption } from "..";
|
||||||
import { CommonChart } from "./common-chart";
|
import { CommonChart } from "./common-chart";
|
||||||
|
|
||||||
export const SystemResourceCPUChart = ({ cpuUsageOverTime }: { cpuUsageOverTime: number[] }) => {
|
export const SystemResourceCPUChart = ({
|
||||||
|
cpuUsageOverTime,
|
||||||
|
hasShadow,
|
||||||
|
labelDisplayMode,
|
||||||
|
}: {
|
||||||
|
cpuUsageOverTime: number[];
|
||||||
|
hasShadow: boolean;
|
||||||
|
labelDisplayMode: LabelDisplayModeOption;
|
||||||
|
}) => {
|
||||||
const chartData = cpuUsageOverTime.map((usage, index) => ({ index, usage }));
|
const chartData = cpuUsageOverTime.map((usage, index) => ({ index, usage }));
|
||||||
const t = useScopedI18n("widget.systemResources.card");
|
const t = useScopedI18n("widget.systemResources.card");
|
||||||
|
|
||||||
@@ -14,11 +24,14 @@ export const SystemResourceCPUChart = ({ cpuUsageOverTime }: { cpuUsageOverTime:
|
|||||||
dataKey={"index"}
|
dataKey={"index"}
|
||||||
series={[{ name: "usage", color: "blue.5" }]}
|
series={[{ name: "usage", color: "blue.5" }]}
|
||||||
title={t("cpu")}
|
title={t("cpu")}
|
||||||
|
icon={IconCpu}
|
||||||
lastValue={
|
lastValue={
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
cpuUsageOverTime.length > 0 ? `${Math.round(cpuUsageOverTime[cpuUsageOverTime.length - 1]!)}%` : undefined
|
cpuUsageOverTime.length > 0 ? `${Math.round(cpuUsageOverTime[cpuUsageOverTime.length - 1]!)}%` : undefined
|
||||||
}
|
}
|
||||||
|
chartType={hasShadow ? "area" : "line"}
|
||||||
yAxisProps={{ domain: [0, 100] }}
|
yAxisProps={{ domain: [0, 100] }}
|
||||||
|
labelDisplayMode={labelDisplayMode}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { Paper, Text } from "@mantine/core";
|
import { Paper, Text } from "@mantine/core";
|
||||||
|
import { IconBrain } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { humanFileSize } from "@homarr/common";
|
import { humanFileSize } from "@homarr/common";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
|
import type { LabelDisplayModeOption } from "..";
|
||||||
import { CommonChart } from "./common-chart";
|
import { CommonChart } from "./common-chart";
|
||||||
|
|
||||||
export const SystemResourceMemoryChart = ({
|
export const SystemResourceMemoryChart = ({
|
||||||
memoryUsageOverTime,
|
memoryUsageOverTime,
|
||||||
totalCapacityInBytes,
|
totalCapacityInBytes,
|
||||||
|
hasShadow,
|
||||||
|
labelDisplayMode,
|
||||||
}: {
|
}: {
|
||||||
memoryUsageOverTime: number[];
|
memoryUsageOverTime: number[];
|
||||||
totalCapacityInBytes: number;
|
totalCapacityInBytes: number;
|
||||||
|
hasShadow: boolean;
|
||||||
|
labelDisplayMode: LabelDisplayModeOption;
|
||||||
}) => {
|
}) => {
|
||||||
const chartData = memoryUsageOverTime.map((usage, index) => ({ index, usage }));
|
const chartData = memoryUsageOverTime.map((usage, index) => ({ index, usage }));
|
||||||
const t = useScopedI18n("widget.systemResources.card");
|
const t = useScopedI18n("widget.systemResources.card");
|
||||||
@@ -27,8 +33,11 @@ export const SystemResourceMemoryChart = ({
|
|||||||
dataKey={"index"}
|
dataKey={"index"}
|
||||||
series={[{ name: "usage", color: "red.6" }]}
|
series={[{ name: "usage", color: "red.6" }]}
|
||||||
title={t("memory")}
|
title={t("memory")}
|
||||||
|
icon={IconBrain}
|
||||||
|
labelDisplayMode={labelDisplayMode}
|
||||||
yAxisProps={{ domain: [0, totalCapacityInBytes] }}
|
yAxisProps={{ domain: [0, totalCapacityInBytes] }}
|
||||||
lastValue={percentageUsed !== undefined ? `${Math.round(percentageUsed * 100)}%` : undefined}
|
lastValue={percentageUsed !== undefined ? `${Math.round(percentageUsed * 100)}%` : undefined}
|
||||||
|
chartType={hasShadow ? "area" : "line"}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user