🔁 Merge pull request #528 from Ateroz/master

Fetch conf.yml from server
This commit is contained in:
Alicia Sykes 2022-03-06 23:32:45 +00:00 committed by GitHub
commit 93911c2520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 38 additions and 22 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## :sparkles: 2.0.4 - Dynamic Config Loading [PR #528](https://github.com/Lissy93/dashy/pull/528)
- `conf.yml` is now loaded dynamically and the app now only needs a browser refresh on config change, not a full rebuild!
## 🐛 2.0.3 - Bug Fixes [PR #488](https://github.com/Lissy93/dashy/pull/488) ## 🐛 2.0.3 - Bug Fixes [PR #488](https://github.com/Lissy93/dashy/pull/488)
- Press enter to submit login form (Re: #483) - Press enter to submit login form (Re: #483)
- Allow disabling write to local storage and disk (Re: #485) - Allow disabling write to local storage and disk (Re: #485)

View File

@ -39,11 +39,15 @@ WORKDIR ${DIRECTORY}
RUN apk add --no-cache tzdata tini RUN apk add --no-cache tzdata tini
# Copy built application from build phase # Copy built application from build phase
COPY --from=BUILD_IMAGE /app ./ COPY --from=BUILD_IMAGE /app/dist/ public/
COPY --from=BUILD_IMAGE /app/node_modules/ node_modules/
COPY services/ services/
COPY src/utils/ src/utils/
COPY package.json yarn.lock server.js ./
# Finally, run start command to serve up the built application # Finally, run start command to serve up the built application
ENTRYPOINT [ "/sbin/tini", "--" ] ENTRYPOINT [ "/sbin/tini", "--" ]
CMD [ "yarn", "build-and-start" ] CMD [ "yarn", "start" ]
# Expose the port # Expose the port
EXPOSE ${PORT} EXPOSE ${PORT}

View File

@ -1,6 +1,6 @@
{ {
"name": "Dashy", "name": "Dashy",
"version": "2.0.3", "version": "2.0.4",
"license": "MIT", "license": "MIT",
"main": "server", "main": "server",
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)", "author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",

View File

@ -68,7 +68,7 @@ const method = (m, mw) => (req, res, next) => (req.method === m ? mw(req, res, n
const app = express() const app = express()
// Serves up static files // Serves up static files
.use(express.static(path.join(__dirname, 'dist'))) .use(express.static(path.join(__dirname, 'dist')))
.use(express.static(path.join(__dirname, 'public'), { index: 'initialization.html' })) .use(express.static(path.join(__dirname, 'public')))
// Load middlewares for parsing JSON, and supporting HTML5 history routing // Load middlewares for parsing JSON, and supporting HTML5 history routing
.use(express.json({ limit: '1mb' })) .use(express.json({ limit: '1mb' }))
.use(history()) .use(history())

View File

@ -28,9 +28,6 @@ module.exports = (ip, port, isDocker) => {
+ `${chars.CYAN}Welcome to Dashy! 🚀${chars.RESET}${chars.BR}` + `${chars.CYAN}Welcome to Dashy! 🚀${chars.RESET}${chars.BR}`
+ `${chars.GREEN}Your new dashboard is now up and running ` + `${chars.GREEN}Your new dashboard is now up and running `
+ `${containerId ? `in container ID ${containerId}` : 'with Docker'}${chars.BR}` + `${containerId ? `in container ID ${containerId}` : 'with Docker'}${chars.BR}`
+ `${chars.GREEN}After updating your config file, run `
+ `'${chars.BRIGHT}docker exec -it ${containerId || '[container-id]'} yarn build`
+ `${chars.RESET}${chars.GREEN}' to rebuild${chars.BR}`
+ `${chars.BLUE}${stars(91)}${chars.BR}${chars.RESET}`; + `${chars.BLUE}${stars(91)}${chars.BR}${chars.RESET}`;
} else { } else {
// Prepare message for users running app on bare metal // Prepare message for users running app on bare metal
@ -38,8 +35,6 @@ module.exports = (ip, port, isDocker) => {
+ `${chars.CYAN}Welcome to Dashy! 🚀${blanks(55)}${chars.GREEN}${chars.BR}` + `${chars.CYAN}Welcome to Dashy! 🚀${blanks(55)}${chars.GREEN}${chars.BR}`
+ `${chars.CYAN}Your new dashboard is now up and running at ${chars.BRIGHT}` + `${chars.CYAN}Your new dashboard is now up and running at ${chars.BRIGHT}`
+ `http://${ip}:${port}${chars.RESET}${blanks(18 - ip.length)}${chars.GREEN}${chars.BR}` + `http://${ip}:${port}${chars.RESET}${blanks(18 - ip.length)}${chars.GREEN}${chars.BR}`
+ `${chars.CYAN}After updating your config file, run '${chars.BRIGHT}yarn build`
+ `${chars.RESET}${chars.CYAN}' to rebuild the app${blanks(6)}${chars.GREEN}${chars.BR}`
+ `${line(75)}${chars.BR}${chars.BR}${chars.RESET}`; + `${line(75)}${chars.BR}${chars.BR}${chars.RESET}`;
} }
// Make some sexy ascii art ;) // Make some sexy ascii art ;)

View File

@ -3,8 +3,8 @@
<EditModeTopBanner v-if="isEditMode" /> <EditModeTopBanner v-if="isEditMode" />
<LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" /> <LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" />
<Header :pageInfo="pageInfo" /> <Header :pageInfo="pageInfo" />
<router-view /> <router-view v-if="!isFetching" />
<Footer :text="footerText" v-if="visibleComponents.footer" /> <Footer :text="footerText" v-if="visibleComponents.footer && !isFetching" />
</div> </div>
</template> </template>
<script> <script>
@ -33,6 +33,7 @@ export default {
data() { data() {
return { return {
isLoading: true, // Set to false after mount complete isLoading: true, // Set to false after mount complete
isFetching: true, // Set to false after the conf has been fetched
}; };
}, },
watch: { watch: {
@ -40,6 +41,9 @@ export default {
// When in edit mode, show confirmation dialog on page exit // When in edit mode, show confirmation dialog on page exit
window.onbeforeunload = isEditMode ? this.confirmExit : null; window.onbeforeunload = isEditMode ? this.confirmExit : null;
}, },
config() {
this.isFetching = false;
},
}, },
computed: { computed: {
/* If the user has specified custom text for footer - get it */ /* If the user has specified custom text for footer - get it */
@ -69,9 +73,6 @@ export default {
return this.$store.state.editMode; return this.$store.state.editMode;
}, },
}, },
created() {
this.$store.dispatch(Keys.INITIALIZE_CONFIG);
},
methods: { methods: {
/* Injects the users custom CSS as a style tag */ /* Injects the users custom CSS as a style tag */
injectCustomStyles(usersCss) { injectCustomStyles(usersCss) {
@ -135,7 +136,8 @@ export default {
}, },
}, },
/* Basic initialization tasks on app load */ /* Basic initialization tasks on app load */
mounted() { async mounted() {
await this.$store.dispatch(Keys.INITIALIZE_CONFIG); // Initialize config before moving on
this.applyLanguage(); // Apply users local language this.applyLanguage(); // Apply users local language
this.hideSplash(); // Hide the splash screen, if visible this.hideSplash(); // Hide the splash screen, if visible
if (this.appConfig.customCss) { // Inject users custom CSS, if present if (this.appConfig.customCss) { // Inject users custom CSS, if present

View File

@ -1,6 +1,8 @@
/* eslint-disable no-param-reassign, prefer-destructuring */ /* eslint-disable no-param-reassign, prefer-destructuring */
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import axios from 'axios';
import yaml from 'js-yaml';
import Keys from '@/utils/StoreMutations'; import Keys from '@/utils/StoreMutations';
import ConfigAccumulator from '@/utils/ConfigAccumalator'; import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { componentVisibility } from '@/utils/ConfigHelpers'; import { componentVisibility } from '@/utils/ConfigHelpers';
@ -14,6 +16,7 @@ Vue.use(Vuex);
const { const {
INITIALIZE_CONFIG, INITIALIZE_CONFIG,
SET_CONFIG, SET_CONFIG,
SET_REMOTE_CONFIG,
SET_MODAL_OPEN, SET_MODAL_OPEN,
SET_LANGUAGE, SET_LANGUAGE,
SET_ITEM_LAYOUT, SET_ITEM_LAYOUT,
@ -38,6 +41,7 @@ const {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
config: {}, config: {},
remoteConfig: {}, // The configuration stored on the server
editMode: false, // While true, the user can drag and edit items + sections editMode: false, // While true, the user can drag and edit items + sections
modalOpen: false, // KB shortcut functionality will be disabled when modal is open modalOpen: false, // KB shortcut functionality will be disabled when modal is open
navigateConfToTab: undefined, // Used to switch active tab in config modal navigateConfToTab: undefined, // Used to switch active tab in config modal
@ -126,6 +130,9 @@ const store = new Vuex.Store({
[SET_CONFIG](state, config) { [SET_CONFIG](state, config) {
state.config = config; state.config = config;
}, },
[SET_REMOTE_CONFIG](state, config) {
state.remoteConfig = config;
},
[SET_LANGUAGE](state, lang) { [SET_LANGUAGE](state, lang) {
const newConfig = state.config; const newConfig = state.config;
newConfig.appConfig.language = lang; newConfig.appConfig.language = lang;
@ -271,7 +278,9 @@ const store = new Vuex.Store({
}, },
actions: { actions: {
/* Called when app first loaded. Reads config and sets state */ /* Called when app first loaded. Reads config and sets state */
[INITIALIZE_CONFIG]({ commit }) { async [INITIALIZE_CONFIG]({ commit }) {
// Get the config file from the server and store it for use by the accumulator
commit(SET_REMOTE_CONFIG, yaml.load((await axios.get('conf.yml')).data));
const deepCopy = (json) => JSON.parse(JSON.stringify(json)); const deepCopy = (json) => JSON.parse(JSON.stringify(json));
const config = deepCopy(new ConfigAccumulator().config()); const config = deepCopy(new ConfigAccumulator().config());
commit(SET_CONFIG, config); commit(SET_CONFIG, config);

View File

@ -14,11 +14,11 @@ import {
} from '@/utils/defaults'; } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler'; import ErrorHandler from '@/utils/ErrorHandler';
import { applyItemId } from '@/utils/SectionHelpers'; import { applyItemId } from '@/utils/SectionHelpers';
import conf from '../../public/conf.yml'; import $store from '@/store';
export default class ConfigAccumulator { export default class ConfigAccumulator {
constructor() { constructor() {
this.conf = conf; this.conf = $store.state.remoteConfig;
} }
/* App Config */ /* App Config */

View File

@ -1,7 +1,8 @@
import axios from 'axios';
import yaml from 'js-yaml';
import { register } from 'register-service-worker'; import { register } from 'register-service-worker';
import { sessionStorageKeys } from '@/utils/defaults'; import { sessionStorageKeys } from '@/utils/defaults';
import { statusMsg, statusErrorMsg } from '@/utils/CoolConsole'; import { statusMsg, statusErrorMsg } from '@/utils/CoolConsole';
import conf from '../../public/conf.yml';
/* Sets a local storage item with the state from the SW lifecycle */ /* Sets a local storage item with the state from the SW lifecycle */
const setSwStatus = (swStateToSet) => { const setSwStatus = (swStateToSet) => {
@ -31,7 +32,8 @@ const setSwStatus = (swStateToSet) => {
* Disable if not running in production * Disable if not running in production
* Or disable if user specified to disable * Or disable if user specified to disable
*/ */
const shouldEnableServiceWorker = () => { const shouldEnableServiceWorker = async () => {
const conf = yaml.load((await axios.get('conf.yml')).data);
if (conf && conf.appConfig && conf.appConfig.enableServiceWorker) { if (conf && conf.appConfig && conf.appConfig.enableServiceWorker) {
setSwStatus({ disabledByUser: false }); setSwStatus({ disabledByUser: false });
return true; return true;
@ -51,8 +53,8 @@ const printSwStatus = (msg) => {
const swUrl = `${process.env.BASE_URL || '/'}service-worker.js`; const swUrl = `${process.env.BASE_URL || '/'}service-worker.js`;
/* If service worker enabled, then register it, and print message when status changes */ /* If service worker enabled, then register it, and print message when status changes */
const registerServiceWorker = () => { const registerServiceWorker = async () => {
if (shouldEnableServiceWorker()) { if (await shouldEnableServiceWorker()) {
register(swUrl, { register(swUrl, {
ready() { ready() {
setSwStatus({ ready: true }); setSwStatus({ ready: true });

View File

@ -2,6 +2,7 @@
const KEY_NAMES = [ const KEY_NAMES = [
'INITIALIZE_CONFIG', 'INITIALIZE_CONFIG',
'SET_CONFIG', 'SET_CONFIG',
'SET_REMOTE_CONFIG',
'SET_MODAL_OPEN', 'SET_MODAL_OPEN',
'SET_LANGUAGE', 'SET_LANGUAGE',
'SET_EDIT_MODE', 'SET_EDIT_MODE',