Compare commits

..

No commits in common. "master" and "3.0.1" have entirely different histories.

85 changed files with 3454 additions and 5660 deletions

View file

@ -1,36 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Dashy",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye",
"customizations": {
"vscode": {
"extensions": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"ms-azuretools.vscode-docker",
"ms-edgedevtools.vscode-edge-devtools",
"firefox-devtools.vscode-firefox-debug",
"aaravb.chrome-extension-developer-tools"
]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "yarn install --ignore-engines --immutable --no-cache --network-timeout 300000 --network-concurrency 1"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -3,7 +3,7 @@ description: Report something that's not working the way it's (probably) intende
title: '[BUG] <title>'
labels: ['🐛 Bug']
assignees:
- Lissy93
- lissy93
body:
- type: dropdown
id: environment

View file

@ -2,8 +2,6 @@ name: Feature Request ✨
description: Suggest an idea for future development of Dashy
title: '[FEATURE_REQUEST] <title>'
labels: ['🦄 Feature Request']
assignees:
- Lissy93
body:
@ -61,4 +59,4 @@ body:
Please note that there is no guarantee that your idea will be implemented
If you haven't already done so, please Star the Dashy's repository on GitHub, to help other users discover it
validations:
required: false
required: false

View file

@ -2,8 +2,6 @@ name: Question 🤷‍♂️
description: Got a question about Dashy, deployment, development or usage?
title: '[QUESTION] <title>'
labels: ['🤷‍♂️ Question']
assignees:
- Lissy93
body:
# Filed 1 - Intro Text

View file

@ -2,8 +2,6 @@ name: Add your Dashboard to the Showcase 🌟
description: Share a screenshot of your dashboard to the Readme showcase!
title: '[SHOWCASE] <title>'
labels: ['💯 Showcase']
assignees:
- lissy93
body:
# 1 - Title
@ -57,4 +55,4 @@ body:
## That's It!
Thanks for sharing your dashboard :) You will receive an update to this ticket once it's added to the showcase
validations:
required: false
required: false

View file

@ -1,12 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

View file

@ -1,7 +1,4 @@
<!--
Thank you for contributing to Dashy!
So that your PR can be handled effectively, please populate the following fields
-->
*Thank you for contributing to Dashy! So that your PR can be handled effectively, please populate the following fields (delete sections that are not applicable)*
**Category**:
> One of: Bugfix / Feature / Code style update / Refactoring Only / Build related changes / Documentation / Other (please specify)
@ -23,5 +20,5 @@ So that your PR can be handled effectively, please populate the following fields
- [ ] There are no (new) build warnings or errors
- [ ] _(If a new config option is added)_ Attribute is outlined in the schema and documented
- [ ] _(If a new dependency is added)_ Package is essential, and has been checked out for security or performance
- [ ] _(If significant change)_ Bumps version in package.json
- [ ] Bumps version, if new feature added

View file

@ -0,0 +1,27 @@
# Based on a label applied to an issue, the bot will add a comment with some additional info
name: 🎯 Auto-Reply to Labeled Tickets
on:
issues:
types:
- labeled
- unlabeled
pull_request_target:
types:
- labeled
- unlabeled
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Label Commenter
uses: peaceiris/actions-label-commenter@v1
with:
config_file: .github/issue-auto-comments.yml
github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}

44
.github/workflows/auto-tag-pr.yml vendored Normal file
View file

@ -0,0 +1,44 @@
# Creates a new tag, whenever the app version (in package.json) is updated in master
# And marks any relevant issues as fixed
name: 🏗️ Release Tag new Versions
on:
push:
branches:
- master
jobs:
tag-pre-release:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
tag: ${{ steps.autotag.outputs.tagname }}
steps:
- uses: actions/checkout@v2
- uses: butlerlogic/action-autotag@stable
id: autotag
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
strategy: package
commit_message_template: "🔖 {{number}} {{message}} (by {{author}})\nSHA: {{sha}}\n."
github-release:
runs-on: ubuntu-latest
needs: tag-pre-release
if: ${{ needs.tag-pre-release.outputs.tag }}
steps:
- uses: actions/checkout@v2
- uses: ncipollo/release-action@v1
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
tag: ${{ needs.tag-pre-release.outputs.tag }}
bodyFile: ".github/LATEST_CHANGELOG.md"
mark-issue-fixed:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'issues' }}
steps:
- uses: actions/checkout@v2
- name: Label Fixed Issues
uses: gh-bot/fix-labeler@master
with:
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
label: '✅ Fixed'

View file

@ -0,0 +1,21 @@
# Close any issue that does not match any of the issue templates
name: 🎯 Close Incomplete Issues
on:
issues:
types: [opened, edited]
jobs:
auto_close_issues:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Automatically close issues that don't follow the issue template
uses: lucasbento/auto-close-issues@v1.0.2
with:
github-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
closed-issues-label: '🙁 Auto-Closed'
issue-close-message: |
Hello @${issue.user.login} 👋
Unfortunately your issue does not follow the format outlined in the template, and has therefore been auto-closed.
To ensure that all relevant info is included, please either update or recreate your issue, and complete the sub-headings provided.
Thank you :)

View file

@ -1,55 +0,0 @@
# When Dashy's version in package.json is updated
# this workflow will create a new tag
# And then publish it to the repository
name: 🏗️ Tag on Version Change
on:
workflow_dispatch:
push:
branches:
- master
paths:
- 'package.json'
jobs:
tag-if-version-updated:
runs-on: ubuntu-latest
steps:
- name: Check Out Repository 🛎️
uses: actions/checkout@v2
- name: Set Up Python 🐍
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Extract Version from package.json 🔢
id: package_version
run: |
import json
with open('package.json', 'r') as f:
version = json.load(f)['version']
print(f"::set-output name=VERSION::{version}")
shell: python
- name: Get Latest Tag 🏷️
id: latest_tag
run: |
git fetch --tags
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` 2>/dev/null)
echo "::set-output name=TAG::${latest_tag:-0}"
- name: Create and Push Tag ⤴️
if: steps.package_version.outputs.VERSION != steps.latest_tag.outputs.TAG && steps.latest_tag.outputs.TAG != '0'
run: |
git config --local user.email "liss-bot@d0h.co"
git config --local user.name "Liss-Bot"
git tag -a ${{ steps.package_version.outputs.VERSION }} -m "Release v${{ steps.package_version.outputs.VERSION }}"
git push origin ${{ steps.package_version.outputs.VERSION }}
env:
GIT_AUTHOR_NAME: Liss-Bot
GIT_AUTHOR_EMAIL: liss-bot@d0h.co
GIT_COMMITTER_NAME: Liss-Bot
GIT_COMMITTER_EMAIL: liss-bot@d0h.co
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}

View file

@ -1,10 +1,11 @@
# Scans, builds and releases a multi-architecture docker image
name: 🐳 Build + Publish Multi-Platform Image
on:
workflow_dispatch:
push:
branches: ['master']
tags: ['*.*']
tags: [v*]
paths:
- '**.js'
- 'src/**'
@ -32,7 +33,7 @@ jobs:
steps:
- name: 🛎️ Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: 🔖 Get App Version
uses: tyankatsu0105/read-package-version-actions@v1
@ -46,12 +47,8 @@ jobs:
${{ env.DH_IMAGE }}
ghcr.io/${{ env.GH_IMAGE }}
tags: |
type=ref,event=tag
type=semver,pattern={{version}},enable=false
type=semver,pattern={{major}}.x
type=raw,value=latest
flavor: |
latest=false
type=ref,event=tag,prefix=release-,suffix={{tag}}
type=semver,pattern={{raw}},value=${{ steps.package-version.outputs.version }}
labels: |
maintainer=Lissy93
org.opencontainers.image.title=Dashy
@ -66,10 +63,10 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
id: buildx
with:
use-buildkit: true
buildkit-daemon-opts: "--oci-worker-no-process-sandbox"
driver-opts: image=moby/buildkit:v0.10.6
- name: 👀 Inspect builder
run: |
@ -80,13 +77,13 @@ jobs:
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
- name: 🔑 Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🔑 Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -96,7 +93,7 @@ jobs:
uses: crazy-max/ghaction-docker-status@v1
- name: ⚒️ Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile

View file

@ -1,36 +0,0 @@
name: 🏗️ Draft New Release
on:
push:
tags:
- '^[0-9]+\.[0-9]+\.[0-9]+$'
- '**'
workflow_dispatch:
inputs:
tag:
description: 'Tag to draft a release for (must already exist)'
required: true
jobs:
create-draft-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code 🛎️
uses: actions/checkout@v2
with:
fetch-depth: 0 # We need all history for generating release notes
- name: Create Draft Release 📝
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
release_name: Release ${{ github.event.inputs.tag || github.ref_name }}
draft: true
prerelease: false
generate_release_notes: true
- name: Output new release URL ↗️
run: 'echo "Draft release URL: ${{ steps.create_release.outputs.html_url }}"'

68
.github/workflows/generate-credits.yml vendored Normal file
View file

@ -0,0 +1,68 @@
# Inserts list of contributors and community members into ./docs/credits.md
# Also generates an SVG showing all contributors, which is embedded into readme
name: 📊 Generate Contributor Credits
on:
workflow_dispatch: # Manual dispatch
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
jobs:
# Job #1 - Generate an embedded SVG asset, showing all contributors
generate-contributors:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/contributors-list@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
svgPath: docs/assets/CONTRIBUTORS.svg
affiliation: all
includeBots: false
excludeUsers: BeginCI snyk-bot
avatarSize: 96
userNameHeight: 20
svgWidth: 830
commitMessage: ':blue_heart: Updates contributor SVG'
# Job #2 - Fetches sponsors and inserts into readme and credits page
insert-sponsors:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2
- name: Generate Sponsors in Readme 💖
uses: JamesIves/github-sponsors-readme-action@1.0.5
with:
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
file: 'README.md'
- name: Generate Sponsors in Credits 💖
uses: JamesIves/github-sponsors-readme-action@1.0.5
with:
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
file: 'docs/credits.md'
# Job #3 - Update the Credits page
insert-credits:
runs-on: ubuntu-latest
name: Inserts contributors into credits.md
steps:
- name: Contributer List - Credits Page
uses: akhilmhdh/contributors-readme-action@v2.3.6
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
image_size: 80
readme_path: docs/credits.md
columns_per_row: 6
commit_message: ':purple_heart: Updates contributors list'
collaborators: all
committer_username: liss-bot
committer_email: liss-bot@d0h.co
- name: Sponsors List - Readme
uses: akhilmhdh/contributors-readme-action@v2.2
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
image_size: 80
readme_path: README.md
columns_per_row: 6
commit_message: ':yellow_heart: Updates sponsors table'
committer_username: liss-bot
committer_email: liss-bot@d0h.co

17
.github/workflows/release-commenter.yml vendored Normal file
View file

@ -0,0 +1,17 @@
# Adds a comment to all issues & PRs that were fixed on a new release
name: 💡 Update Issue after Release
on:
release:
types: [published]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: apexskier/github-release-commenter@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
label-template: 🛩️ Released {release_tag}
comment-template: |
**This has now been released in {release_name} ✨**
If you haven't done so already, please [update your instance](https://github.com/Lissy93/dashy/blob/master/docs/management.md#updating) to `{release_tag}` or later. See {release_link} for full info.

1
.gitignore vendored
View file

@ -13,6 +13,7 @@ yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj

65
.vscode/launch.json vendored
View file

@ -1,65 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "msedge",
"request": "launch",
"name": "dashy: edge",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}",
"breakOnLoad": true,
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}"
},
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
},
"preLaunchTask": "dashy start"
},
{
"type": "chrome",
"request": "launch",
"name": "dashy: chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}",
"breakOnLoad": true,
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}"
},
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
},
"preLaunchTask": "dashy start"
},
{
"type": "firefox",
"request": "launch",
"name": "dashy: firefox",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}",
"breakOnLoad": true,
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}"
},
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
},
"preLaunchTask": "dashy start"
}
]
}

21
.vscode/tasks.json vendored
View file

@ -1,21 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "dashy start",
"type": "npm",
"script": "dev",
"isBackground": true,
"problemMatcher": [
{
"base": "$tsc-watch",
"background": {
"activeOnStart": true,
"beginsPattern": "Starting development server",
"endsPattern": "Compiled successfully"
}
}
],
}
]
}

View file

@ -22,7 +22,7 @@ RUN yarn install --ignore-engines --immutable --no-cache --network-timeout 30000
COPY . ./
# Build initial app for production
RUN yarn build --mode production --no-clean
RUN yarn build --mode production
# Production stage
FROM node:20.11.1-alpine3.19
@ -42,7 +42,7 @@ RUN apk add --no-cache tzdata
COPY --from=BUILD_IMAGE /app ./
# Finally, run start command to serve up the built application
CMD [ "yarn", "build-and-start" ]
CMD [ "yarn", "start" ]
# Expose the port
EXPOSE ${PORT}

210
README.md
View file

@ -8,16 +8,6 @@
<b><a href="./docs/showcase.md">User Showcase</a></b> | <b><a href="https://demo.dashy.to">Live Demo</a></b> | <b><a href="./docs/quick-start.md">Getting Started</a></b> | <b><a href="https://dashy.to/docs">Documentation</a></b> | <b><a href="https://github.com/Lissy93/dashy">GitHub</a></b>
</p>
---
<p align="center">
<sup>Dashy is kindly sponsored by <a href="https://www.lambdatest.com/?utm_source=dashy&utm_medium=sponsor">LambdaTest</a> - Browser Testing</sup><br>
<a href="https://www.lambdatest.com/?utm_source=dashy&utm_medium=sponsor" target="_blank">
<img src="https://www.lambdatest.com/blue-logo.png" width="400" alt="LambdaTest" />
</a>
</p>
<p align="center">
<br>
<sup>Dashy is kindly sponsored by <a href="https://umbrel.com?ref=dashy">Umbrel</a> - the personal home cloud and OS for self-hosting</sup><br>
@ -26,7 +16,6 @@
</a>
</p>
> [!NOTE]
> Version [3.0.0](https://github.com/Lissy93/dashy/releases/tag/3.0.0) has been released, and requires some changes to your setup, see [#1529](https://github.com/Lissy93/dashy/discussions/1529) for details.
@ -37,7 +26,7 @@
- **Getting Started**
- [🌈 Features](#features-)
- [ Demo](#demo-)
- [Demo](#demo-)
- [🚀 Getting Started](#getting-started-)
- [🔧 Configuring](#configuring-)
- **Feature Overview**
@ -209,7 +198,7 @@ Both sections and items can have an icon associated with them, defined under the
The following icon types are supported:
- **Favicon** - Automatically fetch an apps icon from its Favicon or logo image
- **Icon Packs** - Use any icon from [font-awesome], [simple-icons], [selfh.st/icons], or [material icons]
- **Icon Packs** - Use any icon from [font-awesome], [simple-icons] or [material icons]
- **Emoji** - Any valid emoji can be used as an icon
- **Generative** - Unique, auto-generated images for easily identifying services
- **URL** - Pass the URL of any valid image in to have it fetched and rendered
@ -220,7 +209,6 @@ The following icon types are supported:
[font-awesome]: https://fontawesome.com/icons
[simple-icons]: https://simpleicons.org/
[material icons]: https://github.com/Templarian/MaterialDesign
[selfh.st/icons]: https://selfh.st/icons
[dashboard-icons]: https://github.com/WalkxCode/dashboard-icons
@ -253,7 +241,7 @@ Status indicators can be globally enabled by setting `appConfig.statusCheck: tru
> For full widget documentation, see: [**Widgets**](./docs/widgets.md)
You can display dynamic content from services in the form of widgets. There are several pre-built widgets available for showing useful info, and integrations with commonly self-hosted services, but you can also easily create your own for almost any app.
You can display dynamic content from services in the form of widgets. There are several pre-built widgets availible for showing useful info, and integrations with commonly self-hosted services, but you can also easily create your own for almost any app.
<p align="center">
@ -422,7 +410,6 @@ Dashy supports multiple languages and locales. When available, your language sho
- 🇩🇪 **German**: `de` - Contributed by **[@Niklashere](https://github.com/Niklashere)**
- 🇬🇷 **Greek**: `el` - Contributed by **[@aviolaris](https://github.com/aviolaris)**
- 🇮🇳 **Hindi**: `hi` - _Auto-generated_
- 🇭🇺 **Hungarian**: `hu` - Contributed by **[@apgyorfi](https://github.com/apgyorfi)**
- 🇮🇹 **Italian**: `it` - Contributed by **[@alexdelprete](https://github.com/alexdelprete)**
- 🇯🇵 **Japanese**: `ja` - _Auto-generated_
- 🇰🇷 **Korean**: `ko` - Contributed by **[@boggy-cs](https://github.com/boggy-cs)**
@ -430,14 +417,13 @@ Dashy supports multiple languages and locales. When available, your language sho
- 🇵🇱 **Polish**: `pl` - Contributed by **[@skaarj1989](https://github.com/skaarj1989)**
- 🇵🇹 **Portuguese**: `pt` - Contributed by **[@LeoColman](https://github.com/LeoColman)**
- 🛰️ **Galician**: `gl` - Contributed by **[@pvillaverde](https://github.com/pvillaverde)**
- 🇷🇺 **Russian**: `ru` -Contributed by **[@sasetz](https://github.com/sasetz)**
- 🇷🇺 **Russian**: `ru` - _Auto-generated_
- 🇸🇰 **Slovak**: `sk` - Contributed by **[@Smexhy](https://github.com/Smexhy)**
- 🇸🇮 **Slovenian**: `sl` - Contributed by **[@UrekD](https://github.com/UrekD)**
- 🇪🇸 **Spanish**: `es` - Contributed by **[@lu4t](https://github.com/lu4t)**
- 🇸🇪 **Swedish**: `sv` - Contributed by **[@BOZG](https://github.com/BOZG)**
- 🇹🇼 **Traditional Chinese**: `zh-TW` - Contributed by **[@stanly0726](https://github.com/stanly0726)**
- 🇹🇷 **Turkish**: `tr` - Contributed by **[@imsakg](https://github.com/imsakg)**
- 🇺🇦 **Ukrainian**: `uk` - Contributed by **[@allozavrr](https://github.com/allozavrr)**
- 🏴‍☠️ **Pirate**: `pirate` - Contributed by **[@Lissy93](https://github.com/lissy93)**
#### Add your Language
@ -536,11 +522,193 @@ Thank you so much to everyone who has helped with Dashy so far; every contributi
#### Sponsors
Huge thanks to the sponsors helping to support Dashy's development!
![sponsors badge](https://readme-contribs.as93.net/sponsors/lissy93)
<!-- readme: sponsors -start -->
<table>
<tr>
<td align="center">
<a href="https://github.com/undefined">
<img src="" width="80;" alt="undefined"/>
<br />
<sub><b>Undefined</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/koconder">
<img src="https://avatars.githubusercontent.com/u/25068?u=582657b23622aaa3dfe68bd028a780f272f456fa&v=4" width="80;" alt="koconder"/>
<br />
<sub><b>Vincent Koc</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Admonstrator">
<img src="https://avatars.githubusercontent.com/u/69824?u=1e226d7a36cdd661c3e4cd486fea140d045b7d57&v=4" width="80;" alt="Admonstrator"/>
<br />
<sub><b>Aaron Viehl</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tbjers">
<img src="https://avatars.githubusercontent.com/u/1117052?v=4" width="80;" alt="tbjers"/>
<br />
<sub><b>Torgny Bjers</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/emlazzarin">
<img src="https://avatars.githubusercontent.com/u/1141361?u=714e3487a3f2e0df721b01a0133945f075d3ff68&v=4" width="80;" alt="emlazzarin"/>
<br />
<sub><b>Eddy Lazzarin</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AnandChowdhary">
<img src="https://avatars.githubusercontent.com/u/2841780?u=747e554b3a7f12eb20b7910e1c87d817844f714f&v=4" width="80;" alt="AnandChowdhary"/>
<br />
<sub><b>Anand Chowdhary</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/shrippen">
<img src="https://avatars.githubusercontent.com/u/2873570?v=4" width="80;" alt="shrippen"/>
<br />
<sub><b>Shrippen</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bile0026">
<img src="https://avatars.githubusercontent.com/u/5022496?u=aec96ad173c0ea9baaba93807efa8a848af6595c&v=4" width="80;" alt="bile0026"/>
<br />
<sub><b>Zach Biles</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/UlisesGascon">
<img src="https://avatars.githubusercontent.com/u/5110813?u=3c41facd8aa26154b9451de237c34b0f78d672a5&v=4" width="80;" alt="UlisesGascon"/>
<br />
<sub><b>Ulises Gascón</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/digitalarche">
<img src="https://avatars.githubusercontent.com/u/6546135?u=564756d7f44ab2206819eb3148f6d822673f5066&v=4" width="80;" alt="digitalarche"/>
<br />
<sub><b>Digital Archeology</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/InDieTasten">
<img src="https://avatars.githubusercontent.com/u/7047377?u=8d8f8017628b38bc46dcbf3620e194b01d3fb2d1&v=4" width="80;" alt="InDieTasten"/>
<br />
<sub><b>InDieTasten</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/araguaci">
<img src="https://avatars.githubusercontent.com/u/7318668?v=4" width="80;" alt="araguaci"/>
<br />
<sub><b>Araguaci</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/bmcgonag">
<img src="https://avatars.githubusercontent.com/u/7346620?u=2a0f9284f3e12ac1cc15288c254d1ec68a5081e8&v=4" width="80;" alt="bmcgonag"/>
<br />
<sub><b>Brian McGonagill</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vlad-timofeev">
<img src="https://avatars.githubusercontent.com/u/11474041?u=eee43705b54d2ec9f51fc4fcce5ad18dd17c87e4&v=4" width="80;" alt="vlad-timofeev"/>
<br />
<sub><b>Vlad Timofeev</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/helixzz">
<img src="https://avatars.githubusercontent.com/u/12218889?u=d06d0c103dfbdb99450623064f7da3c5a3675fb6&v=4" width="80;" alt="helixzz"/>
<br />
<sub><b>HeliXZz</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/patvdv">
<img src="https://avatars.githubusercontent.com/u/12430107?u=e8911c2fb91af4d30432f76da8c40927b2830bd7&v=4" width="80;" alt="patvdv"/>
<br />
<sub><b>Patrick Van Der Veken</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/plgonzalezrx8">
<img src="https://avatars.githubusercontent.com/u/19900049?v=4" width="80;" alt="plgonzalezrx8"/>
<br />
<sub><b>Pedro Gonzalez</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mryesiller">
<img src="https://avatars.githubusercontent.com/u/24632172?u=0d20f2d615158f87cd60a3398d3efb026c32f291&v=4" width="80;" alt="mryesiller"/>
<br />
<sub><b>Göksel Yeşiller</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/allesauseinerhand">
<img src="https://avatars.githubusercontent.com/u/32039836?v=4" width="80;" alt="allesauseinerhand"/>
<br />
<sub><b>Allesauseinerhand</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lamtrinhdev">
<img src="https://avatars.githubusercontent.com/u/49742151?u=c5eaca5aa6841a80605cf4f7d0e861a9e6339ef3&v=4" width="80;" alt="lamtrinhdev"/>
<br />
<sub><b>LamTrinh.Dev</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Bastii717">
<img src="https://avatars.githubusercontent.com/u/53431819?u=604977bed6ad6875ada890d0d3765a4cacc2fa14&v=4" width="80;" alt="Bastii717"/>
<br />
<sub><b>Bastii717</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/M2TD">
<img src="https://avatars.githubusercontent.com/u/85460457?v=4" width="80;" alt="M2TD"/>
<br />
<sub><b>M2TD</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/frankdez93">
<img src="https://avatars.githubusercontent.com/u/87549420?v=4" width="80;" alt="frankdez93"/>
<br />
<sub><b>Frankdez93</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/NixyJuppie">
<img src="https://avatars.githubusercontent.com/u/138570196?u=b102c222487905728b858704962d32759df29ebe&v=4" width="80;" alt="NixyJuppie"/>
<br />
<sub><b>Nixy</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/nrvo">
<img src="https://avatars.githubusercontent.com/u/151435968?u=e1dcb307fd0efdc45cddbe9490a7b956e4da6835&v=4" width="80;" alt="nrvo"/>
<br />
<sub><b>Nrvo</b></sub>
</a>
</td></tr>
</table>
<!-- readme: sponsors -end -->
#### Contributors
[![contributors badge](https://readme-contribs.as93.net/contributors/lissy93/dashy?perRow=12&limit=96)](./docs/credits.md)
[![Auto-generated contributors](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/CONTRIBUTORS.svg)](./docs/credits.md)
#### Stats
[![Stats](https://repobeats.axiom.co/api/embed/48546e3c768d38c450ba136e32c0992bcf58ad73.svg "Repobeats analytics image")](https://github.com/Lissy93/dashy/blob/master/docs/credits.md)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 MiB

After

Width:  |  Height:  |  Size: 30 MiB

View file

@ -14,9 +14,6 @@
- [Deploying Keycloak](#1-deploy-keycloak)
- [Setting up Keycloak](#2-setup-keycloak-users)
- [Configuring Dashy for Keycloak](#3-enable-keycloak-in-dashy-config-file)
- [Toubleshooting Keycloak](#troubleshooting-keycloak)
- [OIDC Auth](#oidc)
- [authentik](#authentik)
- [Alternative Authentication Methods](#alternative-authentication-methods)
- [VPN](#vpn)
- [IP-Based Access](#ip-based-access)
@ -154,7 +151,7 @@ With basic auth, all logic is happening on the client-side, which could mean a s
If you'd like to protect all your config files from direct access, you can set the `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` environmental variables. You'll then be prompted to enter these credentials when visiting Dashy.
Then, if you'd like your frontend to automatically log you in, without prompting you for credentials (insecure, so only use on a trusted environment), then also specify `VUE_APP_BASIC_AUTH_USERNAME` and `VUE_APP_BASIC_AUTH_PASSWORD`. This is useful for when you're hosting Dashy on a private server, and just want to use auth for user management and to prevent direct access to your config files, while still allowing the frontend to access them. Note that a rebuild is required for these changes to take effect.
Then, if you'd like your frontend to automatically log you in, without prompting you for credentials, then also specify `VUE_APP_BASIC_AUTH_USERNAME` and `VUE_APP_BASIC_AUTH_PASSWORD`. This is useful for when you're hosting Dashy on a private server, and you want to prevent unauthorized access to your config files, while still allowing the frontend to access them. Note that a rebuild is required for these changes to take effect.
**[⬆️ Back to Top](#authentication)**
@ -256,246 +253,6 @@ From within the Keycloak console, you can then configure things like time-outs,
---
### Troubleshooting Keycloak
If you encounter issues with your Keycloak setup, follow these steps to troubleshoot and resolve common problems.
1. Client Authentication Issue
Problem: Redirect loop, if client authentication is enabled.
Solution: Switch off "client authentication" in "TC clients" -> "Advanced" settings.
2. Double URL
Problem: If you get redirected to "https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/my-realm"
Solution: Make sure to turn on "Exclude Issuer From Authentication Response" in "TC clients" -> "Advanced" -> "OpenID Connect Compatibility Modes"
3. Problems with mutiple Dashy Pages
Problem: Refreshing or logging out of dashy results in an "invalid_redirect_uri" error.
Solution: In "TC clients" -> "Access settings" -> "Root URL" https://dashy.my.domain/, valid redirect URIs must be /*
---
## OIDC
Dashy also supports using a general [OIDC compatible](https://openid.net/connect/) authentication server. In order to use it, the authentication section needs to be configured:
```yaml
appConfig:
auth:
enableOidc: true
oidc:
clientId: [registered client id]
endpoint: [OIDC endpoint]
scope: [The scope(s) to request from the OIDC provider]
adminGroup: admin
```
Because Dashy is a SPA, a [public client](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1) registration with PKCE is needed.
An example for Authelia is shared below, but other OIDC systems can be used:
```yaml
identity_providers:
oidc:
clients:
- client_id: dashy
client_name: dashy
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- https://dashy.local # should point to your dashy endpoint
grant_types:
- authorization_code
scopes:
- 'openid'
- 'profile'
- 'roles'
- 'email'
- 'groups'
```
Groups and roles will be populated and available for controlling display similar to [Keycloak](#Keycloak) above.
---
### authentik
This documentation is specific to `authentik`, however it may be useful in getting other idP's working with `Dashy`.
This guide will only walk through the following:
* Creating and configuring an OIDC provider
* Creating and configuring an application
* Assigning groups
* Configuring `Dashy` to use the OIDC client
* Show quick examples of how to hide/show `pages`, `items`, and `sections` using OIDC groups
This guide assumes the following:
* You have a working instance of `authentik` terminated with SSL
* You have a working instance of `Dashy` terminated with SSL
* Users and groups are provisioned
* You are familiar with how `authentik` works in case you need to do further troubleshooting that is outside the scope of this guide.
>[!TIP]
>It it recommended that you create groups specific for `Dashy`. Groups will allow you to display content based on group membership as well as limiting user access to `Dashy`. If you do not need this functionality, then you can forgo creating specific groups.
>[!TIP]
>You can use the application wizard to create the provider and application at one time. This is the recommended route, but only the manual process will be outlined in this guide.
![image](https://github.com/user-attachments/assets/72e45162-6c86-4d6f-a1ae-724ac503c00c)
#### 1. Create an OIDC provider
Login to the admin console for `authentik`. Go to `Applications` > `Providers`. Click `Create`.
![image](https://github.com/user-attachments/assets/c1f7f45d-469c-4bf1-a825-34658341a83e)
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.
![image](https://github.com/user-attachments/assets/e600aeaf-08d1-49aa-b304-11e90e5c89cd)
Scroll down and configure the `Protocol settings`. Set the `Client type` to `Public`. Add the `Redirect URIs/Origins (RegEx)`. If the site is hosted at `dashy.lan.domain.com`, then you would enter as the example below.
>[!NOTE]
>If you have an internal and external domain for `Dashy`, enter both URI's. Enter each URI on a new line.
![image](https://github.com/user-attachments/assets/4a289d7e-d7b4-4ff6-af5d-3e5202fae84e)
Scroll down to set the `Signing Key`. It is recommended to use the built in `authentik Self-signed Certificate` here unless you have special needs for your own custom cert.
![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`.
![image](https://github.com/user-attachments/assets/ae5e87b8-1ad6-41dd-b6e1-9665623f842a)
Lastly, toggle `Include claims in id_token` to on. Click `Finish` to complete creating the provider.
![image](https://github.com/user-attachments/assets/25353b3c-3f54-47cf-bd47-b5023f86d7cf)
Grab the generated `Client ID` and `OpenID Configuration Issuer` URL by clicking the newly created provider as this will use this later when `Dashy` is configured to use the OIDC auth mechanism. In this tutorial, what was generated is used below. Obviously adjust the `Client ID` that was generated and use your domain here for the `issuer`.
```
Client ID: pzN9DCMLqHTTatgtYFg50cl0jn1NmCyBC3wreX15
OpenID Configuration Issuer: https://auth.domain.com/application/o/dashy/
```
#### 2. Create an application
Make sure you are still in the `authentik` admin console then go to `Applications` > `Applications`. Click `Create`.
![image](https://github.com/user-attachments/assets/fd225936-15a1-409f-83c8-e24a43047df0)
Next, it is required to give a user facing `Name`, `Slug` and assign the newly created provider. Use the example below if you have been following the guide. If you have used your own naming, then adjust accordingly. Click `Create` once you are done.
![image](https://github.com/user-attachments/assets/e6574d7d-6b22-4e7d-b388-45341b98746b)
>[!TIP]
>Open the application in a new tab from the `authentik` user portal and upload a custom icon. You can also enter a user facing `Description` that the user would see.
![image](https://github.com/user-attachments/assets/20561387-549f-49de-98e6-30330dcdc734)
#### 3. *(Optional)* Limiting access via `authentik` with groups
If you would like to deny `Dashy` access from specific users who are not within `authentik` based groups, you bind them to the application you just created now. `authentik` will deny access to those who are not members of this group or groups. If you want to allow everyone access from your `authentik` instance, skip this step.
Make sure you are still in the `authentik` admin console then go to `Applications` > `Applications`. Click the newly created `Dashy` application.
![image](https://github.com/user-attachments/assets/613fafe7-881f-4664-a903-945854ac65e2)
Click the `Policy/Group/User Bindings` tab at the top, then click `Bind existing policy`. This assumes you have already created the groups you want to use for `Dashy` and populated users in those groups.
![image](https://github.com/user-attachments/assets/10fca15b-e77d-4624-ae03-0ece3910904c)
Click `Group` for the binding type. Under `Group` select the appropriate group you would like to bind. Make sure `Enabled` is toggeled on. Click `Create`.
![image](https://github.com/user-attachments/assets/ebf680ab-696f-4c08-ae89-d73fe92b398f)
`Dashy` will now be scoped only to users within the assigned groups you have bound the application to. Keep adding groups if you would like to adjust the dashboard visibilty based on group membership.
#### 4. Configure `Dashy` to use OIDC client
>[!IMPORTANT]
>It is highly recommended to edit your `conf.yml` directly for this step.
>[!CAUTION]
>Do not make the same mistake many have made here by including the fully qualified address for the `OpenID Configuration URL`. `Dashy` will append the `.well-known` configuration automatically. If the `.well-known` URI is included the app will get redirect loops and `400` errors.
Enter the `Client ID` in the `clientId` field and `OpenID Configuration Issuer` in the `endpoint` field.
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.
```
appConfig:
theme: glass
layout: auto
iconSize: medium
auth:
enableOidc: true
oidc:
clientId: pzN9DCMLqHTTatgtYFg50cl0jn1NmCyBC3wreX15
endpoint: https://auth.domain.com/application/o/dashy/
```
#### 5. *(OPTIONAL)* Example snippets for dashboard visibility
Using the `hideForKeycloakUsers` configuration option is needed to use the `authentik` groups that were created previously.
Adjusting `pages` visibility:
```
pages:
- name: App Management
path: appmgmt.yml
displayData:
hideForKeycloakUsers:
groups:
- Dashy Users
- name: Network Management
path: network.yml
displayData:
hideForKeycloakUsers:
groups:
- Dashy Users
```
Adjusting `items` visibility:
```
items:
- title: Authentik Admin
icon: authentik.svg
url: https://auth.domain.com/if/admin/
target: newtab
id: 0_1472_authentikadmin
displayData:
hideForKeycloakUsers:
groups:
- Dashy Users
- title: Authentik User
icon: authentik-light.png
url: https://auth.domain.com/if/user/
target: newtab
id: 1_1472_authentikuser
```
Adjusting `sections` visibility:
```
sections:
- name: Authentication
displayData:
sortBy: default
rows: 2
cols: 1
collapsed: false
hideForGuests: false
hideForKeycloakUsers:
groups:
- Dashy Users
```
---
## Alternative Authentication Methods
If you are self-hosting Dashy, and require secure authentication to prevent unauthorized access, then you can either use Keycloak, or one of the following options:

View file

@ -93,9 +93,7 @@ The following file provides a reference of all supported configuration options.
**Field** | **Type** | **Required**| **Description**
--- | --- | --- | ---
**`name`** | `string` | Required | A unique name for that page
**`path`** | `string` | Required | The path (local or remote) to the config file to use.<br>For files located within `/user-data`, you only need to specify filename, for externally hosted files you must include the full URL
For more info, see the[Multi-Page docs](/docs/pages-and-sections.md#multi-page-support)
**`path`** | `string` | Required | The path (local or remote) to the config file to use.<br>For files located within `/public`, you only need to specify filename, for externally hosted files you must include the full URL
**[⬆️ Back to Top](#configuring)**
@ -160,8 +158,6 @@ For more info, see the[Multi-Page docs](/docs/pages-and-sections.md#multi-page-s
**`keycloak`** | `object` | _Optional_ | Config options to point Dashy to your Keycloak server. Requires `enableKeycloak: true`. See [`auth.keycloak`](#appconfigauthkeycloak-optional) for more info
**`enableHeaderAuth`** | `boolean` | _Optional_ | If set to `true`, then authentication using HeaderAuth will be enabled. Note that you need to have your web server/reverse proxy running, and have also configured `auth.headerAuth`. Defaults to `false`
**`headerAuth`** | `object` | _Optional_ | Config options to point Dashy to your headers for authentication. Requires `enableHeaderAuth: true`. See [`auth.headerAuth`](#appconfigauthheaderauth-optional) for more info
**`enableOidc`** | `boolean` | _Optional_ | If set to `true`, then authentication using OIDC will be enabled. Note that you need to have a configured OIDC server and configure it with `auth.oidc`. Defaults to `false`
**`oidc`** | `object` | _Optional_ | Config options to point Dash to your OIDC configuration. Request `enableOidc: true`. See [`auth.oidc`](#appconfigauthoidc-optional) for more info
**`enableGuestAccess`** | `boolean` | _Optional_ | When set to `true`, an unauthenticated user will be able to access the dashboard, with read-only access, without having to login. Requires `auth.users` to be configured. Defaults to `false`.
For more info, see the **[Authentication Docs](/docs/authentication.md)**
@ -198,18 +194,6 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**[⬆️ Back to Top](#configuring)**
## `appConfig.auth.oidc` _(optional)_
**Field** | **Type** | **Required**| **Description**
--- | --- | --- | ---
**`clientId`** | `string` | Required | The client id registered in the OIDC server
**`endpoint`** | `string` | Required | The URL of the OIDC server that should be used.
**`adminRole`** | `string` | _Optional_ | The role that will be considered as admin.
**`adminGroup`** | `string` | _Optional_ | The group that will be considered as admin.
**`scope`** | `string` | Required | The scope(s) to request from the OIDC provider
**[⬆️ Back to Top](#configuring)**
## `appConfig.webSearch` _(optional)_
**Field** | **Type** | **Required**| **Description**
@ -324,7 +308,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**Field** | **Type** | **Required**| **Description**
--- | --- | --- | ---
**`icon`** | `string` | _Optional_ | The icon for a given item or section. <br>See [Icon Docs](/docs/icons.md) for all available supported icon types, including: auto-fetched favicons, generative icons, emoji icons, home-lab service logos, font-awesome, simple-icons, material icons, selfh.st icons, and icons specified by URL
**`icon`** | `string` | _Optional_ | The icon for a given item or section. <br>See [Icon Docs](/docs/icons.md) for all available supported icon types, including: auto-fetched favicons, generative icons, emoji icons, home-lab service logos, font-awesome, simple-icons, material icons, and icons specified by URL
**[⬆️ Back to Top](#configuring)**

File diff suppressed because it is too large Load diff

View file

@ -112,7 +112,7 @@ services:
# volumes:
# - /root/my-config.yml:/app/user-data/conf.yml
ports:
- 8080:8080
- 4000:8080
# Set any environmental variables
environment:
- NODE_ENV=production
@ -182,7 +182,7 @@ dashy should be up within 1-2min after you've started the install task procedure
If you do not want to use Docker, you can run Dashy directly on your host system. For this, you will need both [git](https://git-scm.com/downloads) and the latest or LTS version of [Node.js](https://nodejs.org/) installed, and optionally [yarn](https://yarnpkg.com/)
1. Get Code: `git clone https://github.com/Lissy93/dashy.git` and `cd dashy`
2. Configuration: Fill in your settings in `./user-data/conf.yml`
2. Configuration: Fill in you're settings in `./user-data/conf.yml`
3. Install dependencies: `yarn`
4. Build: `yarn build`
5. Run: `yarn start`

View file

@ -7,7 +7,6 @@ Both sections and items can have an icon, which is specified using the `icon` at
- [Simple Icons](#simple-icons)
- [Generative Icons](#generative-icons)
- [Emoji Icons](#emoji-icons)
- [selfh.st Icons](#selfhst-icons)
- [Home-Lab Icons](#home-lab-icons)
- [Material Icons](#material-design-icons)
- [Icons by URL](#icons-by-url)
@ -64,7 +63,7 @@ Font-Awesome has a wide variety of free icons, but you can also use their pro ic
## Simple Icons
[SimpleIcons.org](https://simpleicons.org/) is a collection of 2000+ high quality, free and open source brand and logo SVG icons. Usage of which is very similar to font-awesome icons. First find the glyph you want to use on the [website](https://simpleicons.org/), then just set your icon to the simple icon slug, prefixed with `si-`.
[SimpleIcons.org](https://simpleicons.org/) is a collection of 2000+ high quality, free and open source brand and logo SVG icons. Usage of which is very similar to font-awesome icons. First find the glyph you want to use on the [website](https://simpleicons.org/), then just set your icon the the simple icon slug, prefixed with `si-`.
<p align="center">
<img width="580" src="https://i.ibb.co/MVhkXfC/simple-icons-example.png" />
@ -110,21 +109,9 @@ For example, these will all render the same rocket (🚀) emoji: `icon: ':rocket
---
## selfh.st Icons
The [selfh.st](https://selfh.st/) project provides a set of icons, originally for self-hosted services, but now expanded to include a wide variety of services. These icons can be used by specifying the icon name (without extension and with all spaces replaced with -) preceded by `sh-`. See https://selfh.st/icons/ for a full list of all available icons. For example, the Home Assistant icon is `sh-home-assistant`.
Note: These icons are fetched from the jsdelivr CDN, so if you require offline access, the [Local Icons](#local-icons) method may be a better option for you.
<p align="center">
<img width="580" src="https://i.ibb.co/pfy09LH/Screenshot-from-2025-01-08-22-04-21.png" />
</p>
---
## Home-Lab Icons
The [dashboard-icons](https://github.com/walkxcode/Dashboard-Icons) repo by [@WalkxCode](https://github.com/WalkxCode) provides a comprehensive collection of 360+ high-quality PNG icons for commonly self-hosted services. Dashy natively supports these icons, and you can use them just by specifying the icon name (without extension) preceded by `hl-`. See [here](https://github.com/walkxcode/Dashboard-Icons/tree/main/png) for a full list of all available icons. Note that these are fetched and cached straight from GitHub, so if you require offline access, the [Local Icons](#local-icons) method may be a better option for you.
The [dashboard-icons](https://github.com/walkxcode/Dashboard-Icons) repo by [@WalkxCode](https://github.com/WalkxCode) provides a comprehensive collection of 360+ high-quality PNG icons for commonly self-hosted services. Dashy natively supports these icons, and you can use them just by specifying the icon name (without extension) preceded by `hl-`. See [here](https://github.com/walkxcode/Dashboard-Icons/tree/main/png) for a full list of all available icons. Note that these are fetched and cached strait from GitHub, so if you require offline access, the [Local Icons](#local-icons) method may be a better option for you.
For example:

View file

@ -8,9 +8,9 @@ You can edit additional pages using the interactive editor, exactly the same was
### Using Local Sub-Pages
To get started, create a new `.yml` config file for your sub-page, placing it within `/app/user-data`. Then within your primary `conf.yml`, choose a name, and specify the path to the new file.
To get started, create a new `.yml` config file for your sub-page, placing it within `/app/public`. Then within your primary `conf.yml`, choose a name, and specify the path to the new file.
This is an example. Make sure to add this to the topmost line above appConfig:, or anywhere else appropriately, to match the yml syntax.
For example:
```yaml
pages:
@ -20,17 +20,7 @@ pages:
path: 'work.yml'
```
The next step is to create the new page, if you mounted `/app/user-data` in the docker command and not a volume, you can simply create the new page into that folder on the host.
If you mounted `/app/user-data/conf.yml` in docker, you can either switch to the volume or create another bind mount to your new additional page.
If you're sub-page is located within `/app/user-data`, then you only need to specify the filename, but if it's anywhere else, then the full path is required.
A default template a page can be found here: [https://github.com/lissy93/dashy/blob/master/user-data/conf.yml](https://github.com/lissy93/dashy/blob/master/user-data/conf.yml) Keep in mind the appConfig cannot be used on subpages and should be removed, for further info see [Restrictions](#restrictions)
The last very important step is to rebuild dashy, this can be easily done through to UI, by opening the settings menu on the top right, navigato to update config and then recompile application.
Now if you reload the page, on the top right there should be a new button to navigate to the new page. 🎉
If you're sub-page is located within `/app/public`, then you only need to specify the filename, but if it's anywhere else, then the full path is required.
### Using Remote Sub-Pages
@ -81,7 +71,7 @@ A normal section will contain zero or more items, for example:
url: http://stackoverflow.com/
```
But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space.
But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space, or for action buttons (_coming soon_).
Item groups may also have an optional title.

View file

@ -118,10 +118,9 @@ Dashy supports [Widgets](/docs/widgets.md) for displaying dynamic content. Below
- [OWM Privacy Policy](https://openweather.co.uk/privacy-policy)
- **[RSS Feed](/docs/widgets.md#rss-feed)**: `https://api.rss2json.com/v1/api.json`
- [Rss2Json Privacy Policy](https://rss2json.com/privacy-policy)
- **[IP Address](/docs/widgets.md#public-ip)**: `https://ipapi.co/json` or `http://ip-api.com/json` or `https://api.ip2location.io/`
- **[IP Address](/docs/widgets.md#public-ip)**: `https://ipapi.co/json` or `http://ip-api.com/json`
- [IPGeoLocation Privacy Policy](https://ipgeolocation.io/privacy.html)
- [IP-API Privacy Policy](https://ip-api.com/docs/legal)
- [IP2Location.io Privacy Policy](https://ip2location.io/privacy-policy)
- **[IP Blacklist](/docs/widgets.md#ip-blacklist)**: `https://api.blacklistchecker.com`
- [Blacklist Checker Privacy Policy](https://blacklistchecker.com/privacy)
- **[Domain Monitor](/docs/widgets.md#domain-monitor)**: `http://api.whoapi.com`
@ -132,8 +131,8 @@ Dashy supports [Widgets](/docs/widgets.md) for displaying dynamic content. Below
- [BlockCypher Privacy Policy](https://www.blockcypher.com/privacy.html)
- **[Code::Stats](/docs/widgets.md#code-stats)**: `https://codestats.net`
- [Code::Stats Privacy Policy](https://codestats.net/tos#privacy)
- **[addy.io](/docs/widgets.md#addyio)**: `https://app.addy.io`
- [addy.io Privacy Policy](https://addy.io/privacy/)
- **[AnonAddy](/docs/widgets.md#anonaddy)**: `https://app.anonaddy.com`
- [AnonAddy Privacy Policy](https://anonaddy.com/privacy/)
- **[Vulnerability Feed](/docs/widgets.md#vulnerability-feed)**: `https://www.cvedetails.com`
- [CVE Details Privacy Policy](https://www.cvedetails.com/privacy.php)
- **[Exchange Rate](/docs/widgets.md#exchange-rates)**: `https://v6.exchangerate-api.com`

View file

@ -42,7 +42,7 @@ Any files placed here will be served up to the root of the domain, and override
For example, if you had `user-data/favicon.ico` this would be accessible at `http://my-dashy-instance.local/favicon.ico`
Example Files in `user-data`:
- `conf.yml` - This is the only file that is compulsory, it's your main Dashy config
- `conf.yml` - This is the only file that is compulsary, it's your main Dashy config
- `**.yml` - Include more config files, if you'd like to have multiple pages, see [Multi-page support](/docs/pages-and-sections.md#multi-page-support) for docs
- `favicon.ico` - The default favicon, shown in the browser's tab title
- `initialization.html` - Static HTML page displayed before the app has finished compiling, see [`public/initialization.html`](https://github.com/Lissy93/dashy/blob/master/public/initialization.html)
@ -61,7 +61,7 @@ Example Files in `user-data`:
Now that you've got Dashy running, you are going to want to set it up with your own content.
Config is written in [YAML Format](https://yaml.org/), and saved in [`/user-data/conf.yml`](https://github.com/Lissy93/dashy/blob/master/user-data/conf.yml).
The format on the config file is pretty straight forward. There are four root attributes:
The format on the config file is pretty straight forward. There are three root attributes:
- [`pageInfo`](https://github.com/Lissy93/dashy/blob/master/docs/configuring.md#pageinfo) - Dashboard meta data, like title, description, nav bar links and footer text
- [`appConfig`](https://github.com/Lissy93/dashy/blob/master/docs/configuring.md#appconfig-optional) - Dashboard settings, like themes, authentication, language and customization

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "dashy",
"version": "3.1.1",
"version": "3.0.1",
"license": "MIT",
"main": "server",
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",
@ -22,7 +22,7 @@
"@sentry/tracing": "^7.102.1",
"@sentry/vue": "^7.102.1",
"ajv": "^8.10.0",
"axios": "^1.12.0",
"axios": "^1.6.0",
"connect-history-api-fallback": "^1.6.0",
"crypto-js": "^4.2.0",
"express": "^4.17.2",
@ -30,12 +30,11 @@
"frappe-charts": "^1.6.2",
"js-yaml": "^4.1.0",
"keycloak-js": "^20.0.3",
"oidc-client-ts": "^3.0.1",
"register-service-worker": "^1.7.2",
"remedial": "^1.0.8",
"rss-parser": "3.13.0",
"rsup-progress": "^3.2.0",
"simple-icons": "^14.4.0",
"simple-icons": "^7.19.0",
"v-jsoneditor": "^1.4.5",
"v-tooltip": "^2.1.3",
"vue": "^2.7.0",

View file

@ -5,11 +5,9 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<!-- Favicon, App Icon -->
<link rel="icon" type="image/png" sizes="64x64" href="<%= BASE_URL %>/web-icons/favicon-64x64.png">
<link rel="icon" type="image/png" sizes="32x32" href="web-icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="web-icons/favicon-16x16.png">
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/loading-screen.css" />
<!-- Default Page Title -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -147,7 +147,6 @@ const app = express()
.use(ENDPOINTS.statusCheck, (req, res) => {
try {
statusCheck(req.url, async (results) => {
res.setHeader('Content-Type', 'application/json');
await res.end(results);
});
} catch (e) {

View file

@ -5,7 +5,7 @@
const { exec } = require('child_process');
module.exports = () => new Promise((resolve, reject) => {
const buildProcess = exec('NODE_OPTIONS="--max-old-space-size=512" npm run build'); // Trigger the build command
const buildProcess = exec('npm run build'); // Trigger the build command
let output = ''; // Will store console output

View file

@ -155,21 +155,11 @@ export default {
e.preventDefault();
return 'You may have unsaved edits. Are you sure you want to exit the page?';
},
/* Detect and apply theme based on OS preference */
applyThemeBasedOnOSPreference() {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const osTheme = prefersDark ? this.appConfig.nightTheme : this.appConfig.dayTheme;
if (osTheme) {
this.$store.commit(Keys.SET_THEME, osTheme);
this.updateTheme(osTheme);
}
},
},
/* Basic initialization tasks on app load */
async mounted() {
await this.$store.dispatch(Keys.INITIALIZE_CONFIG); // Initialize config before moving on
this.applyLanguage(); // Apply users local language
this.applyThemeBasedOnOSPreference(); // Apply theme based on OS preference
this.hideSplash(); // Hide the splash screen, if visible
if (this.appConfig.customCss) { // Inject users custom CSS, if present
const cleanedCss = this.appConfig.customCss.replace(/<\/?[^>]+(>|$)/g, '');

View file

@ -2,7 +2,7 @@
"home": {
"no-results": "Ingen søgeresultater",
"no-data": "Ingen data konfigureret",
"no-items-section": "Ingen genstande at vise endnu"
"no-items-section": "Ingen genstander at vise endnu"
},
"search": {
"search-label": "Søg",
@ -20,7 +20,7 @@
"remember-me-hour": "4 timer",
"remember-me-day": "1 dag",
"remember-me-week": "1 uge",
"remember-me-long-time": "lang tid",
"remember-me-long-time": "en lang tid",
"error-missing-username": "Mangler brugernavn",
"error-missing-password": "Manglende adgangskode",
"error-incorrect-username": "Bruger ikke fundet",
@ -54,7 +54,7 @@
"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",
"copy-config-label": "Kopiera Config",
"data-copied-msg": "Konfigurationen er blevet kopieret til clipboard",
"reset-config-label": "Nulstil konfiguration",
"css-save-btn": "Gem ændringer",
@ -95,7 +95,7 @@
"language-switcher": {
"title": "Skift applikationssprog",
"dropdown-label": "Valg en sprog",
"save-button": "Gem",
"save-button": "Gemme",
"success-msg": "Sprog opdateret til"
},
"theme-maker": {
@ -104,17 +104,17 @@
"reset-button": "Nulstil styles for",
"show-all-button": "Vis alle variabler",
"change-fonts-button": "Skift skrifttyper",
"save-button": "Gem",
"cancel-button": "Annuller",
"saved-toast": "{theme} opdateret succesfuldt",
"save-button": "Gemme",
"cancel-button": "Ophæve",
"saved-toast": "{theme} opdateret med succes",
"copied-toast": "Temadata for {theme} kopieret til udklipsholder",
"reset-toast": "Tilpassede farver for {theme} fjernet"
},
"config-editor": {
"save-location-label": "Lokation for gem",
"location-local-label": "Gem lokalt",
"save-location-label": "Gemme beliggenhed",
"location-local-label": "Anbringe lokalt",
"location-disk-label": "Skriv ændringer til konfigurationsfil",
"save-button": "Gem ændringer",
"save-button": "Gemme ændringer",
"preview-button": "Forhåndsvisning af ændringer",
"valid-label": "Konfigurationen er gyldig",
"status-success-msg": "Opgave fuldført",
@ -155,7 +155,7 @@
"password-label-update": "Skriv dit kodeord",
"backup-button-setup": "Sikkerhedskopiering",
"backup-button-update": "Opdater sikkerhedskopiering",
"backup-id-label": "Dit sikkerhedskopi-ID",
"backup-id-label": "Din sikkerhedskopi-ID",
"backup-id-note": "Dette bruges til at gendanne fra sikkerhedskopier senere. Så gem det sammen med din adgangskode et sikkert sted.",
"restore-title": "Gendan en sikkerhedskopi",
"restore-id-label": "Gendan-ID",
@ -165,7 +165,7 @@
"backup-error-unknown": "Kan ikke behandle anmodningen",
"backup-error-password": "Forkert kodeord. Indtast venligst din nuværende adgangskode.",
"backup-success-msg": "Afsluttet med succes",
"restore-success-msg": "Konfiguration gendannet med succes"
"restore-success-msg": "Konfiguration gendan med succes"
},
"menu": {
"open-section-title": "Åbn i",
@ -174,9 +174,9 @@
"modal": "Pop-Up Modal",
"workspace": "Arbejdsrumsvisning",
"options-section-title": "Muligheder",
"edit-item": "Rediger",
"edit-item": "Redigere",
"move-item": "Kopier eller flyt",
"remove-item": "Fjern"
"remove-item": "Fjerne"
},
"context-menus": {
"item": {
@ -187,14 +187,14 @@
"workspace": "Arbejdsrumsvisning",
"clipboard": "Kopier til udklipsholder",
"options-section-title": "Muligheder",
"edit-item": "Rediger",
"edit-item": "Redigere",
"move-item": "Kopier eller flyt",
"remove-item": "Fjerne",
"copied-toast": "URL er blevet kopieret til udklipsholder"
},
"section": {
"open-section": "Åbn sektion",
"edit-section": "Rediger",
"edit-section": "Redigere",
"expand-collapse": "Udvid / Skjul",
"move-section": "Flytte til",
"remove-section": "Fjerne"
@ -206,35 +206,35 @@
"edit-site-data-subheading": "Rediger webstedsdata",
"edit-page-info-btn": "Rediger sideoplysninger",
"edit-page-info-tooltip": "Apptitel, beskrivelse, navigationslinks, sidefodstekst osv.",
"edit-app-config-btn": "Rediger appkonfiguration",
"edit-app-config-btn": "Redigere appkonfiguration",
"edit-app-config-tooltip": "Alle andre appkonfigurationsmuligheder",
"edit-pages-btn": "Rediger sider",
"edit-pages-btn": "Redigere sider",
"edit-pages-tooltip": "Tilføj eller fjern yderligere visninger",
"config-save-methods-subheading": "Indstillinger for lagring af konfiguration",
"save-locally-btn": "Gem lokalt",
"save-locally-btn": "Gemme lokalt",
"save-locally-tooltip": "Gem konfigurationen lokalt i browserlageret. Dette vil ikke påvirke din konfigurationsfil, men ændringer vil kun blive gemt på denne enhed",
"save-disk-btn": "Gem til disk",
"save-disk-btn": "Gemme til disk",
"save-disk-tooltip": "Gem konfigurationen i filen conf.yml på disken. Dette vil sikkerhedskopiere og derefter overskrive din eksisterende konfiguration",
"export-config-btn": "Exporter konfiguration",
"export-config-btn": "Exportere konfiguration",
"export-config-tooltip": "Se og eksporter ny konfiguration, enten til en fil eller til udklipsholder",
"cloud-backup-btn": "Sikkerhedskopier til cloud",
"cloud-backup-tooltip": "Gem krypteret backup af konfiguration til cloud",
"edit-raw-config-btn": "Rediger rå konfiguration",
"edit-raw-config-tooltip": "Se og rediger rå config via JSON-editor",
"cancel-changes-btn": "Afbryd redigering",
"cancel-changes-btn": "Avbryd redigering",
"cancel-changes-tooltip": "Nulstil aktuelle ændringer, og afslut redigeringstilstand. Dette vil ikke påvirke din gemte konfiguration",
"edit-mode-name": "Redigeringstilstand",
"edit-mode-subtitle": "Du er i redigeringstilstand",
"edit-mode-description": "Det betyder, at du kan foretage ændringer i din konfiguration og se resultaterne, men indtil du gemmer, vil ingen af dine ændringer blive bevaret.",
"save-stage-btn": "Gem",
"cancel-stage-btn": "Afbryd",
"save-stage-btn": "Gemme",
"cancel-stage-btn": "Avbryd",
"save-locally-warning": "Hvis du fortsætter, vil ændringer kun blive gemt i din browser. Du bør eksportere en kopi af din konfiguration til brug på andre maskiner. Vil du fortsætte?"
},
"edit-item": {
"missing-title-err": "En varetitel er påkrævet"
},
"edit-section": {
"edit-section-title": "Rediger sektion",
"edit-section-title": "Redigere sektion",
"add-section-title": "Tilføj ny sektion",
"edit-tooltip": "Klik for at redigere, eller højreklik for at få flere muligheder",
"remove-confirm": "Er du sikker på, at du vil fjerne denne sektion? Denne handling kan fortrydes senere."
@ -273,11 +273,11 @@
"cpu-chart-title": "CPU-historie",
"mem-chart-title": "Hukommelsesbrug",
"mem-breakdown-title": "Hukommelsesnedbrydning",
"load-chart-title": "Systemindlæsning"
"load-chart-title": "Systeminlæsning"
},
"glances": {
"disk-space-free": "Ledig",
"disk-space-used": "I brug",
"disk-space-used": "I bruk",
"disk-mount-point": "Mount Point",
"disk-file-system": "Filsystem",
"disk-io-read": "Læs",
@ -329,7 +329,7 @@
"disk-space": "Diskudrymme",
"dnd": "Forstyr ikke",
"email": "email",
"enabled": "aktiveret",
"enabled": "aktivert",
"federated-shares-ucfirst": "Fødereret deler",
"federated-shares": "fødereret deler",
"files": "fil{plural}",
@ -363,7 +363,7 @@
"other": "andet",
"overall": "Samlet set",
"private-link": "privat link",
"public-link": "offentlig link",
"public-link": "publik link",
"quota-enabled": "Diskkvote er {not} aktiveret for denne bruger",
"received": "modtaget",
"scripts": "scripts",
@ -371,7 +371,7 @@
"started": "Startede",
"storages-by-type": "Opbevaring efter type",
"storages": "Opbevaring{plural}",
"strings-use": "tråde brug",
"strings-use": "strings brug",
"tasks": "Opgaver",
"total-files": "samlede filer",
"total-users": "samlede bruger",
@ -379,7 +379,7 @@
"until": "Så længe",
"updates-available-for": "Opdateringer er tilgængelige for",
"updates-available": "opdatering{plural} tilgængelig",
"used": "brugt",
"used": "brugd",
"user": "bruger",
"using": "ved brug af",
"version": "version",

View file

@ -1,385 +0,0 @@
{
"home": {
"no-results": "Nincs találat a keresésre",
"no-data": "Nincs beállítva adat",
"no-items-section": "Még nincs mit megjeleníteni"
},
"search": {
"search-label": "Keresés",
"search-placeholder": "Kezdj el gépelni a szűréshez",
"clear-search-tooltip": "Keresés törlése",
"enter-to-search-web": "Nyomj entert a webes kereséshez"
},
"splash-screen": {
"loading": "Betöltés"
},
"login": {
"title": "Dashy",
"guest-label": "Vendég hozzáférés",
"username-label": "Felhasználónév",
"password-label": "Jelszó",
"login-button": "Bejelentkezés",
"remember-me-label": "Emlékezz rám",
"remember-me-never": "Soha",
"remember-me-hour": "4 óráig",
"remember-me-day": "1 napig",
"remember-me-week": "1 hétig",
"remember-me-long-time": "Hosszabb ideig",
"error-missing-username": "Hiányzó felhasználónév",
"error-missing-password": "Hiányzó jelszó",
"error-incorrect-username": "A felhasználó nem található",
"error-incorrect-password": "Hibás jelszó",
"success-message": "Bejelentkezés folyamatban...",
"logout-message": "Kijelentkezve",
"already-logged-in-title": "Már be vagy jelentkezve",
"already-logged-in-text": "Be vagy jelentkezve mint",
"proceed-to-dashboard": "Tovább az irányítópultra",
"log-out-button": "Kijelentkezés",
"proceed-guest-button": "Belépés vendégként",
"guest-intro-1": "Ez a rendszer engedélyezi a vendég hozzáférést.",
"guest-intro-2": "A vendég felhasználók csak megtekinthetik az irányítópultokat, módosításokat nem tudnak menteni.",
"error": "Hiba",
"error-no-user-configured": "A hitelesítés nincs engedélyezve, vagy nincs beállított felhasználó",
"error-go-home-button": "Vissza a főoldalra",
"logged-in-guest": "Vendégként bejelentkezve, átirányítás...",
"error-guest-access": "A vendég hozzáférés nem engedélyezett"
},
"app-info": {
"title": "App Info",
"error-log": "Hibanapló",
"no-errors": "Nem található friss hiba",
"help-support": "Súgó és támogatás",
"help-support-description" : "Ha segítségre van szükséged a Dashy futtatásához vagy beállításához, nézd meg a",
"help-support-discussions": "fórumot",
"support-dashy": "Dashy támogatása",
"support-dashy-description": "Ha szeretnél részt venni a projektben, nézd meg a",
"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",
"more-info": "További információ",
"source": "Forráskód",
"documentation": "Dokumentáció",
"privacy-and-security": "Adatvédelem és biztonság",
"privacy-and-security-l1": "Ha szeretnéd megtudni, hogyan kezeli a Dashy az adataidat, nézd meg az",
"privacy-and-security-privacy-policy": "adatvédelmi irányelveinket",
"privacy-and-security-advice": "Ha tanácsot keresel az irányítópultod biztonságossá tételéhez, olvasd el a",
"privacy-and-security-advice-link": "kezelési útmutatót",
"privacy-and-security-security-issue": "Ha biztonsági hibát találtál, jelezd azt a",
"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ó"
},
"config": {
"main-tab": "Főmenü",
"view-config-tab": "Beállítások megtekintése",
"edit-config-tab": "Beállítások szerkesztése",
"custom-css-tab": "Egyéni stílusok",
"heading": "Beállítási lehetőségek",
"download-config-button": "Megtekintés / Exportálás",
"edit-config-button": "Beállítások szerkesztése",
"edit-css-button": "Egyéni CSS szerkesztése",
"cloud-sync-button": "Szinkronizálás bekapcsolása",
"edit-cloud-sync-button": "Szinkronizálási beállítások szerkesztése",
"rebuild-app-button": "Alkalmazás újraépítése",
"change-language-button": "Nyelv módosítása",
"reset-settings-button": "Helyi beállítások visszaállítása",
"disabled-note": "Néhány beállítási lehetőséget a rendszergazda letiltott",
"small-screen-note": "Kis képernyőt használsz, ezért néhány menüelem nem tud jól megjelenni",
"app-info-button": "App infó",
"backup-note": "Módosítás előtt érdemes biztonsági mentést készíteni a beállításokról.",
"reset-config-msg-l1": "Ez törli az összes helyi beállítást, de nem módosítja a 'conf.yml' fájlt.",
"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",
"css-note-label": "Megjegyzés",
"css-note-l1": "A változtatások életbe lépéséhez frissítened kell az oldalt.",
"css-note-l2": "A stílusmódosítások csak helyben vannak mentve, ezért ajánlott másolatot készíteni a CSS-ről.",
"css-note-l3": "Az összes egyéni stílus eltávolításához töröld a tartalmat, majd mentsd a változtatásokat.",
"custom-css": {
"title": "Egyéni CSS",
"base-theme": "Alaptéma"
}
},
"alternate-views": {
"alternate-view-heading": "Váltás másik nézetre",
"default": "Alapértelmezett",
"workspace": "Munkaterület",
"minimal": "Minimalista"
},
"settings": {
"theme-label": "Téma",
"layout-label": "Elrendezés",
"layout-auto": "Automatikus",
"layout-horizontal": "Vízszintes",
"layout-vertical": "Függőleges",
"item-size-label": "Elem mérete",
"item-size-small": "Kicsi",
"item-size-medium": "Közepes",
"item-size-large": "Nagy",
"config-launcher-label": "Beállítások",
"config-launcher-tooltip": "Beállítások frissítése",
"sign-out-tooltip": "Kijelentkezés",
"sign-in-tooltip": "Bejelentkezés",
"sign-in-welcome": "Szia, {username}!",
"hide": "Elrejtés",
"open": "Megnyitás"
},
"updates": {
"app-version-note": "Dashy verzió",
"up-to-date": "Naprakész",
"out-of-date": "Frissítés elérhető",
"unsupported-version-l1": "A Dashy nem támogatott verzióját használod",
"unsupported-version-l2": "A legjobb élmény és a biztonsági javítások érdekében frissíts a következő verzióra"
},
"language-switcher": {
"title": "Alkalmazás nyelvének módosítása",
"dropdown-label": "Válassz nyelvet",
"save-button": "Mentés",
"success-msg": "A nyelv frissítve erre:"
},
"theme-maker": {
"title": "Téma szerkesztő",
"export-button": "Egyéni változók exportálása",
"reset-button": "Stílus visszaállítása ehhez:",
"show-all-button": "Összes változó megjelenítése",
"change-fonts-button": "Betűtípus módosítása",
"save-button": "Mentés",
"cancel-button": "Mégse",
"saved-toast": "{theme} sikeresen frissítve",
"copied-toast": "{theme} témához tartozó adatok a vágólapra másolva",
"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"
},
"app-rebuild": {
"title": "Alkalmazás újraépítése",
"rebuild-note-l1": "A változtatások érvényesítéséhez már nem szükséges újraépítés.",
"rebuild-note-l2": "Bizonyos beállítások (például a belépési pont és a hitelesítés) csak az építés során kerülnek betöltésre, ezért ezekhez újraépítést kell indítani.",
"rebuild-note-l3": "Ez a funkció csak Node és Docker telepítéseken érhető el, statikus verziókban nem.",
"rebuild-button": "Build indítása",
"rebuilding-status-1": "Build folyamatban...",
"rebuilding-status-2": "Ez néhány percet igénybe vehet",
"error-permission": "Nincs jogosultságod a folyamat elindításához",
"success-msg": "Az újraépítés sikeresen befejeződött",
"fail-msg": "Az újraépítés sikertelen volt",
"reload-note": "A változtatások érvényesítéséhez frissíteni kell az oldalt",
"reload-button": "Oldal újratöltése"
},
"cloud-sync": {
"title": "Mentés felhőbe, visszaállítás",
"intro-l1": "A mentés felhőbe, visszaállítás egy választható funkció, amellyel feltöltheted a beállításaidat az internetre, majd bármely másik Dashy példányban visszaállíthatod azokat.",
"intro-l2": "Minden adat teljes körűen AES titkosítással védett, ahol a jelszavad szolgál kulcsként.",
"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",
"backup-button-update": "Biztonsági mentés frissítése",
"backup-id-label": "Biztonsági mentés azonosítója",
"backup-id-note": "Ezt az azonosítót a későbbi visszaállításhoz használhatod, ezért őrizd meg biztonságos helyen a jelszavaddal együtt.",
"restore-title": "Biztonsági mentés visszaállítása",
"restore-id-label": "Visszaállítási azonosító",
"restore-password-label": "Jelszó",
"restore-button": "Visszaállítás",
"backup-missing-password": "Hiányzó jelszó",
"backup-error-unknown": "A kérés feldolgozása nem sikerült",
"backup-error-password": "Hibás jelszó. Kérlek, add meg a helyes jelszót.",
"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",
"sametab": "Jelenlegi fülön",
"newtab": "Új fülön",
"modal": "Felugró ablakban",
"workspace": "Munkaterület nézet",
"clipboard": "Másolás vágólapra",
"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",
"copied-toast": "Az URL a vágólapra másolva"
},
"section": {
"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"
}
},
"footer": {
"dev-by": "Fejlesztette",
"licensed-under": "Licenc típusa:",
"get-the": "Nézd meg a",
"source-code": "forráskódot"
},
"interactive-editor": {
"menu": {
"start-editing-tooltip": "Belépés az interaktív szerkesztőbe",
"edit-site-data-subheading": "Oldaladatok szerkesztése",
"edit-page-info-btn": "Oldalinformációk szerkesztése",
"edit-page-info-tooltip": "Alkalmazás címe, leírása, menülinkek, lábléc szöveg stb.",
"edit-app-config-btn": "Alkalmazás beállításainak szerkesztése",
"edit-app-config-tooltip": "Az alkalmazás összes további beállítási lehetősége",
"edit-pages-btn": "Oldalak szerkesztése",
"edit-pages-tooltip": "További nézetek hozzáadása vagy eltávolítása",
"config-save-methods-subheading": "Mentési beállítások",
"save-locally-btn": "Mentés helyileg",
"save-locally-tooltip": "A konfiguráció helyi mentése a böngészőben. Ez nem módosítja a konfigurációs fájlt, és csak ezen az eszközön marad meg.",
"save-disk-btn": "Mentés lemezre",
"save-disk-tooltip": "A konfiguráció mentése a conf.yml fájlba. Biztonsági mentést készít, majd felülírja a meglévő beállításokat.",
"export-config-btn": "Beállítások exportálása",
"export-config-tooltip": "A beállítások megtekintése és exportálása fájlba vagy vágólapra",
"cloud-backup-btn": "Mentés felhőbe",
"cloud-backup-tooltip": "Titkosított konfigurációs mentés feltöltése a felhőbe",
"edit-raw-config-btn": "Nyers konfiguráció szerkesztése",
"edit-raw-config-tooltip": "A nyers konfiguráció megtekintése és módosítása JSON szerkesztőben",
"cancel-changes-btn": "Szerkesztés megszakítása",
"cancel-changes-tooltip": "A jelenlegi módosítások visszaállítása és a szerkesztési mód bezárása. A mentett beállításokat nem érinti.",
"edit-mode-name": "Szerkesztési mód",
"edit-mode-subtitle": "Jelenleg szerkesztési módban vagy",
"edit-mode-description": "Ez azt jelenti, hogy módosíthatod a beállításokat és megtekintheted az eredményt, de a változtatások nem lesznek automatikusan mentve, amíg kézzel el nem mented azokat.",
"save-stage-btn": "Mentés",
"cancel-stage-btn": "Mégse",
"save-locally-warning": "Ha folytatod, a változtatások csak a böngészőben kerülnek mentésre. Javasolt egy másolat exportálása más eszközök használatához. Szeretnéd folytatni?"
},
"edit-item": {
"missing-title-err": "Hiányzó elemcím"
},
"edit-section": {
"edit-section-title": "Szakasz szerkesztése",
"add-section-title": "Új szakasz hozzáadása",
"edit-tooltip": "Kattints a szerkesztéshez, vagy jobb klikk a további lehetőségekért",
"remove-confirm": "Biztosan törölni szeretnéd ezt a szakaszt? Ez a művelet később nem vonható vissza."
},
"edit-app-config": {
"warning-msg-title": "Csak óvatosan!",
"warning-msg-l1": "A következő beállítások haladó konfigurációhoz valók.",
"warning-msg-l2": "Ha nem vagy biztos benne, hogy melyik mező mire való, olvass utána a",
"warning-msg-docs": "dokumentációban,",
"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",
"download-file-tooltip": "Az összes beállítás letöltése YAML fájlként az eszközre",
"view-title": "Beállítások megtekintése"
}
},
"critical-error": {
"title": "Konfigurációs betöltési hiba",
"subtitle": "A Dashy nem tudott megfelelően betöltődni a konfigurációs hiba miatt.",
"sub-ensure-that": "Győződj meg róla, hogy",
"sub-error-details": "Hiba részletei",
"sub-next-steps": "Következő lépések",
"ignore-button": "Kritikus hibák figyelmen kívül hagyása"
},
"widgets": {
"general": {
"loading": "Betöltés",
"show-more": "Részletek megjelenítése",
"cpu-details": "CPU adatai",
"mem-details": "Mamória adatai",
"show-less": "Kevesebb megjelenítése",
"open-link": "Tovább olvasás"
},
"pi-hole": {
"status-heading": "Állapot"
},
"stat-ping": {
"up": "Online",
"down": "Offline"
},
"net-data": {
"cpu-chart-title": "CPU-használat",
"mem-chart-title": "Memóriahasználat",
"mem-breakdown-title": "Memória-összeomlás",
"load-chart-title": "Rendszerterhelés"
},
"glances": {
"disk-space-free": "Szabad",
"disk-space-used": "Foglalt",
"disk-mount-point": "Csatolás helye",
"disk-file-system": "Fájlrendszer",
"disk-io-read": "Olvasás",
"disk-io-write": "Írás",
"system-load-desc": "A futási sorban várakozó folyamatok száma, az összes mag átlaga"
},
"system-info": {
"uptime": "Rendszer futási ideje"
},
"flight-data": {
"arrivals": "Érkezések",
"departures": "Indulások"
},
"tfl-status": {
"good-service-all": "Minden vonalon zavartalan a közlekedés",
"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"
},
"gluetun-status": {
"vpn-ip": "VPN IP-cím",
"country": "Ország",
"region": "Vármegye / Régió",
"city": "Város",
"post-code": "Irányítószám",
"location": "Cím / Földrajzi hely",
"timezone": "Időzóna",
"organization": "Szervezet"
}
}
}

View file

@ -1,7 +1,7 @@
{
"home": {
"no-results": "Ingen søkeresultater",
"no-data": "Ingen data tilgjengelig"
"no-data": "Ingen data konfigurert"
},
"search": {
"search-label": "Søk",
@ -43,7 +43,7 @@
"cloud-sync-button": "Aktiver skysynkronisering",
"edit-cloud-sync-button": "Rediger skysynkronisering",
"rebuild-app-button": "Bygg program",
"change-language-button": "Endre språk",
"change-language-button": "Endre appspråk",
"reset-settings-button": "Tilbakestill lokale innstillinger",
"app-info-button": "Appinfo",
"backup-note": "Det anbefales å ta en sikkerhetskopi av konfigurasjonen din før du gjør endringer.",
@ -91,7 +91,7 @@
"unsupported-version-l2": "For den beste opplevelsen og de siste sikkerhetsoppdateringene, vennligst oppdater til"
},
"language-switcher": {
"title": "Endre språk",
"title": "Endre applikasjonsspråk",
"dropdown-label": "Velg et språk",
"save-button": "Lagre",
"success-msg": "Språk oppdatert til"
@ -115,7 +115,7 @@
"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-disk": "Konfigurasjonsfil skrevet til disk med hell",
"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.",
@ -131,10 +131,10 @@
"rebuild-note-l1": "En ombygging er nødvendig for at endringer skrevet i conf.yml-filen skal tre i kraft.",
"rebuild-note-l2": "Dette bør skje automatisk, men hvis det ikke har blitt gjort, kan du manuelt utløse det her.",
"rebuild-note-l3": "Dette er ikke nødvendig for endringer som er lagret lokalt.",
"rebuild-button": "Start Bygging",
"rebuilding-status-1": "Bygger ...",
"rebuild-button": "Start Build",
"rebuilding-status-1": "Building ...",
"rebuilding-status-2": "Dette kan ta noen minutter",
"error-permission": "Du har ikke tillatelse til å utføre denne handlingen",
"error-permission": "Du har ikke tillatelse til å utløse denne handlingen",
"success-msg": "Byggingen er fullført",
"fail-msg": "Byggoperasjonen mislyktes",
"reload-note": "En sideinnlasting er nå nødvendig for at endringer skal tre i kraft",
@ -169,4 +169,4 @@
"modal": "Åpne i popup-modus",
"workspace": "Åpne i Workspace-visning"
}
}
}

View file

@ -1,96 +1,50 @@
{
"home": {
"no-results": "Нет результатов",
"no-data": "Данные не настроены",
"no-items-section": "Данные загружаются"
"no-results": "Нет Результатов Поиска",
"no-data": "Данные не настроены"
},
"search": {
"search-label": "Поиск",
"search-placeholder": "Начните вводить фильтр",
"clear-search-tooltip": "Очистить",
"search-placeholder": "Начните вводить, чтобы отфильтровать",
"clear-search-tooltip": "Очистить поиск",
"enter-to-search-web": "Нажмите \"Ввод\" для поиска в Интернете"
},
"splash-screen": {
"loading": "Загрузка"
},
"login": {
"title": "Dashy",
"guest-label": "Гостевой доступ",
"title": "Даши",
"username-label": "Имя пользователя",
"password-label": "Пароль",
"login-button": "Войти",
"remember-me-label": "Запомнить меня на",
"login-button": "Авторизоваться",
"remember-me-label": "Помни меня за",
"remember-me-never": "Никогда",
"remember-me-hour": "4 часа",
"remember-me-day": "1 день",
"remember-me-week": "1 неделю",
"remember-me-long-time": "Надолго",
"remember-me-week": "1 неделя",
"error-missing-username": "Отсутствует имя пользователя",
"error-missing-password": "Отсутствует пароль",
"error-incorrect-username": "Пользователь не найден",
"error-incorrect-password": "Неверный пароль",
"success-message": "Вход в систему...",
"logout-message": "Вы вышли из системы",
"already-logged-in-title": "Вы уже вошли в систему",
"logout-message": "Вышли из",
"already-logged-in-title": "Уже вошли в систему",
"already-logged-in-text": "Вы вошли как",
"proceed-to-dashboard": "Перейти к панели управления",
"proceed-to-dashboard": "Перейти на панель управления",
"log-out-button": "Выйти",
"proceed-guest-button": "Продолжить как гость",
"guest-intro-1": "Этот экземпляр приложения имеет гостевой доступ.",
"guest-intro-2": "Гости имеют имеют доступ к панелям управления только для чтения, так что они не могут записать изменения на диск.",
"error": "Ошибка",
"error-no-user-configured": "Аутентификация отключена или не было настроено ни одного пользователя.",
"error-go-home-button": "Домой",
"logged-in-guest": "Вы вошли как гость, переадресация...",
"error-guest-access": "Гостевой доступ запрещён"
},
"app-info": {
"title": "Информация о Приложении",
"error-log": "Журнал Ошибок",
"no-errors": "Ошибок не зафиксировано",
"help-support": "Помощь и Поддержка",
"help-support-description" : "Для получения поддержки в запуске и настройке Dashy, обратитесь за помощью на",
"help-support-discussions": "страницу Discussions",
"support-dashy": "Поддержать Dashy",
"support-dashy-description": "Если вы хотите поддержать проект, пожалуйста посетите страницу",
"support-dashy-link": "Contributions",
"report-bug": "Сообщить об ошибке",
"report-bug-description": "Если вы считаете, что нашли ошибку, пожалуйста",
"report-bug-link": "сообщите о проблеме",
"more-info": "Больше информации",
"source": "Исходный код",
"documentation": "Документация",
"privacy-and-security": "Приватность и Безопасность",
"privacy-and-security-l1": "Для получения подробной информации о том, как Dashy управляет вашими данными, см.",
"privacy-and-security-privacy-policy": "Политика Приватности",
"privacy-and-security-advice": "Для получения рекомендаций по обеспечению безопасности вашей панели управления вы можете обратиться к",
"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": "Legal",
"list-contributors": "Чтобы посмотреть полный список авторов и благодарностей, см.",
"list-contributors-link": "Credits",
"version": "Версия"
"proceed-guest-button": "Продолжить как гость"
},
"config": {
"main-tab": "Главное меню",
"view-config-tab": осмотреть конфигурацию",
"view-config-tab": "Просмотр конфигурации",
"edit-config-tab": "Изменить конфигурацию",
"custom-css-tab": "Пользовательский CSS",
"custom-css-tab": "Пользовательские стили",
"heading": "Варианты конфигурации",
"download-config-button": "Скачать конфигурацию",
"edit-config-button": "Изменить конфигурацию",
"edit-css-button": "Редактировать пользовательский CSS",
"edit-css-button": "Редактировать собственный CSS",
"cloud-sync-button": "Включить облачную синхронизацию",
"edit-cloud-sync-button": "Изменить облачную синхронизацию",
"rebuild-app-button": "Пересобрать приложение",
"change-language-button": "Выбрать язык",
"edit-cloud-sync-button": "Изменить Cloud Sync",
"rebuild-app-button": "Восстановить приложение",
"change-language-button": "Изменить язык приложения",
"reset-settings-button": "Сбросить локальные настройки",
"disabled-note": "Некоторые пункты конфигурации были отключены вашим администратором",
"small-screen-note": "Вы используете слишком маленький дисплей, некоторые страницы могут не быть оптимальны",
"app-info-button": "Информация о приложении",
"backup-note": "Перед внесением изменений рекомендуется сделать резервную копию вашей конфигурации.",
"reset-config-msg-l1": "Это удалит все пользовательские настройки из локального хранилища, но не повлияет на ваш файл conf.yml.",
@ -98,77 +52,69 @@
"reset-config-msg-l3": "Вы уверены, что хотите продолжить?",
"data-cleared-msg": "Данные успешно очищены",
"actions-label": "Действия",
"copy-config-label": "Скопировать конфигурацию",
"copy-config-label": "Копировать конфигурацию",
"data-copied-msg": "Конфиг скопирован в буфер обмена",
"reset-config-label": "Сбросить конфигурацию",
"css-save-btn": "Сохранить изменения",
"css-note-label": "Примечание",
"css-note-l1": "Вам нужно будет обновить страницу, чтобы изменения вступили в силу.",
"css-note-l2": "Переопределения стилей хранятся только локально, поэтому рекомендуется сделать копию вашего CSS.",
"css-note-l3": "Чтобы удалить все пользовательские стили, удалите содержимое и нажмите «Сохранить изменения».",
"custom-css": {
"title": "Пользовательский CSS",
"base-theme": "Базовая Тема"
}
"css-note-l3": "Чтобы удалить все пользовательские стили, удалите содержимое и нажмите «Сохранить изменения»."
},
"alternate-views": {
"alternate-view-heading": "Переключить вид",
"default": "По умолчанию",
"default": "Дефолт",
"workspace": "Рабочая среда",
"minimal": "Минимальный"
},
"settings": {
"theme-label": "Тема",
"layout-label": "Сетка",
"layout-label": "Макет",
"layout-auto": "Авто",
"layout-horizontal": "Горизонтальная",
"layout-vertical": "Вертикальная",
"layout-horizontal": "По горизонтали",
"layout-vertical": "Вертикальный",
"item-size-label": "Размер элемента",
"item-size-small": "Маленький",
"item-size-medium": "Средний",
"item-size-small": "Небольшой",
"item-size-medium": "Середина",
"item-size-large": "Большой",
"config-launcher-label": "Конфигурация",
"config-launcher-tooltip": "Редактировать конфигурацию",
"config-launcher-label": "Конфиг",
"config-launcher-tooltip": "Обновить конфигурацию",
"sign-out-tooltip": "Выход",
"sign-in-tooltip": "Авторизоваться",
"sign-in-welcome": "Здравствуйте, {username}!",
"hide": "Скрыть",
"open": "Открыть"
"sign-in-welcome": "Здравствуйте, {username}!"
},
"updates": {
"app-version-note": "Dashy версия",
"up-to-date": "Последняя версия",
"app-version-note": "Даши версия",
"up-to-date": "Своевременно",
"out-of-date": "Доступно обновление",
"unsupported-version-l1": "Вы используете неподдерживаемую версию Dashy",
"unsupported-version-l2": "Для оптимальной работы и получения последних исправлений безопасности, пожалуйста, обновите приложение до версии"
"unsupported-version-l2": "Для оптимальной работы и получения последних исправлений безопасности, пожалуйста, обновите до"
},
"language-switcher": {
"title": "Выбрать язык приложения",
"title": "Изменить язык приложения",
"dropdown-label": "Выберите язык",
"save-button": "Сохранить",
"success-msg": "Выбран язык: "
"success-msg": "Язык обновлен до"
},
"theme-maker": {
"title": "Конфигуратор темы",
"export-button": "Экспорт пользовательских переменных",
"reset-button": "Сбросить стили для темы",
"reset-button": "Сбросить стили для",
"show-all-button": "Показать все переменные",
"change-fonts-button": "Выбрать шрифты",
"save-button": "Сохранить",
"cancel-button": "Отмена",
"saved-toast": "Тема {theme} успешно обновлена",
"copied-toast": "Данные для темы {theme} скопированы в буфер обмена",
"reset-toast": "Пользовательские цвета для темы {theme} удалены"
"saved-toast": "{theme} Обновлено успешно",
"copied-toast": "Данные темы для {theme} скопированы в буфер обмена",
"reset-toast": "Пользовательские цвета для {theme} удалены"
},
"config-editor": {
"save-location-label": "Место сохранения",
"location-local-label": "Применить локально",
"location-disk-label": "Записать изменения в файл конфигурации на диск",
"save-location-label": "Сохранить местоположение",
"location-local-label": "Применять локально",
"location-disk-label": "Запись изменений в файл конфигурации",
"save-button": "Сохранить изменения",
"preview-button": "Предпросмотр",
"valid-label": "Конфигурация верна",
"valid-label": "Конфигурация действительна",
"status-success-msg": "Задача завершена",
"status-fail-msg": "Задача была провалена",
"status-fail-msg": "Сбой задачи",
"success-msg-disk": "Файл конфигурации успешно записан на диск",
"success-msg-local": "Локальные изменения успешно сохранены",
"success-note-l1": "Приложение должно перестроиться автоматически.",
@ -177,39 +123,38 @@
"error-msg-save-mode": "Пожалуйста, выберите режим сохранения: локальный или файл",
"error-msg-cannot-save": "Произошла ошибка при сохранении конфигурации",
"error-msg-bad-json": "Ошибка в JSON, возможно, неверный формат",
"warning-msg-validation": "Предупреждение валидатора",
"warning-msg-validation": "Предупреждение о проверке",
"not-admin-note": "Вы не можете записать измененные на диск, потому что вы не вошли в систему как администратор"
},
"app-rebuild": {
"title": "Пересобрать приложение",
"rebuild-note-l1": "Чтобы изменения, записанные в файл conf.yml, вступили в силу, требуется пересборка.",
"rebuild-note-l2": "Это должно произойти автоматически, но если этого не произошло, вы можете запустить её вручную здесь.",
"rebuild-note-l3": "Заметьте, что эта функция доступна только для экземпляров приложения, которые запущены с помощью Node или Docker, но не для тех, которые запущенны статично",
"title": "Восстановить приложение",
"rebuild-note-l1": "Чтобы изменения, записанные в файл conf.yml, вступили в силу, требуется перестройка.",
"rebuild-note-l2": "Это должно произойти автоматически, но если этого не произошло, вы можете запустить это вручную здесь.",
"rebuild-note-l3": "Это не требуется для изменений, хранящихся локально.",
"rebuild-button": "Начать сборку",
"rebuilding-status-1": "Сборка...",
"rebuilding-status-1": "Строительство...",
"rebuilding-status-2": "Это может занять несколько минут",
"error-permission": "У вас нет разрешения на запуск этого действия",
"error-permission": "У вас нет разрешения запускать это действие",
"success-msg": "Сборка успешно завершена",
"fail-msg": "Не удалось выполнить операцию сборки",
"reload-note": "Теперь нужно перезагрузить страницу, чтобы изменения вступили в силу.",
"reload-note": "Теперь требуется перезагрузка страницы, чтобы изменения вступили в силу.",
"reload-button": "Перезагрузить страницу"
},
"cloud-sync": {
"title": "Облачная конфигурация",
"intro-l1": "Облачная конфигурация - это дополнительная функция, которая позволяет вам загрузить свою конфигурацию в Интернет, а затем восстановить ее на любом другом устройстве или экземпляре Dashy.",
"intro-l2": "Все данные полностью зашифрованы с помощью сквозного шифрования, используя AES и ваш пароль в качестве ключа.",
"title": "Резервное копирование и восстановление в облаке",
"intro-l1": "Резервное копирование и восстановление в облако - это дополнительная функция, которая позволяет вам загрузить свою конфигурацию в Интернет, а затем восстановить ее на любом другом устройстве или экземпляре Dashy.",
"intro-l2": "Все данные полностью зашифрованы с помощью AES с использованием вашего пароля в качестве ключа.",
"intro-l3": "Для получения дополнительной информации см.",
"intro-docs": "документацию",
"backup-title-setup": "Создать резервную копию",
"backup-title-setup": "Сделать резервную копию",
"backup-title-update": "Обновить резервную копию",
"password-label-setup": "Выберите пароль",
"password-label-update": "Введите ваш пароль",
"backup-button-setup": "Создать резервную копию",
"backup-button-setup": "Резервное копирование",
"backup-button-update": "Обновить резервную копию",
"backup-id-label": "Ваш резервный идентификатор",
"backup-id-note": "Он используется для восстановления из резервных копий позже. Так что сохраните его вместе со своим паролем в надежном месте.",
"backup-id-note": "Это используется для восстановления из резервных копий позже. Так что храните его вместе со своим паролем в надежном месте.",
"restore-title": "Восстановить резервную копию",
"restore-id-label": "Восстановить резервный идентификатор",
"restore-id-label": "Восстановить ID",
"restore-password-label": "Пароль",
"restore-button": "Восстановить",
"backup-missing-password": "Отсутствует пароль",
@ -219,167 +164,9 @@
"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": "Открыть в",
"sametab": "Текущей вкладке",
"newtab": "Новой вкладке",
"modal": "Всплывающем окне",
"workspace": "Рабочей области",
"clipboard": "Копировать в буфер обмена",
"options-section-title": "Настройки",
"edit-item": "Редактировать",
"move-item": "Скопировать или переместить",
"remove-item": "Удалить",
"copied-toast": "Ссылка была скопирована в буфер обмена"
},
"section": {
"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": "Войти в режим редактирования",
"edit-site-data-subheading": "Редактировать параметры сайта",
"edit-page-info-btn": "Редактировать страницу",
"edit-page-info-tooltip": "Заголовок приложения, описание, ссылки, текст снизу страницы и т. д.",
"edit-app-config-btn": "Редактировать конфигурацию",
"edit-app-config-tooltip": "Конфигурация всего остального приложения",
"edit-pages-btn": "Редактировать страницы",
"edit-pages-tooltip": "Добавить или удалить дополнительные страницы панели управления",
"config-save-methods-subheading": "Сохранить конфигурацию",
"save-locally-btn": "Локально",
"save-locally-tooltip": "Сохранить конфигурацию локально, в хранилище браузера. Это не повлияет на ваш конфигурационный файл на диске, но изменения останутся на этом устройстве",
"save-disk-btn": "На диск",
"save-disk-tooltip": "Сначала создастся резервная копия текущей конфигурации, а затем она перезапишется новой версией",
"export-config-btn": "Экспорт конфигурации",
"export-config-tooltip": "Просмотреть и экспортировать новую конфигурацию, либо как файл, либо в буфер обмена",
"cloud-backup-btn": "Облачное сохранение",
"cloud-backup-tooltip": "Сохранить зашифрованный файл конфигурации в облачное хранилище",
"edit-raw-config-btn": "Редактировать JSON конфигурации",
"edit-raw-config-tooltip": "Просмотреть и редактировать файл конфигурации напрямую, через редактор JSON",
"cancel-changes-btn": "Сбросить изменения",
"cancel-changes-tooltip": "Сбросить текущие изменения и выйти из режима редактирования. Это не изменит сохранённую конфигурацию",
"edit-mode-name": "Режим редактирования",
"edit-mode-subtitle": "Вы находитесь в режиме редактирования",
"edit-mode-description": "Это означает, что вы сможете делать изменения вашей конфигурации и просматривать их результат, но они не вступят в силу, пока вы не сохраните их",
"save-stage-btn": "Сохранить",
"cancel-stage-btn": "Отмена",
"save-locally-warning": "Если вы продолжите, изменения будут сохранены только в вашем браузере. Чтобы использовать новую конфигурацию на других устройствах, экспортируйте её. Продолжить?"
},
"edit-item": {
"missing-title-err": "Отсутствует заголовок элемента"
},
"edit-section": {
"edit-section-title": "Редактировать секцию",
"add-section-title": "Добавить новую секцию",
"edit-tooltip": "Нажмите ЛКМ, чтобы редактировать или ПКМ для дополнительных опций",
"remove-confirm": "Вы уверены, что хотите удалить эту секцию? Это действие можно отменить позже"
},
"edit-app-config": {
"warning-msg-title": "Продолжайте с осторожностью",
"warning-msg-l1": "Следующие настройки предназначены для продвинутой настройки приложения.",
"warning-msg-l2": "Если вы сомневаетесь в том, что значит то или иное поле, пожалуйста ознакомьтесь с",
"warning-msg-docs": "документацией",
"warning-msg-l3": "для избежания непредвиденных последствий."
},
"export": {
"export-title": "Экспорт конфигурации",
"copy-clipboard-btn": "Скопировано в буфер обмена",
"copy-clipboard-tooltip": "Скопировать всю конфигурацию приложения, в формате YAML",
"download-file-btn": "Скачать как файл",
"download-file-tooltip": "Скачать файл конфигурации на ваше устройство в формате YAML",
"view-title": "Просмотреть конфигурацию"
}
},
"critical-error": {
"title": "Ошибка загрузки конфигурации",
"subtitle": "Приложение не смогло загрузиться из-за ошибки в конфигурации.",
"sub-ensure-that": "Удостоверьтесь, что",
"sub-error-details": "Сведения об ошибке",
"sub-next-steps": "Следующие шаги",
"ignore-button": "Игнорировать критические ошибки"
},
"widgets": {
"general": {
"loading": "Загрузка...",
"show-more": "Развернуть сведения",
"cpu-details": "Сведения о ЦПУ",
"mem-details": "Сведения о памяти",
"show-less": "Показать меньше",
"open-link": "Продолжить чтение"
},
"pi-hole": {
"status-heading": "Статус"
},
"stat-ping": {
"up": "Онлайн",
"down": "Оффлайн"
},
"net-data": {
"cpu-chart-title": "История ЦПУ",
"mem-chart-title": "Использование памяти",
"mem-breakdown-title": "Подробности об использовании памяти",
"load-chart-title": "Нагрузка системы"
},
"glances": {
"disk-space-free": "Свободно",
"disk-space-used": "Использовано",
"disk-mount-point": "Путь монитрования",
"disk-file-system": "Файловая система",
"disk-io-read": "Чтение",
"disk-io-write": "Запись",
"system-load-desc": "Количество процессов, ожидающих в очереди запуска, усреднённое между всеми ядрами процессора"
},
"system-info": {
"uptime": "Время работы"
},
"flight-data": {
"arrivals": "Прибытия",
"departures": "Отправления"
},
"tfl-status": {
"good-service-all": "Хорошее обслуживание на всех линиях",
"good-service-rest": "Хорошее обслуживание на всех остальных линиях"
},
"synology-download": {
"download": "Скачивание",
"upload": "Загрузка",
"downloaded": "Скачано",
"uploaded": "Загружено",
"remaining": "Остаётся",
"up": "Вверх",
"down": "Вниз"
},
"gluetun-status": {
"vpn-ip": "VPN IP",
"country": "Страна",
"region": "Регион",
"city": "Город",
"post-code": "Индекс",
"location": "Местоположение",
"timezone": "Часовой пояс",
"organization": "Организация"
}
"sametab": "Открыть в текущей вкладке",
"newtab": "Открыть в новой вкладке",
"modal": "Открыть в модальном всплывающем окне",
"workspace": "Открыть в представлении рабочей области"
}
}

View file

@ -10,9 +10,6 @@
"clear-search-tooltip":"Rensa sök",
"enter-to-search-web":"Tryck på retur för att söka på webben"
},
"splash-screen": {
"loading": "Laddar"
},
"login":{
"title":"Dashy",
"username-label":"Användarnamn",
@ -86,9 +83,7 @@
"config-launcher-tooltip":"Uppdatera konfiguration",
"sign-out-tooltip":"Logga ut",
"sign-in-tooltip":"Logga in",
"sign-in-welcome":"Hej {username}!",
"hide": "Göm",
"open": "Öppna"
"sign-in-welcome":"Hej {username}!"
},
"updates":{
"app-version-note":"Dashy-version",
@ -190,17 +185,14 @@
"newtab":"Ny flik",
"modal":"Pop-Up Modal",
"workspace":"Workspace View",
"clipboard": "Kopiera till urklipp",
"options-section-title":"Alternativ",
"edit-item":"Redigera",
"move-item":"Kopiera eller flytta",
"remove-item":"Ta bort",
"copied-toast": "URLen har blivit kopierad till urklipp"
"remove-item":"Ta bort"
},
"section":{
"open-section":"Öppna sektion",
"edit-section":"Redigera",
"expand-collapse": "Expandera / Minimera",
"move-section":"Flytta till",
"remove-section":"Ta bort"
}
@ -213,8 +205,6 @@
"edit-page-info-tooltip":"Appnamn, beskrivning, navigeringslänkar, sidfotstext, etc",
"edit-app-config-btn":"Redigera appkonfiguration",
"edit-app-config-tooltip":"Övriga appkonfigurationsalternativ",
"edit-pages-btn": "Editera sidor",
"edit-pages-tooltip": "Lägg till eller ta bort ytterligare vyer",
"config-save-methods-subheading":"Alternativ för konfigurationssparande",
"save-locally-btn":"Spara lokalt",
"save-locally-tooltip":"Spara konfigurationen lokalt, till webbläsarens lagring. Detta påverkar inte din konfigurationsfil, men ändringarna sparas bara på denna enhet",
@ -279,15 +269,6 @@
"mem-breakdown-title":"Memory Breakdown",
"load-chart-title":"System Load"
},
"glances": {
"disk-space-free": "Ledigt",
"disk-space-used": "Utnyttjat",
"disk-mount-point": "Mount Point",
"disk-file-system": "Filsystem",
"disk-io-read": "Läs",
"disk-io-write": "Skriv",
"system-load-desc": "Antal processer som väntar i run-queue, i genomsnitt över alla kärnor"
},
"system-info":{
"uptime":"Uptime"
},
@ -298,87 +279,6 @@
"tfl-status":{
"good-service-all":"God service på alla linjer",
"good-service-rest":"God Service på övriga linjer"
},
"gluetun-status": {
"vpn-ip": "VPN IP",
"country": "Land",
"region": "Region",
"city": "Stad",
"post-code": "Postnummer",
"location": "Plats",
"timezone": "Tidszon",
"organization": "Organisation"
},
"nextcloud": {
"active": "aktiv",
"and": "och",
"applications": "applikationer",
"available": "Tillgänglig",
"away": "Borta",
"cache-full": "CACHE FULL",
"chat-room": "chattrum",
"delete-all": "Ta bort alla",
"delete-notification": "Ta bort notifikationer",
"disabled": "Inaktiverad",
"disk-quota": "Disk Quota",
"disk-space": "Diskutrymme",
"dnd": "Stör ej",
"email": "mejl",
"enabled": "aktiverad",
"federated-shares-ucfirst": "Federated shares",
"federated-shares": "federated shares",
"files": "file{plural}",
"free": "ledig",
"groups": "grupper",
"hit-rate": "hit rate",
"hits": "hits",
"home": "hem",
"in": "in",
"keys": "nycklar",
"last-24-hours": "senaste 24 timmarna",
"last-5-minutes": "under de senaste 5 minuterna",
"last-hour": "under den senaste timmen",
"last-login": "Senaste inlogg",
"last-restart": "Senaste omstart",
"load-averages": "Genomsnittlig belastning över alla CPU kärnor",
"local-shares": "Local shares",
"local": "local",
"max-keys": "maximalt antal nycklar",
"memory-used": "memory used",
"memory-utilisation": "memory utilization",
"memory": "minne",
"misses": "misses",
"no-notifications": "Inga notifieringar",
"no-pending-updates": "Inga avvacktande uppdateringar",
"nothing-to-show": "Inget att visa för tillfället",
"of-which": "av vilket",
"of": "av",
"offline": "Offline",
"online": "Online",
"other": "other",
"overall": "Totalt",
"private-link": "privat länk",
"public-link": "publik länk",
"quota-enabled": "Disk Quota är {not}aktiverat för denna användare",
"received": "mottaget",
"scripts": "skript",
"sent": "skickat",
"started": "Startat",
"storages-by-type": "Lagring efter typ",
"storages": "lagring{plural}",
"strings-use": "strings use",
"tasks": "Uppgifter",
"total-files": "totalt antal filer",
"total-users": "totalt antal användare",
"total": "totalt",
"until": "Tills",
"updates-available-for": "Uppdateringar är tillgängliga för",
"updates-available": "uppdatering{plural} tillgänglig",
"used": "använt",
"user": "användare",
"using": "använder",
"version": "version",
"wasted": "onödig"
}
}
}

446
src/assets/locales/ua.json Normal file
View file

@ -0,0 +1,446 @@
{
"home": {
"no-results": "Немає результатів пошуку",
"no-data": "Дані не налаштовано",
"no-items-section": "Поки що немає елементів для показу"
},
"search": {
"search-label": "Пошук",
"search-placeholder": "Почніть друкувати для фільтрування",
"clear-search-tooltip": "Очистити пошук",
"enter-to-search-web": "Натисніть enter для пошуку в Інтернеті"
},
"splash-screen": {
"loading": "Завантаження"
},
"login": {
"title": "Dashy",
"guest-label": "Гостьовий доступ",
"username-label": "Ім'я користувача",
"password-label": "Пароль",
"login-button": "Увійти",
"remember-me-label": "Запам'ятати мене для",
"remember-me-never": "Ніколи не запамʼятовувати",
"remember-me-hour": "4 години",
"remember-me-day": "1 день",
"remember-me-week": "1 тиждень",
"remember-me-long-time": "Довгий час",
"error-missing-username": "Відсутнє ім'я користувача",
"error-missing-password": "Відсутній пароль",
"error-incorrect-username": "Користувача не знайдено",
"error-incorrect-password": "Неправильний пароль",
"success-message": "Вхід...",
"logout-message": "Вихід",
"already-logged-in-title": "Вхід вже виконано",
"already-logged-in-text": "Ви ввійшли як",
"proceed-to-dashboard": "Перейти до інформаційної панелі",
"log-out-button": "Вийти",
"proceed-guest-button": "Продовжити як гість",
"guest-intro-1": "Цей інстанс має гостьовий доступ.",
"guest-intro-2": "Гості мають доступ лише для перегляду до інформаційних панелей, тому не можуть записати жодних змін на диск.",
"error": "Помилка",
"error-no-user-configured": "Автентифікацію не ввімкнено, або користувачі не налаштовані",
"error-go-home-button": "Повернутися додому",
"logged-in-guest": "Увійшли як гість, переспрямування...",
"error-guest-access": "Гостьовий доступ заборонено"
},
"app-info": {
"title": "Інформація про програму",
"error-log": "Журнал помилок",
"no-errors": "Останніх помилок не виявлено",
"help-support": "Допомога та підтримка",
"help-support-description": "Щоб отримати підтримку щодо запуску чи налаштування Dashy, див.",
"help-support-discussions": "Обговорення",
"support-dashy": "Підтримка Dashy",
"support-dashy-description": "Щоб дізнатися, як ви можете взяти участь, перегляньте",
"support-dashy-link": "Сторінка внесків",
"report-bug": "Повідомити про помилку",
"report-bug-description": "Якщо ви вважаєте, що знайшли помилку, будь ласка",
"report-bug-link": "повідомте про проблему",
"more-info": "Докладніше",
"source": "Джерело",
"documentation": "Документація",
"privacy-and-security": "Конфіденційність та безпека",
"privacy-and-security-l1": "Для детальної інформації про те, як Dashy керує вашими даними, див.",
"privacy-and-security-privacy-policy": "Політика конфіденційності",
"privacy-and-security-advice": "Щоб отримати поради щодо захисту вашої інформаційної панелі, ви можете звернутися до розділу",
"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": "Версія"
},
"config": {
"main-tab": "Головне меню",
"view-config-tab": "Переглянути конфігурацію",
"edit-config-tab": "Редагувати конфігурацію",
"custom-css-tab": "Власні стилі",
"heading": "Параметри конфігурації",
"download-config-button": "Переглянути / Експортувати конфігурацію",
"edit-config-button": "Редагувати конфігурацію",
"edit-css-button": "Редагувати власний CSS",
"cloud-sync-button": "Увімкнути хмарну синхронізацію",
"edit-cloud-sync-button": "Редагувати хмарну синхронізацію",
"rebuild-app-button": "Перебудувати програму",
"change-language-button": "Змінити мову програми",
"reset-settings-button": "Скинути локальні налаштування",
"disabled-note": "Деякі функції конфігурації були відключені вашим адміністратором",
"small-screen-note": "Ви використовуєте дуже маленький екран, і деякі екрани в цьому меню можуть бути не найкращими",
"app-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": "Зберегти зміни",
"css-note-label": "Примітка",
"css-note-l1": "Вам потрібно буде оновити сторінку, щоб ваші зміни набули чинності.",
"css-note-l2": "Заміни стилів зберігаються лише локально, тому рекомендується зробити копію вашого CSS.",
"css-note-l3": "Щоб видалити всі спеціальні стилі, видаліть вміст та натисніть Зберегти зміни",
"custom-css": {
"title": "Власні стилі",
"base-theme": "Базова тема"
}
},
"alternate-views": {
"alternate-view-heading": "Змінити перегляд",
"default": "За замовчуванням",
"workspace": "Робоча область",
"minimal": "Мінімальний"
},
"settings": {
"theme-label": "Тема",
"layout-label": "Макет",
"layout-auto": "Авто",
"layout-horizontal": "Горизонтально",
"layout-vertical": "Вертикально",
"item-size-label": "Розмір",
"item-size-small": "Маленький",
"item-size-medium": "Середній",
"item-size-large": "Великий",
"config-launcher-label": "Конфіг",
"config-launcher-tooltip": "Оновити конфігурацію",
"sign-out-tooltip": "Вийти",
"sign-in-tooltip": "Увійти",
"sign-in-welcome": "Привіт {username}!",
"hide": "Приховати",
"open": "Відкрити"
},
"updates": {
"app-version-note": "Версія Dashy",
"up-to-date": "Оновити",
"out-of-date": "Оновлення доступне",
"unsupported-version-l1": "Ви використовуєте непідтримувану версію Dashy",
"unsupported-version-l2": "Для найкращого досвіду та останніх виправлень безпеки, будь ласка, оновіться"
},
"language-switcher": {
"title": "Змінити мову програми",
"dropdown-label": "Оберіть мову",
"save-button": "Зберегти",
"success-msg": "Мову оновлено"
},
"theme-maker": {
"title": "Конфігуратор теми",
"export-button": "Експорт змінних користувача",
"reset-button": "Скинути стилі",
"show-all-button": "Показати всі змінні",
"change-fonts-button": "Змінити шрифти",
"save-button": "Зберегти",
"cancel-button": "Скасувати",
"saved-toast": "{theme} успішно оновлена",
"copied-toast": "Дані теми для {theme} скопійовано в буфер обміну",
"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": "Ви не можете записати зміни на диск, оскільки ви не увійшли як адміністратор"
},
"app-rebuild": {
"title": "Перебудувати програму",
"rebuild-note-l1": "Перебудова потрібна, щоб зміни, записані у файл conf.yml, набули чинності.",
"rebuild-note-l2": "Це має статися автоматично, але якщо цього не сталося, ви можете запустити це звідси вручну.",
"rebuild-note-l3": "Це не потрібно для змін, які зберігаються локально.",
"rebuild-button": "Почати збірку",
"rebuilding-status-1": "Збираємо...",
"rebuilding-status-2": "Це може зайняти кілька хвилин",
"error-permission": "У вас немає дозволу ініціювати цю дію",
"success-msg": "Збірку завершено успішно",
"fail-msg": "Помилка операції збирання",
"reload-note": "Тепер потрібно перезавантажити сторінку, щоб зміни набули чинності",
"reload-button": "Перезавантажити сторінку"
},
"cloud-sync": {
"title": "Резервне копіювання та відновлення в хмарі",
"intro-l1": "Резервне копіювання та відновлення в хмарі є додатковою функцією, яка дає змогу завантажити вашу конфігурацію в Інтернет, а потім відновити її на будь-якому іншому пристрої чи екземплярі Dashy.",
"intro-l2": "Усі дані повністю зашифровані за допомогою AES із використанням вашого пароля як ключа.",
"intro-l3": "Для отримання додаткової інформації див.",
"intro-docs": "документи",
"backup-title-setup": "Створити резервну копію",
"backup-title-update": "Оновити резервну копію",
"password-label-setup": "Оберіть пароль",
"password-label-update": "Введіть свій пароль",
"backup-button-setup": "Резервна копія",
"backup-button-update": "Оновити резервну копію",
"backup-id-label": "Ваш ID резервної копії",
"backup-id-note": "Це використовується для відновлення з резервних копій пізніше. Тож має зберігатися разом із паролем у безпечному місці.",
"restore-title": "Відновити резервну копію",
"restore-id-label": "Відновити ID",
"restore-password-label": "Пароль",
"restore-button": "Відновити",
"backup-missing-password": "Відсутній пароль",
"backup-error-unknown": "Неможливо обробити запит",
"backup-error-password": "Неправильний пароль. Будь ласка введіть поточний пароль.",
"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": "Відкрити в",
"sametab": "Поточна вкладка",
"newtab": "Нова вкладка",
"modal": "Модальне спливаюче вікно",
"workspace": "Перегляд робочої області",
"clipboard": "Копіювати до буфера обміну",
"options-section-title": "Параметри",
"edit-item": "Редагувати",
"move-item": "Копіювати або перемістити",
"remove-item": "Видалити",
"copied-toast": "URL скопійовано до буфера обміну"
},
"section": {
"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": "Увійти в інтерактивний редактор",
"edit-site-data-subheading": "Редагувати дані сайту",
"edit-page-info-btn": "Редагувати інформацію про сторінку",
"edit-page-info-tooltip": "Назва програми, опис, навігаційні посилання, текст нижнього колонтитула тощо",
"edit-app-config-btn": "Редагувати конфігурацію програми",
"edit-app-config-tooltip": "Усі інші параметри конфігурації програми",
"edit-pages-btn": "Редагувати сторінки",
"edit-pages-tooltip": "Додати або видалити додаткові перегляди",
"config-save-methods-subheading": "Параметри збереження конфігурації",
"save-locally-btn": "Зберегти локально",
"save-locally-tooltip": "Зберегти конфігурацію локально у сховище браузера. Це не вплине на файл конфігурації, але зміни буде збережено лише на цьому пристрої",
"save-disk-btn": "Зберегти на диск",
"save-disk-tooltip": "Збережіть конфігурацію у файл conf.yml на диску. Це створить резервну копію, а потім перезапише існуючу конфігурацію",
"export-config-btn": "Експорт конфігурації",
"export-config-tooltip": "Переглянути та експортувати нову конфігурацію у файл або в буфер обміну",
"cloud-backup-btn": "Резервне копіювання у хмару",
"cloud-backup-tooltip": "Зберегти зашифровану резервну копію конфігурації у хмару",
"edit-raw-config-btn": "Редагувати необроблену конфігурацію",
"edit-raw-config-tooltip": "Переглянути та змінити необроблену конфігурацію за допомогою редактора JSON",
"cancel-changes-btn": "Скасувати редагування",
"cancel-changes-tooltip": "Скинути поточні зміни та вийти з режиму редагування. Це не вплине на вашу збережену конфігурацію",
"edit-mode-name": "Режим редагування",
"edit-mode-subtitle": "Ви перебуваєте в режимі редагування",
"edit-mode-description": "Це означає, що ви можете вносити зміни до конфігурації та переглядати результати, але поки ви не оберете збереження, жодні ваші зміни не будуть записані.",
"save-stage-btn": "Зберегти",
"cancel-stage-btn": "Скасувати",
"save-locally-warning": "Якщо ви продовжите, зміни буде збережено лише у вашому браузері. Вам слід експортувати копію вашої конфігурації для використання на інших машинах. Бажаєте продовжити?"
},
"edit-item": {
"missing-title-err": "Потрібна назва елемента"
},
"edit-section": {
"edit-section-title": "Редагувати розділ",
"add-section-title": "Додати новий розділ",
"edit-tooltip": "Натисніть, щоб редагувати, або клацніть правою кнопкою миші, щоб отримати додаткові параметри",
"remove-confirm": "Ви впевнені, що хочете видалити цей розділ? Цю дію можна скасувати пізніше."
},
"edit-app-config": {
"warning-msg-title": "Дійте з обережністю",
"warning-msg-l1": "Ці параметри призначені для розширеної конфігурації програми.",
"warning-msg-l2": "Якщо ви не впевнені щодо будь-якого значення, зверніться до",
"warning-msg-docs": "Документація",
"warning-msg-l3": ", щоб уникнути небажаних наслідків."
},
"export": {
"export-title": "Експорт конфігурації",
"copy-clipboard-btn": "Копіювати до буфера обміну",
"copy-clipboard-tooltip": "Скопіювати всі конфігурації програми до системного буфера обміну у форматі YAML",
"download-file-btn": "Завантажити як файл",
"download-file-tooltip": "Завантажте всю конфігурацію програми на свій пристрій у файлі YAML",
"view-title": "Переглянути конфігурацію"
}
},
"widgets": {
"general": {
"loading": "Завантаження...",
"show-more": "Розгорнути детальніше",
"show-less": "Показати менше",
"open-link": "Продовжити читання"
},
"pi-hole": {
"status-heading": "Статус"
},
"stat-ping": {
"up": "Онлайн",
"down": "Офлайн"
},
"net-data": {
"cpu-chart-title": "Історія CPU",
"mem-chart-title": "Використання пам'яті",
"mem-breakdown-title": "Переривання пам'яті",
"load-chart-title": "Завантаження системи"
},
"glances": {
"disk-space-free": "Вільно",
"disk-space-used": "Використано",
"disk-mount-point": "Точка монтування",
"disk-file-system": "Файлова система",
"disk-io-read": "Читання",
"disk-io-write": "Запис",
"system-load-desc": "Середня кількість процесів, що очікують у черзі виконання, для всіх ядер"
},
"system-info": {
"uptime": "Час роботи"
},
"flight-data": {
"arrivals": "Прибуття",
"departures": "Відправлення"
},
"tfl-status": {
"good-service-all": "Якісний сервіс на всіх лініях",
"good-service-rest": "Якісний сервіс на всіх інших лініях"
},
"synology-download": {
"download": "Завантажити (Download)",
"upload": "Вивантажити (Upload)",
"downloaded": "Завантажено",
"uploaded": "Вивантажено",
"remaining": "Залишилося",
"up": "Вгору",
"down": "Вниз"
},
"gluetun-status": {
"vpn-ip": "VPN IP",
"country": "Країна",
"region": "Регіон",
"city": "Місто",
"post-code": "Поштовий індекс",
"location": "Розташування",
"timezone": "Часовий пояс",
"organization": "Організація"
},
"nextcloud": {
"active": "активно",
"and": "і",
"applications": "програми",
"available": "доступно",
"away": "відсутнє",
"cache-full": "КЕШ ЗАПОВНЕНИЙ",
"chat-room": "кімната чату",
"delete-all": "Видалити все",
"delete-notification": "Видалити сповіщення",
"disabled": "вимкнено",
"disk-quota": "Дискова квота",
"disk-space": "Простір на диску",
"dnd": "Не турбувати",
"email": "електронна пошта",
"enabled": "увімкнено",
"federated-shares-ucfirst": "Об'єднані спільні ресурси",
"federated-shares": "об'єднані спільні ресурси",
"files": "файл{множина}",
"free": "вільний",
"groups": "групи",
"hit-rate": "швидкість процесів",
"hits": "процеси",
"home": "дім",
"in": "у",
"keys": "ключі",
"last-24-hours": "за останні 24 години",
"last-5-minutes": "останні 5 хвилин",
"last-hour": "за останню годину",
"last-login": "Останній вхід",
"last-restart": "Останній перезапуск",
"load-averages": "Завантажити середні значення для всіх ядер ЦПУ",
"local-shares": "Локальні спільні ресурси",
"local": "локальний",
"max-keys": "макс. ключі",
"memory-used": "використана пам'ять",
"memory-utilisation": "використання пам'яті",
"memory": "пам'ять",
"misses": "пропуски",
"no-notifications": "Немає сповіщень",
"no-pending-updates": "Немає очікуваних оновлень",
"nothing-to-show": "Зараз тут нічого показувати",
"of-which": "з яких",
"of": "з",
"offline": "Офлайн",
"online": "Онлайн",
"other": "інше",
"overall": "Загалом",
"private-link": "приватне посилання",
"public-link": "публічне посилання",
"quota-enabled": "Дискова квота {не}ввімкнена для цього користувача",
"received": "отримано",
"scripts": "скрипти",
"sent": "надіслано",
"started": "Розпочато",
"storages-by-type": "Сховища за типом",
"storages": "сховище{множина}",
"strings-use": "рядки використання",
"tasks": "Завдання",
"total-files": "загальна кількість файлів",
"total-users": "загальна кількість користувачів",
"total": "усього",
"until": "Доки",
"updates-available-for": "Оновлення доступні для",
"updates-available": "доступне оновлення{множина}",
"used": "використано",
"user": "користувач",
"using": "використовуючи",
"version": "версія",
"wasted": "витрачено"
}
}
}

View file

@ -1,456 +0,0 @@
{
"home": {
"no-results": "Немає результатів пошуку",
"no-data": "Дані не налаштовано",
"no-items-section": "Поки що немає елементів для показу"
},
"search": {
"search-label": "Пошук",
"search-placeholder": "Почніть вводити текст для фільтрування",
"clear-search-tooltip": "Очистити пошук",
"enter-to-search-web": "Натисніть Enter для пошуку в Інтернеті"
},
"splash-screen": {
"loading": "Завантаження"
},
"login": {
"title": "Dashy",
"guest-label": "Гостьовий доступ",
"username-label": "Ім'я користувача",
"password-label": "Пароль",
"login-button": "Увійти",
"remember-me-label": "Запам'ятати мене для",
"remember-me-never": "Ніколи не запам'ятовувати",
"remember-me-hour": "4 години",
"remember-me-day": "1 день",
"remember-me-week": "1 тиждень",
"remember-me-long-time": "Довгий час",
"error-missing-username": "Відсутнє ім'я користувача",
"error-missing-password": "Відсутній пароль",
"error-incorrect-username": "Користувача не знайдено",
"error-incorrect-password": "Неправильний пароль",
"success-message": "Вхід...",
"logout-message": "Вихід",
"already-logged-in-title": "Вхід вже виконано",
"already-logged-in-text": "Ви увійшли як",
"proceed-to-dashboard": "Перейти до інформаційної панелі",
"log-out-button": "Вийти",
"proceed-guest-button": "Продовжити як гість",
"guest-intro-1": "У цій системі увімкнено гостьовий доступ.",
"guest-intro-2": "Гості мають доступ лише для перегляду до інформаційних панелей, тому не можуть записати жодних змін на диск.",
"error": "Помилка",
"error-no-user-configured": "Автентифікацію не ввімкнено, або користувачі не налаштовані",
"error-go-home-button": "Повернутися на головну",
"logged-in-guest": "Ви увійшли як гість, перенаправлення...",
"error-guest-access": "Гостьовий доступ не дозволено"
},
"app-info": {
"title": "Інформація про програму",
"error-log": "Журнал помилок",
"no-errors": "Останніх помилок не виявлено",
"help-support": "Допомога та підтримка",
"help-support-description": "Щоб отримати підтримку щодо запуску чи налаштування Dashy, див.",
"help-support-discussions": "Обговорення",
"support-dashy": "Підтримка Dashy",
"support-dashy-description": "Щоб дізнатися, як ви можете взяти участь, перегляньте",
"support-dashy-link": "Сторінка внесків",
"report-bug": "Повідомити про помилку",
"report-bug-description": "Якщо ви вважаєте, що знайшли помилку, будь ласка",
"report-bug-link": "повідомте про проблему",
"more-info": "Докладніше",
"source": "Джерело",
"documentation": "Документація",
"privacy-and-security": "Конфіденційність та безпека",
"privacy-and-security-l1": "Для детальної інформації про те, як Dashy керує вашими даними, див.",
"privacy-and-security-privacy-policy": "Політика конфіденційності",
"privacy-and-security-advice": "Щоб отримати поради щодо захисту вашої інформаційної панелі, ви можете звернутися до розділу",
"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": "Версія"
},
"config": {
"main-tab": "Головне меню",
"view-config-tab": "Переглянути налаштування",
"edit-config-tab": "Редагувати налаштування",
"custom-css-tab": "Користувацькі стилі",
"heading": "Параметри налаштування",
"download-config-button": "Переглянути / Експортувати налаштування",
"edit-config-button": "Редагувати налаштування",
"edit-css-button": "Редагувати користувацький CSS",
"cloud-sync-button": "Увімкнути хмарну синхронізацію",
"edit-cloud-sync-button": "Редагувати хмарну синхронізацію",
"rebuild-app-button": "Перебудувати програму",
"change-language-button": "Змінити мову програми",
"reset-settings-button": "Скинути локальні налаштування",
"disabled-note": "Деякі функції налаштувань були вимкнені адміністратором",
"small-screen-note": "Ви використовуєте дуже маленький екран, і деякі екрани в цьому меню можуть бути не найкращими",
"app-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": "Зберегти зміни",
"css-note-label": "Примітка",
"css-note-l1": "Вам потрібно буде оновити сторінку, щоб ваші зміни набули чинності.",
"css-note-l2": "Заміни стилів зберігаються лише локально, тому рекомендується зробити копію вашого CSS.",
"css-note-l3": "Щоб видалити всі користувацькі стилі, видаліть вміст та натисніть Зберегти зміни",
"custom-css": {
"title": "Користувацькі стилі",
"base-theme": "Базова тема"
}
},
"alternate-views": {
"alternate-view-heading": "Змінити перегляд",
"default": "За замовчуванням",
"workspace": "Робочий простір",
"minimal": "Мінімальний"
},
"settings": {
"theme-label": "Тема",
"layout-label": "Макет",
"layout-auto": "Авто",
"layout-horizontal": "Горизонтально",
"layout-vertical": "Вертикально",
"item-size-label": "Розмір елемента",
"item-size-small": "Маленький",
"item-size-medium": "Середній",
"item-size-large": "Великий",
"config-launcher-label": "Налаштування",
"config-launcher-tooltip": "Оновити налаштування",
"sign-out-tooltip": "Вийти",
"sign-in-tooltip": "Увійти",
"sign-in-welcome": "Вітаємо {username}!",
"hide": "Приховати",
"open": "Відкрити"
},
"updates": {
"app-version-note": "Версія Dashy",
"up-to-date": "Оновити",
"out-of-date": "Оновлення доступне",
"unsupported-version-l1": "Ви використовуєте непідтримувану версію Dashy",
"unsupported-version-l2": "Для найкращого досвіду та останніх виправлень безпеки, будь ласка, оновіться"
},
"language-switcher": {
"title": "Змінити мову програми",
"dropdown-label": "Оберіть мову",
"save-button": "Зберегти",
"success-msg": "Мову оновлено"
},
"theme-maker": {
"title": "Конфігуратор теми",
"export-button": "Експорт змінних користувача",
"reset-button": "Скинути стилі",
"show-all-button": "Показати всі змінні",
"change-fonts-button": "Змінити шрифти",
"save-button": "Зберегти",
"cancel-button": "Скасувати",
"saved-toast": "{theme} успішно оновлена",
"copied-toast": "Дані теми для {theme} скопійовано в буфер обміну",
"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": "Ви не можете записати зміни на диск, оскільки ви не увійшли як адміністратор"
},
"app-rebuild": {
"title": "Перебудувати програму",
"rebuild-note-l1": "Перебудова потрібна, щоб зміни, записані у файл conf.yml, набули чинності.",
"rebuild-note-l2": "Це має статися автоматично, але якщо цього не сталося, ви можете запустити це звідси вручну.",
"rebuild-note-l3": "Це не потрібно для змін, які зберігаються локально.",
"rebuild-button": "Почати збірку",
"rebuilding-status-1": "Збираємо...",
"rebuilding-status-2": "Це може зайняти кілька хвилин",
"error-permission": "У вас немає дозволу ініціювати цю дію",
"success-msg": "Збірку завершено успішно",
"fail-msg": "Помилка при виконанні збірки",
"reload-note": "Тепер потрібно перезавантажити сторінку, щоб зміни набули чинності",
"reload-button": "Перезавантажити сторінку"
},
"cloud-sync": {
"title": "Резервне копіювання та відновлення у хмарі",
"intro-l1": "Резервне копіювання та відновлення в хмарі є додатковою функцією, яка дає змогу завантажити ваше налаштування в Інтернет, а потім відновити її на будь-якому іншому пристрої чи екземплярі Dashy.",
"intro-l2": "Усі дані повністю зашифровані за допомогою AES із використанням вашого пароля як ключа.",
"intro-l3": "Для отримання додаткової інформації див.",
"intro-docs": "документи",
"backup-title-setup": "Створити резервну копію",
"backup-title-update": "Оновити резервну копію",
"password-label-setup": "Оберіть пароль",
"password-label-update": "Введіть свій пароль",
"backup-button-setup": "Резервна копія",
"backup-button-update": "Оновити резервну копію",
"backup-id-label": "Ваш ідентифікатор резервної копії",
"backup-id-note": "Це використовується для відновлення з резервних копій пізніше. Тож має зберігатися разом із паролем у безпечному місці.",
"restore-title": "Відновити резервну копію",
"restore-id-label": "Відновити ідентифікатор",
"restore-password-label": "Пароль",
"restore-button": "Відновити",
"backup-missing-password": "Відсутній пароль",
"backup-error-unknown": "Неможливо обробити запит",
"backup-error-password": "Неправильний пароль. Будь ласка, введіть поточний пароль.",
"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": "Відкрити в",
"sametab": "Поточна вкладка",
"newtab": "Нова вкладка",
"modal": "Модальне спливаюче вікно",
"workspace": "Перегляд робочої області",
"clipboard": "Копіювати до буфера обміну",
"options-section-title": "Параметри",
"edit-item": "Редагувати",
"move-item": "Копіювати або перемістити",
"remove-item": "Видалити",
"copied-toast": "URL скопійовано до буфера обміну"
},
"section": {
"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": "Увійти в інтерактивний редактор",
"edit-site-data-subheading": "Редагувати дані сайту",
"edit-page-info-btn": "Редагувати інформацію про сторінку",
"edit-page-info-tooltip": "Назва програми, опис, навігаційні посилання, текст нижнього колонтитула тощо",
"edit-app-config-btn": "Редагувати налаштування програми",
"edit-app-config-tooltip": "Усі інші параметри налаштування програми",
"edit-pages-btn": "Редагувати сторінки",
"edit-pages-tooltip": "Додати або видалити додаткові перегляди",
"config-save-methods-subheading": "Параметри збереження налаштування",
"save-locally-btn": "Зберегти локально",
"save-locally-tooltip": "Зберегти налаштування локально у сховище браузера. Це не вплине на файл налаштування, але зміни буде збережено лише на цьому пристрої",
"save-disk-btn": "Зберегти на диск",
"save-disk-tooltip": "Збережіть налаштування у файл conf.yml на диску. Це створить резервну копію, а потім перезапише наявне налаштування",
"export-config-btn": "Експорт налаштування",
"export-config-tooltip": "Переглянути та експортувати нове налаштування у файл або в буфер обміну",
"cloud-backup-btn": "Резервне копіювання у хмару",
"cloud-backup-tooltip": "Зберегти зашифровану резервну копію налаштування у хмару",
"edit-raw-config-btn": "Редагувати необроблене налаштування",
"edit-raw-config-tooltip": "Переглянути та змінити необроблене налаштування за допомогою редактора JSON",
"cancel-changes-btn": "Скасувати редагування",
"cancel-changes-tooltip": "Скинути поточні зміни та вийти з режиму редагування. Це не вплине на вашу збережене налаштування",
"edit-mode-name": "Режим редагування",
"edit-mode-subtitle": "Ви перебуваєте в режимі редагування",
"edit-mode-description": "Це означає, що ви можете вносити зміни до налаштування та переглядати результати, але поки ви не оберете збереження, жодні ваші зміни не будуть записані.",
"save-stage-btn": "Зберегти",
"cancel-stage-btn": "Скасувати",
"save-locally-warning": "Якщо ви продовжите, зміни буде збережено лише у вашому браузері. Вам слід експортувати копію вашого налаштування для використання на інших машинах. Бажаєте продовжити?"
},
"edit-item": {
"missing-title-err": "Потрібна назва елемента"
},
"edit-section": {
"edit-section-title": "Редагувати розділ",
"add-section-title": "Додати новий розділ",
"edit-tooltip": "Натисніть, щоб редагувати, або клацніть правою кнопкою миші, щоб отримати додаткові параметри",
"remove-confirm": "Ви впевнені, що хочете видалити цей розділ? Цю дію можна скасувати пізніше."
},
"edit-app-config": {
"warning-msg-title": "Дійте з обережністю",
"warning-msg-l1": "Ці параметри призначені для розширеного налаштування програми.",
"warning-msg-l2": "Якщо ви не впевнені щодо будь-якого значення, зверніться до",
"warning-msg-docs": "Документація",
"warning-msg-l3": ", щоб уникнути небажаних наслідків."
},
"export": {
"export-title": "Експорт налаштування",
"copy-clipboard-btn": "Копіювати до буфера обміну",
"copy-clipboard-tooltip": "Скопіювати всі налаштування програми до системного буфера обміну у форматі YAML",
"download-file-btn": "Завантажити як файл",
"download-file-tooltip": "Завантажте все налаштування програми на свій пристрій у файлі YAML",
"view-title": "Переглянути налаштування"
}
},
"critical-error": {
"title": "Помилка завантаження налаштування",
"subtitle": "Dashy не вдалося правильно завантажити через помилку налаштування.",
"sub-ensure-that": "Переконайтеся в цьому",
"sub-error-details": "Деталі помилки",
"sub-next-steps": "Наступні кроки",
"ignore-button": "Ігнорувати критичні помилки"
},
"widgets": {
"general": {
"loading": "Завантаження...",
"show-more": "Розгорнути детальніше",
"cpu-details": "Детальніше по ЦПУ",
"mem-details": "Детальніше по пам'яті",
"show-less": "Показати менше",
"open-link": "Продовжити читання"
},
"pi-hole": {
"status-heading": "Статус"
},
"stat-ping": {
"up": "Онлайн",
"down": "Офлайн"
},
"net-data": {
"cpu-chart-title": "Історія ЦПУ",
"mem-chart-title": "Використання пам'яті",
"mem-breakdown-title": "Переривання пам'яті",
"load-chart-title": "Завантаження системи"
},
"glances": {
"disk-space-free": "Вільно",
"disk-space-used": "Використано",
"disk-mount-point": "Точка монтування",
"disk-file-system": "Файлова система",
"disk-io-read": "Читання",
"disk-io-write": "Запис",
"system-load-desc": "Середня кількість процесів, що очікують у черзі виконання, для всіх ядер"
},
"system-info": {
"uptime": "Час роботи"
},
"flight-data": {
"arrivals": "Прибуття",
"departures": "Відправлення"
},
"tfl-status": {
"good-service-all": "Якісний сервіс на всіх лініях",
"good-service-rest": "Якісний сервіс на всіх інших лініях"
},
"synology-download": {
"download": "Завантажити (Download)",
"upload": "Вивантажити (Upload)",
"downloaded": "Завантажено",
"uploaded": "Вивантажено",
"remaining": "Залишилося",
"up": "Вгору",
"down": "Вниз"
},
"gluetun-status": {
"vpn-ip": "VPN IP",
"country": "Країна",
"region": "Регіон",
"city": "Місто",
"post-code": "Поштовий індекс",
"location": "Розташування",
"timezone": "Часовий пояс",
"organization": "Організація"
},
"nextcloud": {
"active": "активно",
"and": "і",
"applications": "програми",
"available": "доступно",
"away": "відсутнє",
"cache-full": "КЕШ ЗАПОВНЕНИЙ",
"chat-room": "кімната чату",
"delete-all": "Видалити все",
"delete-notification": "Видалити сповіщення",
"disabled": "вимкнено",
"disk-quota": "Дискова квота",
"disk-space": "Простір на диску",
"dnd": "Не турбувати",
"email": "електронна пошта",
"enabled": "увімкнено",
"federated-shares-ucfirst": "Об'єднані спільні ресурси",
"federated-shares": "об'єднані спільні ресурси",
"files": "файл{множина}",
"free": "вільний",
"groups": "групи",
"hit-rate": "рівень попадань",
"hits": "попадання",
"home": "головна",
"in": "у",
"keys": "ключі",
"last-24-hours": "за останні 24 години",
"last-5-minutes": "останні 5 хвилин",
"last-hour": "за останню годину",
"last-login": "Останній вхід",
"last-restart": "Останній перезапуск",
"load-averages": "Завантажити середні значення для всіх ядер ЦПУ",
"local-shares": "Локальні спільні ресурси",
"local": "локальний",
"max-keys": "максимум ключів",
"memory-used": "використана пам'ять",
"memory-utilisation": "використання пам'яті",
"memory": "пам'ять",
"misses": "промахи",
"no-notifications": "Немає сповіщень",
"no-pending-updates": "немає очікуваних оновлень",
"nothing-to-show": "Зараз тут нічого показувати",
"of-which": "з яких",
"of": "з",
"offline": "Офлайн",
"online": "Онлайн",
"other": "інше",
"overall": "Загалом",
"private-link": "приватне посилання",
"public-link": "публічне посилання",
"quota-enabled": "Дискова квота {не}ввімкнена для цього користувача",
"received": "отримано",
"scripts": "скрипти",
"sent": "надіслано",
"started": "Розпочато",
"storages-by-type": "Сховища за типом",
"storages": "сховище{множина}",
"strings-use": "рядки використання",
"tasks": "Завдання",
"total-files": "загальна кількість файлів",
"total-users": "загальна кількість користувачів",
"total": "усього",
"until": "Доки",
"updates-available-for": "Оновлення доступні для",
"updates-available": "доступне оновлення{множина}",
"used": "використано",
"user": "користувач",
"using": "використовуючи",
"version": "версія",
"wasted": "витрачено"
}
}
}

View file

@ -18,7 +18,7 @@
"guest-label": "访客访问",
"username-label": "用户名",
"password-label": "密码",
"login-button": "登",
"login-button": "登",
"remember-me-label": "记住密码",
"remember-me-never": "永不",
"remember-me-hour": "4 小时",
@ -29,10 +29,10 @@
"error-missing-password": "密码空缺",
"error-incorrect-username": "用户不存在",
"error-incorrect-password": "密码不正确",
"success-message": "登成功。",
"success-message": "登成功。",
"logout-message": "注销",
"already-logged-in-title": "已经成功登",
"already-logged-in-text": "你的登身份",
"already-logged-in-title": "已经成功登",
"already-logged-in-text": "你的登身份",
"proceed-to-dashboard": "前往仪表板",
"log-out-button": "注销",
"proceed-guest-button": "以游客身份前往",
@ -41,7 +41,7 @@
"error": "错误",
"error-no-user-configured": "没有启用验证,或者未配置用户",
"error-go-home-button": "Go Home",
"logged-in-guest": "以访客身份登,正在跳转...",
"logged-in-guest": "以访客身份登,正在跳转...",
"error-guest-access": "不允许访客访问"
},
"app-info": {
@ -130,7 +130,7 @@
"config-launcher-label": "设置",
"config-launcher-tooltip": "更新设置",
"sign-out-tooltip": "注销",
"sign-in-tooltip": "登",
"sign-in-tooltip": "登",
"sign-in-welcome": "你好 {username}",
"hide": "隐藏",
"open": "打开"
@ -402,7 +402,7 @@
"last-24-hours": "24 小时内",
"last-5-minutes": "5 分钟内",
"last-hour": "1 小时内",
"last-login": "最后登",
"last-login": "最后登",
"last-restart": "最后重启",
"load-averages": "所有CPU的平均负载",
"local-shares": "本地分享",
@ -445,4 +445,4 @@
"wasted": "浪费"
}
}
}
}

View file

@ -34,8 +34,6 @@
:statusSuccess="statusResponse ? statusResponse.successStatus : undefined"
:statusText="statusResponse ? statusResponse.message : undefined"
/>
<!-- URL of the item (shown on hover, only on some themes) -->
<p class="item-url">{{ item.url | shortUrl }}</p>
<!-- Edit icon (displayed only when in edit mode) -->
<EditModeIcon v-if="isEditMode" class="edit-mode-item" @click="openItemSettings()" />
</a>
@ -124,26 +122,6 @@ export default {
}
},
},
filters: {
shortUrl(value) {
if (!value || typeof value !== 'string') {
return '';
}
try {
// Use URL constructor to parse the input
const url = new URL(value);
return url.hostname;
} catch (e) {
// If the input is not a valid URL, try to handle it as an IP address
const ipPattern = /^(\d{1,3}\.){3}\d{1,3}/;
const match = value.match(ipPattern);
if (match) {
return match[0];
}
return '';
}
},
},
data() {
return {
editMenuOpen: false,
@ -231,9 +209,6 @@ export default {
&.span-7 { min-width: 14%; }
&.span-8 { min-width: 12.5%; }
}
.item-url {
display: none;
}
}
.item {

View file

@ -21,6 +21,7 @@
</template>
<script>
import simpleIcons from 'simple-icons';
import BrokenImage from '@/assets/interface-icons/broken-icon.svg';
import ErrorHandler from '@/utils/ErrorHandler';
import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex';
@ -28,8 +29,6 @@ import emojiLookup from '@/utils/emojis.json';
import { asciiHash } from '@/utils/MiscHelpers';
import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults';
const simpleicons = require('simple-icons');
export default {
name: 'Icon',
props: {
@ -72,7 +71,6 @@ export default {
else if (img.includes('mdi-')) imgType = 'mdi';
else if (img.includes('si-')) imgType = 'si';
else if (img.includes('hl-')) imgType = 'home-lab-icons';
else if (img.includes('sh-')) imgType = 'selfhst-icons';
else if (img.includes('favicon-')) imgType = 'custom-favicon';
else if (img === 'favicon') imgType = 'favicon';
else if (img === 'generative') imgType = 'generative';
@ -91,7 +89,6 @@ export default {
case 'mdi': return img; // Material design icons
case 'simple-icons': return this.getSimpleIcon(img);
case 'home-lab-icons': return this.getHomeLabIcon(img);
case 'selfhst-icons': return this.getSelfhstIcon(img); // selfh.st/icons
case 'svg': return img; // Local SVG icon
case 'emoji': return img; // Emoji/ unicode
default: return '';
@ -189,18 +186,14 @@ export default {
},
/* Returns the SVG path content */
getSimpleIcon(img) {
const imageName = img.charAt(3).toUpperCase() + img.slice(4);
const icon = simpleicons[`si${imageName}`];
const imageName = img.replace('si-', '');
const icon = simpleIcons.Get(imageName);
if (!icon) {
this.imageNotFound(`No icon was found for '${imageName}' in Simple Icons`);
return null;
}
return icon.path;
},
getSelfhstIcon(img, cdn) {
const imageName = img.slice(3).toLocaleLowerCase();
return (cdn || iconCdns.sh).replace('{icon}', imageName);
},
/* Gets home-lab icon from GitHub */
getHomeLabIcon(img, cdn) {
const imageName = img.replace('hl-', '').toLocaleLowerCase();

View file

@ -24,13 +24,6 @@
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
class="layout-icon" tabindex="-2"
/>
<!-- If user logged in via oidc, show oidc logout button -->
<IconLogout
v-if="userType == userStateEnum.oidcEnabled"
@click="oidcLogout()"
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
class="layout-icon" tabindex="-2"
/>
</div>
</div>
</template>
@ -39,7 +32,6 @@
import router from '@/router';
import { logout as registerLogout } from '@/utils/Auth';
import { getKeycloakAuth } from '@/utils/KeycloakAuth';
import { getOidcAuth } from '@/utils/OidcAuth';
import { localStorageKeys, userStateEnum } from '@/utils/defaults';
import IconLogout from '@/assets/interface-icons/user-logout.svg';
@ -64,13 +56,6 @@ export default {
router.push({ path: '/login' });
}, 500);
},
oidcLogout() {
const oidc = getOidcAuth();
this.$toasted.show(this.$t('login.logout-message'));
setTimeout(() => {
oidc.logout();
}, 500);
},
keycloakLogout() {
const keycloak = getKeycloakAuth();
this.$toasted.show(this.$t('login.logout-message'));

View file

@ -1,66 +0,0 @@
<template>
<div class="chuckNorris-wrapper">
<p class="chuckNorris chuckNorris-line">{{ chuckNorrisLine }}</p>
</div>
</template>
<script>
import axios from 'axios';
import WidgetMixin from '@/mixins/WidgetMixin';
import { widgetApiEndpoints } from '@/utils/defaults';
export default {
mixins: [WidgetMixin],
components: {},
data() {
return {
chuckNorrisLine: null,
};
},
computed: {
/* Format the users preferred categories */
categories() {
let usersChoice = this.options.categories;
if (!usersChoice) return '';
if (Array.isArray(usersChoice)) usersChoice = usersChoice.join(',');
const categories = ["animal","career","celebrity","dev","explicit","fashion","food","history","money","movie","music","political","religion","science","sport","travel"];
if (categories.some((cat) => usersChoice.toLowerCase().includes(cat))) return usersChoice;
return '';
},
/* Combine data parameters for the API endpoint */
endpoint() {
if (this.categories !== '') return `${widgetApiEndpoints.chuckNorris}?category=${this.categories}`;
return `${widgetApiEndpoints.chuckNorris}`;
},
},
methods: {
/* Make GET request to ChuckNorris API endpoint */
fetchData() {
axios.get(this.endpoint)
.then((response) => {
this.processData(response.data);
})
.catch((dataFetchError) => {
this.error('Unable to fetch any Chuck Norris quote', dataFetchError);
})
.finally(() => {
this.finishLoading();
});
},
/* Assign data variables to the returned data */
processData(data) {
this.chuckNorrisLine = data.value;
},
},
};
</script>
<style scoped lang="scss">
.chuckNorris-wrapper {
p.chuckNorris {
color: var(--widget-text-color);
font-size: 1.2rem;
}
}
</style>

View file

@ -1,106 +0,0 @@
<template>
<div class="custom-list">
<div class="custom-list-title" v-if="title">
{{ title }}
</div>
<div v-for="(item, key) in data" :key="key" class="custom-list-row">
<div v-if="item.link" class="custom-list-cell">
<a :href="item.link.url" :title="item.link.title" target="_blank">
{{ item.link.text }}
</a>
</div>
<div v-if="item.value" class="custom-list-cell" :title="item.value.title">
{{ item.value.text }}
<span v-if="item.isNew" class="custom-list-new-value"></span>
</div>
</div>
</div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
export default {
mixins: [WidgetMixin],
components: {},
data() {
return {
data: [],
};
},
computed: {
url() {
return this.options.url || '';
},
title() {
return this.options.title || '';
},
daysForNew() {
return parseInt(Number(this.options.daysForNew)) || false;
}
},
methods: {
fetchData() {
if (this.url) {
this.startLoading();
this.makeRequest(this.options.url).then(this.processData);
}
},
processData(data) {
let today = new Date();
this.data = data.sort((a, b) => new Date(a.date) < new Date(b.date));
if (this.daysForNew) {
let threshold = this.daysForNew * 1000 * 60 * 60 * 24;
this.data = this.data.map((item) => {
item.isNew = (today - new Date(item.date) < threshold);
return item;
});
}
this.finishLoading();
},
},
};
</script>
<style scoped lang="scss">
.custom-list {
.custom-list-title {
outline: 2px solid transparent;
border: 1px solid var(--outline-color);
border-radius: var(--curve-factor);
box-shadow: var(--item-shadow);
color: var(--item-text-color);
margin: .5rem;
padding: 0.3rem;
background: var(--item-background);
text-align: center;
}
.custom-list-row {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--widget-text-color);
font-size: 1.1rem;
.custom-list-cell {
display: inline-block;
a {
text-decoration: none;
color: var(--item-text-color);
}
.custom-list-new-value{
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
background-color: var(--success);
display: inline-block;
}
}
&:not(:last-child) {
border-bottom: 1px dashed var(--widget-text-color);
}
}
}
</style>

View file

@ -7,7 +7,7 @@
<div class="buttons">
<button
v-for="(engine, key) in engines" :key="key"
v-on:click="search(engine, openingMethod)">
v-on:click="search(engine)">
{{ engine.title }}
</button>
</div>
@ -15,9 +15,7 @@
</template>
<script>
import router from '@/router';
import WidgetMixin from '@/mixins/WidgetMixin';
import ErrorHandler from '@/utils/ErrorHandler';
export default {
mixins: [WidgetMixin],
@ -37,28 +35,11 @@ export default {
defaultEngine() {
return this.engines[0];
},
openingMethod() {
return this.options.openingMethod || '';
},
},
methods: {
search(engine, openingMethod) {
search(engine) {
if (engine !== undefined && this.query !== '') {
const url = engine.url + this.query;
switch (openingMethod) {
case 'newtab':
window.open(url, '_blank');
break;
case 'sametab':
window.open(url, '_self');
break;
case 'workspace':
router.push({ name: 'workspace', query: { url } });
break;
default:
ErrorHandler(`Unknown opening method: ${openingMethod}`);
window.open(url, '_blank');
}
window.open(engine.url + this.query, '_blank');
}
},
},

View file

@ -5,7 +5,7 @@
Live {{ direction !== 'both' ? direction: 'flight' }} data from {{ airport }}
</p>
<!-- Departures -->
<div v-if="direction !== 'arrival' && departures.length > 0" class="flight-group">
<div v-if="departures.length > 0" class="flight-group">
<h3 class="flight-type-subtitle" v-if="direction === 'both'">
{{ $t('widgets.flight-data.departures') }}
</h3>
@ -16,7 +16,7 @@
</div>
</div>
<!-- Arrivals -->
<div v-if="direction !== 'departure' && arrivals.length > 0" class="flight-group">
<div v-if="arrivals.length > 0" class="flight-group">
<h3 class="flight-type-subtitle" v-if="direction === 'both'">
{{ $t('widgets.flight-data.arrivals') }}
</h3>
@ -137,12 +137,10 @@ export default {
flights.forEach((flight) => {
results.push({
number: flight.number,
airline: flight.airline?.name ?? "unknown airline",
aircraft: flight.aircraft?.model ?? "unknown aircraft",
airport: flight.movement?.airport?.name ?? "unknown airport",
time: flight.movement
? (flight.movement?.revisedTime?.local ?? flight.movement?.scheduledTime?.local ?? "unknown time")
: "unknown time"
airline: flight.airline.name,
aircraft: flight.aircraft.model,
airport: flight.movement.airport.name,
time: flight.movement.actualTimeUtc,
});
});
return results;

View file

@ -77,11 +77,11 @@ export default {
slug: repo.repo,
desc: repo.desc,
lang: repo.lang,
link: `https://github.com/${repo.repo}`,
link: repo.repo_link,
stars: repo.stars,
forks: repo.forks,
newStars: repo.change,
avatar: repo.build_by[0] || 'https://github.com/fluidicon.png',
newStars: parseInt(repo.added_stars, 10),
avatar: repo.avatars[0] || 'https://github.com/fluidicon.png',
});
});
if (this.limit && this.limit < results.length) {

View file

@ -68,11 +68,11 @@ export default {
networks.push({
name: network.interface_name,
speed: network.speed,
online: network.speed ? 'up' : 'down', // v3 to v4 is_up no longer seems to be a default response field
currentDownload: network.bytes_recv,
currentUpload: network.bytes_sent,
totalDownload: network.bytes_recv_gauge,
totalUpload: network.bytes_sent_gauge,
online: network.is_up ? 'up' : 'down',
currentDownload: network.rx,
currentUpload: network.tx,
totalDownload: network.cumulative_rx,
totalUpload: network.cumulative_tx,
changeDownload: this.previous && network.rx > this.previous[index].rx,
changeUpload: this.previous && network.tx > this.previous[index].tx,
});

View file

@ -33,8 +33,8 @@ export default {
/* eslint-disable prefer-destructuring */
Object.keys(trafficData).forEach((keyName) => {
let upOrDown = null;
if (keyName.includes('_sent')) upOrDown = 'up';
else if (keyName.includes('_recv')) upOrDown = 'down';
if (keyName.includes('_tx')) upOrDown = 'up';
else if (keyName.includes('_rx')) upOrDown = 'down';
trafficData[keyName].forEach((dataPoint) => {
const dataTime = this.getRoundedTime(dataPoint[0]);
if (upOrDown === 'up') {

View file

@ -45,17 +45,8 @@ export default {
/* Refreshes iframe contents, called by parent */
update() {
this.startLoading();
const iframe = document.getElementById(this.frameId);
if (iframe.contentWindow) {
try {
iframe.contentWindow.location.href = this.frameUrl;
iframe.contentWindow.location.reload(true);
} catch (e) {
this.error('Failed to refresh iframe', e, true);
}
} else {
this.error('Couldn\'t find iframe', null, true);
}
this.updateCount += 1;
(document.getElementById(this.frameId) || {}).src = this.frameUrl;
this.finishLoading();
},
},

View file

@ -1,258 +0,0 @@
<template>
<div class="minecraft-wrapper">
<a class="minecraft-link" :href="serverLinkEndpoint">
<h3 class="minecraft-title">{{ title }}</h3>
</a>
<div class="minecraft-icon-wrapper">
<img :src="icon" alt="server-icon" class="minecraft-icon" />
</div>
<div class="minecraft-content-wrapper">
<StatusIndicator class="status-indicator" :statusSuccess="status ? online : undefined"
:statusText="status ? statusTooltip : undefined" />
<span v-if="title != server" class="minecraft-server">{{ server }}<br /></span>
<span v-if="!online" class="minecraft-status">Server Offline</span>
<span v-if="online" class="minecraft-version">
{{ software || (bedrock ? "Bedrock" : "Minecraft") }} {{ version }}
</span>
<ul v-if="online" class="minecraft-motd">
<li v-for="(line, idx) in motd" :key="idx">{{ line }}</li>
</ul>
<div v-if="showPlayers" class="player-list">
<span>{{ onlinePlayers }}/{{ maxPlayers }} Players</span>
<ul>
<li v-for="{ name, uuid } in players" :key="uuid">
<a :href="playerLinkEndpoint(uuid)">
<img :src="playerIconEndpoint(uuid)" :alt="`${name}'s Head'`"/>{{ name }}
</a>
</li>
</ul>
</div>
<div v-if="showMods" class="mod-list">
<span>{{ mods.length }} Mods</span>
<ul>
<li v-for="{ name, version } in mods" :key="name">
{{ name }}={{ version }}
</li>
</ul>
</div>
<div v-if="showPlugins" class="plugin-list">
<span>{{ plugins.length }} Plugins</span>
<ul>
<li v-for="{ name, version } in plugins" :key="name">
{{ name }}={{ version }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
import { widgetApiEndpoints } from '@/utils/defaults';
import StatusIndicator from '@/components/LinkItems/StatusIndicator.vue';
export default {
mixins: [WidgetMixin],
components: {
StatusIndicator,
},
data() {
return {
status: false,
online: false,
ip: '',
port: 0,
hostname: '',
iconData: null,
motd: null,
version: null,
software: null,
gamemode: null,
mods: [],
plugins: [],
players: [],
onlinePlayers: null,
maxPlayers: null,
};
},
computed: {
title() {
return this.options.title || this.options.server || this.error('options.server not set');
},
alt() {
return this.title;
},
icon() {
return `https://api.mcsrvstat.us/icon/${this.server}`;
},
server() {
return this.options.server || this.error('options.server not set');
},
bedrock() {
return this.options.bedrock === true;
},
statusTooltip() {
if (!this.status) {
return 'Loading...';
}
if (!this.online) {
return `${this.server} Offline`;
}
return `${this.onlinePlayers}/${this.maxPlayers} Online`;
},
showPlayers() {
return this.options.showPlayers && this.players.length > 0;
},
showMods() {
return this.options.showMods && this.mods.length > 0;
},
showPlugins() {
return this.options.showPlugins && this.plugins.length > 0;
},
endpoint() {
return `${widgetApiEndpoints.minecraftStatus}${this.bedrock ? 'bedrock/' : ''}3/${this.server}`;
},
serverLinkEndpoint() {
return `${widgetApiEndpoints.minecraftServerLink}${this.server}`;
},
},
methods: {
playerIconEndpoint(uuid) {
return `${widgetApiEndpoints.minecraftPlayerIcon}${uuid}/32.png`;
},
playerLinkEndpoint(uuid) {
return `${widgetApiEndpoints.minecraftPlayerLink}${uuid}`;
},
/* Make GET request to McSrvStat.US API endpoint */
fetchData() {
this.startLoading();
fetch(this.endpoint)
.then(response => {
if (!response.ok) {
this.error('Network response was not ok');
}
return response.json();
})
.then(data => {
this.processData(data);
})
.catch(dataFetchError => {
this.error('Unable to fetch data', dataFetchError);
})
.finally(() => {
this.finishLoading();
});
},
/* Assign data variables to the returned data */
processData(data) {
this.online = data.online;
this.ip = data.ip;
this.port = data.port;
this.hostname = data.hostname;
if (this.online) {
this.version = data.version;
this.iconData = data.icon;
this.software = data.software;
this.gamemode = data.gamemode;
this.motd = data.motd.clean || [];
this.players = data.players.list || [];
this.onlinePlayers = data.players.online;
this.maxPlayers = data.players.max;
this.mods = data.mods || [];
this.plugins = data.plugins || [];
}
this.status = true;
},
update() {
this.fetchData();
},
},
};
</script>
<style scoped lang="scss">
.minecraft-wrapper {
margin-top: -1em;
display: grid;
justify-content: center;
grid-template-columns: 64px 1fr;
color: var(--widget-text-color);
padding-top: 0.5rem;
.minecraft-link {
grid-column: 1 / span 2;
text-decoration: none;
.minecraft-title {
font-size: 1.2rem;
margin: 0.25rem auto;
border-bottom: 1px solid var(--widget-text-color);
color: var(--widget-text-color);
}
}
.minecraft-icon {
display: flex;
width: 100%;
max-width: 64px;
margin: 0.25rem auto 0;
padding-top: 0.5rem;
border-radius: var(--curve-factor);
}
.minecraft-content-wrapper {
position: relative;
padding: 0.5rem;
ul.minecraft-motd {
border-top: 1px dashed var(--widget-text-color);
list-style-type: none;
padding: 0.5rem 0;
margin: 0.5rem 0 0 0;
}
.player-list,
.mod-list,
.plugin-list {
span {
font-size: 1.2rem;
border-top: 1px dashed var(--widget-text-color);
padding: 0.25rem 0;
display: block;
}
ul {
list-style-type: '- ';
padding: 0;
margin: 0;
li {
padding: 1rem 0;
margin: 0;
}
}
}
.player-list {
ul {
li {
padding: 0;
list-style-type: none;
img {
width: 1rem;
height: 1rem;
float: left;
position: relative;
margin-right: 1em;
}
}
}
}
}
}
</style>
<style lang="scss">
.minecraft-alt-tt {
min-width: 20rem;
}
</style>

View file

@ -38,7 +38,7 @@ export default {
hostname() {
const usersChoice = this.parseAsEnvVar(this.options.hostname);
if (!usersChoice) this.error('You must specify the hostname for your Pi-Hole server');
return usersChoice;
return usersChoice || 'http://pi.hole';
},
apiKey() {
const usersChoice = this.parseAsEnvVar(this.options.apiKey);
@ -53,7 +53,9 @@ export default {
hideInfo() { return this.options.hideInfo; },
},
filters: {
capitalize,
capitalize(str) {
return capitalize(str);
},
},
methods: {
/* Make GET request to local pi-hole instance */

View file

@ -1,252 +0,0 @@
<template>
<div class="pi-hole-stats-v6-wrapper">
<!-- Current Status -->
<div v-if="status && !hideStatus" class="status">
<span class="status-lbl">{{ $t('widgets.pi-hole.status-heading') }}:</span>
<span :class="`status-val ${getStatusColor(status)}`">{{ status | capitalize }}</span>
</div>
<!-- Block Pie Chart -->
<p :id="chartId" class="block-pie"></p>
<!-- More Data -->
<div v-if="dataTable" class="data-table">
<div class="data-table-row" v-for="(row, inx) in dataTable" :key="inx">
<p class="row-label">{{ row.lbl }}</p>
<p class="row-value">{{ row.val }}</p>
</div>
</div>
</div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
import ChartingMixin from '@/mixins/ChartingMixin';
import { capitalize } from '@/utils/MiscHelpers';
export default {
mixins: [WidgetMixin, ChartingMixin],
data() {
return {
status: null,
dataTable: null,
csrfToken: null,
sid: null,
};
},
computed: {
/* Let user select which comic to display: random, latest or a specific number */
hostname() {
const usersChoice = this.parseAsEnvVar(this.options.hostname);
if (!usersChoice) this.error('You must specify the hostname for your Pi-Hole server');
return usersChoice;
},
apiKey() {
const usersChoice = this.parseAsEnvVar(this.options.apiKey);
if (!usersChoice) this.error('App Password is required, please see the docs');
return usersChoice;
},
hideStatus() { return this.options.hideStatus; },
hideChart() { return this.options.hideChart; },
hideInfo() { return this.options.hideInfo; },
authHeader() {
return {
'X-FTL-SID': this.sid,
'X-FTL-CSRF': this.csrfToken,
Accept: 'application/json',
};
},
authEndpoint() {
return `${this.hostname}/api/auth`;
},
blockingStatusEndpoint() {
return `${this.hostname}/api/dns/blocking`;
},
/* This is actually just the stats that are currently in memory, which amounts to 24hrs when the
service first boots up, but will drift to be a little more than 24hrs worth of data as the
server runs. If you need accurate stats to a particular timeframe, then the
/api/stats/database/summary endpoint is the way to go. */
statsEndpoint() {
return `${this.hostname}/api/stats/summary`;
},
statsDatabaseEndpoint() {
return `${this.hostname}/api/stats/database/summary`;
},
timestampTomorrowMidnight() {
const calcDate = new Date();
calcDate.setHours(0, 0, 0, 0);
calcDate.setDate(calcDate.getDate() + 1);
return parseInt(
String(calcDate.getTime()).substring(0, String(calcDate.getTime()).length - 3),
10,
);
},
timestamp24HoursAgo() {
const calcDate = new Date();
calcDate.setDate(calcDate.getDate() - 1);
return parseInt(
String(calcDate.getTime()).substring(0, String(calcDate.getTime()).length - 3),
10,
);
},
},
filters: {
capitalize,
},
methods: {
fetchData() {
this.makeRequest(
this.authEndpoint,
{ 'Content-Type': 'application/json' },
'POST',
{ password: this.apiKey },
)
.then(this.processAuthData)
.then(
() => {
if (!this.sid || !this.csrfToken) return;
Promise.all([
this.fetchBlockingStatus(),
this.fetchInMemoryStats(),
this.fetchTodayStats(),
this.fetchAllTimeStats(),
]).then(this.processData);
},
);
},
processAuthData({ session }) {
if (!session) {
this.error('Missing session info in auth response');
} else if (session.valid !== true) {
this.error('Authentication failed: Invalid credentials or 2FA token required');
} else {
const { sid, csrf } = session;
if (!sid || !csrf) {
this.error('No CSRF token or SID received');
} else {
this.sid = sid;
this.csrfToken = csrf;
}
}
},
fetchBlockingStatus() {
return this.makeRequest(this.blockingStatusEndpoint, this.authHeader);
},
fetchInMemoryStats() {
return this.makeRequest(this.statsEndpoint, this.authHeader);
},
fetchTodayStats() {
const url = new URL(this.statsDatabaseEndpoint);
url.searchParams.append('from', this.timestamp24HoursAgo);
// Future date because we're looking for "up to present".
url.searchParams.append('until', this.timestampTomorrowMidnight);
return this.makeRequest(url.toString(), this.authHeader);
},
fetchAllTimeStats() {
const url = new URL(this.statsDatabaseEndpoint);
url.searchParams.append('from', 1); // Errors out with 0.
url.searchParams.append('until', this.timestampTomorrowMidnight);
return this.makeRequest(url.toString(), this.authHeader);
},
processData([blockingStatus, inMemoryStats, todayStats, allTimeStats]) {
if (!this.hideStatus) {
this.status = blockingStatus.blocking || 'unknown';
}
if (!this.hideInfo) {
this.dataTable = [
{
lbl: 'Active Clients',
val: `${inMemoryStats.clients.active.toLocaleString('en-US')}/${allTimeStats.total_clients.toLocaleString('en-US')}`,
},
{ lbl: 'Ads Blocked Last 24 Hours', val: todayStats.sum_blocked.toLocaleString('en-US') },
{ lbl: 'DNS Queries Last 24 Hours', val: todayStats.sum_queries.toLocaleString('en-US') },
{ lbl: 'Total DNS Queries', val: allTimeStats.sum_queries.toLocaleString('en-US') },
{
lbl: 'Domains on Block List',
val: inMemoryStats.gravity.domains_being_blocked.toLocaleString('en-US'),
},
];
}
if (!this.hideChart) {
this.generateBlockPie(
Math.round(todayStats.percent_blocked * 10) / 10,
);
}
},
getStatusColor(status) {
if (status === 'enabled') return 'green';
if (status === 'disabled') return 'red';
return 'blue';
},
/* Generate pie chart showing the proportion of queries blocked */
generateBlockPie(blockedTodayPercentage) {
const chartData = {
labels: ['Blocked', 'Allowed'],
datasets: [{
values: [blockedTodayPercentage, 100 - blockedTodayPercentage],
}],
};
return new this.Chart(`#${this.chartId}`, {
title: 'Block Percent Last 24 Hours',
data: chartData,
type: 'donut',
height: 250,
strokeWidth: 18,
colors: ['#f80363', '#20e253'],
tooltipOptions: {
formatTooltipY: d => `${Math.round(d * 10) / 10}%`,
},
});
},
},
};
</script>
<style scoped lang="scss">
.pi-hole-stats-v6-wrapper {
display: flex;
flex-direction: column;
.status {
margin: 0.5rem 0;
.status-lbl {
color: var(--widget-text-color);
font-weight: bold;
}
.status-val {
margin-left: 0.5rem;
font-family: var(--font-monospace);
&.green { color: var(--success); }
&.red { color: var(--danger); }
&.blue { color: var(--info); }
}
}
img.block-percent-chart {
margin: 0.5rem auto;
max-width: 8rem;
width: 100%;
}
.block-pie {
margin: 0;
}
.data-table {
display: flex;
flex-direction: column;
.data-table-row {
display: flex;
justify-content: space-between;
p {
margin: 0.2rem 0;
color: var(--widget-text-color);
font-size: 0.9rem;
&.row-value {
font-family: var(--font-monospace);
}
}
&:not(:last-child) {
border-bottom: 1px dashed var(--widget-text-color);
}
}
}
}
</style>

View file

@ -103,4 +103,5 @@ export default {
}
}
}
</style>

View file

@ -1,159 +0,0 @@
<template>
<div class="pi-hole-queries-wrapper" v-if="results">
<div v-for="section in results" :key="section.id" class="query-section">
<p class="section-title">{{ section.title }}</p>
<div v-for="(query, i) in section.results" :key="i" class="query-row">
<p class="domain">{{ query.domain }}</p>
<p class="count">{{ query.count }}</p>
</div>
</div>
</div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
import { showNumAsThousand } from '@/utils/MiscHelpers';
export default {
mixins: [WidgetMixin],
components: {},
data() {
return {
results: null,
csrfToken: null,
sid: null,
};
},
computed: {
/* Let user select which comic to display: random, latest or a specific number */
hostname() {
const usersChoice = this.parseAsEnvVar(this.options.hostname);
if (!usersChoice) this.error('You must specify the hostname for your Pi-Hole server');
return usersChoice;
},
apiKey() {
const usersChoice = this.parseAsEnvVar(this.options.apiKey);
if (!usersChoice) this.error('App Password is required, please see the docs');
return usersChoice;
},
count() {
const usersChoice = this.options.count;
if (usersChoice && typeof usersChoice === 'number') return usersChoice;
return 10;
},
authHeader() {
return {
'X-FTL-SID': this.sid,
'X-FTL-CSRF': this.csrfToken,
Accept: 'application/json',
};
},
authEndpoint() {
return `${this.hostname}/api/auth`;
},
/* This is actually just the stats that are shown on the Pi-Hole dashboard, which amounts to
24hrs when the service first boots up, but will drift to be a little more than 24hrs worth of
data as the server runs. If you need accurate stats to a particular timeframe, then the
/api/stats/database/top_domains endpoint is the way to go. However, that endpoint does not
return sorted results, so you would have to get everything and sort it yourself, which presents
logistical problems. */
topDomainsEndpoint() {
return `${this.hostname}/api/stats/top_domains`;
},
},
methods: {
fetchData() {
this.makeRequest(
this.authEndpoint,
{ 'Content-Type': 'application/json' },
'POST',
{ password: this.apiKey },
)
.then(this.processAuthData)
.then(
() => {
if (!this.sid || !this.csrfToken) return;
Promise.all([
this.fetchTopAllowedDomains(),
this.fetchTopBlockedDomains(),
]).then(this.processData);
},
);
},
processAuthData({ session }) {
if (!session) {
this.error('Missing session info in auth response');
} else if (session.valid !== true) {
this.error('Authentication failed: Invalid credentials or 2FA token required');
} else {
const { sid, csrf } = session;
if (!sid || !csrf) {
this.error('No CSRF token or SID received');
} else {
this.sid = sid;
this.csrfToken = csrf;
}
}
},
fetchTopAllowedDomains() {
const url = new URL(this.topDomainsEndpoint);
url.searchParams.append('blocked', false);
url.searchParams.append('count', this.count);
return this.makeRequest(url.toString(), this.authHeader);
},
fetchTopBlockedDomains() {
const url = new URL(this.topDomainsEndpoint);
url.searchParams.append('blocked', true);
url.searchParams.append('count', this.count);
return this.makeRequest(url.toString(), this.authHeader);
},
processData([topAllowedDomains, topBlockedDomains]) {
const topAds = [];
topBlockedDomains.domains.forEach(({ domain, count }) => {
topAds.push({ domain, count: showNumAsThousand(count) });
});
const topQueries = [];
topAllowedDomains.domains.forEach(({ domain, count }) => {
topQueries.push({ domain, count: showNumAsThousand(count) });
});
this.results = [
{ id: '01', title: 'Top Ads Blocked', results: topAds },
{ id: '02', title: 'Top Queries', results: topQueries },
];
},
},
};
</script>
<style scoped lang="scss">
.pi-hole-queries-wrapper {
color: var(--widget-text-color);
.query-section {
display: inline-block;
width: 100%;
p.section-title {
margin: 0.75rem 0 0.25rem;
font-size: 1.2rem;
font-weight: bold;
}
.query-row {
display: flex;
justify-content: space-between;
margin: 0.25rem;
p.domain {
margin: 0.25rem 0;
overflow: hidden;
text-overflow: ellipsis;
}
p.count {
margin: 0.25rem 0;
font-family: var(--font-monospace);
}
&:not(:last-child) {
border-bottom: 1px dashed var(--widget-text-color);
}
}
}
}
</style>

View file

@ -1,130 +0,0 @@
<template>
<div :id="chartId" class="pi-hole-traffic"></div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
import ChartingMixin from '@/mixins/ChartingMixin';
export default {
mixins: [WidgetMixin, ChartingMixin],
components: {},
data() {
return {
csrfToken: null,
sid: null,
};
},
computed: {
/* Let user select which comic to display: random, latest or a specific number */
hostname() {
const usersChoice = this.parseAsEnvVar(this.options.hostname);
if (!usersChoice) this.error('You must specify the hostname for your Pi-Hole server');
return usersChoice;
},
apiKey() {
const usersChoice = this.parseAsEnvVar(this.options.apiKey);
if (!usersChoice) this.error('App Password is required, please see the docs');
return usersChoice;
},
authHeader() {
return {
'X-FTL-SID': this.sid,
'X-FTL-CSRF': this.csrfToken,
Accept: 'application/json',
};
},
authEndpoint() {
return `${this.hostname}/api/auth`;
},
historyEndpoint() {
return `${this.hostname}/api/history`;
},
},
methods: {
fetchData() {
this.makeRequest(
this.authEndpoint,
{ 'Content-Type': 'application/json' },
'POST',
{ password: this.apiKey },
)
.then(this.processAuthData)
.then(
() => {
if (!this.sid || !this.csrfToken) return;
this.fetchHistory().then((response) => {
if (this.validate(response)) {
this.processData(response);
}
});
},
);
},
processAuthData({ session }) {
if (!session) {
this.error('Missing session info in auth response');
} else if (session.valid !== true) {
this.error('Authentication failed: Invalid credentials or 2FA token required');
} else {
const { sid, csrf } = session;
if (!sid || !csrf) {
this.error('No CSRF token or SID received');
} else {
this.sid = sid;
this.csrfToken = csrf;
}
}
},
fetchHistory() {
return this.makeRequest(this.historyEndpoint, this.authHeader);
},
validate(data) {
if (!data || !Array.isArray(data['history'])) {
this.error('Got success, but found no results, possible authorization error');
} else if (data.history.length < 1) {
this.error('Request completed succesfully, but no data in Pi-Hole yet');
return false;
}
return true;
},
processData({ history }) {
const timeData = [];
const domainsData = [];
const adsData = [];
history.forEach(({ timestamp, total, blocked }) => {
timeData.push(this.formatTime(timestamp * 1000));
domainsData.push(total - blocked);
adsData.push(blocked);
});
const chartData = {
labels: timeData,
datasets: [
{ name: 'Queries', type: 'bar', values: domainsData },
{ name: 'Ads Blocked', type: 'bar', values: adsData },
],
};
this.generateChart(chartData);
},
generateChart(chartData) {
return new this.Chart(`#${this.chartId}`, {
title: 'Recent Queries & Ads',
data: chartData,
type: 'axis-mixed',
height: this.chartHeight,
colors: ['#20e253', '#f80363'],
truncateLegends: true,
lineOptions: {
regionFill: 1,
hideDots: 1,
},
axisOptions: {
xIsSeries: true,
xAxisMode: 'tick',
},
});
},
},
};
</script>

View file

@ -84,12 +84,9 @@ export default {
/* Assign data variables to the returned data */
processData(holidays) {
const results = [];
const makeDate = (date) => {
const formattedMonth = date.month.toString().padStart(2, '0'); // Ensure two digits
const formattedDay = date.day.toString().padStart(2, '0'); // Ensure two digits
const dateString = `${date.year}-${formattedMonth}-${formattedDay}T00:00:00`;
return timestampToDate(new Date(dateString).getTime());
};
const makeDate = (date) => timestampToDate(
new Date(`${date.year}-${date.month}-${date.day}`).getTime(),
);
const formatType = (ht) => capitalize(ht.replaceAll('_', ' '));
holidays.forEach((holiday) => {
results.push({

View file

@ -24,20 +24,17 @@ export default {
endpoint() {
if (this.provider === 'ipgeolocation') {
return `${widgetApiEndpoints.publicIp2}?apiKey=${this.apiKey}`;
} else if (this.provider === 'ip2location.io') {
return `${widgetApiEndpoints.publicIp4}?key=${this.apiKey}`;
} else if (this.provider === 'ip-api') {
} else if (this.provider === 'ipapi') {
return widgetApiEndpoints.publicIp3;
}
return widgetApiEndpoints.publicIp;
},
apiKey() {
if (this.provider === 'ipgeolocation' && !this.options.apiKey) this.error('Missing API Key');
if (this.provider === 'ip2location.io' && !this.options.apiKey) this.error('Missing API Key');
return this.options.apiKey;
},
provider() {
// Can be either `ip-api`, `ipapi.co`, `ipgeolocation` or `ip2location.io`
// Can be either `ip-api`, `ipapi.co` or `ipgeolocation`
return this.parseAsEnvVar(this.options.provider) || 'ipapi.co';
},
},
@ -75,12 +72,6 @@ export default {
this.location = `${ipInfo.city}, ${ipInfo.regionName}`;
this.flagImg = getCountryFlag(ipInfo.countryCode);
this.mapsUrl = getMapUrl({ lat: ipInfo.lat, lon: ipInfo.lon });
} else if (this.provider === 'ip2location.io') {
this.ipAddr = ipInfo.ip;
this.ispName = ipInfo.isp || 'IP2Location.io Starter plan or higher required.';
this.location = `${ipInfo.city_name}, ${ipInfo.region_name}`;
this.flagImg = getCountryFlag(ipInfo.country_code);
this.mapsUrl = getMapUrl({ lat: ipInfo.latitude, lon: ipInfo.longitude });
} else {
this.error('Unknown API provider fo IP address');
}

View file

@ -131,7 +131,7 @@ export default {
const posts = [];
let { length } = items;
if (this.limit) {
length = Math.min(length, this.limit);
length = this.limit;
}
for (let i = 0; length > i; i += 1) {
posts.push({

View file

@ -1,227 +0,0 @@
<template>
<div class="status-section">
<template v-if="statusData">
<div class="status-wrapper">
<div class="status-item">
<div class="title">Version</div>
<div class="value">{{ statusData.version }}</div>
</div>
<div class="status-item">
<div class="title">Agent Count</div>
<div class="value">{{ statusData.agent_count }}</div>
</div>
<div class="status-item">
<div class="title">Client Count</div>
<div class="value">{{ statusData.client_count }}</div>
</div>
<div class="status-item">
<div class="title">Site Count</div>
<div class="value">{{ statusData.site_count }}</div>
</div>
<div class="status-item">
<div class="title">Disk Usage</div>
<div class="value">{{ statusData.disk_usage_percent }}%</div>
</div>
<div class="status-item">
<div class="title">Memory Usage</div>
<div class="value">{{ statusData.mem_usage_percent }}%</div>
</div>
<div class="status-item">
<div class="title">Days Until Cert Expires</div>
<div class="value">{{ statusData.days_until_cert_expires }}</div>
</div>
<div class="status-item">
<div class="title">Cert Expired</div>
<div class="value">{{ statusData.cert_expired ? 'Yes' : 'No' }}</div>
</div>
<div class="status-item services">
<div class="title">Services Running</div>
<div class="services-list">
<div
v-for="(status, service) in statusData.services_running"
:key="service"
class="service"
>
<span class="service-name">{{ service }}</span>
<span :class="['service-status', status ? 'running' : 'stopped']">
{{ status ? 'Running' : 'Stopped' }}
</span>
</div>
</div>
</div>
</div>
</template>
<template v-if="errorMessage">
<div class="error-message">
<span class="text">{{ errorMessage }}</span>
</div>
</template>
</div>
</template>
<script>
import axios from 'axios';
import WidgetMixin from '@/mixins/WidgetMixin';
import { serviceEndpoints } from '@/utils/defaults';
export default {
mixins: [WidgetMixin],
props: {
options: {
type: Object,
default: () => ({}),
},
},
data() {
return {
statusData: null,
errorMessage: null,
errorMessageConstants: {
missingToken: 'No Token set',
missingUrl: 'No URL set',
},
};
},
mounted() {
this.fetchData();
},
computed: {
token() {
return this.parseAsEnvVar(this.options.token);
},
url() {
return this.parseAsEnvVar(this.options.url);
},
authHeaders() {
return {
'Content-Type': 'application/json',
};
},
proxyReqEndpoint() {
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
return `${baseUrl}${serviceEndpoints.corsProxy}`;
},
},
methods: {
update() {
this.startLoading();
this.fetchData();
},
fetchData() {
const {
authHeaders, url, token, proxyReqEndpoint,
} = this;
if (!this.optionsValid({ url, token })) {
return;
}
const targetURL = url;
const customHeaders = JSON.stringify(authHeaders);
axios.post(
proxyReqEndpoint,
{ auth: token },
{
headers: {
'Target-URL': targetURL,
CustomHeaders: customHeaders,
'Content-Type': 'application/json',
},
},
)
.then((response) => {
this.processData(response.data);
})
.catch(() => {
this.errorMessage = 'Failed to fetch data';
})
.finally(() => {
this.finishLoading();
});
},
processData(response) {
this.statusData = response;
},
optionsValid({ url, token }) {
const errors = [];
if (!url) {
errors.push(this.errorMessageConstants.missingUrl);
}
if (!token) {
errors.push(this.errorMessageConstants.missingToken);
}
if (errors.length === 0) {
return true;
}
this.errorMessage = errors.join('\n');
return false;
},
},
};
</script>
<style scoped lang="scss">
.status-section {
background-color: var(--item-background);
padding: 1em;
border-radius: 8px;
}
.status-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
color: var(--item-text-color);
}
.status-item {
width: 48%;
margin: 1em 0;
}
.title {
font-weight: bold;
color: var(--item-text-color);
}
.value {
margin-top: 0.5em;
color: var(--item-text-color);
}
.services-list {
display: flex;
flex-direction: column;
}
.service {
display: flex;
justify-content: space-between;
margin: 0.5em 0;
width: 100%;
}
.service-name {
font-weight: bold;
color: var(--item-text-color);
}
.service-status {
margin-left: 1em;
}
.service-status.running {
color: var(--success);
font-weight: bold;
}
.service-status.stopped {
color: var(--danger);
}
.error-message {
color: var(--item-text-color);
}
</style>

View file

@ -60,10 +60,10 @@ export default {
},
/* Create authorisation header for the instance from the apiKey */
authHeaders() {
if (!this.apiKey) {
if (!this.options.apiKey) {
return {};
}
const encoded = window.btoa(`:${this.apiKey}`);
const encoded = window.btoa(`:${this.options.apiKey}`);
return { Authorization: `Basic ${encoded}` };
},
},
@ -144,7 +144,7 @@ export default {
return copy;
},
getRowValue(row) {
return this.getValueWithRegex(row, /\b(\d+)(\.\d+)*\b$/);
return this.getValueWithRegex(row, /\b\d+\b$/);
},
getRowMonitorName(row) {
return this.getValueWithRegex(row, /monitor_name="([^"]+)"/);
@ -193,7 +193,7 @@ export default {
white-space: nowrap;
vertical-align: baseline;
padding: .35em .65em;
margin: 0.1em 0.5em;
margin: 1em 0.5em;
min-width: 64px;
&.up {

View file

@ -1,199 +0,0 @@
<template>
<div @click="openStatusPage" class="clickable-widget">
<template v-if="errorMessage">
<div class="error-message">
<span class="text">{{ errorMessage }}</span>
</div>
</template>
<template v-else-if="lastHeartbeats">
<div
v-for="(heartbeat, index) in lastHeartbeats"
:key="index"
class="item-wrapper"
>
<div class="item monitor-row">
<div class="title-title">
<span class="text">
{{
monitorNames && monitorNames[index]
? monitorNames[index]
: `Monitor ${index + 1}`
}}
</span>
</div>
<div class="monitors-container">
<div class="status-container">
<span
class="status-pill"
:class="getStatusClass(heartbeat.status)"
>
{{ getStatusText(heartbeat.status) }}
</span>
</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
import WidgetMixin from '@/mixins/WidgetMixin';
export default {
mixins: [WidgetMixin],
data() {
return {
lastHeartbeats: null,
errorMessage: null,
errorMessageConstants: {
missingHost: 'No host set',
missingSlug: 'No slug set',
},
};
},
computed: {
host() {
return this.parseAsEnvVar(this.options.host);
},
slug() {
return this.parseAsEnvVar(this.options.slug);
},
monitorNames() {
return this.options.monitorNames || [];
},
endpoint() {
return `${this.host}/api/status-page/heartbeat/${this.slug}`;
},
statusPageUrl() {
return `${this.host}/status/${this.slug}`;
},
},
mounted() {
this.fetchData();
},
methods: {
update() {
this.startLoading();
this.fetchData();
},
fetchData() {
const { host, slug } = this;
if (!this.optionsValid({ host, slug })) {
return;
}
this.makeRequest(this.endpoint)
.then(this.processData)
.catch((error) => {
this.errorMessage = error.message || 'Failed to fetch data';
});
},
processData(response) {
const { heartbeatList } = response;
const lastHeartbeats = [];
// Use Object.keys to safely iterate over heartbeatList
Object.keys(heartbeatList).forEach((monitorId) => {
const heartbeats = heartbeatList[monitorId];
if (heartbeats.length > 0) {
const lastHeartbeat = heartbeats[heartbeats.length - 1];
lastHeartbeats.push(lastHeartbeat);
}
});
this.lastHeartbeats = lastHeartbeats;
},
optionsValid({ host, slug }) {
const errors = [];
if (!host) errors.push(this.errorMessageConstants.missingHost);
if (!slug) errors.push(this.errorMessageConstants.missingSlug);
if (errors.length > 0) {
this.errorMessage = errors.join('\n');
return false;
}
return true;
},
openStatusPage() {
window.open(this.statusPageUrl, '_blank');
},
getStatusText(status) {
switch (status) {
case 1:
return 'Up';
case 0:
return 'Down';
case 2:
return 'Pending';
case 3:
return 'Maintenance';
default:
return 'Unknown';
}
},
getStatusClass(status) {
switch (status) {
case 1:
return 'up';
case 0:
return 'down';
case 2:
return 'pending';
case 3:
return 'maintenance';
default:
return 'unknown';
}
},
},
};
</script>
<style scoped lang="scss">
.clickable-widget {
cursor: pointer;
}
.status-pill {
border-radius: 50em;
box-sizing: border-box;
font-size: 0.75em;
display: inline-block;
font-weight: 700;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
padding: 0.35em 0.65em;
margin: 0.1em 0.5em;
min-width: 64px;
&.up {
background-color: #5cdd8b;
color: black;
}
&.down {
background-color: #dc3545;
color: white;
}
&.pending {
background-color: #f8a306;
color: black;
}
&.maintenance {
background-color: #1747f5;
color: white;
}
&.unknown {
background-color: gray;
color: white;
}
}
.monitor-row {
display: flex;
justify-content: space-between;
padding: 0.35em 0.5em;
align-items: center;
}
.title-title {
font-weight: bold;
}
.error-message {
color: red;
font-weight: bold;
}
</style>

View file

@ -46,19 +46,17 @@ const COMPAT = {
'adguard-filter-status': 'AdGuardFilterStatus',
'adguard-stats': 'AdGuardStats',
'adguard-top-domains': 'AdGuardTopDomains',
anonaddy: 'addy.io',
anonaddy: 'AnonAddy',
apod: 'Apod',
'blacklist-check': 'BlacklistCheck',
chucknorris: 'ChuckNorris',
clock: 'Clock',
'code-stats': 'CodeStats',
'covid-stats': 'CovidStats',
'crypto-price-chart': 'CryptoPriceChart',
'crypto-watch-list': 'CryptoWatchList',
'custom-search': 'CustomSearch',
'custom-list': 'CustomList',
'cve-vulnerabilities': 'CveVulnerabilities',
'domain-monitor': 'DomainMonitor',
'code-stats': 'CodeStats',
'covid-stats': 'CovidStats',
'drone-ci': 'DroneCi',
embed: 'EmbedWidget',
'eth-gas-prices': 'EthGasPrices',
@ -82,16 +80,15 @@ const COMPAT = {
'gl-network-traffic': 'GlNetworkTraffic',
'gl-system-load': 'GlSystemLoad',
'gl-cpu-temp': 'GlCpuTemp',
'gluetun-status': 'GluetunStatus',
'health-checks': 'HealthChecks',
'hackernews-trending': 'HackernewsTrending',
'gluetun-status': 'GluetunStatus',
iframe: 'IframeWidget',
image: 'ImageWidget',
joke: 'Jokes',
linkding: 'Linkding',
'minecraft-status': 'MinecraftStatus',
'mullvad-status': 'MullvadStatus',
mvg: 'Mvg',
linkding: 'Linkding',
'mvg-connection': 'MvgConnection',
'nd-cpu-history': 'NdCpuHistory',
'nd-load-history': 'NdLoadHistory',
@ -104,11 +101,8 @@ const COMPAT = {
'nextcloud-user': 'NextcloudUser',
'nextcloud-user-status': 'NextcloudUserStatus',
'pi-hole-stats': 'PiHoleStats',
'pi-hole-stats-v6': 'PiHoleStatsV6',
'pi-hole-top-queries': 'PiHoleTopQueries',
'pi-hole-top-queries-v6': 'PiHoleTopQueriesV6',
'pi-hole-traffic': 'PiHoleTraffic',
'pi-hole-traffic-v6': 'PiHoleTrafficV6',
'proxmox-lists': 'Proxmox',
'public-holidays': 'PublicHolidays',
'public-ip': 'PublicIp',
@ -121,9 +115,7 @@ const COMPAT = {
'synology-download': 'SynologyDownload',
'system-info': 'SystemInfo',
'tfl-status': 'TflStatus',
trmm: 'TacticalRMM',
'uptime-kuma': 'UptimeKuma',
'uptime-kuma-status-page': 'UptimeKumaStatusPage',
'wallet-balance': 'WalletBalance',
weather: 'Weather',
'weather-forecast': 'WeatherForecast',

View file

@ -39,17 +39,11 @@ export default {
document.removeEventListener('pointerup', onPointerUp);
};
const onPointerMove = () => {
clearTimeout(parseInt(el.dataset.longPressTimeout, 10));
document.removeEventListener('pointermove', onPointerMove);
};
const onPointerDown = (e) => {
// If event was right-click, then immediately trigger
if (e.button === 2) return;
startTime = Date.now();
document.addEventListener('pointerup', onPointerUp);
el.addEventListener('pointermove', onPointerMove);
el.addEventListener('click', swallowClick);
const timeoutDuration = LONG_PRESS_DEFAULT_DELAY;
const timeout = setTimeout(triggerEvent, timeoutDuration);

View file

@ -22,7 +22,6 @@ import clickOutside from '@/directives/ClickOutside'; // Directive for closing p
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
import { initHeaderAuth, isHeaderAuthEnabled } from '@/utils/HeaderAuth';
import { initOidcAuth, isOidcEnabled } from '@/utils/OidcAuth';
import Keys from '@/utils/StoreMutations';
import ErrorHandler from '@/utils/ErrorHandler';
@ -63,13 +62,7 @@ const mount = () => new Vue({
}).$mount('#app');
store.dispatch(Keys.INITIALIZE_CONFIG).then(() => {
if (isOidcEnabled()) {
initOidcAuth()
.then(() => mount())
.catch((e) => {
ErrorHandler('Failed to authenticate with OIDC', e);
});
} else if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
initKeycloakAuth()
.then(() => mount())
.catch((e) => {

View file

@ -8,7 +8,7 @@ export default {
},
/* Optionally specify the API version, defaults to V 3 */
apiVersion() {
return this.options.apiVersion || 4;
return this.options.apiVersion || 3;
},
/* Optionally specify basic auth credentials for Glances instance */
credentials() {

View file

@ -136,14 +136,6 @@ const ThemingMixin = {
} else if (hasExternal) {
this.applyRemoteTheme(this.externalThemes[initialTheme]);
}
// Detect OS theme preference and apply the corresponding theme
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const osTheme = prefersDark ? this.appConfig.nightTheme : this.appConfig.dayTheme;
if (osTheme) {
this.$store.commit(Keys.SET_THEME, osTheme);
this.updateTheme(osTheme);
}
},
},
};

View file

@ -2,7 +2,6 @@
* Mixin that all pre-built and custom widgets extend from.
* Manages loading state, error handling, data updates and user options
*/
import axios from 'axios';
import { Progress } from 'rsup-progress';
import ErrorHandler from '@/utils/ErrorHandler';
import { serviceEndpoints } from '@/utils/defaults';
@ -72,9 +71,9 @@ const WidgetMixin = {
this.updater = setInterval(() => { this.update(); }, this.updateInterval);
},
/* Called when an error occurs. Logs to handler, and passes to parent component */
error(msg, stackTrace, quite = false) {
error(msg, stackTrace) {
ErrorHandler(msg, stackTrace);
if (!this.options.ignoreErrors && !quite) {
if (!this.options.ignoreErrors) {
this.$emit('error', msg);
}
},
@ -106,27 +105,59 @@ const WidgetMixin = {
const method = protocol || 'GET';
const url = this.useProxy ? this.proxyReqEndpoint : endpoint;
const data = JSON.stringify(body || {});
const CustomHeaders = options || null;
const headers = this.useProxy
? { 'Target-URL': endpoint, CustomHeaders: JSON.stringify(CustomHeaders) } : CustomHeaders;
const CustomHeaders = options || {};
const headers = new Headers();
// If using a proxy, set the 'Target-URL' header
if (this.useProxy) {
headers.append('Target-URL', endpoint);
}
// Apply widget-specific custom headers
Object.entries(CustomHeaders).forEach(([key, value]) => {
headers.append(key, value);
});
// If the request is a GET, delete the body
const bodyContent = method.toUpperCase() === 'GET' ? undefined : data;
const timeout = this.options.timeout || this.defaultTimeout;
// Setup Fetch request configuration
const requestConfig = {
method, url, headers, data, timeout,
method,
headers,
body: bodyContent,
signal: undefined, // This will be set below
};
// Make request
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
requestConfig.signal = controller.signal;
// Make request using Fetch API
return new Promise((resolve, reject) => {
axios.request(requestConfig)
.then((response) => {
if (response.data.success === false) {
this.error('Proxy returned error from target server', response.data.message);
fetch(url, requestConfig)
.then(async response => {
const responseData = await response.json();
if (responseData.error) {
this.error('Proxy returned error from target server', responseData.error?.message);
}
resolve(response.data);
if (responseData.success === false) {
this.error('Proxy didn\'t return success from target server', responseData.message);
}
resolve(responseData);
})
.catch((dataFetchError) => {
this.error('Unable to fetch data', dataFetchError);
reject(dataFetchError);
.catch(error => {
if (error.name === 'AbortError') {
this.error('Request timed out', error);
} else {
this.error('Unable to fetch data', error);
}
reject(error);
})
.finally(() => {
clearTimeout(timeoutId);
this.finishLoading();
});
});

View file

@ -1142,104 +1142,6 @@ html[data-theme='glow'], html[data-theme=glow-colorful] {
}
}
html[data-theme='glow-dark'] {
--primary: #8faeff;
--background: #11111b;
--background-darker: #0a0a0f;
--curve-factor: 12px;
--item-group-background: #1b1b29;
--item-group-outer-background: #171724;
--item-background: #1d1d2b;
--font-headings: 'Sniglet', cursive;
--item-group-heading-text-color: var(--primary);
--item-group-heading-text-color-hover: #a0bfff;
--item-group-shadow: 0 5px 16px 0 #5c6da933;
--item-background-hover: #33334d;
--item-shadow: 0 1px 5px 0 #5c6da980;
--item-hover-shadow: 0 1px 8px 0 #5c6da9a6;
--item-icon-transform: drop-shadow(1px 2px 3px var(--transparent-50)) saturate(1.1);
--item-icon-transform-hover: drop-shadow(1px 2px 4px var(--transparent-50)) saturate(1.1);
--footer-height: 120px;
--transparent-50: #00000080;
header {
padding: 0.5rem;
background: var(--background-darker);
.page-titles {
h1 {
font-size: 1.8rem;
color: var(--primary);
}
span.subtitle {
font-size: 0.8rem;
color: #ccc;
text-shadow: none;
}
}
.nav .nav-item {
padding: 0.2rem 0.4rem;
box-shadow: none;
color: #ddd;
}
}
.settings-outer {
box-shadow: 0 4px 5px 0 #5c6da91a;
.options-container {
padding: 0.25rem 1.5rem 0.25rem 1rem;
background: var(--background-darker);
}
}
footer {
background: var(--background-darker);
box-shadow: 0 -4px 5px 0 #5c6da91a;
}
.search-wrap input {
background: #2a2a3d;
color: #eee;
box-shadow: 0 1px 5px 0 #5c6da980;
}
div.collapsable:nth-child(1n) {
a.item { color: #a88bff; }
--item-group-shadow: 0 5px 16px 0 #9f72ff33;
--item-group-heading-text-color: #bfa2ff;
--item-group-heading-text-color-hover: #cbb8ff;
--item-background-hover: #3a3a55;
--item-shadow: 0 1px 5px 0 #8656ef80;
--item-hover-shadow: 0 1px 8px 0 #8656efa6;
--item-icon-transform: drop-shadow(1px 2px 3px #8656ef80) saturate(1.1);
--item-icon-transform-hover: drop-shadow(1px 2px 4px #8656ef80) saturate(1.1);
}
div.collapsable:nth-child(2n) {
a.item { color: #e58fff; }
--item-group-shadow: 0 5px 16px 0 #728cff33;
--item-group-heading-text-color: #eaaaff;
--item-group-heading-text-color-hover: #f0c2ff;
--item-background-hover: #3a3a55;
--item-shadow: 0 1px 5px 0 #d356ef80;
--item-hover-shadow: 0 1px 8px 0 #d356efa6;
--item-icon-transform: drop-shadow(1px 2px 3px #d356ef80) saturate(1.1);
--item-icon-transform-hover: drop-shadow(1px 2px 4px #d356ef80) saturate(1.1);
}
div.collapsable:nth-child(3n) {
a.item { color: #56e0f0; }
--item-group-shadow: 0 5px 16px 0 #728cff33;
--item-group-heading-text-color: #7feeff;
--item-group-heading-text-color-hover: #a0f5ff;
--item-background-hover: #3a3a55;
--item-shadow: 0 1px 5px 0 #56ddef80;
--item-hover-shadow: 0 1px 8px 0 #56ddefa6;
--item-icon-transform: drop-shadow(1px 2px 3px #56ddef80) saturate(1.1);
--item-icon-transform-hover: drop-shadow(1px 2px 4px #56ddef80) saturate(1.1);
}
}
html[data-theme='cyberpunk'] {
--pink: #ff2a6d;
--pale: #d1f7ff;
@ -1947,88 +1849,6 @@ html[data-theme='neomorphic'] {
}
html[data-theme="night-bat"] {
// Main colors
--primary: #4780ff;
--background: #252931;
--background-darker: #303540;
// Typography
--font-headings: 'Podkova', 'Roboto', serif;
--font-body: 'Roboto', serif;
--heading-text-color: #fff;
// Items
--item-background: #303540;
--item-background-hover: var(--item-background);
--item-shadow: 0px 3px 0px var(--primary), 2px 2px 6px var(--black);
--item-hover-shadow: 0px 20px 0px 0 var(--primary), 2px 2px 6px var(--black);
// Sections
--item-group-heading-text-color: var(--white);
--item-group-heading-text-color-hover: var(--white);
--item-group-shadow: none;
--item-group-background: none;
--item-group-outer-background: none;
// Nav Links
--nav-link-background-color: var(--background);
--nav-link-background-color-hover: var(--background);
--nav-link-border-color: transparent;
--nav-link-border-color-hover: transparent;
--nav-link-shadow: 4px 4px 0px var(--background-darker), -3px 0px 0px var(--primary), 2px 2px 6px var(--black);
--nav-link-shadow-hover: 6px 6px 0px var(--background-darker), -4px 0px 0px var(--primary), 2px 2px 9px var(--black);
// Misc
--curve-factor: 4px;
--curve-factor-navbar: 8px;
--widget-text-color: var(--white);
// Style overrides
label.lbl-toggle h3 { font-size: 1.3rem; font-weight: bold; }
.content-inner { border-top: 1px dashed var(--primary); }
.item.size-large .tile-title p.description { height: 3rem; }
.item, .nav-outer nav .nav-item { border-radius: 1px; }
.item.size-large { margin: 0.5rem; }
// Show outline when collapsed
.is-collapsed {
background: var(--item-background);
box-shadow: var(--item-shadow);
&:hover {
background: var(--item-background-hover);
box-shadow: var(--item-hover-shadow);
}
}
.widget-base {
background: var(--background-darker);
padding: 1rem 0.5rem;
margin: 0.5rem 0;
}
.item-wrapper {
.item-url {
display: block;
opacity: 0;
position: absolute;
bottom: -1.9rem;
font-size: 0.8rem;
color: var(--background);
transition: all 0.2s cubic-bezier(0.8, 0.8, 0.4, 1.4);
}
a {
transition: all 0.2s cubic-bezier(0.8, 0.8, 0.4, 1.4);
height: calc(100% - 1rem);
}
&:hover {
a { height: calc(100% - 2rem); }
.item-icon {
transform: scale(0.9);
}
.item-url {
display: block;
opacity: 1;
}
}
}
}
html[data-theme='cherry-blossom'] {
--primary: #e1e8ee;
--background: #11171d;
@ -2179,7 +1999,7 @@ html[data-theme="tama"] {
// Background Image
body {
//update the query terms after the '?', to customize for images you want
background: url('https://picsum.photos/1920/1080');
background: url('https://source.unsplash.com/random/1920x1080/?dark,calm,nature,background');
background-color: var(--background-darker);
background-size: cover;
}

View file

@ -3,7 +3,6 @@ import ConfigAccumulator from '@/utils/ConfigAccumalator';
import ErrorHandler from '@/utils/ErrorHandler';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
import { isOidcEnabled } from '@/utils/OidcAuth';
/* Uses config accumulator to get and return app config */
const getAppConfig = () => {
@ -31,19 +30,7 @@ const getUsers = () => {
return []; // Support for old data structure now removed
}
// Otherwise, return the users array, if available
const users = auth.users || [];
if (isOidcEnabled()) {
if (localStorage[localStorageKeys.USERNAME]) {
const user = {
user: localStorage[localStorageKeys.USERNAME],
type: localStorage[localStorageKeys.ISADMIN] === 'true' ? 'admin' : 'normal',
};
users.push(user);
}
}
return users;
return auth.users || [];
};
/**
@ -92,17 +79,6 @@ export const makeBasicAuthHeaders = () => {
export const isLoggedIn = () => {
const users = getUsers();
const cookieToken = getCookieToken();
if (isOidcEnabled()) {
const username = localStorage[localStorageKeys.USERNAME]; // Get username
if (!username) return false; // No username
return users.some((user) => {
if (user.user === username || generateUserToken(user) === cookieToken) {
return true;
} else return false;
});
}
return users.some((user) => {
if (generateUserToken(user) === cookieToken) {
localStorage.setItem(localStorageKeys.USERNAME, user.user);
@ -120,7 +96,7 @@ export const isAuthEnabled = () => {
/* Returns true if guest access is enabled */
export const isGuestAccessEnabled = () => {
const appConfig = getAppConfig();
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled() && !isOidcEnabled()) {
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
return appConfig.auth.enableGuestAccess || false;
}
return false;
@ -253,10 +229,8 @@ export const getUserState = () => {
loggedIn,
guestAccess,
keycloakEnabled,
oidcEnabled,
} = userStateEnum; // Numeric enum options
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
if (isOidcEnabled()) return oidcEnabled;
if (!isAuthEnabled()) return notConfigured; // No auth enabled
if (isLoggedIn()) return loggedIn; // User is logged in
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing

View file

@ -541,50 +541,6 @@
]
}
},
"enableOidc": {
"title": "Enable OIDC?",
"type": "boolean",
"default": false,
"description": "If set to true, enable OIDC. See appConfig.auth.oidc"
},
"oidc": {
"type": "object",
"description": "Configuration for OIDC",
"additionalProperties": false,
"required": [
"clientId",
"endpoint"
],
"properties": {
"endpoint": {
"title": "OIDC Endpoint",
"type": "string",
"description": "Endpoint of OIDC provider"
},
"clientId": {
"title": "OIDC Client Id",
"type": "string",
"description": "ClientId from OIDC provider"
},
"adminRole" : {
"title": "Admin Role",
"type": "string",
"default": false,
"description": "The role that will be considered as admin. If not set, no roles will be considered as admin"
},
"adminGroup" : {
"title": "Admin Group",
"type": "string",
"default": false,
"description": "The group that will be considered as admin. If not set, no groups will be considered as admin"
},
"scope" : {
"title": "OIDC Scope",
"type": "string",
"description": "The scope(s) to request from the OIDC provider"
}
}
},
"enableHeaderAuth": {
"title": "Enable HeaderAuth?",
"type": "boolean",
@ -953,7 +909,7 @@
"title": "Icon",
"type": "string",
"nullable": true,
"description": "An icon, either as a font-awesome, simple-icon, selfh.st, or mdi identifier, emoji, favicon, generative or the URL/path to a local or remote icon asset"
"description": "An icon, either as a font-awesome, simple-icon or mdi identifier, emoji, favicon, generative or the URL/ path to a local or remote icon asset"
},
"url": {
"title": "Service URL",

View file

@ -1,104 +0,0 @@
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { localStorageKeys } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import { statusMsg, statusErrorMsg } from '@/utils/CoolConsole';
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
return config.appConfig || {};
};
class OidcAuth {
constructor() {
const { auth } = getAppConfig();
const {
clientId,
endpoint,
scope,
adminGroup,
adminRole,
} = auth.oidc;
const settings = {
userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: endpoint,
client_id: clientId,
redirect_uri: `${window.location.origin}`,
response_type: 'code',
scope: scope || 'openid profile email roles groups',
response_mode: 'query',
filterProtocolClaims: true,
loadUserInfo: true,
};
this.adminGroup = adminGroup;
this.adminRole = adminRole;
this.userManager = new UserManager(settings);
}
async login() {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
if (code) {
await this.userManager.signinCallback(window.location.href);
window.location.href = '/';
return;
}
const user = await this.userManager.getUser();
if (user === null) {
await this.userManager.signinRedirect();
} else {
const { roles = [], groups = [] } = user.profile;
const info = {
groups,
roles,
};
const isAdmin = (Array.isArray(groups) && groups.includes(this.adminGroup))
|| (Array.isArray(roles) && roles.includes(this.adminRole))
|| false;
statusMsg(`user: ${user.profile.preferred_username} admin: ${isAdmin}`, JSON.stringify(info));
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
localStorage.setItem(localStorageKeys.USERNAME, user.profile.preferred_username);
localStorage.setItem(localStorageKeys.ISADMIN, isAdmin);
}
}
async logout() {
localStorage.removeItem(localStorageKeys.USERNAME);
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
localStorage.removeItem(localStorageKeys.ISADMIN);
try {
await this.userManager.signoutRedirect();
} catch (reason) {
statusErrorMsg('logout', 'could not log out. Redirecting to OIDC instead', reason);
window.location.href = this.userManager.settings.authority;
}
}
}
export const isOidcEnabled = () => {
const { auth } = getAppConfig();
if (!auth) return false;
return auth.enableOidc || false;
};
let oidc;
export const initOidcAuth = () => {
oidc = new OidcAuth();
return oidc.login();
};
export const getOidcAuth = () => {
if (!oidc) {
ErrorHandler("OIDC not initialized, can't get instance of class");
}
return oidc;
};

View file

@ -78,7 +78,6 @@ module.exports = {
'bee',
'tiger',
'glow',
'glow-dark',
'vaporware',
'cyberpunk',
'material-original',
@ -90,7 +89,6 @@ module.exports = {
'tama',
'neomorphic',
'glass-2',
'night-bat',
],
/* Default color options for the theme configurator swatches */
swatches: [
@ -137,7 +135,6 @@ module.exports = {
MOST_USED: 'mostUsed',
LAST_USED: 'lastUsed',
KEYCLOAK_INFO: 'keycloakInfo',
ISADMIN: 'isAdmin',
DISABLE_CRITICAL_WARNING: 'disableCriticalWarning',
},
/* Key names for cookie identifiers */
@ -212,7 +209,6 @@ module.exports = {
fa: 'https://kit.fontawesome.com',
mdi: 'https://cdn.jsdelivr.net/npm/@mdi/font@7.0.96/css/materialdesignicons.min.css',
si: 'https://unpkg.com/simple-icons@v7/icons',
sh: 'https://cdn.jsdelivr.net/gh/selfhst/icons@latest/webp/{icon}.webp',
generative: 'https://api.dicebear.com/7.x/identicon/svg?seed={icon}',
generativeFallback: 'https://evatar.io/{icon}',
localPath: './item-icons',
@ -224,10 +220,9 @@ module.exports = {
},
/* API endpoints for widgets that need to fetch external data */
widgetApiEndpoints: {
anonAddy: 'https://app.addy.io',
anonAddy: 'https://app.anonaddy.com',
astronomyPictureOfTheDay: 'https://apod.as93.net/apod',
blacklistCheck: 'https://api.blacklistchecker.com/check',
chuckNorris: 'https://api.chucknorris.io/jokes/random',
codeStats: 'https://codestats.net/',
covidStats: 'https://disease.sh/v3/covid-19',
cryptoPrices: 'https://api.coingecko.com/api/v3/coins/',
@ -238,22 +233,17 @@ module.exports = {
ethGasHistory: 'https://ethgas.watch/api/gas/trend',
exchangeRates: 'https://v6.exchangerate-api.com/v6/',
flights: 'https://aerodatabox.p.rapidapi.com/flights/airports/icao/',
githubTrending: 'https://trend.doforce.xyz/',
githubTrending: 'https://gh-trending-repos.herokuapp.com/',
hackernewsTrending: 'https://hacker-news.firebaseio.com/v0',
healthChecks: 'https://healthchecks.io/api/v1/checks',
holidays: 'https://kayaposoft.com/enrico/json/v2.0/?action=getHolidaysForDateRange',
jokes: 'https://v2.jokeapi.dev/joke/',
news: 'https://api.currentsapi.services/v1/latest-news',
minecraftPlayerIcon: 'https://mc-heads.net/avatar/',
minecraftPlayerLink: 'https://minecraftuuid.com/?search=',
minecraftServerLink: 'https://mcsrvstat.us/server/',
minecraftStatus: 'https://api.mcsrvstat.us/',
mullvad: 'https://am.i.mullvad.net/json',
mvg: 'https://www.mvg.de/api/fib/v2/',
publicIp: 'https://ipapi.co/json',
publicIp2: 'https://api.ipgeolocation.io/ipgeo',
publicIp3: 'http://ip-api.com/json',
publicIp4: 'https://api.ip2location.io/',
readMeStats: 'https://github-readme-stats.vercel.app/api',
rescueTime: 'https://www.rescuetime.com/anapi/data',
rssToJson: 'https://api.rss2json.com/v1/api.json',
@ -315,11 +305,11 @@ module.exports = {
guestAccess: 2,
notLoggedIn: 3,
keycloakEnabled: 4,
oidcEnabled: 5,
},
/* Progressive Web App settings, used by Vue Config */
pwa: {
name: 'Dashy',
manifestPath: './manifest.json',
themeColor: '#00af87',
msTileColor: '#0b1021',
mode: 'production',
@ -327,30 +317,8 @@ module.exports = {
iconPaths: {
favicon64: './web-icons/favicon-64x64.png',
favicon32: './web-icons/favicon-32x32.png',
favicon16: './web-icons/favicon-16x16.png',
maskIcon: './web-icons/dashy-logo.png',
msTileImage: './web-icons/dashy-logo.png',
},
workboxOptions: {
exclude: [
// https://developer.chrome.com/docs/workbox/modules/workbox-build#properties_14
/\.map$/,
/^manifest.*\.js$/, // default value
/\.nojekyll$/,
/\.gitignore$/,
/conf\.yml$/, // ignore config for runtimeCaching
],
// https://developer.chrome.com/docs/workbox/modules/workbox-build#type-RuntimeCaching
runtimeCaching: [
{
urlPattern: /conf\.yml$/,
handler: 'NetworkFirst',
options: {
cacheName: 'config-cache',
networkTimeoutSeconds: 3,
},
},
],
},
},
};

View file

@ -10,7 +10,6 @@ import el from '@/assets/locales/el.json';
import es from '@/assets/locales/es.json';
import fr from '@/assets/locales/fr.json';
import hi from '@/assets/locales/hi.json';
import hu from '@/assets/locales/hu.json';
import it from '@/assets/locales/it.json';
import ja from '@/assets/locales/ja.json';
import ko from '@/assets/locales/ko.json';
@ -25,7 +24,7 @@ import sk from '@/assets/locales/sk.json';
import sl from '@/assets/locales/sl.json';
import sv from '@/assets/locales/sv.json';
import tr from '@/assets/locales/tr.json';
import uk from '@/assets/locales/uk.json';
import ua from '@/assets/locales/ua.json';
import zhCN from '@/assets/locales/zh-CN.json';
import zhTW from '@/assets/locales/zh-TW.json';
import pirate from '@/assets/locales/zz-pirate.json';
@ -99,12 +98,6 @@ export const languages = [
locale: hi,
flag: '🇮🇳',
},
{ // Hungarian
name: 'Magyar',
code: 'hu',
locale: hu,
flag: '🇭🇺',
},
{ // Italian
name: 'Italiano',
code: 'it',
@ -191,8 +184,8 @@ export const languages = [
},
{ // Ukrainian
name: 'Ukrainian',
code: 'uk',
locale: uk,
code: 'ua',
locale: ua,
flag: '🇺🇦',
},
{ // Chinese

View file

@ -75,9 +75,8 @@ export default {
},
},
mounted() {
this.initiateFontAwesome();
this.initiateMaterialDesignIcons();
this.setTheme();
this.initiateFontAwesome();
this.url = this.getInitialUrl();
},
};

View file

@ -1,7 +1,5 @@
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "/app/.tsbuildinfo",
"target": "esnext",
"module": "esnext",
"strict": false,

View file

@ -49,7 +49,6 @@ const plugins = !isServer ? [
// Webpack Config
const configureWebpack = {
devtool: 'source-map',
mode,
plugins,
module: {
@ -96,8 +95,5 @@ module.exports = {
devServer,
chainWebpack: config => {
config.module.rules.delete('svg');
config.cache({
type: 'filesystem',
});
},
};

1843
yarn.lock

File diff suppressed because it is too large Load diff