mirror of
https://github.com/gethomepage/homepage.git
synced 2026-04-10 20:21:20 -07:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca82155a1 | ||
|
|
1accddf29a | ||
|
|
636d62106c | ||
|
|
d048888d99 | ||
|
|
24804f39fc | ||
|
|
70f8c67d3c | ||
|
|
038f5c3b9f | ||
|
|
ade4c733c7 | ||
|
|
bdbd2e6ff0 | ||
|
|
5f4b0b4e33 | ||
|
|
a56a05b553 | ||
|
|
11d1ceb755 | ||
|
|
6df1cfdeba | ||
|
|
d64fbae776 | ||
|
|
5f7e5430c4 | ||
|
|
3cc3986ec3 | ||
|
|
5af0ab1686 | ||
|
|
f0f5c3c15c | ||
|
|
2ed1da4411 | ||
|
|
e1023466b1 | ||
|
|
9fe5ad62f1 | ||
|
|
173883000f |
7
.github/workflows/docker-publish.yml
vendored
7
.github/workflows/docker-publish.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- feature/**
|
||||
- fix/**
|
||||
- dev
|
||||
tags: [ 'v*.*.*' ]
|
||||
pull_request:
|
||||
@@ -60,7 +61,7 @@ jobs:
|
||||
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
@@ -83,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -91,7 +92,7 @@ jobs:
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
8
.github/workflows/docs-publish.yml
vendored
8
.github/workflows/docs-publish.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||
- run: sudo apt-get install pngquant
|
||||
- name: Test Docs Build
|
||||
run: uv run --frozen zensical build --clean
|
||||
@@ -37,18 +37,18 @@ jobs:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
||||
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||
- run: sudo apt-get install pngquant
|
||||
- name: Build Docs
|
||||
run: uv run --frozen zensical build --clean
|
||||
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
with:
|
||||
path: site
|
||||
- uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
||||
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
id: deployment
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
2
.github/workflows/pr-quality.yml
vendored
2
.github/workflows/pr-quality.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
anti-slop:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: peakoss/anti-slop@a5a4b2440c9de6f65b64f0718a0136a1fdb04f6f # v0
|
||||
- uses: peakoss/anti-slop@85daca1880e9e1af197fc06ea03349daf08f4202 # v0
|
||||
with:
|
||||
max-failures: 4
|
||||
|
||||
6
.github/workflows/release-drafter.yml
vendored
6
.github/workflows/release-drafter.yml
vendored
@@ -26,14 +26,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
|
||||
uses: release-drafter/release-drafter@a6acf82562eee06318b77ab8cb0b11ed81c677a7 # v7
|
||||
uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
version: ${{ github.event.inputs.version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: github.event_name != 'workflow_dispatch' || github.event.inputs.version == ''
|
||||
uses: release-drafter/release-drafter@a6acf82562eee06318b77ab8cb0b11ed81c677a7 # v7
|
||||
uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
env:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter/autolabeler@ebb69bb56f1b0ebd19897745035726b19bef973e
|
||||
- uses: release-drafter/release-drafter/autolabeler@139054aeaa9adc52ab36ddf67437541f039b88e2
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
env:
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
with:
|
||||
version: 9
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
# Run Vitest directly so `--shard` is parsed as an option
|
||||
- run: pnpm -s exec vitest run --coverage --shard ${{ matrix.shard }}/4 --pool forks
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage/lcov.info
|
||||
|
||||
@@ -122,7 +122,7 @@ Use the `gethomepage.dev/pod-selector` selector to specify the pod used for the
|
||||
|
||||
### Traefik IngressRoute support
|
||||
|
||||
Homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set:
|
||||
If enabled (with `traefik: true` in kubernetes.yaml), homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set:
|
||||
|
||||
```yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
|
||||
12
package.json
12
package.json
@@ -18,17 +18,17 @@
|
||||
"@kubernetes/client-node": "^1.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"compare-versions": "^6.1.1",
|
||||
"dockerode": "^4.0.7",
|
||||
"dockerode": "^4.0.10",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"gamedig": "^5.3.2",
|
||||
"i18next": "^25.8.0",
|
||||
"i18next": "^25.10.9",
|
||||
"ical.js": "^2.2.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"json-rpc-2.0": "^1.7.0",
|
||||
"luxon": "^3.6.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"minecraftstatuspinger": "^1.2.2",
|
||||
"next": "^16.1.7",
|
||||
"next": "^16.2.3",
|
||||
"next-i18next": "^15.4.3",
|
||||
"ping": "^0.4.4",
|
||||
"pretty-bytes": "^7.1.0",
|
||||
@@ -47,10 +47,10 @@
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/compat": "^2.0.3",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@@ -64,7 +64,7 @@
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jsdom": "^28.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss": "^8.5.8",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-organize-imports": "^4.3.0",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
|
||||
344
pnpm-lock.yaml
generated
344
pnpm-lock.yaml
generated
@@ -21,8 +21,8 @@ importers:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
dockerode:
|
||||
specifier: ^4.0.7
|
||||
version: 4.0.7
|
||||
specifier: ^4.0.10
|
||||
version: 4.0.10
|
||||
follow-redirects:
|
||||
specifier: ^1.15.11
|
||||
version: 1.15.11
|
||||
@@ -30,8 +30,8 @@ importers:
|
||||
specifier: ^5.3.2
|
||||
version: 5.3.2
|
||||
i18next:
|
||||
specifier: ^25.8.0
|
||||
version: 25.8.0(typescript@5.7.3)
|
||||
specifier: ^25.10.9
|
||||
version: 25.10.9(typescript@5.7.3)
|
||||
ical.js:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
@@ -51,11 +51,11 @@ importers:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
next:
|
||||
specifier: ^16.1.7
|
||||
version: 16.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^16.2.3
|
||||
version: 16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
next-i18next:
|
||||
specifier: ^15.4.3
|
||||
version: 15.4.3(@types/react@19.0.10)(i18next@25.8.0(typescript@5.7.3))(next@16.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-i18next@15.5.3(i18next@25.8.0(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3))(react@19.2.4)
|
||||
version: 15.4.3(@types/react@19.0.10)(i18next@25.10.9(typescript@5.7.3))(next@16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-i18next@15.5.3(i18next@25.10.9(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3))(react@19.2.4)
|
||||
ping:
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
@@ -73,7 +73,7 @@ importers:
|
||||
version: 19.2.4(react@19.2.4)
|
||||
react-i18next:
|
||||
specifier: ^15.5.3
|
||||
version: 15.5.3(i18next@25.8.0(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3)
|
||||
version: 15.5.3(i18next@25.10.9(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3)
|
||||
react-icons:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0(react@19.2.4)
|
||||
@@ -103,8 +103,8 @@ importers:
|
||||
version: 1.6.11
|
||||
devDependencies:
|
||||
'@eslint/compat':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(eslint@9.25.1(jiti@2.6.1))
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3(eslint@9.25.1(jiti@2.6.1))
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
@@ -112,8 +112,8 @@ importers:
|
||||
specifier: ^9.39.2
|
||||
version: 9.39.2
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.10
|
||||
version: 0.5.10(tailwindcss@4.1.18)
|
||||
specifier: ^0.5.11
|
||||
version: 0.5.11(tailwindcss@4.1.18)
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
@@ -125,7 +125,7 @@ importers:
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react@19.0.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@24.1.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2))
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2))
|
||||
eslint:
|
||||
specifier: ^9.25.1
|
||||
version: 9.25.1(jiti@2.6.1)
|
||||
@@ -154,8 +154,8 @@ importers:
|
||||
specifier: ^28.1.0
|
||||
version: 28.1.0
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
specifier: ^8.5.8
|
||||
version: 8.5.8
|
||||
prettier:
|
||||
specifier: ^3.8.1
|
||||
version: 3.8.1
|
||||
@@ -173,7 +173,7 @@ importers:
|
||||
version: 5.7.3
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@types/node@24.1.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)
|
||||
version: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)
|
||||
optionalDependencies:
|
||||
osx-temperature-sensor:
|
||||
specifier: ^1.0.8
|
||||
@@ -290,8 +290,8 @@ packages:
|
||||
'@emnapi/core@1.4.0':
|
||||
resolution: {integrity: sha512-H+N/FqT07NmLmt6OFFtDfwe8PNygprzBikrEMyQfgqSmT0vzE515Pz7R8izwB9q/zsH/MA64AKoul3sA6/CzVg==}
|
||||
|
||||
'@emnapi/runtime@1.9.0':
|
||||
resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==}
|
||||
'@emnapi/runtime@1.9.2':
|
||||
resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
|
||||
|
||||
'@emnapi/wasi-threads@1.0.1':
|
||||
resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==}
|
||||
@@ -462,8 +462,8 @@ packages:
|
||||
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||
|
||||
'@eslint/compat@2.0.2':
|
||||
resolution: {integrity: sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==}
|
||||
'@eslint/compat@2.0.3':
|
||||
resolution: {integrity: sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
peerDependencies:
|
||||
eslint: ^8.40 || 9 || 10
|
||||
@@ -483,8 +483,8 @@ packages:
|
||||
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@eslint/core@1.1.0':
|
||||
resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
|
||||
'@eslint/core@1.1.1':
|
||||
resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
@@ -537,8 +537,8 @@ packages:
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@grpc/grpc-js@1.13.4':
|
||||
resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==}
|
||||
'@grpc/grpc-js@1.14.3':
|
||||
resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==}
|
||||
engines: {node: '>=12.10.0'}
|
||||
|
||||
'@grpc/proto-loader@0.7.15':
|
||||
@@ -546,6 +546,11 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
'@grpc/proto-loader@0.8.0':
|
||||
resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
'@headlessui/react@2.2.9':
|
||||
resolution: {integrity: sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -759,56 +764,56 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.8':
|
||||
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
|
||||
|
||||
'@next/env@16.1.7':
|
||||
resolution: {integrity: sha512-rJJbIdJB/RQr2F1nylZr/PJzamvNNhfr3brdKP6s/GW850jbtR70QlSfFselvIBbcPUOlQwBakexjFzqLzF6pg==}
|
||||
'@next/env@16.2.3':
|
||||
resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==}
|
||||
|
||||
'@next/eslint-plugin-next@15.5.11':
|
||||
resolution: {integrity: sha512-tS/HYQOjIoX9ZNDQitba/baS8sTvo3ekY6Vgdx5lmhN4jov082bdApIChXr94qhMZHvEciz9DZglFFnhguQp/A==}
|
||||
|
||||
'@next/swc-darwin-arm64@16.1.7':
|
||||
resolution: {integrity: sha512-b2wWIE8sABdyafc4IM8r5Y/dS6kD80JRtOGrUiKTsACFQfWWgUQ2NwoUX1yjFMXVsAwcQeNpnucF2ZrujsBBPg==}
|
||||
'@next/swc-darwin-arm64@16.2.3':
|
||||
resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.1.7':
|
||||
resolution: {integrity: sha512-zcnVaaZulS1WL0Ss38R5Q6D2gz7MtBu8GZLPfK+73D/hp4GFMrC2sudLky1QibfV7h6RJBJs/gOFvYP0X7UVlQ==}
|
||||
'@next/swc-darwin-x64@16.2.3':
|
||||
resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.1.7':
|
||||
resolution: {integrity: sha512-2ant89Lux/Q3VyC8vNVg7uBaFVP9SwoK2jJOOR0L8TQnX8CAYnh4uctAScy2Hwj2dgjVHqHLORQZJ2wH6VxhSQ==}
|
||||
'@next/swc-linux-arm64-gnu@16.2.3':
|
||||
resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.7':
|
||||
resolution: {integrity: sha512-uufcze7LYv0FQg9GnNeZ3/whYfo+1Q3HnQpm16o6Uyi0OVzLlk2ZWoY7j07KADZFY8qwDbsmFnMQP3p3+Ftprw==}
|
||||
'@next/swc-linux-arm64-musl@16.2.3':
|
||||
resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.7':
|
||||
resolution: {integrity: sha512-KWVf2gxYvHtvuT+c4MBOGxuse5TD7DsMFYSxVxRBnOzok/xryNeQSjXgxSv9QpIVlaGzEn/pIuI6Koosx8CGWA==}
|
||||
'@next/swc-linux-x64-gnu@16.2.3':
|
||||
resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.7':
|
||||
resolution: {integrity: sha512-HguhaGwsGr1YAGs68uRKc4aGWxLET+NevJskOcCAwXbwj0fYX0RgZW2gsOCzr9S11CSQPIkxmoSbuVaBp4Z3dA==}
|
||||
'@next/swc-linux-x64-musl@16.2.3':
|
||||
resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.7':
|
||||
resolution: {integrity: sha512-S0n3KrDJokKTeFyM/vGGGR8+pCmXYrjNTk2ZozOL1C/JFdfUIL9O1ATaJOl5r2POe56iRChbsszrjMAdWSv7kQ==}
|
||||
'@next/swc-win32-arm64-msvc@16.2.3':
|
||||
resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.1.7':
|
||||
resolution: {integrity: sha512-mwgtg8CNZGYm06LeEd+bNnOUfwOyNem/rOiP14Lsz+AnUY92Zq/LXwtebtUiaeVkhbroRCQ0c8GlR4UT1U+0yg==}
|
||||
'@next/swc-win32-x64-msvc@16.2.3':
|
||||
resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -1062,15 +1067,15 @@ packages:
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
'@swc/helpers@0.5.19':
|
||||
resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==}
|
||||
'@swc/helpers@0.5.21':
|
||||
resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
|
||||
|
||||
'@szmarczak/http-timer@5.0.1':
|
||||
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
'@tailwindcss/forms@0.5.10':
|
||||
resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
|
||||
'@tailwindcss/forms@0.5.11':
|
||||
resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
|
||||
|
||||
@@ -1262,8 +1267,8 @@ packages:
|
||||
'@types/node@22.13.4':
|
||||
resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==}
|
||||
|
||||
'@types/node@24.1.0':
|
||||
resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==}
|
||||
'@types/node@25.5.0':
|
||||
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
|
||||
|
||||
'@types/prismjs@1.26.5':
|
||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||
@@ -1579,8 +1584,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.8:
|
||||
resolution: {integrity: sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==}
|
||||
baseline-browser-mapping@2.10.17:
|
||||
resolution: {integrity: sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1606,8 +1611,8 @@ packages:
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
buildcheck@0.0.6:
|
||||
resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==}
|
||||
buildcheck@0.0.7:
|
||||
resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
bytes@3.1.2:
|
||||
@@ -1646,8 +1651,8 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001780:
|
||||
resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==}
|
||||
caniuse-lite@1.0.30001787:
|
||||
resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
|
||||
|
||||
chai@5.3.3:
|
||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||
@@ -1890,12 +1895,12 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
docker-modem@5.0.6:
|
||||
resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==}
|
||||
docker-modem@5.0.7:
|
||||
resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==}
|
||||
engines: {node: '>= 8.0'}
|
||||
|
||||
dockerode@4.0.7:
|
||||
resolution: {integrity: sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA==}
|
||||
dockerode@4.0.10:
|
||||
resolution: {integrity: sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==}
|
||||
engines: {node: '>= 8.0'}
|
||||
|
||||
doctrine@2.1.0:
|
||||
@@ -2388,10 +2393,10 @@ packages:
|
||||
i18next-fs-backend@2.6.1:
|
||||
resolution: {integrity: sha512-eYWTX7QT7kJ0sZyCPK6x1q+R63zvNKv2D6UdbMf15A8vNb2ZLyw4NNNZxPFwXlIv/U+oUtg8SakW6ZgJZcoqHQ==}
|
||||
|
||||
i18next@25.8.0:
|
||||
resolution: {integrity: sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==}
|
||||
i18next@25.10.9:
|
||||
resolution: {integrity: sha512-hQY9/bFoQKGlSKMlaCuLR8w1h5JjieqrsnZvEmj1Ja6Ec7fbyc4cTrCsY9mb9Sd8YQ/swsrKz1S9M8AcvVI70w==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
typescript: ^5 || ^6
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
@@ -2862,8 +2867,8 @@ packages:
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nan@2.23.0:
|
||||
resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==}
|
||||
nan@2.26.2:
|
||||
resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
@@ -2885,8 +2890,8 @@ packages:
|
||||
react: '>= 17.0.2'
|
||||
react-i18next: '>= 13.5.0'
|
||||
|
||||
next@16.1.7:
|
||||
resolution: {integrity: sha512-WM0L7WrSvKwoLegLYr6V+mz+RIofqQgVAfHhMp9a88ms0cFX8iX9ew+snpWlSBwpkURJOUdvCEt3uLl3NNzvWg==}
|
||||
next@16.2.3:
|
||||
resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -3043,8 +3048,8 @@ packages:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
postcss@8.5.8:
|
||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
@@ -3089,12 +3094,12 @@ packages:
|
||||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
protobufjs@7.5.3:
|
||||
resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==}
|
||||
protobufjs@7.5.4:
|
||||
resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
pump@3.0.3:
|
||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
@@ -3357,8 +3362,8 @@ packages:
|
||||
split-ca@1.0.1:
|
||||
resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==}
|
||||
|
||||
ssh2@1.16.0:
|
||||
resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==}
|
||||
ssh2@1.17.0:
|
||||
resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
stable-hash@0.0.5:
|
||||
@@ -3512,8 +3517,8 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-fs@2.1.3:
|
||||
resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==}
|
||||
tar-fs@2.1.4:
|
||||
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
|
||||
|
||||
tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
@@ -3646,8 +3651,8 @@ packages:
|
||||
undici-types@6.20.0:
|
||||
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||
|
||||
undici-types@7.8.0:
|
||||
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
|
||||
undici@7.24.4:
|
||||
resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==}
|
||||
@@ -3694,8 +3699,8 @@ packages:
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite@7.3.1:
|
||||
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
|
||||
vite@7.3.2:
|
||||
resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -3992,7 +3997,7 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.0':
|
||||
'@emnapi/runtime@1.9.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
@@ -4087,9 +4092,9 @@ snapshots:
|
||||
|
||||
'@eslint-community/regexpp@4.12.1': {}
|
||||
|
||||
'@eslint/compat@2.0.2(eslint@9.25.1(jiti@2.6.1))':
|
||||
'@eslint/compat@2.0.3(eslint@9.25.1(jiti@2.6.1))':
|
||||
dependencies:
|
||||
'@eslint/core': 1.1.0
|
||||
'@eslint/core': 1.1.1
|
||||
optionalDependencies:
|
||||
eslint: 9.25.1(jiti@2.6.1)
|
||||
|
||||
@@ -4107,7 +4112,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/core@1.1.0':
|
||||
'@eslint/core@1.1.1':
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
@@ -4163,16 +4168,23 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@grpc/grpc-js@1.13.4':
|
||||
'@grpc/grpc-js@1.14.3':
|
||||
dependencies:
|
||||
'@grpc/proto-loader': 0.7.15
|
||||
'@grpc/proto-loader': 0.8.0
|
||||
'@js-sdsl/ordered-map': 4.4.2
|
||||
|
||||
'@grpc/proto-loader@0.7.15':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.3.2
|
||||
protobufjs: 7.5.3
|
||||
protobufjs: 7.5.4
|
||||
yargs: 17.7.2
|
||||
|
||||
'@grpc/proto-loader@0.8.0':
|
||||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.3.2
|
||||
protobufjs: 7.5.4
|
||||
yargs: 17.7.2
|
||||
|
||||
'@headlessui/react@2.2.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
@@ -4283,7 +4295,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.9.0
|
||||
'@emnapi/runtime': 1.9.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
@@ -4367,38 +4379,38 @@ snapshots:
|
||||
'@napi-rs/wasm-runtime@0.2.8':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.4.0
|
||||
'@emnapi/runtime': 1.9.0
|
||||
'@emnapi/runtime': 1.9.2
|
||||
'@tybys/wasm-util': 0.9.0
|
||||
optional: true
|
||||
|
||||
'@next/env@16.1.7': {}
|
||||
'@next/env@16.2.3': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.5.11':
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
'@next/swc-darwin-arm64@16.1.7':
|
||||
'@next/swc-darwin-arm64@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.1.7':
|
||||
'@next/swc-darwin-x64@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.1.7':
|
||||
'@next/swc-linux-arm64-gnu@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.7':
|
||||
'@next/swc-linux-arm64-musl@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.7':
|
||||
'@next/swc-linux-x64-gnu@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.7':
|
||||
'@next/swc-linux-x64-musl@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.7':
|
||||
'@next/swc-win32-arm64-msvc@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.1.7':
|
||||
'@next/swc-win32-x64-msvc@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
@@ -4448,7 +4460,7 @@ snapshots:
|
||||
'@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-types/shared': 3.32.1(react@19.2.4)
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
clsx: 2.1.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
@@ -4459,13 +4471,13 @@ snapshots:
|
||||
'@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-stately/flags': 3.1.2
|
||||
'@react-types/shared': 3.32.1(react@19.2.4)
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@react-aria/ssr@3.9.10(react@19.2.4)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
react: 19.2.4
|
||||
|
||||
'@react-aria/utils@3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
@@ -4474,18 +4486,18 @@ snapshots:
|
||||
'@react-stately/flags': 3.1.2
|
||||
'@react-stately/utils': 3.10.8(react@19.2.4)
|
||||
'@react-types/shared': 3.32.1(react@19.2.4)
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
clsx: 2.1.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@react-stately/flags@3.1.2':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
|
||||
'@react-stately/utils@3.10.8(react@19.2.4)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.19
|
||||
'@swc/helpers': 0.5.21
|
||||
react: 19.2.4
|
||||
|
||||
'@react-types/shared@3.32.1(react@19.2.4)':
|
||||
@@ -4598,7 +4610,7 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@swc/helpers@0.5.19':
|
||||
'@swc/helpers@0.5.21':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -4606,7 +4618,7 @@ snapshots:
|
||||
dependencies:
|
||||
defer-to-connect: 2.0.1
|
||||
|
||||
'@tailwindcss/forms@0.5.10(tailwindcss@4.1.18)':
|
||||
'@tailwindcss/forms@0.5.11(tailwindcss@4.1.18)':
|
||||
dependencies:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 4.1.18
|
||||
@@ -4677,7 +4689,7 @@ snapshots:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.1.18
|
||||
'@tailwindcss/oxide': 4.1.18
|
||||
postcss: 8.5.6
|
||||
postcss: 8.5.8
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
'@tanstack/react-virtual@3.13.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
@@ -4781,9 +4793,9 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
||||
'@types/node@24.1.0':
|
||||
'@types/node@25.5.0':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
undici-types: 7.18.2
|
||||
|
||||
'@types/prismjs@1.26.5': {}
|
||||
|
||||
@@ -4859,7 +4871,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.29.0
|
||||
'@typescript-eslint/visitor-keys': 8.29.0
|
||||
debug: 4.4.3
|
||||
debug: 4.4.1
|
||||
fast-glob: 3.3.3
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
@@ -4932,7 +4944,7 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.1.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2))':
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
@@ -4947,7 +4959,7 @@ snapshots:
|
||||
std-env: 3.10.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/node@24.1.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)
|
||||
vitest: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4959,13 +4971,13 @@ snapshots:
|
||||
chai: 5.3.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2))':
|
||||
'@vitest/mocker@3.2.4(vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -5146,7 +5158,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.8: {}
|
||||
baseline-browser-mapping@2.10.17: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
dependencies:
|
||||
@@ -5180,7 +5192,7 @@ snapshots:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
buildcheck@0.0.6:
|
||||
buildcheck@0.0.7:
|
||||
optional: true
|
||||
|
||||
bytes@3.1.2: {}
|
||||
@@ -5223,7 +5235,7 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001780: {}
|
||||
caniuse-lite@1.0.30001787: {}
|
||||
|
||||
chai@5.3.3:
|
||||
dependencies:
|
||||
@@ -5293,8 +5305,8 @@ snapshots:
|
||||
|
||||
cpu-features@0.0.10:
|
||||
dependencies:
|
||||
buildcheck: 0.0.6
|
||||
nan: 2.23.0
|
||||
buildcheck: 0.0.7
|
||||
nan: 2.26.2
|
||||
optional: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -5434,23 +5446,23 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
docker-modem@5.0.6:
|
||||
docker-modem@5.0.7:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
readable-stream: 3.6.2
|
||||
split-ca: 1.0.1
|
||||
ssh2: 1.16.0
|
||||
ssh2: 1.17.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
dockerode@4.0.7:
|
||||
dockerode@4.0.10:
|
||||
dependencies:
|
||||
'@balena/dockerignore': 1.0.2
|
||||
'@grpc/grpc-js': 1.13.4
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
'@grpc/proto-loader': 0.7.15
|
||||
docker-modem: 5.0.6
|
||||
protobufjs: 7.5.3
|
||||
tar-fs: 2.1.3
|
||||
docker-modem: 5.0.7
|
||||
protobufjs: 7.5.4
|
||||
tar-fs: 2.1.4
|
||||
uuid: 10.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -6182,9 +6194,9 @@ snapshots:
|
||||
|
||||
i18next-fs-backend@2.6.1: {}
|
||||
|
||||
i18next@25.8.0(typescript@5.7.3):
|
||||
i18next@25.10.9(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@babel/runtime': 7.29.2
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
@@ -6618,7 +6630,7 @@ snapshots:
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nan@2.23.0:
|
||||
nan@2.26.2:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
@@ -6627,39 +6639,39 @@ snapshots:
|
||||
|
||||
net@1.0.2: {}
|
||||
|
||||
next-i18next@15.4.3(@types/react@19.0.10)(i18next@25.8.0(typescript@5.7.3))(next@16.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-i18next@15.5.3(i18next@25.8.0(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3))(react@19.2.4):
|
||||
next-i18next@15.4.3(@types/react@19.0.10)(i18next@25.10.9(typescript@5.7.3))(next@16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-i18next@15.5.3(i18next@25.10.9(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@types/hoist-non-react-statics': 3.3.7(@types/react@19.0.10)
|
||||
core-js: 3.48.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
i18next: 25.8.0(typescript@5.7.3)
|
||||
i18next: 25.10.9(typescript@5.7.3)
|
||||
i18next-fs-backend: 2.6.1
|
||||
next: 16.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
next: 16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-i18next: 15.5.3(i18next@25.8.0(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3)
|
||||
react-i18next: 15.5.3(i18next@25.10.9(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
next@16.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
next@16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@next/env': 16.1.7
|
||||
'@next/env': 16.2.3
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.10.8
|
||||
caniuse-lite: 1.0.30001780
|
||||
baseline-browser-mapping: 2.10.17
|
||||
caniuse-lite: 1.0.30001787
|
||||
postcss: 8.4.31
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
styled-jsx: 5.1.6(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.1.7
|
||||
'@next/swc-darwin-x64': 16.1.7
|
||||
'@next/swc-linux-arm64-gnu': 16.1.7
|
||||
'@next/swc-linux-arm64-musl': 16.1.7
|
||||
'@next/swc-linux-x64-gnu': 16.1.7
|
||||
'@next/swc-linux-x64-musl': 16.1.7
|
||||
'@next/swc-win32-arm64-msvc': 16.1.7
|
||||
'@next/swc-win32-x64-msvc': 16.1.7
|
||||
'@next/swc-darwin-arm64': 16.2.3
|
||||
'@next/swc-darwin-x64': 16.2.3
|
||||
'@next/swc-linux-arm64-gnu': 16.2.3
|
||||
'@next/swc-linux-arm64-musl': 16.2.3
|
||||
'@next/swc-linux-x64-gnu': 16.2.3
|
||||
'@next/swc-linux-x64-musl': 16.2.3
|
||||
'@next/swc-win32-arm64-msvc': 16.2.3
|
||||
'@next/swc-win32-x64-msvc': 16.2.3
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -6796,7 +6808,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.6:
|
||||
postcss@8.5.8:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
@@ -6837,7 +6849,7 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
protobufjs@7.5.3:
|
||||
protobufjs@7.5.4:
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
'@protobufjs/base64': 1.1.2
|
||||
@@ -6849,10 +6861,10 @@ snapshots:
|
||||
'@protobufjs/path': 1.1.2
|
||||
'@protobufjs/pool': 1.1.0
|
||||
'@protobufjs/utf8': 1.1.0
|
||||
'@types/node': 24.1.0
|
||||
'@types/node': 25.5.0
|
||||
long: 5.3.2
|
||||
|
||||
pump@3.0.3:
|
||||
pump@3.0.4:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
@@ -6875,11 +6887,11 @@ snapshots:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-i18next@15.5.3(i18next@25.8.0(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3):
|
||||
react-i18next@15.5.3(i18next@25.10.9(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 25.8.0(typescript@5.7.3)
|
||||
i18next: 25.10.9(typescript@5.7.3)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
@@ -7190,13 +7202,13 @@ snapshots:
|
||||
|
||||
split-ca@1.0.1: {}
|
||||
|
||||
ssh2@1.16.0:
|
||||
ssh2@1.17.0:
|
||||
dependencies:
|
||||
asn1: 0.2.6
|
||||
bcrypt-pbkdf: 1.0.2
|
||||
optionalDependencies:
|
||||
cpu-features: 0.0.10
|
||||
nan: 2.23.0
|
||||
nan: 2.26.2
|
||||
|
||||
stable-hash@0.0.5: {}
|
||||
|
||||
@@ -7356,11 +7368,11 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tar-fs@2.1.3:
|
||||
tar-fs@2.1.4:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
mkdirp-classic: 0.5.3
|
||||
pump: 3.0.3
|
||||
pump: 3.0.4
|
||||
tar-stream: 2.2.0
|
||||
|
||||
tar-stream@2.2.0:
|
||||
@@ -7507,7 +7519,7 @@ snapshots:
|
||||
|
||||
undici-types@6.20.0: {}
|
||||
|
||||
undici-types@7.8.0: {}
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
undici@7.24.4: {}
|
||||
|
||||
@@ -7570,13 +7582,13 @@ snapshots:
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
|
||||
vite-node@3.2.4(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.1
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -7591,25 +7603,25 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2):
|
||||
dependencies:
|
||||
esbuild: 0.27.2
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.6
|
||||
postcss: 8.5.8
|
||||
rollup: 4.59.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.1.0
|
||||
'@types/node': 25.5.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
|
||||
vitest@3.2.4(@types/node@24.1.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2):
|
||||
vitest@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
'@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -7627,11 +7639,11 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 7.3.1(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite-node: 3.2.4(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.1.0
|
||||
'@types/node': 25.5.0
|
||||
jsdom: 28.1.0
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "Substelsel status onbekend"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Gesond",
|
||||
"degraded": "Gedegradeer",
|
||||
"no_data": "Geen bergingsdata beskikbaar nie"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "Statut du sous-système inconnu"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Sain",
|
||||
"degraded": "Dégradé",
|
||||
"no_data": "Aucune données de stockage disponible"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
@@ -115,12 +115,12 @@
|
||||
"jellyfin": {
|
||||
"playing": "En cours",
|
||||
"transcoding": "En cours d'encodage",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams",
|
||||
"movies": "Movies",
|
||||
"series": "Series",
|
||||
"episodes": "Episodes",
|
||||
"songs": "Songs"
|
||||
"bitrate": "Débit",
|
||||
"no_active": "Aucune lecture en cours",
|
||||
"movies": "Films",
|
||||
"series": "Séries",
|
||||
"episodes": "Épisodes",
|
||||
"songs": "Chansons"
|
||||
},
|
||||
"esphome": {
|
||||
"offline": "Hors ligne",
|
||||
@@ -190,11 +190,11 @@
|
||||
"plex_connection_error": "Vérifier la connexion à Plex"
|
||||
},
|
||||
"tracearr": {
|
||||
"no_active": "No Active Streams",
|
||||
"streams": "Streams",
|
||||
"transcodes": "Transcodes",
|
||||
"directplay": "Direct Play",
|
||||
"bitrate": "Bitrate"
|
||||
"no_active": "Aucune lecture en cours",
|
||||
"streams": "Lectures",
|
||||
"transcodes": "Transcodages",
|
||||
"directplay": "Lectures directes",
|
||||
"bitrate": "Débit"
|
||||
},
|
||||
"omada": {
|
||||
"connectedAp": "APs connectées",
|
||||
@@ -295,12 +295,12 @@
|
||||
"available": "Disponible"
|
||||
},
|
||||
"seerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available",
|
||||
"completed": "Completed",
|
||||
"processing": "Processing",
|
||||
"issues": "Open Issues"
|
||||
"pending": "En attente",
|
||||
"approved": "Validées",
|
||||
"available": "Disponibles",
|
||||
"completed": "Complétées",
|
||||
"processing": "En traitement",
|
||||
"issues": "Problèmes non résolus"
|
||||
},
|
||||
"netalertx": {
|
||||
"total": "Total",
|
||||
@@ -619,13 +619,13 @@
|
||||
"total": "Total"
|
||||
},
|
||||
"pangolin": {
|
||||
"orgs": "Orgs",
|
||||
"orgs": "Organisations",
|
||||
"sites": "Sites",
|
||||
"resources": "Ressources",
|
||||
"targets": "Cibles",
|
||||
"traffic": "Trafique",
|
||||
"in": "In",
|
||||
"out": "Out"
|
||||
"in": "Entrant",
|
||||
"out": "Sortant"
|
||||
},
|
||||
"peanut": {
|
||||
"battery_charge": "Charge de la batterie",
|
||||
@@ -724,8 +724,8 @@
|
||||
"volumeAvailable": "Disponible"
|
||||
},
|
||||
"dispatcharr": {
|
||||
"channels": "Channels",
|
||||
"streams": "Streams"
|
||||
"channels": "Chaînes",
|
||||
"streams": "Lectures"
|
||||
},
|
||||
"mylar": {
|
||||
"series": "Séries",
|
||||
@@ -816,10 +816,10 @@
|
||||
"series": "Séries"
|
||||
},
|
||||
"booklore": {
|
||||
"libraries": "Libraries",
|
||||
"books": "Books",
|
||||
"reading": "Reading",
|
||||
"finished": "Finished"
|
||||
"libraries": "Bibliothèques",
|
||||
"books": "Livres",
|
||||
"reading": "En cours de lecture",
|
||||
"finished": "Terminés"
|
||||
},
|
||||
"jdownloader": {
|
||||
"downloadCount": "File d'attente",
|
||||
@@ -1160,30 +1160,30 @@
|
||||
"artists": "Artistes"
|
||||
},
|
||||
"arcane": {
|
||||
"containers": "Containers",
|
||||
"containers": "Conteneurs",
|
||||
"images": "Images",
|
||||
"image_updates": "Image Updates",
|
||||
"images_unused": "Unused",
|
||||
"environment_required": "Environment ID Required"
|
||||
"image_updates": "Mises à jour d'images",
|
||||
"images_unused": "Inutilisées",
|
||||
"environment_required": "ID d'environnement requis"
|
||||
},
|
||||
"dockhand": {
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
"running": "En cours d'exécution",
|
||||
"stopped": "Arrêtés",
|
||||
"cpu": "CPU",
|
||||
"memory": "Memory",
|
||||
"memory": "Mémoire",
|
||||
"images": "Images",
|
||||
"volumes": "Volumes",
|
||||
"events_today": "Events Today",
|
||||
"pending_updates": "Pending Updates",
|
||||
"events_today": "Événements aujourd'hui",
|
||||
"pending_updates": "Mises à jour en attente",
|
||||
"stacks": "Stacks",
|
||||
"paused": "Paused",
|
||||
"paused": "En pause",
|
||||
"total": "Total",
|
||||
"environment_not_found": "Environment Not Found"
|
||||
"environment_not_found": "Environnement introuvable"
|
||||
},
|
||||
"sparkyfitness": {
|
||||
"eaten": "Eaten",
|
||||
"burned": "Burned",
|
||||
"remaining": "Remaining",
|
||||
"steps": "Steps"
|
||||
"eaten": "Mangé",
|
||||
"burned": "Brûlé",
|
||||
"remaining": "Restant",
|
||||
"steps": "Pas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "Stanje podsustava nepoznato"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Zdrav",
|
||||
"degraded": "Degradirano",
|
||||
"no_data": "Nema dostupnih podataka spremišta"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -624,8 +624,8 @@
|
||||
"resources": "Resursi",
|
||||
"targets": "Ciljevi",
|
||||
"traffic": "Promet",
|
||||
"in": "In",
|
||||
"out": "Out"
|
||||
"in": "Ulaz",
|
||||
"out": "Izlaz"
|
||||
},
|
||||
"peanut": {
|
||||
"battery_charge": "Napunjenost baterije",
|
||||
|
||||
@@ -61,15 +61,15 @@
|
||||
"wlan_devices": "Dispositivi WLAN",
|
||||
"lan_users": "Utenti LAN",
|
||||
"wlan_users": "Utenti WLAN",
|
||||
"up": "UP",
|
||||
"up": "SU",
|
||||
"down": "DOWN",
|
||||
"wait": "Please wait",
|
||||
"wait": "Attendere prego",
|
||||
"empty_data": "Stato del sottosistema sconosciuto"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Sano",
|
||||
"degraded": "Degradato",
|
||||
"no_data": "Nessun dato di archiviazione disponibile"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -88,7 +88,7 @@
|
||||
"partial": "Parziale"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"error": "Errore",
|
||||
"ping": "Ping",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
@@ -96,11 +96,11 @@
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "Stato HTTP",
|
||||
"error": "Error",
|
||||
"error": "Errore",
|
||||
"response": "Risposta",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Not Available"
|
||||
"down": "Giù",
|
||||
"up": "Su",
|
||||
"not_available": "Non disponibile"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "In riproduzione",
|
||||
@@ -113,21 +113,21 @@
|
||||
"songs": "Canzoni"
|
||||
},
|
||||
"jellyfin": {
|
||||
"playing": "Playing",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams",
|
||||
"movies": "Movies",
|
||||
"playing": "In riproduzione",
|
||||
"transcoding": "Transcodifica",
|
||||
"bitrate": "Velocità in bit",
|
||||
"no_active": "Nessuna Trasmissione Attiva",
|
||||
"movies": "Film",
|
||||
"series": "Serie",
|
||||
"episodes": "Episodes",
|
||||
"songs": "Songs"
|
||||
"episodes": "Episodi",
|
||||
"songs": "Canzoni"
|
||||
},
|
||||
"esphome": {
|
||||
"offline": "Offline",
|
||||
"offline_alt": "Offline",
|
||||
"offline": "Disconnesso",
|
||||
"offline_alt": "Disconnesso",
|
||||
"online": "Online",
|
||||
"total": "Total",
|
||||
"unknown": "Unknown"
|
||||
"total": "Totale",
|
||||
"unknown": "Sconosciuto"
|
||||
},
|
||||
"evcc": {
|
||||
"pv_power": "Produzione",
|
||||
@@ -148,7 +148,7 @@
|
||||
"unread": "Non letto"
|
||||
},
|
||||
"fritzbox": {
|
||||
"connectionStatus": "Status",
|
||||
"connectionStatus": "Stato",
|
||||
"connectionStatusUnconfigured": "Non configurato",
|
||||
"connectionStatusConnecting": "Connessione in corso",
|
||||
"connectionStatusAuthenticating": "In fase di autenticazione",
|
||||
@@ -156,11 +156,11 @@
|
||||
"connectionStatusDisconnecting": "Disconnessione in corso",
|
||||
"connectionStatusDisconnected": "Disconnesso",
|
||||
"connectionStatusConnected": "Connesso",
|
||||
"uptime": "Uptime",
|
||||
"uptime": "Tempo di funzionamento",
|
||||
"maxDown": "Max. Down",
|
||||
"maxUp": "Max. Up",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"down": "Giù",
|
||||
"up": "Su",
|
||||
"received": "Ricevuti",
|
||||
"sent": "Inviati",
|
||||
"externalIPAddress": "IP Esterno",
|
||||
@@ -184,17 +184,17 @@
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "In riproduzione",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams",
|
||||
"transcoding": "Transcodifica",
|
||||
"bitrate": "Velocità in bit",
|
||||
"no_active": "Nessuna Trasmissione Attiva",
|
||||
"plex_connection_error": "Controllare la connessione a Plex"
|
||||
},
|
||||
"tracearr": {
|
||||
"no_active": "No Active Streams",
|
||||
"streams": "Streams",
|
||||
"transcodes": "Transcodes",
|
||||
"directplay": "Direct Play",
|
||||
"bitrate": "Bitrate"
|
||||
"no_active": "Nessuna Trasmissione Attiva",
|
||||
"streams": "Flussi",
|
||||
"transcodes": "Transcodifiche",
|
||||
"directplay": "Riproduzione diretta",
|
||||
"bitrate": "Velocità in bit"
|
||||
},
|
||||
"omada": {
|
||||
"connectedAp": "AP Connessi",
|
||||
@@ -211,11 +211,11 @@
|
||||
"plex": {
|
||||
"streams": "Trasmissioni attive",
|
||||
"albums": "Album",
|
||||
"movies": "Movies",
|
||||
"movies": "Film",
|
||||
"tv": "Programmi televisivi"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"rate": "Rapporto",
|
||||
"queue": "Coda",
|
||||
"timeleft": "Tempo Rimanente"
|
||||
},
|
||||
@@ -227,13 +227,13 @@
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"leech": "In download",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qbittorrent": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"leech": "In download",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qnap": {
|
||||
@@ -430,21 +430,21 @@
|
||||
"medusa": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"series": "Series"
|
||||
"series": "Serie"
|
||||
},
|
||||
"minecraft": {
|
||||
"players": "Giocatori",
|
||||
"version": "Versione",
|
||||
"status": "Status",
|
||||
"status": "Stato",
|
||||
"up": "Online",
|
||||
"down": "Offline"
|
||||
"down": "Disconnesso"
|
||||
},
|
||||
"miniflux": {
|
||||
"read": "Letti",
|
||||
"unread": "Unread"
|
||||
"unread": "Non letto"
|
||||
},
|
||||
"authentik": {
|
||||
"users": "Users",
|
||||
"users": "Utenti",
|
||||
"loginsLast24H": "Accessi (24h)",
|
||||
"failedLoginsLast24H": "Accessi Falliti (24h)"
|
||||
},
|
||||
@@ -456,19 +456,19 @@
|
||||
},
|
||||
"glances": {
|
||||
"cpu": "CPU",
|
||||
"load": "Load",
|
||||
"wait": "Please wait",
|
||||
"load": "Carico",
|
||||
"wait": "Attendere prego",
|
||||
"temp": "TEMP",
|
||||
"_temp": "Temp.",
|
||||
"warn": "Avviso",
|
||||
"uptime": "UP",
|
||||
"total": "Total",
|
||||
"free": "Free",
|
||||
"used": "Used",
|
||||
"days": "d",
|
||||
"hours": "h",
|
||||
"uptime": "SU",
|
||||
"total": "Totale",
|
||||
"free": "Libero",
|
||||
"used": "Utilizzato",
|
||||
"days": "g",
|
||||
"hours": "o",
|
||||
"crit": "Critico",
|
||||
"read": "Read",
|
||||
"read": "Letti",
|
||||
"write": "Scrittura",
|
||||
"gpu": "GPU",
|
||||
"mem": "Mem.",
|
||||
@@ -489,57 +489,57 @@
|
||||
"1-day": "Prevalentemente Soleggiato",
|
||||
"1-night": "Prevalentemente Sereno",
|
||||
"2-day": "Parzialmente Nuvoloso",
|
||||
"2-night": "Partly Cloudy",
|
||||
"2-night": "Parzialmente Nuvoloso",
|
||||
"3-day": "Nuvoloso",
|
||||
"3-night": "Cloudy",
|
||||
"3-night": "Nuvoloso",
|
||||
"45-day": "Nebbioso",
|
||||
"45-night": "Foggy",
|
||||
"48-day": "Foggy",
|
||||
"48-night": "Foggy",
|
||||
"45-night": "Nebbioso",
|
||||
"48-day": "Nebbioso",
|
||||
"48-night": "Nebbioso",
|
||||
"51-day": "Pioggerella Leggera",
|
||||
"51-night": "Light Drizzle",
|
||||
"51-night": "Pioggerella Leggera",
|
||||
"53-day": "Pioggerella",
|
||||
"53-night": "Drizzle",
|
||||
"53-night": "Pioggerella",
|
||||
"55-day": "Pioggerella Pesante",
|
||||
"55-night": "Heavy Drizzle",
|
||||
"55-night": "Pioggerella Pesante",
|
||||
"56-day": "Leggera Pioggia Gelata",
|
||||
"56-night": "Light Freezing Drizzle",
|
||||
"56-night": "Leggera Pioggia Gelata",
|
||||
"57-day": "Pioggerella Gelata",
|
||||
"57-night": "Freezing Drizzle",
|
||||
"57-night": "Pioggerella Gelata",
|
||||
"61-day": "Pioggia Leggera",
|
||||
"61-night": "Light Rain",
|
||||
"61-night": "Pioggia Leggera",
|
||||
"63-day": "Pioggia",
|
||||
"63-night": "Rain",
|
||||
"63-night": "Pioggia",
|
||||
"65-day": "Pioggia Intensa",
|
||||
"65-night": "Heavy Rain",
|
||||
"65-night": "Pioggia Intensa",
|
||||
"66-day": "Grandine",
|
||||
"66-night": "Freezing Rain",
|
||||
"67-day": "Freezing Rain",
|
||||
"67-night": "Freezing Rain",
|
||||
"66-night": "Grandine",
|
||||
"67-day": "Grandine",
|
||||
"67-night": "Grandine",
|
||||
"71-day": "Leggera Nevicata",
|
||||
"71-night": "Light Snow",
|
||||
"71-night": "Leggera Nevicata",
|
||||
"73-day": "Neve",
|
||||
"73-night": "Snow",
|
||||
"73-night": "Neve",
|
||||
"75-day": "Nevicata Intensa",
|
||||
"75-night": "Heavy Snow",
|
||||
"75-night": "Nevicata Intensa",
|
||||
"77-day": "Fiocchi di Neve",
|
||||
"77-night": "Snow Grains",
|
||||
"77-night": "Fiocchi di Neve",
|
||||
"80-day": "Leggeri Rovesci",
|
||||
"80-night": "Light Showers",
|
||||
"80-night": "Leggeri Rovesci",
|
||||
"81-day": "Rovesci",
|
||||
"81-night": "Showers",
|
||||
"81-night": "Rovesci",
|
||||
"82-day": "Intensi Rovesci",
|
||||
"82-night": "Heavy Showers",
|
||||
"82-night": "Intensi Rovesci",
|
||||
"85-day": "Rovesci di Neve",
|
||||
"85-night": "Snow Showers",
|
||||
"86-day": "Snow Showers",
|
||||
"86-night": "Snow Showers",
|
||||
"85-night": "Rovesci di Neve",
|
||||
"86-day": "Rovesci di Neve",
|
||||
"86-night": "Rovesci di Neve",
|
||||
"95-day": "Temporale",
|
||||
"95-night": "Thunderstorm",
|
||||
"95-night": "Temporale",
|
||||
"96-day": "Temporale con grandine",
|
||||
"96-night": "Thunderstorm With Hail",
|
||||
"99-day": "Thunderstorm With Hail",
|
||||
"99-night": "Thunderstorm With Hail"
|
||||
"96-night": "Temporale con grandine",
|
||||
"99-day": "Temporale con grandine",
|
||||
"99-night": "Temporale con grandine"
|
||||
},
|
||||
"homebridge": {
|
||||
"available_update": "Sistema",
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "서브시스템 상태 알 수 없음"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "정상",
|
||||
"degraded": "성능 저하",
|
||||
"no_data": "저장소 데이터 없음"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "수신",
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "Status podsystemu nieznany"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Zdrowy",
|
||||
"degraded": "Zdegradowany",
|
||||
"no_data": "Brak danych o miejscu przechowywania"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
"empty_data": "Status do Subsistema desconhecido"
|
||||
},
|
||||
"unifi_drive": {
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"no_data": "No storage data available"
|
||||
"healthy": "Saudável",
|
||||
"degraded": "Degradado",
|
||||
"no_data": "Sem dados de armazenamento disponíveis"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -114,9 +114,9 @@
|
||||
},
|
||||
"jellyfin": {
|
||||
"playing": "Jogando",
|
||||
"transcoding": "Transcoding",
|
||||
"transcoding": "Transcodificando",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams",
|
||||
"no_active": "Sem Transmissões Ativas",
|
||||
"movies": "Filmes",
|
||||
"series": "Séries",
|
||||
"episodes": "Episódios",
|
||||
@@ -190,10 +190,10 @@
|
||||
"plex_connection_error": "Verifique a conexão do Plex"
|
||||
},
|
||||
"tracearr": {
|
||||
"no_active": "No Active Streams",
|
||||
"streams": "Streams",
|
||||
"transcodes": "Transcodes",
|
||||
"directplay": "Direct Play",
|
||||
"no_active": "Sem Transmissões Ativas",
|
||||
"streams": "Transmissões",
|
||||
"transcodes": "Transcodificações",
|
||||
"directplay": "Reprodução direta",
|
||||
"bitrate": "Bitrate"
|
||||
},
|
||||
"omada": {
|
||||
@@ -619,13 +619,13 @@
|
||||
"total": "Total"
|
||||
},
|
||||
"pangolin": {
|
||||
"orgs": "Orgs",
|
||||
"orgs": "Organizações",
|
||||
"sites": "Sites",
|
||||
"resources": "Recursos",
|
||||
"targets": "Targets",
|
||||
"traffic": "Traffic",
|
||||
"in": "In",
|
||||
"out": "Out"
|
||||
"targets": "Alvos",
|
||||
"traffic": "Tráfego",
|
||||
"in": "Em",
|
||||
"out": "Saída"
|
||||
},
|
||||
"peanut": {
|
||||
"battery_charge": "Carga da bateria",
|
||||
@@ -1164,11 +1164,11 @@
|
||||
"images": "Imagens",
|
||||
"image_updates": "Atualizações de Imagem",
|
||||
"images_unused": "Não utilizado",
|
||||
"environment_required": "Environment ID Required"
|
||||
"environment_required": "ID do ambiente necessário"
|
||||
},
|
||||
"dockhand": {
|
||||
"running": "Executando",
|
||||
"stopped": "Stopped",
|
||||
"stopped": "Parado",
|
||||
"cpu": "CPU",
|
||||
"memory": "Memória",
|
||||
"images": "Imagens",
|
||||
@@ -1178,12 +1178,12 @@
|
||||
"stacks": "Pilhas",
|
||||
"paused": "Pausado",
|
||||
"total": "Total",
|
||||
"environment_not_found": "Environment Not Found"
|
||||
"environment_not_found": "Ambiente não encontrado"
|
||||
},
|
||||
"sparkyfitness": {
|
||||
"eaten": "Eaten",
|
||||
"burned": "Burned",
|
||||
"remaining": "Remaining",
|
||||
"steps": "Steps"
|
||||
"eaten": "Comido",
|
||||
"burned": "Queimado",
|
||||
"remaining": "Restante",
|
||||
"steps": "Passos"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@
|
||||
"cpu": "CPU",
|
||||
"load": "负载",
|
||||
"wait": "请稍候",
|
||||
"temp": "转速",
|
||||
"temp": "温度",
|
||||
"_temp": "Temp",
|
||||
"warn": "Warn",
|
||||
"uptime": "运行时间",
|
||||
|
||||
@@ -7,7 +7,10 @@ export function setCookieHeader(url, params) {
|
||||
const existingCookie = cookieJar.getCookieStringSync(url.toString());
|
||||
if (existingCookie) {
|
||||
params.headers = params.headers ?? {};
|
||||
params.headers[params.cookieHeader ?? "Cookie"] = existingCookie;
|
||||
const cookieHeader = params.cookieHeader ?? "Cookie";
|
||||
if (!params.headers[cookieHeader]) {
|
||||
params.headers[cookieHeader] = existingCookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,4 +42,16 @@ describe("utils/proxy/cookie-jar", () => {
|
||||
|
||||
expect(params.headers.Cookie).toContain("c=d");
|
||||
});
|
||||
|
||||
it("does not overwrite an explicit cookie header", async () => {
|
||||
const { addCookieToJar, setCookieHeader } = await import("./cookie-jar");
|
||||
|
||||
const url = new URL("http://example4.test/path");
|
||||
addCookieToJar(url, { "set-cookie": ["sid=1; Path=/"] });
|
||||
|
||||
const params = { headers: { Cookie: "manual=1" } };
|
||||
setCookieHeader(url, params);
|
||||
|
||||
expect(params.headers.Cookie).toBe("manual=1");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,23 +224,43 @@ function homepageDNSLookupFn() {
|
||||
};
|
||||
}
|
||||
|
||||
const homepageLookup = homepageDNSLookupFn();
|
||||
const agentCache = new Map();
|
||||
|
||||
function getAgent(protocol, disableIpv6) {
|
||||
const cacheKey = `${protocol}:${disableIpv6 ? "ipv4" : "auto"}`;
|
||||
const cachedAgent = agentCache.get(cacheKey);
|
||||
if (cachedAgent) {
|
||||
return cachedAgent;
|
||||
}
|
||||
|
||||
const agentOptions = {
|
||||
keepAlive: true,
|
||||
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
|
||||
lookup: homepageLookup,
|
||||
};
|
||||
|
||||
const agent =
|
||||
protocol === "https:"
|
||||
? new https.Agent({ ...agentOptions, rejectUnauthorized: false })
|
||||
: new http.Agent(agentOptions);
|
||||
|
||||
agentCache.set(cacheKey, agent);
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function httpProxy(url, params = {}) {
|
||||
const constructedUrl = new URL(url);
|
||||
const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true";
|
||||
const agentOptions = {
|
||||
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
|
||||
lookup: homepageDNSLookupFn(),
|
||||
};
|
||||
|
||||
let request = null;
|
||||
if (constructedUrl.protocol === "https:") {
|
||||
request = httpsRequest(constructedUrl, {
|
||||
agent: new https.Agent({ ...agentOptions, rejectUnauthorized: false }),
|
||||
agent: getAgent(constructedUrl.protocol, disableIpv6),
|
||||
...params,
|
||||
});
|
||||
} else {
|
||||
request = httpRequest(constructedUrl, {
|
||||
agent: new http.Agent(agentOptions),
|
||||
agent: getAgent(constructedUrl.protocol, disableIpv6),
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const { state, cache, logger, dns, net, cookieJar } = vi.hoisted(() => ({
|
||||
body: Buffer.from(""),
|
||||
},
|
||||
error: null,
|
||||
lastAgent: null,
|
||||
lastAgentOptions: null,
|
||||
lastRequestParams: null,
|
||||
lastWrittenBody: null,
|
||||
@@ -59,6 +60,7 @@ vi.mock("follow-redirects", async () => {
|
||||
state.lastWrittenBody = chunk;
|
||||
});
|
||||
req.end = vi.fn(() => {
|
||||
state.lastAgent = params?.agent ?? null;
|
||||
state.lastAgentOptions = params?.agent?.opts ?? null;
|
||||
if (state.error) {
|
||||
req.emit("error", state.error);
|
||||
@@ -104,6 +106,7 @@ describe("utils/proxy/http cachedRequest", () => {
|
||||
headers: { "content-type": "application/json" },
|
||||
body: Buffer.from(""),
|
||||
};
|
||||
state.lastAgent = null;
|
||||
state.lastAgentOptions = null;
|
||||
state.lastRequestParams = null;
|
||||
state.lastWrittenBody = null;
|
||||
@@ -307,6 +310,7 @@ describe("utils/proxy/http httpProxy", () => {
|
||||
headers: { "content-type": "application/json" },
|
||||
body: Buffer.from("ok"),
|
||||
};
|
||||
state.lastAgent = null;
|
||||
state.lastAgentOptions = null;
|
||||
state.lastRequestParams = null;
|
||||
state.lastWrittenBody = null;
|
||||
@@ -397,6 +401,7 @@ describe("utils/proxy/http httpProxy", () => {
|
||||
|
||||
await httpMod.httpProxy("http://example.com");
|
||||
|
||||
expect(state.lastAgentOptions.keepAlive).toBe(true);
|
||||
expect(state.lastAgentOptions.family).toBe(4);
|
||||
expect(state.lastAgentOptions.autoSelectFamily).toBe(false);
|
||||
});
|
||||
@@ -409,6 +414,17 @@ describe("utils/proxy/http httpProxy", () => {
|
||||
expect(state.lastAgentOptions.rejectUnauthorized).toBe(false);
|
||||
});
|
||||
|
||||
it("reuses the same keep-alive agent for repeated http requests", async () => {
|
||||
const httpMod = await import("./http");
|
||||
|
||||
await httpMod.httpProxy("http://example.com/first");
|
||||
const firstAgent = state.lastAgent;
|
||||
await httpMod.httpProxy("http://example.com/second");
|
||||
|
||||
expect(state.lastAgentOptions.keepAlive).toBe(true);
|
||||
expect(state.lastAgent).toBe(firstAgent);
|
||||
});
|
||||
|
||||
it("returns a sanitized error response when the request fails", async () => {
|
||||
state.error = Object.assign(new Error("boom"), { code: "EHOSTUNREACH" });
|
||||
const httpMod = await import("./http");
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
import cache from "memory-cache";
|
||||
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
|
||||
const proxyName = "omadaProxyHandler";
|
||||
const sessionCacheKey = `${proxyName}__session`;
|
||||
|
||||
const logger = createLogger(proxyName);
|
||||
|
||||
async function login(loginUrl, username, password, controllerVersionMajor) {
|
||||
function getSessionCacheId(group, service, index) {
|
||||
return [sessionCacheKey, group, service, index ?? "0"].join(".");
|
||||
}
|
||||
|
||||
function shouldRetryWithFreshSession(status, responseData, attempt, usedCachedSession) {
|
||||
return attempt === 0 && usedCachedSession && (status === 401 || status === 403 || responseData?.errorCode > 0);
|
||||
}
|
||||
|
||||
function getCookieHeader(responseHeaders) {
|
||||
const setCookie = responseHeaders?.["set-cookie"];
|
||||
if (!setCookie) return null;
|
||||
|
||||
const cookies = new Map();
|
||||
(Array.isArray(setCookie) ? setCookie : [setCookie]).forEach((cookie) => {
|
||||
const cookiePair = cookie.split(";")[0];
|
||||
if (!cookiePair) return;
|
||||
|
||||
const separatorIndex = cookiePair.indexOf("=");
|
||||
const cookieName = separatorIndex === -1 ? cookiePair : cookiePair.slice(0, separatorIndex);
|
||||
cookies.set(cookieName, cookiePair);
|
||||
});
|
||||
|
||||
return cookies.size > 0 ? Array.from(cookies.values()).join("; ") : null;
|
||||
}
|
||||
|
||||
async function login(loginUrl, username, password, controllerVersionMajor, sessionCacheId) {
|
||||
const params = {
|
||||
username,
|
||||
password,
|
||||
@@ -20,7 +48,7 @@ async function login(loginUrl, username, password, controllerVersionMajor) {
|
||||
};
|
||||
}
|
||||
|
||||
const [status, contentType, data] = await httpProxy(loginUrl, {
|
||||
const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
@@ -28,7 +56,20 @@ async function login(loginUrl, username, password, controllerVersionMajor) {
|
||||
},
|
||||
});
|
||||
|
||||
return [status, JSON.parse(data.toString())];
|
||||
const loginResponseData = JSON.parse(data.toString());
|
||||
|
||||
if (status === 200 && loginResponseData.errorCode === 0) {
|
||||
cache.put(
|
||||
sessionCacheId,
|
||||
{
|
||||
token: loginResponseData.result.token,
|
||||
cookieHeader: getCookieHeader(responseHeaders),
|
||||
},
|
||||
55 * 60 * 1000, // Cache session for 55 minutes
|
||||
);
|
||||
}
|
||||
|
||||
return [status, loginResponseData];
|
||||
}
|
||||
|
||||
export default async function omadaProxyHandler(req, res) {
|
||||
@@ -86,182 +127,247 @@ export default async function omadaProxyHandler(req, res) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [loginStatus, loginResponseData] = await login(
|
||||
loginUrl,
|
||||
widget.username,
|
||||
widget.password,
|
||||
controllerVersionMajor,
|
||||
);
|
||||
const sessionCacheId = getSessionCacheId(group, service, index);
|
||||
let session = cache.get(sessionCacheId);
|
||||
|
||||
if (loginStatus !== 200 || loginResponseData.errorCode > 0) {
|
||||
return res
|
||||
.status(loginStatus)
|
||||
.json({ error: { message: "Error logging in to Omada controller", url: loginUrl, data: loginResponseData } });
|
||||
}
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
try {
|
||||
const usedCachedSession = Boolean(session);
|
||||
|
||||
const { token } = loginResponseData.result;
|
||||
if (!session) {
|
||||
const [loginStatus, loginResponseData] = await login(
|
||||
loginUrl,
|
||||
widget.username,
|
||||
widget.password,
|
||||
controllerVersionMajor,
|
||||
sessionCacheId,
|
||||
);
|
||||
|
||||
let sitesUrl;
|
||||
let body = {};
|
||||
let params = { token };
|
||||
let headers = { "Csrf-Token": token };
|
||||
let method = "GET";
|
||||
if (loginStatus !== 200 || loginResponseData.errorCode > 0) {
|
||||
return res.status(loginStatus).json({
|
||||
error: { message: "Error logging in to Omada controller", url: loginUrl, data: loginResponseData },
|
||||
});
|
||||
}
|
||||
|
||||
switch (controllerVersionMajor) {
|
||||
case 3:
|
||||
sitesUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||
body = {
|
||||
method: "getUserSites",
|
||||
params: {
|
||||
userName: widget.username,
|
||||
},
|
||||
};
|
||||
method = "POST";
|
||||
break;
|
||||
case 4:
|
||||
sitesUrl = `${url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
session = cache.get(sessionCacheId);
|
||||
}
|
||||
|
||||
[status, contentType, data] = await httpProxy(sitesUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
});
|
||||
const { token, cookieHeader } = session;
|
||||
|
||||
const sitesResponseData = JSON.parse(data);
|
||||
let sitesUrl;
|
||||
let body = {};
|
||||
let params = { token };
|
||||
let headers = { "Csrf-Token": token };
|
||||
if (cookieHeader) {
|
||||
headers.Cookie = cookieHeader;
|
||||
}
|
||||
let method = "GET";
|
||||
|
||||
if (status !== 200 || sitesResponseData.errorCode > 0) {
|
||||
logger.debug(`HTTP ${status} getting sites list: ${sitesResponseData.msg}`);
|
||||
return res
|
||||
.status(status)
|
||||
.json({ error: { message: "Error getting sites list", url, data: sitesResponseData } });
|
||||
}
|
||||
switch (controllerVersionMajor) {
|
||||
case 3:
|
||||
sitesUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||
body = {
|
||||
method: "getUserSites",
|
||||
params: {
|
||||
userName: widget.username,
|
||||
},
|
||||
};
|
||||
headers = { "Content-Type": "application/json" };
|
||||
if (cookieHeader) {
|
||||
headers.Cookie = cookieHeader;
|
||||
}
|
||||
method = "POST";
|
||||
break;
|
||||
case 4:
|
||||
sitesUrl = `${url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const site =
|
||||
controllerVersionMajor === 3
|
||||
? sitesResponseData.result.siteList.find((s) => s.name === widget.site)
|
||||
: sitesResponseData.result.data.find((s) => s.name === widget.site);
|
||||
|
||||
if (!site) {
|
||||
return res.status(status).json({ error: { message: `Site ${widget.site} is not found`, url: sitesUrl, data } });
|
||||
}
|
||||
|
||||
let siteResponseData;
|
||||
|
||||
let connectedAp;
|
||||
let activeUser;
|
||||
let connectedSwitches;
|
||||
let connectedGateways;
|
||||
let alerts;
|
||||
|
||||
if (controllerVersionMajor === 3) {
|
||||
// Omada v3 controller requires switching site
|
||||
const switchUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||
method = "POST";
|
||||
body = {
|
||||
method: "switchSite",
|
||||
params: {
|
||||
siteName: site.siteName,
|
||||
userName: widget.username,
|
||||
},
|
||||
};
|
||||
headers = { "Content-Type": "application/json" };
|
||||
params = { token };
|
||||
|
||||
[status, contentType, data] = await httpProxy(switchUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
});
|
||||
|
||||
const switchResponseData = JSON.parse(data);
|
||||
if (status !== 200 || switchResponseData.errorCode > 0) {
|
||||
logger.error(`HTTP ${status} getting sites list: ${data}`);
|
||||
return res.status(status).json({ error: { message: "Error switching site", url: switchUrl, data } });
|
||||
}
|
||||
|
||||
const statsUrl = `${url}/web/v1/controller?getGlobalStat=&token=${token}`;
|
||||
[status, contentType, data] = await httpProxy(statsUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify({
|
||||
method: "getGlobalStat",
|
||||
}),
|
||||
headers,
|
||||
});
|
||||
|
||||
siteResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||
return res.status(status).json({ error: { message: "Error getting stats", url: statsUrl, data } });
|
||||
}
|
||||
|
||||
connectedAp = siteResponseData.result.connectedAp;
|
||||
activeUser = siteResponseData.result.activeUser;
|
||||
alerts = siteResponseData.result.alerts;
|
||||
} else if ([4, 5, 6].includes(controllerVersionMajor)) {
|
||||
const siteName = controllerVersionMajor > 4 ? site.id : site.key;
|
||||
const siteStatsUrl =
|
||||
controllerVersionMajor === 4
|
||||
? `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`
|
||||
: `${url}/${cId}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
|
||||
[status, contentType, data] = await httpProxy(siteStatsUrl, {
|
||||
headers: {
|
||||
"Csrf-Token": token,
|
||||
},
|
||||
});
|
||||
|
||||
siteResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||
logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${siteResponseData.msg}`);
|
||||
return res.status(status === 200 ? 500 : status).json({
|
||||
error: {
|
||||
message: "Error getting stats",
|
||||
url: siteStatsUrl,
|
||||
data: siteResponseData,
|
||||
},
|
||||
[status, contentType, data] = await httpProxy(sitesUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify(body),
|
||||
headers: { ...headers },
|
||||
});
|
||||
|
||||
const sitesResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || sitesResponseData.errorCode > 0) {
|
||||
logger.debug(`HTTP ${status} getting sites list: ${sitesResponseData.msg}`);
|
||||
if (shouldRetryWithFreshSession(status, sitesResponseData, attempt, usedCachedSession)) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
return res
|
||||
.status(status)
|
||||
.json({ error: { message: "Error getting sites list", url, data: sitesResponseData } });
|
||||
}
|
||||
|
||||
const site =
|
||||
controllerVersionMajor === 3
|
||||
? sitesResponseData.result.siteList.find((s) => s.name === widget.site)
|
||||
: sitesResponseData.result.data.find((s) => s.name === widget.site);
|
||||
|
||||
if (!site) {
|
||||
return res
|
||||
.status(status)
|
||||
.json({ error: { message: `Site ${widget.site} is not found`, url: sitesUrl, data } });
|
||||
}
|
||||
|
||||
let siteResponseData;
|
||||
|
||||
let connectedAp;
|
||||
let activeUser;
|
||||
let connectedSwitches;
|
||||
let connectedGateways;
|
||||
let alerts;
|
||||
|
||||
if (controllerVersionMajor === 3) {
|
||||
// Omada v3 controller requires switching site
|
||||
const switchUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
|
||||
method = "POST";
|
||||
body = {
|
||||
method: "switchSite",
|
||||
params: {
|
||||
siteName: site.siteName,
|
||||
userName: widget.username,
|
||||
},
|
||||
};
|
||||
headers = { "Content-Type": "application/json" };
|
||||
if (cookieHeader) {
|
||||
headers.Cookie = cookieHeader;
|
||||
}
|
||||
params = { token };
|
||||
|
||||
[status, contentType, data] = await httpProxy(switchUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify(body),
|
||||
headers: { ...headers },
|
||||
});
|
||||
|
||||
const switchResponseData = JSON.parse(data);
|
||||
if (status !== 200 || switchResponseData.errorCode > 0) {
|
||||
logger.error(`HTTP ${status} getting sites list: ${data}`);
|
||||
if (shouldRetryWithFreshSession(status, switchResponseData, attempt, usedCachedSession)) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
return res.status(status).json({ error: { message: "Error switching site", url: switchUrl, data } });
|
||||
}
|
||||
|
||||
const statsUrl = `${url}/web/v1/controller?getGlobalStat=&token=${token}`;
|
||||
[status, contentType, data] = await httpProxy(statsUrl, {
|
||||
method,
|
||||
params,
|
||||
body: JSON.stringify({
|
||||
method: "getGlobalStat",
|
||||
}),
|
||||
headers: { ...headers },
|
||||
});
|
||||
|
||||
siteResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||
if (shouldRetryWithFreshSession(status, siteResponseData, attempt, usedCachedSession)) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
return res.status(status).json({ error: { message: "Error getting stats", url: statsUrl, data } });
|
||||
}
|
||||
|
||||
connectedAp = siteResponseData.result.connectedAp;
|
||||
activeUser = siteResponseData.result.activeUser;
|
||||
alerts = siteResponseData.result.alerts;
|
||||
} else if ([4, 5, 6].includes(controllerVersionMajor)) {
|
||||
const siteName = controllerVersionMajor > 4 ? site.id : site.key;
|
||||
const siteStatsUrl =
|
||||
controllerVersionMajor === 4
|
||||
? `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`
|
||||
: `${url}/${cId}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
|
||||
[status, contentType, data] = await httpProxy(siteStatsUrl, {
|
||||
headers: { ...headers },
|
||||
});
|
||||
|
||||
siteResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || siteResponseData.errorCode > 0) {
|
||||
logger.debug(`HTTP ${status} getting stats for site ${widget.site} with message ${siteResponseData.msg}`);
|
||||
if (shouldRetryWithFreshSession(status, siteResponseData, attempt, usedCachedSession)) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
return res.status(status === 200 ? 500 : status).json({
|
||||
error: {
|
||||
message: "Error getting stats",
|
||||
url: siteStatsUrl,
|
||||
data: siteResponseData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const alertUrl =
|
||||
controllerVersionMajor === 4
|
||||
? `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`
|
||||
: `${url}/${cId}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
|
||||
[status, contentType, data] = await httpProxy(alertUrl, {
|
||||
headers: { ...headers },
|
||||
});
|
||||
const alertResponseData = JSON.parse(data);
|
||||
|
||||
if (status !== 200 || alertResponseData.errorCode > 0) {
|
||||
if (shouldRetryWithFreshSession(status, alertResponseData, attempt, usedCachedSession)) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
return res.status(status === 200 ? 500 : status).json({
|
||||
error: {
|
||||
message: "Error getting alerts",
|
||||
url: alertUrl,
|
||||
data: alertResponseData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
activeUser = siteResponseData.result.totalClientNum;
|
||||
connectedAp = siteResponseData.result.connectedApNum;
|
||||
connectedGateways = siteResponseData.result.connectedGatewayNum;
|
||||
connectedSwitches = siteResponseData.result.connectedSwitchNum;
|
||||
alerts = alertResponseData.result.alertNum;
|
||||
}
|
||||
|
||||
return res.send(
|
||||
JSON.stringify({
|
||||
connectedAp,
|
||||
activeUser,
|
||||
alerts,
|
||||
connectedGateways,
|
||||
connectedSwitches,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError && attempt === 0) {
|
||||
cache.del(sessionCacheId);
|
||||
session = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const alertUrl =
|
||||
controllerVersionMajor === 4
|
||||
? `${url}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`
|
||||
: `${url}/${cId}/api/v2/sites/${siteName}/alerts/num?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||
|
||||
[status, contentType, data] = await httpProxy(alertUrl, {
|
||||
headers: {
|
||||
"Csrf-Token": token,
|
||||
},
|
||||
});
|
||||
const alertResponseData = JSON.parse(data);
|
||||
|
||||
activeUser = siteResponseData.result.totalClientNum;
|
||||
connectedAp = siteResponseData.result.connectedApNum;
|
||||
connectedGateways = siteResponseData.result.connectedGatewayNum;
|
||||
connectedSwitches = siteResponseData.result.connectedSwitchNum;
|
||||
alerts = alertResponseData.result.alertNum;
|
||||
}
|
||||
|
||||
return res.send(
|
||||
JSON.stringify({
|
||||
connectedAp,
|
||||
activeUser,
|
||||
alerts,
|
||||
connectedGateways,
|
||||
connectedSwitches,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,24 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({
|
||||
httpProxy: vi.fn(),
|
||||
getServiceWidget: vi.fn(),
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
const { httpProxy, getServiceWidget, cache, logger } = vi.hoisted(() => {
|
||||
const store = new Map();
|
||||
|
||||
return {
|
||||
httpProxy: vi.fn(),
|
||||
getServiceWidget: vi.fn(),
|
||||
cache: {
|
||||
get: vi.fn((k) => store.get(k)),
|
||||
put: vi.fn((k, v) => store.set(k, v)),
|
||||
del: vi.fn((k) => store.delete(k)),
|
||||
_reset: () => store.clear(),
|
||||
},
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("utils/logger", () => ({
|
||||
default: () => logger,
|
||||
@@ -20,15 +30,19 @@ vi.mock("utils/config/service-helpers", () => ({
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
httpProxy,
|
||||
}));
|
||||
vi.mock("memory-cache", () => ({
|
||||
default: cache,
|
||||
...cache,
|
||||
}));
|
||||
|
||||
import omadaProxyHandler from "./proxy";
|
||||
|
||||
describe("widgets/omada/proxy", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Clear one-off implementations between tests (some branches return early).
|
||||
httpProxy.mockReset();
|
||||
getServiceWidget.mockReset();
|
||||
cache._reset();
|
||||
});
|
||||
|
||||
it("fetches controller info, logs in, selects site, and returns overview stats (v4)", async () => {
|
||||
@@ -51,6 +65,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
// sites list
|
||||
.mockResolvedValueOnce([
|
||||
@@ -91,6 +106,12 @@ describe("widgets/omada/proxy", () => {
|
||||
connectedSwitches: 3,
|
||||
}),
|
||||
);
|
||||
expect(httpProxy.mock.calls[2][1]).toMatchObject({
|
||||
headers: {
|
||||
"Csrf-Token": "t",
|
||||
Cookie: "TPOMADA_SESSIONID=sid",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns an error when controller info cannot be retrieved", async () => {
|
||||
@@ -169,6 +190,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 2, msg: "bad" })]);
|
||||
|
||||
@@ -195,6 +217,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
@@ -222,6 +245,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
// getUserSites
|
||||
.mockResolvedValueOnce([
|
||||
@@ -271,6 +295,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
@@ -301,6 +326,7 @@ describe("widgets/omada/proxy", () => {
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
@@ -324,4 +350,414 @@ describe("widgets/omada/proxy", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses a cached Omada session across polls", async () => {
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]);
|
||||
|
||||
const req = { query: { group: "g", service: "svc", index: "0" } };
|
||||
|
||||
await omadaProxyHandler(req, createMockRes());
|
||||
await omadaProxyHandler(req, createMockRes());
|
||||
|
||||
const loginCalls = httpProxy.mock.calls.filter(([url]) => url.toString().includes("/api/v2/login"));
|
||||
expect(loginCalls).toHaveLength(1);
|
||||
expect(httpProxy.mock.calls[6][1].headers.Cookie).toBe("TPOMADA_SESSIONID=sid");
|
||||
});
|
||||
|
||||
it("does not reuse a cached session across different widget identities", async () => {
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t1" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid1; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t2" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid2; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]);
|
||||
|
||||
await omadaProxyHandler({ query: { group: "g1", service: "svc", index: "0" } }, createMockRes());
|
||||
await omadaProxyHandler({ query: { group: "g2", service: "svc", index: "0" } }, createMockRes());
|
||||
|
||||
const loginCalls = httpProxy.mock.calls.filter(([url]) => url.toString().includes("/api/v2/login"));
|
||||
expect(loginCalls).toHaveLength(2);
|
||||
expect(httpProxy.mock.calls[2][1].headers.Cookie).toBe("TPOMADA_SESSIONID=sid1");
|
||||
expect(httpProxy.mock.calls[7][1].headers.Cookie).toBe("TPOMADA_SESSIONID=sid2");
|
||||
});
|
||||
|
||||
it("keeps the latest value when Omada sets the same cookie more than once during login", async () => {
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{
|
||||
"set-cookie": [
|
||||
"TPOMADA_SESSIONID=deleteMe; Path=/; Max-Age=0",
|
||||
"TPOMADA_SESSIONID=sid; Path=/; HttpOnly",
|
||||
"rememberMe=deleteMe; Path=/; Max-Age=0",
|
||||
],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]);
|
||||
|
||||
const req = { query: { group: "g", service: "svc", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await omadaProxyHandler(req, res);
|
||||
|
||||
expect(httpProxy.mock.calls[2][1].headers.Cookie).toBe("TPOMADA_SESSIONID=sid; rememberMe=deleteMe");
|
||||
expect(res.body).toBe(
|
||||
JSON.stringify({
|
||||
connectedAp: 2,
|
||||
activeUser: 10,
|
||||
alerts: 4,
|
||||
connectedGateways: 1,
|
||||
connectedSwitches: 3,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reuse a mutated content-length header on later GET requests", async () => {
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
const responses = [
|
||||
[200, "application/json", JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } })],
|
||||
[
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=sid; Path=/; HttpOnly"] },
|
||||
],
|
||||
[
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
],
|
||||
[
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })],
|
||||
];
|
||||
|
||||
httpProxy.mockImplementation(async (_url, params = {}) => {
|
||||
if (params.body) {
|
||||
params.headers["content-length"] = Buffer.byteLength(params.body);
|
||||
}
|
||||
|
||||
return responses.shift();
|
||||
});
|
||||
|
||||
const req = { query: { group: "g", service: "svc", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await omadaProxyHandler(req, res);
|
||||
|
||||
expect(httpProxy.mock.calls[2][1].headers["content-length"]).toBe(2);
|
||||
expect(httpProxy.mock.calls[3][1].headers["content-length"]).toBeUndefined();
|
||||
expect(httpProxy.mock.calls[4][1].headers["content-length"]).toBeUndefined();
|
||||
expect(res.body).toBe(
|
||||
JSON.stringify({
|
||||
connectedAp: 2,
|
||||
activeUser: 10,
|
||||
alerts: 4,
|
||||
connectedGateways: 1,
|
||||
connectedSwitches: 3,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("clears the cached session and re-authenticates when an authenticated response is not JSON", async () => {
|
||||
cache.put("omadaProxyHandler__session.g.svc.0", {
|
||||
token: "stale-token",
|
||||
cookieHeader: "TPOMADA_SESSIONID=stale",
|
||||
});
|
||||
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "text/html", Buffer.from("<!DOCTYPE html>login")])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "fresh-token" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=fresh; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]);
|
||||
|
||||
const req = { query: { group: "g", service: "svc", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await omadaProxyHandler(req, res);
|
||||
|
||||
expect(cache.del).toHaveBeenCalledWith("omadaProxyHandler__session.g.svc.0");
|
||||
const loginCalls = httpProxy.mock.calls.filter(([url]) => url.toString().includes("/api/v2/login"));
|
||||
expect(loginCalls).toHaveLength(1);
|
||||
expect(res.body).toBe(
|
||||
JSON.stringify({
|
||||
connectedAp: 2,
|
||||
activeUser: 10,
|
||||
alerts: 4,
|
||||
connectedGateways: 1,
|
||||
connectedSwitches: 3,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("clears the cached session and re-authenticates when a cached session returns a JSON auth error", async () => {
|
||||
cache.put("omadaProxyHandler__session.g.svc.0", {
|
||||
token: "stale-token",
|
||||
cookieHeader: "TPOMADA_SESSIONID=stale",
|
||||
});
|
||||
|
||||
getServiceWidget.mockResolvedValue({
|
||||
url: "http://omada",
|
||||
username: "u",
|
||||
password: "p",
|
||||
site: "Default",
|
||||
});
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 1, msg: "Login required" })])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "fresh-token" } })),
|
||||
{ "set-cookie": ["TPOMADA_SESSIONID=fresh; Path=/; HttpOnly"] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }),
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
200,
|
||||
"application/json",
|
||||
JSON.stringify({
|
||||
errorCode: 0,
|
||||
result: {
|
||||
totalClientNum: 10,
|
||||
connectedApNum: 2,
|
||||
connectedGatewayNum: 1,
|
||||
connectedSwitchNum: 3,
|
||||
},
|
||||
}),
|
||||
])
|
||||
.mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]);
|
||||
|
||||
const req = { query: { group: "g", service: "svc", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await omadaProxyHandler(req, res);
|
||||
|
||||
expect(cache.del).toHaveBeenCalledWith("omadaProxyHandler__session.g.svc.0");
|
||||
const loginCalls = httpProxy.mock.calls.filter(([url]) => url.toString().includes("/api/v2/login"));
|
||||
expect(loginCalls).toHaveLength(1);
|
||||
expect(res.body).toBe(
|
||||
JSON.stringify({
|
||||
connectedAp: 2,
|
||||
activeUser: 10,
|
||||
alerts: 4,
|
||||
connectedGateways: 1,
|
||||
connectedSwitches: 3,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ const widget = {
|
||||
endpoint: "org/{org}/sites",
|
||||
},
|
||||
resources: {
|
||||
endpoint: "org/{org}/resources",
|
||||
endpoint: "org/{org}/resources?pageSize=200",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user