Compare commits

...

72 Commits

Author SHA1 Message Date
Tobias a768d01dbe
Merge pull request #1581 from CrazyWolf13/master
🧾 [docs](add) keycloak troubleshooting
2024-05-13 22:57:33 +02:00
Tobias c3b199361c 🧾 [docs](add) keycloak troubleshooting 2024-05-13 22:24:10 +02:00
Alicia Sykes 4b919f8a9b
🔀 Merge pull request #1573 from twsouthwick/oidc
Enable public application OIDC client support
Fixes #823
2024-05-13 21:02:44 +01:00
Alicia Bot 5a88beaf64 💙 Updates contributor SVG 2024-05-12 02:27:43 +01:00
liss-bot e151729cd8 💛 Updates sponsors table 2024-05-12 02:27:41 +01:00
liss-bot be9a996928 💜 Updates contributors list 2024-05-12 02:27:39 +01:00
Alicia Sykes eea193ae5c
🔀 Merge pull request #1577 from Lissy93/FIX/holiday-widget
[FIX] Date parsing on Safari for the Public Holidays widget
Fixes  #1532
2024-05-10 01:09:13 +01:00
Alicia Sykes 72d2f1bb74 🐛 Fixes (i think) date parsing for Safari users in holidays widget (#1532) 2024-05-10 00:56:30 +01:00
Alicia Sykes b97ba745a5
🔀 Merge pull request #1576 from Lissy93/FIX/widget-header-auth
[FIX] Auth headers for widgets
Fixes #1549
2024-05-10 00:45:33 +01:00
Alicia Sykes 46e1c2027e 🐛 Fixes auth headers for widgets (#1549) (#1561) 2024-05-10 00:13:59 +01:00
Alicia Sykes bb9bced18b
🔀 Merge pull request #1575 from twsouthwick/devcontainer
Add devcontainer support
2024-05-09 22:27:38 +01:00
Taylor Southwick c5f4f17449 Add devcontainer support 2024-05-09 17:13:01 +00:00
Taylor Southwick b9902e3fa0 Enable public application OIDC client support
This change uses oidc-client-ts to enable dashy to authenticate with as OIDC client. It populates the groups and roles so that it can be used the same as keycloak for showing/hiding elements on the dashboard.
2024-05-09 06:24:36 +00:00
Alicia Bot 93c2c77f6b 💙 Updates contributor SVG 2024-05-05 02:27:14 +01:00
liss-bot d6af666230 💛 Updates sponsors table 2024-05-05 02:27:10 +01:00
liss-bot beb262871e 💜 Updates contributors list 2024-05-05 02:27:09 +01:00
Tobias a7c15ce36f
Merge pull request #1563 from CrazyWolf13/master
🐳 Revert start cmd Dockerfile
2024-05-01 11:38:58 +02:00
Tobias 2f86b16491 temp revert start cmd 2024-05-01 11:33:01 +02:00
Alicia Sykes 5854db4205
🔀 Merge pull request #1423 from stinkybernie/update-simple-icons
Update 'simple-icons' from v7.19.0 to v10.4.0
Fixes #1254
2024-04-29 01:14:24 +01:00
Alicia Sykes c3aa2b5282
Merge branch 'master' into update-simple-icons 2024-04-29 00:54:02 +01:00
Alicia Sykes d92ae25700
🔀 Merge pull request #1542 from Lissy93/FEAT/3.0.1-improvements
[FEAT] Clearer error messaging and documented user-data dir (3.0.1)
2024-04-28 22:58:57 +01:00
Alicia Sykes 3fb87fa9b8 🔥 Fix DeepScan warn, by removing obsolete param 2024-04-28 22:27:24 +01:00
Alicia Sykes b5415ca5b9 💫 Adds explainer, below loading animation 2024-04-28 22:24:35 +01:00
Alicia Sykes 749f3b21da 🛂 Disable config for guests (#1552) 2024-04-28 22:21:51 +01:00
Alicia Sykes e970fc69c1 🐛 Apply custom headers to fetch request (#1549) 2024-04-28 21:54:16 +01:00
Alicia Sykes a9c46c362d
🔀 Merge pull request #1524 from zcq100/update-chinese-translate
Update chinese translate
2024-04-28 21:14:03 +01:00
Alicia Sykes c6f72ad84a
🔀 Merge pull request #1509 from GuilhermeLCS95/master
I added some translations to portuguese in pt.json
2024-04-28 21:07:09 +01:00
Alicia Sykes 31bf46a406
🔀 Merge pull request #1508 from dyauss/master
Translation for gluetun status sentences in pt.json
2024-04-28 21:06:45 +01:00
Alicia Sykes 83059bc536 Merge branch 'master' of github.com:lissy93/dashy into FEAT/3.0.1-improvements 2024-04-28 20:41:23 +01:00
Alicia Sykes a327bf2349 💫 Increase time between stages on initilization screen 2024-04-28 20:40:57 +01:00
Alicia Sykes 21eb2a604d 🐳 Updates port comment in docker-compose (#1555) 2024-04-28 20:16:15 +01:00
Alicia Sykes db9d7e362d 🛂 Remove page reload on auth failure (#981) 2024-04-28 20:00:17 +01:00
Alicia Sykes 85de40d950 🛂 Adds option for env vars for passwords 2024-04-28 15:08:05 +01:00
Alicia Sykes fd421cda15 🛂 Adds option for env vars for passwords 2024-04-28 15:07:48 +01:00
Alicia Sykes 98b3fda407 🔧 Adds new template vars to .env 2024-04-28 15:07:02 +01:00
Alicia Sykes 27f10d76db 🦺 Update schema validation, disable strict for multi options 2024-04-28 15:05:53 +01:00
Alicia Sykes 29626ebb06 🗃 Updates schema to allow for env vars on user passwords 2024-04-28 15:05:01 +01:00
Alicia Bot f9f365aa3a 💙 Updates contributor SVG 2024-04-28 02:27:13 +01:00
liss-bot 292a46f402 💛 Updates sponsors table 2024-04-28 02:27:07 +01:00
liss-bot f766e990b9 💜 Updates contributors list 2024-04-28 02:27:05 +01:00
Alicia Sykes 25ba708a9f 📝 Add Umbrel asset 2024-04-27 23:20:22 +01:00
Alicia Sykes 0813b796ab 📝 Documents HTTP authorization 2024-04-27 23:17:43 +01:00
Alicia Sykes 1f6b433148 🛂 Implements HTTP authorization client-side 2024-04-27 23:17:23 +01:00
Alicia Sykes 99643acddf 🛂 Adds support for HTTP authorization 2024-04-27 23:16:56 +01:00
Alicia Sykes 9d683dcbf0 🐛 Fix layout and item size buttons 2024-04-27 15:52:40 +01:00
Alicia Sykes 3416615d30 Merge branch 'FEAT/3.0.1-improvements' of github.com:lissy93/dashy into FEAT/3.0.1-improvements 2024-04-27 01:01:48 +01:00
Alicia Sykes db63362327 💬 Translate critical error text, and update styles 2024-04-27 00:45:30 +01:00
Alicia Sykes 9e6fb17d93 🥅 Catch error caused by empty config 2024-04-27 00:32:01 +01:00
Alicia Sykes 4594c99b57 🐛 Fix collapse state persistence (#1546) 2024-04-26 00:29:35 +01:00
Alicia Sykes f77c192e66
📝 Switch Umbrel asset 2024-04-24 11:05:08 +01:00
Alicia Sykes ca96e0c1de
📝 Adds Umbrel sponsor link 2024-04-23 22:25:44 +01:00
Alicia Sykes 1bbb91b3fe 🐳 Change start command in Dockerfile (#1543) 2024-04-22 12:40:46 +01:00
Alicia Sykes d58cde69f6 🐛 Fix page title not being applied (#1544) 2024-04-22 12:25:09 +01:00
Alicia Sykes f68d65264c 🤖 Remove star reminder automation (#1412) 2024-04-21 23:37:03 +01:00
Alicia Sykes 27bbdef6df 🔖 Bump to 3.0.1 2024-04-21 22:33:49 +01:00
Alicia Sykes 94307da70b 🗑 Deleted old img folder 2024-04-21 22:33:24 +01:00
Alicia Sykes d34c4b55f5 💄 Adds another shade of white 2024-04-21 22:30:41 +01:00
Alicia Sykes 915a5dfd38 🥅 Also catch error for sub-pages 2024-04-21 22:30:11 +01:00
Alicia Sykes be513a0952 Shows error details, if a critical error happens 2024-04-21 22:29:32 +01:00
Alicia Sykes 2ce3b29ad2 👔 Adds logic to show dialog on critical error 2024-04-21 22:28:50 +01:00
Alicia Sykes a138602670 📝 Clearer docs for user-data directory (#1538) 2024-04-21 21:39:14 +01:00
Alicia Sykes ecef01b034 🥅 Better error handling when config cannot be found 2024-04-21 14:46:38 +01:00
Alicia Sykes f295958c44 Put config backups in own directory 2024-04-21 14:45:52 +01:00
Alicia Bot 382f8f3ec0 💙 Updates contributor SVG 2024-04-21 02:26:38 +01:00
liss-bot 1e72debe5f 💛 Updates sponsors table 2024-04-21 02:26:32 +01:00
liss-bot 54a00ee099 💜 Updates contributors list 2024-04-21 02:26:30 +01:00
Alicia Sykes fa6d1925b7 🚸 Makes GitHub cards hyperlinks (fixes #1531) 2024-04-20 21:47:51 +01:00
r1a 07391dd4d1
Update chinese translate 2024-03-28 10:04:43 +08:00
GuilhermeLCS b2e5597e49 I added some translations to portuguese in pt.json 2024-03-13 22:05:43 -03:00
GuilhermeLCS ee3e45a56e I added some portuguese translations in pt.json 2024-03-13 21:32:48 -03:00
Dyauss b55b7f7778 Translation for gluetun status sentences 2024-03-13 21:32:02 -03:00
Michael D 69caa99c90 ⬆️ - Updated 'simple-icons' from v7.19.0 to v10.4.0 and update API calls 2023-12-29 11:59:59 -08:00
55 changed files with 1646 additions and 714 deletions

View File

@ -0,0 +1,36 @@
// 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"
}

13
.env
View File

@ -27,6 +27,17 @@
# The path to the user data directory
# USER_DATA_DIR=user-data
# Enable HTTP basic auth to protect your *.yml config files
# ENABLE_HTTP_AUTH=true
# Enable basic HTTP auth to protect your *.yml config files
# BASIC_AUTH_USERNAME
# BASIC_AUTH_PASSWORD
# If you'd like frontend to automatically authenticate when basic auth enabled, set credentials here too
# VUE_APP_BASIC_AUTH_USERNAME
# VUE_APP_BASIC_AUTH_PASSWORD
# Override where the path to the configuration file is, can be a remote URL
# VUE_APP_CONFIG_PATH=/conf.yml
@ -52,7 +63,7 @@
# VUE_APP_VERSION=2.0.0
# Directory for conf.yml backups
# BACKUP_DIR=./user-data/
# BACKUP_DIR=./user-data/config-backups
# Setup any other user defined vars by prepending VUE_APP_ to the var name
# VUE_APP_pihole_ip=http://your.pihole.ip

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# 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,22 +0,0 @@
name: ⭐ Hello non-Stargazers
on:
issues:
types: [opened, reopened]
jobs:
check-user:
if: >
${{
! contains( github.event.issue.labels.*.name, '📌 Keep Open') &&
! contains( github.event.issue.labels.*.name, '🌈 Feedback') &&
! contains( github.event.issue.labels.*.name, '💯 Showcase') &&
github.event.comment.author_association != 'CONTRIBUTOR'
}}
runs-on: ubuntu-latest
name: Add comment to issues opened by non-stargazers
steps:
- name: comment
uses: qxip/please-star-light@v4
with:
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
autoclose: false
message: "If you're enjoying Dashy, consider dropping us a ⭐<br>_<sub>🤖 I'm a bot, and this message was automated</sub>_"

1
.gitignore vendored
View File

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

65
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,65 @@
{
// 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 Normal file
View File

@ -0,0 +1,21 @@
{
"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

@ -8,6 +8,14 @@
<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">
<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>
<a href="https://umbrel.com?ref=dashy">
<img width="400" src="https://github.com/Lissy93/dashy/blob/WEBSITE/docs-site-source/static/umbrel-banner.jpg?raw=true" />
</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.
@ -517,6 +525,13 @@ Huge thanks to the sponsors helping to support Dashy's development!
<!-- 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"/>
@ -531,13 +546,6 @@ Huge thanks to the sponsors helping to support Dashy's development!
<sub><b>Aaron Viehl</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/peng1can">
<img src="https://avatars.githubusercontent.com/u/225854?v=4" width="80;" alt="peng1can"/>
<br />
<sub><b>Peng1can</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"/>
@ -545,21 +553,13 @@ Huge thanks to the sponsors helping to support Dashy's development!
<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>
<td align="center">
<a href="https://github.com/shrippen">
<img src="https://avatars.githubusercontent.com/u/2873570?v=4" width="80;" alt="shrippen"/>
@ -584,7 +584,7 @@ Huge thanks to the sponsors helping to support Dashy's development!
</td>
<td align="center">
<a href="https://github.com/digitalarche">
<img src="https://avatars.githubusercontent.com/u/6546135?u=d033c9c16e8367987aec3f9ff5922bc67dd1eedf&v=4" width="80;" alt="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>
@ -600,17 +600,17 @@ Huge thanks to the sponsors helping to support Dashy's development!
<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>
<sub><b>Araguaci</b></sub>
</a>
</td></tr>
<tr>
</td>
<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></tr>
<tr>
<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"/>
@ -627,11 +627,18 @@ Huge thanks to the sponsors helping to support Dashy's development!
</td>
<td align="center">
<a href="https://github.com/patvdv">
<img src="https://avatars.githubusercontent.com/u/12430107?v=4" width="80;" alt="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?u=48a58d2da520a9d712184c6e6e99927ff3cbf179&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"/>
@ -640,13 +647,20 @@ Huge thanks to the sponsors helping to support Dashy's development!
</a>
</td>
<td align="center">
<a href="https://github.com/undefined">
<img src="" width="80;" alt="undefined"/>
<a href="https://github.com/allesauseinerhand">
<img src="https://avatars.githubusercontent.com/u/32039836?v=4" width="80;" alt="allesauseinerhand"/>
<br />
<sub><b>Undefined</b></sub>
<sub><b>Allesauseinerhand</b></sub>
</a>
</td></tr>
<tr>
<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"/>

View File

@ -20,7 +20,7 @@ services:
# - /path/to/my-config.yml:/app/user-data/conf.yml
# - /path/to/item-icons:/app/user-data/item-icons/
# Set port that web service will be served on. Keep container port as 80
# Set port that web service will be served on. Keep container port as 8080
ports:
- 4000:8080

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 MiB

After

Width:  |  Height:  |  Size: 30 MiB

View File

@ -6,11 +6,15 @@
- [Logging In and Out](#logging-in-and-out)
- [Guest Access](#enabling-guest-access)
- [Per-User Access](#granular-access)
- [Using Environment Variables for Passwords](#using-environment-variables-for-passwords)
- [Adding HTTP Auth to Configuration](#adding-http-auth-to-configuration)
- [Security Considerations](#security)
- [HTTP Auth](#http-auth)
- [Keycloak Auth](#keycloak)
- [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)
- [Alternative Authentication Methods](#alternative-authentication-methods)
- [VPN](#vpn)
- [IP-Based Access](#ip-based-access)
@ -115,6 +119,27 @@ You can also prevent any user from writing changes to disk, using `preventWriteT
To disable all UI config features, including View Config, set `disableConfiguration`. Alternatively you can disable UI config features for all non admin users by setting `disableConfigurationForNonAdmin` to true.
### Using Environment Variables for Passwords
If you don't want to hash your password, you can instead leave out the `hash` attribute, and replace it with `password` which should have the value of an environmental variable name you wish to use.
Note that env var must begin with `VUE_APP_`, and you must set this variable before building the app.
For example:
```yaml
auth:
users:
- user: bob
password: VUE_APP_BOB
```
Just be sure to set `VUE_APP_BOB='my super secret password'` before build-time.
### Adding HTTP Auth to Configuration
If you'd also like to prevent direct visit access to your configuration file, you can set the `ENABLE_HTTP_AUTH` environmental variable.
### Security
With basic auth, all logic is happening on the client-side, which could mean a skilled user could manipulate the code to view parts of your configuration, including the hash. If the SHA-256 hash is of a common password, it may be possible to determine it, using a lookup table, in order to find the original password. Which can be used to manually generate the auth token, that can then be inserted into session storage, to become a valid logged in user. Therefore, you should always use a long, strong and unique password, and if you instance contains security-critical info and/ or is exposed directly to the internet, and alternative authentication method may be better. The purpose of the login page is merely to prevent immediate unauthorized access to your homepage.
@ -123,6 +148,16 @@ With basic auth, all logic is happening on the client-side, which could mean a s
---
## HTTP Auth
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, 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)**
---
## Keycloak
Dashy also supports using a [Keycloak](https://www.keycloak.org/) authentication server. The setup for this is a bit more involved, but it gives you greater security overall, useful for if your instance is exposed to the internet.
@ -219,6 +254,67 @@ 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]
```
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) abvoe.
---
## 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

@ -158,6 +158,8 @@ The following file provides a reference of all supported configuration options.
**`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)**
@ -194,6 +196,15 @@ 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.
**[⬆️ Back to Top](#configuring)**
## `appConfig.webSearch` _(optional)_
**Field** | **Type** | **Required**| **Description**

View File

@ -4,6 +4,13 @@
<!-- readme: sponsors -start -->
<table>
<tr>
<td align="center">
<a href="https://github.com/github">
<img src="https://avatars.githubusercontent.com/u/9919?v=4" width="80;" alt="github"/>
<br />
<sub><b>GitHub</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"/>
@ -18,13 +25,6 @@
<sub><b>Aaron Viehl</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/peng1can">
<img src="https://avatars.githubusercontent.com/u/225854?v=4" width="80;" alt="peng1can"/>
<br />
<sub><b>Null</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"/>
@ -32,28 +32,21 @@
<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>
<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>Null</b></sub>
</a>
</td>
</td></tr>
<tr>
<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"/>
@ -70,7 +63,7 @@
</td>
<td align="center">
<a href="https://github.com/digitalarche">
<img src="https://avatars.githubusercontent.com/u/6546135?u=d033c9c16e8367987aec3f9ff5922bc67dd1eedf&v=4" width="80;" alt="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>
@ -88,15 +81,15 @@
<br />
<sub><b>Null</b></sub>
</a>
</td></tr>
<tr>
</td>
<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></tr>
<tr>
<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"/>
@ -113,11 +106,18 @@
</td>
<td align="center">
<a href="https://github.com/patvdv">
<img src="https://avatars.githubusercontent.com/u/12430107?v=4" width="80;" alt="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?u=48a58d2da520a9d712184c6e6e99927ff3cbf179&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"/>
@ -125,14 +125,28 @@
<sub><b>Göksel Yeşiller</b></sub>
</a>
</td>
<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>Null</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/forwardemail">
<img src="https://avatars.githubusercontent.com/u/32481436?v=4" width="80;" alt="forwardemail"/>
<br />
<sub><b>Forward Email - Open-source & Privacy-focused Email Service (2023)</b></sub>
</a>
</td></tr>
<tr>
</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"/>
@ -140,6 +154,13 @@
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/getumbrel">
<img src="https://avatars.githubusercontent.com/u/59408891?v=4" width="80;" alt="getumbrel"/>
<br />
<sub><b>Umbrel</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"/>
@ -153,7 +174,8 @@
<br />
<sub><b>Null</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/terminaltrove">
<img src="https://avatars.githubusercontent.com/u/121595180?v=4" width="80;" alt="terminaltrove"/>
@ -225,13 +247,6 @@
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/m42e">
<img src="https://avatars.githubusercontent.com/u/2410802?v=4" width="80;" alt="m42e"/>
<br />
<sub><b>Matthias Bilger</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/CrazyWolf13">
<img src="https://avatars.githubusercontent.com/u/96661824?v=4" width="80;" alt="CrazyWolf13"/>
@ -239,6 +254,13 @@
<sub><b>Tobias</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/m42e">
<img src="https://avatars.githubusercontent.com/u/2410802?v=4" width="80;" alt="m42e"/>
<br />
<sub><b>Matthias Bilger</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/pinarruiz">
<img src="https://avatars.githubusercontent.com/u/37040888?v=4" width="80;" alt="pinarruiz"/>
@ -332,6 +354,13 @@
<sub><b>Mihai</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/zcq100">
<img src="https://avatars.githubusercontent.com/u/425234?v=4" width="80;" alt="zcq100"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/wozboz">
<img src="https://avatars.githubusercontent.com/u/51856582?v=4" width="80;" alt="wozboz"/>
@ -345,27 +374,13 @@
<br />
<sub><b>Totto16</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/aviolaris">
<img src="https://avatars.githubusercontent.com/u/48277853?v=4" width="80;" alt="aviolaris"/>
<br />
<sub><b>Andreas Violaris</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/Tracreed">
<img src="https://avatars.githubusercontent.com/u/6306365?v=4" width="80;" alt="Tracreed"/>
<a href="https://github.com/toddejohnson">
<img src="https://avatars.githubusercontent.com/u/507545?v=4" width="80;" alt="toddejohnson"/>
<br />
<sub><b>David Alasow</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/DimitriDR">
<img src="https://avatars.githubusercontent.com/u/56969769?v=4" width="80;" alt="DimitriDR"/>
<br />
<sub><b>Dimitri</b></sub>
<sub><b>Todd Johnson</b></sub>
</a>
</td>
<td align="center">
@ -376,60 +391,24 @@
</a>
</td>
<td align="center">
<a href="https://github.com/alucarddelta">
<img src="https://avatars.githubusercontent.com/u/20882097?v=4" width="80;" alt="alucarddelta"/>
<a href="https://github.com/DimitriDR">
<img src="https://avatars.githubusercontent.com/u/56969769?v=4" width="80;" alt="DimitriDR"/>
<br />
<sub><b>Brent</b></sub>
<sub><b>Dimitri</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/berksmbl">
<img src="https://avatars.githubusercontent.com/u/10000339?v=4" width="80;" alt="berksmbl"/>
<a href="https://github.com/aviolaris">
<img src="https://avatars.githubusercontent.com/u/48277853?v=4" width="80;" alt="aviolaris"/>
<br />
<sub><b>Berk Sümbül</b></sub>
<sub><b>Andreas Violaris</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Tuzi555">
<img src="https://avatars.githubusercontent.com/u/62188066?v=4" width="80;" alt="Tuzi555"/>
<a href="https://github.com/Tracreed">
<img src="https://avatars.githubusercontent.com/u/6306365?v=4" width="80;" alt="Tracreed"/>
<br />
<sub><b>Jakub Tuzar</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/Bogyie">
<img src="https://avatars.githubusercontent.com/u/82003678?v=4" width="80;" alt="Bogyie"/>
<br />
<sub><b>Bogyeong Kim</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/stanly0726">
<img src="https://avatars.githubusercontent.com/u/37040069?v=4" width="80;" alt="stanly0726"/>
<br />
<sub><b>Stanly0726</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/onedr0p">
<img src="https://avatars.githubusercontent.com/u/213795?v=4" width="80;" alt="onedr0p"/>
<br />
<sub><b>ᗪєνιη ᗷυнʟ</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/zcq100">
<img src="https://avatars.githubusercontent.com/u/425234?v=4" width="80;" alt="zcq100"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rtm516">
<img src="https://avatars.githubusercontent.com/u/5401186?v=4" width="80;" alt="rtm516"/>
<br />
<sub><b>Rtm516</b></sub>
<sub><b>David Alasow</b></sub>
</a>
</td>
<td align="center">
@ -441,8 +420,94 @@
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/k073l">
<img src="https://avatars.githubusercontent.com/u/21180271?v=4" width="80;" alt="k073l"/>
<a href="https://github.com/rtm516">
<img src="https://avatars.githubusercontent.com/u/5401186?v=4" width="80;" alt="rtm516"/>
<br />
<sub><b>Rtm516</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/onedr0p">
<img src="https://avatars.githubusercontent.com/u/213795?v=4" width="80;" alt="onedr0p"/>
<br />
<sub><b>ᗪєνιη ᗷυнʟ</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/stanly0726">
<img src="https://avatars.githubusercontent.com/u/37040069?v=4" width="80;" alt="stanly0726"/>
<br />
<sub><b>Stanly0726</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Bogyie">
<img src="https://avatars.githubusercontent.com/u/82003678?v=4" width="80;" alt="Bogyie"/>
<br />
<sub><b>Bogyeong Kim</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Tuzi555">
<img src="https://avatars.githubusercontent.com/u/62188066?v=4" width="80;" alt="Tuzi555"/>
<br />
<sub><b>Jakub Tuzar</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/berksmbl">
<img src="https://avatars.githubusercontent.com/u/10000339?v=4" width="80;" alt="berksmbl"/>
<br />
<sub><b>Berk Sümbül</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/alucarddelta">
<img src="https://avatars.githubusercontent.com/u/20882097?v=4" width="80;" alt="alucarddelta"/>
<br />
<sub><b>Brent</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/BySempron">
<img src="https://avatars.githubusercontent.com/u/15928132?v=4" width="80;" alt="BySempron"/>
<br />
<sub><b>Sergio</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ssrangisetti">
<img src="https://avatars.githubusercontent.com/u/46807508?v=4" width="80;" alt="ssrangisetti"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/zigotica">
<img src="https://avatars.githubusercontent.com/u/178855?v=4" width="80;" alt="zigotica"/>
<br />
<sub><b>Sergi Meseguer</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rokiden">
<img src="https://avatars.githubusercontent.com/u/11071229?v=4" width="80;" alt="rokiden"/>
<br />
<sub><b>Denis Kazimirov</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/patrickheeney">
<img src="https://avatars.githubusercontent.com/u/1407228?v=4" width="80;" alt="patrickheeney"/>
<br />
<sub><b>Patrick Heeney</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/mmihaly">
<img src="https://avatars.githubusercontent.com/u/50031464?v=4" width="80;" alt="mmihaly"/>
<br />
<sub><b>Null</b></sub>
</a>
@ -455,41 +520,12 @@
</a>
</td>
<td align="center">
<a href="https://github.com/mmihaly">
<img src="https://avatars.githubusercontent.com/u/50031464?v=4" width="80;" alt="mmihaly"/>
<a href="https://github.com/k073l">
<img src="https://avatars.githubusercontent.com/u/21180271?v=4" width="80;" alt="k073l"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/patrickheeney">
<img src="https://avatars.githubusercontent.com/u/1407228?v=4" width="80;" alt="patrickheeney"/>
<br />
<sub><b>Patrick Heeney</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/rokiden">
<img src="https://avatars.githubusercontent.com/u/11071229?v=4" width="80;" alt="rokiden"/>
<br />
<sub><b>Denis Kazimirov</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ssrangisetti">
<img src="https://avatars.githubusercontent.com/u/46807508?v=4" width="80;" alt="ssrangisetti"/>
<br />
<sub><b>Null</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/BySempron">
<img src="https://avatars.githubusercontent.com/u/15928132?v=4" width="80;" alt="BySempron"/>
<br />
<sub><b>Sergio</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/itsmejoeeey">
<img src="https://avatars.githubusercontent.com/u/9375730?v=4" width="80;" alt="itsmejoeeey"/>
@ -510,6 +546,14 @@
<br />
<sub><b>Thomas Wienecke</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/GuilhermeLCS95">
<img src="https://avatars.githubusercontent.com/u/116608998?v=4" width="80;" alt="GuilhermeLCS95"/>
<br />
<sub><b>GuilhermeLCS</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/deneor">
@ -524,8 +568,7 @@
<br />
<sub><b>Dan Gilbert</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/moemoeq">
<img src="https://avatars.githubusercontent.com/u/1808434?v=4" width="80;" alt="moemoeq"/>
@ -546,7 +589,8 @@
<br />
<sub><b>Alexander Mnich</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/alayham">
<img src="https://avatars.githubusercontent.com/u/518776?v=4" width="80;" alt="alayham"/>
@ -567,8 +611,7 @@
<br />
<sub><b>Sacha</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/shazzx">
<img src="https://avatars.githubusercontent.com/u/131521332?v=4" width="80;" alt="shazzx"/>
@ -589,7 +632,8 @@
<br />
<sub><b>Null</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/Smexhy">
<img src="https://avatars.githubusercontent.com/u/4880625?v=4" width="80;" alt="Smexhy"/>
@ -610,8 +654,7 @@
<br />
<sub><b>Steffen Schmidt</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/StevKast">
<img src="https://avatars.githubusercontent.com/u/17804308?v=4" width="80;" alt="StevKast"/>
@ -620,10 +663,10 @@
</a>
</td>
<td align="center">
<a href="https://github.com/AmadeusGraves">
<img src="https://avatars.githubusercontent.com/u/18572939?v=4" width="80;" alt="AmadeusGraves"/>
<a href="https://github.com/twsouthwick">
<img src="https://avatars.githubusercontent.com/u/583206?v=4" width="80;" alt="twsouthwick"/>
<br />
<sub><b>Ángel Fernández Sánchez</b></sub>
<sub><b>Taylor Southwick</b></sub>
</a>
</td>
<td align="center">
@ -632,7 +675,8 @@
<br />
<sub><b>Ryan Turner</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/rubjo">
<img src="https://avatars.githubusercontent.com/u/42270947?v=4" width="80;" alt="rubjo"/>
@ -653,8 +697,7 @@
<br />
<sub><b>Andrey</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/oka4shi">
<img src="https://avatars.githubusercontent.com/u/67847553?v=4" width="80;" alt="oka4shi"/>
@ -669,6 +712,14 @@
<sub><b>Nicholas Malcolm</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/stinkybernie">
<img src="https://avatars.githubusercontent.com/u/155188453?v=4" width="80;" alt="stinkybernie"/>
<br />
<sub><b>Michael D</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/miclav">
<img src="https://avatars.githubusercontent.com/u/11891522?v=4" width="80;" alt="miclav"/>
@ -684,10 +735,10 @@
</a>
</td>
<td align="center">
<a href="https://github.com/ethan-hann">
<img src="https://avatars.githubusercontent.com/u/36464732?v=4" width="80;" alt="ethan-hann"/>
<a href="https://github.com/AmadeusGraves">
<img src="https://avatars.githubusercontent.com/u/18572939?v=4" width="80;" alt="AmadeusGraves"/>
<br />
<sub><b>Ethan Hann</b></sub>
<sub><b>Ángel Fernández Sánchez</b></sub>
</a>
</td>
<td align="center">
@ -696,8 +747,7 @@
<br />
<sub><b>Null</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/sur1v">
<img src="https://avatars.githubusercontent.com/u/19678230?v=4" width="80;" alt="sur1v"/>
@ -711,7 +761,8 @@
<br />
<sub><b>Soaibuzzaman</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/pablomalo">
<img src="https://avatars.githubusercontent.com/u/25877142?v=4" width="80;" alt="pablomalo"/>
@ -739,8 +790,7 @@
<br />
<sub><b>Kxenox</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/jrobles98">
<img src="https://avatars.githubusercontent.com/u/30221842?v=4" width="80;" alt="jrobles98"/>
@ -754,7 +804,8 @@
<br />
<sub><b>Jnach</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/imlonghao">
<img src="https://avatars.githubusercontent.com/u/4951333?v=4" width="80;" alt="imlonghao"/>
@ -782,8 +833,7 @@
<br />
<sub><b>Null</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/dr460nf1r3">
<img src="https://avatars.githubusercontent.com/u/12834713?v=4" width="80;" alt="dr460nf1r3"/>
@ -797,7 +847,8 @@
<br />
<sub><b>Null</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/allozavrr">
<img src="https://avatars.githubusercontent.com/u/63748214?v=4" width="80;" alt="allozavrr"/>
@ -812,6 +863,13 @@
<sub><b>Xert</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/maximemoreillon">
<img src="https://avatars.githubusercontent.com/u/29086128?v=4" width="80;" alt="maximemoreillon"/>
<br />
<sub><b>Maxime Moreillon</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/emiran-orange">
<img src="https://avatars.githubusercontent.com/u/71817149?v=4" width="80;" alt="emiran-orange"/>
@ -825,14 +883,21 @@
<br />
<sub><b>Eduardo Gomez</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/Dylan-Bs">
<img src="https://avatars.githubusercontent.com/u/35694107?v=4" width="80;" alt="Dylan-Bs"/>
<br />
<sub><b>Dylan Bersans</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/dyauss">
<img src="https://avatars.githubusercontent.com/u/50002238?v=4" width="80;" alt="dyauss"/>
<br />
<sub><b>Thandy Norberto</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dougaldhub">
@ -934,13 +999,6 @@
<sub><b>0n1cOn3</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/maximemoreillon">
<img src="https://avatars.githubusercontent.com/u/29086128?v=4" width="80;" alt="maximemoreillon"/>
<br />
<sub><b>Maxime Moreillon</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Glitch3dPenguin">
<img src="https://avatars.githubusercontent.com/u/3271160?v=4" width="80;" alt="Glitch3dPenguin"/>
@ -954,15 +1012,15 @@
<br />
<sub><b>Markus Krause</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/asenov">
<img src="https://avatars.githubusercontent.com/u/280619?v=4" width="80;" alt="asenov"/>
<br />
<sub><b>Мирослав Асенов</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/luispabon">
<img src="https://avatars.githubusercontent.com/u/6388823?v=4" width="80;" alt="luispabon"/>
@ -997,15 +1055,15 @@
<br />
<sub><b>Jeremy Chauvin</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/Hellhium">
<img src="https://avatars.githubusercontent.com/u/11504877?v=4" width="80;" alt="Hellhium"/>
<br />
<sub><b>Jemy SCHNEPP</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/jjmung">
<img src="https://avatars.githubusercontent.com/u/6049600?v=4" width="80;" alt="jjmung"/>
@ -1040,15 +1098,15 @@
<br />
<sub><b>Hendrik Strydom</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/hubortje">
<img src="https://avatars.githubusercontent.com/u/62364169?v=4" width="80;" alt="hubortje"/>
<br />
<sub><b>Harald Töpfer</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/gbrown09">
<img src="https://avatars.githubusercontent.com/u/3360055?v=4" width="80;" alt="gbrown09"/>
@ -1062,6 +1120,13 @@
<br />
<sub><b>FormatToday</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ethan-hann">
<img src="https://avatars.githubusercontent.com/u/36464732?v=4" width="80;" alt="ethan-hann"/>
<br />
<sub><b>Ethan Hann</b></sub>
</a>
</td></tr>
</table>
<!-- readme: contributors -end -->

View File

@ -32,7 +32,32 @@ Your dashboard should now be up and running at `http://localhost:8080` (or your
---
## 3. Configure
## 3. User Data Directory
Your config file should be placed inside `user-data/` (in Docker, that's `/app/user-data/`).
This directory can also contain some optional assets you wish to use within your dashboard, like icons, fonts, styles, scripts, etc.
Any files placed here will be served up to the root of the domain, and override the contents of `public/`.
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 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)
- `robots.txt` - Search engine crawl rules, override this if you want your dashboard to be indexable
- `manifest.json` - PWA configuration file, for installing Dashy on mobile devices
- `index.html` - The main index page which initializes the client-side app, copy it from [`/public/index.html`](https://github.com/Lissy93/dashy/blob/master/public/index.html)
- `**.html` - Write your own HTML pages, and access them at `http://my-dashy-instance.local/my-page.html`
- `fonts/` - Custom fonts (be sure to include the ones already in [`public/fonts`](https://github.com/Lissy93/dashy/tree/master/public/fonts)
- `item-icons/` - To use your own icons for items on your dashboard, see [Icons --> Local Icons](/docs/icons.md#local-icons)
- `web-icons/` - Override Dashy logo
- `widget-resources/` - Fonts, icons and assets for custom widgets
---
## 4. Configure
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).
@ -41,6 +66,7 @@ The format on the config file is pretty straight forward. There are three root a
- [`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
- [`sections`](https://github.com/Lissy93/dashy/blob/master/docs/configuring.md#section) - An array of sections, each including an array of items
- [`pages`](https://github.com/Lissy93/dashy/blob/master/docs/configuring.md#pages-optional) - Have multiples pages in your dashboard
You can view a full list of all available config options in the [Configuring Docs](https://github.com/Lissy93/dashy/blob/master/docs/configuring.md).
@ -76,11 +102,11 @@ Notes:
- It's also possible to edit your config directly through the UI, and changes will be saved in this file
- Check your config against Dashy's schema, with `docker exec -it [container-id] yarn validate-config`
- You might find it helpful to look at some examples, a collection of which can be [found here](https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10)
- After editing your config, the app will rebuild in the background, which may take a minute
- It's also possible to load a remote config, e.g. from a GitHub Gist
---
## 4. Further Customisation
## 5. Further Customisation
Once you've got Dashy setup, you'll want to ensure the container is properly healthy, secured, backed up and kept up-to-date. All this is covered in the [Management Docs](https://github.com/Lissy93/dashy/blob/master/docs/management.md).
@ -97,7 +123,7 @@ You might also want to check out the docs for specific features you'd like to us
---
## 5. Final Note
## 6. Final Note
If you need any help or support in getting Dashy running, head over to the [Discussions](https://github.com/Lissy93/dashy/discussions) page. If you think you've found a bug, please do [raise it](https://github.com/Lissy93/dashy/issues/new/choose) so it can be fixed. For contact options, see the [Support Page](https://github.com/Lissy93/dashy/blob/master/.github/SUPPORT.md).
@ -118,7 +144,7 @@ yarn build # Build the app
yarn start # Start the app
```
Then edit `./user-data/conf.yml` and rebuild the app with `yarn build`
Then edit `./user-data/conf.yml`
---

View File

@ -1,6 +1,6 @@
{
"name": "dashy",
"version": "3.0.0",
"version": "3.1.0",
"license": "MIT",
"main": "server",
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",
@ -26,14 +26,16 @@
"connect-history-api-fallback": "^1.6.0",
"crypto-js": "^4.2.0",
"express": "^4.17.2",
"express-basic-auth": "^1.2.1",
"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",
"simple-icons": "^10.4.0",
"rsup-progress": "^3.2.0",
"simple-icons": "^7.19.0",
"v-jsoneditor": "^1.4.5",
"v-tooltip": "^2.1.3",
"vue": "^2.7.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 B

View File

@ -50,6 +50,14 @@
<p class="time-note" id="note">This may take a minute or two</p>
<div class="why-am-i-seeing-this">
<h3>Why are you seeing this screen?</h3>
<p>
The app's built files aren't yet present in the /dist directory,
so this page is displayed while we compile the source.
</p>
</div>
<style lang="css">
/* Page Layout Styles */
body,
@ -60,7 +68,7 @@
}
body {
background: #141b33;
background: #0d1220;
display: flex;
flex-direction: column;
justify-content: center;
@ -194,15 +202,34 @@
}
.hide { display: none; }
.why-am-i-seeing-this {
color: #808080a6;
font-family: Tahoma, 'Trebuchet MS', sans-serif;
max-width: 25rem;
border: 2px solid #808080a6;
border-radius: 6px;
padding: 0.5rem;
position: absolute;
bottom: 1rem;
background: #8080800d;
font-size: 0.9rem;
}
.why-am-i-seeing-this h3 {
margin: 0 0 0.5rem 0;
}
.why-am-i-seeing-this p {
margin: 0;
}
</style>
<script>
const refreshRate = 8000;
// Refresh at interval
// Refresh the page every 10 seconds
const refreshRate = 10000;
setTimeout(() => { location.reload(); }, refreshRate);
// Get current stage
let initStage = parseInt(sessionStorage.getItem('initStage') || 0);
// Get current stage
let initStage = parseInt(sessionStorage.getItem('initStage') || 0);
// Check if stage in session storage is old, and if so, reset it
const now = Math.round(Date.now()/1000);
@ -262,4 +289,4 @@
</script>
</body>
</html>
</html>

View File

@ -6,14 +6,20 @@
* */
/* Import built-in Node server modules */
const fs = require('fs');
const os = require('os');
const dns = require('dns');
const http = require('http');
const path = require('path');
const util = require('util');
const dns = require('dns');
const os = require('os');
const crypto = require('crypto');
/* Import NPM dependencies */
const yaml = require('js-yaml');
/* Import Express + middleware functions */
const express = require('express');
const basicAuth = require('express-basic-auth');
const history = require('connect-history-api-fallback');
/* Kick of some basic checks */
@ -61,7 +67,7 @@ const printWelcomeMessage = () => {
console.log(printMessage(ip, port, isDocker)); // eslint-disable-line no-console
});
} catch (e) {
// Fetching info for welcome message failed, print simple msg instead
// No clue what could of gone wrong here, but print fallback message if above failed
console.log(`Dashy server has started (${port})`); // eslint-disable-line no-console
}
};
@ -71,6 +77,64 @@ const printWarning = (msg, error) => {
console.warn(`\x1b[103m\x1b[34m${msg}\x1b[0m\n`, error || ''); // eslint-disable-line no-console
};
/* Load appConfig.auth.users from config (if present) for authorization purposes */
function loadUserConfig() {
try {
const filePath = path.join(__dirname, process.env.USER_DATA_DIR || 'user-data', 'conf.yml');
const fileContents = fs.readFileSync(filePath, 'utf8');
const data = yaml.load(fileContents);
return data?.appConfig?.auth?.users || null;
} catch (e) {
return [];
}
}
/* If HTTP auth is enabled, and no username/password are pre-set, then check passed credentials */
function customAuthorizer(username, password) {
const sha256 = (input) => crypto.createHash('sha256').update(input).digest('hex').toUpperCase();
const generateUserToken = (user) => {
if (!user.user || (!user.hash && !user.password)) return '';
const strAndUpper = (input) => input.toString().toUpperCase();
const passwordHash = user.hash || sha256(process.env[user.password]);
const sha = sha256(strAndUpper(user.user) + strAndUpper(passwordHash));
return strAndUpper(sha);
};
if (password.startsWith('Bearer ')) {
const token = password.slice('Bearer '.length);
const users = loadUserConfig();
return users.some(user => generateUserToken(user) === token);
} else {
const users = loadUserConfig();
const userHash = sha256(password);
return users.some(user => (
user.user.toLowerCase() === username.toLowerCase() && user.hash.toUpperCase() === userHash
));
}
}
/* If a username and password are set, setup auth for config access, otherwise skip */
function getBasicAuthMiddleware() {
const configUsers = process.env.ENABLE_HTTP_AUTH ? loadUserConfig() : null;
const { BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD } = process.env;
if (BASIC_AUTH_USERNAME && BASIC_AUTH_PASSWORD) {
return basicAuth({
users: { [BASIC_AUTH_USERNAME]: BASIC_AUTH_PASSWORD },
challenge: true,
unauthorizedResponse: () => 'Unauthorized - Incorrect username or password',
});
} else if ((configUsers && configUsers.length > 0)) {
return basicAuth({
authorizer: customAuthorizer,
challenge: true,
unauthorizedResponse: () => 'Unauthorized - Incorrect token',
});
} else {
return (req, res, next) => next();
}
}
const protectConfig = getBasicAuthMiddleware();
/* A middleware function for Connect, that filters requests based on method type */
const method = (m, mw) => (req, res, next) => (req.method === m ? mw(req, res, next) : next());
@ -134,6 +198,11 @@ const app = express()
res.end(JSON.stringify({ success: false, message: e }));
}
})
// Middleware to serve any .yml files in USER_DATA_DIR with optional protection
.get('/*.yml', protectConfig, (req, res) => {
const ymlFile = req.path.split('/').pop();
res.sendFile(path.join(__dirname, process.env.USER_DATA_DIR || 'user-data', ymlFile));
})
// Serves up static files
.use(express.static(path.join(__dirname, process.env.USER_DATA_DIR || 'user-data')))
.use(express.static(path.join(__dirname, 'dist')))

View File

@ -11,7 +11,7 @@ const schema = require('../src/utils/ConfigSchema.json');
/* Tell AJV to use strict mode, and report all errors */
const validatorOptions = {
strict: true,
strict: false,
allowUnionTypes: true,
allErrors: true,
};

View File

@ -1,15 +1,15 @@
module.exports = (config, req) => {
try {
if ( config.appConfig.auth.enableHeaderAuth ) {
const userHeader = config.appConfig.auth.headerAuth.userHeader;
const proxyWhitelist = config.appConfig.auth.headerAuth.proxyWhitelist;
if ( proxyWhitelist.includes(req.socket.remoteAddress) ) {
return { "success": true, "user": req.headers[userHeader.toLowerCase()] };
if (config.appConfig.auth.enableHeaderAuth) {
const { userHeader } = config.appConfig.auth.headerAuth;
const { proxyWhitelist } = config.appConfig.auth.headerAuth;
if (proxyWhitelist.includes(req.socket.remoteAddress)) {
return { success: true, user: req.headers[userHeader.toLowerCase()] };
}
}
return {};
} catch (e) {
console.warn("Error get-user: ", e);
return { 'success': false };
console.warn('Error get-user: ', e);
return { success: false };
}
};
};

View File

@ -14,18 +14,23 @@ module.exports = async (newConfig, render) => {
return configObj.filename.replaceAll('/', '').replaceAll('..', '');
};
// Path to config file (with navigational characters stripped)
const usersFileName = makeSafeFileName(newConfig);
// Path to user data directory
const userDataDirectory = process.env.USER_DATA_DIR || './user-data/';
// Define constants for the config file
const settings = {
defaultLocation: process.env.USER_DATA_DIR || './user-data/',
defaultLocation: userDataDirectory,
backupLocation: process.env.BACKUP_DIR || path.join(userDataDirectory, 'config-backups'),
defaultFile: 'conf.yml',
filename: 'conf',
backupDenominator: '.backup.yml',
};
// Make the full file name and path to save the backup config file
const backupFilePath = `${path.normalize(process.env.BACKUP_DIR || settings.defaultLocation)
const backupFilePath = `${path.normalize(settings.backupLocation)
}/${usersFileName || settings.filename}-`
+ `${Math.round(new Date() / 1000)}${settings.backupDenominator}`;
@ -45,15 +50,20 @@ module.exports = async (newConfig, render) => {
message: !success ? errorMsg : getSuccessMessage(),
});
// Makes a backup of the existing config file
// Create a backup of current config, and if backup dir doesn't yet exist, create it
await fsPromises
.copyFile(defaultFilePath, backupFilePath)
.catch((error) => render(getRenderMessage(false, `Unable to backup ${settings.defaultFile}: ${error}`)));
.mkdir(settings.backupLocation, { recursive: true })
.then(() => fsPromises.copyFile(defaultFilePath, backupFilePath))
.catch((error) => render(
getRenderMessage(false, `Unable to backup ${settings.defaultFile}: ${error}`),
));
// Writes the new content to the conf.yml file
await fsPromises
.writeFile(defaultFilePath, newConfig.config.toString(), writeFileOptions)
.catch((error) => render(getRenderMessage(false, `Unable to write to ${settings.defaultFile}: ${error}`)));
.catch((error) => render(
getRenderMessage(false, `Unable to write to ${settings.defaultFile}: ${error}`),
));
// If successful, then render hasn't yet been called- call it
await render(getRenderMessage(true));

View File

@ -4,6 +4,7 @@
<LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" />
<Header :pageInfo="pageInfo" />
<router-view v-if="!isFetching" />
<CriticalError v-if="hasCriticalError" />
<Footer :text="footerText" v-if="visibleComponents.footer && !isFetching" />
</div>
</template>
@ -12,6 +13,7 @@
import Header from '@/components/PageStrcture/Header.vue';
import Footer from '@/components/PageStrcture/Footer.vue';
import EditModeTopBanner from '@/components/InteractiveEditor/EditModeTopBanner.vue';
import CriticalError from '@/components/PageStrcture/CriticalError.vue';
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
import { welcomeMsg } from '@/utils/CoolConsole';
import ErrorHandler from '@/utils/ErrorHandler';
@ -29,6 +31,7 @@ export default {
Footer,
LoadingScreen,
EditModeTopBanner,
CriticalError,
},
data() {
return {
@ -72,6 +75,9 @@ export default {
isEditMode() {
return this.$store.state.editMode;
},
hasCriticalError() {
return this.$store.state.criticalError;
},
subPageClassName() {
const currentSubPage = this.$store.state.currentConfigInfo;
return (currentSubPage && currentSubPage.pageId) ? currentSubPage.pageId : '';

View File

@ -312,6 +312,14 @@
"view-title": "View Config"
}
},
"critical-error": {
"title": "Configuration Load Error",
"subtitle": "Dashy has failed to load correctly due to a configuration error.",
"sub-ensure-that": "Ensure that",
"sub-error-details": "Error Details",
"sub-next-steps": "Next Steps",
"ignore-button": "Ignore Critical Errors"
},
"widgets": {
"general": {
"loading": "Loading...",

View File

@ -10,8 +10,12 @@
"clear-search-tooltip": "Limpar busca",
"enter-to-search-web": "Tecle enter para buscar na rede"
},
"splash-screen": {
"loading": "Carregando"
},
"login": {
"title": "Dashy",
"guest-label": "Entrar como convidado",
"username-label": "Nome do usuário",
"password-label": "Senha",
"login-button": "Conectar",
@ -31,8 +35,20 @@
"already-logged-in-text": "Você está logado como",
"proceed-to-dashboard": "Ir para o Painel",
"log-out-button": "Sair",
"proceed-guest-button": "Seguir como Convidado"
"proceed-guest-button": "Seguir como Convidado",
"guest-intro-1": "Essa instância possui o acesso como convidado ativado",
"guest-intro-2": "Convidados podem apenas visualizar os painéis, logo não podem escrever quaisquer mudanças no disco.",
"error": "Erro",
"error-no-user-configured": "Autenticação não está habilitada, ou nenhum usuário foi configurado.",
"error-go-home-button": "Ir para a página inicial",
"logged-in-guest": "Logado como convidado. Redirecionando...",
"error-guest-access": "O acesso como convidado não foi permitido"
},
"app-info": {
"title": "Informação do App"
},
"config": {
"main-tab": "Menu Principal",
"view-config-tab": "Ver configuração",
@ -299,6 +315,16 @@
"remaining": "Restante",
"up": "Up",
"down": "Down"
},
"gluetun-status": {
"vpn-ip": "IP da VPN",
"country": "País",
"region": "Região",
"city": "Cidade",
"post-code": "Código postal",
"location": "Localização",
"timezone": "Fuso horário",
"organization": "Organização"
}
}
}
}

View File

@ -29,7 +29,7 @@
"error-missing-password": "密码空缺",
"error-incorrect-username": "用户不存在",
"error-incorrect-password": "密码不正确",
"success-message": "登陆成功。。。",
"success-message": "登陆成功。",
"logout-message": "注销",
"already-logged-in-title": "已经成功登陆",
"already-logged-in-text": "你的登陆身份",
@ -37,11 +37,11 @@
"log-out-button": "注销",
"proceed-guest-button": "以游客身份前往",
"guest-intro-1": "该实例已启用访客访问.",
"guest-intro-2": "访客只有访问权限,无法保存变更",
"guest-intro-2": "访客只有访问权限无法保存变更",
"error": "错误",
"error-no-user-configured": "没有启用验证,或者未配置用户",
"error-no-user-configured": "没有启用验证或者未配置用户",
"error-go-home-button": "Go Home",
"logged-in-guest": "以访客身份登陆,正在跳转...",
"logged-in-guest": "以访客身份登陆正在跳转...",
"error-guest-access": "不允许访客访问"
},
"app-info": {
@ -78,22 +78,22 @@
"config": {
"main-tab": "主菜单",
"view-config-tab": "视图设置",
"edit-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": "编辑云端同步",
"edit-config-button": "修改设置",
"edit-css-button": "自定义CSS",
"cloud-sync-button": "云端同步",
"edit-cloud-sync-button": "修改云端同步",
"rebuild-app-button": "重建应用",
"change-language-button": "更改语言",
"reset-settings-button": "恢复本地设置",
"reset-settings-button": "恢复默认设置",
"disabled-note": "您的管理员已禁用某些配置功能",
"small-screen-note": "您正在使用非常小的屏幕,某些菜单屏幕可能不够优化",
"app-info-button": "应用详情",
"backup-note": "建议在进行更改之前备份你的配置。",
"reset-config-msg-l1": "这将从本地存储中删除所有用户设置,但不会影响conf.yml文件。",
"reset-config-msg-l1": "这将从本地存储中删除所有用户设置,但不会影响 conf.yml 文件。",
"reset-config-msg-l2": "如果想在以后使用它们,应该首先备份你所做的任何更改。",
"reset-config-msg-l3": "确定执行吗?",
"data-cleared-msg": "成功清空数据",
@ -103,9 +103,9 @@
"reset-config-label": "重置设置",
"css-save-btn": "保存更改",
"css-note-label": "注意",
"css-note-l1": "你需要刷新页面才能使更改生效。",
"css-note-l2": "样式覆盖仅存储在本地,因此建议复制你的 CSS。",
"css-note-l3": "要删除所有自定义样式,请删除内容并点击保存更改",
"css-note-l1": "需刷新页面使其生效。",
"css-note-l2": "自定义样式仅在本地有效,设置储存在当前浏览器,建议做好备份。",
"css-note-l3": "如需删除自定义样式,清空上面内容并点击保存。",
"custom-css": {
"title": "自定义 CSS",
"base-theme": "基础主题"
@ -132,8 +132,8 @@
"sign-out-tooltip": "注销",
"sign-in-tooltip": "登陆",
"sign-in-welcome": "你好 {username}",
"hide": "Hide",
"open": "Open"
"hide": "隐藏",
"open": "打开"
},
"updates": {
"app-version-note": "Dashy 版本",
@ -195,9 +195,10 @@
"reload-button": "刷新页面"
},
"cloud-sync": {
"title": "云备份 &恢复",
"title": "云备份&恢复",
"intro-l1": "云备份和云恢复是一项试验性功能,你将配置上传到网络,然后在其他设备或 Dashy 实例上恢复。",
"intro-l2": "所有数据都使用AES端对端加密使用你的密码作为密钥。",
"intro-docs": "文档",
"intro-l3": "有关更多信息,请参阅",
"backup-title-setup": "创建备份",
"backup-title-update": "更新备份",
@ -268,11 +269,11 @@
"edit-pages-tooltip": "添加或删除其他的视图",
"config-save-methods-subheading": "配置保存选项",
"save-locally-btn": "暂存本地",
"save-locally-tooltip": "将设置保存在本地浏览器上. 这不会影响配置文件,但更改只会保留在当前设备上.",
"save-locally-tooltip": "将设置保存在浏览器上。这不会影响配置文件,仅作用于当前的浏览器。",
"save-disk-btn": "保存",
"save-disk-tooltip": "将设置保存到服务端的conf.yml文件.它会备份之前的配置文件.",
"save-disk-tooltip": "将设置保存到服务端的conf.yml文件它会备份之前的配置文件.",
"export-config-btn": "导出配置",
"export-config-tooltip": "查看并导出新的配置 到 文件 或 剪贴板",
"export-config-tooltip": "查看并导出新的配置到 文件 或 剪贴板",
"cloud-backup-btn": "备份到云端",
"cloud-backup-tooltip": "以加密的方式保存到云端",
"edit-raw-config-btn": "编辑原始配置",
@ -284,10 +285,10 @@
"edit-mode-description": "你可以对配置进行修改并预览,在保存之前,你的任何更改都不会被保留。",
"save-stage-btn": "保存",
"cancel-stage-btn": "取消",
"save-locally-warning": "如果你继续,更改将仅保存在你的浏览器中。 你应该导出配置的副本以在其他机器上使用。 你想继续吗"
"save-locally-warning": "配置将保存到你当前的浏览器上。你也可以导出配置到其他设备上使用。是否继续"
},
"edit-item": {
"missing-title-err": "项目标题是必需的"
"missing-title-err": "标题是必需的"
},
"edit-section": {
"edit-section-title": "编辑 Section",
@ -314,7 +315,9 @@
"widgets": {
"general": {
"loading": "加载中...",
"show-more": "展开详情",
"show-more": "显示更多",
"cpu-details": "CPU 详情",
"mem-details": "内存 详情",
"show-less": "显示更少信息",
"open-link": "继续读取"
},

View File

@ -116,7 +116,8 @@ export default {
},
mounted() {
const jsonData = { ...this.config };
jsonData.sections = jsonData.sections.map(({ filteredItems, ...section }) => section);
jsonData.sections = (jsonData.sections || []).map(({ filteredItems, ...section }) => section);
if (!jsonData.pageInfo) jsonData.pageInfo = { title: 'Dashy' };
this.jsonData = jsonData;
if (!this.allowWriteToDisk) this.saveMode = 'local';
},

View File

@ -64,7 +64,6 @@ export default {
return this.$store.state.editMode;
},
sectionKey() {
if (this.isEditMode) return undefined;
return `collapsible-${this.uniqueKey}`;
},
collapseClass() {
@ -104,12 +103,23 @@ export default {
watch: {
checkboxState(newState) {
this.isExpanded = newState;
this.updateLocalStorage(); // Save every change immediately
},
uniqueKey() {
this.checkboxState = this.isExpanded;
uniqueKey(newVal, oldVal) {
if (newVal !== oldVal) {
this.refreshCollapseState(); // Refresh state when key changes
}
},
},
methods: {
refreshCollapseState() {
this.checkboxState = this.isExpanded;
},
updateLocalStorage() {
const collapseState = this.locallyStoredCollapseStates();
collapseState[this.uniqueKey] = this.checkboxState;
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify(collapseState));
},
/* Either expand or collapse section, based on it's current state */
toggle() {
this.checkboxState = !this.checkboxState;

View File

@ -21,7 +21,6 @@
</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';
@ -29,6 +28,8 @@ 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: {
@ -186,8 +187,8 @@ export default {
},
/* Returns the SVG path content */
getSimpleIcon(img) {
const imageName = img.replace('si-', '');
const icon = simpleIcons.Get(imageName);
const imageName = img.charAt(3).toUpperCase() + img.slice(4);
const icon = simpleicons[`si${imageName}`];
if (!icon) {
this.imageNotFound(`No icon was found for '${imageName}' in Simple Icons`);
return null;

View File

@ -0,0 +1,153 @@
<template>
<div class="critical-error-wrap" v-if="shouldShow">
<button class="close" title="Close Warning" @click="close">🗙</button>
<h3>{{ $t('critical-error.title') }}</h3>
<p>{{ $t('critical-error.subtitle') }}</p>
<h4>{{ $t('critical-error.sub-ensure-that') }}</h4>
<ul>
<li>The configuration file can be found at the specified location</li>
<li>There are no CORS rules preventing client-side access</li>
<li>The YAML is valid, parsable and matches the schema</li>
</ul>
<h4>{{ $t('critical-error.sub-error-details') }}</h4>
<pre>{{ this.$store.state.criticalError }}</pre>
<h4>{{ $t('critical-error.sub-next-steps') }}</h4>
<ul>
<li>Check the browser console for more details
(<a href="https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md#how-to-open-browser-console">see how</a>)
</li>
<li>View the
<a href="https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md">Troubleshooting Guide</a>
and <a href="https://dashy.to/docs/">Docs</a>
</li>
<li>
If you've verified the config is present, accessible and valid, and cannot find the solution
in the troubleshooting, docs or GitHub issues,
then <a href="https://github.com/Lissy93/dashy/issues/new/choose">open a ticket on GitHub</a>
</li>
<li>Click 'Ignore Critical Errors' below to not show this warning again</li>
</ul>
<button class="user-doesnt-care" @click="ignoreWarning">
{{ $t('critical-error.ignore-button') }}
</button>
</div>
</template>
<script>
import { localStorageKeys } from '@/utils/defaults';
import Keys from '@/utils/StoreMutations';
export default {
name: 'CriticalError',
computed: {
/* Determines if we should show this component.
* If error present AND user hasn't disabled */
shouldShow() {
return this.$store.state.criticalError
&& !localStorage[localStorageKeys.DISABLE_CRITICAL_WARNING];
},
},
methods: {
/* Ignore all future errors, by putting a key in local storage */
ignoreWarning() {
localStorage.setItem(localStorageKeys.DISABLE_CRITICAL_WARNING, true);
this.close();
},
/* Close this dialog, by removing this error from the local store */
close() {
this.$store.commit(Keys.CRITICAL_ERROR_MSG, null);
},
},
};
</script>
<style scoped lang="scss">
@import '@/styles/media-queries.scss';
.critical-error-wrap {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 3;
max-width: 50rem;
background: var(--background-darker);
padding: 1rem;
border-radius: var(--curve-factor);
color: var(--danger);
border: 2px solid var(--danger);
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5rem;
transition: all 0.2s ease-in-out;
@include tablet-down {
top: 50%;
width: 85vw;
}
p, ul, h4, a {
margin: 0;
color: var(--white);
}
pre {
color: var(--warning);
font-size: 0.8rem;
overflow: auto;
background: var(--transparent-white-10);
padding: 0.5rem;
border-radius: var(--curve-factor);
}
h4 {
margin: 0.5rem 0 0 0;
font-size: 1.2rem;
}
h3 {
font-size: 2.2rem;
text-align: center;
background: var(--danger);
color: var(--white);
margin: -1rem -1rem 1rem -1rem;
padding: 0.5rem;
}
ul {
padding-left: 1rem;
}
.user-doesnt-care {
background: var(--background-darker);
color: var(--white);
border-radius: var(--curve-factor);
border: none;
text-decoration: underline;
padding: 0.25rem 0.5rem;
cursor: pointer;
width: fit-content;
margin: 0 auto;
transition: all 0.2s ease-in-out;
&:hover {
background: var(--danger);
color: var(--background-darker);
text-decoration: none;
}
}
.close {
position: absolute;
top: 1rem;
right: 1rem;
width: 1.5rem;
height: 1.5rem;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
background: var(--background);
color: var(--primary);
border: none;
border-radius: var(--curve-factor);
transition: all 0.2s ease-in-out;
&:hover {
background: var(--primary);
color: var(--background);
}
}
}
</style>

View File

@ -24,6 +24,13 @@
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>
@ -32,6 +39,7 @@
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';
@ -56,6 +64,13 @@ 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

@ -37,8 +37,10 @@ export default {
input: '',
};
},
props: {
iconSize: String,
computed: {
iconSize() {
return this.$store.getters.iconSize;
},
},
components: {
IconSmall,

View File

@ -5,19 +5,19 @@
<IconDeafault
@click="updateDisplayLayout('auto')"
v-tooltip="tooltip($t('settings.layout-auto'))"
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`"
:class="`layout-icon ${layout === 'auto' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconHorizontal
@click="updateDisplayLayout('horizontal')"
v-tooltip="tooltip($t('settings.layout-horizontal'))"
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`"
:class="`layout-icon ${layout === 'horizontal' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconVertical
@click="updateDisplayLayout('vertical')"
v-tooltip="tooltip($t('settings.layout-vertical'))"
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`"
:class="`layout-icon ${layout === 'vertical' ? 'selected' : ''}`"
tabindex="-2"
/>
</div>
@ -40,6 +40,11 @@ export default {
IconHorizontal,
IconVertical,
},
computed: {
layout() {
return this.$store.getters.layout;
},
},
methods: {
updateDisplayLayout(layout) {
this.$store.commit(StoreKeys.SET_ITEM_LAYOUT, layout);

View File

@ -7,7 +7,7 @@
<div class="options-outer">
<div :class="`options-container ${!settingsVisible ? 'hide' : ''}`">
<ThemeSelector />
<LayoutSelector :displayLayout="displayLayout" />
<LayoutSelector :displayLayout="$store.getters.layout" />
<ItemSizeSelector :iconSize="iconSize" />
<ConfigLauncher />
<AuthButtons v-if="userState !== 0" :userType="userState" />

View File

@ -1,9 +1,15 @@
<template>
<div class="readme-stats">
<img class="stats-card" v-if="!hideProfileCard" :src="profileCard" alt="Profile Card" />
<img class="stats-card" v-if="!hideLanguagesCard" :src="topLanguagesCard" alt="Languages" />
<a v-if="!hideProfileCard" :href="profileCardLink" target="_blank">
<img class="stats-card" :src="profileCard" alt="Profile Card" />
</a>
<a v-if="!hideLanguagesCard" :href="profileCardLink" target="_blank">
<img class="stats-card" :src="topLanguagesCard" alt="Languages" />
</a>
<template v-if="repos">
<img class="stats-card" v-for="(repo, i) in repoCards" :key="i" :src="repo" :alt="repo" />
<a v-for="(repo, i) in repoCards" :key="i" :href="repo.cardHref" target="_blank">
<img class="stats-card" :src="repo.cardSrc" :alt="repo" />
</a>
</template>
</div>
</template>
@ -61,6 +67,9 @@ export default {
profileCard() {
return `${widgetApiEndpoints.readMeStats}?username=${this.username}${this.cardConfig}`;
},
profileCardLink() {
return `https://github.com/${this.username}`;
},
topLanguagesCard() {
return `${widgetApiEndpoints.readMeStats}/top-langs/?username=${this.username}`
+ `${this.cardConfig}&langs_count=12`;
@ -70,8 +79,11 @@ export default {
this.repos.forEach((repo) => {
const username = repo.split('/')[0];
const repoName = repo.split('/')[1];
cards.push(`${widgetApiEndpoints.readMeStats}/pin/?username=${username}&repo=${repoName}`
+ `${this.cardConfig}&show_owner=true`);
cards.push({
cardSrc: `${widgetApiEndpoints.readMeStats}/pin/?username=${username}`
+ `&repo=${repoName}${this.cardConfig}&show_owner=true`,
cardHref: `https://github.com/${username}/${repoName}`,
});
});
return cards;
},

View File

@ -84,9 +84,12 @@ export default {
/* Assign data variables to the returned data */
processData(holidays) {
const results = [];
const makeDate = (date) => timestampToDate(
new Date(`${date.year}-${date.month}-${date.day}`).getTime(),
);
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 formatType = (ht) => capitalize(ht.replaceAll('_', ' '));
holidays.forEach((holiday) => {
results.push({

View File

@ -22,7 +22,9 @@ 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';
// Initialize global Vue components
Vue.use(VueI18n);
@ -61,16 +63,25 @@ const mount = () => new Vue({
}).$mount('#app');
store.dispatch(Keys.INITIALIZE_CONFIG).then(() => {
// Keycloak is enabled, redirect to KC login page
if (isKeycloakEnabled()) {
if (isOidcEnabled()) {
initOidcAuth()
.then(() => mount())
.catch((e) => {
ErrorHandler('Failed to authenticate with OIDC', e);
});
} else if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
initKeycloakAuth()
.then(() => mount())
.catch(() => window.location.reload());
} else if (isHeaderAuthEnabled()) {
.catch((e) => {
ErrorHandler('Failed to authenticate with Keycloak', e);
});
} else if (isHeaderAuthEnabled()) { // If header auth is enabled, initialize auth
initHeaderAuth()
.then(() => mount())
.catch(() => window.location.reload());
} else { // If Keycloak not enabled, then proceed straight to the app
.catch((e) => {
ErrorHandler('Failed to authenticate with server', e);
});
} else { // If no third-party auth, just mount the app as normal
mount();
}
});

View File

@ -28,19 +28,24 @@ const HomeMixin = {
return this.$store.state.modalOpen;
},
pageId() {
return (this.subPageInfo && this.subPageInfo.pageId) ? this.subPageInfo.pageId : 'home';
return this.$store.state.currentConfigInfo?.confId || 'home';
},
},
data: () => ({
searchValue: '',
}),
async mounted() {
// await this.getConfigForRoute();
},
watch: {
async $route() {
this.loadUpConfig();
},
pageInfo: {
handler(newPageInfo) {
if (newPageInfo && newPageInfo.title) {
document.title = newPageInfo.title;
}
},
immediate: true,
},
},
async created() {
this.loadUpConfig();
@ -79,6 +84,14 @@ const HomeMixin = {
searching(searchValue) {
this.searchValue = searchValue || '';
},
/* Returns a unique ID based on the page and section name */
makeSectionId(section) {
const normalize = (str) => (
str ? str.trim().toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
: `unnamed-${(`000${Math.floor(Math.random() * 1000)}`).slice(-3)}`
);
return `${this.pageId || 'unknown-page'}-${normalize(section.name)}`;
},
/* Returns true if there is one or more sections in the config */
checkTheresData(sections) {
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];

View File

@ -2,6 +2,7 @@
* 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';
@ -105,51 +106,27 @@ const WidgetMixin = {
const method = protocol || 'GET';
const url = this.useProxy ? this.proxyReqEndpoint : endpoint;
const data = JSON.stringify(body || {});
const CustomHeaders = options || {};
const headers = new Headers(this.useProxy
? ({ ...CustomHeaders, 'Target-URL': endpoint })
: CustomHeaders);
// If the request is a GET, delete the body
const bodyContent = method.toUpperCase() === 'GET' ? undefined : data;
const CustomHeaders = options || null;
const headers = this.useProxy
? { 'Target-URL': endpoint, CustomHeaders: JSON.stringify(CustomHeaders) } : CustomHeaders;
const timeout = this.options.timeout || this.defaultTimeout;
// Setup Fetch request configuration
const requestConfig = {
method,
headers,
body: bodyContent,
signal: undefined, // This will be set below
method, url, headers, data, timeout,
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
requestConfig.signal = controller.signal;
// Make request using Fetch API
// Make request
return new Promise((resolve, reject) => {
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);
axios.request(requestConfig)
.then((response) => {
if (response.data.success === false) {
this.error('Proxy returned error from target server', response.data.message);
}
if (responseData.success === false) {
this.error('Proxy didn\'t return success from target server', responseData.message);
}
resolve(responseData);
resolve(response.data);
})
.catch(error => {
if (error.name === 'AbortError') {
this.error('Request timed out', error);
} else {
this.error('Unable to fetch data', error);
}
reject(error);
.catch((dataFetchError) => {
this.error('Unable to fetch data', dataFetchError);
reject(dataFetchError);
})
.finally(() => {
clearTimeout(timeoutId);
this.finishLoading();
});
});

View File

@ -8,7 +8,7 @@ import { makePageName, formatConfigPath, componentVisibility } from '@/utils/Con
import { applyItemId } from '@/utils/SectionHelpers';
import filterUserSections from '@/utils/CheckSectionVisibility';
import ErrorHandler, { InfoHandler, InfoKeys } from '@/utils/ErrorHandler';
import { isUserAdmin } from '@/utils/Auth';
import { isUserAdmin, makeBasicAuthHeaders, isLoggedInAsGuest } from '@/utils/Auth';
import { localStorageKeys, theme as defaultTheme } from './utils/defaults';
Vue.use(Vuex);
@ -41,8 +41,15 @@ const {
INSERT_ITEM,
UPDATE_CUSTOM_CSS,
CONF_MENU_INDEX,
CRITICAL_ERROR_MSG,
} = Keys;
const emptyConfig = {
appConfig: {},
pageInfo: { title: 'Dashy' },
sections: [],
};
const store = new Vuex.Store({
state: {
config: {}, // The current config being used, and rendered to the UI
@ -51,6 +58,7 @@ const store = new Vuex.Store({
modalOpen: false, // KB shortcut functionality will be disabled when modal is open
currentConfigInfo: {}, // For multi-page support, will store info about config file
isUsingLocalConfig: false, // If true, will use local config instead of fetched
criticalError: null, // Will store a message, if a critical error occurs
navigateConfToTab: undefined, // Used to switch active tab in config modal
},
getters: {
@ -106,7 +114,8 @@ const store = new Vuex.Store({
}
// Disable everything
if (appConfig.disableConfiguration
|| (appConfig.disableConfigurationForNonAdmin && !isUserAdmin())) {
|| (appConfig.disableConfigurationForNonAdmin && !isUserAdmin())
|| isLoggedInAsGuest()) {
perms.allowWriteToDisk = false;
perms.allowSaveLocally = false;
perms.allowViewConfig = false;
@ -137,10 +146,18 @@ const store = new Vuex.Store({
return foundSection;
},
layout(state) {
return state.config.appConfig.layout || 'auto';
const pageId = state.currentConfigInfo.confId;
const layoutStoreKey = pageId
? `${localStorageKeys.LAYOUT_ORIENTATION}-${pageId}` : localStorageKeys.LAYOUT_ORIENTATION;
const appConfigLayout = state.config.appConfig.layout;
return localStorage.getItem(layoutStoreKey) || appConfigLayout || 'auto';
},
iconSize(state) {
return state.config.appConfig.iconSize || 'medium';
const pageId = state.currentConfigInfo.confId;
const sizeStoreKey = pageId
? `${localStorageKeys.ICON_SIZE}-${pageId}` : localStorageKeys.ICON_SIZE;
const appConfigSize = state.config.appConfig.iconSize;
return localStorage.getItem(sizeStoreKey) || appConfigSize || 'medium';
},
},
mutations: {
@ -174,6 +191,10 @@ const store = new Vuex.Store({
state.editMode = editMode;
}
},
[CRITICAL_ERROR_MSG](state, message) {
if (message) ErrorHandler(message);
state.criticalError = message;
},
[UPDATE_ITEM](state, payload) {
const { itemId, newItem } = payload;
const newConfig = { ...state.config };
@ -298,11 +319,23 @@ const store = new Vuex.Store({
InfoHandler('Color palette updated', InfoKeys.VISUAL);
},
[SET_ITEM_LAYOUT](state, layout) {
state.config.appConfig.layout = layout;
const newConfig = { ...state.config };
newConfig.appConfig.layout = layout;
state.config = newConfig;
const pageId = state.currentConfigInfo.confId;
const layoutStoreKey = pageId
? `${localStorageKeys.LAYOUT_ORIENTATION}-${pageId}` : localStorageKeys.LAYOUT_ORIENTATION;
localStorage.setItem(layoutStoreKey, layout);
InfoHandler('Layout updated', InfoKeys.VISUAL);
},
[SET_ITEM_SIZE](state, iconSize) {
state.config.appConfig.iconSize = iconSize;
const newConfig = { ...state.config };
newConfig.appConfig.iconSize = iconSize;
state.config = newConfig;
const pageId = state.currentConfigInfo.confId;
const sizeStoreKey = pageId
? `${localStorageKeys.ICON_SIZE}-${pageId}` : localStorageKeys.ICON_SIZE;
localStorage.setItem(sizeStoreKey, iconSize);
InfoHandler('Item size updated', InfoKeys.VISUAL);
},
[UPDATE_CUSTOM_CSS](state, customCss) {
@ -320,16 +353,39 @@ const store = new Vuex.Store({
actions: {
/* Fetches the root config file, only ever called by INITIALIZE_CONFIG */
async [INITIALIZE_ROOT_CONFIG]({ commit }) {
// Load and parse config from root config file
const configFilePath = process.env.VUE_APP_CONFIG_PATH || '/conf.yml';
const data = await yaml.load((await axios.get(configFilePath)).data);
// Replace missing root properties with empty objects
if (!data.appConfig) data.appConfig = {};
if (!data.pageInfo) data.pageInfo = {};
if (!data.sections) data.sections = [];
// Set the state, and return data
commit(SET_ROOT_CONFIG, data);
return data;
try {
// Attempt to fetch the YAML file
const response = await axios.get(configFilePath, makeBasicAuthHeaders());
let data;
try {
data = yaml.load(response.data);
} catch (parseError) {
commit(CRITICAL_ERROR_MSG, `Failed to parse YAML: ${parseError.message}`);
return { ...emptyConfig };
}
// Replace missing root properties with empty objects
if (!data.appConfig) data.appConfig = {};
if (!data.pageInfo) data.pageInfo = {};
if (!data.sections) data.sections = [];
// Set the state, and return data
commit(SET_ROOT_CONFIG, data);
commit(CRITICAL_ERROR_MSG, null);
return data;
} catch (fetchError) {
if (fetchError.response) {
commit(
CRITICAL_ERROR_MSG,
'Failed to fetch configuration: Server responded with status '
+ `${fetchError.response?.status || 'mystery status'}`,
);
} else if (fetchError.request) {
commit(CRITICAL_ERROR_MSG, 'Failed to fetch configuration: No response from server');
} else {
commit(CRITICAL_ERROR_MSG, `Failed to fetch configuration: ${fetchError.message}`);
}
return { ...emptyConfig };
}
},
/**
* Fetches config and updates state
@ -339,6 +395,7 @@ const store = new Vuex.Store({
*/
async [INITIALIZE_CONFIG]({ commit, state }, subConfigId) {
const rootConfig = state.rootConfig || await this.dispatch(Keys.INITIALIZE_ROOT_CONFIG);
commit(SET_IS_USING_LOCAL_CONFIG, false);
if (!subConfigId) { // Use root config as config
commit(SET_CONFIG, rootConfig);
@ -351,7 +408,7 @@ const store = new Vuex.Store({
const json = JSON.parse(localSectionsRaw);
if (json.length >= 1) localSections = json;
} catch (e) {
ErrorHandler('Malformed section data in local storage');
commit(CRITICAL_ERROR_MSG, 'Malformed section data in local storage');
}
}
if (localSections.length > 0) {
@ -366,11 +423,10 @@ const store = new Vuex.Store({
)?.path);
if (!subConfigPath) {
ErrorHandler(`Unable to find config for '${subConfigId}'`);
return null;
commit(CRITICAL_ERROR_MSG, `Unable to find config for '${subConfigId}'`);
return { ...emptyConfig };
}
axios.get(subConfigPath).then((response) => {
axios.get(subConfigPath, makeBasicAuthHeaders()).then((response) => {
// Parse the YAML
const configContent = yaml.load(response.data) || {};
// Certain values must be inherited from root config
@ -389,17 +445,17 @@ const store = new Vuex.Store({
commit(SET_IS_USING_LOCAL_CONFIG, true);
}
} catch (e) {
ErrorHandler('Malformed section data in local storage for sub-config');
commit(CRITICAL_ERROR_MSG, 'Malformed section data in local storage for sub-config');
}
}
// Set the config
commit(SET_CONFIG, configContent);
commit(SET_CURRENT_CONFIG_INFO, { confPath: subConfigPath, confId: subConfigId });
}).catch((err) => {
ErrorHandler(`Unable to load config from '${subConfigPath}'`, err);
commit(CRITICAL_ERROR_MSG, `Unable to load config from '${subConfigPath}'`, err);
});
}
return null;
return { ...emptyConfig };
},
},
modules: {},

View File

@ -31,6 +31,7 @@
--transparent-white-70: #ffffffb3;
--transparent-white-50: #ffffff80;
--transparent-white-30: #ffffff4d;
--transparent-white-10: #ffffff0f;
/* Color variables for specific components
* all variables are optional, since they inherit initial values from above*

View File

@ -1717,6 +1717,7 @@ html[data-theme='neomorphic'] {
.config-buttons > svg,
.display-options svg,
form.minimal input,
.critical-error-wrap button.user-doesnt-care,
a.config-button, button.config-button {
border-radius: 0.35rem;
box-shadow: var(--glass-button-shadow);
@ -1724,6 +1725,7 @@ html[data-theme='neomorphic'] {
border: 1px solid rgba(255, 255, 255, 0.19);
background: rgba(255, 255, 255, 0.15);
transition: all 0.2s ease-in-out;
text-decoration: none;
&:hover, &.selected {
box-shadow: var(--glass-button-hover-shadow);
border: 1px solid rgba(255, 255, 255, 0.25) !important;
@ -1791,6 +1793,11 @@ html[data-theme='neomorphic'] {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(50px);
}
.critical-error-wrap {
backdrop-filter: blur(15px);
background: #0f0528c4;
}
}
html[data-theme='glass'] {

View File

@ -22,6 +22,11 @@ html {
}
}
#dashy {
position: relative;
min-height: 100vh;
}
/* Hide text, and show 'Loading...' while Vue is initializing tags */
[v-cloak] > * { display:none }
[v-cloak]::before { content: "loading…" }

View File

@ -3,6 +3,7 @@ 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 = () => {
@ -11,8 +12,6 @@ const getAppConfig = () => {
return config.appConfig || {};
};
// const appConfig = $store.getters.appConfig || {};
/**
* Called when the user is still using array for users, prints warning
* This was a breaking change, implemented in V 1.6.5
@ -41,37 +40,52 @@ const getUsers = () => {
* @returns {String} The hashed token
*/
const generateUserToken = (user) => {
if (!user.user || !user.hash) {
ErrorHandler('Invalid user object. Must have `user` and `hash` parameters');
if (!user.user || (!user.hash && !user.password)) {
ErrorHandler('Invalid user object. Must have `user` and either a `hash` or `password` param');
return undefined;
}
const passHash = user.hash || sha256(process.env[user.password]).toString().toUpperCase();
const strAndUpper = (input) => input.toString().toUpperCase();
const sha = sha256(strAndUpper(user.user) + strAndUpper(user.hash));
const sha = sha256(strAndUpper(user.user) + strAndUpper(passHash));
return strAndUpper(sha);
};
export const getCookieToken = () => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${cookieKeys.AUTH_TOKEN}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
};
export const makeBasicAuthHeaders = () => {
const token = getCookieToken();
const bearerAuth = (token && token.length > 5) ? `Bearer ${token}` : null;
const username = process.env.VUE_APP_BASIC_AUTH_USERNAME
|| localStorage[localStorageKeys.USERNAME]
|| 'user';
const password = process.env.VUE_APP_BASIC_AUTH_PASSWORD || bearerAuth;
const basicAuth = `Basic ${btoa(`${username}:${password}`)}`;
const headers = password
? { headers: { Authorization: basicAuth, 'WWW-Authenticate': 'true' } }
: {};
return headers;
};
/**
* Checks if the user is currently authenticated
* @returns {Boolean} Will return true if the user is logged in, else false
*/
export const isLoggedIn = () => {
const users = getUsers();
let userAuthenticated = document.cookie.split(';').some((cookie) => {
if (cookie && cookie.split('=').length > 1) {
const cookieKey = cookie.split('=')[0].trim();
const cookieValue = cookie.split('=')[1].trim();
if (cookieKey === cookieKeys.AUTH_TOKEN) {
userAuthenticated = users.some((user) => {
if (generateUserToken(user) === cookieValue) {
localStorage.setItem(localStorageKeys.USERNAME, user.user);
return true;
} else return false;
});
return userAuthenticated;
} else return false;
const cookieToken = getCookieToken();
return users.some((user) => {
if (generateUserToken(user) === cookieToken) {
localStorage.setItem(localStorageKeys.USERNAME, user.user);
return true;
} else return false;
});
return userAuthenticated;
};
/* Returns true if authentication is enabled */
@ -83,7 +97,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()) {
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled() && !isOidcEnabled()) {
return appConfig.auth.enableGuestAccess || false;
}
return false;
@ -108,7 +122,18 @@ export const checkCredentials = (username, pass, users, messages) => {
} else {
users.forEach((user) => {
if (user.user.toLowerCase() === username.toLowerCase()) { // User found
if (user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
if (user.password) {
if (!user.password.startsWith('VUE_APP_')) {
ErrorHandler('Invalid password format. Please use VUE_APP_ prefix');
response = { correct: false, msg: messages.incorrectPassword };
} else if (!process.env[user.password]) {
ErrorHandler(`Missing environmental variable for ${user.password}`);
} else if (process.env[user.password] === pass) {
response = { correct: true, msg: messages.successMsg };
} else {
response = { correct: false, msg: messages.incorrectPassword };
}
} else if (user.hash && user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
response = { correct: true, msg: messages.successMsg }; // Password is correct
} else { // User found, but password is not a match
response = { correct: false, msg: messages.incorrectPassword };
@ -163,9 +188,9 @@ export const getCurrentUser = () => {
* Checks if the user is viewing the dashboard as a guest
* Returns true if guest mode enabled, and user not logged in
* */
export const isLoggedInAsGuest = (currentUser) => {
export const isLoggedInAsGuest = () => {
const guestEnabled = isGuestAccessEnabled();
const loggedIn = isLoggedIn() && currentUser;
const loggedIn = isLoggedIn();
return guestEnabled && !loggedIn;
};
@ -205,8 +230,10 @@ 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

@ -500,14 +500,9 @@
"users": {
"title": "Users",
"type": "array",
"description": "Usernames and hashed credentials for frontend authentication",
"description": "Usernames and hashed credentials for frontend authentication. Needs to be set at build-time.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"user",
"hash"
],
"properties": {
"user": {
"title": "Username",
@ -521,6 +516,12 @@
"minLength": 64,
"maxLength": 64
},
"password": {
"title": "Password",
"type": "string",
"description": "The environmental variable pointing to a plaintext password for that user. Must start with VUE_APP_",
"pattern": "^VUE_APP_.*"
},
"type": {
"title": "Privileges",
"type": "string",
@ -531,6 +532,39 @@
"description": "User type, denoting privilege level, either admin or normal",
"default": "normal"
}
},
"additionalProperties": false,
"required": ["user"],
"oneOf": [
{ "required": ["hash"] },
{ "required": ["password"] }
]
}
},
"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"
}
}
},

View File

@ -3,7 +3,7 @@ import sha256 from 'crypto-js/sha256';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { cookieKeys, localStorageKeys, serviceEndpoints } from '@/utils/defaults';
import { InfoHandler, ErrorHandler, InfoKeys } from '@/utils/ErrorHandler';
import { logout } from '@/utils/Auth';
import { logout as authLogout } from '@/utils/Auth';
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
@ -22,7 +22,6 @@ class HeaderAuth {
this.users = auth.users;
}
/* eslint-disable class-methods-use-this */
login() {
return new Promise((resolve, reject) => {
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
@ -44,6 +43,7 @@ class HeaderAuth {
}
});
} catch (e) {
ErrorHandler('Error while trying to login using header authentication', e);
reject(e);
}
}
@ -51,8 +51,9 @@ class HeaderAuth {
});
}
// eslint-disable-next-line class-methods-use-this
logout() {
logout();
authLogout();
}
}

View File

@ -27,7 +27,7 @@ const determineIntersection = (source = [], target = []) => {
/* Returns false if the displayData of a section/item
should not be rendered for the current user/ guest */
export const isVisibleToUser = (displayData, currentUser) => {
const isGuest = isLoggedInAsGuest(currentUser); // Check if current user is a guest
const isGuest = isLoggedInAsGuest(); // Check if current user is a guest
// Checks if user explicitly has access to a certain section
const checkVisibility = () => {

90
src/utils/OidcAuth.js Normal file
View File

@ -0,0 +1,90 @@
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 } = auth.oidc;
const settings = {
userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: endpoint,
client_id: clientId,
redirect_uri: `${window.location.origin}`,
response_type: 'code',
scope: 'openid profile email roles groups',
response_mode: 'query',
filterProtocolClaims: true,
};
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,
};
statusMsg(`user: ${user.profile.preferred_username}`, JSON.stringify(info));
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
localStorage.setItem(localStorageKeys.USERNAME, user.profile.preferred_username);
}
}
async logout() {
localStorage.removeItem(localStorageKeys.USERNAME);
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
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

@ -29,6 +29,7 @@ const KEY_NAMES = [
'INSERT_ITEM',
'UPDATE_CUSTOM_CSS',
'CONF_MENU_INDEX',
'CRITICAL_ERROR_MSG',
];
// Convert array of key names into an object, and export

View File

@ -135,6 +135,7 @@ module.exports = {
MOST_USED: 'mostUsed',
LAST_USED: 'lastUsed',
KEYCLOAK_INFO: 'keycloakInfo',
DISABLE_CRITICAL_WARNING: 'disableCriticalWarning',
},
/* Key names for cookie identifiers */
cookieKeys: {
@ -304,6 +305,7 @@ module.exports = {
guestAccess: 2,
notLoggedIn: 3,
keycloakEnabled: 4,
oidcEnabled: 5,
},
/* Progressive Web App settings, used by Vue Config */
pwa: {

View File

@ -19,14 +19,7 @@
</router-link>
</div>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections) || isEditMode"
:class="`item-group-container `
+ `orientation-${layout} `
+ `item-size-${itemSizeBound} `
+ (isEditMode ? 'edit-mode ' : '')
+ (singleSectionView ? 'single-section-view ' : '')
+ (this.colCount ? `col-count-${this.colCount} ` : '')"
>
<div v-if="checkTheresData(sections) || isEditMode" :class="computedClass">
<template v-for="(section, index) in filteredSections">
<Section
:key="index"
@ -34,7 +27,7 @@
:title="section.name"
:icon="section.icon || undefined"
:displayData="getDisplayData(section)"
:groupId="`${pageId}-section-${index}`"
:groupId="makeSectionId(section)"
:items="section.filteredItems"
:widgets="section.widgets"
:searchTerm="searchValue"
@ -70,7 +63,7 @@ import ExportConfigMenu from '@/components/InteractiveEditor/ExportConfigMenu.vu
import AddNewSection from '@/components/InteractiveEditor/AddNewSectionLauncher.vue';
import NotificationThing from '@/components/Settings/LocalConfigWarning.vue';
import StoreKeys from '@/utils/StoreMutations';
import { localStorageKeys, modalNames } from '@/utils/defaults';
import { modalNames } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import BackIcon from '@/assets/interface-icons/back-arrow.svg';
@ -120,19 +113,13 @@ export default {
iconSize() {
return this.$store.getters.iconSize;
},
},
watch: {
layoutOrientation(layout) {
if (layout) {
localStorage.setItem(localStorageKeys.LAYOUT_ORIENTATION, layout);
this.layout = layout;
}
},
iconSize(size) {
if (size) {
localStorage.setItem(localStorageKeys.ICON_SIZE, size);
this.itemSizeBound = size;
}
computedClass() {
let classes = 'item-group-container '
+ ` orientation-${this.$store.getters.layout} item-size-${this.itemSizeBound}`;
if (this.isEditMode) classes += ' edit-mode';
if (this.singleSectionView) classes += ' single-section-view';
if (this.colCount) classes += ` col-count-${this.colCount}`;
return classes;
},
},
methods: {

View File

@ -34,7 +34,7 @@
:index="index"
:title="section.name"
:icon="section.icon || undefined"
:groupId="`section-${index}`"
:groupId="makeSectionId(section)"
:items="filterTiles(section.items)"
:widgets="section.widgets"
:selected="selectedSection === index"

View File

@ -49,6 +49,7 @@ const plugins = !isServer ? [
// Webpack Config
const configureWebpack = {
devtool: 'source-map',
mode,
plugins,
module: {

264
yarn.lock
View File

@ -1179,93 +1179,93 @@
mkdirp "^1.0.4"
rimraf "^3.0.2"
"@sentry-internal/feedback@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.110.0.tgz#7103a08cd6bfb43583087d7476a5f24c5857cc22"
integrity sha512-hrfWa3WkSOiBO5Srcr1j4kuGOlbsQic+REpLOofllVIs56DOo9+Aj9svxT+dcvZERv/nlFSV/E0BfGy9g08IEg==
"@sentry-internal/feedback@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.111.0.tgz#c715e7e6a1877b60cd1f4dff85969660e0deff3f"
integrity sha512-xaKgPPDEirOan7c9HwzYA1KK87kRp/qfIx9ZKLOEtxwy6nqoMuSByGqSwm1Oqfcjpbd7y6/y+7Bw+69ZKNVLDQ==
dependencies:
"@sentry/core" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry/core" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry-internal/replay-canvas@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.110.0.tgz#af21b56157f44c44a2eedf4326ef37f4ea440fa8"
integrity sha512-SNa+AfyfX+vc6Xw0pIfDsa5Qnc9cpexU6M2D19gadtVhmep7qoFBuhBVZrSv6BtdCxvrb5EyYsHYGfjQdIDcvg==
"@sentry-internal/replay-canvas@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.111.0.tgz#aa3cba0477f312cbf40eff4eabeaeda6221a55b6"
integrity sha512-3KPBIpiegTYmuVw9gA2aKuliAQONS3Ny1kJc9x5kz6XQGuLFxqlh6KzoCVaKfQJeq2WJqRNeR4KFFuNGuB3H8w==
dependencies:
"@sentry/core" "7.110.0"
"@sentry/replay" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry/core" "7.111.0"
"@sentry/replay" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry-internal/tracing@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.110.0.tgz#00f2086b0efb8dd5a67831074e52b176aa542d32"
integrity sha512-IIHHa9e/mE7uOMJfNELI8adyoELxOy6u6TNCn5t6fphmq84w8FTc9adXkG/FY2AQpglkIvlILojfMROFB2aaAQ==
"@sentry-internal/tracing@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.111.0.tgz#b352df9f38009c5d306308a829a1dd9a57f084fd"
integrity sha512-CgXly8rsdu4loWVKi2RqpInH3C2cVBuaYsx4ZP5IJpzSinsUAMyyr3Pc0PZzCyoVpBBXGBGj/4HhFsY3q6Z0Vg==
dependencies:
"@sentry/core" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry/core" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry/browser@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.110.0.tgz#40900d76a8f423a7163a594ec9267a2e0ebd8a5b"
integrity sha512-gIxedVm6ZgkjQfgCDgLWJgAsolq6OxV8hQ2j1+RaDL2RngvelFo/vlX5f2sD6EbjVp77Cri8u5GkMJF+v4p84g==
"@sentry/browser@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.111.0.tgz#29da73e7192eb5643d101c47922d7374e4cc88ed"
integrity sha512-x7S9XoJh+TbMnur4eBhPpCVo+p7udABBV2gQk+Iw6LP9e8EFKmGmNyl76vSsT6GeFJ7mwxDEKfuwbVoLBjIvHw==
dependencies:
"@sentry-internal/feedback" "7.110.0"
"@sentry-internal/replay-canvas" "7.110.0"
"@sentry-internal/tracing" "7.110.0"
"@sentry/core" "7.110.0"
"@sentry/replay" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry-internal/feedback" "7.111.0"
"@sentry-internal/replay-canvas" "7.111.0"
"@sentry-internal/tracing" "7.111.0"
"@sentry/core" "7.111.0"
"@sentry/replay" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry/core@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.110.0.tgz#2945d3ac0ef116ed313fbfb9da4f483b66fe5bca"
integrity sha512-g4suCQO94mZsKVaAbyD1zLFC5YSuBQCIPHXx9fdgtfoPib7BWjWWePkllkrvsKAv4u8Oq05RfnKOhOMRHpOKqg==
"@sentry/core@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.111.0.tgz#54c9037a3b79b3623377dce1887b69b40670e201"
integrity sha512-/ljeMjZu8CSrLGrseBi/7S2zRIFsqMcvfyG6Nwgfc07J9nbHt8/MqouE1bXZfiaILqDBpK7BK9MLAAph4mkAWg==
dependencies:
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry/replay@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.110.0.tgz#e185c88cec573724b46b79ada7ef5a7098acd1b6"
integrity sha512-EEpGPf3iBJjWejvoxKLVMnLtLNwPTUxHJV1oxUkbcSi3B/tG5hW7LArYDjAcvkfa4VmA8JLCwj2vYU5MQ8tj6g==
"@sentry/replay@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.111.0.tgz#6d21bddf2ec245db6eb2c471e81efd94364107ae"
integrity sha512-cSbI4A4hrO0sZ0ynvLQauPg8YyaDOQkhGkyvbws8W9WgfxR8X827bY9S0f1TPfgaFiVcKb0iRaAwyXHg3pyzOg==
dependencies:
"@sentry-internal/tracing" "7.110.0"
"@sentry/core" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry-internal/tracing" "7.111.0"
"@sentry/core" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sentry/tracing@^7.102.1":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.110.0.tgz#9e8836babba9894309d337f3006b98d79b863329"
integrity sha512-pAydcCqzyzn2Uv9qmuDX5saHbXp4eMMsBW2C/oSkVdKQQSdA7JeG27d82Jz3cMVrfjv105lShP5qS2YjhBTkow==
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.111.0.tgz#3c9da76e6ca1f2e17e138ce828330e93e53f67a4"
integrity sha512-+BHvdCJxcNnBkru3Y5aFZssEwyNU/mwPTSZqYOhFilokVIrDmVrP/R9g8jHSUqXF4KwB3RaknTPj/4484Z0erA==
dependencies:
"@sentry-internal/tracing" "7.110.0"
"@sentry-internal/tracing" "7.111.0"
"@sentry/types@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.110.0.tgz#c3f252b008cab905097fc71e174191f20bdaf4f3"
integrity sha512-DqYBLyE8thC5P5MuPn+sj8tL60nCd/f5cerFFPcudn5nJ4Zs1eI6lKlwwyHYTEu5c4KFjCB0qql6kXfwAHmTyA==
"@sentry/types@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.111.0.tgz#9c869c3c51d606041916765ba58f29de915707ac"
integrity sha512-Oti4pgQ55+FBHKKcHGu51ZUxO1u52G5iVNK4mbtAN+5ArSCy/2s1H8IDJiOMswn3acfUnCR0oB/QsbEgAPZ26g==
"@sentry/utils@7.110.0":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.110.0.tgz#68ef59359d608a1a6a7205b780196a042ad73ab2"
integrity sha512-VBsdLLN+5tf73fhf/Cm7JIsUJ6y9DkJj8h4I6Mxx0rszrvOyH6S5px40K+V4jdLBzMEvVinC7q2Cbf1YM18BSw==
"@sentry/utils@7.111.0":
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.111.0.tgz#e006cc1e751b30ff5cf914c34eb143102e2e8c2d"
integrity sha512-CB5rz1EgCSwj3xoXogsCZ5pQtfERrURc/ItcCuoaijUhkD0iMq5MCNWMHW3mBsBrqx/Oba+XGvDu0t/5+SWwBg==
dependencies:
"@sentry/types" "7.110.0"
"@sentry/types" "7.111.0"
"@sentry/vue@^7.102.1":
version "7.110.0"
resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.110.0.tgz#60bca341bf55c0ebe3bbd5f220241f5e34adbb8b"
integrity sha512-N8qAAPNJMV9fRMfvbRIWgFrn+wNH6ABGdc7fbFg1y3y0rOw58YMMg0+WdHMGEeWhH7N2/cCJGUHdz4egqaM3gQ==
version "7.111.0"
resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.111.0.tgz#064e1523fc97a81e0a62fc9c40bd905e975fb4f0"
integrity sha512-MEvv+1r7548rMuZF3WbxY2OYxHyjuROMTptYR2xrQj+jEkJ1hFbZyn5J+uH/9OamGY2rksnMqxFBcnfdqrItvA==
dependencies:
"@sentry/browser" "7.110.0"
"@sentry/core" "7.110.0"
"@sentry/types" "7.110.0"
"@sentry/utils" "7.110.0"
"@sentry/browser" "7.111.0"
"@sentry/core" "7.111.0"
"@sentry/types" "7.111.0"
"@sentry/utils" "7.111.0"
"@sideway/address@^4.1.5":
version "4.1.5"
@ -1336,9 +1336,9 @@
"@types/estree" "*"
"@types/eslint@*":
version "8.56.9"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.9.tgz#403e9ced04a34e63f1c383c5b8ee1a94442c8cc4"
integrity sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==
version "8.56.10"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
@ -1436,9 +1436,9 @@
integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==
"@types/qs@*":
version "6.9.14"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b"
integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==
version "6.9.15"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce"
integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==
"@types/range-parser@*":
version "1.2.7"
@ -1816,24 +1816,24 @@
semver "^7.3.4"
strip-ansi "^6.0.0"
"@vue/compiler-core@3.4.21":
version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz#868b7085378fc24e58c9aed14c8d62110a62be1a"
integrity sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==
"@vue/compiler-core@3.4.23":
version "3.4.23"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.23.tgz#a08f5998e391ad75e602a66dd7255af9054df2f3"
integrity sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==
dependencies:
"@babel/parser" "^7.23.9"
"@vue/shared" "3.4.21"
"@babel/parser" "^7.24.1"
"@vue/shared" "3.4.23"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.0.2"
source-map-js "^1.2.0"
"@vue/compiler-dom@3.4.21":
version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803"
integrity sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==
"@vue/compiler-dom@3.4.23":
version "3.4.23"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz#6fa622d1e5c8508551564c5dc5948e9cddf60867"
integrity sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==
dependencies:
"@vue/compiler-core" "3.4.21"
"@vue/shared" "3.4.21"
"@vue/compiler-core" "3.4.23"
"@vue/shared" "3.4.23"
"@vue/compiler-sfc@2.7.16":
version "2.7.16"
@ -1847,27 +1847,27 @@
prettier "^1.18.2 || ^2.0.0"
"@vue/compiler-sfc@^3.4.15":
version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2"
integrity sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==
version "3.4.23"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz#7041517b9bbd1b304f0db33bfa424e9a899fda8d"
integrity sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==
dependencies:
"@babel/parser" "^7.23.9"
"@vue/compiler-core" "3.4.21"
"@vue/compiler-dom" "3.4.21"
"@vue/compiler-ssr" "3.4.21"
"@vue/shared" "3.4.21"
"@babel/parser" "^7.24.1"
"@vue/compiler-core" "3.4.23"
"@vue/compiler-dom" "3.4.23"
"@vue/compiler-ssr" "3.4.23"
"@vue/shared" "3.4.23"
estree-walker "^2.0.2"
magic-string "^0.30.7"
postcss "^8.4.35"
source-map-js "^1.0.2"
magic-string "^0.30.8"
postcss "^8.4.38"
source-map-js "^1.2.0"
"@vue/compiler-ssr@3.4.21":
version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz#b84ae64fb9c265df21fc67f7624587673d324fef"
integrity sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==
"@vue/compiler-ssr@3.4.23":
version "3.4.23"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz#1ae4afe962a9e156b1a79eff909c37cd423dd4c2"
integrity sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==
dependencies:
"@vue/compiler-dom" "3.4.21"
"@vue/shared" "3.4.21"
"@vue/compiler-dom" "3.4.23"
"@vue/shared" "3.4.23"
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2":
version "3.3.0"
@ -1901,10 +1901,10 @@
resolved "https://registry.yarnpkg.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz#ceb924b4ecb3b9c43871c7a429a02f8423e621ab"
integrity sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==
"@vue/shared@3.4.21":
version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
"@vue/shared@3.4.23":
version "3.4.23"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.23.tgz#e536a6dfd2f5f950d08c2e8ebcfe7e5329a851a1"
integrity sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==
"@vue/web-component-wrapper@^1.2.0":
version "1.3.0"
@ -2712,6 +2712,13 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
basic-auth@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
dependencies:
safe-buffer "5.1.2"
batch@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@ -3121,9 +3128,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001587:
version "1.0.30001609"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz#fc34fad75c0c6d6d6303bdbceec2da8f203dabd6"
integrity sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==
version "1.0.30001612"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz#d34248b4ec1f117b70b24ad9ee04c90e0b8a14ae"
integrity sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==
case-sensitive-paths-webpack-plugin@^2.3.0:
version "2.4.0"
@ -3576,9 +3583,9 @@ copy-webpack-plugin@^5.1.1:
webpack-log "^2.0.0"
core-js-compat@^3.31.0, core-js-compat@^3.36.1, core-js-compat@^3.6.5:
version "3.36.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8"
integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==
version "3.37.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.0.tgz#d9570e544163779bb4dff1031c7972f44918dc73"
integrity sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==
dependencies:
browserslist "^4.23.0"
@ -3588,9 +3595,9 @@ core-js@^2.4.0:
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.6.5:
version "3.36.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.1.tgz#c97a7160ebd00b2de19e62f4bbd3406ab720e578"
integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==
version "3.37.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.0.tgz#d8dde58e91d156b2547c19d8a4efd5c7f6c426bb"
integrity sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==
core-util-is@1.0.2:
version "1.0.2"
@ -4265,9 +4272,9 @@ ejs@^2.6.1:
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
electron-to-chromium@^1.4.668:
version "1.4.736"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz#ecb4348f4d5c70fb1e31c347e5bad6b751066416"
integrity sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==
version "1.4.745"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz#9c202ce9cbf18a5b5e0ca47145fd127cc4dd2290"
integrity sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==
elliptic@^6.5.3, elliptic@^6.5.5:
version "6.5.5"
@ -4810,6 +4817,13 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
express-basic-auth@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381"
integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==
dependencies:
basic-auth "^2.0.1"
express@^4.16.3, express@^4.17.1, express@^4.17.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
@ -6789,10 +6803,10 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
magic-string@^0.30.7:
version "0.30.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d"
integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==
magic-string@^0.30.8:
version "0.30.10"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
@ -8231,7 +8245,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3
picocolors "^0.2.1"
source-map "^0.6.1"
postcss@^8.4.14, postcss@^8.4.35:
postcss@^8.4.14, postcss@^8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
@ -9208,7 +9222,7 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.0:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==