diff --git a/src/App.vue b/src/App.vue
index 2598f68a..83c7a959 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -12,7 +12,7 @@ import Header from '@/components/PageStrcture/Header.vue';
import Footer from '@/components/PageStrcture/Footer.vue';
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
import Defaults, { localStorageKeys, splashScreenTime } from '@/utils/defaults';
-import conf from '../public/conf.yml';
+import { config, appConfig, pageInfo } from '@/utils/ConfigAccumalator';
export default {
name: 'app',
@@ -21,48 +21,18 @@ export default {
Footer,
LoadingScreen,
},
+ provide: {
+ config,
+ },
data() {
return {
- // pageInfo: this.getPageInfo(conf.pageInfo),
showFooter: Defaults.visibleComponents.footer,
isLoading: true,
+ appConfig,
+ pageInfo,
};
},
- computed: {
- pageInfo() {
- return this.getPageInfo(conf.pageInfo);
- },
- appConfig() {
- if (localStorage[localStorageKeys.APP_CONFIG]) {
- return JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
- } else if (conf.appConfig) {
- return conf.appConfig;
- } else {
- return Defaults.appConfig;
- }
- },
- },
methods: {
- /* Returns either page info from the config, or default values */
- getPageInfo(pageInfo) {
- const defaults = Defaults.pageInfo;
-
- let localPageInfo;
- try {
- localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
- } catch (e) {
- localPageInfo = {};
- }
- if (pageInfo) {
- return {
- title: localPageInfo.title || pageInfo.title || defaults.title,
- description: localPageInfo.description || pageInfo.description || defaults.description,
- navLinks: localPageInfo.navLinks || pageInfo.navLinks || defaults.navLinks,
- footerText: localPageInfo.footerText || pageInfo.footerText || defaults.footerText,
- };
- }
- return defaults;
- },
getFooterText() {
if (this.pageInfo && this.pageInfo.footerText) {
return this.pageInfo.footerText;
diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue
index 5a89a00f..53cb47fc 100644
--- a/src/components/LinkItems/Item.vue
+++ b/src/components/LinkItems/Item.vue
@@ -20,12 +20,20 @@
+
@@ -122,6 +150,7 @@ export default {
box-shadow: var(--item-shadow);
cursor: pointer;
text-decoration: none;
+ position: relative;
&:hover {
box-shadow: var(--item-hover-shadow);
background: var(--item-background-hover);
@@ -175,6 +204,13 @@ export default {
}
}
+/* Colored dot showing service status */
+.status-indicator {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
.opening-method-icon {
display: none; // Hidden by default, visible on hover
}
diff --git a/src/components/LinkItems/ItemGroup.vue b/src/components/LinkItems/ItemGroup.vue
index ef3dba8c..a375d7d6 100644
--- a/src/components/LinkItems/ItemGroup.vue
+++ b/src/components/LinkItems/ItemGroup.vue
@@ -28,6 +28,7 @@
:color="item.color"
:backgroundColor="item.backgroundColor"
:itemSize="newItemSize"
+ :enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
@itemClicked="$emit('itemClicked')"
@triggerModal="triggerModal"
/>
@@ -49,6 +50,7 @@ import IframeModal from '@/components/LinkItems/IframeModal.vue';
export default {
name: 'ItemGroup',
+ inject: ['config'],
props: {
groupId: String,
title: String,
@@ -92,6 +94,10 @@ export default {
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
+ shouldEnableStatusCheck(itemPreference) {
+ const globalPreference = this.config.appConfig.statusCheck || false;
+ return itemPreference !== undefined ? itemPreference : globalPreference;
+ },
},
};
diff --git a/src/components/LinkItems/StatusIndicator.vue b/src/components/LinkItems/StatusIndicator.vue
new file mode 100644
index 00000000..6b1864dd
--- /dev/null
+++ b/src/components/LinkItems/StatusIndicator.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
index 90c589d6..eea75224 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,12 +1,14 @@
import Vue from 'vue';
+/* Import component Vue plugins, used throughout the app */
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
import VModal from 'vue-js-modal'; // Modal component
import VSelect from 'vue-select'; // Select dropdown component
import VTabs from 'vue-material-tabs'; // Tab view component, used on the config page
import Toasted from 'vue-toasted'; // Toast component, used to show confirmation notifications
+
import { toastedOptions } from './utils/defaults';
-import App from './App.vue';
+import Dashy from './App.vue';
import router from './router';
import './registerServiceWorker';
@@ -20,5 +22,5 @@ Vue.config.productionTip = false;
new Vue({
router,
- render: (awesome) => awesome(App),
+ render: (awesome) => awesome(Dashy),
}).$mount('#app');
diff --git a/src/router.js b/src/router.js
index 968938f4..b32aa255 100644
--- a/src/router.js
+++ b/src/router.js
@@ -1,36 +1,15 @@
import Vue from 'vue';
import Router from 'vue-router';
-import Home from './views/Home.vue';
-import Login from './views/Login.vue';
-import conf from '../public/conf.yml'; // Main site configuration
-import { pageInfo as defaultPageInfo, localStorageKeys } from './utils/defaults';
-import { isLoggedIn } from './utils/Auth';
+
+import Home from '@/views/Home.vue';
+import Login from '@/views/Login.vue';
+import { isLoggedIn } from '@/utils/Auth';
+import { appConfig, pageInfo, sections } from '@/utils/ConfigAccumalator';
Vue.use(Router);
-const { sections, pageInfo, appConfig } = conf;
-let localPageInfo;
-try {
- localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
-} catch (e) {
- localPageInfo = undefined;
-}
-
-let localAppConfig;
-try {
- localAppConfig = JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
-} catch (e) {
- localAppConfig = undefined;
-}
-
-const config = {
- sections: sections || [],
- pageInfo: localPageInfo || pageInfo || defaultPageInfo,
- appConfig: localAppConfig || appConfig || {},
-};
-
const isAuthenticated = () => {
- const users = config.appConfig.auth;
+ const users = appConfig.auth;
return (!users || isLoggedIn(users));
};
@@ -40,7 +19,11 @@ const router = new Router({
path: '/',
name: 'home',
component: Home,
- props: config,
+ props: {
+ appConfig,
+ pageInfo,
+ sections,
+ },
meta: {
title: pageInfo.title || 'Home Page',
metaTags: [
@@ -56,7 +39,7 @@ const router = new Router({
name: 'login',
component: Login,
props: {
- appConfig: config.appConfig,
+ appConfig,
},
beforeEnter: (to, from, next) => {
if (isAuthenticated()) router.push({ path: '/' });
diff --git a/src/utils/ConfigAccumalator.js b/src/utils/ConfigAccumalator.js
new file mode 100644
index 00000000..2ecec4ac
--- /dev/null
+++ b/src/utils/ConfigAccumalator.js
@@ -0,0 +1,58 @@
+/**
+ * Reads the users config from `conf.yml`, and combines it with any local preferences
+ * Also ensures that any missing attributes are populated with defaults, and the
+ * object is structurally sound, to avoid any error if the user is missing something
+ * The main config object is make up of three parts: appConfig, pageInfo and sections
+ */
+import Defaults, { localStorageKeys } from '@/utils/defaults';
+import conf from '../../public/conf.yml';
+
+export const appConfig = (() => {
+ if (localStorage[localStorageKeys.APP_CONFIG]) {
+ return JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
+ } else if (conf.appConfig) {
+ return conf.appConfig;
+ } else {
+ return Defaults.appConfig;
+ }
+})();
+
+export const pageInfo = (() => {
+ const defaults = Defaults.pageInfo;
+ let localPageInfo;
+ try {
+ localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]);
+ } catch (e) {
+ localPageInfo = {};
+ }
+ const pi = conf.pageInfo || defaults; // The page info object to return
+ pi.title = localPageInfo.title || conf.pageInfo.title || defaults.title;
+ pi.description = localPageInfo.description || conf.pageInfo.description || defaults.description;
+ pi.navLinks = localPageInfo.navLinks || conf.pageInfo.navLinks || defaults.navLinks;
+ pi.footerText = localPageInfo.footerText || conf.pageInfo.footerText || defaults.footerText;
+ return pi;
+})();
+
+export const sections = (() => {
+ // If the user has stored sections in local storage, return those
+ const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
+ if (localSections) {
+ try {
+ const json = JSON.parse(localSections);
+ if (json.length >= 1) return json;
+ } catch (e) {
+ // The data in local storage has been malformed, will return conf.sections instead
+ }
+ }
+ // If the function hasn't yet returned, then return the config file sections
+ return conf.sections;
+})();
+
+export const config = (() => {
+ const result = {
+ appConfig,
+ pageInfo,
+ sections,
+ };
+ return result;
+})();
diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json
index 748b649d..7c8f5035 100644
--- a/src/utils/ConfigSchema.json
+++ b/src/utils/ConfigSchema.json
@@ -95,6 +95,11 @@
"default": false,
"description": "Display a loading screen when the app is launched"
},
+ "statusCheck": {
+ "type": "boolean",
+ "default": false,
+ "description": "Displays an online/ offline status for each of your services"
+ },
"auth": {
"type": "array",
"description": "Usernames and hashed credentials for frontend authentication",
@@ -256,6 +261,11 @@
"provider": {
"type": "string",
"description": "Provider name, e.g. Microsoft"
+ },
+ "statusCheck": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether or not to display online/ offline status for this service. Will override appConfig.statusCheck"
}
}
}