mirror of https://github.com/Lissy93/dashy.git
🔀 Merge pull request #1542 from Lissy93/FEAT/3.0.1-improvements
[FEAT] Clearer error messaging and documented user-data dir (3.0.1)
This commit is contained in:
commit
d92ae25700
13
.env
13
.env
|
@ -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
|
||||
|
|
|
@ -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>_"
|
|
@ -42,7 +42,7 @@ RUN apk add --no-cache tzdata
|
|||
COPY --from=BUILD_IMAGE /app ./
|
||||
|
||||
# Finally, run start command to serve up the built application
|
||||
CMD [ "yarn", "build-and-start" ]
|
||||
CMD [ "yarn", "start" ]
|
||||
|
||||
# Expose the port
|
||||
EXPOSE ${PORT}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
- [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)
|
||||
|
@ -115,6 +118,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 +147,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.
|
||||
|
|
|
@ -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`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dashy",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"license": "MIT",
|
||||
"main": "server",
|
||||
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",
|
||||
|
@ -26,6 +26,7 @@
|
|||
"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",
|
||||
|
|
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 |
|
@ -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>
|
||||
|
|
75
server.js
75
server.js
|
@ -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')))
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 : '';
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -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';
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -37,8 +37,10 @@ export default {
|
|||
input: '',
|
||||
};
|
||||
},
|
||||
props: {
|
||||
iconSize: String,
|
||||
computed: {
|
||||
iconSize() {
|
||||
return this.$store.getters.iconSize;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
IconSmall,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
16
src/main.js
16
src/main.js
|
@ -23,6 +23,7 @@ import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/u
|
|||
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||
import { initHeaderAuth, isHeaderAuthEnabled } from '@/utils/HeaderAuth';
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
// Initialize global Vue components
|
||||
Vue.use(VueI18n);
|
||||
|
@ -61,16 +62,19 @@ const mount = () => new Vue({
|
|||
}).$mount('#app');
|
||||
|
||||
store.dispatch(Keys.INITIALIZE_CONFIG).then(() => {
|
||||
// Keycloak is enabled, redirect to KC login page
|
||||
if (isKeycloakEnabled()) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -105,10 +105,18 @@ 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);
|
||||
const headers = new Headers();
|
||||
|
||||
// If using a proxy, set the 'Target-URL' header
|
||||
if (this.useProxy) {
|
||||
headers.append('Target-URL', endpoint);
|
||||
}
|
||||
// Apply widget-specific custom headers
|
||||
Object.entries(CustomHeaders).forEach(([key, value]) => {
|
||||
headers.append(key, value);
|
||||
});
|
||||
|
||||
// If the request is a GET, delete the body
|
||||
const bodyContent = method.toUpperCase() === 'GET' ? undefined : data;
|
||||
|
|
102
src/store.js
102
src/store.js
|
@ -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: {},
|
||||
|
|
|
@ -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*
|
||||
|
|
|
@ -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'] {
|
||||
|
|
|
@ -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…" }
|
||||
|
|
|
@ -11,8 +11,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 +39,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 */
|
||||
|
@ -108,7 +121,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 +187,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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,9 +532,15 @@
|
|||
"description": "User type, denoting privilege level, either admin or normal",
|
||||
"default": "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["user"],
|
||||
"oneOf": [
|
||||
{ "required": ["hash"] },
|
||||
{ "required": ["password"] }
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
"enableHeaderAuth": {
|
||||
"title": "Enable HeaderAuth?",
|
||||
"type": "boolean",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
264
yarn.lock
264
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue