mirror of https://github.com/Lissy93/dashy.git
Adds an (optional) status check feature, plus some refactoring
This commit is contained in:
parent
195d433f75
commit
0b1f66b7b7
42
src/App.vue
42
src/App.vue
|
@ -12,7 +12,7 @@ import Header from '@/components/PageStrcture/Header.vue';
|
||||||
import Footer from '@/components/PageStrcture/Footer.vue';
|
import Footer from '@/components/PageStrcture/Footer.vue';
|
||||||
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
|
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
|
||||||
import Defaults, { localStorageKeys, splashScreenTime } from '@/utils/defaults';
|
import Defaults, { localStorageKeys, splashScreenTime } from '@/utils/defaults';
|
||||||
import conf from '../public/conf.yml';
|
import { config, appConfig, pageInfo } from '@/utils/ConfigAccumalator';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
|
@ -21,48 +21,18 @@ export default {
|
||||||
Footer,
|
Footer,
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
config,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// pageInfo: this.getPageInfo(conf.pageInfo),
|
|
||||||
showFooter: Defaults.visibleComponents.footer,
|
showFooter: Defaults.visibleComponents.footer,
|
||||||
isLoading: true,
|
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: {
|
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() {
|
getFooterText() {
|
||||||
if (this.pageInfo && this.pageInfo.footerText) {
|
if (this.pageInfo && this.pageInfo.footerText) {
|
||||||
return this.pageInfo.footerText;
|
return this.pageInfo.footerText;
|
||||||
|
|
|
@ -20,12 +20,20 @@
|
||||||
<!-- Small icon, showing opening method on hover -->
|
<!-- Small icon, showing opening method on hover -->
|
||||||
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon" :openingMethod="target"
|
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon" :openingMethod="target"
|
||||||
:position="itemSize === 'medium'? 'bottom right' : 'top right'"/>
|
:position="itemSize === 'medium'? 'bottom right' : 'top right'"/>
|
||||||
|
<StatusIndicator
|
||||||
|
class="status-indicator"
|
||||||
|
v-if="enableStatusCheck"
|
||||||
|
:statusSuccess="statusResponse ? statusResponse.successStatus : undefined"
|
||||||
|
:statusText="statusResponse ? statusResponse.message : undefined"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
||||||
import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon';
|
import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon';
|
||||||
|
import StatusIndicator from '@/components/LinkItems/StatusIndicator';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Item',
|
name: 'Item',
|
||||||
|
@ -44,6 +52,7 @@ export default {
|
||||||
validator: (value) => ['newtab', 'sametab', 'iframe'].indexOf(value) !== -1,
|
validator: (value) => ['newtab', 'sametab', 'iframe'].indexOf(value) !== -1,
|
||||||
},
|
},
|
||||||
itemSize: String,
|
itemSize: String,
|
||||||
|
enableStatusCheck: Boolean,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -52,11 +61,13 @@ export default {
|
||||||
color: this.color,
|
color: this.color,
|
||||||
background: this.backgroundColor,
|
background: this.backgroundColor,
|
||||||
},
|
},
|
||||||
|
statusResponse: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
ItemOpenMethodIcon,
|
ItemOpenMethodIcon,
|
||||||
|
StatusIndicator,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/* Called when an item is clicked, manages the opening of iframe & resets the search field */
|
/* Called when an item is clicked, manages the opening of iframe & resets the search field */
|
||||||
|
@ -88,9 +99,11 @@ export default {
|
||||||
trigger: 'hover focus',
|
trigger: 'hover focus',
|
||||||
hideOnTargetClick: true,
|
hideOnTargetClick: true,
|
||||||
html: false,
|
html: false,
|
||||||
|
placement: this.statusResponse ? 'left' : 'auto',
|
||||||
delay: { show: 600, hide: 200 },
|
delay: { show: 600, hide: 200 },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/* Used by certain themes, which display an icon with animated CSS */
|
||||||
getUnicodeOpeningIcon() {
|
getUnicodeOpeningIcon() {
|
||||||
switch (this.target) {
|
switch (this.target) {
|
||||||
case 'newtab': return '"\\f360"';
|
case 'newtab': return '"\\f360"';
|
||||||
|
@ -99,9 +112,24 @@ export default {
|
||||||
default: return '"\\f054"';
|
default: return '"\\f054"';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
checkWebsiteStatus() {
|
||||||
|
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||||
|
const endpoint = `${baseUrl}/ping?url=${this.url}`;
|
||||||
|
axios.get(endpoint)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data) this.statusResponse = response.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.statusResponse = {
|
||||||
|
statusText: 'Failed to make request',
|
||||||
|
statusSuccess: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.manageTitleEllipse();
|
this.manageTitleEllipse();
|
||||||
|
if (this.enableStatusCheck) this.checkWebsiteStatus();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -122,6 +150,7 @@ export default {
|
||||||
box-shadow: var(--item-shadow);
|
box-shadow: var(--item-shadow);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: var(--item-hover-shadow);
|
box-shadow: var(--item-hover-shadow);
|
||||||
background: var(--item-background-hover);
|
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 {
|
.opening-method-icon {
|
||||||
display: none; // Hidden by default, visible on hover
|
display: none; // Hidden by default, visible on hover
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
:color="item.color"
|
:color="item.color"
|
||||||
:backgroundColor="item.backgroundColor"
|
:backgroundColor="item.backgroundColor"
|
||||||
:itemSize="newItemSize"
|
:itemSize="newItemSize"
|
||||||
|
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
|
||||||
@itemClicked="$emit('itemClicked')"
|
@itemClicked="$emit('itemClicked')"
|
||||||
@triggerModal="triggerModal"
|
@triggerModal="triggerModal"
|
||||||
/>
|
/>
|
||||||
|
@ -49,6 +50,7 @@ import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ItemGroup',
|
name: 'ItemGroup',
|
||||||
|
inject: ['config'],
|
||||||
props: {
|
props: {
|
||||||
groupId: String,
|
groupId: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -92,6 +94,10 @@ export default {
|
||||||
modalChanged(changedTo) {
|
modalChanged(changedTo) {
|
||||||
this.$emit('change-modal-visibility', changedTo);
|
this.$emit('change-modal-visibility', changedTo);
|
||||||
},
|
},
|
||||||
|
shouldEnableStatusCheck(itemPreference) {
|
||||||
|
const globalPreference = this.config.appConfig.statusCheck || false;
|
||||||
|
return itemPreference !== undefined ? itemPreference : globalPreference;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-tooltip="{
|
||||||
|
content: statusText || otherStatusText, classes: ['status-tooltip', `tip-${color()}`] }"
|
||||||
|
class="indicator"
|
||||||
|
@click="showToast()">
|
||||||
|
<div :class="`dot dot-${color()}`">
|
||||||
|
<span><span></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'StatusIndicator',
|
||||||
|
props: {
|
||||||
|
statusText: String,
|
||||||
|
statusSuccess: Boolean,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* Returns a color, based on success status */
|
||||||
|
color() {
|
||||||
|
switch (this.statusSuccess) {
|
||||||
|
case undefined: return ((new Date() - this.startTime) > 2000) ? 'grey' : 'yellow';
|
||||||
|
case true: return 'green'; // Success!
|
||||||
|
default: return 'red'; // Not success, therefore failure
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
startTime: new Date(), // Used for timeout
|
||||||
|
otherStatusText: 'Checking...', // Used before server has responded
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.statusText) this.otherStatusText = 'Request timed out';
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
padding: 5px;
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
cursor: help;
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.25);
|
||||||
|
filter: saturate(2);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: .75; transform: scale(1); }
|
||||||
|
25% { opacity: 0.75; transform: scale(1); }
|
||||||
|
100% { opacity: 0; transform: scale(1.8); }
|
||||||
|
}
|
||||||
|
@keyframes applyOpacity {
|
||||||
|
50% { opacity: 0.9; }
|
||||||
|
to { opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
animation: applyOpacity 1s ease-in 8s forwards;
|
||||||
|
> span, > span span, > span span:after {
|
||||||
|
animation: pulse 1s linear 0.5s 2;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
&.dot-green {
|
||||||
|
background-color: var(--success);
|
||||||
|
span, span:after {
|
||||||
|
background-color: var(--success);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.dot-red {
|
||||||
|
background-color: var(--danger);
|
||||||
|
span, span:after {
|
||||||
|
background-color: var(--danger);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.dot-yellow {
|
||||||
|
background-color: var(--warning);
|
||||||
|
span, span:after {
|
||||||
|
background-color: var(--warning);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.dot-grey {
|
||||||
|
background-color: var(--medium-grey);
|
||||||
|
span, span:after {
|
||||||
|
background-color: var(--medium-grey);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.status-tooltip {
|
||||||
|
background: var(--background-darker) !important;
|
||||||
|
font-size: 1rem;
|
||||||
|
z-index: 10;
|
||||||
|
&.tip-green { border: 1px solid var(--success); }
|
||||||
|
&.tip-yellow { border: 1px solid var(--warning); }
|
||||||
|
&.tip-red { border: 1px solid var(--danger); }
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,14 @@
|
||||||
import Vue from 'vue';
|
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 VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
|
||||||
import VModal from 'vue-js-modal'; // Modal component
|
import VModal from 'vue-js-modal'; // Modal component
|
||||||
import VSelect from 'vue-select'; // Select dropdown component
|
import VSelect from 'vue-select'; // Select dropdown component
|
||||||
import VTabs from 'vue-material-tabs'; // Tab view component, used on the config page
|
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 Toasted from 'vue-toasted'; // Toast component, used to show confirmation notifications
|
||||||
|
|
||||||
import { toastedOptions } from './utils/defaults';
|
import { toastedOptions } from './utils/defaults';
|
||||||
import App from './App.vue';
|
import Dashy from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import './registerServiceWorker';
|
import './registerServiceWorker';
|
||||||
|
|
||||||
|
@ -20,5 +22,5 @@ Vue.config.productionTip = false;
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
render: (awesome) => awesome(App),
|
render: (awesome) => awesome(Dashy),
|
||||||
}).$mount('#app');
|
}).$mount('#app');
|
||||||
|
|
|
@ -1,36 +1,15 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import Home from './views/Home.vue';
|
|
||||||
import Login from './views/Login.vue';
|
import Home from '@/views/Home.vue';
|
||||||
import conf from '../public/conf.yml'; // Main site configuration
|
import Login from '@/views/Login.vue';
|
||||||
import { pageInfo as defaultPageInfo, localStorageKeys } from './utils/defaults';
|
import { isLoggedIn } from '@/utils/Auth';
|
||||||
import { isLoggedIn } from './utils/Auth';
|
import { appConfig, pageInfo, sections } from '@/utils/ConfigAccumalator';
|
||||||
|
|
||||||
Vue.use(Router);
|
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 isAuthenticated = () => {
|
||||||
const users = config.appConfig.auth;
|
const users = appConfig.auth;
|
||||||
return (!users || isLoggedIn(users));
|
return (!users || isLoggedIn(users));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +19,11 @@ const router = new Router({
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: Home,
|
component: Home,
|
||||||
props: config,
|
props: {
|
||||||
|
appConfig,
|
||||||
|
pageInfo,
|
||||||
|
sections,
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
title: pageInfo.title || 'Home Page',
|
title: pageInfo.title || 'Home Page',
|
||||||
metaTags: [
|
metaTags: [
|
||||||
|
@ -56,7 +39,7 @@ const router = new Router({
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login,
|
component: Login,
|
||||||
props: {
|
props: {
|
||||||
appConfig: config.appConfig,
|
appConfig,
|
||||||
},
|
},
|
||||||
beforeEnter: (to, from, next) => {
|
beforeEnter: (to, from, next) => {
|
||||||
if (isAuthenticated()) router.push({ path: '/' });
|
if (isAuthenticated()) router.push({ path: '/' });
|
||||||
|
|
|
@ -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;
|
||||||
|
})();
|
|
@ -95,6 +95,11 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "Display a loading screen when the app is launched"
|
"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": {
|
"auth": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Usernames and hashed credentials for frontend authentication",
|
"description": "Usernames and hashed credentials for frontend authentication",
|
||||||
|
@ -256,6 +261,11 @@
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Provider name, e.g. Microsoft"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue