Add UI for health checks (broken links) (#2420)
Refs on https://github.com/openhab/openhab-core/pull/4115. This is the starting point for a UI that shows issues with the users installation, the first function is broken links. --------- Also-by: Florian Hotze <florianh_dev@icloud.com> Signed-off-by: Arne Seime <arne.seime@gmail.com>pull/2638/head
parent
e9210dc8c2
commit
32688a562e
|
@ -39,6 +39,10 @@
|
|||
</f7-list-item>
|
||||
<li v-if="showSettingsSubmenu">
|
||||
<ul class="menu-sublinks">
|
||||
<f7-list-item v-if="$store.getters.apiEndpoint('links')" link="/settings/health/" title="Health checks" view=".view-main" panel-close :animate="false" no-chevron
|
||||
:class="{ currentsection: currentUrl.indexOf('/settings/health') === 0 }">
|
||||
<f7-icon slot="media" f7="heart" color="gray" />
|
||||
</f7-list-item>
|
||||
<f7-list-item v-if="$store.getters.apiEndpoint('things')" link="/settings/things/" title="Things" view=".view-main" panel-close :animate="false" no-chevron
|
||||
:class="{ currentsection: currentUrl.indexOf('/settings/things') === 0 }">
|
||||
<f7-icon slot="media" f7="lightbulb" color="gray" />
|
||||
|
|
|
@ -23,6 +23,8 @@ const ItemEditPage = () => import(/* webpackChunkName: "admin-config" */ '../pag
|
|||
const ItemMetadataEditPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/items/metadata/item-metadata-edit.vue')
|
||||
const ItemsAddFromTextualDefinition = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/items/parser/items-add-from-textual-definition.vue')
|
||||
|
||||
const HealthOverviewPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/health/health-overview.vue')
|
||||
const HealthOrphanLinksPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/health/health-orphanlinks.vue')
|
||||
const ThingsListPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/things/things-list.vue')
|
||||
const ThingDetailsPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/things/thing-details.vue')
|
||||
const AddThingChooseBindingPage = () => import(/* webpackChunkName: "admin-config" */ '../pages/settings/things/add/choose-binding.vue')
|
||||
|
@ -67,7 +69,7 @@ const SetupWizardPage = () => import(/* webpackChunkName: "setup-wizard" */ '../
|
|||
|
||||
const checkDirtyBeforeLeave = function (routeTo, routeFrom, resolve, reject) {
|
||||
if (this.currentPageEl && this.currentPageEl.__vue__ && this.currentPageEl.__vue__.$parent && this.currentPageEl.__vue__.$parent.beforeLeave &&
|
||||
!routeTo.path.startsWith(routeFrom.path)) {
|
||||
!routeTo.path.startsWith(routeFrom.path)) {
|
||||
this.currentPageEl.__vue__.$parent.beforeLeave(this, routeTo, routeFrom, resolve, reject)
|
||||
} else {
|
||||
resolve()
|
||||
|
@ -77,11 +79,17 @@ const checkDirtyBeforeLeave = function (routeTo, routeFrom, resolve, reject) {
|
|||
const loadAsync = (page, props) => {
|
||||
return (routeTo, routeFrom, resolve, reject) => {
|
||||
if (!props) {
|
||||
page().then((c) => { resolve({ component: c.default }) })
|
||||
page().then((c) => {
|
||||
resolve({ component: c.default })
|
||||
})
|
||||
} else if (typeof props === 'object') {
|
||||
page().then((c) => { resolve({ component: c.default }, { props }) })
|
||||
page().then((c) => {
|
||||
resolve({ component: c.default }, { props })
|
||||
})
|
||||
} else if (typeof props === 'function') {
|
||||
page().then((c) => { resolve({ component: c.default }, { props: props(routeTo, routeFrom, resolve, reject) }) })
|
||||
page().then((c) => {
|
||||
resolve({ component: c.default }, { props: props(routeTo, routeFrom, resolve, reject) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +224,9 @@ export default [
|
|||
beforeEnter: [enforceAdminForRoute],
|
||||
beforeLeave: [checkDirtyBeforeLeave],
|
||||
async: (routeTo, routeFrom, resolve, reject) => {
|
||||
PageEditors[routeTo.params.type]().then((c) => { resolve({ component: c.default }, (routeTo.params.uid === 'add') ? { props: { createMode: true } } : {}) })
|
||||
PageEditors[routeTo.params.type]().then((c) => {
|
||||
resolve({ component: c.default }, (routeTo.params.uid === 'add') ? { props: { createMode: true } } : {})
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -232,6 +242,18 @@ export default [
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'health',
|
||||
beforeEnter: [enforceAdminForRoute],
|
||||
async: loadAsync(HealthOverviewPage),
|
||||
routes: [
|
||||
{
|
||||
path: 'orphanlinks',
|
||||
beforeEnter: [enforceAdminForRoute],
|
||||
async: loadAsync(HealthOrphanLinksPage)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'things/',
|
||||
beforeEnter: [enforceAdminForRoute],
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<f7-page @page:afterin="onPageAfterIn">
|
||||
<f7-navbar title="Orphan links" back-link="Health checks" back-link-url="/settings/health/" back-link-force>
|
||||
<f7-nav-right>
|
||||
<developer-dock-icon />
|
||||
</f7-nav-right>
|
||||
</f7-navbar>
|
||||
|
||||
<f7-block class="block-narrow">
|
||||
<f7-col>
|
||||
<f7-block-footer class="padding-horizontal">
|
||||
Orphan links are items pointing to non-existent thing channels or vica versa.
|
||||
<br>
|
||||
<br>
|
||||
Note that only the links of managed Items can be purged, not links defined
|
||||
in <code>.items</code> files - these must be fixed manually in the corresponding file.
|
||||
The latter are marked with <f7-icon f7="lock_fill" size="1rem" color="gray" />.
|
||||
</f7-block-footer>
|
||||
</f7-col>
|
||||
</f7-block>
|
||||
|
||||
<f7-block class="block-narrow">
|
||||
<!-- skeleton for not ready -->
|
||||
<f7-col v-if="!ready">
|
||||
<f7-block-title> Loading...</f7-block-title>
|
||||
<f7-list contacts-list class="col">
|
||||
<f7-list-group>
|
||||
<f7-list-item media-item v-for="n in 10" :key="n" :class="`skeleton-text skeleton-effect-blink`"
|
||||
title="Type of problem" subtitle="Item name" footer="Channel link" />
|
||||
</f7-list-group>
|
||||
</f7-list>
|
||||
</f7-col>
|
||||
|
||||
<f7-col v-else>
|
||||
<f7-block-title>
|
||||
{{ orphanLinks.length }} orphan links found
|
||||
</f7-block-title>
|
||||
<f7-list class="col" contacts-list>
|
||||
<f7-list-item v-for="orphanLink in orphanLinks" :key="orphanLink.itemChannelLink.channelUID" media-item
|
||||
:link="getLinkForProblem(orphanLink)" :title="'Problem: ' + orphanLinkProblemExplanation[orphanLink.problem]"
|
||||
:subtitle="'Item name: ' + orphanLink.itemChannelLink.itemName"
|
||||
:footer="'Channel UID: ' + orphanLink.itemChannelLink.channelUID">
|
||||
<f7-icon v-if="!orphanLink.itemChannelLink.editable" slot="after-title" f7="lock_fill" size="1rem" color="gray" />
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-col>
|
||||
</f7-block>
|
||||
<f7-block class="block-narrow">
|
||||
<f7-col>
|
||||
<f7-list>
|
||||
<f7-list-button color="red" @click="purgeAllManaged()">
|
||||
Purge all managed links (will purge {{ purgeableLinksCount }} managed links)
|
||||
</f7-list-button>
|
||||
</f7-list>
|
||||
</f7-col>
|
||||
</f7-block>
|
||||
</f7-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
ready: false,
|
||||
loading: false,
|
||||
orphanLinks: [],
|
||||
|
||||
orphanLinkProblemExplanation: {
|
||||
THING_CHANNEL_MISSING: 'The item is linked to a thing channel that does not exist',
|
||||
ITEM_MISSING: 'The item does not exist',
|
||||
ITEM_AND_THING_CHANNEL_MISSING: 'Neither the item nor thing channel exists'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
purgeableLinksCount () {
|
||||
return this.orphanLinks.filter((l) => l.itemChannelLink.editable).length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onPageAfterIn () {
|
||||
this.load()
|
||||
},
|
||||
load () {
|
||||
this.loading = true
|
||||
this.$oh.api.get('/rest/links/orphans').then((data) => {
|
||||
this.orphanLinks = data
|
||||
this.loading = false
|
||||
this.ready = true
|
||||
})
|
||||
},
|
||||
getLinkForProblem (orphanLink) {
|
||||
if (orphanLink.problem === 'THING_CHANNEL_MISSING') {
|
||||
return '/settings/items/' + orphanLink.itemChannelLink.itemName
|
||||
}
|
||||
return null
|
||||
},
|
||||
purgeAllManaged () {
|
||||
this.loading = true
|
||||
this.$oh.api.post('/rest/links/purge').catch((e) => {
|
||||
// ignore parseerror due to empty response
|
||||
if (e === 'parseerror') return
|
||||
console.error(e)
|
||||
}).finally(() => {
|
||||
this.load()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<f7-page @page:afterin="onPageAfterIn">
|
||||
<f7-navbar title="Health checks" back-link="Settings" back-link-url="/settings/" back-link-force>
|
||||
<f7-nav-right>
|
||||
<developer-dock-icon />
|
||||
</f7-nav-right>
|
||||
</f7-navbar>
|
||||
|
||||
<f7-block class="block-narrow">
|
||||
<f7-col>
|
||||
<f7-block-footer class="padding-horizontal">
|
||||
This page provides information about potential issues with your openHAB setup.
|
||||
<br>
|
||||
It is recommended to fix these issues to ensure a stable and reliable system.
|
||||
</f7-block-footer>
|
||||
</f7-col>
|
||||
</f7-block>
|
||||
|
||||
<f7-block class="block-narrow">
|
||||
<f7-col>
|
||||
<f7-list media-list>
|
||||
<f7-list-item
|
||||
media-item
|
||||
link="orphanlinks/"
|
||||
title="Orphan Links"
|
||||
:badge="orphanLinksCount > 0 ? orphanLinksCount : undefined"
|
||||
:after="orphanLinksCount > 0 ? undefined : orphanLinksCount"
|
||||
:badge-color="orphanLinksCount ? 'red' : 'blue'"
|
||||
:footer="objectsSubtitles.orphanLinks">
|
||||
<f7-icon slot="media" f7="link" color="gray" />
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-col>
|
||||
</f7-block>
|
||||
</f7-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
objectsSubtitles: {
|
||||
orphanLinks:
|
||||
'Items pointing to non-existent thing channels or vica versa'
|
||||
},
|
||||
orphanLinksCount: '',
|
||||
|
||||
expandedTypes: {
|
||||
systemSettings: this.$f7.width >= 1450
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
apiEndpoints () {
|
||||
return this.$store.state.apiEndpoints
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
apiEndpoints () {
|
||||
this.loadCounters()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadCounters () {
|
||||
if (!this.apiEndpoints) return
|
||||
if (this.$store.getters.apiEndpoint('links')) {
|
||||
this.$oh.api.get('/rest/links/orphans').then((data) => {
|
||||
this.orphanLinksCount = data.length.toString()
|
||||
})
|
||||
}
|
||||
},
|
||||
onPageAfterIn () {
|
||||
this.loadCounters()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -23,6 +23,17 @@
|
|||
<f7-col :class="!addonsLoaded || (addonsLoaded && addonsInstalled.length > 0) ? 'settings-col' : ''" width="100" medium="50">
|
||||
<f7-block-title>Configuration</f7-block-title>
|
||||
<f7-list media-list class="search-list">
|
||||
<f7-list-item
|
||||
v-if="$store.getters.apiEndpoint('links')"
|
||||
media-item
|
||||
link="health/"
|
||||
title="Health check"
|
||||
:badge="(healthCount > 0) ? healthCount : undefined"
|
||||
:after="(healthCount > 0) ? undefined : 0"
|
||||
badge-color="red"
|
||||
:footer="objectsSubtitles.health">
|
||||
<f7-icon slot="media" f7="heart" color="gray" />
|
||||
</f7-list-item>
|
||||
<f7-list-item
|
||||
v-if="$store.getters.apiEndpoint('things')"
|
||||
media-item
|
||||
|
@ -207,6 +218,7 @@ export default {
|
|||
addonsServices: [],
|
||||
systemServices: [],
|
||||
objectsSubtitles: {
|
||||
health: 'Manage detected system health issues',
|
||||
things: 'Manage the physical layer',
|
||||
model: 'The semantic model of your home',
|
||||
items: 'Manage the functional layer',
|
||||
|
@ -218,6 +230,7 @@ export default {
|
|||
scripts: 'Rules dedicated to running code',
|
||||
schedule: 'View upcoming time-based rules'
|
||||
},
|
||||
healthCount: '',
|
||||
inboxCount: '',
|
||||
thingsCount: '',
|
||||
itemsCount: '',
|
||||
|
@ -291,6 +304,7 @@ export default {
|
|||
},
|
||||
loadCounters () {
|
||||
if (!this.apiEndpoints) return
|
||||
if (this.$store.getters.apiEndpoint('links')) this.$oh.api.get('/rest/links/orphans').then((data) => { this.healthCount = data.length.toString() })
|
||||
if (this.$store.getters.apiEndpoint('inbox')) this.$oh.api.get('/rest/inbox?includeIgnored=false').then((data) => { this.inboxCount = data.filter((e) => e.flag === 'NEW').length.toString() })
|
||||
if (this.$store.getters.apiEndpoint('things')) this.$oh.api.get('/rest/things?staticDataOnly=true').then((data) => { this.thingsCount = data.length.toString() })
|
||||
if (this.$store.getters.apiEndpoint('items')) this.$oh.api.get('/rest/items?staticDataOnly=true').then((data) => { this.itemsCount = data.length.toString() })
|
||||
|
|
Loading…
Reference in New Issue