mirror of https://github.com/lissy93/dashy
Compare commits
4 Commits
f77c192e66
...
3416615d30
Author | SHA1 | Date |
---|---|---|
Alicia Sykes | 3416615d30 | |
Alicia Sykes | db63362327 | |
Alicia Sykes | 9e6fb17d93 | |
Alicia Sykes | 4594c99b57 |
|
@ -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;
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
<template>
|
||||
<div class="critical-error-wrap" v-if="shouldShow">
|
||||
<button class="close" title="Close Warning" @click="close">🗙</button>
|
||||
<h3>Configuration Load Error</h3>
|
||||
<p>
|
||||
Dashy has failed to load correctly due to a configuration error.
|
||||
</p>
|
||||
<h4>Ensure that</h4>
|
||||
<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>Error Details</h4>
|
||||
<h4>{{ $t('critical-error.sub-error-details') }}</h4>
|
||||
<pre>{{ this.$store.state.criticalError }}</pre>
|
||||
<h4>Next Steps</h4>
|
||||
<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>)
|
||||
|
@ -29,7 +27,9 @@
|
|||
</li>
|
||||
<li>Click 'Ignore Critical Errors' below to not show this warning again</li>
|
||||
</ul>
|
||||
<button class="user-doesnt-care" @click="ignoreWarning">Ignore Critical Errors</button>
|
||||
<button class="user-doesnt-care" @click="ignoreWarning">
|
||||
{{ $t('critical-error.ignore-button') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -69,6 +69,7 @@ export default {
|
|||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
max-width: 50rem;
|
||||
background: var(--background-darker);
|
||||
padding: 1rem;
|
||||
border-radius: var(--curve-factor);
|
||||
|
|
|
@ -28,7 +28,7 @@ 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: () => ({
|
||||
|
@ -84,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];
|
||||
|
|
15
src/store.js
15
src/store.js
|
@ -44,6 +44,12 @@ const {
|
|||
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
|
||||
|
@ -335,7 +341,7 @@ const store = new Vuex.Store({
|
|||
data = yaml.load(response.data);
|
||||
} catch (parseError) {
|
||||
commit(CRITICAL_ERROR_MSG, `Failed to parse YAML: ${parseError.message}`);
|
||||
return {};
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
// Replace missing root properties with empty objects
|
||||
if (!data.appConfig) data.appConfig = {};
|
||||
|
@ -357,7 +363,7 @@ const store = new Vuex.Store({
|
|||
} else {
|
||||
commit(CRITICAL_ERROR_MSG, `Failed to fetch configuration: ${fetchError.message}`);
|
||||
}
|
||||
return {};
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -368,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);
|
||||
|
@ -396,7 +403,7 @@ const store = new Vuex.Store({
|
|||
|
||||
if (!subConfigPath) {
|
||||
commit(CRITICAL_ERROR_MSG, `Unable to find config for '${subConfigId}'`);
|
||||
return null;
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
|
||||
axios.get(subConfigPath).then((response) => {
|
||||
|
@ -428,7 +435,7 @@ const store = new Vuex.Store({
|
|||
commit(CRITICAL_ERROR_MSG, `Unable to load config from '${subConfigPath}'`, err);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
return { ...emptyConfig };
|
||||
},
|
||||
},
|
||||
modules: {},
|
||||
|
|
|
@ -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'] {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue