mirror of https://github.com/Lissy93/dashy.git
🔁 Merge pull request #528 from Ateroz/master
Fetch conf.yml from server
This commit is contained in:
commit
93911c2520
|
@ -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)
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)",
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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 ;)
|
||||||
|
|
14
src/App.vue
14
src/App.vue
|
@ -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
|
||||||
|
|
11
src/store.js
11
src/store.js
|
@ -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);
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue