diff --git a/.env b/.env index 867b40cd..20f181d4 100644 --- a/.env +++ b/.env @@ -64,6 +64,9 @@ # Directory for conf.yml backups # BACKUP_DIR=./user-data/config-backups +# Set to 'true' to disable automatic backups before each config save +# DISABLE_CONFIG_BACKUPS=true + # Setup any other user defined vars by prepending VITE_APP_ to the var name # VITE_APP_pihole_ip=http://your.pihole.ip # VITE_APP_pihole_key=your_pihole_secret_key diff --git a/.github/workflows/build-release-assets.yml b/.github/workflows/build-release-assets.yml deleted file mode 100644 index 1736b741..00000000 --- a/.github/workflows/build-release-assets.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: 📦 Build & Upload Release Assets - -# Builds Dashy and uploads a pre-built tarball to the GitHub release. -# This allows non-Docker installs (e.g. Proxmox VE community scripts) to -# download a ready-to-run package without having to build from source. -# -# The tarball contains the compiled frontend (dist/) plus all server-side -# files. Users extract it and run `yarn install --production` + `node server`. -# -# Triggered whenever a new release is created, or when manually dispatched - -on: - release: - types: [created] - workflow_dispatch: - inputs: - tag: - description: 'Tag to build assets for (must already exist as a release)' - required: true - -permissions: - contents: write - -concurrency: - group: ${{ github.workflow }}-${{ github.event.release.tag_name || github.event.inputs.tag }} - cancel-in-progress: true - -jobs: - build-release-assets: - name: Build app & upload tarball - runs-on: ubuntu-latest - env: - TAG: ${{ github.event.release.tag_name || github.event.inputs.tag }} - - steps: - - name: Checkout code 🛎️ - uses: actions/checkout@v6 - with: - ref: refs/tags/${{ env.TAG }} - - - name: Setup Node.js ⚙️ - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'yarn' - - - name: Install dependencies 📥 - run: yarn install --frozen-lockfile --ignore-engines --network-timeout 300000 - - - name: Build app 🏗️ - run: NODE_OPTIONS=--openssl-legacy-provider yarn build --mode production - - - name: Package release artifact 📦 - run: | - STAGING="dashy-release-staging" - mkdir -p "$STAGING" - - # Runtime files - cp -r dist "$STAGING/" - cp -r services "$STAGING/" - cp -r public "$STAGING/" - cp -r user-data "$STAGING/" - cp server.js "$STAGING/" - cp yarn.lock "$STAGING/" - - # src/utils/ files referenced directly by the server at runtime - mkdir -p "$STAGING/src/utils/config" - cp src/utils/config/ConfigSchema.json "$STAGING/src/utils/config/" - - # Strip devDependencies so `yarn install --production` stays lean - node -e " - const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); - delete pkg.devDependencies; - require('fs').writeFileSync('$STAGING/package.json', JSON.stringify(pkg, null, 2)); - " - - TARBALL="dashy-${TAG}.tar.gz" - tar -czf "${TARBALL}" -C "${STAGING}" . - echo "TARBALL=${TARBALL}" >> "$GITHUB_ENV" - echo "Size: $(du -sh ${TARBALL} | cut -f1)" - - - name: Upload tarball to GitHub Release 🚀 - uses: softprops/action-gh-release@v3 - with: - tag_name: ${{ env.TAG }} - files: ${{ env.TARBALL }} - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4fff70f8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,240 @@ +# CI checks to run when PR is opened +name: 🚦 PR Check + +on: + pull_request: + branches: ['master', 'develop'] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + name: 🔎 Detect Changes + runs-on: ubuntu-latest + outputs: + lockfile: ${{ steps.filter.outputs.lockfile }} + workflows: ${{ steps.filter.outputs.workflows }} + locales: ${{ steps.filter.outputs.locales }} + translations: ${{ steps.filter.outputs.translations }} + src: ${{ steps.filter.outputs.src }} + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Filter Paths + uses: dorny/paths-filter@v4 + id: filter + with: + filters: | + lockfile: + - 'yarn.lock' + workflows: + - '.github/workflows/**' + locales: + - 'src/assets/locales/**' + - 'src/**/*.vue' + - 'src/**/*.js' + - 'tests/locales/**' + translations: + - 'src/assets/locales/**' + src: + - 'src/**' + - 'package.json' + - 'yarn.lock' + - 'eslint.config.mjs' + - 'tsconfig.json' + + lint: + name: 🛡️ Lint + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.src == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run ESLint + run: yarn lint + + typecheck: + name: 🦴 Typecheck + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.src == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run vue-tsc + run: yarn typecheck + + test: + name: 🧪 Test + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run Tests + run: yarn test + + locales: + name: 🌐 Locale Check + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.locales == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Check Locales + run: yarn validate-locales + + spellcheck: + name: ✏️ Spellcheck + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.translations == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Spellcheck en.json + uses: crate-ci/typos@v1 + with: + files: src/assets/locales/en.json + + build: + name: 🏗️ Build Check + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Build Project + run: yarn build + + - name: Verify Build Output + run: | + if [ ! -d "dist" ]; then + echo "❌ Build failed: dist directory not created" + exit 1 + fi + if [ ! -f "dist/index.html" ]; then + echo "❌ Build failed: index.html not found" + exit 1 + fi + echo "✅ Build successful" + + docker-smoke: + name: 🐳 Docker Smoke Test + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Build & Test Docker Image + run: sh tests/docker-smoke-test.sh + timeout-minutes: 10 + + dependency-review: + name: 🔒 Dependency Audit + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.lockfile == 'true' + permissions: + contents: read + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Review Dependencies + uses: actions/dependency-review-action@v5 + with: + fail-on-severity: moderate + + secret-scan: + name: 🔑 Secret Scanning + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Scan PR Diff for Secrets + uses: trufflesecurity/trufflehog@v3.95.3 + with: + base: ${{ github.event.pull_request.base.sha }} + head: ${{ github.event.pull_request.head.sha }} + extra_args: --only-verified + + workflow-audit: + name: 🛠️ Workflow Audit + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.workflows == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Run Actionlint + uses: raven-actions/actionlint@v2 + with: + fail-on-error: true + + - name: Run Zizmor + uses: zizmorcore/zizmor-action@v0.5.4 + with: + inputs: .github/workflows/ + advanced-security: false + annotations: true diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml deleted file mode 100644 index 22757c22..00000000 --- a/.github/workflows/docker-build-publish.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: 🐳 Build + Publish Multi-Platform Image - -on: - workflow_dispatch: - push: - tags: ['*.*.*'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - packages: write - -env: - DH_IMAGE: ${{ secrets.DOCKER_REPO }} - GH_IMAGE: ${{ github.repository_owner }}/${{ github.event.repository.name }} - -jobs: - docker: - runs-on: ubuntu-latest - permissions: { contents: read, packages: write } - - steps: - - name: 🛎️ Checkout Repo - uses: actions/checkout@v6 - - - name: 🗂️ Make Docker Meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.DH_IMAGE }} - ghcr.io/${{ env.GH_IMAGE }} - tags: | - type=ref,event=tag - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}}.x - type=raw,value=latest - flavor: | - latest=false - - - name: ⏱️ Capture Build Timestamp - id: timestamp - run: echo "iso=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" - - - name: 🔧 Set up QEMU - uses: docker/setup-qemu-action@v4 - with: - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - - name: 🔧 Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: 🔑 Login to DockerHub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: 🔑 Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: 🚦 Check Registry Status - uses: crazy-max/ghaction-docker-status@v4 - - - name: ⚒️ Build and Push - uses: docker/build-push-action@v7 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/arm/v7 - tags: ${{ steps.meta.outputs.tags }} - build-args: | - VERSION=${{ steps.meta.outputs.version }} - REVISION=${{ github.sha }} - CREATED=${{ steps.timestamp.outputs.iso }} - cache-from: type=gha - cache-to: type=gha,mode=max - push: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..6bd3db38 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,400 @@ +# Builds and publishes the multi-arch Docker image +# +# Triggered by: +# - On git tag push, publishes to :X.Y.Z, :X.Y, and :latest +# - On manual dispatch from master, rebuilds and updates :latest +# - On weekly cron, rebuilds :latest from master for upstream patches +# +# The workflow will: +# - Builds multi-arch (amd64, arm64, armv7) in parallel on native runners +# - Trivy scans + reports security issues, and fails on CRITICAL CVEs +# - Publishes to GHCR, and to Docker Hub if creds are configured +# - Attests both the build provenance and SBOM and publishes to GHCR +# - Uploads digest, SBOM and outputs as artifact, and shows MD summary + +name: 🐳 Docker + +on: + workflow_dispatch: + inputs: + tag: + description: 'Existing git tag to build. Empty = build current ref as :latest.' + required: false + default: '' + push: + tags: ['*.*.*'] + schedule: + - cron: '0 4 * * 0' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.tag }} + cancel-in-progress: false + +permissions: + contents: read + +env: + DH_IMAGE: ${{ vars.DOCKER_REPO || 'lissy93/dashy' }} + GH_IMAGE: ghcr.io/${{ github.repository }} + +jobs: + build: + name: 🔨 Build (${{ matrix.arch }}) + timeout-minutes: 30 + permissions: + contents: read + packages: write + security-events: write + env: + DOCKER_BUILD_SUMMARY: "false" + DOCKER_BUILD_RECORD_UPLOAD: "false" + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + - platform: linux/arm/v7 + runner: ubuntu-latest + arch: armv7 + runs-on: ${{ matrix.runner }} + steps: + - name: 🛎️ Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag || github.ref }} + + - name: 🏷️ Resolve build version + id: version + env: + INPUT_TAG: ${{ inputs.tag }} + EVENT_NAME: ${{ github.event_name }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ -n "$INPUT_TAG" ]; then + v="$INPUT_TAG" + elif [ "$EVENT_NAME" = "push" ]; then + v="$REF_NAME" + else + v="latest" + fi + echo "value=$v" >> "$GITHUB_OUTPUT" + + - name: 🔧 Set up QEMU + if: matrix.arch == 'armv7' + uses: docker/setup-qemu-action@v4 + with: + platforms: linux/arm/v7 + + - name: 🔧 Set up Buildx + uses: docker/setup-buildx-action@v4 + + - name: 🔑 Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ⏱️ Capture build timestamp + id: timestamp + run: echo "iso=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" + + - name: 🔨 Build image (load for scan) + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=${{ matrix.arch }} + cache-to: type=gha,scope=${{ matrix.arch }},mode=max + load: true + tags: dashy-scan:${{ matrix.arch }} + provenance: false + build-args: | + VERSION=${{ steps.version.outputs.value }} + REVISION=${{ github.sha }} + CREATED=${{ steps.timestamp.outputs.iso }} + + - name: 🛡️ Trivy vulnerability scan + uses: aquasecurity/trivy-action@v0.36.0 + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2 + TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db:1 + with: + version: v0.70.0 + image-ref: dashy-scan:${{ matrix.arch }} + severity: CRITICAL + ignore-unfixed: true + exit-code: ${{ github.event_name == 'schedule' && '1' || '0' }} + vuln-type: 'os,library' + format: 'sarif' + output: 'trivy-${{ matrix.arch }}.sarif' + timeout: '10m' + + - name: 📤 Upload Trivy SARIF + if: always() && hashFiles(format('trivy-{0}.sarif', matrix.arch)) != '' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: trivy-${{ matrix.arch }}.sarif + category: trivy-${{ matrix.arch }} + + - name: 📤 Upload Trivy artifact + if: always() && hashFiles(format('trivy-{0}.sarif', matrix.arch)) != '' + uses: actions/upload-artifact@v7 + with: + name: trivy-${{ matrix.arch }} + path: trivy-${{ matrix.arch }}.sarif + if-no-files-found: ignore + retention-days: 1 + + - name: 🚀 Push by digest + id: push + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=${{ matrix.arch }} + outputs: type=image,name=${{ env.GH_IMAGE }},push-by-digest=true,name-canonical=true,push=true + provenance: false + build-args: | + VERSION=${{ steps.version.outputs.value }} + REVISION=${{ github.sha }} + CREATED=${{ steps.timestamp.outputs.iso }} + + - name: 🧬 Write digest + env: + DIGEST: ${{ steps.push.outputs.digest }} + DIGESTS_DIR: ${{ runner.temp }}/digests + ARCH: ${{ matrix.arch }} + run: | + mkdir -p "$DIGESTS_DIR" + echo "$DIGEST" > "$DIGESTS_DIR/$ARCH" + + - name: 📤 Upload digest + uses: actions/upload-artifact@v7 + with: + name: digest-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/${{ matrix.arch }} + if-no-files-found: error + retention-days: 1 + + merge: + name: 🧩 Merge & Push Manifests + needs: build + timeout-minutes: 30 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write + env: + HAS_DH: ${{ secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' }} + SEMVER_VALUE: ${{ inputs.tag || github.ref_name }} + SEMVER_ENABLE: ${{ github.event_name == 'push' || inputs.tag != '' }} + LATEST_ENABLE: ${{ inputs.tag == '' }} + steps: + - name: 📥 Download digests + uses: actions/download-artifact@v7 + with: + path: ${{ runner.temp }}/digests + pattern: digest-* + merge-multiple: true + + - name: 📥 Download Trivy SARIFs + uses: actions/download-artifact@v7 + continue-on-error: true + with: + path: ${{ runner.temp }}/trivy + pattern: trivy-* + merge-multiple: true + + - name: 🔧 Set up Buildx + uses: docker/setup-buildx-action@v4 + + - name: 🔑 Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 🔑 Login to Docker Hub + if: env.HAS_DH == 'true' + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: 🗂️ Generate tags + id: meta + uses: docker/metadata-action@v6 + with: + images: | + ${{ env.GH_IMAGE }} + ${{ env.HAS_DH == 'true' && env.DH_IMAGE || '' }} + tags: | + type=raw,value=latest,enable=${{ env.LATEST_ENABLE }} + type=semver,pattern={{version}},value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + type=semver,pattern={{major}}.{{minor}},value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + type=semver,pattern={{major}}.x,value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + flavor: | + latest=false + + - name: 🧩 Create & push manifest + id: manifest + working-directory: ${{ runner.temp }}/digests + run: | + set -euo pipefail + TAGS=() + while IFS= read -r tag; do TAGS+=(-t "$tag"); done \ + < <(jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON") + SOURCES=() + for f in *; do SOURCES+=("${GH_IMAGE}@$(cat "$f")"); done + docker buildx imagetools create "${TAGS[@]}" "${SOURCES[@]}" + PRIMARY=$(jq -r --arg img "$GH_IMAGE" \ + '[.tags[] | select(startswith($img + ":"))] | first // empty' \ + <<< "$DOCKER_METADATA_OUTPUT_JSON") + DIGEST=$(docker buildx imagetools inspect "$PRIMARY" --format '{{.Manifest.Digest}}') + echo "primary_tag=$PRIMARY" >> "$GITHUB_OUTPUT" + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + + - name: 🔐 Generate SBOM (SPDX) + uses: anchore/sbom-action@v0.24.0 + with: + image: ${{ steps.manifest.outputs.primary_tag }} + format: spdx-json + output-file: sbom.spdx.json + upload-artifact: false + + - name: 🪪 Attest SBOM + id: attest_sbom + uses: actions/attest@v4 + continue-on-error: true + with: + subject-name: ${{ env.GH_IMAGE }} + subject-digest: ${{ steps.manifest.outputs.digest }} + sbom-path: sbom.spdx.json + push-to-registry: true + + - name: 🛡️ Attest build provenance + id: attest_provenance + uses: actions/attest-build-provenance@v4 + continue-on-error: true + with: + subject-name: ${{ env.GH_IMAGE }} + subject-digest: ${{ steps.manifest.outputs.digest }} + push-to-registry: true + + - name: 📋 Summary + if: always() + continue-on-error: true + env: + SBOM_OUTCOME: ${{ steps.attest_sbom.outcome }} + SBOM_URL: ${{ steps.attest_sbom.outputs.attestation-url }} + PROV_OUTCOME: ${{ steps.attest_provenance.outcome }} + PROV_URL: ${{ steps.attest_provenance.outputs.attestation-url }} + DIGEST: ${{ steps.manifest.outputs.digest }} + PRIMARY: ${{ steps.manifest.outputs.primary_tag }} + TAGS_JSON: ${{ steps.meta.outputs.json }} + DIGESTS_DIR: ${{ runner.temp }}/digests + TRIVY_DIR: ${{ runner.temp }}/trivy + # Behold, some ugly bash, to produce a pretty output (don't read it, just trust) + run: | + set -euo pipefail + + attest_line() { + local label="$1" outcome="$2" url="$3" + case "$outcome" in + success) + if [ -n "$url" ]; then + echo "- ✅ $label attested ([view]($url))" + else + echo "- ✅ $label attested" + fi ;; + failure) echo "- ⚠️ $label attestation failed (image pushed without attest)" ;; + *) echo "- ⏭️ $label attestation \`$outcome\`" ;; + esac + } + + trivy_section() { + local dir="$1" + [ -d "$dir" ] || return 0 + local found=0 + local lines="" + local arch f n + for arch in amd64 arm64 armv7; do + f="$dir/trivy-$arch.sarif" + [ -f "$f" ] || continue + found=1 + n=$(jq '[.runs[]?.results[]?] | length' "$f" 2>/dev/null || echo 0) + [[ "$n" =~ ^[0-9]+$ ]] || n=0 + if [ "$n" = "0" ]; then + lines+="- ✅ \`$arch\` — no fixable CRITICAL CVEs"$'\n' + else + lines+="- ⚠️ \`$arch\` — $n fixable CRITICAL CVE(s)"$'\n' + fi + done + [ "$found" = "1" ] || return 0 + echo "## Security Scan" + echo "" + echo "Trivy (CRITICAL severity, fixable only):" + echo "" + printf '%s\n' "$lines" + echo "---" + echo "" + } + + arch_section() { + local arch="$1" + local file="$DIGESTS_DIR/$arch" + [ -f "$file" ] || return 0 + local digest manifest size count + digest=$(cat "$file") + manifest=$(docker buildx imagetools inspect "${PRIMARY%%:*}@$digest" --raw 2>/dev/null || echo '{}') + size=$(jq '[.layers[]?.size // 0] | add // 0' <<< "$manifest") + count=$(jq '.layers // [] | length' <<< "$manifest") + echo "#### Dashy \`$arch\`" + echo "" + echo "- **Digest:** \`$digest\`" + [ "$size" != "0" ] && echo "- **Size:** $(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "$size B")" + [ "$count" != "0" ] && echo "- **Layers:** $count" + echo "" + } + + # Clear auto-generated "Attestation Created" blocks from attest actions. + : > "$GITHUB_STEP_SUMMARY" + { + if [ -n "$DIGEST" ]; then + echo "## Docker Image" + echo "" + echo "**Manifest:** \`$DIGEST\`" + echo "" + echo '```bash' + jq -r '.tags[] | "docker pull \(.)"' <<< "$TAGS_JSON" + echo '```' + echo "" + echo "---" + echo "" + fi + echo "## Attestations" + echo "" + attest_line "SBOM" "$SBOM_OUTCOME" "$SBOM_URL" + attest_line "Build provenance" "$PROV_OUTCOME" "$PROV_URL" + echo "" + echo "---" + echo "" + trivy_section "$TRIVY_DIR" + echo "## Build Info" + echo "" + for arch in amd64 arm64 armv7; do + arch_section "$arch" + done + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml deleted file mode 100644 index beaf1c37..00000000 --- a/.github/workflows/draft-release.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: 🏗️ Draft New Release - -on: - push: - tags: - - '*.*.*' - workflow_dispatch: - inputs: - tag: - description: 'Tag to draft a release for (must already exist)' - required: true - -permissions: - contents: write - -jobs: - create-draft-release: - runs-on: ubuntu-latest - env: - TAG: ${{ github.event.inputs.tag || github.ref_name }} - steps: - - name: Checkout code 🛎️ - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Check if major or minor version changed 🔍 - id: version_check - env: - CURRENT_TAG: ${{ github.event.inputs.tag || github.ref_name }} - run: | - git fetch --tags --force - CURRENT_MM=$(echo "$CURRENT_TAG" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - - # Find the immediately previous tag (to detect patch-only bumps) - PREVIOUS_TAG=$(git tag --sort=-version:refname \ - | grep -v "^${CURRENT_TAG}$" | head -1) - - if [ -z "$PREVIOUS_TAG" ]; then - echo "No previous tag found, creating release" - echo "should_release=true" >> $GITHUB_OUTPUT - echo "previous_tag=" >> $GITHUB_OUTPUT - exit 0 - fi - - PREVIOUS_MM=$(echo "$PREVIOUS_TAG" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - if [ "$CURRENT_MM" = "$PREVIOUS_MM" ]; then - echo "Patch-only bump ($PREVIOUS_TAG -> $CURRENT_TAG), skipping" - echo "should_release=false" >> $GITHUB_OUTPUT - echo "previous_tag=" >> $GITHUB_OUTPUT - exit 0 - fi - - # Minor/major bump — find the last tag from the previous release - PREV_RELEASE_TAG=$(git tag --sort=-version:refname | while read -r t; do - [ "$t" = "$CURRENT_TAG" ] && continue - t_mm=$(echo "$t" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - if [ "$t_mm" != "$CURRENT_MM" ]; then echo "$t"; break; fi - done) - echo "Minor/major bump, comparing against ${PREV_RELEASE_TAG:-$PREVIOUS_TAG}" - echo "should_release=true" >> $GITHUB_OUTPUT - echo "previous_tag=${PREV_RELEASE_TAG:-$PREVIOUS_TAG}" >> $GITHUB_OUTPUT - - - name: Create draft release 📝 - if: steps.version_check.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch' - id: create_release - uses: softprops/action-gh-release@v3 - with: - tag_name: ${{ env.TAG }} - name: Release ${{ env.TAG }} - draft: true - prerelease: false - generate_release_notes: true - previous_tag: ${{ steps.version_check.outputs.previous_tag }} - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Job summary 📋 - if: always() - env: - REPO_URL: ${{ github.server_url }}/${{ github.repository }} - SHOULD_RELEASE: ${{ steps.version_check.outputs.should_release }} - RELEASE_URL: ${{ steps.create_release.outputs.url }} - PREV_TAG: ${{ steps.version_check.outputs.previous_tag }} - run: | - { - echo "## 🏗️ Draft Release" - echo "" - echo "| Step | Result |" - echo "|------|--------|" - echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |" - - if [ -n "$PREV_TAG" ]; then - echo "| Compared against | [\`${PREV_TAG}\`](${REPO_URL}/releases/tag/${PREV_TAG}) |" - fi - - if [ -n "$RELEASE_URL" ]; then - echo "| Draft release | ✅ [Review and publish](${RELEASE_URL}) |" - elif [ "$SHOULD_RELEASE" = "false" ]; then - echo "| Draft release | ⏭️ Skipped (patch-only bump) |" - else - echo "| Draft release | ❌ Failed |" - fi - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/manual-tag.yml b/.github/workflows/manual-tag.yml deleted file mode 100644 index 85cf5ff0..00000000 --- a/.github/workflows/manual-tag.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Manual fallback for creating a tag with optional version bump. -# The automated flow is handled by bump-and-tag.yml on PR merge. -name: 🏷️ Tag on Version Change - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to tag (e.g. 3.2.0). Leave empty to auto-bump patch.' - required: false - -concurrency: - group: manual-tag-version - cancel-in-progress: false - -jobs: - tag-version: - runs-on: ubuntu-latest - - steps: - - name: Check Out Repository 🛎️ - uses: actions/checkout@v6 - with: - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Configure Git Identity 🤖 - run: | - git config user.name "Liss-Bot" - git config user.email "liss-bot@d0h.co" - - - name: Determine and Apply Version 🔢 - id: version - env: - INPUT_VERSION: ${{ github.event.inputs.version }} - run: | - CURRENT=$(node -p "require('./package.json').version") - if [ -n "$INPUT_VERSION" ]; then - TARGET="${INPUT_VERSION#v}" - else - npm version patch --no-git-tag-version > /dev/null - TARGET=$(node -p "require('./package.json').version") - fi - if [ "$TARGET" != "$CURRENT" ]; then - npm version "$TARGET" --no-git-tag-version --allow-same-version - git add package.json - git commit -m "🔖 Bump version to $TARGET [skip ci]" - git push - echo "Committed version bump to $TARGET" - else - echo "package.json already at $CURRENT, skipping commit" - fi - echo "TARGET=$TARGET" >> $GITHUB_OUTPUT - - - name: Create and Push Tag ⤴️ - env: - TAG: ${{ steps.version.outputs.TARGET }} - run: | - git fetch --tags --force - if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists, skipping" - else - git tag -a "$TAG" -m "Release v$TAG" - git push origin "$TAG" - echo "Created and pushed tag $TAG" - fi diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 65f2072a..fc599c6b 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,11 +1,15 @@ # Syncs the full source of the Dashy repo over to our Codeberg mirror # For all you non-Microsoft babes! # This is then accessible over at https://codeberg.org/alicia/dashy -name: 🪞 Mirror to Codeberg +name: 🪞 Mirror on: workflow_dispatch: schedule: - cron: '30 3 * * 0' # At 03:30 on Sunday + +permissions: + contents: read + jobs: codeberg: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-quality-check.yml b/.github/workflows/pr-quality-check.yml deleted file mode 100644 index 60fd2490..00000000 --- a/.github/workflows/pr-quality-check.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: 🔍 PR Quality Check - -on: - pull_request: - branches: ['master', 'develop'] - paths-ignore: - - '**.md' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - changes: - name: 🔎 Detect Changes - runs-on: ubuntu-latest - outputs: - lockfile: ${{ steps.filter.outputs.lockfile }} - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔎 Filter Paths - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - lockfile: - - 'yarn.lock' - - lint: - name: 📝 Lint Code - runs-on: ubuntu-latest - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: 📦 Install Dependencies - run: yarn install --frozen-lockfile - - - name: 🔍 Run ESLint - run: yarn lint - - typecheck: - name: 🧷 Type Check - runs-on: ubuntu-latest - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: 📦 Install Dependencies - run: yarn install --frozen-lockfile - - - name: 🧷 Run vue-tsc - run: yarn typecheck - - test: - name: 🧪 Run Tests - runs-on: ubuntu-latest - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: 📦 Install Dependencies - run: yarn install --frozen-lockfile - - - name: 🧪 Run Tests - run: yarn test - - build: - name: 🏗️ Build Application - runs-on: ubuntu-latest - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: 📦 Install Dependencies - run: yarn install --frozen-lockfile - - - name: 🏗️ Build Project - run: yarn build - - - name: ✅ Verify Build Output - run: | - if [ ! -d "dist" ]; then - echo "❌ Build failed: dist directory not created" - exit 1 - fi - if [ ! -f "dist/index.html" ]; then - echo "❌ Build failed: index.html not found" - exit 1 - fi - echo "✅ Build successful" - - docker-smoke: - name: 🐳 Docker Smoke Test - runs-on: ubuntu-latest - continue-on-error: true - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🐳 Build & Test Docker Image - run: sh tests/docker-smoke-test.sh - timeout-minutes: 10 - - security: - name: 🔒 Security Audit - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.lockfile == 'true' - continue-on-error: true - steps: - - name: 🛎️ Checkout Code - uses: actions/checkout@v6 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: 📦 Install Dependencies - run: yarn install --frozen-lockfile - - - name: 🔒 Run Security Audit - run: yarn audit --level high diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c4f7c581 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,171 @@ +# Builds Dashy and drafts a GitHub release with the compiled tarball, +# along with SHA256 checksum and SLSA build-provenance attestation +# +# Triggered by: +# - Push of any major/minor (X.Y.0) git tag +# - Manual dispatch with any existing tag (any version) + +name: 🚀 Release + +on: + push: + tags: ['*.*.0'] + workflow_dispatch: + inputs: + tag: + description: 'Existing git tag to release (e.g. 4.2.0)' + required: true + +concurrency: + group: ${{ github.workflow }}-${{ inputs.tag || github.ref_name }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + release: + name: 🚀 Build & Draft Release + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: write + id-token: write + attestations: write + env: + TAG: ${{ inputs.tag || github.ref_name }} + steps: + - name: 🛎️ Checkout tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ env.TAG }} + fetch-depth: 0 + + - name: 🔧 Setup Node.js + uses: actions/setup-node@v6 # zizmor: ignore[cache-poisoning] + with: + node-version: '20' + + - name: 📥 Install dependencies + run: yarn install --frozen-lockfile --ignore-engines --network-timeout 300000 + + - name: 🏗️ Build app + run: NODE_OPTIONS=--openssl-legacy-provider yarn build --mode production + + - name: 📦 Package release tarball + id: package + run: | + set -euo pipefail + STAGING="dashy-release-staging" + mkdir -p "$STAGING" + cp -r dist "$STAGING/" + cp -r services "$STAGING/" + cp -r public "$STAGING/" + cp -r user-data "$STAGING/" + cp server.js "$STAGING/" + cp yarn.lock "$STAGING/" + mkdir -p "$STAGING/src/utils/config" + cp src/utils/config/ConfigSchema.json "$STAGING/src/utils/config/" + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + delete pkg.devDependencies; + fs.writeFileSync('$STAGING/package.json', JSON.stringify(pkg, null, 2)); + " + TARBALL="dashy-${TAG}.tar.gz" + tar -czf "$TARBALL" -C "$STAGING" . + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + echo "size=$(du -h "$TARBALL" | cut -f1)" >> "$GITHUB_OUTPUT" + + - name: 🔢 Generate SHA256 checksum + id: checksum + env: + TARBALL: ${{ steps.package.outputs.tarball }} + run: | + set -euo pipefail + CHECKSUM="${TARBALL}.sha256" + sha256sum "$TARBALL" > "$CHECKSUM" + echo "file=$CHECKSUM" >> "$GITHUB_OUTPUT" + + - name: 🪪 Generate build provenance attestation + id: attest + uses: actions/attest-build-provenance@v4 + with: + subject-path: ${{ steps.package.outputs.tarball }} + + - name: 📤 Rename attestation bundle + id: attest_asset + env: + TARBALL: ${{ steps.package.outputs.tarball }} + BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: | + set -euo pipefail + OUT="${TARBALL}.intoto.jsonl" + cp "$BUNDLE" "$OUT" + echo "file=$OUT" >> "$GITHUB_OUTPUT" + + - name: 🔎 Find previous release tag + id: prev + env: + CURRENT_TAG: ${{ env.TAG }} + run: | + set -euo pipefail + git fetch --tags --force + PREV=$({ echo "$CURRENT_TAG"; git tag | grep -E '^[0-9]+\.[0-9]+\.0$' || true; } \ + | sort -uV \ + | awk -v cur="$CURRENT_TAG" '$0 == cur { print prev; exit } { prev = $0 }') + echo "tag=$PREV" >> "$GITHUB_OUTPUT" + + - name: 📝 Create draft release + id: release + # Kept over `gh release create` for built-in generate_release_notes, + # previous_tag selection, fail_on_unmatched_files, and multi-file upload. + uses: softprops/action-gh-release@v3 # zizmor: ignore[superfluous-actions] + with: + tag_name: ${{ env.TAG }} + name: Release ${{ env.TAG }} + draft: true + prerelease: false + generate_release_notes: true + previous_tag: ${{ steps.prev.outputs.tag }} + fail_on_unmatched_files: true + files: | + ${{ steps.package.outputs.tarball }} + ${{ steps.checksum.outputs.file }} + ${{ steps.attest_asset.outputs.file }} + token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + + - name: 📋 Job summary + if: always() + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + PREV_TAG: ${{ steps.prev.outputs.tag }} + RELEASE_URL: ${{ steps.release.outputs.url }} + TARBALL: ${{ steps.package.outputs.tarball }} + SIZE: ${{ steps.package.outputs.size }} + ATTEST_URL: ${{ steps.attest.outputs.attestation-url }} + run: | + set -euo pipefail + { + echo "## 🚀 Release Draft" + echo "" + echo "| Item | Value |" + echo "|------|-------|" + echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |" + if [ -n "$PREV_TAG" ]; then + echo "| Notes since | [\`${PREV_TAG}\`](${REPO_URL}/releases/tag/${PREV_TAG}) |" + fi + if [ -n "$TARBALL" ]; then + echo "| Tarball | \`${TARBALL}\` (${SIZE:-?}) |" + fi + if [ -n "$ATTEST_URL" ]; then + echo "| Attestation | ✅ [View](${ATTEST_URL}) |" + else + echo "| Attestation | ❌ Failed |" + fi + if [ -n "$RELEASE_URL" ]; then + echo "| Draft release | ✅ [Review and publish](${RELEASE_URL}) |" + else + echo "| Draft release | ❌ Failed |" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/bump-and-tag.yml b/.github/workflows/tag.yml similarity index 66% rename from .github/workflows/bump-and-tag.yml rename to .github/workflows/tag.yml index 4378d6aa..58902f4f 100644 --- a/.github/workflows/bump-and-tag.yml +++ b/.github/workflows/tag.yml @@ -1,18 +1,32 @@ -# Creates a new git tag when a PR is merged +# Creates a new git tag when a PR is merged, or on manual dispatch # -# Here's the flow: +# PR trigger flow: # - Triggered whenever a PR is merged, if that PR made code changes # - If version wasn't bumped in PR, increment patch version and update package.json # - Otherwise (if the PR did bump version) we use the new version from package.json # - Creates and pushes a git tag for the new version # - That git tag then triggers Docker publishing and release drafting in other CI -# - Add tags to issues from newly relesaed features/fixes (if applicable) +# - Add labels and release comments to referenced issues (if applicable) # - Trigger fresh deploy of docs site, so changelog remains up-to-date # - Finally, shows summary of actions taken and new tag published -name: 🔖 Auto Version & Tag +# +# Manual dispatch flow: +# - If a version is provided, sets package.json to that version +# - If no version is provided, increments patch version automatically +# - Creates and pushes a git tag, then triggers docs site rebuild + +name: 🔖 Tag on: - pull_request_target: + workflow_dispatch: + inputs: + version: + description: 'Version to tag (e.g. 4.1.0). Leave blank to auto-increment patch.' + required: false + type: string + # Btw, this is a safe and intentional trigger + # It only runs once reviewed PR merged, and secrets are scoped + pull_request_target: # zizmor: ignore[dangerous-triggers] types: [closed] branches: [master] @@ -25,14 +39,37 @@ permissions: pull-requests: read issues: write +env: + IS_MANUAL: ${{ github.event_name == 'workflow_dispatch' }} + jobs: version-and-tag: - if: github.event.pull_request.merged == true + if: >- + github.event_name == 'workflow_dispatch' + || github.event.pull_request.merged == true runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - name: Check PR for code changes and version bump 📂 + - name: 🔢 Validate manual dispatch + if: env.IS_MANUAL == 'true' + env: + INPUT_VERSION: ${{ inputs.version }} + DISPATCH_REF: ${{ github.ref }} + run: | + set -euo pipefail + if [ "$DISPATCH_REF" != "refs/heads/master" ]; then + echo "::error::Manual dispatch only allowed from master (got: $DISPATCH_REF)" + exit 1 + fi + if [ -n "$INPUT_VERSION" ] && ! printf '%s' "$INPUT_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid version '${INPUT_VERSION}'. Must be semver (e.g. 4.1.0)." + exit 1 + fi + + - name: 📂 Check PR for code changes and version bump id: check_pr + if: env.IS_MANUAL != 'true' uses: actions/github-script@v8 with: script: | @@ -43,7 +80,7 @@ jobs: github.rest.pulls.listFiles, { owner, repo, pull_number } ); const codePatterns = [ - /^src\//, /^services\//, /^public\//, /^Dockerfile$/, /^[^/]+\.js$/, + /^src\//, /^services\//, /^public\//, /^Dockerfile$/, /^yarn.lock$/, /^[^/]+\.js$/, ]; const codeChanged = files.some(f => codePatterns.some(p => p.test(f.filename)) @@ -83,21 +120,21 @@ jobs: core.setOutput('needs_bump', needsBump.toString()); core.setOutput('needs_tag', needsTag.toString()); - - name: Checkout repository 🛎️ - if: steps.check_pr.outputs.needs_tag == 'true' + - name: 🛎️ Checkout repository + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' uses: actions/checkout@v6 with: token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - name: Configure git identity 👤 - if: steps.check_pr.outputs.needs_tag == 'true' + - name: 👤 Configure git identity + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' run: | git config user.name "Liss-Bot" git config user.email "liss-bot@d0h.co" - - name: Extract referenced issues 🔍 + - name: 🔍 Extract referenced issues id: issues - if: steps.check_pr.outputs.needs_tag == 'true' + if: env.IS_MANUAL != 'true' && steps.check_pr.outputs.needs_tag == 'true' uses: actions/github-script@v8 with: script: | @@ -119,47 +156,76 @@ jobs: core.info(`Found issue references: ${unique.join(', ')}`); core.setOutput('numbers', unique.join(',')); - - name: Bump patch version ⬆️ - if: steps.check_pr.outputs.needs_bump == 'true' - run: | - npm version patch --no-git-tag-version - git add package.json - git commit -m "🔖 Bump version to $(node -p "require('./package.json').version")" - git push - - - name: Create and push tag 🏷️ - id: tag - if: steps.check_pr.outputs.needs_tag == 'true' + - name: ⬆️ Bump version + id: bump + if: >- + env.IS_MANUAL == 'true' + || steps.check_pr.outputs.needs_bump == 'true' env: - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_TITLE: ${{ github.event.pull_request.title }} - PR_AUTHOR: ${{ github.event.pull_request.user.login }} - MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + if [ "$IS_MANUAL" = "true" ] && [ -n "$INPUT_VERSION" ]; then + npm version "$INPUT_VERSION" --no-git-tag-version --allow-same-version + else + npm version patch --no-git-tag-version + fi + NEW_VERSION=$(node -p "require('./package.json').version") + git add package.json + if git diff --cached --quiet; then + echo "package.json already at $NEW_VERSION, nothing to commit" + echo "bumped=false" >> "$GITHUB_OUTPUT" + else + git commit -m "🔖 Bump version to $NEW_VERSION" + git push + echo "bumped=true" >> "$GITHUB_OUTPUT" + fi + + - name: 🏷️ Create and push tag + id: tag + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' + env: + PR_NUMBER: ${{ github.event.pull_request.number || '' }} + PR_TITLE: ${{ github.event.pull_request.title || '' }} + PR_AUTHOR: ${{ github.event.pull_request.user.login || github.actor }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }} ISSUES: ${{ steps.issues.outputs.numbers }} run: | + set -euo pipefail VERSION=$(node -p "require('./package.json').version") git fetch --tags --force if git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then echo "Tag $VERSION already exists, skipping" + echo "result=existed" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" exit 0 fi { printf 'Dashy v%s 🚀\n\n' "$VERSION" - printf 'PR: #%s - %s\n' "$PR_NUMBER" "$PR_TITLE" + if [ -n "$PR_NUMBER" ]; then + printf 'PR: #%s - %s\n' "$PR_NUMBER" "$PR_TITLE" + else + printf 'Manual release by @%s\n' "$PR_AUTHOR" + fi if [ -n "$ISSUES" ]; then printf 'Resolves: %s\n' "$(echo "$ISSUES" | sed 's/,/, #/g; s/^/#/')" fi printf 'Author: @%s\n' "$PR_AUTHOR" - printf 'Merge commit: %s\n' "$MERGE_SHA" + printf 'Commit: %s\n' "$MERGE_SHA" } > tag-message.txt git tag -a "$VERSION" -F tag-message.txt git push origin "$VERSION" + echo "result=created" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - name: Label referenced issues 🛩️ + - name: 🛩️ Label referenced issues id: label - if: steps.check_pr.outputs.needs_tag == 'true' && steps.issues.outputs.numbers != '' + if: >- + env.IS_MANUAL != 'true' + && steps.check_pr.outputs.needs_tag == 'true' + && steps.issues.outputs.numbers != '' continue-on-error: true uses: actions/github-script@v8 env: @@ -234,53 +300,65 @@ jobs: } } - - name: Trigger docs site rebuild 📝 + - name: 📝 Trigger docs site rebuild id: docs - if: steps.tag.outcome == 'success' + if: steps.tag.outputs.result == 'created' continue-on-error: true env: HOOK_URL: ${{ secrets.DOCS_SITE_REBUILD_HOOK }} + VERSION: ${{ steps.tag.outputs.version }} run: | + set -euo pipefail if [ -z "$HOOK_URL" ]; then echo "::warning::DOCS_SITE_REBUILD_HOOK secret is not set, skipping" exit 1 fi - VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") curl -sf -X POST -d '{}' "${HOOK_URL}?trigger_title=v${VERSION}+released" \ --max-time 15 --retry 2 --retry-max-time 30 echo "Triggered docs rebuild for v${VERSION}" - - name: Job summary 📋 + - name: 📋 Job summary if: always() env: - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number || '' }} + PR_TITLE: ${{ github.event.pull_request.title || '' }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} NEEDS_BUMP: ${{ steps.check_pr.outputs.needs_bump }} NEEDS_TAG: ${{ steps.check_pr.outputs.needs_tag }} ISSUES: ${{ steps.issues.outputs.numbers }} + BUMPED: ${{ steps.bump.outputs.bumped }} TAG_OUTCOME: ${{ steps.tag.outcome }} + TAG_RESULT: ${{ steps.tag.outputs.result }} + TAG_VERSION: ${{ steps.tag.outputs.version }} LABEL_OUTCOME: ${{ steps.label.outcome }} DOCS_OUTCOME: ${{ steps.docs.outcome }} run: | - VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") + set -euo pipefail + VERSION="${TAG_VERSION:-$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown")}" { echo "## 🔖 Auto Version & Tag" echo "" echo "| Step | Result |" echo "|------|--------|" - echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) — ${PR_TITLE} |" - if [ "$NEEDS_BUMP" = "true" ]; then + if [ "$IS_MANUAL" = "true" ]; then + echo "| Trigger | Manual dispatch |" + elif [ -n "$PR_NUMBER" ]; then + echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) — ${PR_TITLE} |" + fi + + if [ "$BUMPED" = "true" ]; then echo "| Version bump | ✅ \`${VERSION}\` |" else echo "| Version bump | ⏭️ Skipped |" fi - if [ "$NEEDS_TAG" = "true" ] && [ "$TAG_OUTCOME" = "success" ]; then + if [ "$TAG_RESULT" = "created" ]; then echo "| Tag | ✅ [\`${VERSION}\`](${REPO_URL}/releases/tag/${VERSION}) |" - elif [ "$NEEDS_TAG" = "true" ]; then + elif [ "$TAG_RESULT" = "existed" ]; then + echo "| Tag | ⏭️ Already exists: \`${VERSION}\` |" + elif [ "$TAG_OUTCOME" = "failure" ]; then echo "| Tag | ❌ Failed |" else echo "| Tag | ⏭️ Skipped |" diff --git a/.github/workflows/update-docs-site.yml b/.github/workflows/update-docs-site.yml index de1f627c..72fa8150 100644 --- a/.github/workflows/update-docs-site.yml +++ b/.github/workflows/update-docs-site.yml @@ -1,4 +1,4 @@ -name: 📝 Update Documentation +name: 📝 Sync Docs # This will run whenever the /docs directory in master branch is updated, # or if the workflow is manually dispatched, plus a sync check on Sun at 03:30 UTC @@ -12,6 +12,8 @@ on: paths: - 'docs/**' +permissions: + contents: write # Jobs to be run: # 1. Checkout master branch diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 2d320b26..3e6af4a9 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -4,6 +4,10 @@ on: workflow_dispatch: # Manual dispatch schedule: - cron: '0 1 * * 0' # At 01:00 on Sunday. + +permissions: + contents: read + jobs: update-wiki: runs-on: ubuntu-latest diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000..fce28059 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,17 @@ +# zizmor config, for the static analysis of our GH Actions workflows +# Run with: `zizmor .github/workflows/`. Docs: https://docs.zizmor.sh +# Scan is also runs in our ci.yml, whenever workflow changes are made + +# 2 rules are disabled right now: +# - unpinned-uses: The only unpinned versions are official ones. +# And in the context of each, there shouldn't be any risk. +# Pinning causes the need to update each whenever a new version or patch is released +# - artipacked: Too many false positives. +# Was flagging every `actions/checkout` for not having `persist-credentials: false` +# Even tho we never upload or do anything public with the checkout artifacts + +rules: + unpinned-uses: + disable: true + artipacked: + disable: true diff --git a/.gitignore b/.gitignore index 07be5993..81db961b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules /dist +# Dashy config backups +user-data/config-backups/ + # local env files .env.local .env.*.local diff --git a/LICENSE b/LICENSE index 1682645c..32df6263 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2021-2024 Alicia Sykes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2019-2026 Alicia Sykes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/authentication.md b/docs/authentication.md index b3a56a7f..0bec846c 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -30,7 +30,7 @@ > [!IMPORTANT] > Dashy's built-in auth is not intended to protect a publicly hosted instance against unauthorized access. Instead you should use an auth provider compatible with your reverse proxy, or access Dashy via your VPN, or implement your own SSO logic. > -> If Dashy is only accessible within your home network and you just want a login page, then the built-in auth may be sufficient. To also protect server-side endpoints and config files, set `ENABLE_HTTP_AUTH=true` (see [Adding HTTP Auth to Configuration](#adding-http-auth-to-configuration)). +> If Dashy is only accessible within your home network and you just want a login page, then the built-in auth may be sufficient. To also protect server-side endpoints and config files: with built-in auth set `ENABLE_HTTP_AUTH=true` ([details](#adding-http-auth-to-configuration)). (Or, consider setting up[OIDC](#oidc), [Keycloak](#keycloak), or [Header Auth](#header-authentication), where the server-side enforcement is on automatically). ## Built-In Auth @@ -210,11 +210,13 @@ Use the following run command, replacing the attributes (default credentials, po docker run -d \ -p 8081:8080 \ --name auth-server \ - -e KEYCLOAK_USER=admin \ - -e KEYCLOAK_PASSWORD=admin \ - quay.io/keycloak/keycloak:15.0.2 + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + quay.io/keycloak/keycloak:25.0 start-dev ``` +(The `KEYCLOAK_USER` / `KEYCLOAK_PASSWORD` env vars and the `/auth` URL prefix from Keycloak 16 and older have been replaced. If you are still on 17 or older, set `legacySupport: true` in your Dashy config later on.) + If you need to pull from DockerHub, a non-official image is available [here](https://registry.hub.docker.com/r/jboss/keycloak). Or if you would prefer not to use Docker, you can also directly install Keycloak from source, following [this guide](https://www.keycloak.org/docs/latest/getting_started/index.html). You should now be able to access the Keycloak web interface, using the port specified above (e.g. `http://127.0.0.1:8081`), login with the default credentials, and when prompted create a new password. @@ -230,14 +232,27 @@ Before we can use Keycloak, we must first set it up with some users. Keycloak us You can now create your first user. 1. In the left-hand menu, click 'Users', then 'Add User' -2. Fill in the form, including username and hit 'Save' +2. Fill in the form. On Keycloak 25 and newer, *First name* and *Last name* are required by the default user-profile schema. If you skip them the user can sign in but login will then fail with "Account is not fully set up" 3. Under the 'Credentials' tab, give the new user an initial password. They will be prompted to change this after first login -The last thing we need to do in the Keycloak admin console is to create a new client +Next, create a new client for Dashy. 1. Within your new realm, navigate to 'Clients' on the left-hand side, then click 'Create' in the top-right -2. Choose a 'Client ID', set 'Client Protocol' to 'openid-connect', and for 'Valid Redirect URIs' put a URL pattern to where you're hosting Dashy (if you're just testing locally, then * is fine), and do the same for the 'Web Origins' field -3. Make note of your client-id, and click 'Save' +2. Choose a 'Client ID' (e.g. `dashy`), set 'Client Protocol' to 'openid-connect' +3. Turn *Client authentication* OFF and leave *Standard flow* enabled. Dashy is a SPA, so it acts as an OAuth public client with PKCE. A confidential client requires a client_secret that a browser app can't safely hold +4. For 'Valid Redirect URIs' put the URL where you host Dashy (e.g. `https://dashy.example.com/*`, or just `*` while testing locally). Do the same for the 'Web Origins' field +5. Make note of your client-id, and click 'Save' + +For the `adminRole` check to work, the role must appear in the id_token (Keycloak's default mapper only adds it to the access token): + +1. Open your `dashy` client, go to the *Client scopes* tab, click the dedicated scope row (`dashy-dedicated`) +2. Add a new mapper of type *User Realm Role*, name it (e.g. `realm_roles`), claim name `realm_access.roles`, multivalued ON, *Add to ID token* ON, *Add to access token* ON +3. (Optional, for `adminGroup` instead of `adminRole`) Add a second mapper of type *Group Membership*, claim name `groups` + +To create the admin role itself and grant it to a user: + +1. *Realm roles* in the left-hand menu, *Create role*, name it (e.g. `dashy-admin`) +2. *Users* → pick your admin user → *Role mapping* → *Assign role* → select `dashy-admin` ### 3. Enable Keycloak in Dashy Config File @@ -246,13 +261,15 @@ For example: ```yaml appConfig: - ... + # ... + disableConfigurationForNonAdmin: true auth: enableKeycloak: true keycloak: serverUrl: 'http://localhost:8081' realm: 'alicia-homelab' clientId: 'dashy' + adminRole: 'dashy-admin' # role name that grants admin privileges ``` Note that if you are using Keycloak V 17 or older, you will also need to set `legacySupport: true` (also under `appConfig.auth.keycloak`). This is because the API endpoint was updated in later versions. @@ -281,12 +298,19 @@ sections: groups: ['DevelopmentTeam'] ``` -Depending on how you're hosting Dashy and Keycloak, you may also need to set some HTTP headers, to prevent a CORS error. This would typically be the `Access-Control-Allow-Origin [URL-of Dashy]` on your Keycloak instance. See the [Setting Headers](https://github.com/Lissy93/dashy/blob/master/docs/management.md#setting-headers) guide in the management docs for more info. - Your app is now secured :) When you load Dashy, it will redirect to your Keycloak login page, and any user without valid credentials will be prevented from accessing your dashboard. From within the Keycloak console, you can then configure things like time-outs, password policies, etc. You can also backup your full Keycloak config, and it is recommended to do this, along with your Dashy config. You can spin up both Dashy and Keycloak simultaneously and restore both applications configs using a `docker-compose.yml` file, and this is recommended. +### CORS Headers +If Dashy and Keycloak run on different origins (typical when testing locally on different `localhost:` ports), Keycloak's default `Content-Security-Policy: frame-ancestors 'self'` and `X-Frame-Options: SAMEORIGIN` block the hidden iframe `keycloak-js` uses to check your session. Symptom: a generic "Authentication failed (Keycloak)" toast on first load. To allow the iframe, open *Realm settings → Security defenses → Browser headers*, clear `X-Frame-Options`, and change `Content-Security-Policy` to `frame-src 'self' ; frame-ancestors 'self' ; object-src 'none';`. Same-origin production deployments don't hit this. + +### How server-side enforcement works + +Dashy's server reads `auth.keycloak` from `conf.yml` at boot, lazily fetches your Keycloak realm's OIDC discovery doc + JWKS, then verifies the `id_token` the SPA attaches to every API call as `Authorization: Bearer `. Tokens that fail signature / issuer / audience / expiry verification are rejected with `401`. Write endpoints (`POST /config-manager/save`) additionally require the `adminRole` (or `adminGroup`) to be present in the token claims, and non-admins receive `403`. Note that the `/conf.yml` remains anonymously readable still (so the SPA can bootstrap before login). + +The admin check reads the role / group claim from the id_token, so the client mapper from Step 2 above (the one with *Add to ID token* on) is what makes `adminRole` / `adminGroup` work. Without it the server gets a token with no roles claim and treats everyone as non-admin. + ### Troubleshooting Keycloak If you encounter issues with your Keycloak setup, follow these steps to troubleshoot and resolve common problems. @@ -357,6 +381,7 @@ Dashy also supports using a general [OIDC compatible](https://openid.net/connect ```yaml appConfig: + disableConfigurationForNonAdmin: true # Prevent authenticated non-admins using editor auth: enableOidc: true oidc: @@ -368,6 +393,8 @@ appConfig: Because Dashy is a SPA, a [public client](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1) registration with PKCE is needed. +If you set `adminGroup`, include `groups` in `scope` (e.g. `scope: 'openid profile email groups'`) so your IdP actually returns the claim in the id_token. Same goes for `adminRole` and a `roles` scope if your IdP needs one. + Note, that if your `clientId` is numeric, you must place it in quotes. Otherwise it will be interpreted as a number and truncated to 64 chars! An example for Authelia is shared below, but other OIDC systems can be used: @@ -396,6 +423,12 @@ identity_providers: Groups and roles will be populated and available for controlling display similar to [Keycloak](#Keycloak) above. +### How server-side enforcement works + +Dashy's server reads `auth.oidc` from `conf.yml` at boot, lazily fetches the OIDC discovery doc + JWKS from your `endpoint`, then verifies the `id_token` the SPA attaches to every API call as `Authorization: Bearer `. Tokens that fail signature / issuer / audience / expiry verification are rejected with `401`. Write endpoints (`POST /config-manager/save`) additionally require the `adminGroup` (or `adminRole`) to be present in the token's `groups` / `roles` claims, and non-admins receive `403`. Note that the `/conf.yml` remains anonymously readable still + +Your IdP must include `groups` / `roles` in the id_token, not only the access token, for the admin check to work (most IdPs do this when the `groups` scope is requested). + --- ## authentik @@ -433,7 +466,7 @@ A dialog box will pop-up, select the `OAuth2/OpenID Provider`. Click `Next`. ![image](https://github.com/user-attachments/assets/ea84fe57-b813-404d-8dad-5e221b440bdb) -On the next page of the wizard, set the `Name`, `Authentication flow`, and `Authorization flow`. See example below. Using the `default-provider-authorization-implicit-consent` authorization flow on internal services and `default-provider-authorization-explicit-consent` on external services is a common practice. However, it is fully up to you on how you would like to configure this option. `Implicit` will login directly without user consent, `explicit` will ask if the user approves the service being logged into with their user credentials. +On the next page of the wizard, set the `Name`, `Authentication flow`, `Authorization flow`, and `Invalidation flow`. See example below. Using the `default-provider-authorization-implicit-consent` authorization flow on internal services and `default-provider-authorization-explicit-consent` on external services is a common practice. However, it is fully up to you on how you would like to configure this option. `Implicit` will login directly without user consent, `explicit` will ask if the user approves the service being logged into with their user credentials. For the invalidation flow (required on Authentik 2023.10 and later) the built-in `default-provider-invalidation-flow` is fine. ![image](https://github.com/user-attachments/assets/e600aeaf-08d1-49aa-b304-11e90e5c89cd) @@ -448,7 +481,15 @@ Scroll down to set the `Signing Key`. It is recommended to use the built in `aut ![image](https://github.com/user-attachments/assets/386c0750-9d2b-4482-8938-8b301b489b38) -Expand `Advanced protocol settings` then verify the `Scopes` are set to what is highlighted in `white` below. Set the `Subject mode` to `Based on the Users's Email`. +If you plan to use `adminGroup` in your Dashy config, you need a `groups` scope mapping first. Authentik does not ship one by default. Open *Customisation > Property Mappings* in a new tab, click *Create > Scope Mapping*, set *Name* to `groups`, *Scope name* to `groups`, and *Expression* to: + +```python +return {"groups": [g.name for g in request.user.ak_groups.all()]} +``` + +Save it, then come back to the provider wizard. + +Expand `Advanced protocol settings` then verify the `Scopes` are set to what is highlighted in `white` below (including the `groups` mapping you just created, if you want `adminGroup` to work). Set the `Subject mode` to `Based on the Users's Email`. ![image](https://github.com/user-attachments/assets/ae5e87b8-1ad6-41dd-b6e1-9665623f842a) @@ -507,11 +548,12 @@ Enter the `Client ID` in the `clientId` field and `OpenID Configuration Issuer` Below is how to configure the `auth` section in the yaml syntax. Once this is enabled, when an attempt to access `Dashy` is made it will now redirect you to the `authentik` login page moving forward. -``` +```yaml appConfig: theme: glass layout: auto iconSize: medium + disableConfigurationForNonAdmin: true # Prevent logged-in, non-admins using the view/edit config features auth: enableOidc: true oidc: diff --git a/docs/multi-language-support.md b/docs/multi-language-support.md index d055d8b3..9a01ac97 100644 --- a/docs/multi-language-support.md +++ b/docs/multi-language-support.md @@ -79,6 +79,25 @@ If you are not comfortable with making pull requests, or do not want to modify t --- +## Checking Translation Coverage + +We've got a short test/lint script for verifying translation files are complete and valid, and to show per-language coverage report. + +```bash +yarn validate-locales +``` + +This will run the following checks: +- Ensure all locale files are registered, present and parsable (failure) +- Non-existing translations used in the code, not present in en.json (failure) +- Translations in en.json never used anywhere in the code (warn) +- Translations in other locales not used in en.json or code (warn) +- Missing translations in other locales compared to en.json (coverage report) + +This script is located in [`tests/locales/check-locales.js`](https://github.com/lissy93/dashy/blob/master/tests/locales/check-locales.js), and can be executed directly, or with `yarn validate-locales` and is also run as part of the CI workflow on PRs. + +--- + ## Adding New Text to a Component If you're working on a new component, then any text that is displayed to the user should be extracted out of the component, and stored in the file. This also applies to any existing components, that might have been forgotten to be translated. Thankfully, everything is already setup, so this is a pretty easy job. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b8c6e101..c6cff83c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,939 +1,939 @@ -# Troubleshooting - -> _**This document contains common problems and their solutions.**_
-> Please ensure your issue isn't listed here, before opening a new ticket. -> -> _Found something not listed here? Consider adding it, to help other users._ - -## Contents - -- [Config not saving](#config-not-saving) - - [Permission denied or read-only filesystem](#permission-denied-or-read-only-filesystem-eacces-erofs) - - [Kubernetes ConfigMap mount is read-only](#kubernetes-configmap-mount-is-read-only) - - [SELinux or AppArmor blocks the write](#selinux-or-apparmor-blocks-the-write) - - [Backup step fails so save aborts](#backup-step-fails-so-save-aborts) - - [Save button is missing or returns 403](#save-button-is-missing-or-returns-403-forbidden) - - [Save unavailable on Vercel, Netlify or other static hosts](#save-unavailable-on-vercel-netlify-or-other-static-hosts) - - [/config-manager/save returns 404 or HTML](#config-managersave-returns-404-or-html) - - ["Invalid filename" when saving a sub-page](#invalid-filename-when-saving-a-sub-page) - - ["Cannot save to an external URL"](#cannot-save-to-an-external-url) - - [Saved successfully but the UI shows the old config](#saved-successfully-but-the-ui-shows-the-old-config) - - [Container crashes or restart loop after saving](#container-crashes-or-restart-loop-after-saving-310-and-311-only) - - [Intentionally read-only mode](#intentionally-read-only-mode) -- [Refused to Connect in Web Content View](#refused-to-connect-in-modal-or-workspace-view) -- [404 On Static Hosting](#404-on-static-hosting) -- [404 from Mobile Home Screen](#404-after-launch-from-mobile-home-screen) -- [404 On Multi-Page Apps](#404-on-multi-page-apps) -- [Dashy hosted at a sub-path](#dashy-hosted-at-a-sub-path-eg-examplecomdashy) -- [Sub-page shows "Unable to find config for ..."](#sub-page-shows-unable-to-find-config-for-) -- [Sub-page missing from nav, or won't open when clicked](#sub-page-missing-from-nav-or-wont-open-when-clicked) -- [Sub-page ignores its theme, layout or appConfig](#sub-page-ignores-its-theme-layout-or-appconfig) -- [Sub-config files return 404](#sub-config-files-return-404) -- [Yarn Build or Run Error](#yarn-error) -- [`yarn build` fails inside the container](#yarn-build-fails-inside-the-container) -- [Remote Config Not Loading](#remote-config-not-loading) -- [High CPU or RAM Usage on Startup](#high-cpu-or-ram-usage-on-startup) -- [Heap limit Allocation failed](#ineffective-mark-compacts-near-heap-limit-allocation-failed) -- [Command failed with signal "SIGKILL"](#command-failed-with-signal-sigkill) -- [Auth Validation Error: "should be object"](#auth-validation-error-should-be-object) -- [App Not Starting After Update to 2.0.4](#app-not-starting-after-update-to-204) -- [Keycloak Redirect Error](#keycloak-redirect-error) -- [OIDC or Keycloak failure on numeric client IDs](#oidc-or-keycloak-failure-on-numeric-client-ids) -- [Mount Type Mismatch](#mount-type-mismatch) -- [Styles and Assets not Updating](#styles-and-assets-not-updating) -- [DockerHub toomanyrequests](#dockerhub-toomanyrequests) -- [Old image tags fail to pull](#old-image-tags-fail-to-pull) -- [Config Validation Errors](#config-validation-errors) -- [Node Sass unsupported environment](#node-sass-does-not-yet-support-your-current-environment) -- [Unreachable Code Error](#unreachable-code-error) -- [Cannot find module './_baseValues'](#error-cannot-find-module-_basevalues) -- [Ngrok Invalid Host Headers](#invalid-host-header-while-running-through-ngrok) -- [Warnings in the Console during deploy](#warnings-in-the-console-during-deploy) -- [Docker Login Fails on Ubuntu](#docker-login-fails-on-ubuntu) -- [Status Checks Failing](#status-checks-failing) -- [Healthcheck Failing in Docker](#healthcheck-failing-in-docker) -- [Diagnosing Widget Errors](#widget-errors) -- [Fixing Widget CORS Errors](#widget-cors-errors) -- [CORS Proxy connect ECONNREFUSED or ENOTFOUND](#cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-) -- [CORS Proxy Target-URL host blocked or scheme rejected](#cors-proxy-target-url-host--is-blocked--must-use-http-or-https) -- [Widget Shows Error Incorrectly](#widget-shows-error-incorrectly) -- [Weather Forecast Widget 401](#weather-forecast-widget-401) -- [Widget Displaying Inaccurate Data](#widget-displaying-inaccurate-data) -- [Font Awesome Icons not Displaying](#font-awesome-icons-not-displaying) -- [Copy to Clipboard not Working](#copy-to-clipboard-not-working) -- [How to Reset Local Settings](#how-to-reset-local-settings) -- [How to make a bug report](#how-to-make-a-bug-report) -- [Public IP Widget not working for ipinfo or ipquery providers](#public-ip-widget-not-working-for-ipinfo-or-ipquery-providers) -- [How-To Open Browser Console](#how-to-open-browser-console) -- [Git Contributions not Displaying](#git-contributions-not-displaying) - ---- - -## Config not saving - -There should be an error message, explaining the reason the config save failed. First check [browser console](#how-to-open-browser-console) (F12 --> Console), and then your server-side logs in the terminal. Then, see the following sections for solutions to each possible error. - - - - - - - - - - -### Permission denied or read-only filesystem (EACCES, EROFS) - -The container can't write to your `conf.yml` or its directory. Almost always an ownership mismatch: the host directory belongs to a different uid than the one Dashy runs as inside the container. Less commonly a read-only mount or an over-strict file mode. - -Dashy runs as UID=1000 (default non-root node user). You can see this by running `docker exec -it dashy id`. Then, check who owns the user-data directory, with: `docker exec -it dashy ls -la /app/user-data` - if it's not `1000` then that's the issue. And the solution is just to run `sudo chown -R 1000:1000 /path/to/your/user-data` to set the right owner. - -Fixes: -1. **Hand the directory to uid 1000** (recommended). Keeps the container running as a non-root user, which is how Dashy is built to run `sudo chown -R 1000:1000 /path/to/your/user-data` -2. **Run the container as your own user** if `chown` isn't practical (multi-user hosts, NAS appliances, host directories you don't want relabelled). Add `--user $(id -u):$(id -g)` to `docker run`, or set `user: "1000:1000"` (or your host uid:gid) on the service in `docker-compose.yml`. -3. **Loosen a single-file mount** if its mode is `444`. Narrow case, only fixes that one symptom: `chmod 644 /path/to/conf.yml` - -**Common mistakes** -- **Using uid/gid 1001.** A common guess on Synology, Unraid and similar where 1001 is the host's first user. Dashy's container is 1000, not 1001. -- **`chmod` alone for a UID mismatch.** Loosens permissions but doesn't change who owns the file. You need `chown`. -- **`chmod -R 777` or `775`.** Works as a workaround, masks the real problem, weakens security. Use `chown` to the right uid instead. - -**Other gotchas:** -- **Named Docker volumes** (created with `docker volume create`) inherit ownership from whatever first writes to them. If an older container set them up as root, the diagnose step will show that. Recreate the volume or `chown` the underlying directory under `/var/lib/docker/volumes/`. -- **macOS hosts** rarely hit this. Docker Desktop transparently maps host uid to container uid through its VM. If saves are failing on macOS, look elsewhere first. -- **Storage layers that ignore POSIX permissions** (some NAS app-data folders use FUSE, SMB or overlay mounts where `chmod` and `chgrp` are silent no-ops). Bind-mount user-data from a native filesystem path instead. - -### Kubernetes ConfigMap mount is read-only - -If you've mounted your `conf.yml` from a ConfigMap, writes will always fail with `EROFS` regardless of UID. ConfigMap volumes are read-only by design. Either treat the ConfigMap as the source of truth and edit it directly (saves through the UI won't work), or use a writable volume type like a `PersistentVolumeClaim` for `user-data/`. - -### SELinux or AppArmor blocks the write - -If you're on RHEL/Fedora, or systems with SELinux or AppArmour, and you've confirmed permissions are fine, and container's UID matches the host owner, but you still see `EACCES`. - -For SELinux, add the `:Z` flag to your volume mount so Docker relabels it for the container (e.g. `volumes: [ './user-data:/app/user-data:Z' ]`) - -For AppArmor, check `dmesg` for `apparmor="DENIED"` lines and adjust the profile. Disabling enforcement is a last resort. - -### Backup step fails so save aborts - -Before each save, Dashy backs up the current `conf.yml` to `user-data/config-backups/`. If that folder can't be written, the whole save aborts with `Unable to backup conf.yml`. - -Two ways out: -1. Point `BACKUP_DIR` at a writable path -2. Set `DISABLE_CONFIG_BACKUPS=true` to skip the backup step entirely - -### Save button is missing or returns 403 Forbidden - -Have you got auth setup? If so, make sure you are logged in as an admin, or set `type: admin` to your user in `conf.yml`. - -Beyond that, there's several other config options which prevent saving the config file, so if you didn't mean to add them, just remove from `conf.yml` -- **`appConfig.preventWriteToDisk: true`** disables disk save and the button -- **`appConfig.preventLocalSave: true`** disables the "Local" save option -- **`appConfig.disableConfiguration: true`** hides the editor entirely. `disableConfigurationForNonAdmin: true` does the same just for non-admins. - -### Save unavailable on Vercel, Netlify or other static hosts -Updating source config file on static hosts is not possible, since they have no Node server, nor have write access to modify any files. -The "Local" save mode will still work (changes are just persisted in your browser), but the real solution is to copy/export the updated YAML and replace it in the source config file in your repo. - -Related: [#1465](https://github.com/Lissy93/dashy/issues/1465). - -### `/config-manager/save` returns 404 or HTML -How are you running/serving Dashy? -If you've got a reverse proxy which only forwards specific path prefixes then maybe you're missing the `/config-manager/*` API endpoints? -Check the failed request in the browser's Network tab. If the response is HTML (a proxy error page) or a plain 404, your proxy isn't routing the path. Add `/config-manager/` to whatever you're forwarding, or simplify the rules so everything reaches Dashy. - -Or if you're serving up the compiled Vue app directly, instead of using the Node server, then the endpoint won't be available. - -### "Invalid filename" when saving a sub-page - -The save endpoint rejects sub-page filenames with path separators or non-yaml extensions. Check the `path:` value of the page in your `pages:` block. It needs to be a plain basename like `home.yml`, not `pages/home.yml` or `home.txt`. - -### "Cannot save to an external URL" - -The sub-page you are editing is loaded from a remote URL. Dashy can't write back to that URL. -You will need to edit the file at it's origin yourself instead (click the Export to view the YAML). -Or you could download the config to `user-data/something.yml`, and update `path:` to point to the local version. - -### Saved successfully but the UI shows the old config - -Two unrelated causes share this symptom: - -1. **Local storage overrides the file.** Dashy lets users save settings locally in browser storage, which take priority over `conf.yml`. Open Dashy in incognito to confirm. If the changes appear there, clear local settings via Config menu > "Clear Local Settings". -2. **Docker isn't picking up file changes.** Some text editors save by replacing the inode, which breaks single-file bind mounts. Edit the file in place, or mount the parent directory rather than the single file. [More background](https://medium.com/@jonsbun/why-need-to-be-careful-when-mounting-single-files-into-a-docker-container-4f929340834). - - - -### Container crashes or restart loop after saving (3.1.0 and 3.1.1 only) - -If your container crashes or restart-loops right after clicking save, with logs like `ERR_HTTP_HEADERS_SENT` or `ERR_STREAM_WRITE_AFTER_END`, this was a known double-`res.end()` bug in 3.1.0 and 3.1.1. Fixed in v3.2.13 and later. - -```bash -docker pull lissy93/dashy:latest -docker compose up -d --force-recreate -``` - -### Intentionally read-only mode - -To stop anyone saving from the UI, set `appConfig.preventWriteToDisk: true` in `conf.yml`. This disables the endpoint and the save button. For Docker users, you can harden things further, by mounting the config and all user-data as read-only. - ---- - -## `Refused to Connect` in Modal or Workspace View - -This is not an issue with Dashy, but instead caused by the target app preventing direct access through embedded elements. - -As defined in [RFC-7034](https://datatracker.ietf.org/doc/html/rfc7034), for any web content to be accessed through an embedded element, it must have the [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) HTTP header set to `ALLOW`. If you are getting a `Refused to Connect` error then this header is set to `DENY` (or `SAMEORIGIN` and it's on a different host). Thankfully, for self-hosted services, it is easy to set these headers. - -These settings are usually set in the config file for the web server that's hosting the target application, here are some examples of how to enable cross-origin access with common web servers: - -### NGINX - -In NGINX, you can use the [`add_header`](https://nginx.org/en/docs/http/ngx_http_headers_module.html) module within the app block. - -```text -server { - ... - add_header X-Frame-Options SAMEORIGIN always; -} -``` - -Then reload with `service nginx reload` - -### Caddy - -In Caddy, you can use the [`header`](https://caddyserver.com/docs/caddyfile/directives/header) directive. - -```text -header { - X-Frame-Options SAMEORIGIN -} -``` - -### Apache - -In Apache, you can use the [`mod_headers`](https://httpd.apache.org/docs/current/mod/mod_headers.html) module to set the `X-Frame-Options` in your config file. This file is usually located somewhere like `/etc/apache2/httpd.conf - -```text -Header set X-Frame-Options: "ALLOW-FROM http://[dashy-location]/" -``` - -### LightHttpd - -```text -Content-Security-Policy: frame-ancestors 'self' https://[dashy-location]/ -``` - ---- - -## 404 On Static Hosting - -If you're seeing Dashy's 404 page on initial load/ refresh, and then the main app when you go back to Home, then this is likely caused by the Vue router, and if so can be fixed in one of two ways. - -The first solution is to switch the routing mode, from HTML5 `history` mode to `hash` mode, by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set. - -If this works, but you wish to continue using HTML5 history mode, then a bit of extra [server configuration](/docs/management.md#web-server-configuration) is required. This is explained in more detaail in the [Vue Docs](https://router.vuejs.org/guide/essentials/history-mode.html). Once completed, you can then use `VITE_APP_ROUTING_MODE=history` (the default) again, for neater URLs. - ---- - -## 404 after Launch from Mobile Home Screen - -Similar to the above issue, if you get a 404 after using iOS and Android's "Add to Home Screen" feature, then this is caused by Vue router. -It can be fixed by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set. - -See also: [#628](https://github.com/Lissy93/dashy/issues/628), [#762](https://github.com/Lissy93/dashy/issues/762) - ---- - -## 404 On Multi-Page Apps - -Similar to above, if you get a 404 error when visiting a page directly on multi-page apps, then this can be fixed by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set, then refreshing the page. - -See also: [#670](https://github.com/Lissy93/dashy/issues/670), [#763](https://github.com/Lissy93/dashy/issues/763) - ---- - -## Dashy hosted at a sub-path (e.g. `example.com/dashy`) - -If the homepage works but sub-page links 404, or assets fail to load, it's almost always the base path. -Rebuild with `BASE_URL` set to the sub-path - leading slash, no trailing slash: - -Vue Router uses this to prefix every route. Without it, links resolve to `/home/...` instead of `/dashy/home/...` and skip your reverse proxy altogether. More detail in [web-server configuration](/docs/management.md#web-server-configuration). - ---- - -## Sub-page shows "Unable to find config for ..." - -This means Dashy couldn't match the URL segment to any entry in your `pages:` list. A few causes: - -### Old bookmark from before an upgrade -Slugs are now trimmed more aggressively (e.g. `🌐 Command Center` used to give `-command-center`, now gives `command-center`). Re-bookmark from the nav, or update the URL by hand. - -### The page was renamed or removed -The URL no longer resolves to anything. Check the `pages:` array in `conf.yml` and confirm the sub-page still exists. - -### The path points at an unreachable file -If the sub-config YAML can't be fetched (404, CORS, auth), you'll see "Unable to load config from ..." instead. Verify the `path:` is correct, reachable from the browser, and CORS-open if remote. - -### Page name literally "Main" -`main` is reserved in the URL scheme to mean "the root config". A page named "Main" becomes reachable at `/home/main-page` (not `/home/main`). Rename the page if that's confusing. - -### Service worker is serving a stale app -Hard-refresh (Ctrl + F5) after a major upgrade. The PWA cache may still be pointing at old routes. Also see [Styles and Assets not Updating](#styles-and-assets-not-updating). - ---- - -## Sub-page missing from nav, or won't open when clicked - -If page defined in `pages:` is nowhere in the nav bar, or its link goes to a different page, then there's probably something wrong with the name you chose. Note that Dashy strips out any non-alphanumeric characters. -- Ensure each page does have a valid `name` and `path` field -- Check two pages don't have the same/similar name -- Check each page has a name which has at least some alpha-numeric characters -- Very long names could be being stripped/truncated - ---- - -## Sub-page ignores its theme, layout or appConfig - -This is by design. Only the `appConfig` from your root `conf.yml` is used - theme, layout, iconSize, statusCheck, etc. are inherited globally so behaviour stays consistent across pages. - -If you put `appConfig` inside a sub-page YAML, it's silently dropped on load. Move the values to the root config. See [Restrictions](/docs/pages-and-sections.md#restrictions). - ---- - -## Sub-config files return 404 - -If your `conf.yml` references additional pages via `pages:` and the browser shows `Sub-config load failed: /something.yml`, the cause is almost always a Docker mount that only exposes `conf.yml` and not the rest of `user-data/`. - -If you've done this: - -```yaml -volumes: - - ./my-conf.yml:/app/user-data/conf.yml -``` - -Only `conf.yml` exists inside the container. Anything it references (sub-configs, custom icons, fonts, CSS) isn't there. - -Mount the directory instead: - -```yaml -volumes: - - ./user-data:/app/user-data -``` - -Now everything in your `user-data` folder is reachable at the web root. Same applies to `docker run -v`. - ---- - -## Yarn Error - -For more info, see [Issue #1](https://github.com/Lissy93/dashy/issues/1) - -First of all, check that you've got yarn installed correctly - see the [yarn installation docs](https://classic.yarnpkg.com/en/docs/install) for more info. - -If you're getting an error about scenarios, then you've likely installed the wrong yarn... (you're [not](https://github.com/yarnpkg/yarn/issues/2821) the only one!). You can fix it by uninstalling, adding the correct repo, and reinstalling, for example, in Debian: - -- `sudo apt remove yarn` -- `curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -` -- `echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list` -- `sudo apt update && sudo apt install yarn` - -Alternatively, as a workaround, you have several options: - -- Try using [NPM](https://www.npmjs.com/get-npm) instead: So clone, cd, then run `npm install`, `npm run build` and `npm start` -- Try using [Docker](https://www.docker.com/get-started) instead, and all of the system setup and dependencies will already be taken care of. So from within the directory, just run `docker build -t lissy93/dashy .` to build, and then use docker start to run the project, e.g: `docker run -it -p 8080:8080 lissy93/dashy` (see the [deploying docs](https://github.com/Lissy93/dashy/blob/master/docs/deployment.md#deploy-with-docker) for more info) - ---- - -## `yarn build` fails inside the container - -If you run `docker exec yarn build` and get `vite: not found` (or similar), it's because the published image ships only production dependencies. The build toolchain (vite, vue-tsc, sass, etc.) lives in `devDependencies` and isn't installed in the runtime image. - -You almost certainly don't need to rebuild. Dashy's Express server reads `user-data/conf.yml` on every request, so config changes show up on a page refresh, no rebuild required. - -If you genuinely need a fresh build (you've patched something in `src/`), do it on the host with `yarn install && yarn build`, or build a custom image from a checkout of the repo. - ---- - -## Remote Config Not Loading - -If you've got a multi-page dashboard, and are hosting the additional config files yourself, then CORS rules will apply. A CORS error will look something like: - -```text -Access to XMLHttpRequest at 'https://example.com/raw/my-config.yml' from origin 'http://dashy.local' has been blocked by CORS policy: -No 'Access-Control-Allow-Origin' header is present on the requested resource. -``` - -The solution is to add the appropriate headers onto the target server, to allow it to accept requests from the origin where you're running Dashy. - -If it is a remote service, that you do not have admin access to, then another option is to proxy the request. Either host your own, or use a publicly accessible service, like [allorigins.win](https://allorigins.win), e.g: `https://api.allorigins.win/raw?url=https://pastebin.com/raw/4tZpaJV5`. For git-based services specifically, there's [raw.githack.com](https://raw.githack.com/) - ---- - -## High CPU or RAM Usage on Startup - -When the Dashy container first starts, it runs a Vue production build in parallel with the server. This is **a one-time cost per container start**, but it briefly uses around **1–1.5 GB of RAM and 100% of one CPU core** for anywhere from 30 seconds to several minutes (depending on host speed). On Pi-class hardware or VMs with less than 1 GB of RAM, this spike can be enough to lock up the host. - -**To work around it:** - -1. **Allocate at least 1 GB of RAM to the container** - 2 GB is recommended on Raspberry Pi or low-powered VMs. Anything below 512 MB is unlikely to complete the first build. -2. **Set explicit Docker resource limits** so the build can't starve other services on the same host: - ```yaml - services: - dashy: - image: lissy93/dashy:latest - deploy: - resources: - limits: - memory: 2g - cpus: '1.5' - ``` -3. **Wait it out** - once the build completes, idle CPU drops to near zero and idle RAM is typically under 100 MB. If you watch `docker stats`, you'll see the spike taper off. -4. If the spike never tapers (i.e., Dashy stays at 100% CPU forever and never serves the page), see [Heap limit Allocation failed](#ineffective-mark-compacts-near-heap-limit-allocation-failed) below - that usually means the build was killed mid-way and is being retried. - -See also: [#1585](https://github.com/Lissy93/dashy/issues/1585), [#969](https://github.com/Lissy93/dashy/issues/969), [#1500](https://github.com/Lissy93/dashy/issues/1500), [#877](https://github.com/Lissy93/dashy/issues/877) - ---- - -## Ineffective mark-compacts near heap limit Allocation failed - -If you see an error message, similar to: - -```text -<--- Last few GCs ---> - -[61:0x74533040] 229060 ms: Mark-sweep (reduce) 127.1 (236.9) -> 127.1 (137.4) MB, 5560.7 / 0.3 ms (average mu = 0.286, current mu = 0.011) allocation failure scavenge might not succeed - -<--- JS stacktrace ---> - -FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory -``` - -This is likely caused by insufficient memory allocation to the container. When the container first starts up, or has to rebuild, the memory usage spikes, and if there isn't enough memory, it may terminate. This can be specified with, for example: `--memory=1024m`. For more info, see [Docker: Runtime options with Memory, CPUs, and GPUs](https://docs.docker.com/config/containers/resource_constraints/). For more context on what the spike is, see [High CPU or RAM Usage on Startup](#high-cpu-or-ram-usage-on-startup) above. - -See also: [#380](https://github.com/Lissy93/dashy/issues/380), [#350](https://github.com/Lissy93/dashy/issues/350), [#297](https://github.com/Lissy93/dashy/issues/297), [#349](https://github.com/Lissy93/dashy/issues/349), [#510](https://github.com/Lissy93/dashy/issues/510), [#511](https://github.com/Lissy93/dashy/issues/511) and [#834](https://github.com/Lissy93/dashy/issues/834) - ---- - -## Command failed with signal "SIGKILL" - -In Docker, this can be caused by not enough memory. When the container first starts up, or has to rebuild, the memory usage spikes, and so a larger allocation may be required. This can be specified with, for example: `--memory=1024m`. For more info, see [Docker: Runtime options with Memory, CPUs, and GPUs](https://docs.docker.com/config/containers/resource_constraints/) - -See also [#624](https://github.com/Lissy93/dashy/issues/624) - ---- - -## Auth Validation Error: "should be object" - -In V 1.6.5 an update was made that in the future will become a breaking change. You will need to update you config to reflect this before V 2.0.0 is released. In the meantime, your previous config will continue to function normally, but you will see a validation warning. The change means that the structure of the `appConfig.auth` object is now an object, which has a `users` property. - -For more info, see [this announcement](https://github.com/Lissy93/dashy/discussions/177). - -You can fix this by replacing: - -```yaml -auth: -- user: xxx - hash: xxx -``` - -with - -```yaml -auth: - users: - - user: xxx - hash: xxx -``` - ---- - -## App Not Starting After Update to 2.0.4 - -Version 2.0.4 introduced changes to how the config is read, and the app is build. If you were previously mounting `/public` as a volume, then this will over-write the build app, preventing it from starting. The solution is to just pass in the file(s) / sub-directories that you need. For example: - -```yaml -volumes: -- /srv/dashy/conf.yml:/app/user-data/conf.yml -- /srv/dashy/item-icons:/app/public/item-icons -``` - ---- - -## Keycloak Redirect Error - -Check the [browser's console output](#how-to-open-browser-console), if you've not set any headers, you will likely see a CORS error here, which would be the source of the issue. - -You need to allow Dashy to make requests to Keycloak, and Keycloak to redirect to Dashy. The way you do this depends on how you're hosting these applications / which proxy you are using, and examples can be found in the [Management Docs](/docs/management.md#setting-headers). - -For example, add the access control header to Keycloak, like: - -`Access-Control-Allow-Origin [URL-of Dashy]` - -Note that for requests that transport sensitive info like credentials, setting the accept header to a wildcard (`*`) is not allowed - see [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials), so you will need to specify the actual URL. - -You should also ensure that Keycloak is correctly configured, with a user, realm and application, and be sure that you have set a valid redirect URL in Keycloak ([screenshot](https://user-images.githubusercontent.com/1862727/148599768-db4ee4f8-72c5-402d-8f00-051d999e6267.png)). - -For more details on how to set headers, see the [Example Headers](/docs/management.md#setting-headers) in the management docs, or reference the documentation for your proxy. - -If you're running in Kubernetes, you will need to enable CORS ingress rules, see [docs](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors), e.g: - -```text -nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com" -nginx.ingress.kubernetes.io/enable-cors: "true" -``` - -See also: #479, #409, #507, #491, #341, #520 - ---- - -## OIDC or Keycloak failure on numeric client IDs - -If your IdP rejects the login with an *"invalid client"* / *"client not found"* error, and your `clientId` is a long numeric value, the cause is almost certainly YAML number parsing. - -YAML parses unquoted numeric tokens as Numbers, and JavaScript can't represent integers larger than 2^53 (~16 digits) without losing precision. So an unquoted numeric `clientId` will be silently truncated (e.g. `918756876419824312` → `918756876419824300`), or - for very large values - converted to scientific notation (e.g. `9.187568764198242e+37`), and the IdP will reject it. - -The fix is to wrap the `clientId` in quotes in your `conf.yml` so it gets parsed as a string: - -```yaml -appConfig: - auth: - enableOidc: true - oidc: - clientId: "918756876419824312" - endpoint: https://idp.example.com/ -``` - -The same applies to `auth.keycloak.clientId`. Dashy will print a warning in the [browser console](#how-to-open-browser-console) when it detects a numeric `clientId`, to help diagnose this. - -See also: #1941 - ---- - -## Mount Type Mismatch - -```text -Error response from daemon: ... mount through procfd: not a directory: -Are you trying to mount a directory onto a file (or vice-versa)? -``` - -This means the host side and container side of your volume don't agree on whether the target is a file or a directory. - -Recommended pattern: mount a host directory onto `/app/user-data`. The directory must exist on the host and contain at least a `conf.yml`: - -```bash -mkdir -p ~/dashy-data -cp /path/to/your/conf.yml ~/dashy-data/conf.yml -docker run -d -p 8080:8080 -v ~/dashy-data:/app/user-data lissy93/dashy:latest -``` - -If you'd rather mount a single file (`-v ~/conf.yml:/app/user-data/conf.yml`), the host path must be a file that already exists, otherwise Docker creates a directory in its place and you'll see this error. - ---- - -## Styles and Assets not Updating - -If you find that your styles and other visual assets work when visiting `ip:port` by not `dashy.domain.com`, then this is usually caused by caching. In your browser, do a hard-refresh (Ctrl + F5). If you use Cloudflare, then you can clear the cache through the management console, or set the cache level to Bypass for certain files, under the Rules tab. - ---- - -## DockerHub `toomanyrequests` - -This situation relates to error messages similar to one of the following, returned when pulling, updating or running the Docker container from Docker Hub. - -```text -Continuing execution. Pulling image lissy93/dashy:release-1.6.0 -error pulling image configuration: toomanyrequests -``` - -or - -```text -You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit -``` - -When DockerHub returns one of these errors, or a `429` status, that means you've hit your rate limit. This was [introduced](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/) last year, and prevents unauthenticated or free users from running docker pull more than 100 times per 6 hours. -You can [check your rate limit status](https://www.docker.com/blog/checking-your-current-docker-pull-rate-limits-and-status/) by looking for the `ratelimit-remaining` header in any DockerHub responses. - -### Solution 1 - Use an alternate container registry - -- Dashy is also available through GHCR, which at present does not have any hard limits. Just use `docker pull ghcr.io/lissy93/dashy:latest` to fetch the image -- You can also build the image from source, by cloning the repo, and running `docker build -t dashy .` or use the pre-made docker compose - -### Solution 2 - Increase your rate limits - -- Logging in to DockerHub will increase your rate limit from 100 requests to 200 requests per 6 hour period -- Upgrading to a Pro for $5/month will increase your image requests to 5,000 per day, and any plans above have no rate limits -- Since rate limits are counted based on your IP address, proxying your requests, or using a VPN may work - ---- - -## Old image tags fail to pull - -If `docker pull` returns `manifest unknown` or `manifest for lissy93/dashy:arm32v7 not found`, the cause is a stale architecture-specific tag in your compose file or run command. Tags like `:arm32v7`, `:arm64v8`, and `:multi-arch` are no longer published. - -The `:latest` tag is now multi-arch and works on amd64, arm64, and arm/v7 (Raspberry Pi 2 and up) without you having to pick a variant. Just use: - -```yaml -image: lissy93/dashy:latest -``` - -Docker fetches the right architecture for your host automatically. To pin a version, use a semver tag, e.g. `lissy93/dashy:3.2.14`. - ---- - -## Config Validation Errors - -The configuration file is validated against [Dashy's Schema](https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json) using AJV. - -First, check that your syntax is valid, using [YAML Validator](https://codebeautify.org/yaml-validator/) or [JSON Validator](https://codebeautify.org/jsonvalidator). If the issue persists, then take a look at the [schema](https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json), and verify that the field you are trying to add/ modify matches the required format. You can also use [this tool](https://www.jsonschemavalidator.net/s/JFUj7X9J) to validate your JSON config against the schema, or run `yarn validate-config`. - -If you're trying to use a recently released feature, and are getting a warning, this is likely because you've not yet updated the the current latest version of Dashy. - -If the issue still persists, you should raise an issue. - ---- - -## Node Sass does not yet support your current environment - -Caused by node-sass's binaries being built for a for a different architecture -To fix this, just run: `yarn rebuild node-sass` - ---- - -## Unreachable Code Error - -An error similar to: `Fatal error in , line 0. Unreachable code, FailureMessage Object: 0xffe6c8ac. Illegal instruction (core dumped)` -Is related to a bug in a downstream package, see [nodejs/docker-node#1477](https://github.com/nodejs/docker-node/issues/1477). -Usually, updating your system and packages will resolve the issue. - -See also: [#776](https://github.com/Lissy93/dashy/issues/776) - ---- - -## Error: Cannot find module './_baseValues' - -Clearing the cache should fix this: `yarn cache clean` -If the issue persists, remove (`rm -rf node_modules\ yarn.lock`) and reinstall (`yarn`) node_modules - ---- - -## Invalid Host Header while running through ngrok - -Just add the [-host-header](https://ngrok.com/docs#http-host-header) flag, e.g. `ngrok http 8080 -host-header="localhost:8080"` - ---- - -## Warnings in the Console during deploy - -Please acknowledge the difference between errors and warnings before raising an issue about messages in the console. It's not unusual to see warnings about a new version of a certain package being available, an asset bundle bing oversized or a service worker not yet having a cache. These shouldn't have any impact on the running application, so please don't raise issues about these unless it directly relates to a bug or issue you're experiencing. Errors on the other hand should not appear in the console, and they are worth looking into further. - ---- - -## Docker Login Fails on Ubuntu - -Run `sudo apt install gnupg2 pass && gpg2 -k` - ---- - -## Status Checks Failing - -If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at: - -If your service requires requests to include any authorization in the headers, then use the `statusCheckHeaders` property, as described in the [docs](/docs/status-indicators.md#setting-custom-headers). - -If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), and can be fixed by setting the headers on your target app, to include: - -```text -Access-Control-Allow-Origin: https://location-of-dashy/ -Vary: Origin -``` - -If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting `statusCheckAllowInsecure` to true for a given item. - -If your service is online, but responds with a status code that is not in the 2xx range, then you can use `statusCheckAcceptCodes` to set an accepted status code. - -If you get an error, like `Service Unavailable: Server resulted in a fatal error`, even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include `https://` (or whatever protocol) before the URL, and ensure that if needed, you've specified the port. - -Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see [#445](https://github.com/Lissy93/dashy/discussions/445). - -If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access. - -Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point `statusCheckUrl` to your custom page. - -For further troubleshooting, use an application like [Postman](https://postman.com) to diagnose the issue. Set the parameter to `GET`, and then make a call to: `https://[url-of-dashy]/status-check/?&url=[service-url]`. Where the service URL must have first been encoded (e.g. with `encodeURIComponent()` or [urlencoder.io](https://www.urlencoder.io/)) - -If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method. - ---- - -## Healthcheck Failing in Docker - -If `docker ps` shows the Dashy container as `unhealthy`, the periodic healthcheck (`node services/healthcheck.js`, run every 5 minutes by default) couldn't reach the local server. - -### SSL-enabled Dashy - -The healthcheck reads the same cert paths as the main server (`/etc/ssl/certs/dashy-pub.pem` and `/etc/ssl/certs/dashy-priv.key`) to detect whether to probe HTTPS or HTTP. If you've mounted certs at non-default paths via `SSL_PUB_KEY_PATH` / `SSL_PRIV_KEY_PATH`, the healthcheck will pick those up automatically as long as those env vars are set in the container environment (not just at run time). - -### Custom port - -If you've set `PORT` to override the default 8080, the healthcheck honors the same env var, so it should work without changes. Make sure `PORT` is set in the container environment, not just in the host-side Docker port mapping. - -### Container is unhealthy past the grace period - -The healthcheck has a 20s `start-period` after which failures start counting. The image is prebuilt, so startup is just `node server.js` binding to a port - fast even on a Pi. If the container is still `unhealthy` past the grace period, the server has likely crashed. Check `docker logs ` for the real error (usually a malformed `conf.yml` or a missing `user-data` mount). - -See also: [#1410](https://github.com/Lissy93/dashy/issues/1410) - ---- - -## Widget Errors - -### Find Error Message - -If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can [open the browser console](/docs/troubleshooting.md#how-to-open-browser-console) to see more details. - -### Check Config - -Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct. - -### Timeout Error - -If the error message in the console includes: `Error: timeout of 500ms exceeded`, then your Glances endpoint is slower to respond than expected. You can fix this by [setting timeout](https://github.com/Lissy93/dashy/blob/master/docs/widgets.md#setting-timeout) to a larger value. This is done on each widget, with the `timeout` attribute, and is specified in ms. E.g. `timeout: 5000` would only fail if no response is returned within 5 seconds. - -### CORS error - -If the console message mentions to corss-origin blocking, then this is a CORS error, see: [Fixing Widget CORS Errors](#widget-cors-errors) - -### More Info - -If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman. - ---- - -## Widget CORS Errors - -The most common widget issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). - -There are several ways to fix a CORS error: - -### Option 1 - Ensure Correct Protocol - -You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted. - -### Option 2 - Set Headers - -If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers. -Add the `Access-Control-Allow-Origin` header, with the value of either `*` to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example: - -```text -Access-Control-Allow-Origin: https://url-of-dashy.local -``` - -or - -```text -Access-Control-Allow-Origin: * -``` - -For more info on how to set headers, see: [Setting Headers](/docs/management.md#setting-headers) in the management docs - -### Option 3 - Proxying Request - -You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found [here](/docs/widgets.md#proxying-requests). If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you. - -Just add the `useProxy: true` option to the failing widget. - -### Option 4 - Use a plugin - -For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for [Chrome](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US) or [Firefox](https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/), more details [here](https://mybrowseraddon.com/access-control-allow-origin.html) - ---- - -## CORS Proxy `connect ECONNREFUSED ...` or `getaddrinfo ENOTFOUND ...` - -The target host is unreachable from the Dashy container. If the target is on the same host as Dashy, **don't use `localhost`** - inside a Docker container that resolves to the container itself, not the host. Use the host's LAN IP, the Docker bridge gateway, or `host.docker.internal` (on Docker Desktop). If the target is on a different Docker network, attach Dashy to that network too. - ---- - -## CORS Proxy `Target-URL host '...' is blocked` / `must use http:// or https://` - -To prevent the CORS proxy from being abused as a Server-Side Request Forgery vector, Dashy refuses to proxy a small number of host/scheme combinations by default: - -- **Cloud instance metadata services** - `169.254.169.254`, `metadata.google.internal`, and the matching IPv6 forms. These are reserved magic addresses on AWS, Azure, GCP, DigitalOcean, Hetzner, Oracle Cloud and most other providers. A widget that successfully fetches them on a cloud-hosted Dashy can leak the host's IAM credentials, so they're blocked unconditionally. -- **Non-HTTP(S) schemes** - `file://`, `ftp://`, `gopher://`, `javascript:`, `data:`, and similar. The proxy is meant for HTTP APIs only. - -If you're running Dashy in a fully isolated/private environment and you've deliberately decided you want to allow these (for example, you genuinely need to query your cloud provider's metadata API from a widget), you can opt out of all proxy restrictions by setting the environment variable: - -```bash -DANGEROUSLY_DISABLE_PROXY_RESTRICTIONS=true -``` - -The variable is named so loudly because flipping it on a Dashy instance that's exposed to anything other than fully trusted users re-opens the SSRF surface - anyone who can hit `/cors-proxy` can then use Dashy as a relay to reach internal services. **Don't set it on cloud-hosted or internet-exposed deployments.** - -Note that this is an all-or-nothing escape hatch, not a per-host allowlist. If you only need to reach one specific host that's currently blocked, please open a feature request describing the use case. - ---- - -## Widget Shows Error Incorrectly - -When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI. - -In some instances, this is a false positive, and the widget is actually functioning correctly. -If this is the case, you can disable the UI error message of a given widget by setting: `ignoreErrors: true` - ---- - -## Weather Forecast Widget 401 - -A 401 error means your API key is invalid, it is not an issue with Dashy. - -Usually this happens due to an error in your config. If you're unsure, copy and paste the [example](/docs/widgets.md#weather) config, replacing the API key with your own. - -Check that `apiKey` is correctly specified, and nested within `options`. Ensure your input city is valid. - -To test your API key, try making a request to `https://api.openweathermap.org/data/2.5/weather?q=London&appid=[your-api-key]` - -If [Weather widget](/docs/widgets.md#weather-forecast) is working fine, but you are getting a `401` for the [Weather Forecast widget](/docs/widgets.md#weather-forecast), then this is also an OWM API key issue. -Since the forecasting API requires an upgraded plan. ULPT: You can get a free, premium API key by filling in [this form](https://home.openweathermap.org/students). It's a student plan, but there's no verification to check that you are still a student. - -A future update will be pushed out, to use a free weather forecasting API. - -See also: [#803](https://github.com/Lissy93/dashy/issues/803), [#789](https://github.com/Lissy93/dashy/issues/789), [#577](https://github.com/Lissy93/dashy/issues/577), [#621](https://github.com/Lissy93/dashy/issues/621), [#578](https://github.com/Lissy93/dashy/issues/578), [#806](https://github.com/Lissy93/dashy/discussions/806) - ---- - -## Widget Displaying Inaccurate Data - -If any widget is not displaying the data you expect, first confirm that your config is correct, then try manually calling the API endpoint. - -If the raw API output is correct, yet the widget is rendering incorrect results, then it is likely a bug, and a ticket should be raised. You can start to debug the issue, by looking at the widget's code ([here](https://github.com/Lissy93/dashy/tree/master/src/components/Widgets)), and the browser console + networking tab. - -If the API itself is returning incorrect, incomplete or inaccurate data then an issue needs to be raised **with the API provider** (not Dashy!). You can find the API provider included within the widget docs, or for a full list see the [Privacy Docs](https://github.com/Lissy93/dashy/blob/master/docs/privacy.md#widgets). - -See also: [#807](https://github.com/Lissy93/dashy/issues/807) (re, domain monitor) - ---- - -## Font Awesome Icons not Displaying - -Usually, Font Awesome will be automatically enabled if one or more of your icons are using Font-Awesome. If this is not happening, then you can always manually enable (or disable) Font Awesome by setting: [`appConfig`](/docs/configuring.md#appconfig-optional).`enableFontAwesome` to `true`. - -If you are trying to use a premium icon, then you must have a [Pro License](https://fontawesome.com/plans). You'll then need to specify your Pro plan API key under `appConfig.fontAwesomeKey`. You can find this key, by logging into your FA account, navigate to Account → [Kits](https://fontawesome.com/kits) → New Kit → Copy Kit Code. The code is a 10-digit alpha-numeric code, and is also visible within the new kit's URL, for example: `81e48ce079`. - -

- -Be sure that you're specifying the icon category and name correctly. You're icon should look be `[category] fa-[icon-name]`. The following categories are supported: `far` _(regular)_, `fas` _(solid)_, `fal`_(light)_, `fad` _(duo-tone)_ and `fab`_(brands)_. With the exception of brands, you'll usually want all your icons to be in from same category, so they look uniform. - -Ensure the icon you are trying to use, is available within [FontAwesome Version 5](https://fontawesome.com/v5/search) (we've not yet upgraded to V6, as it works a little differently). - -Examples: `fab fa-raspberry-pi`, `fas fa-database`, `fas fa-server`, `fas fa-ethernet` - -Finally, check the [browser console](#how-to-open-browser-console) for any error messages, and raise a ticket if the issue persists. - ---- - -## Copy to Clipboard not Working - -If the copy to clipboard feature (either under Config --> Export, or Item --> Copy URL) isn't functioning as expected, first check the browser console. If you see `TypeError: Cannot read properties of undefined (reading 'writeText')` then this feature is not supported by your browser. -The most common reason for this, is if you not running the app over HTTPS. Copying to the clipboard requires the app to be running in a secure origin / aka have valid HTTPS cert. You can read more about this [here](https://stackoverflow.com/a/71876238/979052). - -As a workaround, you could either: - -- Highlight the text and copy / Ctrl + C -- Or setup SSL - [here's a guide](https://github.com/Lissy93/dashy/blob/master/docs/management.md#ssl-certificates) on doing so - ---- - -## How to Reset Local Settings - -Some settings are stored locally, in the browser's storage. - -In some instances cached assets can prevent your settings from being updated, in which case you may wish to reset local data. - -To clear all local data from the UI, head to the Config Menu, then click "Reset Local Settings", and Confirm when prompted. -This will not affect your config file. But be sure that you keep a backup of your config, if you've not written changes it to disk. - -You can also view any and all data that Dashy is storing, using the developer tools. Open your browser's dev tools (usually F12), in Chromium head to the Application tab, or in Firefox go to the Storage tab. Select Local Storage, then scroll down the the URL Dashy is running on. You should now see all data being stored, and you can select and delete any fields you wish. - -For a full list of all data that may be cached, see the [Privacy Docs](/docs/privacy.md#browser-storage). - ---- - -## How to make a bug report - -### Step 1 - Where to open issues - -You will need a GitHub account in order to raise a ticket. You can then [click here](https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E) to open a new bug report. - -### Step 2 - Checking it's not already covered - -Before submitting, please check that: - -- A similar ticket has not previously been opened -- The issue is not covered in the [troubleshooting guide](https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md) or [docs](https://github.com/Lissy93/dashy/tree/master/docs#readme) - -### Step 3 - Describe the Issue - -Your ticket will likely be dealt with more effectively if you can explain the issue clearly, and provide all relevant supporting material. - -Complete the fields, asking for your environment info and version of Dashy. -Then describe the issue, briefly explaining the steps to reproduce, expected outcome and actual outcome. - -### Step 4 - Provide Supporting Info - -Where relevant please also include: - -- A screenshot of the issue -- The relevant parts of your config file -- Logs - - If client-side issue, then include the browser logs ([see how](#how-to-open-browser-console)) - - If server-side / during deployment, include the terminal output - -_Take care to redact any personal info, (like IP addresses, auth hashes or API keys)._ - -### Step 5 - Fix Released - -A maintainer will aim to respond within 48 hours. -The timeframe for resolving your issue, will vary depending on severity of the bug and the complexity of the fix. -You will be notified on your ticket, when a fix has been released. - -Finally, be sure to remain respectful to other users and project maintainers, in line with the [Contributor Covenant Code of Conduct](https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct). - ---- - -## Public IP Widget not working for `ipinfo` or `ipquery` providers - -If you've set `options.provider` to either `ipinfo` and `ipquery` and the requests are failing, it's likley that they're being blocked. Check your adblocker (uBlock, PrivacyBadger, etc), DNS block lists (PiHole, AdGuard, etc). Or, try proxying the request (with `useProxy: true`) or just try a different provider. - ---- - -## How-To Open Browser Console - -When raising a bug, one crucial piece of info needed is the browser's console output. This will help the developer diagnose and fix the issue. - -If you've been asked for this info, but are unsure where to find it, then it is under the "Console" tab, in the browsers developer tools, which can be opened with F12. You can right-click the console, and select Save As to download the log. - -To open dev tools, and jump straight to the console: - -- Win / Linux: Ctrl + Shift + J -- MacOS: Cmd + Option + J - -For more detailed walk through, see [this article](https://support.shortpoint.com/support/solutions/articles/1000222881-save-browser-console-file). - ---- - -## Git Contributions not Displaying - -If you've contributed to Dashy (or any other project), but your contributions are not showing up on your GH profile, or in Dashy's [Credits Page](https://github.com/Lissy93/dashy/blob/master/docs/credits.md), then this is likely a git config issue. - -These statistics are generated using the username / email associated with commits. This info needs to be setup on your local machine using [`git config`](https://git-scm.com/docs/git-config). - -Run the following commands (replacing name + email with your info): - -- `git config --global user.name "John Doe"` -- `git config --global user.email johndoe@example.com` - -For more info, see [Git First Time Setup Docs](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup). - -Note that only contributions to the master / main branch or a project are counted +# Troubleshooting + +> _**This document contains common problems and their solutions.**_
+> Please ensure your issue isn't listed here, before opening a new ticket. +> +> _Found something not listed here? Consider adding it, to help other users._ + +## Contents + +- [Config not saving](#config-not-saving) + - [Permission denied or read-only filesystem](#permission-denied-or-read-only-filesystem-eacces-erofs) + - [Kubernetes ConfigMap mount is read-only](#kubernetes-configmap-mount-is-read-only) + - [SELinux or AppArmor blocks the write](#selinux-or-apparmor-blocks-the-write) + - [Backup step fails so save aborts](#backup-step-fails-so-save-aborts) + - [Save button is missing or returns 403](#save-button-is-missing-or-returns-403-forbidden) + - [Save unavailable on Vercel, Netlify or other static hosts](#save-unavailable-on-vercel-netlify-or-other-static-hosts) + - [/config-manager/save returns 404 or HTML](#config-managersave-returns-404-or-html) + - ["Invalid filename" when saving a sub-page](#invalid-filename-when-saving-a-sub-page) + - ["Cannot save to an external URL"](#cannot-save-to-an-external-url) + - [Saved successfully but the UI shows the old config](#saved-successfully-but-the-ui-shows-the-old-config) + - [Container crashes or restart loop after saving](#container-crashes-or-restart-loop-after-saving-310-and-311-only) + - [Intentionally read-only mode](#intentionally-read-only-mode) +- [Refused to Connect in Web Content View](#refused-to-connect-in-modal-or-workspace-view) +- [404 / Routing issues](#404--routing-issues) + - [404 On Static Hosting](#404-on-static-hosting) + - [404 from Mobile Home Screen](#404-after-launch-from-mobile-home-screen) + - [404 On Multi-Page Apps](#404-on-multi-page-apps) + - [Dashy hosted at a sub-path](#dashy-hosted-at-a-sub-path-eg-examplecomdashy) +- [Sub-pages](#sub-pages) + - [Sub-page shows "Unable to find config for ..."](#sub-page-shows-unable-to-find-config-for-) + - [Sub-page missing from nav, or won't open when clicked](#sub-page-missing-from-nav-or-wont-open-when-clicked) + - [Sub-page ignores its theme, layout or appConfig](#sub-page-ignores-its-theme-layout-or-appconfig) + - [Sub-config files return 404](#sub-config-files-return-404) + - [Remote Config Not Loading](#remote-config-not-loading) +- [Build & memory errors](#build--memory-errors) + - [Yarn Build or Run Error](#yarn-error) + - [`yarn build` fails inside the container](#yarn-build-fails-inside-the-container) + - [High CPU or RAM Usage on Startup](#high-cpu-or-ram-usage-on-startup) + - [Heap limit Allocation failed](#ineffective-mark-compacts-near-heap-limit-allocation-failed) + - [Command failed with signal "SIGKILL"](#command-failed-with-signal-sigkill) + - [Node Sass unsupported environment](#node-sass-does-not-yet-support-your-current-environment) + - [Unreachable Code Error](#unreachable-code-error) + - [Cannot find module './_baseValues'](#error-cannot-find-module-_basevalues) +- [Auth & OIDC](#auth--oidc) + - [Auth Validation Error: "should be object"](#auth-validation-error-should-be-object) + - [Keycloak Redirect Error](#keycloak-redirect-error) + - [OIDC or Keycloak failure on numeric client IDs](#oidc-or-keycloak-failure-on-numeric-client-ids) + - [Redirect loop after login](#redirect-loop-after-login) + - [invalid_redirect_uri](#invalid_redirect_uri) + - [Login works in the browser but the dashboard refuses to save anything (403)](#login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403) + - [Logged in but no admin controls](#logged-in-but-no-admin-controls) + - [Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"](#login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user) + - [Sign-out leaves you stuck on Authentik](#sign-out-leaves-you-stuck-on-authentik) + - [Untrusted certificate from Authentik](#untrusted-certificate-from-authentik) + - [Numeric client_id getting truncated](#numeric-client_id-getting-truncated) +- [Docker & image issues](#docker--image-issues) + - [App Not Starting After Update to 2.0.4](#app-not-starting-after-update-to-204) + - [Mount Type Mismatch](#mount-type-mismatch) + - [DockerHub toomanyrequests](#dockerhub-toomanyrequests) + - [Old image tags fail to pull](#old-image-tags-fail-to-pull) + - [Healthcheck Failing in Docker](#healthcheck-failing-in-docker) + - [Docker Login Fails on Ubuntu](#docker-login-fails-on-ubuntu) +- [Styles and Assets not Updating](#styles-and-assets-not-updating) +- [Config Validation Errors](#config-validation-errors) +- [Ngrok Invalid Host Headers](#invalid-host-header-while-running-through-ngrok) +- [Warnings in the Console during deploy](#warnings-in-the-console-during-deploy) +- [Status Checks Failing](#status-checks-failing) +- [Widgets](#widgets) + - [Diagnosing Widget Errors](#widget-errors) + - [Fixing Widget CORS Errors](#widget-cors-errors) + - [CORS Proxy connect ECONNREFUSED or ENOTFOUND](#cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-) + - [CORS Proxy Target-URL host blocked or scheme rejected](#cors-proxy-target-url-host--is-blocked--must-use-http-or-https) + - [Widget Shows Error Incorrectly](#widget-shows-error-incorrectly) + - [Weather Forecast Widget 401](#weather-forecast-widget-401) + - [Widget Displaying Inaccurate Data](#widget-displaying-inaccurate-data) + - [Public IP Widget not working for ipinfo or ipquery providers](#public-ip-widget-not-working-for-ipinfo-or-ipquery-providers) +- [Font Awesome Icons not Displaying](#font-awesome-icons-not-displaying) +- [Copy to Clipboard not Working](#copy-to-clipboard-not-working) +- [How-To / Reference](#how-to--reference) + - [How to Reset Local Settings](#how-to-reset-local-settings) + - [How to make a bug report](#how-to-make-a-bug-report) + - [How-To Open Browser Console](#how-to-open-browser-console) + - [Git Contributions not Displaying](#git-contributions-not-displaying) + +--- + +## Config not saving + +There should be an error message, explaining the reason the config save failed. First check [browser console](#how-to-open-browser-console) (F12 --> Console), and then your server-side logs in the terminal. Then, see the following sections for solutions to each possible error. + + + + + + + + + + +### Permission denied or read-only filesystem (EACCES, EROFS) + +The container can't write to your `conf.yml` or its directory. Almost always an ownership mismatch: the host directory belongs to a different uid than the one Dashy runs as inside the container. Less commonly a read-only mount or an over-strict file mode. + +Dashy runs as UID=1000 (default non-root node user). You can see this by running `docker exec -it dashy id`. Then, check who owns the user-data directory, with: `docker exec -it dashy ls -la /app/user-data` - if it's not `1000` then that's the issue. And the solution is just to run `sudo chown -R 1000:1000 /path/to/your/user-data` to set the right owner. + +Fixes: +1. **Hand the directory to uid 1000** (recommended). Keeps the container running as a non-root user, which is how Dashy is built to run `sudo chown -R 1000:1000 /path/to/your/user-data` +2. **Run the container as your own user** if `chown` isn't practical (multi-user hosts, NAS appliances, host directories you don't want relabelled). Add `--user $(id -u):$(id -g)` to `docker run`, or set `user: "1000:1000"` (or your host uid:gid) on the service in `docker-compose.yml`. +3. **Loosen a single-file mount** if its mode is `444`. Narrow case, only fixes that one symptom: `chmod 644 /path/to/conf.yml` + +**Common mistakes** +- **Using uid/gid 1001.** A common guess on Synology, Unraid and similar where 1001 is the host's first user. Dashy's container is 1000, not 1001. +- **`chmod` alone for a UID mismatch.** Loosens permissions but doesn't change who owns the file. You need `chown`. +- **`chmod -R 777` or `775`.** Works as a workaround, masks the real problem, weakens security. Use `chown` to the right uid instead. + +**Other gotchas:** +- **Named Docker volumes** (created with `docker volume create`) inherit ownership from whatever first writes to them. If an older container set them up as root, the diagnose step will show that. Recreate the volume or `chown` the underlying directory under `/var/lib/docker/volumes/`. +- **macOS hosts** rarely hit this. Docker Desktop transparently maps host uid to container uid through its VM. If saves are failing on macOS, look elsewhere first. +- **Storage layers that ignore POSIX permissions** (some NAS app-data folders use FUSE, SMB or overlay mounts where `chmod` and `chgrp` are silent no-ops). Bind-mount user-data from a native filesystem path instead. + +### Kubernetes ConfigMap mount is read-only + +If you've mounted your `conf.yml` from a ConfigMap, writes will always fail with `EROFS` regardless of UID. ConfigMap volumes are read-only by design. Either treat the ConfigMap as the source of truth and edit it directly (saves through the UI won't work), or use a writable volume type like a `PersistentVolumeClaim` for `user-data/`. + +### SELinux or AppArmor blocks the write + +If you're on RHEL/Fedora, or systems with SELinux or AppArmour, and you've confirmed permissions are fine, and container's UID matches the host owner, but you still see `EACCES`. + +For SELinux, add the `:Z` flag to your volume mount so Docker relabels it for the container (e.g. `volumes: [ './user-data:/app/user-data:Z' ]`) + +For AppArmor, check `dmesg` for `apparmor="DENIED"` lines and adjust the profile. Disabling enforcement is a last resort. + +### Backup step fails so save aborts + +Before each save, Dashy backs up the current `conf.yml` to `user-data/config-backups/`. If that folder can't be written, the whole save aborts with `Unable to backup conf.yml`. + +Two ways out: +1. Point `BACKUP_DIR` at a writable path +2. Set `DISABLE_CONFIG_BACKUPS=true` to skip the backup step entirely + +### Save button is missing or returns 403 Forbidden + +Have you got auth setup? If so, make sure you are logged in as an admin, or set `type: admin` to your user in `conf.yml`. + +Beyond that, there's several other config options which prevent saving the config file, so if you didn't mean to add them, just remove from `conf.yml` +- **`appConfig.preventWriteToDisk: true`** disables disk save and the button +- **`appConfig.preventLocalSave: true`** disables the "Local" save option +- **`appConfig.disableConfiguration: true`** hides the editor entirely. `disableConfigurationForNonAdmin: true` does the same just for non-admins. + +### Save unavailable on Vercel, Netlify or other static hosts +Updating source config file on static hosts is not possible, since they have no Node server, nor have write access to modify any files. +The "Local" save mode will still work (changes are just persisted in your browser), but the real solution is to copy/export the updated YAML and replace it in the source config file in your repo. + +Related: [#1465](https://github.com/Lissy93/dashy/issues/1465). + +### `/config-manager/save` returns 404 or HTML +How are you running/serving Dashy? +If you've got a reverse proxy which only forwards specific path prefixes then maybe you're missing the `/config-manager/*` API endpoints? +Check the failed request in the browser's Network tab. If the response is HTML (a proxy error page) or a plain 404, your proxy isn't routing the path. Add `/config-manager/` to whatever you're forwarding, or simplify the rules so everything reaches Dashy. + +Or if you're serving up the compiled Vue app directly, instead of using the Node server, then the endpoint won't be available. + +### "Invalid filename" when saving a sub-page + +The save endpoint rejects sub-page filenames with path separators or non-yaml extensions. Check the `path:` value of the page in your `pages:` block. It needs to be a plain basename like `home.yml`, not `pages/home.yml` or `home.txt`. + +### "Cannot save to an external URL" + +The sub-page you are editing is loaded from a remote URL. Dashy can't write back to that URL. +You will need to edit the file at it's origin yourself instead (click the Export to view the YAML). +Or you could download the config to `user-data/something.yml`, and update `path:` to point to the local version. + +### Saved successfully but the UI shows the old config + +Two unrelated causes share this symptom: + +1. **Local storage overrides the file.** Dashy lets users save settings locally in browser storage, which take priority over `conf.yml`. Open Dashy in incognito to confirm. If the changes appear there, clear local settings via Config menu > "Clear Local Settings". +2. **Docker isn't picking up file changes.** Some text editors save by replacing the inode, which breaks single-file bind mounts. Edit the file in place, or mount the parent directory rather than the single file. [More background](https://medium.com/@jonsbun/why-need-to-be-careful-when-mounting-single-files-into-a-docker-container-4f929340834). + + + +### Container crashes or restart loop after saving (3.1.0 and 3.1.1 only) + +If your container crashes or restart-loops right after clicking save, with logs like `ERR_HTTP_HEADERS_SENT` or `ERR_STREAM_WRITE_AFTER_END`, this was a known double-`res.end()` bug in 3.1.0 and 3.1.1. Fixed in v3.2.13 and later. + +```bash +docker pull lissy93/dashy:latest +docker compose up -d --force-recreate +``` + +### Intentionally read-only mode + +To hide the "Save to disk" UI for everyone, set `appConfig.preventWriteToDisk: true` in `conf.yml`. This is a UI-only flag — the `/config-manager/save` server endpoint itself is gated by the configured auth method (`auth.users` with `ENABLE_HTTP_AUTH`, OIDC/Keycloak admin role, header-auth, etc.), so anyone unauthenticated or non-admin already can't save regardless of this flag. For Docker users, you can harden things further by mounting `user-data` (or just `conf.yml`) as read-only — the kernel will refuse the write even before the server tries. + +--- + +## `Refused to Connect` in Modal or Workspace View + +This is not an issue with Dashy, but instead caused by the target app preventing direct access through embedded elements. + +As defined in [RFC-7034](https://datatracker.ietf.org/doc/html/rfc7034), for any web content to be accessed through an embedded element, it must have the [`X-Frame-Options`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) HTTP header set to `ALLOW`. If you are getting a `Refused to Connect` error then this header is set to `DENY` (or `SAMEORIGIN` and it's on a different host). Thankfully, for self-hosted services, it is easy to set these headers. + +These settings are usually set in the config file for the web server that's hosting the target application, here are some examples of how to enable cross-origin access with common web servers: + +### NGINX + +In NGINX, you can use the [`add_header`](https://nginx.org/en/docs/http/ngx_http_headers_module.html) module within the app block. + +```text +server { + ... + add_header X-Frame-Options SAMEORIGIN always; +} +``` + +Then reload with `service nginx reload` + +### Caddy + +In Caddy, you can use the [`header`](https://caddyserver.com/docs/caddyfile/directives/header) directive. + +```text +header { + X-Frame-Options SAMEORIGIN +} +``` + +### Apache + +In Apache, you can use the [`mod_headers`](https://httpd.apache.org/docs/current/mod/mod_headers.html) module to set the `X-Frame-Options` in your config file. This file is usually located somewhere like `/etc/apache2/httpd.conf + +```text +Header set X-Frame-Options: "ALLOW-FROM http://[dashy-location]/" +``` + +### LightHttpd + +```text +Content-Security-Policy: frame-ancestors 'self' https://[dashy-location]/ +``` + +--- + +## 404 / Routing issues + +### 404 On Static Hosting + +If you're seeing Dashy's 404 page on initial load/ refresh, and then the main app when you go back to Home, then this is likely caused by the Vue router, and if so can be fixed in one of two ways. + +The first solution is to switch the routing mode, from HTML5 `history` mode to `hash` mode, by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set. + +If this works, but you wish to continue using HTML5 history mode, then a bit of extra [server configuration](/docs/management.md#web-server-configuration) is required. This is explained in more detaail in the [Vue Docs](https://router.vuejs.org/guide/essentials/history-mode.html). Once completed, you can then use `VITE_APP_ROUTING_MODE=history` (the default) again, for neater URLs. + +### 404 after Launch from Mobile Home Screen + +Similar to the above issue, if you get a 404 after using iOS and Android's "Add to Home Screen" feature, then this is caused by Vue router. +It can be fixed by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set. + +See also: [#628](https://github.com/Lissy93/dashy/issues/628), [#762](https://github.com/Lissy93/dashy/issues/762) + +### 404 On Multi-Page Apps + +Similar to above, if you get a 404 error when visiting a page directly on multi-page apps, then this can be fixed by rebuilding Dashy with the `VITE_APP_ROUTING_MODE=hash` build-time environment variable set, then refreshing the page. + +See also: [#670](https://github.com/Lissy93/dashy/issues/670), [#763](https://github.com/Lissy93/dashy/issues/763) + +### Dashy hosted at a sub-path (e.g. `example.com/dashy`) + +If the homepage works but sub-page links 404, or assets fail to load, it's almost always the base path. +Rebuild with `BASE_URL` set to the sub-path - leading slash, no trailing slash: + +Vue Router uses this to prefix every route. Without it, links resolve to `/home/...` instead of `/dashy/home/...` and skip your reverse proxy altogether. More detail in [web-server configuration](/docs/management.md#web-server-configuration). + +--- + +## Sub-pages + +### Sub-page shows "Unable to find config for ..." + +This means Dashy couldn't match the URL segment to any entry in your `pages:` list. A few causes: + +#### Old bookmark from before an upgrade +Slugs are now trimmed more aggressively (e.g. `🌐 Command Center` used to give `-command-center`, now gives `command-center`). Re-bookmark from the nav, or update the URL by hand. + +#### The page was renamed or removed +The URL no longer resolves to anything. Check the `pages:` array in `conf.yml` and confirm the sub-page still exists. + +#### The path points at an unreachable file +If the sub-config YAML can't be fetched (404, CORS, auth), you'll see "Unable to load config from ..." instead. Verify the `path:` is correct, reachable from the browser, and CORS-open if remote. + +#### Page name literally "Main" +`main` is reserved in the URL scheme to mean "the root config". A page named "Main" becomes reachable at `/home/main-page` (not `/home/main`). Rename the page if that's confusing. + +#### Service worker is serving a stale app +Hard-refresh (Ctrl + F5) after a major upgrade. The PWA cache may still be pointing at old routes. Also see [Styles and Assets not Updating](#styles-and-assets-not-updating). + +### Sub-page missing from nav, or won't open when clicked + +If page defined in `pages:` is nowhere in the nav bar, or its link goes to a different page, then there's probably something wrong with the name you chose. Note that Dashy strips out any non-alphanumeric characters. +- Ensure each page does have a valid `name` and `path` field +- Check two pages don't have the same/similar name +- Check each page has a name which has at least some alpha-numeric characters +- Very long names could be being stripped/truncated + +### Sub-page ignores its theme, layout or appConfig + +This is by design. Only the `appConfig` from your root `conf.yml` is used - theme, layout, iconSize, statusCheck, etc. are inherited globally so behaviour stays consistent across pages. + +If you put `appConfig` inside a sub-page YAML, it's silently dropped on load. Move the values to the root config. See [Restrictions](/docs/pages-and-sections.md#restrictions). + +### Sub-config files return 404 + +If your `conf.yml` references additional pages via `pages:` and the browser shows `Sub-config load failed: /something.yml`, the cause is almost always a Docker mount that only exposes `conf.yml` and not the rest of `user-data/`. + +If you've done this: + +```yaml +volumes: + - ./my-conf.yml:/app/user-data/conf.yml +``` + +Only `conf.yml` exists inside the container. Anything it references (sub-configs, custom icons, fonts, CSS) isn't there. + +Mount the directory instead: + +```yaml +volumes: + - ./user-data:/app/user-data +``` + +Now everything in your `user-data` folder is reachable at the web root. Same applies to `docker run -v`. + +### Remote Config Not Loading + +If you've got a multi-page dashboard, and are hosting the additional config files yourself, then CORS rules will apply. A CORS error will look something like: + +```text +Access to XMLHttpRequest at 'https://example.com/raw/my-config.yml' from origin 'http://dashy.local' has been blocked by CORS policy: +No 'Access-Control-Allow-Origin' header is present on the requested resource. +``` + +The solution is to add the appropriate headers onto the target server, to allow it to accept requests from the origin where you're running Dashy. + +If it is a remote service, that you do not have admin access to, then another option is to proxy the request. Either host your own, or use a publicly accessible service, like [allorigins.win](https://allorigins.win), e.g: `https://api.allorigins.win/raw?url=https://pastebin.com/raw/4tZpaJV5`. For git-based services specifically, there's [raw.githack.com](https://raw.githack.com/) + +--- + +## Build & memory errors + +### Yarn Error + +For more info, see [Issue #1](https://github.com/Lissy93/dashy/issues/1) + +First of all, check that you've got yarn installed correctly - see the [yarn installation docs](https://classic.yarnpkg.com/en/docs/install) for more info. + +If you're getting an error about scenarios, then you've likely installed the wrong yarn... (you're [not](https://github.com/yarnpkg/yarn/issues/2821) the only one!). You can fix it by uninstalling, adding the correct repo, and reinstalling, for example, in Debian: + +- `sudo apt remove yarn` +- `curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -` +- `echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list` +- `sudo apt update && sudo apt install yarn` + +Alternatively, as a workaround, you have several options: + +- Try using [NPM](https://www.npmjs.com/get-npm) instead: So clone, cd, then run `npm install`, `npm run build` and `npm start` +- Try using [Docker](https://www.docker.com/get-started) instead, and all of the system setup and dependencies will already be taken care of. So from within the directory, just run `docker build -t lissy93/dashy .` to build, and then use docker start to run the project, e.g: `docker run -it -p 8080:8080 lissy93/dashy` (see the [deploying docs](https://github.com/Lissy93/dashy/blob/master/docs/deployment.md#deploy-with-docker) for more info) + +### `yarn build` fails inside the container + +If you run `docker exec yarn build` and get `vite: not found` (or similar), it's because the published image ships only production dependencies. The build toolchain (vite, vue-tsc, sass, etc.) lives in `devDependencies` and isn't installed in the runtime image. + +You almost certainly don't need to rebuild. Dashy's Express server reads `user-data/conf.yml` on every request, so config changes show up on a page refresh, no rebuild required. + +If you genuinely need a fresh build (you've patched something in `src/`), do it on the host with `yarn install && yarn build`, or build a custom image from a checkout of the repo. + +### High CPU or RAM Usage on Startup + +When the Dashy container first starts, it runs a Vue production build in parallel with the server. This is **a one-time cost per container start**, but it briefly uses around **1–1.5 GB of RAM and 100% of one CPU core** for anywhere from 30 seconds to several minutes (depending on host speed). On Pi-class hardware or VMs with less than 1 GB of RAM, this spike can be enough to lock up the host. + +**To work around it:** + +1. **Allocate at least 1 GB of RAM to the container** - 2 GB is recommended on Raspberry Pi or low-powered VMs. Anything below 512 MB is unlikely to complete the first build. +2. **Set explicit Docker resource limits** so the build can't starve other services on the same host: + ```yaml + services: + dashy: + image: lissy93/dashy:latest + deploy: + resources: + limits: + memory: 2g + cpus: '1.5' + ``` +3. **Wait it out** - once the build completes, idle CPU drops to near zero and idle RAM is typically under 100 MB. If you watch `docker stats`, you'll see the spike taper off. +4. If the spike never tapers (i.e., Dashy stays at 100% CPU forever and never serves the page), see [Heap limit Allocation failed](#ineffective-mark-compacts-near-heap-limit-allocation-failed) below - that usually means the build was killed mid-way and is being retried. + +See also: [#1585](https://github.com/Lissy93/dashy/issues/1585), [#969](https://github.com/Lissy93/dashy/issues/969), [#1500](https://github.com/Lissy93/dashy/issues/1500), [#877](https://github.com/Lissy93/dashy/issues/877) + +### Ineffective mark-compacts near heap limit Allocation failed + +If you see an error message, similar to: + +```text +<--- Last few GCs ---> + +[61:0x74533040] 229060 ms: Mark-sweep (reduce) 127.1 (236.9) -> 127.1 (137.4) MB, 5560.7 / 0.3 ms (average mu = 0.286, current mu = 0.011) allocation failure scavenge might not succeed + +<--- JS stacktrace ---> + +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +This is likely caused by insufficient memory allocation to the container. When the container first starts up, or has to rebuild, the memory usage spikes, and if there isn't enough memory, it may terminate. This can be specified with, for example: `--memory=1024m`. For more info, see [Docker: Runtime options with Memory, CPUs, and GPUs](https://docs.docker.com/config/containers/resource_constraints/). For more context on what the spike is, see [High CPU or RAM Usage on Startup](#high-cpu-or-ram-usage-on-startup) above. + +See also: [#380](https://github.com/Lissy93/dashy/issues/380), [#350](https://github.com/Lissy93/dashy/issues/350), [#297](https://github.com/Lissy93/dashy/issues/297), [#349](https://github.com/Lissy93/dashy/issues/349), [#510](https://github.com/Lissy93/dashy/issues/510), [#511](https://github.com/Lissy93/dashy/issues/511) and [#834](https://github.com/Lissy93/dashy/issues/834) + +### Command failed with signal "SIGKILL" + +In Docker, this can be caused by not enough memory. When the container first starts up, or has to rebuild, the memory usage spikes, and so a larger allocation may be required. This can be specified with, for example: `--memory=1024m`. For more info, see [Docker: Runtime options with Memory, CPUs, and GPUs](https://docs.docker.com/config/containers/resource_constraints/) + +See also [#624](https://github.com/Lissy93/dashy/issues/624) + +### Node Sass does not yet support your current environment + +Caused by node-sass's binaries being built for a for a different architecture +To fix this, just run: `yarn rebuild node-sass` + +### Unreachable Code Error + +An error similar to: `Fatal error in , line 0. Unreachable code, FailureMessage Object: 0xffe6c8ac. Illegal instruction (core dumped)` +Is related to a bug in a downstream package, see [nodejs/docker-node#1477](https://github.com/nodejs/docker-node/issues/1477). +Usually, updating your system and packages will resolve the issue. + +See also: [#776](https://github.com/Lissy93/dashy/issues/776) + +### Error: Cannot find module './_baseValues' + +Clearing the cache should fix this: `yarn cache clean` +If the issue persists, remove (`rm -rf node_modules\ yarn.lock`) and reinstall (`yarn`) node_modules + +--- + +## Auth & OIDC + +### Auth Validation Error: "should be object" + +In V 1.6.5 an update was made that in the future will become a breaking change. You will need to update you config to reflect this before V 2.0.0 is released. In the meantime, your previous config will continue to function normally, but you will see a validation warning. The change means that the structure of the `appConfig.auth` object is now an object, which has a `users` property. + +For more info, see [this announcement](https://github.com/Lissy93/dashy/discussions/177). + +You can fix this by replacing: + +```yaml +auth: +- user: xxx + hash: xxx +``` + +with + +```yaml +auth: + users: + - user: xxx + hash: xxx +``` + +### Keycloak Redirect Error + +Check the [browser's console output](#how-to-open-browser-console), if you've not set any headers, you will likely see a CORS error here, which would be the source of the issue. + +You need to allow Dashy to make requests to Keycloak, and Keycloak to redirect to Dashy. The way you do this depends on how you're hosting these applications / which proxy you are using, and examples can be found in the [Management Docs](/docs/management.md#setting-headers). + +For example, add the access control header to Keycloak, like: + +`Access-Control-Allow-Origin [URL-of Dashy]` + +Note that for requests that transport sensitive info like credentials, setting the accept header to a wildcard (`*`) is not allowed - see [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials), so you will need to specify the actual URL. + +You should also ensure that Keycloak is correctly configured, with a user, realm and application, and be sure that you have set a valid redirect URL in Keycloak ([screenshot](https://user-images.githubusercontent.com/1862727/148599768-db4ee4f8-72c5-402d-8f00-051d999e6267.png)). + +For more details on how to set headers, see the [Example Headers](/docs/management.md#setting-headers) in the management docs, or reference the documentation for your proxy. + +If you're running in Kubernetes, you will need to enable CORS ingress rules, see [docs](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors), e.g: + +```text +nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com" +nginx.ingress.kubernetes.io/enable-cors: "true" +``` + +See also: #479, #409, #507, #491, #341, #520 + +### OIDC or Keycloak failure on numeric client IDs + +If your IdP rejects the login with an *"invalid client"* / *"client not found"* error, and your `clientId` is a long numeric value, the cause is almost certainly YAML number parsing. + +YAML parses unquoted numeric tokens as Numbers, and JavaScript can't represent integers larger than 2^53 (~16 digits) without losing precision. So an unquoted numeric `clientId` will be silently truncated (e.g. `918756876419824312` → `918756876419824300`), or - for very large values - converted to scientific notation (e.g. `9.187568764198242e+37`), and the IdP will reject it. + +The fix is to wrap the `clientId` in quotes in your `conf.yml` so it gets parsed as a string: + +```yaml +appConfig: + auth: + enableOidc: true + oidc: + clientId: "918756876419824312" + endpoint: https://idp.example.com/ +``` + +The same applies to `auth.keycloak.clientId`. Dashy will print a warning in the [browser console](#how-to-open-browser-console) when it detects a numeric `clientId`, to help diagnose this. + +See also: #1941 + + +### Redirect loop after login +Your `endpoint` probably includes `.well-known/openid-configuration`. Drop everything from `.well-known` onwards + +### invalid_redirect_uri +The redirect URI Authentik has registered for the provider doesn't exactly match the URL Dashy is being served from. Register both the bare URL and the trailing-slash version, and make sure the scheme matches (`http` vs `https`). + +### Login works in the browser but the dashboard refuses to save anything (403) +Dashy's server is rejecting the id_token. Check Dashy's container logs for `[auth-oidc] token verification failed`. Common causes: +- **Issuer mismatch**. Authentik is behind a reverse proxy that isn't sending `X-Forwarded-Proto: https`, so its discovery document advertises `http://` while you configured `https://` in Dashy. Fix the proxy or set `AUTHENTIK_HOST`/`AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` on the Authentik containers +- **Audience mismatch**. The `aud` claim in the id_token is not `dashy`. Confirm the provider's Client ID is exactly `dashy` (no leading or trailing whitespace) +- **Dashy server can't reach Authentik**. The Dashy container fails to fetch the discovery document. Exec into the container and try `wget -qO- https://auth.example.com/application/o/dashy/.well-known/openid-configuration` +- **Clock skew**. The middleware allows 30 seconds of drift. If a container's clock is further off than that, `exp`/`iat` checks fail + +### Logged in but no admin controls +The id_token doesn't include the groups claim. Open browser devtools after logging in, find the call to `/application/o/dashy/userinfo/`, and check the response. You should see a `groups` array containing `dashy-admins`. If not: +- The `groups` scope mapping doesn't exist, or is not attached to the provider's property mappings +- The user is not in the `dashy-admins` group +- The conf.yml is missing `groups` from `scope:` and Authentik is therefore not sending it + +### Login works but Dashy errors on the callback with "OIDC signinCallback returned no user" +The id_token came back without a username claim. Confirm the provider has "profile" and "email" in its scopes and the "Include claims in id_token" is on + +### Sign-out leaves you stuck on Authentik +Dashy redirects to Authentik's end-session endpoint on logout. If Authentik's invalidation flow prompts for confirmation (the default), that's expected - click through it. To skip the prompt entirely, change the provider's invalidation flow to one without a consent stage. + +### Untrusted certificate from Authentik +Self-signed certs make Dashy's server-side fetch of the discovery document fail. Use a real cert (Let's Encrypt, or your homelab CA installed into the Dashy image) for the Authentik hostname. + +### Numeric client_id getting truncated +Don't use numeric-only client IDs. If you must, wrap the value in quotes in conf.yml so YAML treats it as a string + + +--- + +## Docker & image issues + +### App Not Starting After Update to 2.0.4 + +Version 2.0.4 introduced changes to how the config is read, and the app is build. If you were previously mounting `/public` as a volume, then this will over-write the build app, preventing it from starting. The solution is to just pass in the file(s) / sub-directories that you need. For example: + +```yaml +volumes: +- /srv/dashy/conf.yml:/app/user-data/conf.yml +- /srv/dashy/item-icons:/app/public/item-icons +``` + +### Mount Type Mismatch + +```text +Error response from daemon: ... mount through procfd: not a directory: +Are you trying to mount a directory onto a file (or vice-versa)? +``` + +This means the host side and container side of your volume don't agree on whether the target is a file or a directory. + +Recommended pattern: mount a host directory onto `/app/user-data`. The directory must exist on the host and contain at least a `conf.yml`: + +```bash +mkdir -p ~/dashy-data +cp /path/to/your/conf.yml ~/dashy-data/conf.yml +docker run -d -p 8080:8080 -v ~/dashy-data:/app/user-data lissy93/dashy:latest +``` + +If you'd rather mount a single file (`-v ~/conf.yml:/app/user-data/conf.yml`), the host path must be a file that already exists, otherwise Docker creates a directory in its place and you'll see this error. + +### DockerHub `toomanyrequests` + +This situation relates to error messages similar to one of the following, returned when pulling, updating or running the Docker container from Docker Hub. + +```text +Continuing execution. Pulling image lissy93/dashy:release-1.6.0 +error pulling image configuration: toomanyrequests +``` + +or + +```text +You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit +``` + +When DockerHub returns one of these errors, or a `429` status, that means you've hit your rate limit. This was [introduced](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/) last year, and prevents unauthenticated or free users from running docker pull more than 100 times per 6 hours. +You can [check your rate limit status](https://www.docker.com/blog/checking-your-current-docker-pull-rate-limits-and-status/) by looking for the `ratelimit-remaining` header in any DockerHub responses. + +#### Solution 1 - Use an alternate container registry + +- Dashy is also available through GHCR, which at present does not have any hard limits. Just use `docker pull ghcr.io/lissy93/dashy:latest` to fetch the image +- You can also build the image from source, by cloning the repo, and running `docker build -t dashy .` or use the pre-made docker compose + +#### Solution 2 - Increase your rate limits + +- Logging in to DockerHub will increase your rate limit from 100 requests to 200 requests per 6 hour period +- Upgrading to a Pro for $5/month will increase your image requests to 5,000 per day, and any plans above have no rate limits +- Since rate limits are counted based on your IP address, proxying your requests, or using a VPN may work + +### Old image tags fail to pull + +If `docker pull` returns `manifest unknown` or `manifest for lissy93/dashy:arm32v7 not found`, the cause is a stale architecture-specific tag in your compose file or run command. Tags like `:arm32v7`, `:arm64v8`, and `:multi-arch` are no longer published. + +The `:latest` tag is now multi-arch and works on amd64, arm64, and arm/v7 (Raspberry Pi 2 and up) without you having to pick a variant. Just use: + +```yaml +image: lissy93/dashy:latest +``` + +Docker fetches the right architecture for your host automatically. To pin a version, use a semver tag, e.g. `lissy93/dashy:3.2.14`. + +### Healthcheck Failing in Docker + +If `docker ps` shows the Dashy container as `unhealthy`, the periodic healthcheck (`node services/healthcheck.js`, run every 5 minutes by default) couldn't reach the local server. + +#### SSL-enabled Dashy + +The healthcheck reads the same cert paths as the main server (`/etc/ssl/certs/dashy-pub.pem` and `/etc/ssl/certs/dashy-priv.key`) to detect whether to probe HTTPS or HTTP. If you've mounted certs at non-default paths via `SSL_PUB_KEY_PATH` / `SSL_PRIV_KEY_PATH`, the healthcheck will pick those up automatically as long as those env vars are set in the container environment (not just at run time). + +#### Custom port + +If you've set `PORT` to override the default 8080, the healthcheck honors the same env var, so it should work without changes. Make sure `PORT` is set in the container environment, not just in the host-side Docker port mapping. + +#### Container is unhealthy past the grace period + +The healthcheck has a 20s `start-period` after which failures start counting. The image is prebuilt, so startup is just `node server.js` binding to a port - fast even on a Pi. If the container is still `unhealthy` past the grace period, the server has likely crashed. Check `docker logs ` for the real error (usually a malformed `conf.yml` or a missing `user-data` mount). + +See also: [#1410](https://github.com/Lissy93/dashy/issues/1410) + +### Docker Login Fails on Ubuntu + +Run `sudo apt install gnupg2 pass && gpg2 -k` + +--- + +## Styles and Assets not Updating + +If you find that your styles and other visual assets work when visiting `ip:port` by not `dashy.domain.com`, then this is usually caused by caching. In your browser, do a hard-refresh (Ctrl + F5). If you use Cloudflare, then you can clear the cache through the management console, or set the cache level to Bypass for certain files, under the Rules tab. + +--- + +## Config Validation Errors + +The configuration file is validated against [Dashy's Schema](https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json) using AJV. + +First, check that your syntax is valid, using [YAML Validator](https://codebeautify.org/yaml-validator/) or [JSON Validator](https://codebeautify.org/jsonvalidator). If the issue persists, then take a look at the [schema](https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json), and verify that the field you are trying to add/ modify matches the required format. You can also use [this tool](https://www.jsonschemavalidator.net/s/JFUj7X9J) to validate your JSON config against the schema, or run `yarn validate-config`. + +If you're trying to use a recently released feature, and are getting a warning, this is likely because you've not yet updated the the current latest version of Dashy. + +If the issue still persists, you should raise an issue. + +--- + +## Invalid Host Header while running through ngrok + +Just add the [-host-header](https://ngrok.com/docs#http-host-header) flag, e.g. `ngrok http 8080 -host-header="localhost:8080"` + +--- + +## Warnings in the Console during deploy + +Please acknowledge the difference between errors and warnings before raising an issue about messages in the console. It's not unusual to see warnings about a new version of a certain package being available, an asset bundle bing oversized or a service worker not yet having a cache. These shouldn't have any impact on the running application, so please don't raise issues about these unless it directly relates to a bug or issue you're experiencing. Errors on the other hand should not appear in the console, and they are worth looking into further. + +--- + +## Status Checks Failing + +If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at: + +If your service requires requests to include any authorization in the headers, then use the `statusCheckHeaders` property, as described in the [docs](/docs/status-indicators.md#setting-custom-headers). + +If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), and can be fixed by setting the headers on your target app, to include: + +```text +Access-Control-Allow-Origin: https://location-of-dashy/ +Vary: Origin +``` + +If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting `statusCheckAllowInsecure` to true for a given item. + +If your service is online, but responds with a status code that is not in the 2xx range, then you can use `statusCheckAcceptCodes` to set an accepted status code. + +If you get an error, like `Service Unavailable: Server resulted in a fatal error`, even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include `https://` (or whatever protocol) before the URL, and ensure that if needed, you've specified the port. + +Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see [#445](https://github.com/Lissy93/dashy/discussions/445). + +If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access. + +Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point `statusCheckUrl` to your custom page. + +For further troubleshooting, use an application like [Postman](https://postman.com) to diagnose the issue. Set the parameter to `GET`, and then make a call to: `https://[url-of-dashy]/status-check/?&url=[service-url]`. Where the service URL must have first been encoded (e.g. with `encodeURIComponent()` or [urlencoder.io](https://www.urlencoder.io/)) + +If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method. + +--- + +## Widgets + +### Widget Errors + +#### Find Error Message + +If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can [open the browser console](/docs/troubleshooting.md#how-to-open-browser-console) to see more details. + +#### Check Config + +Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct. + +#### Timeout Error + +If the error message in the console includes: `Error: timeout of 500ms exceeded`, then your Glances endpoint is slower to respond than expected. You can fix this by [setting timeout](https://github.com/Lissy93/dashy/blob/master/docs/widgets.md#setting-timeout) to a larger value. This is done on each widget, with the `timeout` attribute, and is specified in ms. E.g. `timeout: 5000` would only fail if no response is returned within 5 seconds. + +#### CORS error + +If the console message mentions to corss-origin blocking, then this is a CORS error, see: [Fixing Widget CORS Errors](#widget-cors-errors) + +#### More Info + +If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman. + +### Widget CORS Errors + +The most common widget issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). + +There are several ways to fix a CORS error: + +#### Option 1 - Ensure Correct Protocol + +You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted. + +#### Option 2 - Set Headers + +If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers. +Add the `Access-Control-Allow-Origin` header, with the value of either `*` to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example: + +```text +Access-Control-Allow-Origin: https://url-of-dashy.local +``` + +or + +```text +Access-Control-Allow-Origin: * +``` + +For more info on how to set headers, see: [Setting Headers](/docs/management.md#setting-headers) in the management docs + +#### Option 3 - Proxying Request + +You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found [here](/docs/widgets.md#proxying-requests). If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you. + +Just add the `useProxy: true` option to the failing widget. + +#### Option 4 - Use a plugin + +For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for [Chrome](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US) or [Firefox](https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/), more details [here](https://mybrowseraddon.com/access-control-allow-origin.html) + +### CORS Proxy `connect ECONNREFUSED ...` or `getaddrinfo ENOTFOUND ...` + +The target host is unreachable from the Dashy container. If the target is on the same host as Dashy, **don't use `localhost`** - inside a Docker container that resolves to the container itself, not the host. Use the host's LAN IP, the Docker bridge gateway, or `host.docker.internal` (on Docker Desktop). If the target is on a different Docker network, attach Dashy to that network too. + +### CORS Proxy `Target-URL host '...' is blocked` / `must use http:// or https://` + +To prevent the CORS proxy from being abused as a Server-Side Request Forgery vector, Dashy refuses to proxy a small number of host/scheme combinations by default: + +- **Cloud instance metadata services** - `169.254.169.254`, `metadata.google.internal`, and the matching IPv6 forms. These are reserved magic addresses on AWS, Azure, GCP, DigitalOcean, Hetzner, Oracle Cloud and most other providers. A widget that successfully fetches them on a cloud-hosted Dashy can leak the host's IAM credentials, so they're blocked unconditionally. +- **Non-HTTP(S) schemes** - `file://`, `ftp://`, `gopher://`, `javascript:`, `data:`, and similar. The proxy is meant for HTTP APIs only. + +If you're running Dashy in a fully isolated/private environment and you've deliberately decided you want to allow these (for example, you genuinely need to query your cloud provider's metadata API from a widget), you can opt out of all proxy restrictions by setting the environment variable: + +```bash +DANGEROUSLY_DISABLE_PROXY_RESTRICTIONS=true +``` + +The variable is named so loudly because flipping it on a Dashy instance that's exposed to anything other than fully trusted users re-opens the SSRF surface - anyone who can hit `/cors-proxy` can then use Dashy as a relay to reach internal services. **Don't set it on cloud-hosted or internet-exposed deployments.** + +Note that this is an all-or-nothing escape hatch, not a per-host allowlist. If you only need to reach one specific host that's currently blocked, please open a feature request describing the use case. + +### Widget Shows Error Incorrectly + +When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI. + +In some instances, this is a false positive, and the widget is actually functioning correctly. +If this is the case, you can disable the UI error message of a given widget by setting: `ignoreErrors: true` + +### Weather Forecast Widget 401 + +A 401 error means your API key is invalid, it is not an issue with Dashy. + +Usually this happens due to an error in your config. If you're unsure, copy and paste the [example](/docs/widgets.md#weather) config, replacing the API key with your own. + +Check that `apiKey` is correctly specified, and nested within `options`. Ensure your input city is valid. + +To test your API key, try making a request to `https://api.openweathermap.org/data/2.5/weather?q=London&appid=[your-api-key]` + +If [Weather widget](/docs/widgets.md#weather-forecast) is working fine, but you are getting a `401` for the [Weather Forecast widget](/docs/widgets.md#weather-forecast), then this is also an OWM API key issue. +Since the forecasting API requires an upgraded plan. ULPT: You can get a free, premium API key by filling in [this form](https://home.openweathermap.org/students). It's a student plan, but there's no verification to check that you are still a student. + +A future update will be pushed out, to use a free weather forecasting API. + +See also: [#803](https://github.com/Lissy93/dashy/issues/803), [#789](https://github.com/Lissy93/dashy/issues/789), [#577](https://github.com/Lissy93/dashy/issues/577), [#621](https://github.com/Lissy93/dashy/issues/621), [#578](https://github.com/Lissy93/dashy/issues/578), [#806](https://github.com/Lissy93/dashy/discussions/806) + +### Widget Displaying Inaccurate Data + +If any widget is not displaying the data you expect, first confirm that your config is correct, then try manually calling the API endpoint. + +If the raw API output is correct, yet the widget is rendering incorrect results, then it is likely a bug, and a ticket should be raised. You can start to debug the issue, by looking at the widget's code ([here](https://github.com/Lissy93/dashy/tree/master/src/components/Widgets)), and the browser console + networking tab. + +If the API itself is returning incorrect, incomplete or inaccurate data then an issue needs to be raised **with the API provider** (not Dashy!). You can find the API provider included within the widget docs, or for a full list see the [Privacy Docs](https://github.com/Lissy93/dashy/blob/master/docs/privacy.md#widgets). + +See also: [#807](https://github.com/Lissy93/dashy/issues/807) (re, domain monitor) + +### Public IP Widget not working for `ipinfo` or `ipquery` providers + +If you've set `options.provider` to either `ipinfo` and `ipquery` and the requests are failing, it's likley that they're being blocked. Check your adblocker (uBlock, PrivacyBadger, etc), DNS block lists (PiHole, AdGuard, etc). Or, try proxying the request (with `useProxy: true`) or just try a different provider. + +--- + +## Font Awesome Icons not Displaying + +Usually, Font Awesome will be automatically enabled if one or more of your icons are using Font-Awesome. If this is not happening, then you can always manually enable (or disable) Font Awesome by setting: [`appConfig`](/docs/configuring.md#appconfig-optional).`enableFontAwesome` to `true`. + +If you are trying to use a premium icon, then you must have a [Pro License](https://fontawesome.com/plans). You'll then need to specify your Pro plan API key under `appConfig.fontAwesomeKey`. You can find this key, by logging into your FA account, navigate to Account → [Kits](https://fontawesome.com/kits) → New Kit → Copy Kit Code. The code is a 10-digit alpha-numeric code, and is also visible within the new kit's URL, for example: `81e48ce079`. + +

+ +Be sure that you're specifying the icon category and name correctly. You're icon should look be `[category] fa-[icon-name]`. The following categories are supported: `far` _(regular)_, `fas` _(solid)_, `fal`_(light)_, `fad` _(duo-tone)_ and `fab`_(brands)_. With the exception of brands, you'll usually want all your icons to be in from same category, so they look uniform. + +Ensure the icon you are trying to use, is available within [FontAwesome Version 5](https://fontawesome.com/v5/search) (we've not yet upgraded to V6, as it works a little differently). + +Examples: `fab fa-raspberry-pi`, `fas fa-database`, `fas fa-server`, `fas fa-ethernet` + +Finally, check the [browser console](#how-to-open-browser-console) for any error messages, and raise a ticket if the issue persists. + +--- + +## Copy to Clipboard not Working + +If the copy to clipboard feature (either under Config --> Export, or Item --> Copy URL) isn't functioning as expected, first check the browser console. If you see `TypeError: Cannot read properties of undefined (reading 'writeText')` then this feature is not supported by your browser. +The most common reason for this, is if you not running the app over HTTPS. Copying to the clipboard requires the app to be running in a secure origin / aka have valid HTTPS cert. You can read more about this [here](https://stackoverflow.com/a/71876238/979052). + +As a workaround, you could either: + +- Highlight the text and copy / Ctrl + C +- Or setup SSL - [here's a guide](https://github.com/Lissy93/dashy/blob/master/docs/management.md#ssl-certificates) on doing so + +--- + +## How-To / Reference + +### How to Reset Local Settings + +Some settings are stored locally, in the browser's storage. + +In some instances cached assets can prevent your settings from being updated, in which case you may wish to reset local data. + +To clear all local data from the UI, head to the Config Menu, then click "Reset Local Settings", and Confirm when prompted. +This will not affect your config file. But be sure that you keep a backup of your config, if you've not written changes it to disk. + +You can also view any and all data that Dashy is storing, using the developer tools. Open your browser's dev tools (usually F12), in Chromium head to the Application tab, or in Firefox go to the Storage tab. Select Local Storage, then scroll down the the URL Dashy is running on. You should now see all data being stored, and you can select and delete any fields you wish. + +For a full list of all data that may be cached, see the [Privacy Docs](/docs/privacy.md#browser-storage). + +### How to make a bug report + +#### Step 1 - Where to open issues + +You will need a GitHub account in order to raise a ticket. You can then [click here](https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E) to open a new bug report. + +#### Step 2 - Checking it's not already covered + +Before submitting, please check that: + +- A similar ticket has not previously been opened +- The issue is not covered in the [troubleshooting guide](https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md) or [docs](https://github.com/Lissy93/dashy/tree/master/docs#readme) + +#### Step 3 - Describe the Issue + +Your ticket will likely be dealt with more effectively if you can explain the issue clearly, and provide all relevant supporting material. + +Complete the fields, asking for your environment info and version of Dashy. +Then describe the issue, briefly explaining the steps to reproduce, expected outcome and actual outcome. + +#### Step 4 - Provide Supporting Info + +Where relevant please also include: + +- A screenshot of the issue +- The relevant parts of your config file +- Logs + - If client-side issue, then include the browser logs ([see how](#how-to-open-browser-console)) + - If server-side / during deployment, include the terminal output + +_Take care to redact any personal info, (like IP addresses, auth hashes or API keys)._ + +#### Step 5 - Fix Released + +A maintainer will aim to respond within 48 hours. +The timeframe for resolving your issue, will vary depending on severity of the bug and the complexity of the fix. +You will be notified on your ticket, when a fix has been released. + +Finally, be sure to remain respectful to other users and project maintainers, in line with the [Contributor Covenant Code of Conduct](https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct). + +### How-To Open Browser Console + +When raising a bug, one crucial piece of info needed is the browser's console output. This will help the developer diagnose and fix the issue. + +If you've been asked for this info, but are unsure where to find it, then it is under the "Console" tab, in the browsers developer tools, which can be opened with F12. You can right-click the console, and select Save As to download the log. + +To open dev tools, and jump straight to the console: + +- Win / Linux: Ctrl + Shift + J +- MacOS: Cmd + Option + J + +For more detailed walk through, see [this article](https://support.shortpoint.com/support/solutions/articles/1000222881-save-browser-console-file). + +### Git Contributions not Displaying + +If you've contributed to Dashy (or any other project), but your contributions are not showing up on your GH profile, or in Dashy's [Credits Page](https://github.com/Lissy93/dashy/blob/master/docs/credits.md), then this is likely a git config issue. + +These statistics are generated using the username / email associated with commits. This info needs to be setup on your local machine using [`git config`](https://git-scm.com/docs/git-config). + +Run the following commands (replacing name + email with your info): + +- `git config --global user.name "John Doe"` +- `git config --global user.email johndoe@example.com` + +For more info, see [Git First Time Setup Docs](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup). + +Note that only contributions to the master / main branch or a project are counted diff --git a/docs/widgets.md b/docs/widgets.md index 668ef2af..d774b4cc 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -160,10 +160,11 @@ A simple, live-updating local weather component, showing temperature, conditions --- | --- | --- | --- **`apiKey`** | `string` | Required | Your OpenWeatherMap API key. You can get one for free at [openweathermap.org](https://openweathermap.org/) **`city`** | `string` | Required | A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format +**`cityId`** | `number` | _Optional_ | An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on [openweathermap.org](https://openweathermap.org/) (e.g. `2643743` for London, GB). If provided, this will override the `city` option **`units`** | `string` | _Optional_ | The units to use for displaying data, can be either `metric` or `imperial`. Defaults to `metric` **`hideDetails`** | `boolean` | _Optional_ | If set to `true`, the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to `false` -**`lat`** | `number` | _Optional_ | To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the `city` option -**`lon`** | `number` | _Optional_ | To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the `city` option +**`lat`** | `number` | _Optional_ | To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the `city` and `cityId` options +**`lon`** | `number` | _Optional_ | To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the `city` and `cityId` options #### Example @@ -197,6 +198,9 @@ Displays the weather (temperature and conditions) for the next few days for a gi --- | --- | --- | --- **`apiKey`** | `string` | Required | Your OpenWeatherMap API key. You can get one at [openweathermap.org](https://openweathermap.org/) or for free via the [OWM Student Plan](https://home.openweathermap.org/students) **`city`** | `string` | Required | A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format +**`cityId`** | `number` | _Optional_ | An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on [openweathermap.org](https://openweathermap.org/) (e.g. `2643743` for London, GB). If provided, this will override the `city` option +**`lat`** | `number` | _Optional_ | Latitude for a specific location. If provided alongside `lon`, this will override the `city` and `cityId` options +**`lon`** | `number` | _Optional_ | Longitude for a specific location. If provided alongside `lat`, this will override the `city` and `cityId` options **`numDays`** | `number` | _Optional_ | The number of days to display of forecast info to display. Defaults to `4`, max `16` days **`units`** | `string` | _Optional_ | The units to use for displaying data, can be either `metric` or `imperial`. Defaults to `metric` **`hideDetails`** | `boolean` | _Optional_ | If set to `true`, the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to `false` diff --git a/package.json b/package.json index 76a2547d..ef7f6f92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dashy", - "version": "4.0.8", + "version": "4.0.10", "license": "MIT", "main": "server", "author": "Alicia Sykes (https://aliciasykes.com)", @@ -10,6 +10,7 @@ "build": "vite build", "preview": "vite preview", "lint": "eslint \"src/**/*.{js,vue}\"", + "validate-locales": "node tests/locales/check-locales.js", "typecheck": "vue-tsc --noEmit", "test": "vitest run", "test:watch": "vitest", @@ -28,7 +29,7 @@ "@codemirror/lint": "^6.9.6", "@codemirror/search": "^6.7.0", "@codemirror/state": "^6.6.0", - "@codemirror/view": "^6.41.1", + "@codemirror/view": "^6.43.0", "@jsonforms/core": "^3.7.0", "@jsonforms/vue": "^3.7.0", "@jsonforms/vue-vanilla": "^3.7.0", @@ -37,10 +38,11 @@ "ajv": "^8.20.0", "ajv-formats": "^3.0.1", "crypto-js": "^4.2.0", - "dompurify": "^3.4.1", - "express": "^4.21.0", + "dompurify": "^3.4.3", + "express": "^4.22.2", "express-basic-auth": "^1.2.1", "frappe-charts": "^1.6.2", + "jose": "^5.10.0", "js-yaml": "^4.1.0", "keycloak-js": "^26.0.0", "oidc-client-ts": "^3.0.1", @@ -51,16 +53,16 @@ "vue-router": "^4.4.0", "vue-select": "4.0.0-beta.6", "vuex": "^4.1.0", - "yaml": "^2.8.3" + "yaml": "^2.9.0" }, "devDependencies": { "@eslint/js": "^10.0.1", "@vitejs/plugin-vue": "^5.0.0", - "@vitest/ui": "^4.1.5", + "@vitest/ui": "^4.1.6", "@vue/compiler-sfc": "^3.5.0", "@vue/test-utils": "^2.4.8", "autoprefixer": "^10.4.27", - "eslint": "^10.2.1", + "eslint": "^10.4.0", "eslint-plugin-import-x": "^4.16.2", "eslint-plugin-vue": "^10.9.1", "globals": "^17.5.0", @@ -71,9 +73,9 @@ "vite": "^6.2.0", "vite-plugin-pwa": "^1.3.0", "vite-svg-loader": "^5.1.0", - "vitest": "^4.1.5", + "vitest": "^4.1.6", "vue-eslint-parser": "^10.0.0", - "vue-tsc": "^3.2.8" + "vue-tsc": "^3.2.9" }, "engines": { "node": ">=18.0.0" @@ -90,9 +92,10 @@ "resolutions": { "braces": "^3.0.3", "micromatch": "^4.0.8", - "postcss": "^8.4.31", + "postcss": "^8.5.14", "serialize-javascript": "^7.0.3", - "vite": "^6.2.0" + "vite": "^6.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.29.4" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/services/app.js b/services/app.js index dd2b1023..da68f39e 100644 --- a/services/app.js +++ b/services/app.js @@ -30,6 +30,7 @@ const systemInfo = require('./system-info'); // Basic system info, for resource const sslServer = require('./ssl-server'); // TLS-enabled web server const corsProxy = require('./cors-proxy'); // Enables API requests to CORS-blocked services const getUser = require('./get-user'); // Enables server side user lookup +const { loadOidcSettings, createOidcMiddleware } = require('./auth-oidc'); // Server-side OIDC/Keycloak token verification /* Service endpoint URL paths (see also serviceEndpoints in src/utils/config/defaults.js) */ const ENDPOINTS = { @@ -78,7 +79,7 @@ process.on('unhandledRejection', (reason) => { /* Load appConfig.auth from config (if present) for authorization purposes */ function loadAuthConfig() { try { - const filePath = path.join(rootDir, process.env.USER_DATA_DIR || 'user-data', 'conf.yml'); + const filePath = path.resolve(rootDir, process.env.USER_DATA_DIR || 'user-data', 'conf.yml'); const fileContents = fs.readFileSync(filePath, 'utf8'); const data = yaml.load(fileContents); return data?.appConfig?.auth || {}; @@ -116,9 +117,9 @@ function customAuthorizer(username, password) { } } -/* If auth is enabled, setup auth for config access, otherwise skip */ -function getBasicAuthMiddleware() { - const authConfig = loadAuthConfig(); +/* Pick an auth strategy based on what's configured in conf.yml + env. + OIDC / Keycloak takes precedence — it's the strongest enforcement we offer. */ +function getAuthMiddleware(authConfig, oidcSettings) { const confUsers = authConfig.users || null; const hasConfUsers = confUsers && confUsers.length > 0; const useConfAuth = process.env.ENABLE_HTTP_AUTH && hasConfUsers; @@ -133,7 +134,9 @@ function getBasicAuthMiddleware() { + ' This will cause auth failures. Set ENABLE_HTTP_AUTH=true, or remove users from conf.yml.'); } - if (useConfAuth) { + if (oidcSettings) { + return createOidcMiddleware(oidcSettings); + } else if (useConfAuth) { return basicAuth({ authorizer: customAuthorizer, challenge: true, @@ -163,11 +166,37 @@ function getBasicAuthMiddleware() { return (req, res, next) => next(); } -const protectConfig = getBasicAuthMiddleware(); +const initialAuthConfig = loadAuthConfig(); +const oidcSettings = loadOidcSettings(initialAuthConfig); +const protectConfig = getAuthMiddleware(initialAuthConfig, oidcSettings); -/* Middleware to restrict write endpoints to admin users only */ +/* True when any auth method is configured. Used to keep zero-auth deployments + open (their original behaviour) while closing the gate for everyone else. */ +const authIsConfigured = Boolean( + oidcSettings + || (process.env.ENABLE_HTTP_AUTH && initialAuthConfig.users?.length) + || (process.env.BASIC_AUTH_USERNAME && process.env.BASIC_AUTH_PASSWORD) + || (initialAuthConfig.enableHeaderAuth && initialAuthConfig.headerAuth), +); + +/* Require an authenticated identity on this request. No-op for zero-auth deploys. */ +function requireAuth(req, res, next) { + if (!authIsConfigured) return next(); + if (req.auth) return next(); + return res.status(401).json({ success: false, message: 'Unauthorized' }); +} + +/* Restrict to admin users. OIDC/Keycloak get isAdmin from token claims; the + conf.yml `users[]` path falls back to looking up user.type === 'admin'. */ function requireAdmin(req, res, next) { - if (!req.auth) return next(); + if (!authIsConfigured) return next(); + if (!req.auth) { + return res.status(401).json({ success: false, message: 'Unauthorized' }); + } + if (typeof req.auth.isAdmin === 'boolean') { + if (req.auth.isAdmin) return next(); + return res.status(403).json({ success: false, message: 'Forbidden - Admin access required' }); + } const users = loadUserConfig(); if (!users || users.length === 0) return next(); const user = users.find(u => u.user.toLowerCase() === req.auth.user.toLowerCase()); @@ -191,7 +220,7 @@ const app = express() // Load middlewares for parsing JSON, and supporting HTML5 history routing .use(express.json({ limit: '1mb' })) // GET endpoint to run status of a given URL with GET request - .use(ENDPOINTS.statusCheck, protectConfig, method('GET', (req, res) => { + .use(ENDPOINTS.statusCheck, protectConfig, requireAuth, method('GET', (req, res) => { try { statusCheck(req.url, (results) => { if (!res.headersSent) { @@ -223,7 +252,7 @@ const app = express() }); })) // GET endpoint to return system info, for widget - .use(ENDPOINTS.systemInfo, protectConfig, method('GET', (req, res) => { + .use(ENDPOINTS.systemInfo, protectConfig, requireAuth, method('GET', (req, res) => { try { safeEnd(res, JSON.stringify(systemInfo())); } catch (e) { @@ -231,7 +260,7 @@ const app = express() } })) // GET for accessing non-CORS API services - .use(ENDPOINTS.corsProxy, protectConfig, (req, res) => { + .use(ENDPOINTS.corsProxy, protectConfig, requireAuth, (req, res) => { try { corsProxy(req, res); } catch (e) { @@ -239,7 +268,7 @@ const app = express() } }) // GET endpoint to return user info - .use(ENDPOINTS.getUser, protectConfig, method('GET', (req, res) => { + .use(ENDPOINTS.getUser, protectConfig, requireAuth, method('GET', (req, res) => { try { safeEnd(res, JSON.stringify(getUser(config, req))); } catch (e) { @@ -249,13 +278,13 @@ const app = express() // Middleware to serve any .yml files in USER_DATA_DIR with optional protection .get('/*.yml', protectConfig, (req, res) => { const ymlFile = req.path.split('/').pop(); - const filePath = path.join(rootDir, process.env.USER_DATA_DIR || 'user-data', ymlFile); + const filePath = path.resolve(rootDir, process.env.USER_DATA_DIR || 'user-data', ymlFile); res.sendFile(filePath, (err) => { if (err) safeEnd(res, errBody(`Could not read ${ymlFile}`), 404); }); }) // Serves up static files - .use(express.static(path.join(rootDir, process.env.USER_DATA_DIR || 'user-data'))) + .use(express.static(path.resolve(rootDir, process.env.USER_DATA_DIR || 'user-data'))) .use(express.static(path.join(rootDir, 'dist'))) .use(express.static(path.join(rootDir, 'public'), { index: 'initialization.html' })) // If no other route is matched, serve up the index.html with a 404 status diff --git a/services/auth-oidc.js b/services/auth-oidc.js new file mode 100644 index 00000000..c41747af --- /dev/null +++ b/services/auth-oidc.js @@ -0,0 +1,140 @@ +/** + * Server-side OIDC / Keycloak token verification. + * + * Why this exists: Dashy's OIDC + Keycloak flows are otherwise entirely + * client-side, so the server never sees an identity. This module verifies the + * id_token a logged-in browser attaches to API requests, so admin-gated + * endpoints (and authenticated endpoints generally) can enforce auth server-side. + * + * The middleware is permissive on a missing Authorization header — bootstrap + * fetches (e.g. /conf.yml) must succeed before the user is logged in. Endpoints + * that require auth should be paired with `requireAuth` (in app.js). + * A present-but-invalid token is always rejected. + */ + +const { createRemoteJWKSet, jwtVerify } = require('jose'); + +/* Normalise the OIDC / Keycloak block from conf.yml into a unified shape. + Returns null when neither provider is enabled or required fields are missing. */ +function loadOidcSettings(authConfig) { + if (!authConfig || typeof authConfig !== 'object') return null; + + if (authConfig.enableOidc && authConfig.oidc) { + const { endpoint, clientId, adminGroup, adminRole } = authConfig.oidc; + if (!endpoint || !clientId) return null; + return { + kind: 'oidc', + issuer: String(endpoint), + clientId: String(clientId), + adminGroup: adminGroup || null, + adminRole: adminRole || null, + }; + } + + if (authConfig.enableKeycloak && authConfig.keycloak) { + const { + serverUrl, realm, clientId, adminGroup, adminRole, legacySupport, + } = authConfig.keycloak; + if (!serverUrl || !realm || !clientId) return null; + // Mirror the URL keycloak-js builds in the browser. + const base = (legacySupport ? `${serverUrl}/auth` : serverUrl).replace(/\/$/, ''); + return { + kind: 'keycloak', + issuer: `${base}/realms/${realm}`, + clientId: String(clientId), + adminGroup: adminGroup || null, + adminRole: adminRole || null, + }; + } + + return null; +} + +/* Per-issuer cache: discovery metadata + JWKS resolver. createRemoteJWKSet + handles key caching + rotation internally. */ +const issuerCache = new Map(); + +async function fetchDiscovery(issuer) { + const base = issuer.endsWith('/') ? issuer : `${issuer}/`; + const url = new URL('.well-known/openid-configuration', base); + const res = await fetch(url); + if (!res.ok) throw new Error(`OIDC discovery returned ${res.status} for ${url}`); + return res.json(); +} + +async function getIssuerContext(issuer) { + const cached = issuerCache.get(issuer); + if (cached) return cached; + const config = await fetchDiscovery(issuer); + if (!config.issuer || !config.jwks_uri) { + throw new Error('Discovery document is missing `issuer` or `jwks_uri`'); + } + const ctx = { + canonicalIssuer: config.issuer, + jwks: createRemoteJWKSet(new URL(config.jwks_uri)), + }; + issuerCache.set(issuer, ctx); + return ctx; +} + +/* Pure helper: true when the token's claims map to the configured admin group/role. + Handles standard OIDC top-level `groups`/`roles` claims plus Keycloak's nested + realm_access / resource_access role shapes (mirrors what KeycloakAuth.js does + client-side). */ +function deriveIsAdmin(claims, settings) { + if (!claims) return false; + const groups = Array.isArray(claims.groups) ? [...claims.groups] : []; + const roles = Array.isArray(claims.roles) ? [...claims.roles] : []; + + if (settings.kind === 'keycloak') { + const realmRoles = claims.realm_access && claims.realm_access.roles; + if (Array.isArray(realmRoles)) roles.push(...realmRoles); + const clientRoles = claims.resource_access + && claims.resource_access[settings.clientId] + && claims.resource_access[settings.clientId].roles; + if (Array.isArray(clientRoles)) roles.push(...clientRoles); + } + + const { adminGroup, adminRole } = settings; + if (adminGroup && groups.includes(adminGroup)) return true; + if (adminRole && roles.includes(adminRole)) return true; + return false; +} + +/* Connect middleware factory. Verifies Bearer id_token; sets req.auth on success. */ +function createOidcMiddleware(settings) { + return async (req, res, next) => { + const header = req.headers.authorization || ''; + const match = header.match(/^Bearer\s+(.+)$/i); + if (!match) return next(); // Permissive: no token attached, let downstream gates decide + const token = match[1].trim(); + if (!token) return next(); + + try { + const { canonicalIssuer, jwks } = await getIssuerContext(settings.issuer); + const { payload } = await jwtVerify(token, jwks, { + issuer: canonicalIssuer, + audience: settings.clientId, + clockTolerance: '30s', + }); + req.auth = { + user: payload.preferred_username || payload.email || payload.sub || 'unknown', + isAdmin: deriveIsAdmin(payload, settings), + claims: payload, + }; + return next(); + } catch (e) { + console.warn('[auth-oidc] token verification failed:', e.message || e); // eslint-disable-line no-console + return res.status(401).json({ + success: false, + message: 'Unauthorized - Invalid or expired token', + }); + } + }; +} + +module.exports = { + loadOidcSettings, + createOidcMiddleware, + deriveIsAdmin, +}; diff --git a/src/App.vue b/src/App.vue index 7ec62a4d..4f6bdb8a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -183,17 +183,21 @@ export default { } }, + /* Case-insensitive lookup that returns the canonical locale code, or undefined */ + resolveLocale(availibleLocales, userLang) { + if (!userLang) return undefined; + const target = userLang.toLowerCase(); + return availibleLocales.find((lang) => lang.toLowerCase() === target); + }, + /* Auto-detects users language from browser/ os, when not specified */ autoDetectLanguage(availibleLocales) { - const isLangSupported = (languageList, userLang) => languageList - .map(lang => lang.toLowerCase()).find((lang) => lang === userLang.toLowerCase()); - const usersBorwserLang1 = window.navigator.language || ''; // e.g. en-GB or or '' const usersBorwserLang2 = usersBorwserLang1.split('-')[0]; // e.g. en or undefined - const usersSpairLangs = window.navigator.languages; // e.g [en, en-GB, en-US] - return isLangSupported(availibleLocales, usersBorwserLang1) - || isLangSupported(availibleLocales, usersBorwserLang2) - || usersSpairLangs.find((spair) => isLangSupported(availibleLocales, spair)) + const usersSpairLangs = window.navigator.languages || []; // e.g [en, en-GB, en-US] + return this.resolveLocale(availibleLocales, usersBorwserLang1) + || this.resolveLocale(availibleLocales, usersBorwserLang2) + || usersSpairLangs.map((spair) => this.resolveLocale(availibleLocales, spair)).find(Boolean) || defaultLanguage; }, @@ -202,11 +206,9 @@ export default { const availibleLocales = this.$i18n.availableLocales; // All available locales const usersLang = localStorage[localStorageKeys.LANGUAGE] || this.appConfig.language; if (usersLang) { - if (availibleLocales.includes(usersLang)) { - return usersLang; - } else { - ErrorHandler(`Unsupported Language: '${usersLang}'`); - } + const resolved = this.resolveLocale(availibleLocales, usersLang); + if (resolved) return resolved; + ErrorHandler(`Unsupported Language: '${usersLang}'`); } return this.autoDetectLanguage(availibleLocales); }, diff --git a/src/assets/locales/ar.json b/src/assets/locales/ar.json index 10f3a939..08685bbc 100644 --- a/src/assets/locales/ar.json +++ b/src/assets/locales/ar.json @@ -47,12 +47,6 @@ }, "app-info": { "title": "معلومات التطبيق", - "error-log": "سجل الأخطاء", - "no-errors": "لم يتم اكتشاف أخطاء مؤخراً", - "help-support": "المساعدة والدعم", - "help-support-description": "للحصول على دعم في تشغيل أو إعداد Dashy، راجع", - "help-support-discussions": "المناقشات", - "support-dashy-link": "صفحة المساهمات", "report-bug": "الإبلاغ عن خطأ", "report-bug-description": "إذا كنت تعتقد أنك وجدت خطأ، يرجى", "report-bug-link": "فتح Issue", @@ -68,8 +62,6 @@ "privacy-and-security-security-policy": "سياسة الأمن", "license": "الترخيص", "license-under": "مرخص بموجب", - "licence-third-party": "لتراخيص الوحدات الخارجية، يرجى الاطلاع على", - "licence-third-party-link": "قانوني", "list-contributors": "للقائمة الكاملة للمساهمين والشكر، انظر", "list-contributors-link": "الاعتمادات", "version": "الإصدار" @@ -95,8 +87,6 @@ "reset-config-msg-l2": "يجب عليك أولاً الاحتفاظ بنسخة احتياطية من أي تغييرات أجريتها محليًا ، إذا كنت ترغب في استخدامها في المستقبل.", "reset-config-msg-l3": "هل أنت متأكد انك تريد المتابعة؟", "data-cleared-msg": "تم مسح البيانات بنجاح", - "actions-label": "أجراءات", - "copy-config-label": "نسخ التكوين", "data-copied-msg": "تم نسخ التكوين إلى الحافظة", "reset-config-label": "إعادة التهيئة", "css-save-btn": "حفظ التغييرات", @@ -130,8 +120,7 @@ "sign-out-tooltip": "خروج", "sign-in-tooltip": "تسجيل دخول", "sign-in-welcome": "مرحبًا {username}!", - "hide": "إخفاء", - "open": "فتح" + "hide": "إخفاء" }, "updates": { "app-version-note": "ملاحظة نسخة التطبيق", @@ -159,23 +148,11 @@ "reset-toast": "تمت إزالة الألوان المخصصة لـ {theme}" }, "config-editor": { - "save-location-label": "حفظ الموقع", - "location-local-label": "تطبيق محليا", - "location-disk-label": "اكتب التغييرات في ملف التكوين", - "save-button": "حفظ التغييرات", "preview-button": "معاينة التغييرات", "valid-label": "التكوين صالح", - "status-success-msg": "اكتملت المهمة", - "status-fail-msg": "فشلت المهمة", "success-msg-disk": "تمت كتابة ملف التكوين على القرص بنجاح", "success-msg-local": "تم حفظ التغييرات المحلية بنجاح", - "success-note-l1": "يجب إعادة إنشاء التطبيق تلقائيًا.", - "success-note-l2": "قد يستغرق هذا ما يصل إلى دقيقة.", - "success-note-l3": "ستحتاج إلى تحديث الصفحة لتصبح التغييرات سارية المفعول.", - "error-msg-save-mode": "الرجاء تحديد \"وضع الحفظ\": محلي أو ملف", "error-msg-cannot-save": "حدث خطأ أثناء حفظ التكوين", - "error-msg-bad-json": "خطأ في JSON ، ربما يكون غير صحيح", - "warning-msg-validation": "تحذير التحقق", "not-admin-note": "لا يمكنك الكتابة التغيير إلى القرص ، لأنك لم تقم بتسجيل الدخول كمسؤول" }, "cloud-sync": { @@ -185,7 +162,6 @@ "intro-l3": "لمزيد من المعلومات ، يرجى الاطلاع على", "intro-docs": "الوثائق", "backup-title-setup": "أصنع نسخة إحتياطية", - "backup-title-update": "تحديث النسخ الاحتياطي", "password-label-setup": "اختر كلمة مرور", "password-label-update": "ادخل رقمك السري", "backup-button-setup": "دعم", @@ -202,17 +178,6 @@ "backup-success-msg": "تم بنجاح", "restore-success-msg": "تمت استعادة التكوين بنجاح" }, - "menu": { - "open-section-title": "فتح في", - "sametab": "فتح في علامة التبويب الحالية", - "newtab": "فتح في علامة تبويب جديدة", - "modal": "فتح في Pop-Up Modal", - "workspace": "فتح في عرض مساحة العمل", - "options-section-title": "خيارات", - "edit-item": "تعديل", - "move-item": "نسخ أو نقل", - "remove-item": "إزالة" - }, "context-menus": { "item": { "open-section-title": "فتح في", @@ -232,16 +197,9 @@ "open-section": "فتح القسم", "edit-section": "تعديل", "expand-collapse": "توسيع / طي", - "move-section": "نقل إلى", "remove-section": "إزالة" } }, - "footer": { - "dev-by": "تطوير بواسطة", - "licensed-under": "مرخص بموجب", - "get-the": "احصل على", - "source-code": "كود المصدر" - }, "interactive-editor": { "menu": { "start-editing-tooltip": "الدخول إلى المحرر التفاعلي", @@ -289,7 +247,6 @@ "warning-msg-l3": "لتجنب العواقب غير المقصودة." }, "export": { - "export-title": "تصدير الإعدادات", "copy-clipboard-btn": "نسخ إلى الحافظة", "copy-clipboard-tooltip": "نسخ جميع إعدادات التطبيق إلى حافظة النظام بتنسيق YAML", "download-file-btn": "تحميل كملف", @@ -306,7 +263,6 @@ }, "widgets": { "general": { - "loading": "جاري التحميل...", "show-more": "توسيع التفاصيل", "cpu-details": "تفاصيل الـ CPU", "mem-details": "تفاصيل الذاكرة (RAM)", @@ -347,13 +303,9 @@ "good-service-rest": "خدمة جيدة في جميع الخطوط الأخرى" }, "synology-download": { - "download": "تحميل", - "upload": "رفع", "downloaded": "تم تحميله", "uploaded": "تم رفعه", - "remaining": "متبقي", - "up": "أعلى", - "down": "أسفل" + "remaining": "متبقي" }, "gluetun-status": { "vpn-ip": "عنوان VPN IP", diff --git a/src/assets/locales/bg.json b/src/assets/locales/bg.json index 424fd62e..33b8c34f 100644 --- a/src/assets/locales/bg.json +++ b/src/assets/locales/bg.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Първо трябва да архивирате всички промени, които сте направили локално, ако искате да ги използвате в бъдеще.", "reset-config-msg-l3": "Искате ли да продължите?", "data-cleared-msg": "Данните са изчистени успешно", - "actions-label": "Действия", - "copy-config-label": "Копиране на конфиг", "data-copied-msg": "Конфигурацията беше копирана в clipboard", "reset-config-label": "Нулиране на конфигурация", "css-save-btn": "Запазване на промените", @@ -110,23 +108,11 @@ "reset-toast": "Персонализираните цветове за {theme} бяха премахнати" }, "config-editor": { - "save-location-label": "Запазване локално", - "location-local-label": "Прилагане локално", - "location-disk-label": "Запис на промените в конфигурационния файл", - "save-button": "Запазване на промените", "preview-button": "Преглед на промените", "valid-label": "Валидни конфигурации", - "status-success-msg": "Задачата завършена", - "status-fail-msg": "Заадачата пропадна", "success-msg-disk": "Конфигурационният файл е записан на диска успешно", "success-msg-local": "Локалните промени са запазени успешно", - "success-note-l1": "Приложението би трябвало да се ребилдне автоматично.", - "success-note-l2": "Това може да отнеме до минута.", - "success-note-l3": "Ще трябва да презаредите страницата за да бъдат приложени промените.", - "error-msg-save-mode": "Моля изберете тип на запазване: Локално или Файл", "error-msg-cannot-save": "Появи се грешка при запис на конфигурация", - "error-msg-bad-json": "Грешка с JSON, веротяно е неправилно форматиран", - "warning-msg-validation": "Предупреждение за валидиране", "not-admin-note": "Не можете да пишете променено на диска, защото не сте влезли като администратор" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Всички данни са напълно криптирани от край до край с AES, като се използва паролата ви като ключ.", "intro-l3": "За повече информация, моля, вижте", "backup-title-setup": "Направете резервно копие", - "backup-title-update": "Актуализиране на бекъп", "password-label-setup": "Изберете парола", "password-label-update": "Въведете Вашата парола", "backup-button-setup": "Бекъп", @@ -152,17 +137,6 @@ "backup-success-msg": "Приключи с успех", "restore-success-msg": "Конфигурацията е възстановена успешно" }, - "menu": { - "open-section-title": "Отваряне в", - "sametab": "Текущ таб", - "newtab": "Нов таб", - "modal": "Изскачащ модален прозорец", - "workspace": "Изглед на работното пространство", - "options-section-title": "Опции", - "edit-item": "Редактиране", - "move-item": "Копиране или изместване", - "remove-item": "Премахване" - }, "context-menus": { "item": { "open-section-title": "Отваряне в", @@ -181,7 +155,6 @@ "open-section": "Отваряне на секция", "edit-section": "Редактиране", "expand-collapse": "Разгъване / свиване", - "move-section": "Местене в", "remove-section": "Премахване" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "за да се избегнат непредвидени последици." }, "export": { - "export-title": "Експорт на конфигурация", "copy-clipboard-btn": "Копиране в клипборда", "copy-clipboard-tooltip": "Кпиране на конфигурациите на приложението в YAML формат", "download-file-btn": "Запазване във файл", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "Зареждане...", "show-more": "Повече детайли", "show-less": "Покажи по-малко", "open-link": "Отваряне на линка" @@ -280,13 +251,9 @@ "good-service-rest": "Добра работа на всички други линии" }, "synology-download": { - "download": "Теглене", - "upload": "Качване", "downloaded": "Изтеглено", "uploaded": "Качено", - "remaining": "Оставащо", - "up": "Нагоре", - "down": "Надолу" + "remaining": "Оставащо" } } } diff --git a/src/assets/locales/bn.json b/src/assets/locales/bn.json index fb41b351..431a330b 100644 --- a/src/assets/locales/bn.json +++ b/src/assets/locales/bn.json @@ -60,8 +60,6 @@ "reset-config-msg-l2": "আপনি যদি ভবিষ্যতে সেগুলি ব্যবহার করতে চান তবে প্রথমে স্থানীয়ভাবে আপনার করা যেকোনো পরিবর্তনের ব্যাকআপ নেওয়া উচিত।", "reset-config-msg-l3": "আপনি কি নিশ্চিত আপনি সামনে এগুতে চান?", "data-cleared-msg": "ডেটা সফলভাবে মুছে ফেলা হয়েছে৷", - "actions-label": "ক্রিয়াকলাপ", - "copy-config-label": "সজ্জিতকরণ অনুলিপি", "data-copied-msg": "কনফিগারেশনটি ক্লিপবোর্ডে অনুলিপি করা হয়েছে", "reset-config-label": "সজ্জিতকরণ পুন:স্থাপন", "css-save-btn": "পরিবর্তনগুলোর সংরক্ষন", @@ -118,23 +116,11 @@ "reset-toast": "{theme}-এর জন্য কাস্টম রং সরানো হয়েছে" }, "config-editor": { - "save-location-label": "স্থান সংরক্ষন", - "location-local-label": "স্থানীয়ভাবে আবেদন করুন", - "location-disk-label": "কনফিগ ফাইলে পরিবর্তন লিখুন", - "save-button": "পরিবর্তনগুলোর সংরক্ষন", "preview-button": "পূর্বরূপ পরিবর্তন", "valid-label": "কনফিগ বৈধ", - "status-success-msg": "কাজ সম্পূর্ণ", - "status-fail-msg": "কাজ ব্যর্থ হয়েছে৷", "success-msg-disk": "কনফিগ ফাইল সফলভাবে ডিস্কে লেখা হয়েছে", "success-msg-local": "স্থানীয় পরিবর্তনগুলি সফলভাবে সংরক্ষিত হয়েছে৷", - "success-note-l1": "অ্যাপটি স্বয়ংক্রিয়ভাবে পুনর্নির্মাণ করা উচিত।", - "success-note-l2": "এতে এক মিনিট পর্যন্ত সময় লাগতে পারে।", - "success-note-l3": "পরিবর্তনগুলি কার্যকর করার জন্য আপনাকে পৃষ্ঠাটি রিফ্রেশ করতে হবে৷", - "error-msg-save-mode": "অনুগ্রহ করে একটি সংরক্ষণ মোড নির্বাচন করুন: স্থানীয় বা ফাইল।", "error-msg-cannot-save": "কনফিগার সংরক্ষণ করার সময় একটি ত্রুটি ঘটেছে৷", - "error-msg-bad-json": "JSON-এ ত্রুটি, সম্ভবত বিকৃত", - "warning-msg-validation": "বৈধতা সতর্কতা", "not-admin-note": "আপনি ডিস্কে পরিবর্তিত লিখতে পারবেন না, কারণ আপনি অ্যাডমিন হিসাবে লগ ইন করেননি৷" }, "cloud-sync": { @@ -143,7 +129,6 @@ "intro-l2": "All data is fully end-to-end encrypted with AES, using your password as the key.", "intro-l3": "আরো তথ্যের জন্য, দেখুন", "backup-title-setup": "একটি ব্যাকআপ করুন", - "backup-title-update": "ব্যাকআপ আপডেট করুন", "password-label-setup": "একটি পাসওয়ার্ড চয়ন করুন", "password-label-update": "আপনার পাসওয়ার্ড লিখুন", "backup-button-setup": "ব্যাকআপ", @@ -160,17 +145,6 @@ "backup-success-msg": "সফলভাবে সম্পন্ন", "restore-success-msg": "কনফিগারেশন সফলভাবে পুনরুদ্ধার করা হয়েছে" }, - "menu": { - "open-section-title": "খুলুন", - "sametab": "বর্তমান ট্যাব", - "newtab": "নতুন ট্যাব", - "modal": "পপ-আপ মডেল", - "workspace": "ওয়ার্কস্পেস ভিউ", - "options-section-title": "বিকল্প", - "edit-item": "সম্পাদনা করুন", - "move-item": "অনুলিপি বা সরান", - "remove-item": "মুছুন" - }, "context-menus": { "item": { "open-section-title": "খুলুন", @@ -189,7 +163,6 @@ "open-section": "বিভাগ খুলুন", "edit-section": "সম্পাদনা করুন", "expand-collapse": "প্রসারিত/সংকোচন করুন", - "move-section": "সরান-", "remove-section": "মুছুন" } }, @@ -240,7 +213,6 @@ "warning-msg-l3": "অনাকাঙ্ক্ষিত পরিণতি এড়াতে।" }, "export": { - "export-title": "এক্সপোর্ট কনফিগার", "copy-clipboard-btn": "ক্লিপবোর্ডে কপি করুন", "copy-clipboard-tooltip": "YAML ফর্ম্যাটে, সিস্টেম ক্লিপবোর্ডে সমস্ত অ্যাপ কনফিগারেশন কপি করুন", "download-file-btn": "ফাইল হিসাবে ডাউনলোড করুন", @@ -249,7 +221,6 @@ }, "widgets": { "general": { - "loading": "লোড হচ্ছে...", "show-more": "বিস্তারিত বিবরণ...", "show-less": "কম প্রদর্শন", "open-link": "পড়া চালিয়ে যান" @@ -288,13 +259,9 @@ "good-service-rest": "অন্যান্য সমস্ত লাইনে ভাল পরিষেবা" }, "synology-download": { - "download": "ডাউনলোড করুন", - "upload": "আপলোড করুন", "downloaded": "ডাউনলোড করা হয়েছে", "uploaded": "আপলোড করা হয়েছে", - "remaining": "অবশিষ্ট", - "up": "উপরে", - "down": "নিচে" + "remaining": "অবশিষ্ট" }, "gluetun-status": { "vpn-ip": "ভিপিএন আইপি", diff --git a/src/assets/locales/cs.json b/src/assets/locales/cs.json index 75f51f85..60596409 100644 --- a/src/assets/locales/cs.json +++ b/src/assets/locales/cs.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Všechny změny, které jste vykonali lokálně, byste si měli co nejdříve zálohovat, pokud je chcete použít v budoucnosti.", "reset-config-msg-l3": "Opravdu chcete pokračovat?", "data-cleared-msg": "Údaje byly úspěšne vymazány", - "actions-label": "Akce", - "copy-config-label": "Kopírovat konfiguraci", "data-copied-msg": "Konfigurace byla zkopírována do schránky", "reset-config-label": "Obnovit konfiguraci", "css-save-btn": "Uložit změny", @@ -110,23 +108,11 @@ "reset-toast": "Vlastní barvy pro {theme} byly odstraněny" }, "config-editor": { - "save-location-label": "Uložit umístění", - "location-local-label": "Aplikovať lokálně", - "location-disk-label": "Zapsat změny do konfiguračního souboru", - "save-button": "Uložit změny", "preview-button": "Ukázka změn", "valid-label": "Konfigurace je platná", - "status-success-msg": "Akce dokončena", - "status-fail-msg": "Akce selhala", "success-msg-disk": "Konfigurační soubor byl úspěšně zapsán na disk", "success-msg-local": "Místní změny byly úspěšne uloženy", - "success-note-l1": "Aplikace by se měla automaticky obnovit.", - "success-note-l2": "Můýe to trvat až minutu.", - "success-note-l3": "Aby se změny projevily, budete museť obnovit stránku.", - "error-msg-save-mode": "Vyberte režim uložení: Místní/Soubor", "error-msg-cannot-save": "Při ukládání konfigurace se vyskytla chyba", - "error-msg-bad-json": "Chyba v JSON, možná má nesprávny formát", - "warning-msg-validation": "Chyba validace", "not-admin-note": "Nemůžete zapisovat změny na disk, protože nejste přihlášený/á jako správca" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Všechny údaje jsou end-to-end šifrované pomocí AES, pričemž jako klíč se používá Vaše heslo.", "intro-l3": "Více informací nájdete na", "backup-title-setup": "Vytvořit zálohu", - "backup-title-update": "Aktualizovat zálohu", "password-label-setup": "Vytvořte heslo pro cloud", "password-label-update": "Zadajte heslo pro cloud", "backup-button-setup": "Záloha", @@ -152,17 +137,6 @@ "backup-success-msg": "Záloha úspěšně dokončena.", "restore-success-msg": "Záloha byla úspěšně obnovena" }, - "menu": { - "open-section-title": "Otevřít v", - "sametab": "Aktuální karta", - "newtab": "Nová karta", - "modal": "Modální okno", - "workspace": "Zobrazení pracovního prostoru", - "options-section-title": "Možnosti", - "edit-item": "Upravit", - "move-item": "Kopírovat nebo přesunout", - "remove-item": "Odstranit" - }, "context-menus": { "item": { "open-section-title": "Otevřít v", @@ -181,7 +155,6 @@ "open-section": "Otevřená sekce", "edit-section": "Upravit", "expand-collapse": "Rozbalit / sbaliť", - "move-section": "Přesunout do", "remove-section": "Odstranit" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "Aby se předešlo neúmyslným následkům." }, "export": { - "export-title": "Exportovat konfiguraci", "copy-clipboard-btn": "Kopírovať do schránky", "copy-clipboard-tooltip": "Kopírovať všechny konfigurace aplikace do systémové schránky ve formátu YAML", "download-file-btn": "Stáhnout jako soubor", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "Načítání...", "show-more": "Rozbalit podrobnosti", "show-less": "Zobrazit méně", "open-link": "Pokračovať ve čtení" @@ -280,13 +251,9 @@ "good-service-rest": "Dobré služby na všech ostatních linkách" }, "synology-download": { - "download": "Stáhnout", - "upload": "Nahrat", "downloaded": "Stáhnuto", "uploaded": "Nahráno", - "remaining": "Zbývající", - "up": "Aktivní", - "down": "Neaktivní" + "remaining": "Zbývající" }, "gluetun-status": { "vpn-ip": "IP VPN", diff --git a/src/assets/locales/da.json b/src/assets/locales/da.json index 887f29d5..6639c1e5 100644 --- a/src/assets/locales/da.json +++ b/src/assets/locales/da.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Du bør først sikkerhedskopiere eventuelle ændringer, du har foretaget lokalt, hvis du vil bruge dem i fremtiden.", "reset-config-msg-l3": "Er du sikker på at du vil fortsætte?", "data-cleared-msg": "Data blev succesfuldt ryddet", - "actions-label": "Handlinger", - "copy-config-label": "Kopiere Config", "data-copied-msg": "Konfigurationen er blevet kopieret til clipboard", "reset-config-label": "Nulstil konfiguration", "css-save-btn": "Gem ændringer", @@ -110,23 +108,11 @@ "reset-toast": "Tilpassede farver for {theme} fjernet" }, "config-editor": { - "save-location-label": "Lokation for gem", - "location-local-label": "Gem lokalt", - "location-disk-label": "Skriv ændringer til konfigurationsfil", - "save-button": "Gem ændringer", "preview-button": "Forhåndsvisning af ændringer", "valid-label": "Konfigurationen er gyldig", - "status-success-msg": "Opgave fuldført", - "status-fail-msg": "Opgave mislykkedes", "success-msg-disk": "Konfigurationsfilen er skrevet til disken", "success-msg-local": "Lokale ændringer blev gemt", - "success-note-l1": "Appen skal genopbygges automatisk.", - "success-note-l2": "Dette kan tage op til et minut.", - "success-note-l3": "Du skal opdatere siden for at ændringerne træder i kraft.", - "error-msg-save-mode": "Vælg venligst en gemtilstand: Lokal eller Fil", "error-msg-cannot-save": "Der opstod en fejl under lagring af konfiguration", - "error-msg-bad-json": "Fejl i JSON, muligvis forkert udformet", - "warning-msg-validation": "Valideringsadvarsel", "not-admin-note": "Du kan ikke skrive ændringer til disk, fordi du ikke er logget ind som admin" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Alle data er fuldstændigt end-to-end krypteret med AES, ved at bruge din adgangskode som nøglen.", "intro-l3": "For mere information, se venligst", "backup-title-setup": "Lav en sikkerhedskopi", - "backup-title-update": "Opdater sikkerhedskopiering", "password-label-setup": "Vælg et kodeord", "password-label-update": "Skriv dit kodeord", "backup-button-setup": "Sikkerhedskopiering", @@ -152,17 +137,6 @@ "backup-success-msg": "Afsluttet med succes", "restore-success-msg": "Konfiguration gendannet med succes" }, - "menu": { - "open-section-title": "Åbn i", - "sametab": "Aktuel fane", - "newtab": "Ny fane", - "modal": "Pop-Up Modal", - "workspace": "Arbejdsrumsvisning", - "options-section-title": "Muligheder", - "edit-item": "Rediger", - "move-item": "Kopier eller flyt", - "remove-item": "Fjern" - }, "context-menus": { "item": { "open-section-title": "Åbn i", @@ -181,7 +155,6 @@ "open-section": "Åbn sektion", "edit-section": "Rediger", "expand-collapse": "Udvid / Skjul", - "move-section": "Flytte til", "remove-section": "Fjerne" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "for at undgå utilsigtede konsekvenser." }, "export": { - "export-title": "Eksporter konfiguration", "copy-clipboard-btn": "Kopier til udklipsholder", "copy-clipboard-tooltip": "Kopier alle app-konfigurationer til systemets udklipsholder i YAML-format", "download-file-btn": "Download som fil", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "Indlæser...", "show-more": "Udvid detaljer", "show-less": "Vis mindre", "open-link": "Fortsæt med at læse" @@ -280,13 +251,9 @@ "good-service-rest": "God service på alle andre linjer" }, "synology-download": { - "download": "Download", - "upload": "Upload", "downloaded": "Downloaded", "uploaded": "Uploaded", - "remaining": "Tilbage", - "up": "Op", - "down": "Ned" + "remaining": "Tilbage" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/de.json b/src/assets/locales/de.json index 57565423..21a37ecf 100644 --- a/src/assets/locales/de.json +++ b/src/assets/locales/de.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "App Info", - "support-dashy-link": "Contributions Seite", "report-bug": "Melde einen Fehler", "report-bug-description": "Wenn Sie glauben, einen Fehler gefunden zu haben, dann bitte", "report-bug-link": "öffne ein Issue", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "Security Policy", "license": "Lizenz", "license-under": "Lizensiert unter", - "licence-third-party": "Für Lizenzen von Drittanbietermodulen, siehe", - "licence-third-party-link": "Legal", "list-contributors": "Für eine vollstandige Liste aller Beteiligten und Dank, siehe", "list-contributors-link": "Credits", "version": "Version" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Sie sollten zuerst alle Änderungen, die Sie lokal vorgenommen haben, sichern, wenn Sie sie in Zukunft wiederverwenden möchten.", "reset-config-msg-l3": "Sind Sie sicher, dass Sie fortfahren möchten?", "data-cleared-msg": "Daten erfolgreich gelöscht", - "actions-label": "Aktionen", - "copy-config-label": "Konfiguration kopieren", "data-copied-msg": "Konfiguration wurde in die Zwischenablage kopiert", "reset-config-label": "Konfiguration zurücksetzen", "css-save-btn": "Änderungen speichern", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Abmelden", "sign-in-tooltip": "Anmelden", "sign-in-welcome": "Hallo {username}!", - "hide": "Verstecke", - "open": "Öffne" + "hide": "Verstecke" }, "updates": { "app-version-note": "Dashy Version", @@ -153,23 +147,11 @@ "reset-toast": "Benutzerdefinierte Farben für {theme} wurden entfernt" }, "config-editor": { - "save-location-label": "Speicherort", - "location-local-label": "Lokal anwenden", - "location-disk-label": "Änderungen in die Konfigurationsdatei schreiben", - "save-button": "Änderungen speichern", "preview-button": "Vorschau der Änderungen", "valid-label": "Syntax ist gültig", - "status-success-msg": "Aufgabe abgeschlossen", - "status-fail-msg": "Aufgabe fehlgeschlagen", "success-msg-disk": "Konfigurationsdatei wurde erfolgreich auf die Festplatte geschrieben", "success-msg-local": "Lokale Änderungen wurden erfolgreich gespeichert", - "success-note-l1": "Die Applikation sollte automatisch re-kompiliert werden.", - "success-note-l2": "Dies kann bis zu einer Minute dauern.", - "success-note-l3": "Sie müssen die Seite aktualisieren damit die Änderungen wirksam werden.", - "error-msg-save-mode": "Bitte wählen Sie einen Speichermodus: Lokal oder Datei", "error-msg-cannot-save": "Beim Speichern der Konfiguration ist ein Fehler aufgetreten", - "error-msg-bad-json": "Fehler in JSON-Daten, möglicherweise fehlerhafter Syntax", - "warning-msg-validation": "Validierungswarnung", "not-admin-note": "Änderungen können nicht auf die Festplatte gespeichert werden, da Sie nicht als Administrator angemeldet sind" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Weitere Informationen finden Sie in der", "intro-docs": "Dokumentation", "backup-title-setup": "Backup erstellen", - "backup-title-update": "Backup aktualisieren", "password-label-setup": "Passwort auswählen", "password-label-update": "Passwort eingeben", "backup-button-setup": "Backup", @@ -196,17 +177,6 @@ "backup-success-msg": "Erfolgreich abgeschlossen", "restore-success-msg": "Konfiguration erfolgreich wiederhergestellt" }, - "menu": { - "open-section-title": "Öffne in", - "sametab": "Aktueller Tab", - "newtab": "Neuer Tab", - "modal": "Popup Modal", - "workspace": "Arbeitsflächenansicht", - "options-section-title": "Optionen", - "edit-item": "Bearbeiten", - "move-item": "Kopieren oder Verschieben", - "remove-item": "Entfernen" - }, "context-menus": { "item": { "open-section-title": "Öffnen in", @@ -225,7 +195,6 @@ "open-section": "Sektion öffnen", "edit-section": "Bearbeiten", "expand-collapse": "Aus- / Einklappen", - "move-section": "Verschieben nach", "remove-section": "Entfernen" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "um unbeabsichtigte Folgen zu vermeiden." }, "export": { - "export-title": "Konfiguration exportieren", "copy-clipboard-btn": "In Zwischenablage kopieren", "copy-clipboard-tooltip": "Applikationskonfiguration als YAML in Zwischenablage kopieren", "download-file-btn": "Datei herunterladen", @@ -285,7 +253,6 @@ }, "widgets": { "general": { - "loading": "Lade...", "show-more": "Details", "cpu-details": "Details CPU", "mem-details": "Details Arbeitsspeicher", @@ -326,13 +293,9 @@ "good-service-rest": "Guter Service auf allen anderen Leitungen" }, "synology-download": { - "download": "Download", - "upload": "Upload", "downloaded": "Heruntergeladen", "uploaded": "Hochgeladen", - "remaining": "Verbleibend", - "up": "Hoch", - "down": "Runter" + "remaining": "Verbleibend" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/el.json b/src/assets/locales/el.json index 3b54b608..d1304ece 100644 --- a/src/assets/locales/el.json +++ b/src/assets/locales/el.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Πληροφορίες Εφαρμογής", - "support-dashy-link": "Σελίδα συνεισφορών", "report-bug": "Αναφέρετε Πρόβλημα", "report-bug-description": "Αν πιστεύετε ότι έχετε βρει κάποιο σφάλμα, τότε παρακαλείστε να", "report-bug-link": "υποβάλετε μια Αναφορά", @@ -56,14 +55,12 @@ "privacy-and-security": "Απόρρητο & Ασφάλεια", "privacy-and-security-l1": "Για μια ανάλυση του τρόπου διαχείρισης των δεδομένων σας από τον Dashy, ανατρέξτε στην", "privacy-and-security-privacy-policy": "Πολιτική Απορρήτου", - "app-info.privacy-and-security-advice": "Για συμβουλές σχετικά με την ασφάλεια του dashboard σας, μπορείτε να ανατρέξετε στα", - "app-info.privacy-and-security-advice-link": "Εγγραφα Διαχείρισης", + "privacy-and-security-advice": "Για συμβουλές σχετικά με την ασφάλεια του dashboard σας, μπορείτε να ανατρέξετε στα", + "privacy-and-security-advice-link": "Εγγραφα Διαχείρισης", "privacy-and-security-security-issue": "Αν έχετε εντοπίσει κάποιο πιθανό ζήτημα ασφαλείας, αναφέρετέ το ακολουθώντας την", "privacy-and-security-security-policy": "Πολιτική Ασφαλείας", "license": "Αδεια", "license-under": "Άδεια χρήσης σύμφωνα με την", - "licence-third-party": "Για άδειες χρήσης δομοστοιχείων λογισμικού τρίτων, ανατρέξτε στο", - "licence-third-party-link": "Νομικό Πλαίσιο", "list-contributors": "Για την πλήρη λίστα των συντελεστών και ευχαριστιών, ανατρέξτε στις", "list-contributors-link": "Ευχαριστίες", "version": "Εκδοση" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Θα πρέπει πρώτα να δημιουργήσετε αντίγραφα ασφαλείας για τυχούσες αλλαγές που έχετε κάνει τοπικά, αν θέλετε να είναι διαθέσιμες για μελλοντική χρήση.", "reset-config-msg-l3": "Είστε βέβαιοι ότι θέλετε να συνεχίσετε;", "data-cleared-msg": "Τα δεδομένα εκκαθαρίστηκαν με επιτυχία", - "actions-label": "Ενέργειες", - "copy-config-label": "Αντιγραφή διαμόρφωσης", "data-copied-msg": "Η διαμόρφωση έχει αντιγραφεί στο πρόχειρο", "reset-config-label": "Επαναφορά Διαμόρφωσης", "css-save-btn": "Αποθήκευση Αλλαγών", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Αποσύνδεση", "sign-in-tooltip": "Σύνδεση", "sign-in-welcome": "Γεια σου {username}!", - "hide": "Απόκρυψη", - "open": "Ανοιγμα" + "hide": "Απόκρυψη" }, "updates": { "app-version-note": "Dashy έκδοση", @@ -153,23 +147,11 @@ "reset-toast": "Τα προσαρμοσμένα χρώματα για το {theme} καταργήθηκαν" }, "config-editor": { - "save-location-label": "Αποθήκευση Τοποθεσίας", - "location-local-label": "Εφαρμογή Τοπικά", - "location-disk-label": "Εγγραφή Αλλαγών στο Αρχείο Διαμόρφωσης", - "save-button": "Αποθήκευση Αλλαγών", "preview-button": "Προεπισκόπηση Αλλαγών", "valid-label": "Η Διαμόρφωση είναι Εγκυρη", - "status-success-msg": "Η Εργασία Ολοκληρώθηκε", - "status-fail-msg": "Η Εργασία Απέτυχε", "success-msg-disk": "Το αρχείο διαμόρφωσης εγγράφηκε στον δίσκο επιτυχώς", "success-msg-local": "Οι τοπικές αλλαγές αποθηκεύτηκαν με επιτυχία", - "success-note-l1": "Η εφαρμογή θα πρέπει να χτιστεί εκ νέου, αυτομάτως.", - "success-note-l2": "Αυτό μπορεί να διαρκέσει έως και ένα λεπτό.", - "success-note-l3": "Θα χρειαστεί να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές.", - "error-msg-save-mode": "Παρακαλώ επιλέξτε έναν τρόπο αποθήκευσης: Τοπικά ή σε Αρχείο", "error-msg-cannot-save": "Παρουσιάστηκε σφάλμα κατά την αποθήκευση των παραμέτρων", - "error-msg-bad-json": "Σφάλμα στο JSON: Μη έγκυρη μορφοποίηση", - "warning-msg-validation": "Προειδοποίηση επικύρωσης", "not-admin-note": "Δεν μπορείτε να γράψετε αλλαγές στον δίσκο, επειδή δεν έχετε συνδεθεί ως διαχειριστής" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Για περισσότερες πληροφορίες, ανατρέξτε στα", "intro-docs": "έγγραφα", "backup-title-setup": "Δημιουργία αντιγράφου ασφαλείας", - "backup-title-update": "Ενημέρωση αντιγράφου ασφαλείας", "password-label-setup": "Επιλέξτε έναν κωδικό πρόσβασης", "password-label-update": "Εισάγετε τον κωδικό σας", "backup-button-setup": "Αντίγραφο Ασφαλείας", @@ -196,17 +177,6 @@ "backup-success-msg": "Ολοκληρώθηκε Επιτυχώς", "restore-success-msg": "Η Διαμόρφωση Αποκαταστάθηκε Επιτυχώς" }, - "menu": { - "open-section-title": "Ανοιγμα σε", - "sametab": "Τρέχουσα Καρτέλα", - "newtab": "Νέα Καρτέλα", - "modal": "Αναδυόμενο Παράθυρο", - "workspace": "Προβολή Χώρου Εργασίας", - "options-section-title": "Επιλογές", - "edit-item": "Επεξεργασία", - "move-item": "Αντιγραφή ή Μετακίνηση", - "remove-item": "Αφαίρεση" - }, "context-menus": { "item": { "open-section-title": "Ανοιγμα Σε", @@ -225,7 +195,6 @@ "open-section": "Ανοιγμα ενότητας", "edit-section": "Επεξεργασία", "expand-collapse": "Ανάπτυξη / Σύμπτυξη", - "move-section": "Μετακίνηση σε", "remove-section": "Αφαίρεση" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "προς αποφυγή ανεπιθύμητων συνεπειών." }, "export": { - "export-title": "Εξαγωγή διαμόρφωσης", "copy-clipboard-btn": "Αντιγραφή στο πρόχειρο", "copy-clipboard-tooltip": "Αντιγράψτε όλες τις ρυθμίσεις της εφαρμογής στο πρόχειρο του συστήματος, σε YAML μορφή", "download-file-btn": "Λήψη ως Αρχείο", @@ -285,7 +253,6 @@ }, "widgets": { "general": { - "loading": "Γίνεται φόρτωση...", "show-more": "Ανάπτυξη Λεπτομερειών", "show-less": "Εμφάνιση Λιγότερων", "open-link": "Συνέχιση Ανάγνωσης" diff --git a/src/assets/locales/en-GB.json b/src/assets/locales/en-GB.json index eace799e..978912db 100644 --- a/src/assets/locales/en-GB.json +++ b/src/assets/locales/en-GB.json @@ -1,7 +1,6 @@ { "app-info": { - "license": "Licence", - "licence-third-party": "For licences for third-party modules, please see" + "license": "Licence" }, "theme-maker": { "reset-toast": "Custom Colours for {theme} Removed" diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index ac6217c8..311de5b6 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -52,7 +52,6 @@ }, "app-info": { "title": "App Info", - "support-dashy-link": "Contributions page", "sponsor-heading": "Sponsor Dashy", "sponsor-l1-prefix": "Enjoying Dashy? Consider", "sponsor-l1-link": "sponsoring me on GitHub", @@ -74,8 +73,6 @@ "privacy-and-security-security-policy": "Security Policy", "license": "License", "license-under": "Licensed under", - "licence-third-party": "For licenses for third-party modules, please see", - "licence-third-party-link": "Legal", "list-contributors": "For the full list of contributors and thanks, see", "list-contributors-link": "Credits", "version": "Version" @@ -102,8 +99,6 @@ "reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.", "reset-config-msg-l3": "Are you sure you want to proceed?", "data-cleared-msg": "Data cleared successfully", - "actions-label": "Actions", - "copy-config-label": "Copy Config", "data-copied-msg": "Config has been copied to clipboard", "reset-config-label": "Reset Config", "css-save-btn": "Save Changes", @@ -162,7 +157,6 @@ "sign-in-welcome": "Hello {username}!", "please-login": "Log in for full access", "hide": "Hide", - "open": "Open", "options-title": "Options", "options-tooltip": "Options" }, @@ -256,23 +250,11 @@ "reset-toast": "Custom Colors for {theme} Removed" }, "config-editor": { - "save-location-label": "Save Location", - "location-local-label": "Apply Locally", - "location-disk-label": "Write Changes to Config File", - "save-button": "Save Changes", "preview-button": "Preview Changes", "valid-label": "Config is Valid", - "status-success-msg": "Task Complete", - "status-fail-msg": "Task Failed", "success-msg-disk": "Config file written to disk successfully", "success-msg-local": "Local changes saved successfully", - "success-note-l1": "You will need to refresh the page for changes to take effect.", - "success-note-l2": "", - "success-note-l3": "", - "error-msg-save-mode": "Please select a Save Mode: Local or File", "error-msg-cannot-save": "An error occurred saving config", - "error-msg-bad-json": "Error in JSON, possibly malformed", - "warning-msg-validation": "Validation Warning", "not-admin-note": "You cannot write changes to disk because you are not logged in as an administrator", "preview-applied-msg": "Config previewed. Close this modal to see the changes.", "reset-confirm-msg": "Discard all unsaved changes and revert to the original config?", @@ -301,7 +283,6 @@ "intro-l3": "For more info, please see the", "intro-docs": "docs", "backup-title-setup": "Make a Backup", - "backup-title-update": "Update Backup", "password-label-setup": "Choose a Password", "password-label-update": "Enter your Password", "backup-button-setup": "Backup", @@ -318,17 +299,6 @@ "backup-success-msg": "Completed Successfully", "restore-success-msg": "Config Restored Successfully" }, - "menu": { - "open-section-title": "Open In", - "sametab": "Current Tab", - "newtab": "New Tab", - "modal": "Pop-Up Modal", - "workspace": "Workspace View", - "options-section-title": "Options", - "edit-item": "Edit", - "move-item": "Copy or Move", - "remove-item": "Remove" - }, "context-menus": { "item": { "open-section-title": "Open In", @@ -348,7 +318,6 @@ "open-section": "Open Section", "edit-section": "Edit", "expand-collapse": "Expand / Collapse", - "move-section": "Move To", "remove-section": "Remove", "section-options": "Section Options" } @@ -423,7 +392,6 @@ "warning-msg-l3": "to avoid unintended consequences." }, "export": { - "export-title": "Export Config", "copy-clipboard-btn": "Copy to Clipboard", "copy-clipboard-tooltip": "Copy all app config to system clipboard, in YAML format", "download-file-btn": "Download as File", @@ -461,7 +429,6 @@ }, "widgets": { "general": { - "loading": "Loading...", "show-more": "Expand Details", "cpu-details": "CPU Details", "mem-details": "Memory Details", @@ -502,13 +469,9 @@ "good-service-rest": "Good Service on all other Lines" }, "synology-download": { - "download": "Download", - "upload": "Upload", "downloaded": "Downloaded", "uploaded": "Uploaded", - "remaining": "Remaining", - "up": "Up", - "down": "Down" + "remaining": "Remaining" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/es.json b/src/assets/locales/es.json index 7a52465a..2fd486a9 100644 --- a/src/assets/locales/es.json +++ b/src/assets/locales/es.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Es recomedable realizar primero una copia de seguridad de los cambios hecho en su configuración local, por si los necesitas usar en el futuro.", "reset-config-msg-l3": "¿Estás seguro de que quieres continuar?", "data-cleared-msg": "Datos eliminados correctamente", - "actions-label": "Acciones", - "copy-config-label": "Copiar la Configuración", "data-copied-msg": "La configuración ha sido copiada al portapapeles", "reset-config-label": "Resetear la Configuración", "css-save-btn": "Guardar Cambios", @@ -110,23 +108,11 @@ "reset-toast": "Colores personalizados de {theme} borrados" }, "config-editor": { - "save-location-label": "Guardar Ubicación", - "location-local-label": "Aplicar localmente", - "location-disk-label": "Guardar cambion en el fichero de configuración", - "save-button": "Guardar Cambios", "preview-button": "Previsualizar los cambios", "valid-label": "La configuración es válida", - "status-success-msg": "Tarea Completada", - "status-fail-msg": "La Tarea ha fallado", "success-msg-disk": "Fichero de configuración guardado en disco correctamente", "success-msg-local": "Los cambios locales se han guardado correctamente", - "success-note-l1": "La app se recompilará automáticamente.", - "success-note-l2": "Esto puede llevar algo más de un minuto.", - "success-note-l3": "Es necesario refrescar la página para que los cambios tengan efecto.", - "error-msg-save-mode": "Por favor selecciona un modo de Guardar: Local o Fichero", "error-msg-cannot-save": "Se ha producido un error al guardar la configuración", - "error-msg-bad-json": "Error en el JSON, probablemente esté mal construído", - "warning-msg-validation": "Advertencia de validación", "not-admin-note": "No puedes guardar los cambios en el disco, porque no estás conectado como un Administrador" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Todos los datos están cifrados de extremo a extremo con AES, la Contraseña elegida es la clave de cifrado.", "intro-l3": "Para más información, por favor consulta", "backup-title-setup": "Hacer una copia de seguridad", - "backup-title-update": "Restaurar copia de seguridad", "password-label-setup": "Selecciona una Contraseña", "password-label-update": "Escribe tu Contraseña", "backup-button-setup": "Copia de Seguridad", @@ -152,17 +137,6 @@ "backup-success-msg": "Completado con éxito", "restore-success-msg": "Configuración restaurada con éxito" }, - "menu": { - "open-section-title": "Abrir en", - "sametab": "Abrir en la pestaña actual", - "newtab": "Abrir en una nueva pestaña", - "modal": "Abrir en un Pop-Up", - "workspace": "Abrir en el espacio de trabajo", - "options-section-title": "Opciones", - "edit-item": "Editar", - "move-item": "Copiar o Mover", - "remove-item": "Eliminar" - }, "context-menus": { "item": { "open-section-title": "Abrir en", @@ -181,7 +155,6 @@ "open-section": "Abrir la sección", "edit-section": "Editar", "expand-collapse": "Expandir / Colapsar", - "move-section": "Mover a", "remove-section": "Eliminar" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "para evitar consecuencias imprevistas." }, "export": { - "export-title": "Exportar configuración", "copy-clipboard-btn": "Copiar al portapapeles", "copy-clipboard-tooltip": "Copiar toda la configuración de la aplicación al portapapeles del sistema, en formato YAML", "download-file-btn": "Descargar como archivo", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "Cargando...", "show-more": "Ampliar detalles", "show-less": "Mostrar menos", "open-link": "Continuar leyendo" @@ -280,13 +251,9 @@ "good-service-rest": "Buen servicio en todas las demás líneas" }, "synology-download": { - "download": "Descargar", - "upload": "Cargar", "downloaded": "Descargado", "uploaded": "Cargado", - "remaining": "Restante", - "up": "Levantado", - "down": "Caído" + "remaining": "Restante" }, "gluetun-status": { "vpn-ip": "IP de la VPN", diff --git a/src/assets/locales/fr.json b/src/assets/locales/fr.json index 443f8a6d..03202b78 100644 --- a/src/assets/locales/fr.json +++ b/src/assets/locales/fr.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Informations sur l'application", - "support-dashy-link": "« Contributions »", "report-bug": "Signaler un bug", "report-bug-description": "Si vous pensez avoir trouvé un bug, alors s'il vous plaît, ", "report-bug-link": "signalez-le", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "politique en matière de sécurité", "license": "Licence", "license-under": "Sous licence", - "licence-third-party": "Pour les licences des modules tiers, veuillez consulter", - "licence-third-party-link": "le document juridique", "list-contributors": "Pour la liste complète des contributeurs et les remerciements, voir", "list-contributors-link": "les crédits", "version": "Version" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Vous devez d'abord sauvegarder toutes les modifications locales si vous souhaitez les utiliser à l'avenir.", "reset-config-msg-l3": "Êtes-vous sûr de vouloir continuer ?", "data-cleared-msg": "Données effacées avec succès", - "actions-label": "Actions", - "copy-config-label": "Copier la configuration", "data-copied-msg": "La configuration a été copiée dans le presse-papiers", "reset-config-label": "Réinitialiser la configuration", "css-save-btn": "Enregistrer", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Déconnexion", "sign-in-tooltip": "Connexion", "sign-in-welcome": "Bonjour {username} !", - "hide": "Masquer", - "open": "Ouvrir" + "hide": "Masquer" }, "updates": { "app-version-note": "Version de Dashy", @@ -153,23 +147,11 @@ "reset-toast": "Couleurs personnalisées pour {theme} supprimées" }, "config-editor": { - "save-location-label": "Enregistrer localement", - "location-local-label": "Appliquer localement", - "location-disk-label": "Appliquer dans le fichier de configuration", - "save-button": "Enregistrer", "preview-button": "Prévisualiser les modifications", "valid-label": "La configuration est valide", - "status-success-msg": "Tâche terminée", - "status-fail-msg": "Échec de la tâche", "success-msg-disk": "Le fichier de configuration est écrit avec succès sur le disque", "success-msg-local": "Les modifications locales ont bien été enregistrées", - "success-note-l1": "L'application devrait se reconstruire automatiquement.", - "success-note-l2": "Cela peut prendre une minute.", - "success-note-l3": "Vous devrez actualiser la page pour que les modifications prennent effet.", - "error-msg-save-mode": "Veuillez sélectionner un mode d'enregistrement : Local ou Fichier", "error-msg-cannot-save": "Une erreur s'est produite lors de l'enregistrement de la configuration", - "error-msg-bad-json": "Erreur dans le fichier JSON, il est peut-être mal formé", - "warning-msg-validation": "Attention", "not-admin-note": "Vous ne pouvez pas écrire les modifications sur le disque, car vous n'êtes pas connecté en tant qu'administrateur" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Pour plus d'informations, veuillez consulter la", "intro-docs": "documentation", "backup-title-setup": "Sauvegarde", - "backup-title-update": "Mettre à jour la sauvegarde", "password-label-setup": "Choisissez un mot de passe", "password-label-update": "Entrer votre mot de passe", "backup-button-setup": "Sauvegarder", @@ -196,17 +177,6 @@ "backup-success-msg": "Sauvegarde effectuée avec succès", "restore-success-msg": "Configuration restaurée avec succès" }, - "menu": { - "open-section-title": "Ouvrir ...", - "sametab": "Ouvrir dans l'onglet actuel", - "newtab": "Ouvrir dans un nouvel onglet", - "modal": "Ouvrir en mode fenêtré", - "workspace": "Ouvrir en plein écran", - "options-section-title": "Options", - "edit-item": "Modifier", - "move-item": "Copier et Déplacer", - "remove-item": "Supprimer" - }, "context-menus": { "item": { "open-section-title": "Ouvrir ...", @@ -225,7 +195,6 @@ "open-section": "Ouvrir", "edit-section": "Modifier", "expand-collapse": "Développer / Réduire", - "move-section": "Déplacer vers", "remove-section": "Supprimer" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "pour éviter des conséquences inattendues." }, "export": { - "export-title": "Exporter la configuration", "copy-clipboard-btn": "Copier dans le presse-papiers", "copy-clipboard-tooltip": "Copier la configuration complète de l'application sur votre appareil dans un fichier YAML", "download-file-btn": "Télécharger le fichier", @@ -285,7 +253,6 @@ }, "widgets": { "general": { - "loading": "Chargement ...", "show-more": "Développer les détails", "show-less": "Montrer moins", "open-link": "Lire la suite" @@ -324,13 +291,9 @@ "good-service-rest": "Bon service sur toutes les autres lignes" }, "synology-download": { - "download": "Téléchargement", - "upload": "Envoi", "downloaded": "Téléchargé", "uploaded": "Envoyer", - "remaining": "Restant", - "up": "Up", - "down": "Down" + "remaining": "Restant" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/gl.json b/src/assets/locales/gl.json index 91514d69..a924b137 100644 --- a/src/assets/locales/gl.json +++ b/src/assets/locales/gl.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Información da aplicación", - "support-dashy-link": "Contribucións", "report-bug": "Informar dun erro", "report-bug-description": "Se atopaches un erro, por favor", "report-bug-link": "abre unha incidencia", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "Política de seguridade", "license": "Licenza", "license-under": "Licenciado baixo", - "licence-third-party": "Para licenzas de módulos de terceiros, consulta", - "licence-third-party-link": "Legal", "list-contributors": "Para a lista completa de contribuíntes e agradecementos, consulta", "list-contributors-link": "Créditos", "version": "Versión" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Deberías facer unha copia de seguridade de calquera cambio que fixeches localmente, se queres usalos no futuro.", "reset-config-msg-l3": "Seguro que queres continuar?", "data-cleared-msg": "Datos eliminados correctamente", - "actions-label": "Accións", - "copy-config-label": "Copiar configuración", "data-copied-msg": "A configuración foi copiada ao portapapeis", "reset-config-label": "Restablecer configuración", "css-save-btn": "Gardar cambios", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Pechar sesión", "sign-in-tooltip": "Iniciar sesión", "sign-in-welcome": "Ola {username}!", - "hide": "Ocultar", - "open": "Abrir" + "hide": "Ocultar" }, "updates": { "app-version-note": "Versión de Dashy", @@ -153,23 +147,11 @@ "reset-toast": "Eliminadas as cores personalizadas de {theme}" }, "config-editor": { - "save-location-label": "Localización de gardado", - "location-local-label": "Aplicar localmente", - "location-disk-label": "Gardar cambios no ficheiro de configuración", - "save-button": "Gardar cambios", "preview-button": "Previsualizar cambios", "valid-label": "A configuración é válida", - "status-success-msg": "Tarefa completada", - "status-fail-msg": "Fallou a tarefa", "success-msg-disk": "Ficheiro de configuración gardado no disco correctamente", "success-msg-local": "Cambios locais gardados correctamente", - "success-note-l1": "A aplicación debe reconstruírse automaticamente.", - "success-note-l2": "Isto pode levar ata un minuto.", - "success-note-l3": "Terás que actualizar a páxina para que se apliquen os cambios.", - "error-msg-save-mode": "Selecciona un modo de gardado: Local ou Ficheiro", "error-msg-cannot-save": "Produciuse un erro ao gardar a configuración", - "error-msg-bad-json": "Erro en JSON, posiblemente malformado", - "warning-msg-validation": "Aviso de validación", "not-admin-note": "Non podes gardar cambios no disco porque non iniciaches sesión como administrador" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Para obter máis información, consulta a", "intro-docs": "documentación", "backup-title-setup": "Facer unha copia de seguridade", - "backup-title-update": "Actualizar copia de seguridade", "password-label-setup": "Escolle un contrasinal", "password-label-update": "Introduce o teu contrasinal", "backup-button-setup": "Copia de seguridade", @@ -196,17 +177,6 @@ "backup-success-msg": "Completado con éxito", "restore-success-msg": "Configuración restaurada con éxito" }, - "menu": { - "open-section-title": "Abrir en", - "sametab": "Lapela actual", - "newtab": "Nova lapela", - "modal": "Diálogo emerxente", - "workspace": "Vista do espazo de traballo", - "options-section-title": "Opcións", - "edit-item": "Editar", - "move-item": "Copiar ou mover", - "remove-item": "Eliminar" - }, "context-menus": { "item": { "open-section-title": "Abrir en", @@ -225,7 +195,6 @@ "open-section": "Abrir sección", "edit-section": "Editar", "expand-collapse": "Expandir / Colapsar", - "move-section": "Mover a", "remove-section": "Eliminar" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "para evitar consecuencias non desexadas." }, "export": { - "export-title": "Exportar configuración", "copy-clipboard-btn": "Copiar ao portapapeis", "copy-clipboard-tooltip": "Copiar toda a configuración da aplicación ao portapapeis do sistema, en formato YAML", "download-file-btn": "Descargar como ficheiro", @@ -285,7 +253,6 @@ }, "widgets": { "general": { - "loading": "Cargando...", "show-more": "Expandir detalles", "show-less": "Amosar menos", "open-link": "Continuar lendo" @@ -324,13 +291,9 @@ "good-service-rest": "Bos servizos en todas as outras liñas" }, "synology-download": { - "download": "Descargar", - "upload": "Subir", "downloaded": "Descargado", "uploaded": "Subido", - "remaining": "Restante", - "up": "Arriba", - "down": "Abaixo" + "remaining": "Restante" }, "gluetun-status": { "vpn-ip": "IP da VPN", diff --git a/src/assets/locales/hi.json b/src/assets/locales/hi.json index 71a91685..339e1ff6 100644 --- a/src/assets/locales/hi.json +++ b/src/assets/locales/hi.json @@ -50,8 +50,6 @@ "reset-config-msg-l2": "यदि आप भविष्य में उनका उपयोग करना चाहते हैं, तो आपको पहले स्थानीय रूप से किए गए किसी भी परिवर्तन का बैकअप लेना चाहिए।", "reset-config-msg-l3": "क्या आप सुनिश्चित रूप से आगे बढ़ना चाहते हैं?", "data-cleared-msg": "डेटा सफलतापूर्वक साफ़ किया गया", - "actions-label": "कार्रवाई", - "copy-config-label": "कॉन्फिग कॉपी करें", "data-copied-msg": "कॉन्फिग को क्लिपबोर्ड पर कॉपी कर दिया गया है", "reset-config-label": "कॉन्फ़िगर रीसेट करें", "css-save-btn": "परिवर्तनों को सुरक्षित करें", @@ -107,22 +105,10 @@ "reset-toast": "{थीम} के लिए कस्टम रंग निकाले गए" }, "config-editor": { - "save-location-label": "स्थान सहेजें", - "location-local-label": "स्थानीय रूप से आवेदन करें", - "location-disk-label": "कॉन्फ़िग फ़ाइल में परिवर्तन लिखें", - "save-button": "परिवर्तनों को सुरक्षित करें", "valid-label": "कॉन्फिग मान्य है", - "status-success-msg": "कार्य पूर्ण", - "status-fail-msg": "कार्य विफल", "success-msg-disk": "डिस्क पर सफलतापूर्वक लिखी गई कॉन्फ़िग फ़ाइल", "success-msg-local": "स्थानीय परिवर्तन सफलतापूर्वक सहेजे गए", - "success-note-l1": "ऐप को स्वचालित रूप से पुनर्निर्माण करना चाहिए।", - "success-note-l2": "इसमें एक मिनट तक का समय लग सकता है।", - "success-note-l3": "परिवर्तनों को प्रभावी करने के लिए आपको पृष्ठ को रीफ्रेश करना होगा।", - "error-msg-save-mode": "कृपया एक सहेजें मोड चुनें: स्थानीय या फ़ाइल", "error-msg-cannot-save": "कॉन्फ़िगरेशन सहेजने में त्रुटि हुई", - "error-msg-bad-json": "JSON में त्रुटि, संभवतः विकृत", - "warning-msg-validation": "सत्यापन चेतावनी", "not-admin-note": "आप डिस्क में बदला हुआ नहीं लिख सकते, क्योंकि आप एक व्यवस्थापक के रूप में लॉग इन नहीं हैं" }, "cloud-sync": { @@ -131,7 +117,6 @@ "intro-l2": "कुंजी के रूप में आपके पासवर्ड का उपयोग करते हुए, सभी डेटा एईएस के साथ पूरी तरह से एंड-टू-एंड एन्क्रिप्टेड है।", "intro-l3": "अधिक जानकारी के लिए, कृपया देखें", "backup-title-setup": "एक बैकअप बनाओ", - "backup-title-update": "बैकअप अपडेट करें", "password-label-setup": "एक पासवर्ड चुनें", "password-label-update": "अपना कूटशब्द भरें", "backup-button-setup": "बैकअप", @@ -147,11 +132,5 @@ "backup-error-password": "गलत पासवर्ड। कृपया अपना वर्तमान पासवर्ड दर्ज करें।", "backup-success-msg": "सफलतापुर्वक पूरा", "restore-success-msg": "कॉन्फ़िगरेशन सफलतापूर्वक पुनर्स्थापित किया गया" - }, - "menu": { - "sametab": "वर्तमान टैब में खोलें", - "newtab": "वेब टेब में खोलें", - "modal": "पॉप-अप मोडल में खोलें", - "workspace": "कार्यक्षेत्र दृश्य में खोलें" } } diff --git a/src/assets/locales/hu.json b/src/assets/locales/hu.json index e9a3aaac..618f84bb 100644 --- a/src/assets/locales/hu.json +++ b/src/assets/locales/hu.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "App Info", - "support-dashy-link": "közreműködők oldalát", "report-bug": "Hiba bejelentése", "report-bug-description": "Ha úgy gondolod, hogy hibát találtál, kérlek", "report-bug-link": "jelentsd be itt", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "biztonsági szabályzatunk alapján", "license": "Licenc", "license-under": "Licenc típusa:", - "licence-third-party": "A harmadik féltől származó modulok licenceiért látogasd meg a", - "licence-third-party-link": "jogi oldalunkat", "list-contributors": "A teljes közreműködői lista és köszönetnyilvánítás itt található:", "list-contributors-link": "Készítők", "version": "Verzió" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Mentsd el a helyi változtatásokat, ha később is használni akarod őket", "reset-config-msg-l3": "Biztosan folytatod?", "data-cleared-msg": "Az adatok sikeresen törölve", - "actions-label": "Műveletek", - "copy-config-label": "Beállítások másolása", "data-copied-msg": "A konfiguráció a vágólapra másolva", "reset-config-label": "Beállítások visszaállítása", "css-save-btn": "Változtatások mentése", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Kijelentkezés", "sign-in-tooltip": "Bejelentkezés", "sign-in-welcome": "Szia, {username}!", - "hide": "Elrejtés", - "open": "Megnyitás" + "hide": "Elrejtés" }, "updates": { "app-version-note": "Dashy verzió", @@ -153,23 +147,11 @@ "reset-toast": "A(z) {theme} egyéni színei törölve" }, "config-editor": { - "save-location-label": "Mentés helye", - "location-local-label": "Helyi mentés", - "location-disk-label": "Változások mentése a konfigurációs fájlba", - "save-button": "Változtatások mentése", "preview-button": "Változtatások előnézete", "valid-label": "A konfiguráció érvényes", - "status-success-msg": "Feladat sikeresen befejezve", - "status-fail-msg": "A feladat sikertelen volt", "success-msg-disk": "A konfigurációs fájl sikeresen mentve a lemezre", "success-msg-local": "A helyi változtatások sikeresen elmentve", - "success-note-l1": "A változtatások érvényesítéséhez frissítened kell az oldalt.", - "success-note-l2": "", - "success-note-l3": "", - "error-msg-save-mode": "Válaszd ki a mentés módját: helyi vagy fájlba mentés", "error-msg-cannot-save": "Hiba történt a konfiguráció mentése közben", - "error-msg-bad-json": "Hibás JSON-formátum, valószínűleg rosszul van megadva", - "warning-msg-validation": "Érvényesítési figyelmeztetés", "not-admin-note": "Nem írhatsz a lemezre, mert nem rendszergazdaként vagy bejelentkezve" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "További információért lásd a", "intro-docs": "dokumentációt", "backup-title-setup": "Biztonsági mentés készítése", - "backup-title-update": "Mentés frissítése", "password-label-setup": "Válassz jelszót", "password-label-update": "Add meg a jelszavad", "backup-button-setup": "Biztonsági mentés", @@ -196,17 +177,6 @@ "backup-success-msg": "Sikeresen befejezve", "restore-success-msg": "A beállítások sikeresen visszaállítva" }, - "menu": { - "open-section-title": "Megnyitás itt", - "sametab": "Jelenlegi fülön", - "newtab": "Új fülön", - "modal": "Felugró ablakban", - "workspace": "Munkaterület nézet", - "options-section-title": "Beállítások", - "edit-item": "Szerkesztés", - "move-item": "Másolás vagy áthelyezés", - "remove-item": "Eltávolítás" - }, "context-menus": { "item": { "open-section-title": "Megnyitás itt", @@ -225,7 +195,6 @@ "open-section": "Szakasz megnyitása", "edit-section": "Szerkesztés", "expand-collapse": "Kinyitás / Összecsukás", - "move-section": "Áthelyezés ide", "remove-section": "Eltávolítás" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "hogy elkerüld a nem kívánt következményeket." }, "export": { - "export-title": "Konfiguráció exportálása", "copy-clipboard-btn": "Másolás vágólapra", "copy-clipboard-tooltip": "Az összes beállítás másolása a vágólapra YAML formátumban", "download-file-btn": "Letöltés fájlként", @@ -293,7 +261,6 @@ }, "widgets": { "general": { - "loading": "Betöltés", "show-more": "Részletek megjelenítése", "cpu-details": "CPU adatai", "mem-details": "Mamória adatai", @@ -334,13 +301,9 @@ "good-service-rest": "Minden más vonalon zavartalan a közlekedés" }, "synology-download": { - "download": "Letöltés", - "upload": "Feltöltés", "downloaded": "Letöltve", "uploaded": "Feltöltve", - "remaining": "Hátralévő", - "up": "Feltöltés", - "down": "Letöltés" + "remaining": "Hátralévő" }, "gluetun-status": { "vpn-ip": "VPN IP-cím", diff --git a/src/assets/locales/it.json b/src/assets/locales/it.json index 54ca10e0..0dcd6b16 100644 --- a/src/assets/locales/it.json +++ b/src/assets/locales/it.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Dovresti prima eseguire il backup di tutte le modifiche apportate localmente, se desideri utilizzarle in futuro.", "reset-config-msg-l3": "Sei sicuro di voler procedere?", "data-cleared-msg": "Dati cancellati con successo", - "actions-label": "Azioni", - "copy-config-label": "Copia configurazione", "data-copied-msg": "La configurazione è stata copiata negli appunti", "reset-config-label": "Ripristina configurazione", "css-save-btn": "Salvare le modifiche", @@ -110,23 +108,11 @@ "reset-toast": "Rimossi Colori personalizzati per {theme}" }, "config-editor": { - "save-location-label": "Posizione del salvataggio", - "location-local-label": "Salvataggio Locale", - "location-disk-label": "Salva le modifiche nel file di configurazione", - "save-button": "Salvare le modifiche", "preview-button": "Preview Changes", "valid-label": "La configurazione è valida", - "status-success-msg": "Attività completata", - "status-fail-msg": "Attività fallita", "success-msg-disk": "File di configurazione scritto correttamente su disco", "success-msg-local": "Modifiche locali salvate correttamente", - "success-note-l1": "L'app dovrebbe ricompilarsi automaticamente.", - "success-note-l2": "Questa operazione potrebbe richiedere fino a un minuto.", - "success-note-l3": "Sarà necessario aggiornare la pagina affinché le modifiche abbiano effetto.", - "error-msg-save-mode": "Seleziona una modalità di salvataggio: Localmente o in un File", "error-msg-cannot-save": "Si è verificato un errore durante il salvataggio della configurazione", - "error-msg-bad-json": "Errore nella struttura JSON, probabilmente non formattata correttamente", - "warning-msg-validation": "Avviso di convalida", "not-admin-note": "Non puoi scrivere le modifiche su disco, perché non sei autenticato come amministratore" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Tutti i dati sono completamente crittografati end-to-end con AES, utilizzando la tua password come chiave.", "intro-l3": "Per maggiori informazioni, vedere il", "backup-title-setup": "Fai un backup", - "backup-title-update": "Aggiorna backup", "password-label-setup": "Scegli una password", "password-label-update": "Inserisci la tua password", "backup-button-setup": "Backup", @@ -152,17 +137,6 @@ "backup-success-msg": "Completato con successo", "restore-success-msg": "Configurazione ripristinata con successo" }, - "menu": { - "open-section-title": "Open In", - "sametab": "Tab Attuale", - "newtab": "Nuovo Tab", - "modal": "Pop-Up", - "workspace": "Workspace", - "options-section-title": "Opzioni", - "edit-item": "Edita", - "move-item": "Copia/Sposta", - "remove-item": "Rimuovi" - }, "context-menus": { "item": { "open-section-title": "Apri In", @@ -180,7 +154,6 @@ "section": { "open-section": "Apri Sezione", "edit-section": "Edita", - "move-section": "Sposta in", "remove-section": "Rimuovi" } }, @@ -228,7 +201,6 @@ "warning-msg-l3": "per evitare spiacevoli conseguenze." }, "export": { - "export-title": "Esporta Configurazione", "copy-clipboard-btn": "Copia negli Appunti", "copy-clipboard-tooltip": "Copia la configurazione negli appunti, in formato YAML", "download-file-btn": "Salva file", @@ -237,7 +209,6 @@ }, "widgets": { "general": { - "loading": "Caricamento...", "show-more": "Dettagli", "show-less": "Minori dettagli", "open-link": "Continua Lettura" diff --git a/src/assets/locales/ja.json b/src/assets/locales/ja.json index 69e2aef4..89916e53 100644 --- a/src/assets/locales/ja.json +++ b/src/assets/locales/ja.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "アプリ情報", - "support-dashy-link": "コントリビューションページ", "report-bug": "バグの報告", "report-bug-description": "バグを発見したと思われる場合は、Issueを立ててください: ", "report-bug-link": "Issueを立てる", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "セキュリティーポリシー", "license": "ライセンス", "license-under": "Licensed under", - "licence-third-party": "サードパーティーモジュールのライセンスは、こちらをご覧ください: ", - "licence-third-party-link": "リーガル", "list-contributors": "すべてのコントリビューターのリストは、こちらをご覧ください: ", "list-contributors-link": "クレジット", "version": "バージョン" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "後でローカルで行った変更を使用したい場合は、先にバックアップしてください。", "reset-config-msg-l3": "続行しますか?", "data-cleared-msg": "データは正常にクリアされました", - "actions-label": "アクション", - "copy-config-label": "設定のコピー", "data-copied-msg": "設定がクリップボードにコピーされました", "reset-config-label": "設定のリセット", "css-save-btn": "変更内容を保存", @@ -124,8 +119,7 @@ "sign-out-tooltip": "サインアウト", "sign-in-tooltip": "ログイン", "sign-in-welcome": "{username}さん、こんにちは!", - "hide": "閉じる", - "open": "開く" + "hide": "閉じる" }, "updates": { "app-version-note": "Dashyのバージョン", @@ -153,23 +147,11 @@ "reset-toast": "{theme}のカスタムカラーが削除されました" }, "config-editor": { - "save-location-label": "保存する場所", - "location-local-label": "ローカルで適用", - "location-disk-label": "設定ファイルに変更を書き込む", - "save-button": "変更内容を保存", "preview-button": "変更内容をプレビュー", "valid-label": "設定は有効です", - "status-success-msg": "タスクが完了しました", - "status-fail-msg": "タスクが失敗しました", "success-msg-disk": "設定ファイルがディスクに正常に書き込まれました", "success-msg-local": "ローカルの変更が正常に保存されました", - "success-note-l1": "アプリは自動的に再構築されます。", - "success-note-l2": "これには最大1分かかる場合があります。", - "success-note-l3": "変更を適用にするには、ページを更新する必要があります。", - "error-msg-save-mode": "保存モードを選択してください (ローカルまたはファイル)", "error-msg-cannot-save": "設定の保存中にエラーが発生しました", - "error-msg-bad-json": "JSONに間違いがあります。形式が間違っているかもしれません", - "warning-msg-validation": "バリデーション警告", "not-admin-note": "管理者としてログインしていないため、変更をディスクに書き込むことはできません" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "詳細については、以下をご覧ください: ", "intro-docs": "ドキュメント", "backup-title-setup": "バックアップの作成", - "backup-title-update": "バックアップの更新", "password-label-setup": "パスワード", "password-label-update": "パスワードを入力してください", "backup-button-setup": "バックアップ", @@ -196,17 +177,6 @@ "backup-success-msg": "正常に完了しました", "restore-success-msg": "設定が正常に復元されました" }, - "menu": { - "open-section-title": "開く", - "sametab": "現在のタブで開く", - "newtab": "新しいタブで開く", - "modal": "ポップアップモーダルで開く", - "workspace": "ワークスペース表示で開く", - "options-section-title": "編集", - "edit-item": "編集", - "move-item": "コピー・移動", - "remove-item": "削除" - }, "context-menus": { "item": { "open-section-title": "開く", @@ -225,7 +195,6 @@ "open-section": "セクションを開く", "edit-section": "編集", "expand-collapse": "開く / 閉じる", - "move-section": "移動", "remove-section": "削除" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "を参照し、意図しない結果にならないように注意してください。" }, "export": { - "export-title": "設定をエクスポート", "copy-clipboard-btn": "クリップボードにコピー", "copy-clipboard-tooltip": "すべてのアプリ設定をシステムのクリップボードにYAML形式でコピーします", "download-file-btn": "ファイルとしてダウンロード", @@ -285,7 +253,6 @@ }, "widgets": { "general": { - "loading": "読み込み中...", "show-more": "詳細を開く", "show-less": "詳細を閉じる", "open-link": "続きを読む" diff --git a/src/assets/locales/ko.json b/src/assets/locales/ko.json index e27be107..1c41b35e 100644 --- a/src/assets/locales/ko.json +++ b/src/assets/locales/ko.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "나중에 사용하려면 먼저 로컬에서 변경한 내용을 백업해야 합니다.", "reset-config-msg-l3": "계속하시겠습니까?", "data-cleared-msg": "데이터 삭제 성공", - "actions-label": "동작", - "copy-config-label": "설정 복사", "data-copied-msg": "설정이 클립보드에 복사되었습니다.", "reset-config-label": "설정 초기화", "css-save-btn": "변경 사항을 저장", @@ -110,23 +108,11 @@ "reset-toast": "C{theme}에 대한 사용자 정의 색상이 제거되었습니다." }, "config-editor": { - "save-location-label": "저장 위치", - "location-local-label": "로컬로 적용", - "location-disk-label": "설정 파일에 변경 사항 쓰기", - "save-button": "변경 사항 저장", "preview-button": "변경 사항 미리보기", "valid-label": "유효한 설정", - "status-success-msg": "작업 완료", - "status-fail-msg": "작업 실패", "success-msg-disk": "설정 파일이 디스크에 성공적으로 저장되었습니다.", "success-msg-local": "로컬 변경 사항이 성공적으로 저장되었습니다.", - "success-note-l1": "앱이 자동으로 다시 빌드됩니다.", - "success-note-l2": "이 작업은 최대 몇 분이 걸릴 수 있습니다.", - "success-note-l3": "변경 사항을 적용하려면 페이지를 새로고침해야 합니다.", - "error-msg-save-mode": "저장 모드를 선택하세요: 로컬 또는 파일", "error-msg-cannot-save": "설정을 저장하는 동안 오류가 발생했습니다.", - "error-msg-bad-json": "JSON 오류, 형식이 잘못되었을 수 있습니다.", - "warning-msg-validation": "유효성 검증 경고", "not-admin-note": "관리자로 로그인하지 않았기 때문에 디스크에 변경 사항을 저장할 수 없습니다." }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "모든 데이터는 암호를 키로 사용하여 AES로 종단 간 암호화됩니다..", "intro-l3": "자세한 내용은", "backup-title-setup": "백업 생성하기", - "backup-title-update": "백업 업데이트", "password-label-setup": "비밀번호 선택", "password-label-update": "비밀번호를 입력하세요", "backup-button-setup": "백업", @@ -152,17 +137,6 @@ "backup-success-msg": "성공적으로 완료됨", "restore-success-msg": "설정이 성공적으로 복원되었습니다." }, - "menu": { - "open-section-title": "열기", - "sametab": "현재 탭", - "newtab": "새로운 탭", - "modal": "팝업 모달", - "workspace": "작업 공간 보기", - "options-section-title": "옵션", - "edit-item": "수정", - "move-item": "복사 또는 이동", - "remove-item": "제거" - }, "context-menus": { "item": { "open-section-title": "열기", @@ -181,7 +155,6 @@ "open-section": "섹션 열기", "edit-section": "수정", "expand-collapse": "펼치기 / 접기", - "move-section": "이동", "remove-section": "제거" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "의도하지 않은 결과를 피하기 위해." }, "export": { - "export-title": "설정 내보내기", "copy-clipboard-btn": "클립보드에 복사", "copy-clipboard-tooltip": "모든 앱 설정을 YAML 형식으로 시스템 클립보드에 복사", "download-file-btn": "파일로 다운로드", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "로딩중...", "show-more": "더보기", "show-less": "줄이기", "open-link": "계속 읽기" @@ -280,13 +251,9 @@ "good-service-rest": "다른 모든 노선 원활히 운행 중" }, "synology-download": { - "download": "다운로드", - "upload": "업로드", "downloaded": "다운로드됨", "uploaded": "업로드됨", - "remaining": "남은 작업", - "up": "위로", - "down": "아래로" + "remaining": "남은 작업" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/ky.json b/src/assets/locales/ky.json index 451b5ab2..fa4ca4c5 100644 --- a/src/assets/locales/ky.json +++ b/src/assets/locales/ky.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Тиркеме жөнүндө маалымат", - "support-dashy-link": "Салым кошкондор бети", "report-bug": "Ката тууралуу билдирүү", "report-bug-description": "Эгер ката таптым деп ойлосоңуз", "report-bug-link": "маселе ачуу", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "Коопсуздук саясаты", "license": "Лицензия", "license-under": "Лицензиясы", - "licence-third-party": "Үчүнчү тарап модулдарынын лицензиялары жөнүндө", - "licence-third-party-link": "Юридикалык маалымат", "list-contributors": "Салым кошкондордун толук тизмеси жана ыраазычылык билдирүү үчүн", "list-contributors-link": "Кредиттер", "version": "Версия" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Жергиликтүү өзгөртүүлөрдү келечекте колдонуу үчүн алдын ала камдык көчүрмөсүн сактаңыз.", "reset-config-msg-l3": "Улантууну каалайсызбы?", "data-cleared-msg": "Дайындар ийгиликтүү тазаланды", - "actions-label": "Аракеттер", - "copy-config-label": "Конфигурацияны көчүрүү", "data-copied-msg": "Конфигурация алмашуу буферине көчүрүлдү", "reset-config-label": "Конфигурацияны баштапкы абалга келтирүү", "css-save-btn": "Өзгөртүүлөрдү сактоо", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Чыгуу", "sign-in-tooltip": "Кирүү", "sign-in-welcome": "Салам, {username}!", - "hide": "Жашыруу", - "open": "Ачуу" + "hide": "Жашыруу" }, "updates": { "app-version-note": "Dashy версиясы", @@ -153,23 +147,11 @@ "reset-toast": "{theme} темасынын өзгөчө түстөрү өчүрүлдү" }, "config-editor": { - "save-location-label": "Сактоо жайгашуусу", - "location-local-label": "Жергиликтүү (браузерде)", - "location-disk-label": "Дисктеги конфигурация файлына жазуу", - "save-button": "Өзгөртүүлөрдү сактоо", "preview-button": "Өзгөртүүлөрдү алдын ала көрүү", "valid-label": "Конфигурация туура", - "status-success-msg": "Тапшырма ийгиликтүү аткарылды", - "status-fail-msg": "Тапшырма аткарылган жок", "success-msg-disk": "Конфигурация файлы дискке ийгиликтүү жазылды", "success-msg-local": "Жергиликтүү өзгөртүүлөр сакталды", - "success-note-l1": "Өзгөртүүлөр күчүнө кириши үчүн баракты жаңыртыңыз.", - "success-note-l2": "", - "success-note-l3": "", - "error-msg-save-mode": "Сактоо режимин тандаңыз: Жергиликтүү же Диск", "error-msg-cannot-save": "Конфигурацияны сактоодо ката кетти", - "error-msg-bad-json": "JSON'до ката, форматы туура эмес болушу мүмкүн", - "warning-msg-validation": "Валидация эскертүүсү", "not-admin-note": "Дискке жазуу укугу жок, администратор катары кирүү керек" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Көбүрөөк маалымат алуу үчүн", "intro-docs": "документацияны караңыз", "backup-title-setup": "Камдык көчүрмө түзүү", - "backup-title-update": "Камдык көчүрмөнү жаңыртуу", "password-label-setup": "Сырсөз түзүңүз", "password-label-update": "Учурдагы сырсөзүңүздү киргизиңиз", "backup-button-setup": "Камдык көчүрмө түзүү", @@ -196,17 +177,6 @@ "backup-success-msg": "Ийгиликтүү аяктады", "restore-success-msg": "Конфигурация ийгиликтүү калыбына келтирилди" }, - "menu": { - "open-section-title": "Ачуу жолу", - "sametab": "Учурдагы таб", - "newtab": "Жаңы таб", - "modal": "Ачылуучу терезе", - "workspace": "Жумуш мейкиндиги көрүнүшү", - "options-section-title": "Жөндөөлөр", - "edit-item": "Түзөтүү", - "move-item": "Көчүрүү же жылдыруу", - "remove-item": "Өчүрүү" - }, "context-menus": { "item": { "open-section-title": "Ачуу жолу", @@ -225,7 +195,6 @@ "open-section": "Бөлүмдү ачуу", "edit-section": "Түзөтүү", "expand-collapse": "Жайуу / Жыйноо", - "move-section": "Жылдыруу", "remove-section": "Өчүрүү" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "керексиз кесепеттерден качуу үчүн." }, "export": { - "export-title": "Конфигурацияны экспорттоо", "copy-clipboard-btn": "Алмашуу буферине көчүрүү", "copy-clipboard-tooltip": "Конфигурацияны YAML форматында алмашуу буферине көчүрүү", "download-file-btn": "Файл катары жүктөө", @@ -293,7 +261,6 @@ }, "widgets": { "general": { - "loading": "Жүктөлүүдө...", "show-more": "Көбүрөөк чоо-жай", "cpu-details": "Процессор чоо-жайы", "mem-details": "Эстутум чоо-жайы", @@ -334,13 +301,9 @@ "good-service-rest": "Калган линияларда кызмат жакшы" }, "synology-download": { - "download": "Жүктөө", - "upload": "Жүктөө (upload)", "downloaded": "Жүктөлгөн", "uploaded": "Жүктөлгөн (upload)", - "remaining": "Калганы", - "up": "Өйдө", - "down": "Төмөн" + "remaining": "Калганы" }, "gluetun-status": { "vpn-ip": "VPN IP дареги", diff --git a/src/assets/locales/nb.json b/src/assets/locales/nb.json index 9bf6d338..0c9290a7 100644 --- a/src/assets/locales/nb.json +++ b/src/assets/locales/nb.json @@ -27,9 +27,7 @@ "logout-message": "Logget ut", "already-logged-in-title": "Allerede logget inn", "already-logged-in-text": "Du er logget inn som", - "continue-to-dashboard": "Fortsett til dashbordet", - "log-out-button": "Logg ut", - "continue-guest-button": "Fortsett som gjest" + "log-out-button": "Logg ut" }, "config": { "main-tab": "Hovedmeny", @@ -50,8 +48,6 @@ "reset-config-msg-l2": "Du bør først ta sikkerhetskopi av eventuelle endringer du har gjort lokalt, hvis du vil bruke dem i fremtiden.", "reset-config-msg-l3": "Er du sikker på at du vil fortsette?", "data-cleared-msg": "Data slettet vellykket", - "actions-label": "Handlinger", - "copy-config-label": "Kopier konfigurasjon", "data-copied-msg": "Konfig er kopiert til utklippstavlen", "reset-config-label": "Tilbakestill konfigurasjon", "css-save-btn": "Lagre endringer", @@ -107,22 +103,10 @@ "reset-toast": "Egendefinerte farger for {theme} fjernet" }, "config-editor": { - "save-location-label": "Lagre beliggenhet", - "location-local-label": "Søk lokalt", - "location-disk-label": "Skriv endringer i konfigurasjonsfil", - "save-button": "Lagre endringer", "valid-label": "Konfigurasjon er gyldig", - "status-success-msg": "Oppgaven fullført", - "status-fail-msg": "Oppgaven mislyktes", "success-msg-disk": "Konfigurasjonsfil skrevet til disk vellykket", "success-msg-local": "Lokale endringer er lagret", - "success-note-l1": "Appen bør bygge om automatisk.", - "success-note-l2": "Dette kan ta opptil et minutt.", - "success-note-l3": "Du må oppdatere siden for at endringene skal tre i kraft.", - "error-msg-save-mode": "Velg en lagringsmodus: lokal eller fil", "error-msg-cannot-save": "Det oppsto en feil under konfigurering", - "error-msg-bad-json": "Feil i JSON, muligens feilformet", - "warning-msg-validation": "Valideringsadvarsel", "not-admin-note": "Du kan ikke skrive endret til disk, fordi du ikke er logget inn som admin" }, "cloud-sync": { @@ -131,7 +115,6 @@ "intro-l2": "Alle data er helt ende-til-ende-kryptert med AES, og bruker passordet ditt som nøkkelen.", "intro-l3": "For mer informasjon, se", "backup-title-setup": "Lag en sikkerhetskopi", - "backup-title-update": "Oppdater sikkerhetskopi", "password-label-setup": "Velg et passord", "password-label-update": "Skriv inn passordet ditt", "backup-button-setup": "Sikkerhetskopiering", @@ -147,11 +130,5 @@ "backup-error-password": "Feil passord. Skriv inn ditt nåværende passord.", "backup-success-msg": "Fullført vellykket", "restore-success-msg": "Konfigurasjon gjenopprettet vellykket" - }, - "menu": { - "sametab": "Åpne i nåværende fane", - "newtab": "Åpne i ny fane", - "modal": "Åpne i popup-modus", - "workspace": "Åpne i Workspace-visning" } } diff --git a/src/assets/locales/nl.json b/src/assets/locales/nl.json index b6618a35..b0f94524 100644 --- a/src/assets/locales/nl.json +++ b/src/assets/locales/nl.json @@ -38,8 +38,6 @@ "reset-config-msg-l2": "Maak eerst een backup van de lokale veranderingen, om ze later nog te kunnen gebruiken.", "reset-config-msg-l3": "Weet je het zeker?", "data-cleared-msg": "Data succesvol verwijderd", - "actions-label": "Acties", - "copy-config-label": "Kopieer Config", "data-copied-msg": "Config is gekopieerd naar het klembord", "reset-config-label": "Reset Config", "css-save-btn": "Sla Wijzigingen Op", @@ -85,22 +83,10 @@ "reset-toast": "Aangepaste Kleuren voor {theme} Verwijderd" }, "config-editor": { - "save-location-label": "Sla Locatie Op", - "location-local-label": "Pas Lokaal Toe", - "location-disk-label": "Sla Veranderingen Op in Config Bestand", - "save-button": "Opslaan", "valid-label": "Config is Geldig", - "status-success-msg": "Taak Voltooid", - "status-fail-msg": "Taak Gefaald", "success-msg-disk": "Config bestand succesvol opgeslagen", "success-msg-local": "Lokale aanpassingen succesvol opgeslagen", - "success-note-l1": "De applicatie zou automatisch moeten herbouwen.", - "success-note-l2": "Dit duurt maximaal een minuut.", - "success-note-l3": "Herlaad de pagina om de veranderingen toe te passen.", - "error-msg-save-mode": "Selecteer een Save Mode: Lokaal of Bestand", - "error-msg-cannot-save": "Een fout trad op tijdens het opslaan van de config", - "error-msg-bad-json": "Fout in JSON, mogelijk ongeldige structuur", - "warning-msg-validation": "Validatie Waarschuwing" + "error-msg-cannot-save": "Een fout trad op tijdens het opslaan van de config" }, "cloud-sync": { "title": "Cloud Backup & Herstel", @@ -108,7 +94,6 @@ "intro-l2": "Alle data is volledig end-to-end versleuteld met AES, met je wachtwoord als de sleutel.", "intro-l3": "Voor meer informatie, zie", "backup-title-setup": "Maak een Backup", - "backup-title-update": "Update Backup", "password-label-setup": "Kies een Wachtwoord", "password-label-update": "Voer Wachtwoord in", "backup-button-setup": "Backup", diff --git a/src/assets/locales/pl.json b/src/assets/locales/pl.json index b6fc1f78..89d20f84 100644 --- a/src/assets/locales/pl.json +++ b/src/assets/locales/pl.json @@ -50,8 +50,6 @@ "reset-config-msg-l2": "Zrób kopię zapasową jeśli obecne ustawienia są ważne.", "reset-config-msg-l3": "Czy na pewno chcesz kontynuować?", "data-cleared-msg": "Dane wyczyszczone pomyślnie", - "actions-label": "Akcje", - "copy-config-label": "Kopia konfiguracji", "data-copied-msg": "Konfiguracja skopiowana do schowka", "reset-config-label": "Zresetuj konfigurację", "css-save-btn": "Zapisz zmiany", @@ -107,22 +105,10 @@ "reset-toast": "Niestandardowe kolory dla {theme} usunięte" }, "config-editor": { - "save-location-label": "Lokalizacja zapisu", - "location-local-label": "Pamięć podręczna", - "location-disk-label": "Plik na dysku", - "save-button": "Zapisz", "valid-label": "Konfiguracja poprawna", - "status-success-msg": "Zadanie ukończone", - "status-fail-msg": "Zadanie nie powiodło się", "success-msg-disk": "Pomyślnie zapisano na dysku", "success-msg-local": "Pomyślnie zapisano w pamięci podręcznej", - "success-note-l1": "Aplikacja powinna automatycznie się przebudować.", - "success-note-l2": "Może to zająć około minuty.", - "success-note-l3": "Będzie konieczne odświeżenie strony", - "error-msg-save-mode": "Proszę wybrać pomiędzy pamięcią podręczną lub plikiem na dysku", "error-msg-cannot-save": "Wystąpił błąd podczas zapisywania", - "error-msg-bad-json": "Błąd w JSON", - "warning-msg-validation": "Ostrzeżenie", "not-admin-note": "Nie możesz zapisywać na dysku, wymagane uprawnienia administratora" }, "cloud-sync": { @@ -131,7 +117,6 @@ "intro-l2": "Wszystkie dane są w pełni zaszyfrowane z wykorzystaniem AES, kluczem będzie podane hasło.", "intro-l3": "Aby uzyskać więcej informacji przejdź do", "backup-title-setup": "Tworzenie", - "backup-title-update": "Zaktualizuj", "password-label-setup": "Wybierz hasło", "password-label-update": "Wprowadź hasło", "backup-button-setup": "Zapisz", @@ -147,11 +132,5 @@ "backup-error-password": "Hasło niepoprawne. Proszę wprowadzić aktualne hasło.", "backup-success-msg": "zakończono pomyślnie", "restore-success-msg": "Przywrócono pomyślnie" - }, - "menu": { - "sametab": "Otwórz w tej karcie", - "newtab": "Otwórz w nowej karcie", - "modal": "Otwórz w oknie modalnym", - "workspace": "Otwórz w obszarze roboczym" } } diff --git a/src/assets/locales/pt.json b/src/assets/locales/pt.json index f87316d4..9c4b9796 100644 --- a/src/assets/locales/pt.json +++ b/src/assets/locales/pt.json @@ -66,8 +66,6 @@ "reset-config-msg-l2": "Você deve primeiro fazer um backup de todas as alterações locais, se quiser usá-las no futuro.", "reset-config-msg-l3": "Tem certeza que deseja continuar?", "data-cleared-msg": "Dados apagados", - "actions-label": "Ações", - "copy-config-label": "Copiar configuração", "data-copied-msg": "A configuração foi copiada para a área de trabalho", "reset-config-label": "Reset de configuração", "css-save-btn": "Salvar mudanças", @@ -124,23 +122,11 @@ "reset-toast": "Cores customizadas do {theme} removidas" }, "config-editor": { - "save-location-label": "Salvar localiação", - "location-local-label": "Aplicar localmente", - "location-disk-label": "Salvar alterações no arquivo de configuração", - "save-button": "Salvar alterações", "preview-button": "Previsualizar", "valid-label": "Configuração é válida", - "status-success-msg": "Tarefa concluída", - "status-fail-msg": "Falha na tarefa", "success-msg-disk": "Arquivo de configuração gravado no disco com sucesso", "success-msg-local": "Mudanças locais salvas com sucesso", - "success-note-l1": "O app deve atualizar automaticamente.", - "success-note-l2": "Isso pode levar até um minuto.", - "success-note-l3": "Você precisará atualizar a página para aplicar as mudanças.", - "error-msg-save-mode": "Modo de salvar: Localmente ou Arquivo", "error-msg-cannot-save": "Ocorreu um erro ao salvar o arquivo", - "error-msg-bad-json": "Erro no JSON, possivelmente mal formatado", - "warning-msg-validation": "Erro de validação", "not-admin-note": "Você não pode salvar essa mudança no disco. Você não está conectado como admin" }, "cloud-sync": { @@ -149,7 +135,6 @@ "intro-l2": "Todos os dados são totalmente criptografados de ponta a ponta com AES, usando sua senha como chave.", "intro-l3": "Para obter mais informações, consulte o", "backup-title-setup": "Faça um backup", - "backup-title-update": "Atualizar backup", "password-label-setup": "Escolha uma senha", "password-label-update": "Coloque sua senha", "backup-button-setup": "Backup", @@ -166,17 +151,6 @@ "backup-success-msg": "Completado com sucesso", "restore-success-msg": "Configuração restaurada com sucesso" }, - "menu": { - "open-section-title": "Abrir em", - "sametab": "Aba atual", - "newtab": "Nova aba", - "modal": "Pop-up", - "workspace": "Visualização Workspace", - "options-section-title": "Opções", - "edit-item": "Editar", - "move-item": "Copiar ou Mover", - "remove-item": "Remover" - }, "context-menus": { "item": { "open-section-title": "Abrir em", @@ -194,7 +168,6 @@ "section": { "open-section": "Abrir seção", "edit-section": "Editar", - "move-section": "Mover para", "remove-section": "Remover" } }, @@ -242,7 +215,6 @@ "warning-msg-l3": "para evitar consequências indesejadas" }, "export": { - "export-title": "Expotar configuração", "copy-clipboard-btn": "Copiar para área de transferência", "copy-clipboard-tooltip": "Copiar toda as configurações do app para a área de transferência, no formato YAML", "download-file-btn": "Baixar como arquivo", @@ -251,7 +223,6 @@ }, "widgets": { "general": { - "loading": "Carregando...", "show-more": "Mostrar mais", "show-less": "Mostrar menos", "open-link": "Continuar leitura" @@ -290,13 +261,9 @@ "good-service-rest": "Bom serviço em todas as outras linhas." }, "synology-download": { - "download": "Baixar", - "upload": "Carregar", "downloaded": "Baixado", "uploaded": "Carregado", - "remaining": "Restante", - "up": "Up", - "down": "Down" + "remaining": "Restante" }, "gluetun-status": { "vpn-ip": "IP da VPN", diff --git a/src/assets/locales/ro.json b/src/assets/locales/ro.json index 74d116b8..6b3c54db 100644 --- a/src/assets/locales/ro.json +++ b/src/assets/locales/ro.json @@ -47,7 +47,6 @@ }, "app-info": { "title": "Informații Aplicație", - "support-dashy-link": "Pagina de Contribuții", "report-bug": "Raportează o Eroare", "report-bug-description": "Dacă crezi că ai găsit o eroare, atunci te rog", "report-bug-link": "deschide o Problemă", @@ -63,8 +62,6 @@ "privacy-and-security-security-policy": "Politica de Securitate", "license": "Licență", "license-under": "Licențiat sub", - "licence-third-party": "Pentru licențele modulelor terțe părți, vă rugăm să consultați", - "licence-third-party-link": "Legal", "list-contributors": "Pentru lista completă a contribuitorilor și mulțumiri, vedeți", "list-contributors-link": "Credite", "version": "Versiune", @@ -96,8 +93,6 @@ "reset-config-msg-l2": "Ar trebui să faceți mai întâi o copie de siguranță a oricăror modificări pe care le-ați făcut local, dacă doriți să le utilizați în viitor.", "reset-config-msg-l3": "Sunteți sigur că doriți să continuați?", "data-cleared-msg": "Datele au fost șterse cu succes", - "actions-label": "Acțiuni", - "copy-config-label": "Copiază Configurația", "data-copied-msg": "Configurația a fost copiată în clipboard", "reset-config-label": "Resetează Configurația", "css-save-btn": "Salvează Modificările", @@ -133,7 +128,6 @@ "sign-in-tooltip": "Conectare", "sign-in-welcome": "Bună {username}!", "hide": "Ascunde", - "open": "Deschide", "layout-masonry": "Zidărie", "language-label": "Limbă", "nav-links-tooltip": "Navigare și configurări", @@ -170,23 +164,11 @@ "reset-toast": "Culorile Personalizate pentru {theme} au fost Eliminate" }, "config-editor": { - "save-location-label": "Locația de Salvare", - "location-local-label": "Aplică Local", - "location-disk-label": "Scrie Modificările în Fișierul de Configurație", - "save-button": "Salvează Modificările", "preview-button": "Previzualizează Modificările", "valid-label": "Configurația este Valabilă", - "status-success-msg": "Sarcină Completată", - "status-fail-msg": "Sarcină Eșuată", "success-msg-disk": "Fișierul de configurație a fost scris pe disc cu succes", "success-msg-local": "Modificările locale au fost salvate cu succes", - "success-note-l1": "Aplicația ar trebui să se reconstruiască automat.", - "success-note-l2": "Aceasta poate dura până la un minut.", - "success-note-l3": "Va trebui să reîmprospătați pagina pentru ca modificările să aibă efect.", - "error-msg-save-mode": "Vă rugăm să selectați un Mod de Salvare: Local sau Fișier", "error-msg-cannot-save": "A apărut o eroare la salvarea configurației", - "error-msg-bad-json": "Eroare în JSON, posibil malformat", - "warning-msg-validation": "Avertisment de Validare", "not-admin-note": "Nu puteți scrie modificările pe disc deoarece nu sunteți conectat ca administrator", "preview-applied-msg": "Configurația a fost previzualizată. Închide această fereastră pentru a vedea modificările.", "reset-confirm-msg": "Renunți la toate modificările nesalvate și revii la configurația originală?", @@ -215,7 +197,6 @@ "intro-l3": "Pentru mai multe informații, vă rugăm să consultați", "intro-docs": "documentația", "backup-title-setup": "Creați un Backup", - "backup-title-update": "Actualizați Backup-ul", "password-label-setup": "Alegeți o Parolă", "password-label-update": "Introduceți Parola", "backup-button-setup": "Backup", @@ -232,17 +213,6 @@ "backup-success-msg": "Finalizat cu Succes", "restore-success-msg": "Configurația a fost Restaurată cu Succes" }, - "menu": { - "open-section-title": "Deschide În", - "sametab": "Tab Curent", - "newtab": "Tab Nou", - "modal": "Modal Pop-Up", - "workspace": "Vizualizare Spațiu de Lucru", - "options-section-title": "Opțiuni", - "edit-item": "Editare", - "move-item": "Copiază sau Mută", - "remove-item": "Șterge" - }, "context-menus": { "item": { "open-section-title": "Deschide În", @@ -262,7 +232,6 @@ "open-section": "Deschide Secțiunea", "edit-section": "Editare", "expand-collapse": "Extinde / Colapsează", - "move-section": "Mută La", "remove-section": "Șterge", "section-options": "Opțiuni secțiune" } @@ -319,7 +288,6 @@ "warning-msg-l3": "pentru a evita consecințele nedorite." }, "export": { - "export-title": "Exportă Configurația", "copy-clipboard-btn": "Copiază în Clipboard", "copy-clipboard-tooltip": "Copiază toată configurația aplicației în clipboard-ul sistemului, în format YAML", "download-file-btn": "Descarcă ca Fișier", @@ -367,7 +335,6 @@ }, "widgets": { "general": { - "loading": "Se încarcă...", "show-more": "Extinde Detaliile", "cpu-details": "Detalii CPU", "mem-details": "Detalii Memorie", @@ -408,13 +375,9 @@ "good-service-rest": "Servicii Bune pe Celelalte Linii" }, "synology-download": { - "download": "Descărcare", - "upload": "Încărcare", "downloaded": "Descărcat", "uploaded": "Încărcat", - "remaining": "Rămas", - "up": "Sus", - "down": "Jos" + "remaining": "Rămas" }, "gluetun-status": { "vpn-ip": "IP VPN", diff --git a/src/assets/locales/ru.json b/src/assets/locales/ru.json index 8912eeb3..28a020f4 100644 --- a/src/assets/locales/ru.json +++ b/src/assets/locales/ru.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Информация о Приложении", - "support-dashy-link": "Contributions", "report-bug": "Сообщить об ошибке", "report-bug-description": "Если вы считаете, что нашли ошибку, пожалуйста", "report-bug-link": "сообщите о проблеме", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "Политикой Безопасности", "license": "Лицензия", "license-under": "Лицензировано под", - "licence-third-party": "Лицензии на модули сторонних производителей см.", - "licence-third-party-link": "Legal", "list-contributors": "Чтобы посмотреть полный список авторов и благодарностей, см.", "list-contributors-link": "Credits", "version": "Версия" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Вы должны сначала сделать резервную копию любых изменений, которые вы внесли локально, если вы хотите использовать их в будущем.", "reset-config-msg-l3": "Вы уверены, что хотите продолжить?", "data-cleared-msg": "Данные успешно очищены", - "actions-label": "Действия", - "copy-config-label": "Скопировать конфигурацию", "data-copied-msg": "Конфиг скопирован в буфер обмена", "reset-config-label": "Сбросить конфигурацию", "css-save-btn": "Сохранить изменения", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Выход", "sign-in-tooltip": "Авторизоваться", "sign-in-welcome": "Здравствуйте, {username}!", - "hide": "Скрыть", - "open": "Открыть" + "hide": "Скрыть" }, "updates": { "app-version-note": "Dashy версия", @@ -153,23 +147,11 @@ "reset-toast": "Пользовательские цвета для темы {theme} удалены" }, "config-editor": { - "save-location-label": "Место сохранения", - "location-local-label": "Применить локально", - "location-disk-label": "Записать изменения в файл конфигурации на диск", - "save-button": "Сохранить изменения", "preview-button": "Предпросмотр", "valid-label": "Конфигурация верна", - "status-success-msg": "Задача завершена", - "status-fail-msg": "Задача была провалена", "success-msg-disk": "Файл конфигурации успешно записан на диск", "success-msg-local": "Локальные изменения успешно сохранены", - "success-note-l1": "Приложение должно перестроиться автоматически.", - "success-note-l2": "Это может занять до минуты.", - "success-note-l3": "Вам нужно будет обновить страницу, чтобы изменения вступили в силу.", - "error-msg-save-mode": "Пожалуйста, выберите режим сохранения: локальный или файл", "error-msg-cannot-save": "Произошла ошибка при сохранении конфигурации", - "error-msg-bad-json": "Ошибка в JSON, возможно, неверный формат", - "warning-msg-validation": "Предупреждение валидатора", "not-admin-note": "Вы не можете записать измененные на диск, потому что вы не вошли в систему как администратор" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Для получения дополнительной информации см.", "intro-docs": "документацию", "backup-title-setup": "Создать резервную копию", - "backup-title-update": "Обновить резервную копию", "password-label-setup": "Выберите пароль", "password-label-update": "Введите ваш пароль", "backup-button-setup": "Создать резервную копию", @@ -196,17 +177,6 @@ "backup-success-msg": "Успешно завершено", "restore-success-msg": "Конфигурация успешно восстановлена" }, - "menu": { - "open-section-title": "Открыть в", - "sametab": "Текущей вкладке", - "newtab": "Новой вкладке", - "modal": "Всплывающем окне", - "workspace": "Рабочей области", - "options-section-title": "Настройки", - "edit-item": "Редактировать", - "move-item": "Скопировать или переместить", - "remove-item": "Удалить" - }, "context-menus": { "item": { "open-section-title": "Открыть в", @@ -225,7 +195,6 @@ "open-section": "Открыть секцию", "edit-section": "Редактировать", "expand-collapse": "Раскрыть / Свернуть", - "move-section": "Переместить в", "remove-section": "Удалить" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": "для избежания непредвиденных последствий." }, "export": { - "export-title": "Экспорт конфигурации", "copy-clipboard-btn": "Скопировано в буфер обмена", "copy-clipboard-tooltip": "Скопировать всю конфигурацию приложения, в формате YAML", "download-file-btn": "Скачать как файл", @@ -293,7 +261,6 @@ }, "widgets": { "general": { - "loading": "Загрузка...", "show-more": "Развернуть сведения", "cpu-details": "Сведения о ЦПУ", "mem-details": "Сведения о памяти", @@ -334,13 +301,9 @@ "good-service-rest": "Хорошее обслуживание на всех остальных линиях" }, "synology-download": { - "download": "Скачивание", - "upload": "Загрузка", "downloaded": "Скачано", "uploaded": "Загружено", - "remaining": "Остаётся", - "up": "Вверх", - "down": "Вниз" + "remaining": "Остаётся" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/sk.json b/src/assets/locales/sk.json index 63a4495e..a5d0814a 100644 --- a/src/assets/locales/sk.json +++ b/src/assets/locales/sk.json @@ -52,8 +52,6 @@ "reset-config-msg-l2": "Všetky zmeny, ktoré ste vykonali lokálne, by ste si mali najskôr zálohovať, ak ich chcete použiť v budúcnosti.", "reset-config-msg-l3": "Naozaj chcete pokračovať?", "data-cleared-msg": "Údaje boli úspešne vymazané", - "actions-label": "Akcie", - "copy-config-label": "Kopírovať konfig", "data-copied-msg": "Konfigurácia bola skopírovaná do schránky", "reset-config-label": "Obnoviť konfiguráciu", "css-save-btn": "Uložiť zmeny", @@ -110,23 +108,11 @@ "reset-toast": "Vlastné farby pre {theme} boli odstránené" }, "config-editor": { - "save-location-label": "Uložiť umiestnenie", - "location-local-label": "Aplikovať lokálne", - "location-disk-label": "Zapíšte zmeny do konfiguračného súboru", - "save-button": "Uložiť zmeny", "preview-button": "Ukážka zmien", "valid-label": "Konfigurácia je platná", - "status-success-msg": "Úloha dokončená", - "status-fail-msg": "Úloha zlyhala", "success-msg-disk": "Konfiguračný súbor bol úspešne zapísaný na disk", "success-msg-local": "Miestne zmeny boli úspešne uložené", - "success-note-l1": "Aplikácia by sa mala automaticky obnoviť.", - "success-note-l2": "Môže to trvať až minútu.", - "success-note-l3": "Aby sa zmeny prejavili, budete musieť obnoviť stránku.", - "error-msg-save-mode": "Vyberte režim uloženia: Miestny alebo Súbor", "error-msg-cannot-save": "Pri ukladaní konfigurácie sa vyskytla chyba", - "error-msg-bad-json": "Chyba v JSON, možno má nesprávny formát", - "warning-msg-validation": "Upozornenie na validáciu", "not-admin-note": "Nemôžete zapisovať zmeneny na disk, pretože nie ste prihlásený ako správca" }, "cloud-sync": { @@ -135,7 +121,6 @@ "intro-l2": "Všetky údaje sú úplne end-to-end šifrované pomocou AES, pričom ako kľúč sa používa vaše heslo.", "intro-l3": "Viac informácií nájdete na", "backup-title-setup": "Vytvoriť zálohu", - "backup-title-update": "Aktualizovať zálohu", "password-label-setup": "Vyberte si heslo", "password-label-update": "Zadajte svoje heslo", "backup-button-setup": "Záloha", @@ -152,17 +137,6 @@ "backup-success-msg": "Úspešne dokončené", "restore-success-msg": "Konfigurácia bola úspešne obnovená" }, - "menu": { - "open-section-title": "Otvoriť v", - "sametab": "Aktuálna karta", - "newtab": "Nová karta", - "modal": "Vyskakovací modal", - "workspace": "Zobrazenie pracovného priestoru", - "options-section-title": "Možnosti", - "edit-item": "Upraviť", - "move-item": "Kopírovať alebo presunúť", - "remove-item": "Odstrániť" - }, "context-menus": { "item": { "open-section-title": "Otvoriť v", @@ -181,7 +155,6 @@ "open-section": "Otvorená sekcia", "edit-section": "Upraviť", "expand-collapse": "Rozbaliť / zbaliť", - "move-section": "Presunúť do", "remove-section": "Odstrániť" } }, @@ -232,7 +205,6 @@ "warning-msg-l3": "aby sa predišlo neúmyselným následkom." }, "export": { - "export-title": "Exportovať konfiguráciu", "copy-clipboard-btn": "Kopírovať do schránky", "copy-clipboard-tooltip": "Kopírovať všetky konfigurácie aplikácie do systémovej schránky vo formáte YAML", "download-file-btn": "Stiahnuť ako súbor", @@ -241,7 +213,6 @@ }, "widgets": { "general": { - "loading": "Načítavam...", "show-more": "Rozbaliť podrobnosti", "show-less": "Zobraziť menej", "open-link": "Pokračovať v čítaní" @@ -280,13 +251,9 @@ "good-service-rest": "Dobré služby na všetkých ostatných linkách" }, "synology-download": { - "download": "Stiahnuť", - "upload": "Nahrať", "downloaded": "Stiahnuté", "uploaded": "Nahrané", - "remaining": "Zostávajúce", - "up": "Aktívne", - "down": "Neaktívne" + "remaining": "Zostávajúce" }, "gluetun-status": { "vpn-ip": "IP VPN", @@ -301,7 +268,6 @@ "nextcloud": { "active": "active", "and": "a", - "aplications": "aplikácie", "available": "dostupné", "away": "Preč", "cache-full": "MEDZIPAMÄTE PLNÉ", diff --git a/src/assets/locales/sl.json b/src/assets/locales/sl.json index 5d307edd..417c84c3 100644 --- a/src/assets/locales/sl.json +++ b/src/assets/locales/sl.json @@ -50,8 +50,6 @@ "reset-config-msg-l2": "Če želeti spremembe, ki ste jih naredili lokalno uporabiti v prihodnosti, ustvarite varnostno kopijo.", "reset-config-msg-l3": "Ali ste prepričani, da želite nadaljevati?", "data-cleared-msg": "Podatki so bili uspešno izbrisani", - "actions-label": "Dejanja", - "copy-config-label": "Kopiraj Konfiguracijo", "data-copied-msg": "Config je bil kopiran v odložišče", "reset-config-label": "Ponastavi Konfiguracijo", "css-save-btn": "Shrani spremembe", @@ -108,23 +106,11 @@ "reset-toast": "Barve po Meri za {theme} Odstranjene" }, "config-editor": { - "save-location-label": "Način Shranjevanja", - "location-local-label": "Shrani Lokalno", - "location-disk-label": "Zapišite spremembe v datoteko za konfiguracijo", - "save-button": "Shrani Spremembe", "preview-button": "Predogled Sprememb", "valid-label": "Konfiguracija je veljavna", - "status-success-msg": "Operacija dokončana", - "status-fail-msg": "Operacija ni uspela", "success-msg-disk": "Konfiguracijska datoteka je uspešno zapisana na disk", "success-msg-local": "Lokalne spremembe so bile uspešno shranjene", - "success-note-l1": "Aplikacija se bo samodejno obnovila.", - "success-note-l2": "To lahko traja do ene minute.", - "success-note-l3": "Za uveljavitev sprememb boste morali osvežiti stran.", - "error-msg-save-mode": "Izberite način shranjevanja: Lokalno ali v Datoteko", "error-msg-cannot-save": "Pri shranjevanju konfiguracije je prišlo do napake", - "error-msg-bad-json": "Napaka v JSON -u, morda nepravilno oblikovana", - "warning-msg-validation": "Opozorilo o Validaciji", "not-admin-note": "Ne morete zapisati spremenjenega na disk, ker niste prijavljeni kot skrbnik" }, "cloud-sync": { @@ -133,7 +119,6 @@ "intro-l2": "Vsi podatki so v celoti šifrirani z AES, pri čemer je vaše geslo ključ.", "intro-l3": "Za več informacij si oglejte", "backup-title-setup": "Ustvari Varnostno Kopijo", - "backup-title-update": "Posodobi Varnostno Kopijo", "password-label-setup": "Izberi Geslo", "password-label-update": "Vnesite Geslo", "backup-button-setup": "Varnosto Kopiraj", @@ -150,17 +135,6 @@ "backup-success-msg": "Uspešno Zaključeno", "restore-success-msg": "Konfiguracija Uspešno Obnovljena" }, - "menu": { - "open-section-title": "Odpri V", - "sametab": "Odpri v Trenutnem Zavihku", - "newtab": "Odpri v Novem Zavihku", - "modal": "Odpri v Pojavnem Oknu", - "workspace": "Odpri v Delovnem Pogledu", - "options-section-title": "Nastavitve", - "edit-item": "Uredi", - "move-item": "Kopiral ali Premakni", - "remove-item": "Odstrani" - }, "context-menus": { "item": { "open-section-title": "Odpri V", @@ -176,7 +150,6 @@ "section": { "open-section": "Odpri Razdelek", "edit-section": "Uredi", - "move-section": "Premakni v", "remove-section": "Odstrani" } }, @@ -221,7 +194,6 @@ "warning-msg-l3": "da bi se izognili neželenim posledicam." }, "export": { - "export-title": "Izvozi Nastavitve", "copy-clipboard-btn": "Kopirati v Odložišče", "copy-clipboard-tooltip": "Kopirajte vso konfiguracijo aplikacije v sistemsko odložišče v formatu YAML", "download-file-btn": "Prenesi kot Datoteko", diff --git a/src/assets/locales/sv.json b/src/assets/locales/sv.json index 55a37255..cfa213a9 100644 --- a/src/assets/locales/sv.json +++ b/src/assets/locales/sv.json @@ -55,8 +55,6 @@ "reset-config-msg-l2": "Du bör först göra en säkerhetskopia av alla ändringar du har gjort lokalt, om du vill använda dem i framtiden.", "reset-config-msg-l3": "Är du säker på att du vill fortsätta?", "data-cleared-msg": "Datarensning har lyckats", - "actions-label": "Åtgärder", - "copy-config-label": "Kopiera konfiguration", "data-copied-msg": "Konfiguration har kopierats till urklipp", "reset-config-label": "Återställ konfiguration", "css-save-btn": "Spara ändringar", @@ -86,8 +84,7 @@ "sign-out-tooltip": "Logga ut", "sign-in-tooltip": "Logga in", "sign-in-welcome": "Hej {username}!", - "hide": "Göm", - "open": "Öppna" + "hide": "Göm" }, "updates": { "app-version-note": "Dashy-version", @@ -115,23 +112,11 @@ "reset-toast": "Egna färger för {theme} har tagits bort" }, "config-editor": { - "save-location-label": "Sparningsplats", - "location-local-label": "Tillämpa lokalt", - "location-disk-label": "Skriv ändringar till konfigurationsfil", - "save-button": "Spara ändringar", "preview-button": "Förhandsgranska ändringar", "valid-label": "Konfigurationen är giltig", - "status-success-msg": "Åtgärden slutfördes", - "status-fail-msg": "Åtgärden misslyckades", "success-msg-disk": "Konfigurationsfil har skrivits till disk utan problem", "success-msg-local": "Lokala ändringar har sparats utan problem", - "success-note-l1": "Återskapa", - "success-note-l2": "Detta kan ta upp till en minut.", - "success-note-l3": "Du måste uppdatera sidan för att ändringar ska gälla", - "error-msg-save-mode": "Välj Lagringsläge: Lokalt eller Fil", "error-msg-cannot-save": "Ett fel uppstod när konfigurationen skulle sparas", - "error-msg-bad-json": "Fel i JSON, möjligen felformaterat", - "warning-msg-validation": "Valideringsvarning", "not-admin-note": "Du kan inte skriva ändringar till disk, eftersom du inte är inloggad som admin" }, "cloud-sync": { @@ -140,7 +125,6 @@ "intro-l2": "All data är fullständigt end-to-end krypterad med AES, med ditt lösenord som nyckel.", "intro-l3": "För mer information, vänligen se", "backup-title-setup": "Gör en säkerhetskopia", - "backup-title-update": "Uppdatera säkerhetskopia", "password-label-setup": "Välj lösenord", "password-label-update": "Ange ditt lösenord", "backup-button-setup": "Säkerhetskopiering", @@ -157,17 +141,6 @@ "backup-success-msg": "Slutfört utan problem", "restore-success-msg": "Konfigurationen har återställts utan problem" }, - "menu": { - "open-section-title": "Öppna i", - "sametab": "Denna flik", - "newtab": "Ny flik", - "modal": "Pop-Up Modal", - "workspace": "Workspace-vy", - "options-section-title": "Alternativ", - "edit-item": "Redigera", - "move-item": "Kopiera eller flytta", - "remove-item": "Ta bort" - }, "context-menus": { "item": { "open-section-title": "Öppna i", @@ -186,7 +159,6 @@ "open-section": "Öppna sektion", "edit-section": "Redigera", "expand-collapse": "Expandera / Minimera", - "move-section": "Flytta till", "remove-section": "Ta bort" } }, @@ -236,7 +208,6 @@ "warning-msg-l3": "för att undvika oavsiktliga konsekvenser." }, "export": { - "export-title": "Exportera konfiguration", "copy-clipboard-btn": "Kopiera till urklipp", "copy-clipboard-tooltip": "Kopiera alla appkonfigurationer till systemets urklipp i YAML-format", "download-file-btn": "Ladda ned som fil", @@ -245,7 +216,6 @@ }, "widgets": { "general": { - "loading": "Laddar...", "show-more": "Visa mer info", "show-less": "Visa mindre", "open-link": "Läs mer" diff --git a/src/assets/locales/tr.json b/src/assets/locales/tr.json index 63aa31db..f9765812 100644 --- a/src/assets/locales/tr.json +++ b/src/assets/locales/tr.json @@ -47,7 +47,6 @@ }, "app-info": { "title": "Uygulama Bilgisi", - "support-dashy-link": "Katkı sayfası", "report-bug": "Hata Bildir", "report-bug-description": "Bir hata bulduğunuzu düşünüyorsanız, lütfen", "report-bug-link": "bir Sorun bildirin", @@ -63,8 +62,6 @@ "privacy-and-security-security-policy": "Güvenlik Politikası", "license": "Lisans", "license-under": "Şu lisans altında:", - "licence-third-party": "Üçüncü taraf modüllerin lisansları için lütfen şuraya bakın:", - "licence-third-party-link": "Yasal Bilgiler", "list-contributors": "Katkıda bulunanların tam listesi ve teşekkürler için şuraya bakın:", "list-contributors-link": "Katkıda Bulunanlar", "version": "Sürüm" @@ -90,8 +87,6 @@ "reset-config-msg-l2": "Gelecekte kullanmak istiyorsanız, önce yerel olarak yaptığınız tüm değişiklikleri yedeklemelisiniz.", "reset-config-msg-l3": "Devam etmek istediğinizden emin misiniz?", "data-cleared-msg": "Veriler başarıyla temizlendi", - "actions-label": "Eylemler", - "copy-config-label": "Yapılandırmayı Kopyala", "data-copied-msg": "Yapılandırma panoya kopyalandı", "reset-config-label": "Yapılandırmayı Sıfırla", "css-save-btn": "Değişiklikleri Kaydet", @@ -125,8 +120,7 @@ "sign-out-tooltip": "Çıkış Yap", "sign-in-tooltip": "Giriş Yap", "sign-in-welcome": "Merhaba {username}!", - "hide": "Gizle", - "open": "Aç" + "hide": "Gizle" }, "updates": { "app-version-note": "Dashy versiyon", @@ -154,23 +148,11 @@ "reset-toast": "{theme} İçin Özel Renkler Kaldırıldı" }, "config-editor": { - "save-location-label": "Konumu kaydet", - "location-local-label": "Yerel Olarak Uygula", - "location-disk-label": "Değişiklikleri Yapılandırma Dosyasına Yaz", - "save-button": "Değişiklikleri Kaydet", "preview-button": "Değişiklikleri Önizle", "valid-label": "Yapılandırma Geçerli", - "status-success-msg": "İşlem Tamamlandı", - "status-fail-msg": "İşlem Başarısız", "success-msg-disk": "Yapılandırma dosyası diske başarıyla yazıldı", "success-msg-local": "Yerel değişiklikler başarıyla kaydedildi", - "success-note-l1": "Değişikliklerin etkili olması için sayfayı yenilemeniz gerekecek.", - "success-note-l2": "", - "success-note-l3": "", - "error-msg-save-mode": "Lütfen bir Kaydetme Modu seçin: Yerel veya Dosya", "error-msg-cannot-save": "Yapılandırma kaydedilirken bir hata oluştu", - "error-msg-bad-json": "JSON'da hata, muhtemelen hatalı biçimlendirilmiş", - "warning-msg-validation": "Doğrulama Uyarısı", "not-admin-note": "Yönetici olarak giriş yapmadığınız için değiştirilenleri diske yazamazsınız." }, "cloud-sync": { @@ -180,7 +162,6 @@ "intro-l3": "Daha fazla bilgi için lütfen şuraya bakın:", "intro-docs": "belgeler", "backup-title-setup": "Yedekleme Yap", - "backup-title-update": "Yedeği Güncelle", "password-label-setup": "Bir Şifre Seçin", "password-label-update": "Şifrenizi Girin", "backup-button-setup": "Yedekle", @@ -197,17 +178,6 @@ "backup-success-msg": "Başarıyla tamamlandı", "restore-success-msg": "Yapılandırma Başarıyla Geri Yüklendi" }, - "menu": { - "open-section-title": "Şurada Aç", - "sametab": "Mevcut Sekme", - "newtab": "Yeni Sekme", - "modal": "Açılır Pencere", - "workspace": "Çalışma Alanı Görünümü", - "options-section-title": "Seçenekler", - "edit-item": "Düzenle", - "move-item": "Kopyala veya Taşı", - "remove-item": "Kaldır" - }, "context-menus": { "item": { "open-section-title": "Şurada aç:", @@ -227,7 +197,6 @@ "open-section": "Bölümü Aç", "edit-section": "Düzenle", "expand-collapse": "Genişlet / Daralt", - "move-section": "Şuraya Taşı:", "remove-section": "Kaldır" } }, @@ -278,7 +247,6 @@ "warning-msg-l3": "istenmeyen sonuçlardan kaçınmak için." }, "export": { - "export-title": "Yapılandırmayı Dışarı Aktar", "copy-clipboard-btn": "Yapılandırmayı Panoya Kopyala", "copy-clipboard-tooltip": "Tüm uygulama yapılandırmasını sistem panosuna YAML biçiminde kopyalayın", "download-file-btn": "Dosya Olarak İndir", @@ -295,7 +263,6 @@ }, "widgets": { "general": { - "loading": "Yükleniyor...", "show-more": "Ayrıntıları Genişlet", "cpu-details": "CPU Detayları", "mem-details": "Bellek Detayları", @@ -336,13 +303,9 @@ "good-service-rest": "Diğer Hatlarda İyi Hizmet" }, "synology-download": { - "download": "İndir", - "upload": "Yükle", "downloaded": "İndirildi", "uploaded": "Yüklendi", - "remaining": "Kalan", - "up": "Yükleme", - "down": "İndirme" + "remaining": "Kalan" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/uk.json b/src/assets/locales/uk.json index 1551a4f9..915a623a 100644 --- a/src/assets/locales/uk.json +++ b/src/assets/locales/uk.json @@ -46,7 +46,6 @@ }, "app-info": { "title": "Інформація про програму", - "support-dashy-link": "Сторінка внесків", "report-bug": "Повідомити про помилку", "report-bug-description": "Якщо ви вважаєте, що знайшли помилку, будь ласка", "report-bug-link": "повідомте про проблему", @@ -62,8 +61,6 @@ "privacy-and-security-security-policy": "Політика безпеки", "license": "Ліцензія", "license-under": "Ліцензія згідно", - "licence-third-party": "Ліцензії на сторонні модулі див.", - "licence-third-party-link": "Юридична інформація", "list-contributors": "Повний список співавторів та подяки див.", "list-contributors-link": "Подяки", "version": "Версія" @@ -89,8 +86,6 @@ "reset-config-msg-l2": "Ви повинні спочатку створити резервну копію будь-яких змін, які ви внесли локально, якщо ви хочете використовувати їх у майбутньому.", "reset-config-msg-l3": "Ви впевнені, що бажаєте продовжити?", "data-cleared-msg": "Дані успішно видалено", - "actions-label": "Дії", - "copy-config-label": "Копіювати налаштування", "data-copied-msg": "Налаштування скопійовано до буфера обміну", "reset-config-label": "Скинути налаштування", "css-save-btn": "Зберегти зміни", @@ -124,8 +119,7 @@ "sign-out-tooltip": "Вийти", "sign-in-tooltip": "Увійти", "sign-in-welcome": "Вітаємо {username}!", - "hide": "Приховати", - "open": "Відкрити" + "hide": "Приховати" }, "updates": { "app-version-note": "Версія Dashy", @@ -153,23 +147,11 @@ "reset-toast": "Видалені користувацькі кольори для {theme}" }, "config-editor": { - "save-location-label": "Місце збереження", - "location-local-label": "Застосувати локально", - "location-disk-label": "Записати зміни до файлу налаштування", - "save-button": "Зберегти зміни", "preview-button": "Попередній перегляд змін", "valid-label": "Налаштування дійсне", - "status-success-msg": "Завдання виконано", - "status-fail-msg": "Не вдалося виконати завдання", "success-msg-disk": "Файл налаштування успішно записаний на диск", "success-msg-local": "Локальні зміни успішно збережено", - "success-note-l1": "Програма повинна автоматично перебудуватися.", - "success-note-l2": "Це може зайняти до хвилини.", - "success-note-l3": "Вам потрібно буде оновити сторінку, щоб зміни набули чинності.", - "error-msg-save-mode": "Будь ласка, виберіть режим збереження: локальний або файловий", "error-msg-cannot-save": "Під час збереження налаштування сталася помилка", - "error-msg-bad-json": "Помилка у форматі JSON, можливо, некоректно сформований", - "warning-msg-validation": "Попередження про перевірку", "not-admin-note": "Ви не можете записати зміни на диск, оскільки ви не увійшли як адміністратор" }, "cloud-sync": { @@ -179,7 +161,6 @@ "intro-l3": "Для отримання додаткової інформації див.", "intro-docs": "документи", "backup-title-setup": "Створити резервну копію", - "backup-title-update": "Оновити резервну копію", "password-label-setup": "Оберіть пароль", "password-label-update": "Введіть свій пароль", "backup-button-setup": "Резервна копія", @@ -196,17 +177,6 @@ "backup-success-msg": "Успішно завершено", "restore-success-msg": "Налаштування успішно відновлено" }, - "menu": { - "open-section-title": "Відкрити в", - "sametab": "Поточна вкладка", - "newtab": "Нова вкладка", - "modal": "Модальне спливаюче вікно", - "workspace": "Перегляд робочого простору", - "options-section-title": "Параметри", - "edit-item": "Редагувати", - "move-item": "Копіювати або перемістити", - "remove-item": "Видалити" - }, "context-menus": { "item": { "open-section-title": "Відкрити в", @@ -225,7 +195,6 @@ "open-section": "Відкрити розділ", "edit-section": "Редагувати", "expand-collapse": "Розгорнути / Згорнути", - "move-section": "Перемістити до", "remove-section": "Видалити" } }, @@ -276,7 +245,6 @@ "warning-msg-l3": ", щоб уникнути небажаних наслідків." }, "export": { - "export-title": "Експорт налаштування", "copy-clipboard-btn": "Копіювати до буфера обміну", "copy-clipboard-tooltip": "Скопіювати всі налаштування програми до системного буфера обміну у форматі YAML", "download-file-btn": "Завантажити як файл", @@ -293,7 +261,6 @@ }, "widgets": { "general": { - "loading": "Завантаження...", "show-more": "Розгорнути детальніше", "cpu-details": "Детальніше по ЦПУ", "mem-details": "Детальніше по пам'яті", @@ -334,13 +301,9 @@ "good-service-rest": "Якісний сервіс на всіх інших лініях" }, "synology-download": { - "download": "Завантажити (Download)", - "upload": "Вивантажити (Upload)", "downloaded": "Завантажено", "uploaded": "Вивантажено", - "remaining": "Залишилося", - "up": "Вгору", - "down": "Вниз" + "remaining": "Залишилося" }, "gluetun-status": { "vpn-ip": "VPN IP", diff --git a/src/assets/locales/zh-CN.json b/src/assets/locales/zh-CN.json index 7b4b158b..9de9acef 100644 --- a/src/assets/locales/zh-CN.json +++ b/src/assets/locales/zh-CN.json @@ -1,4 +1,9 @@ { + "general": { + "close-modal": "关闭对话框", + "confirm": "确认", + "cancel": "取消" + }, "home": { "no-results": "找不到结果", "no-data": "没有配置数据", @@ -8,7 +13,8 @@ "search-label": "搜索", "search-placeholder": "输入以筛选", "clear-search-tooltip": "清空搜索", - "enter-to-search-web": "点击回车搜索" + "enter-to-search-web": "点击回车搜索", + "enter-to-open-url": "按回车键打开网址" }, "splash-screen": { "loading": "加载中" @@ -46,9 +52,14 @@ }, "app-info": { "title": "应用信息", - "support-dashy-link": "贡献页面", + "sponsor-heading": "赞助 Dashy", + "sponsor-l1-prefix": "喜欢 Dashy 吗?请考虑", + "sponsor-l1-link": "在 GitHub 上赞助我", + "sponsor-l1-suffix": "以支持持续开发 🩷", "report-bug": "报告漏洞", "report-bug-description": "如果您认为发现了漏洞,请", + "report-bug-debug-link": "查看调试菜单", + "report-bug-middle": "并", "report-bug-link": "提交问题", "more-info": "更多信息", "source": "源代码", @@ -62,8 +73,6 @@ "privacy-and-security-security-policy": "安全政策", "license": "许可证", "license-under": "根据许可证授权", - "licence-third-party": "有关第三方模块的许可证,请参阅", - "licence-third-party-link": "法律信息", "list-contributors": "查看所有贡献者和致谢的完整列表,请查看", "list-contributors-link": "致谢", "version": "版本" @@ -84,13 +93,12 @@ "disabled-note": "您的管理员已禁用某些配置功能", "small-screen-note": "您正在使用非常小的屏幕,某些菜单屏幕可能不够优化", "app-info-button": "应用详情", + "debug-info-button": "调试信息", "backup-note": "建议在进行更改之前备份你的配置。", "reset-config-msg-l1": "这将从本地存储中删除所有用户设置,但不会影响 conf.yml 文件。", "reset-config-msg-l2": "如果想在以后使用它们,应该首先备份你所做的任何更改。", "reset-config-msg-l3": "确定执行吗?", "data-cleared-msg": "成功清空数据", - "actions-label": "行为", - "copy-config-label": "复制设置", "data-copied-msg": "设置已保存在剪切板", "reset-config-label": "重置设置", "css-save-btn": "保存更改", @@ -109,30 +117,119 @@ "workspace": "工作空间", "minimal": "最小化" }, + "remote-config": { + "title": "加载远程配置?", + "apply-button": "应用预览", + "intro": "您正在从外部 URL 加载配置。是否继续?", + "warning": "请确保在继续之前已阅读所加载的内容。一旦加载,您的浏览器将能够执行此配置中指定的任何客户端代码。", + "field-title": "标题", + "field-source": "来源", + "field-sections": "分组", + "field-items": "项目", + "preview-note": "仅预览。这不会对磁盘做任何更改,也不会以任何方式影响您的 Dashy 实例。", + "applied-toast": "已应用远程配置(仅预览)", + "previewing-banner": "正在预览远程配置", + "revert-button": "还原", + "error-invalid-url": "远程配置 URL 无效(必须为 http 或 https)", + "error-fetch": "无法获取远程配置。请检查 URL 是否可达,且允许 CORS。", + "error-parse": "远程配置不是有效的 YAML", + "error-not-config": "远程文件不像是 Dashy 配置", + "error-apply": "应用远程配置失败" + }, "settings": { "theme-label": "主题", "layout-label": "布局", "layout-auto": "自动", "layout-horizontal": "水平", "layout-vertical": "垂直", + "layout-masonry": "瀑布流", "item-size-label": "尺寸", "item-size-small": "小", "item-size-medium": "中", "item-size-large": "大", + "language-label": "语言", "config-launcher-label": "设置", "config-launcher-tooltip": "更新设置", + "nav-links-tooltip": "导航与配置", + "toggle-nav-aria": "切换导航菜单", "sign-out-tooltip": "注销", "sign-in-tooltip": "登录", "sign-in-welcome": "你好 {username}!", + "please-login": "登录以获得完整访问权限", "hide": "隐藏", - "open": "打开" + "options-title": "选项", + "options-tooltip": "选项" }, "updates": { "app-version-note": "Dashy 版本", "up-to-date": "已经是最新版了", "out-of-date": "存在可用更新", "unsupported-version-l1": "你使用的是不受支持的 Dashy 版本", - "unsupported-version-l2": "为获得最佳体验和最近的安全补丁,请更新至" + "unsupported-version-l2": "为获得最佳体验和最近的安全补丁,请更新至", + "sw-update-available": "Dashy 有新版本可用。", + "sw-update-action": "刷新" + }, + "debug": { + "getting-help": "获取帮助", + "getting-help-msg": "如果您遇到问题,社区可以提供帮助。", + "resources": { + "troubleshooting-name": "故障排除", + "troubleshooting-desc": "我们在此处概述了常见问题的解决方法", + "docs-name": "文档", + "docs-desc": "使用指南和配置说明", + "source-name": "源代码", + "source-desc": "或者,您可以从代码中确定问题", + "email-name": "邮件支持", + "email-note": "(仅限赞助者)", + "email-desc": "给我留言,我会尽力提供帮助" + }, + "reporting-bug": "报告漏洞", + "reporting-bug-l1-prefix": "如果您发现某些功能未按预期工作,请", + "reporting-bug-l1-link": "提交问题", + "reporting-bug-l1-suffix": "至我们的 GitHub 仓库。", + "reporting-bug-l2-intro": "提交漏洞报告时,请包含相关信息,例如:", + "reporting-bug-list": { + "version": "Dashy 版本", + "deployment": "部署类型", + "errors": "错误日志", + "config": "配置片段", + "overrides": "相关覆盖项", + "environment": "环境信息" + }, + "reporting-bug-note": "请务必删除任何敏感信息。如果工单未包含足够的信息用于调试、诊断和修复,可能会被关闭。由于时间有限,赞助者的支持和功能请求会被优先处理。", + "app-version": "应用版本", + "error-log": "错误日志", + "error-log-hint-prefix": "请参阅", + "error-log-hint-link": "此处", + "error-log-hint-suffix": "以了解如何查看浏览器日志。服务端日志中可能也有有用的信息。", + "no-errors": "本次会话未记录错误。", + "current-config": "当前配置", + "current-config-hint": "提交问题时,请包含您配置文件的相关部分。", + "config-render-error": "无法渲染配置:{error}", + "local-storage": "本地存储", + "local-storage-hint": "保存在此浏览器中的设置。在清除之前,这些设置将覆盖默认值。", + "no-local-storage": "本地未存储任何内容。", + "local-storage-error": "无法读取本地存储:{error}", + "environment": "环境", + "environment-hint": "提交漏洞报告时附上的浏览器与设备信息。", + "env": { + "unknown": "未知", + "yes": "是", + "no": "否", + "browser": "浏览器", + "os": "操作系统", + "viewport": "视口", + "screen": "屏幕", + "dpr": "像素比", + "languages": "语言", + "timezone": "时区", + "colorScheme": "颜色方案", + "reducedMotion": "减少动态效果", + "online": "在线", + "route": "当前路由", + "mode": "构建模式", + "swActive": "Service Worker" + } }, "language-switcher": { "title": "更改应用语言", @@ -153,24 +250,31 @@ "reset-toast": "{theme} 的自定义颜色已经移除" }, "config-editor": { - "save-location-label": "保存位置", - "location-local-label": "应用到本地", - "location-disk-label": "将变更写入配置文件", - "save-button": "保存变更", "preview-button": "预览更改", "valid-label": "设置有效", - "status-success-msg": "任务完成", - "status-fail-msg": "任务失败", "success-msg-disk": "配置文件保存成功", "success-msg-local": "本地变更保存成功", - "success-note-l1": "应用需要自动重建", - "success-note-l2": "这将持续一段时间", - "success-note-l3": "你需要刷新页面以使变更生效", - "error-msg-save-mode": "请选择保存位置:本地或者文件", "error-msg-cannot-save": "保存配置时出错", - "error-msg-bad-json": "JSON 错误,可能格式错误", - "warning-msg-validation": "验证警告", - "not-admin-note": "不能保存变更到配置文件,因为你没有以管理员身份登录" + "not-admin-note": "不能保存变更到配置文件,因为你没有以管理员身份登录", + "preview-applied-msg": "配置已预览。关闭此弹窗以查看更改。", + "reset-confirm-msg": "丢弃所有未保存的更改并还原到原始配置?", + "wrap-label": "换行", + "format-label": "格式化", + "reset-label": "重置", + "reset-tooltip": "丢弃所有编辑并还原到原始配置", + "download-tooltip": "下载为 conf.yml", + "download-label": "下载 YAML", + "copy-tooltip": "复制 YAML 到剪贴板", + "copy-label": "复制 YAML", + "copy-success-msg": "配置已复制到剪贴板", + "copy-fail-msg": "复制失败:{message}", + "format-fail-msg": "无法格式化:{message}", + "status-valid": "有效", + "status-invalid": "无效", + "status-warning": "{n} 个警告", + "status-warnings": "{n} 个警告", + "parse-fail-msg": "YAML 解析错误:{message}", + "editor-init-fail-msg": "编辑器启动失败:{message}" }, "cloud-sync": { "title": "云备份&恢复", @@ -179,7 +283,6 @@ "intro-docs": "文档", "intro-l3": "有关更多信息,请参阅", "backup-title-setup": "创建备份", - "backup-title-update": "更新备份", "password-label-setup": "选择密码", "password-label-update": "输入密码", "backup-button-setup": "备份", @@ -196,17 +299,6 @@ "backup-success-msg": "备份完成", "restore-success-msg": "恢复完成" }, - "menu": { - "open-section-title": "打开方式", - "sametab": "在当前页打开", - "newtab": "在新标签页打开", - "modal": "在弹出窗口打开", - "workspace": "在工作空间打开", - "options-section-title": "选项", - "edit-item": "编辑", - "move-item": "复制或移动", - "remove-item": "删除" - }, "context-menus": { "item": { "open-section-title": "打开方式", @@ -215,6 +307,7 @@ "modal": "在弹出窗口打开", "workspace": "在工作空间打开", "clipboard": "复制到剪切板", + "newwindow": "在新窗口打开", "options-section-title": "选项", "edit-item": "编辑", "move-item": "复制或移动", @@ -225,20 +318,24 @@ "open-section": "打开 Section", "edit-section": "编辑", "expand-collapse": "展开 / 折叠", - "move-section": "移动到", - "remove-section": "删除" + "remove-section": "删除", + "section-options": "分组选项" } }, "interactive-editor": { "menu": { "start-editing-tooltip": "进入交互编辑器", + "config-unavailable": "配置编辑器不可用", "edit-site-data-subheading": "修改网站数据", + "edit-config-as-code-btn": "以代码形式编辑配置", + "edit-config-as-code-tooltip": "打开 YAML 编辑器以编辑原始配置文件", "edit-page-info-btn": "修改页面信息", "edit-page-info-tooltip": "应用标题、描述、导航链接、页尾文本等等。", "edit-app-config-btn": "修改应用设置", "edit-app-config-tooltip": "所有应用程序的其他设置选项", "edit-pages-btn": "修改页面", "edit-pages-tooltip": "添加或删除其他的视图", + "edit-pages-subconfig-disabled": "页面仅能在根配置中编辑", "config-save-methods-subheading": "配置保存选项", "save-locally-btn": "暂存本地", "save-locally-tooltip": "将设置保存在浏览器上。这不会影响配置文件,仅作用于当前的浏览器。", @@ -252,6 +349,7 @@ "edit-raw-config-tooltip": "通过 JSON 编辑器查看和修改原始配置", "cancel-changes-btn": "放弃修改", "cancel-changes-tooltip": "放弃当前修改,并退出编辑模式。这不会影响你已保存的配置。", + "leave-while-editing-confirm": "您有未保存的更改。离开此页面并丢弃这些更改?", "edit-mode-name": "编辑模式", "edit-mode-subtitle": "你已进入编辑模式", "edit-mode-description": "你可以对配置进行修改并预览,在保存之前,你的任何更改都不会被保留。", @@ -268,6 +366,24 @@ "edit-tooltip": "单击以修改,或右键点击以获取更多选项", "remove-confirm": "确定要删除此部分吗? 此操作可以稍后撤消。" }, + "edit-widget": { + "edit-widget-title": "编辑小组件", + "add-widget-title": "添加新小组件", + "add-widget-btn": "添加小组件", + "type-label": "小组件类型", + "label-label": "标签", + "update-interval-label": "更新间隔(秒)", + "timeout-label": "超时(毫秒)", + "use-proxy-label": "使用代理", + "ignore-errors-label": "忽略错误", + "options-heading": "小组件选项", + "add-option-btn": "添加选项", + "key-placeholder": "键", + "value-placeholder": "值", + "missing-type-err": "小组件类型是必需的", + "remove-widget": "移除小组件", + "remove-confirm": "确定要移除此小组件吗?此操作可以稍后撤消。" + }, "edit-app-config": { "warning-msg-title": "谨慎处理", "warning-msg-l1": "以下选项用于高级应用配置。", @@ -276,16 +392,43 @@ "warning-msg-l3": "以避免意外的结果。" }, "export": { - "export-title": "导出配置", "copy-clipboard-btn": "复制到剪切板", "copy-clipboard-tooltip": "以YAML格式,将所有配置复制到剪切板 ", "download-file-btn": "下载配置文件", - "download-file-tooltip": "以YAML文件格式,保存所有配置到本地" + "download-file-tooltip": "以YAML文件格式,保存所有配置到本地", + "current-config-title": "当前配置", + "preview-toggle": "YAML 预览", + "issues-title": "架构问题 ({n})", + "config-list-title": "配置列表", + "col-title": "标题", + "col-path": "路径", + "col-content": "内容", + "col-status": "状态", + "col-actions": "操作", + "content-summary": "{sections} 个分组,{items} 个项目,{widgets} 个小组件", + "status-loading": "加载中", + "status-valid": "有效", + "status-warnings": "{n} 个警告", + "status-error": "错误", + "status-unknown": "未知", + "edit-current-btn": "编辑配置", + "edit-current-tooltip": "在 JSON 编辑器中打开当前配置", + "copy-fail-msg": "无法复制到剪贴板,请查看日志", + "download-row-tooltip": "下载 YAML", + "preview-row-tooltip": "切换 YAML 预览", + "apply-row-tooltip": "应用配置" } }, + "critical-error": { + "title": "配置加载错误", + "subtitle": "由于配置错误,Dashy 未能正确加载。", + "sub-ensure-that": "请确保", + "sub-error-details": "错误详情", + "sub-next-steps": "后续步骤", + "ignore-button": "忽略严重错误" + }, "widgets": { "general": { - "loading": "加载中...", "show-more": "显示更多", "cpu-details": "CPU 详情", "mem-details": "内存 详情", @@ -326,13 +469,9 @@ "good-service-rest": "所有其他线路正常" }, "synology-download": { - "download": "下载", - "upload": "上传", "downloaded": "已下载", "uploaded": "已上传", - "remaining": "剩余", - "up": "上行", - "down": "下行" + "remaining": "剩余" }, "gluetun-status": { "vpn-ip": "VPN IP", @@ -342,7 +481,8 @@ "post-code": "邮编", "location": "位置", "timezone": "时区", - "organization": "组织" + "organization": "组织", + "forwarded-port": "转发端口" }, "nextcloud": { "active": "激活", diff --git a/src/assets/locales/zh-TW.json b/src/assets/locales/zh-TW.json index 0763bf59..a7676a66 100644 --- a/src/assets/locales/zh-TW.json +++ b/src/assets/locales/zh-TW.json @@ -1,4 +1,9 @@ { + "general": { + "close-modal": "關閉對話框", + "confirm": "確認", + "cancel": "取消" + }, "home": { "no-results": "沒有結果", "no-data": "未設定資料", @@ -8,7 +13,8 @@ "search-label": "搜尋", "search-placeholder": "開始輸入以篩選", "clear-search-tooltip": "清空搜尋", - "enter-to-search-web": "按下 Enter 鍵來搜尋網際網路" + "enter-to-search-web": "按下 Enter 鍵來搜尋網際網路", + "enter-to-open-url": "按下 Enter 鍵以開啟網址" }, "splash-screen": { "loading": "正在載入" @@ -46,9 +52,14 @@ }, "app-info": { "title": "應用程式資訊", - "support-dashy-link": "貢獻頁面", + "sponsor-heading": "贊助 Dashy", + "sponsor-l1-prefix": "喜歡 Dashy 嗎?請考慮", + "sponsor-l1-link": "在 GitHub 上贊助我", + "sponsor-l1-suffix": "以協助持續開發 🩷", "report-bug": "回報錯誤", "report-bug-description": "若您認為您找到錯誤,請", + "report-bug-debug-link": "查看除錯選單", + "report-bug-middle": "並", "report-bug-link": "提出問題", "more-info": "更多資訊", "source": "原始碼", @@ -62,8 +73,6 @@ "privacy-and-security-security-policy": "安全政策", "license": "授權條款", "license-under": "授權條款為", - "licence-third-party": "關於第三方模組的授權條款,請參閱", - "licence-third-party-link": "法律聲明", "list-contributors": "關於貢獻和致謝者的完整名單,請參閱", "list-contributors-link": "致謝", "version": "版本" @@ -84,13 +93,12 @@ "disabled-note": "某些設定功能被您的管理員停用", "small-screen-note": "您使用的裝置螢幕過小,此選單的某些畫面可能會跑版", "app-info-button": "App 詳細資訊", + "debug-info-button": "除錯資訊", "backup-note": "建議在對您的設定做出變更之前先進行備份。", "reset-config-msg-l1": "這會移除所有的本機設定,但不會影響 ‘conf.yml’ 檔案。", "reset-config-msg-l2": "如果您未來還想使用您所做出的本機變更的話,請先備份。", "reset-config-msg-l3": "您確定要繼續嗎?", "data-cleared-msg": "已成功清空資料", - "actions-label": "動作", - "copy-config-label": "複製設定", "data-copied-msg": "設定已複製到剪貼簿", "reset-config-label": "重置設定", "css-save-btn": "儲存變更", @@ -109,30 +117,119 @@ "workspace": "工作區", "minimal": "最小化" }, + "remote-config": { + "title": "載入遠端設定?", + "apply-button": "套用預覽", + "intro": "您正在從外部網址載入設定。您要繼續嗎?", + "warning": "請務必先閱讀您所載入的內容後再繼續。一旦載入,您的瀏覽器將可能執行此設定中所指定的任何用戶端程式碼。", + "field-title": "標題", + "field-source": "來源", + "field-sections": "區塊", + "field-items": "項目", + "preview-note": "僅供預覽。這不會對硬碟做出任何變更,也不會以任何方式影響您的 Dashy 實例。", + "applied-toast": "已套用遠端設定(僅預覽)", + "previewing-banner": "正在預覽遠端設定", + "revert-button": "還原", + "error-invalid-url": "遠端設定的網址無效(必須為 http 或 https)", + "error-fetch": "無法取得遠端設定。請確認網址可達,且已允許 CORS。", + "error-parse": "遠端設定不是有效的 YAML", + "error-not-config": "遠端檔案似乎不是 Dashy 設定", + "error-apply": "套用遠端設定失敗" + }, "settings": { "theme-label": "主題", "layout-label": "版面配置", "layout-auto": "自動", "layout-horizontal": "水平", "layout-vertical": "垂直", + "layout-masonry": "瀑布流", "item-size-label": "尺寸", "item-size-small": "小", "item-size-medium": "中", "item-size-large": "大", + "language-label": "語言", "config-launcher-label": "設定", "config-launcher-tooltip": "更新設定", + "nav-links-tooltip": "導覽與設定", + "toggle-nav-aria": "切換導覽選單", "sign-out-tooltip": "登出", "sign-in-tooltip": "登入", "sign-in-welcome": "您好 {username}!", + "please-login": "登入以取得完整存取權限", "hide": "隱藏", - "open": "開啟" + "options-title": "選項", + "options-tooltip": "選項" }, "updates": { "app-version-note": "Dashy 版本", "up-to-date": "已經是最新版本", "out-of-date": "有可用更新", "unsupported-version-l1": "您使用的是不受支援的 Dashy 版本", - "unsupported-version-l2": "為獲得最佳體驗和最新的安全性更新,請更新至" + "unsupported-version-l2": "為獲得最佳體驗和最新的安全性更新,請更新至", + "sw-update-available": "Dashy 有新版本可用。", + "sw-update-action": "重新整理" + }, + "debug": { + "getting-help": "取得協助", + "getting-help-msg": "如果您遇到問題,社群可以提供協助。", + "resources": { + "troubleshooting-name": "疑難排解", + "troubleshooting-desc": "我們在此列出了常見問題的解決方法", + "docs-name": "說明文件", + "docs-desc": "使用指南與設定說明", + "source-name": "原始碼", + "source-desc": "或者,您可以從程式碼中找出問題", + "email-name": "電子郵件支援", + "email-note": "(僅限贊助者)", + "email-desc": "歡迎來信,我會盡力協助" + }, + "reporting-bug": "回報錯誤", + "reporting-bug-l1-prefix": "如果您發現某些功能未按預期運作,請", + "reporting-bug-l1-link": "提出問題", + "reporting-bug-l1-suffix": "至我們的 GitHub 儲存庫。", + "reporting-bug-l2-intro": "提交錯誤報告時,請包含相關資訊,例如:", + "reporting-bug-list": { + "version": "Dashy 版本", + "deployment": "部署類型", + "errors": "錯誤紀錄", + "config": "設定片段", + "overrides": "相關覆寫項目", + "environment": "環境資訊" + }, + "reporting-bug-note": "請務必移除任何敏感資訊。若工單未包含足夠的資訊以進行除錯、診斷與修正,可能會被關閉。由於時間有限,贊助者的支援與功能請求會優先處理。", + "app-version": "應用程式版本", + "error-log": "錯誤紀錄", + "error-log-hint-prefix": "請參閱", + "error-log-hint-link": "此處", + "error-log-hint-suffix": "以了解如何查看瀏覽器日誌。伺服器日誌中可能也包含有用的資訊。", + "no-errors": "本次工作階段未記錄任何錯誤。", + "current-config": "目前設定", + "current-config-hint": "提出問題時,請包含您設定檔的相關部分。", + "config-render-error": "無法呈現設定:{error}", + "local-storage": "本機儲存空間", + "local-storage-hint": "儲存在此瀏覽器中的設定。在清除之前,這些設定會覆蓋預設值。", + "no-local-storage": "本機未儲存任何內容。", + "local-storage-error": "無法讀取本機儲存空間:{error}", + "environment": "環境", + "environment-hint": "提交錯誤報告時要附上的瀏覽器與裝置資訊。", + "env": { + "unknown": "未知", + "yes": "是", + "no": "否", + "browser": "瀏覽器", + "os": "作業系統", + "viewport": "可視區域", + "screen": "螢幕", + "dpr": "像素比", + "languages": "語言", + "timezone": "時區", + "colorScheme": "色彩模式", + "reducedMotion": "減少動態效果", + "online": "線上", + "route": "目前路由", + "mode": "建置模式", + "swActive": "Service Worker" + } }, "language-switcher": { "title": "更改應用程式語言", @@ -153,24 +250,31 @@ "reset-toast": "{theme} 的自訂顏色已移除" }, "config-editor": { - "save-location-label": "儲存位置", - "location-local-label": "套用到本機", - "location-disk-label": "將變更寫入設定檔", - "save-button": "儲存變更", "preview-button": "預覽變更", "valid-label": "設定有效", - "status-success-msg": "工作完成", - "status-fail-msg": "工作失敗", "success-msg-disk": "設定檔已成功寫入硬碟", "success-msg-local": "本機變更已儲存成功", - "success-note-l1": "應用程式應會自動重新建置。", - "success-note-l2": "可能會需要一分鐘", - "success-note-l3": "您需要重新整理頁面以套用變更。", - "error-msg-save-mode": "请選擇儲存位置:本機或者檔案", "error-msg-cannot-save": "儲存設定時發生錯誤", - "error-msg-bad-json": "JSON 錯誤,可能是格式錯誤", - "warning-msg-validation": "驗證警告", - "not-admin-note": "您無法將變更寫入磁碟,因為您未以系統管理員身分登入" + "not-admin-note": "您無法將變更寫入磁碟,因為您未以系統管理員身分登入", + "preview-applied-msg": "已預覽設定。關閉此視窗以查看變更。", + "reset-confirm-msg": "捨棄所有未儲存的變更並還原為原始設定?", + "wrap-label": "換行", + "format-label": "格式化", + "reset-label": "重設", + "reset-tooltip": "捨棄所有編輯內容並還原為原始設定", + "download-tooltip": "下載為 conf.yml", + "download-label": "下載 YAML", + "copy-tooltip": "複製 YAML 到剪貼簿", + "copy-label": "複製 YAML", + "copy-success-msg": "設定已複製到剪貼簿", + "copy-fail-msg": "複製失敗:{message}", + "format-fail-msg": "無法格式化:{message}", + "status-valid": "有效", + "status-invalid": "無效", + "status-warning": "{n} 個警告", + "status-warnings": "{n} 個警告", + "parse-fail-msg": "YAML 解析錯誤:{message}", + "editor-init-fail-msg": "編輯器啟動失敗:{message}" }, "cloud-sync": { "title": "雲端備份 & 復原", @@ -179,7 +283,6 @@ "intro-l3": "若想了解更多資訊,請參考", "intro-docs": "說明文件", "backup-title-setup": "建立備份", - "backup-title-update": "更新備份", "password-label-setup": "選擇密碼", "password-label-update": "輸入密碼", "backup-button-setup": "備份", @@ -196,17 +299,6 @@ "backup-success-msg": "備份成功", "restore-success-msg": "設定已成功復原" }, - "menu": { - "open-section-title": "以...開啟", - "sametab": "目前分頁", - "newtab": "新分頁", - "modal": "彈出視窗", - "workspace": "工作區", - "options-section-title": "選項", - "edit-item": "編輯", - "move-item": "複製或移動", - "remove-item": "移除" - }, "context-menus": { "item": { "open-section-title": "以...開啟", @@ -215,6 +307,7 @@ "modal": "彈出視窗", "workspace": "工作區", "clipboard": "複製到剪貼簿", + "newwindow": "新視窗", "options-section-title": "選項", "edit-item": "編輯", "move-item": "複製或移動", @@ -225,20 +318,24 @@ "open-section": "開啟區域", "edit-section": "編輯", "expand-collapse": "展開 / 收合", - "move-section": "移動至", - "remove-section": "移除" + "remove-section": "移除", + "section-options": "區塊選項" } }, "interactive-editor": { "menu": { "start-editing-tooltip": "進入互動式編輯器", + "config-unavailable": "設定編輯器無法使用", "edit-site-data-subheading": "編輯網站資料", + "edit-config-as-code-btn": "以程式碼形式編輯設定", + "edit-config-as-code-tooltip": "開啟 YAML 編輯器以編輯原始設定檔", "edit-page-info-btn": "編輯頁面資訊", "edit-page-info-tooltip": "應用程式標題、說明、導航列連結、頁尾文字...等", "edit-app-config-btn": "編輯應用程式設定", "edit-app-config-tooltip": "其餘應用程式設定選項", "edit-pages-btn": "編輯頁面", "edit-pages-tooltip": "新增或移除額外檢視", + "edit-pages-subconfig-disabled": "頁面僅能從根層級設定中編輯", "config-save-methods-subheading": "設定儲存選項", "save-locally-btn": "本機儲存", "save-locally-tooltip": "將設定儲存到瀏覽器儲存空間中。這不會影響到您的設定檔,但變更僅會儲存在此裝置", @@ -252,6 +349,7 @@ "edit-raw-config-tooltip": "透過 JSON 編輯器檢視或編輯原始設定", "cancel-changes-btn": "取消編輯", "cancel-changes-tooltip": "重設目前的更動,並退出編輯模式。這不會影響您已儲存的設定", + "leave-while-editing-confirm": "您有未儲存的變更。離開此頁面並捨棄變更?", "edit-mode-name": "編輯模式", "edit-mode-subtitle": "目前為編輯模式", "edit-mode-description": "這代表您可以編輯設定並預覽。但在您儲存前,變更將不被儲存。", @@ -268,6 +366,24 @@ "edit-tooltip": "點擊以編輯,右鍵以查看更多選項", "remove-confirm": "您確定要移除此區塊嗎?此操作可復原。" }, + "edit-widget": { + "edit-widget-title": "編輯小工具", + "add-widget-title": "新增小工具", + "add-widget-btn": "新增小工具", + "type-label": "小工具類型", + "label-label": "標籤", + "update-interval-label": "更新間隔(秒)", + "timeout-label": "逾時(毫秒)", + "use-proxy-label": "使用代理", + "ignore-errors-label": "忽略錯誤", + "options-heading": "小工具選項", + "add-option-btn": "新增選項", + "key-placeholder": "鍵", + "value-placeholder": "值", + "missing-type-err": "必須選擇小工具類型", + "remove-widget": "移除小工具", + "remove-confirm": "您確定要移除此小工具嗎?此操作可復原。" + }, "edit-app-config": { "warning-msg-title": "謹慎操作", "warning-msg-l1": "下列選項用於進階應用程式設定。", @@ -276,17 +392,46 @@ "warning-msg-l3": "以避免意外後果。" }, "export": { - "export-title": "匯出設定", "copy-clipboard-btn": "複製至剪貼簿", "copy-clipboard-tooltip": "將所有應用程式設定以 YAML 格式複製到系統剪貼簿。", "download-file-btn": "下載檔案", - "download-file-tooltip": "將所有應用程式設定以 YAML 檔案下載到您的裝置。" + "download-file-tooltip": "將所有應用程式設定以 YAML 檔案下載到您的裝置。", + "current-config-title": "目前設定", + "preview-toggle": "YAML 預覽", + "issues-title": "結構描述問題 ({n})", + "config-list-title": "設定清單", + "col-title": "標題", + "col-path": "路徑", + "col-content": "內容", + "col-status": "狀態", + "col-actions": "操作", + "content-summary": "{sections} 個區塊,{items} 個項目,{widgets} 個小工具", + "status-loading": "載入中", + "status-valid": "有效", + "status-warnings": "{n} 個警告", + "status-error": "錯誤", + "status-unknown": "未知", + "edit-current-btn": "編輯設定", + "edit-current-tooltip": "在 JSON 編輯器中開啟目前設定", + "copy-fail-msg": "無法複製到剪貼簿,請查看記錄", + "download-row-tooltip": "下載 YAML", + "preview-row-tooltip": "切換 YAML 預覽", + "apply-row-tooltip": "套用設定" } }, + "critical-error": { + "title": "設定載入錯誤", + "subtitle": "由於設定錯誤,Dashy 無法正確載入。", + "sub-ensure-that": "請確認", + "sub-error-details": "錯誤詳情", + "sub-next-steps": "後續步驟", + "ignore-button": "忽略嚴重錯誤" + }, "widgets": { "general": { - "loading": "正在載入...", "show-more": "展開詳細資訊", + "cpu-details": "CPU 詳細資訊", + "mem-details": "記憶體詳細資訊", "show-less": "顯示較少", "open-link": "繼續閱讀" }, @@ -324,13 +469,9 @@ "good-service-rest": "所有其他路線正常營運" }, "synology-download": { - "download": "下載", - "upload": "上傳", "downloaded": "已下載", "uploaded": "已上傳", - "remaining": "剩餘", - "up": "上傳", - "down": "下載" + "remaining": "剩餘" }, "gluetun-status": { "vpn-ip": "VPN IP", @@ -340,7 +481,8 @@ "post-code": "郵遞區號", "location": "地區", "timezone": "時區", - "organization": "組織" + "organization": "組織", + "forwarded-port": "轉送連接埠" }, "nextcloud": { "active": "啟用", diff --git a/src/assets/locales/zz-pirate.json b/src/assets/locales/zz-pirate.json index a13cd002..25dbc745 100644 --- a/src/assets/locales/zz-pirate.json +++ b/src/assets/locales/zz-pirate.json @@ -51,21 +51,10 @@ "copied-toast": "Theme data for {theme} copied t' ye clipboard" }, "config-editor": { - "save-location-label": "Save Location", - "location-local-label": "Apply Locally", - "location-disk-label": "Write Changes to Config File", - "save-button": "Save Changes", "valid-label": "Config is Valid", - "status-success-msg": "Task Complete", - "status-fail-msg": "Task Failed", "success-msg-disk": "Th' config file written to disk successfully", "success-msg-local": "Ye local changes were successfully saved", - "success-note-l1": "th' app should rebuild automatically.", - "success-note-l2": "This may take up t' a minute.", - "success-note-l3": "ye will need t' refresh th' page fer changes t' take effect.", "error-msg-cannot-save": "An error occurred savin' config", - "error-msg-bad-json": "Error in ye JSON, possibly malformed", - "warning-msg-validation": "Validation Warnin' Ahead", "not-admin-note": "ye cannot write changed t' disk, because ye be not logged in as an admin" }, "cloud-sync": { @@ -73,7 +62,6 @@ "intro-l2": "All data be fully end-t'-end encrypted with AES, usin' yer password as th' key.", "intro-l3": "For more info, please see th'", "backup-title-setup": "Make ye Backup", - "backup-title-update": "Update ye Backup", "password-label-setup": "Choose ye Password", "password-label-update": "Enter yer Password", "backup-id-label": "Yer Backup ID", @@ -81,11 +69,5 @@ "backup-missing-password": "Missin'g' Password", "backup-error-unknown": "Unable t' process request", "backup-error-password": "Incorrect password. Walk the plank! Please enter yer current password." - }, - "menu": { - "sametab": "Stay Aboard", - "newtab": "Walk the Plank", - "modal": "Open in ye Pop-Up Ship", - "workspace": "Open on Workspace Deck" } } diff --git a/src/components/Settings/LanguageSwitcher.vue b/src/components/Settings/LanguageSwitcher.vue index 40dd73a0..b8ff6da9 100644 --- a/src/components/Settings/LanguageSwitcher.vue +++ b/src/components/Settings/LanguageSwitcher.vue @@ -127,11 +127,13 @@ export default {