This commit is contained in:
Alicia Sykes 2024-04-27 01:01:56 +01:00 committed by GitHub
commit 40e242269d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 342 additions and 66 deletions

2
.env
View File

@ -52,7 +52,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

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>_"

View File

@ -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}

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="460" src="https://github.com/Lissy93/dashy/blob/WEBSITE/docs-site-source/static/umbrel-banner-github.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.

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.0.1",
"license": "MIT",
"main": "server",
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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: {
@ -174,6 +182,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 };
@ -320,16 +332,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);
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 +374,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 +387,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,8 +402,8 @@ 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) => {
@ -389,17 +425,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

@ -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: {

View File

@ -34,7 +34,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"

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"