Setup wizard: Add primary IP config (#2565)

Add-on finders scan the network for devices supported by OH add-ons to suggest suitable add-ons.
These are presented in the setup-wizard (and add-ons store).

To limit network traffic, especially for IP broadcast and multicast scans,
finders could limit the traffic to one subnet.
This is especially relevant if the setup would be on servers with many
network interfaces or when using Docker. 

This commit adds setting up a primary IP address to the setup,
which will also default the broadcast address accordingly to the primary address.
Querying add-on suggestions is delayed until after this step, 
and some delay is built into the process to allow suggestions finders to scan the network.

See discussion in https://github.com/openhab/openhab-core/pull/4036.

---------

Also-by: Florian Hotze <florianh_dev@icloud.com>
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
pull/2567/head
Mark Herwege 2024-05-07 12:54:30 +02:00 committed by GitHub
parent 3fa319248b
commit 5f3d6daa21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 182 additions and 36 deletions

View File

@ -6,6 +6,7 @@
"setupwizard.skipSetup": "Skip Setup", "setupwizard.skipSetup": "Skip Setup",
"setupwizard.skipSetup.confirm.title": "Skip Setup", "setupwizard.skipSetup.confirm.title": "Skip Setup",
"setupwizard.skipSetup.confirm.message": "Are you sure? Setup saves you time by performing just a few basic configuration tasks. You should only skip it if you know what you're doing.", "setupwizard.skipSetup.confirm.message": "Are you sure? Setup saves you time by performing just a few basic configuration tasks. You should only skip it if you know what you're doing.",
"setupwizard.configureLater": "Configure in Settings Later",
"setupwizard.location.title": "Set your Location", "setupwizard.location.title": "Set your Location",
"setupwizard.location.header1": "Would you like to set your home's location?", "setupwizard.location.header1": "Would you like to set your home's location?",
"setupwizard.location.header2": "It will help determining data dependent on your position, like sunrise/sunset times or the weather.", "setupwizard.location.header2": "It will help determining data dependent on your position, like sunrise/sunset times or the weather.",
@ -17,7 +18,10 @@
"setupwizard.location.retrieveFromDevice.notAvailable.message": "Geolocation is not available", "setupwizard.location.retrieveFromDevice.notAvailable.message": "Geolocation is not available",
"setupwizard.location.footer": "This will ask your device for the permission to use its current location, only to help you fill in your current latitude and longitude above. You can revoke the permission afterwards.", "setupwizard.location.footer": "This will ask your device for the permission to use its current location, only to help you fill in your current latitude and longitude above. You can revoke the permission afterwards.",
"setupwizard.location.setLocation": "Set Location", "setupwizard.location.setLocation": "Set Location",
"setupwizard.location.configureLater": "Configure in Settings Later", "setupwizard.network.title": "Select Primary Network",
"setupwizard.network.header1": "openHAB by default restricts some discovery broadcast network traffic to your primary network.",
"setupwizard.network.header2": "Select your server's primary IP address that is part of your primary network.",
"setupwizard.network.setNetwork": "Set Primary Network",
"setupwizard.persistence.title": "Choose Persistence Add-ons", "setupwizard.persistence.title": "Choose Persistence Add-ons",
"setupwizard.persistence.header1": "openHAB relies on persistence add-ons to store and retrieve historic states.", "setupwizard.persistence.header1": "openHAB relies on persistence add-ons to store and retrieve historic states.",
"setupwizard.persistence.header2": "Select persistence add-ons to match the functionality you require.", "setupwizard.persistence.header2": "Select persistence add-ons to match the functionality you require.",
@ -46,6 +50,7 @@
"setupwizard.addons.installingAddon": "Installing Add-on: {addon}", "setupwizard.addons.installingAddon": "Installing Add-on: {addon}",
"setupwizard.addons.progress": "{current} of {total}", "setupwizard.addons.progress": "{current} of {total}",
"setupwizard.addons.pleaseWait": "Please Wait...", "setupwizard.addons.pleaseWait": "Please Wait...",
"setupwizard.addons.suggestionsWaitMessage": "We are searching the most relevant add-ons for you...",
"setupwizard.addons.waitMessage": "It may take a few minutes to install the add-ons you selected.", "setupwizard.addons.waitMessage": "It may take a few minutes to install the add-ons you selected.",
"setupwizard.welcome.title": "Welcome to openHAB!", "setupwizard.welcome.title": "Welcome to openHAB!",
"setupwizard.welcome.bindingsInstalled": "You have installed one or more bindings. Things provided by these bindings will appear in the Things Inbox. You can accept and further configure from there.", "setupwizard.welcome.bindingsInstalled": "You have installed one or more bindings. Things provided by these bindings will appear in the Things Inbox. You can accept and further configure from there.",

View File

@ -173,6 +173,18 @@ export default {
} }
} }
}) })
// Add event listener for locale change
this.$f7.on('localeChange', () => {
if (this.autocompleteAddons) {
this.autocompleteAddons.params.pageTitle = this.$t('setupwizard.addons.selectAddons')
this.autocompleteAddons.params.searchbarPlaceholder = this.$t('setupwizard.addons.selectAddons.placeholder')
this.autocompleteAddons.params.searchbarDisableText = this.$t('dialogs.cancel')
this.autocompleteAddons.params.popupCloseLinkText = this.$t('dialogs.close')
this.autocompleteAddons.params.pageBackLinkText = this.$t('dialogs.back')
this.autocompleteAddons.params.notFoundText = this.$t('dialogs.search.nothingFound')
}
})
} }
} }
</script> </script>

View File

@ -92,10 +92,40 @@
<small v-t="'setupwizard.location.footer'" /> <small v-t="'setupwizard.location.footer'" />
</f7-block-footer> </f7-block-footer>
</f7-block> </f7-block>
<f7-block class="display-flex flex-direction-column padding"> <f7-block class="display-flex flex-direction-column padding" v-if="networksReady">
<div> <div>
<f7-button v-if="location" large fill color="blue" :text="$t('setupwizard.location.setLocation')" @click="setLocation" /> <f7-button v-if="location" large fill color="blue" :text="$t('setupwizard.location.setLocation')" @click="setLocation" />
<f7-button large color="blue" :text="$t('setupwizard.location.configureLater')" class="margin-top" @click="skipLocation" /> <f7-button large color="blue" :text="$t('setupwizard.configureLater')" class="margin-top" @click="skipLocation" />
</div>
</f7-block>
</f7-tab>
<f7-tab id="network" ref="network">
<f7-block>
<f7-link
icon-ios="f7:arrow_left"
icon-aurora="f7:arrow_left"
icon-md="material:arrow_back"
tab-link="#location"
color="blue"
tab-link-active />
<f7-login-screen-title>
<div class="padding">
<f7-icon size="48" color="blue" f7="wifi" />
</div>
{{ $t('setupwizard.network.title') }}
</f7-login-screen-title>
</f7-block>
<f7-block strong>
{{ $t('setupwizard.network.header1') }} {{ $t('setupwizard.network.header2') }}
</f7-block>
<f7-list>
<parameter-options class="network" v-if="networksReady" :config-description="networkConfigDescription" :value="network" @input="(value) => network = value" />
</f7-list>
<f7-block class="display-flex flex-direction-column padding">
<div>
<f7-button large fill color="blue" :text="$t('setupwizard.network.setNetwork')" @click="setNetwork" />
<f7-button large color="blue" :text="$t('setupwizard.configureLater')" class="margin-top" @click="skipNetwork" />
</div> </div>
</f7-block> </f7-block>
</f7-tab> </f7-tab>
@ -106,7 +136,7 @@
icon-ios="f7:arrow_left" icon-ios="f7:arrow_left"
icon-aurora="f7:arrow_left" icon-aurora="f7:arrow_left"
icon-md="material:arrow_back" icon-md="material:arrow_back"
tab-link="#location" :tab-link="(networkConfigDescription && networkConfigDescription.options && networkConfigDescription.options.length > 1) ? '#network' : '#location'"
color="blue" color="blue"
tab-link-active /> tab-link-active />
<f7-login-screen-title> <f7-login-screen-title>
@ -120,7 +150,13 @@
{{ $t('setupwizard.persistence.header1') }} {{ $t('setupwizard.persistence.header2') }} {{ $t('setupwizard.persistence.header1') }} {{ $t('setupwizard.persistence.header2') }}
</f7-block> </f7-block>
<f7-block style="margin-top: 0; margin-bottom: 2em"> <f7-block style="margin-top: 0; margin-bottom: 2em">
<addons-setup-wizard v-if="addonsReady && recommendedAddonsByType('persistence').length" <f7-block v-if="!addonSuggestionsReady">
<div class="display-flex justify-content-center margin-bottom">
<f7-progressbar id="suggestions-progress-bar-persistence" :progress="0" />
</div>
<div v-t="'setupwizard.addons.suggestionsWaitMessage'" />
</f7-block>
<addons-setup-wizard v-if="addonSuggestionsReady && recommendedAddonsByType('persistence').length"
:addons="recommendedAddonsByType('persistence')" :addons="recommendedAddonsByType('persistence')"
:preSelectedAddons="selectedAddons" :preSelectedAddons="selectedAddons"
@update="updateAddonSelection(recommendedAddonsByType('persistence'), $event)" /> @update="updateAddonSelection(recommendedAddonsByType('persistence'), $event)" />
@ -128,7 +164,10 @@
<small v-t="'setupwizard.persistence.footer'" /> <small v-t="'setupwizard.persistence.footer'" />
</f7-block-footer> </f7-block-footer>
<div> <div>
<f7-button v-if="selectedAddons.length > 0" large fill color="blue" :text="$t('setupwizard.persistence.install')" @click="selectPersistence" /> <f7-button v-if="addonSuggestionsReady && (selectedAddons.length > 0)"
large fill color="blue"
:text="$t('setupwizard.persistence.install')"
@click="selectPersistence" />
<f7-button large color="blue" :text="$t('setupwizard.persistence.installLater')" class="margin-top" @click="skipPersistence" /> <f7-button large color="blue" :text="$t('setupwizard.persistence.installLater')" class="margin-top" @click="skipPersistence" />
</div> </div>
</f7-block> </f7-block>
@ -155,7 +194,13 @@
<a class="text-color-blue external" target="_blank" href="https://www.openhab.org/addons/" v-t="'setupwizard.addons.browseAddonsOnWebsite'" /> <a class="text-color-blue external" target="_blank" href="https://www.openhab.org/addons/" v-t="'setupwizard.addons.browseAddonsOnWebsite'" />
</f7-block> </f7-block>
<f7-block class="padding"> <f7-block class="padding">
<addons-setup-wizard v-if="addonsReady" <f7-block v-if="!addonSuggestionsReady">
<div class="display-flex justify-content-center margin-bottom">
<f7-progressbar id="suggestions-progress-bar-addons" :progress="0" />
</div>
<div v-t="'setupwizard.addons.suggestionsWaitMessage'" />
</f7-block>
<addons-setup-wizard v-if="addonSuggestionsReady && mainAddons.length"
:enableAddonSelection="true" :enableAddonSelection="true"
:addons="mainAddons" :addons="mainAddons"
:preSelectedAddons="selectedAddons" :preSelectedAddons="selectedAddons"
@ -164,7 +209,7 @@
<small v-t="'setupwizard.addons.footer'" /> <small v-t="'setupwizard.addons.footer'" />
</f7-block-footer> </f7-block-footer>
<div> <div>
<f7-button v-if="toInstallAddons.filter(a => (!preSelectedAddon(a) && !a.installed)).length > 0" <f7-button v-if="addonSuggestionsReady && (toInstallAddons.filter(a => (!preSelectedAddon(a) && !a.installed)).length > 0)"
large fill color="blue" large fill color="blue"
:text="$tc('setupwizard.addons.installAddons', toInstallAddons.filter(a => (!preSelectedAddon(a) && !a.installed)).length)" :text="$tc('setupwizard.addons.installAddons', toInstallAddons.filter(a => (!preSelectedAddon(a) && !a.installed)).length)"
@click="installAddons" /> @click="installAddons" />
@ -229,6 +274,12 @@
width 240px width 240px
.page-content .page-content
margin-top inherit margin-top inherit
.network
.block-header
.item-label
text-align left
margin-left 0 !important
margin-right 0 !important
.tab-active .tab-active
scroll-snap-align start scroll-snap-align start
@ -247,19 +298,18 @@
<script> <script>
import i18n from '@/components/i18n-mixin' import i18n from '@/components/i18n-mixin'
import { loadLocaleMessages } from '@/js/i18n' import { loadLocaleMessages } from '@/js/i18n'
import AddonsSetupWizard from '@/components/addons/addons-setup-wizard' import AddonsSetupWizard from '@/components/addons/addons-setup-wizard'
export default { export default {
mixins: [i18n], mixins: [i18n],
components: { components: {
'parameter-location': () => import('@/components/config/controls/parameter-location.vue'), 'parameter-location': () => import('@/components/config/controls/parameter-location.vue'),
'parameter-options': () => import('@/components/config/controls/parameter-options.vue'),
AddonsSetupWizard AddonsSetupWizard
}, },
data () { data () {
return { return {
i18nReady: false, i18nReady: false,
addonsReady: false,
availableLanguages: null, availableLanguages: null,
availableRegions: null, availableRegions: null,
availableTimezones: null, availableTimezones: null,
@ -267,7 +317,10 @@ export default {
region: null, region: null,
timezone: null, timezone: null,
location: null, location: null,
autocompleteAddons: null, networksReady: false,
networkConfigDescription: null,
network: null,
addonSuggestionsReady: false,
addons: [], addons: [],
// all recommended addons, pre-defined // all recommended addons, pre-defined
recommendedAddons: ['persistence-rrd4j', 'persistence-mapdb', 'automation-jsscripting', 'ui-basic', 'binding-astro'], recommendedAddons: ['persistence-rrd4j', 'persistence-mapdb', 'automation-jsscripting', 'ui-basic', 'binding-astro'],
@ -323,14 +376,6 @@ export default {
timezone: this.timezone timezone: this.timezone
}).then(() => { }).then(() => {
this.$f7.emit('localeChange') this.$f7.emit('localeChange')
if (this.autocompleteAddons) {
this.autocompleteAddons.params.pageTitle = this.$t('setupwizard.addons.selectAddons')
this.autocompleteAddons.params.searchbarPlaceholder = this.$t('setupwizard.addons.selectAddons.placeholder')
this.autocompleteAddons.params.searchbarDisableText = this.$t('dialogs.cancel')
this.autocompleteAddons.params.popupCloseLinkText = this.$t('dialogs.close')
this.autocompleteAddons.params.pageBackLinkText = this.$t('dialogs.back')
this.autocompleteAddons.params.notFoundText = this.$t('dialogs.search.nothingFound')
}
this.$refs.location.show() this.$refs.location.show()
}) })
}, },
@ -363,27 +408,102 @@ export default {
this.$oh.api.put('/rest/services/org.openhab.i18n/config', { this.$oh.api.put('/rest/services/org.openhab.i18n/config', {
location: this.location location: this.location
}).then(() => { }).then(() => {
this.showPersistence() this.showNetwork()
}) })
}, },
skipLocation () { skipLocation () {
this.showNetwork()
},
showNetwork () {
if (this.networkConfigDescription?.options?.length > 1) {
this.$refs.network.show()
} else {
this.skipNetwork()
}
},
setNetwork () {
this.$oh.api.put('/rest/services/org.openhab.network/config', {
primaryAddress: this.network
}).then(() => {
this.addonSuggestionsReady = false
this.getSuggestedAddons()
this.showPersistence()
})
},
skipNetwork () {
this.getSuggestedAddons()
this.showPersistence() this.showPersistence()
}, },
showPersistence () { showPersistence () {
this.updateAddonSelection([], this.recommendedAddonsByType('persistence')) if (this.addonSuggestionsReady) {
this.updateAddonSelection([], this.recommendedAddonsByType('persistence'))
} else {
this.$f7.once('addon-suggestions-ready', () => {
this.updateAddonSelection([], this.recommendedAddonsByType('persistence'))
})
}
this.$refs.persistence.show() this.$refs.persistence.show()
}, },
selectPersistence () { selectPersistence () {
this.showAddons() this.showAddons()
}, },
skipPersistence () { skipPersistence () {
this.updateAddonSelection(this.recommendedAddonsByType('persistence'), []) if (this.addonSuggestionsReady) {
this.updateAddonSelection(this.recommendedAddonsByType('persistence'), [])
} else {
this.$f7.once('addon-suggestions-ready', () => {
this.updateAddonSelection(this.recommendedAddonsByType('persistence'), [])
})
}
this.showAddons() this.showAddons()
}, },
showAddons () { showAddons () {
this.updateAddonSelection([], this.selectedAddons.filter(a => !this.preSelectedAddon(a))) if (this.addonSuggestionsReady) {
this.updateAddonSelection([], this.selectedAddons.filter(a => !this.preSelectedAddon(a)))
} else {
this.$f7.once('addon-suggestions-ready', () => {
this.updateAddonSelection([], this.selectedAddons.filter(a => !this.preSelectedAddon(a)))
})
}
this.$refs.addons.show() this.$refs.addons.show()
}, },
/**
* Load the list of suggested add-ons.
* Emits <code>addon-suggestions-ready</code> event once add-on suggestions are ready.
*
* @returns {Promise} resolves quickly if <code>this.addonSuggestionsReady</code> is <code>true</code>
*/
getSuggestedAddons () {
if (this.addonSuggestionsReady) return Promise.resolve()
// wait 10 seconds for suggestions to refresh after network scan
return new Promise(() => {
this.$f7.progressbar.set('#suggestions-progress-bar-persistence', 0)
this.$f7.progressbar.set('#suggestions-progress-bar-addons', 0)
let progress = 0
const self = this
function loading () {
setTimeout(() => {
const progressBefore = progress
progress += 10
self.$f7.progressbar.set('#suggestions-progress-bar-persistence', progress)
self.$f7.progressbar.set('#suggestions-progress-bar-addons', progress)
if (progressBefore < 100) {
loading() // keep loading
} else {
self.$oh.api.get('/rest/addons/suggestions').then((suggestions) => {
const suggestedAddons = suggestions.flatMap(s => s.id)
self.selectedAddons = self.addons.filter(a => (self.recommendedAddons.includes(a.uid) || suggestedAddons.includes(a.id)))
.sort((a, b) => a.uid.toUpperCase().localeCompare(b.uid.toUpperCase()))
self.addonSuggestionsReady = true
self.$f7.emit('addon-suggestions-ready')
return Promise.resolve()
})
}
}, 1000)
}
loading()
})
},
preSelectedAddon (addon) { preSelectedAddon (addon) {
return (this.preSelectingAddonTypes.includes(addon.type) || this.preSelectingAddons.includes(addon.uid)) return (this.preSelectingAddonTypes.includes(addon.type) || this.preSelectingAddons.includes(addon.uid))
}, },
@ -443,9 +563,9 @@ export default {
console.log('Installing add-on: ' + addon.uid) console.log('Installing add-on: ' + addon.uid)
progressDialog.setTitle(self.$t('setupwizard.addons.installingAddon', { addon: addon.label })) progressDialog.setTitle(self.$t('setupwizard.addons.installingAddon', { addon: addon.label }))
self.$oh.api.post('/rest/addons/' + addon.uid + '/install', {}, 'text').then((data) => { self.$oh.api.post('/rest/addons/' + addon.uid + '/install', {}, 'text').then(() => {
const checkTimer = setInterval(() => { const checkTimer = setInterval(() => {
checkAddonStatus(addon).then((addon) => { checkAddonStatus(addon).then(() => {
clearInterval(checkTimer) clearInterval(checkTimer)
installNextAddon() installNextAddon()
}).catch(() => { }).catch(() => {
@ -494,14 +614,23 @@ export default {
default: [], default: [],
masonry: null masonry: null
} }
}).then((data) => { }).then(() => {
// this will force the pages to be refreshed // this will force the pages to be refreshed
this.$f7.emit('sidebarRefresh', null) this.$f7.emit('sidebarRefresh', null)
}) })
} }
}, },
mounted () { mounted () {
Promise.all([this.$oh.api.get('/rest/config-descriptions/system:i18n'), this.$oh.api.get('/rest/services/org.openhab.i18n/config')]).then((data) => { const promises = [
this.$oh.api.get('/rest/config-descriptions/system:i18n'),
this.$oh.api.get('/rest/services/org.openhab.i18n/config'),
this.$oh.api.get('/rest/config-descriptions/system:network'),
this.$oh.api.get('/rest/services/org.openhab.network/config'),
this.$oh.api.get('/rest/addons')
]
Promise.all(promises).then((data) => {
// i18n config descriptions
this.availableLanguages = data[0].parameters.find(p => p.name === 'language').options this.availableLanguages = data[0].parameters.find(p => p.name === 'language').options
this.availableRegions = data[0].parameters.find(p => p.name === 'region').options this.availableRegions = data[0].parameters.find(p => p.name === 'region').options
this.availableTimezones = data[0].parameters.find(p => p.name === 'timezone').options this.availableTimezones = data[0].parameters.find(p => p.name === 'timezone').options
@ -517,21 +646,21 @@ export default {
} }
} }
// i18n config
if (data[1].language) this.language = data[1].language if (data[1].language) this.language = data[1].language
if (data[1].location) this.location = data[1].location if (data[1].location) this.location = data[1].location
if (data[1].region) this.region = data[1].region if (data[1].region) this.region = data[1].region
if (data[1].timezone) this.timezone = data[1].timezone if (data[1].timezone) this.timezone = data[1].timezone
this.i18nReady = true this.i18nReady = true
})
this.$oh.api.get('/rest/addons/suggestions').then((suggestedAddons) => { // network config description & config
const suggestions = suggestedAddons.flatMap(s => s.id) this.networkConfigDescription = data[2].parameters.find(p => p.name === 'primaryAddress')
this.$oh.api.get('/rest/addons').then(data => { this.network = data[3].primaryAddress
this.addons = data.sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase())) this.networksReady = true
this.selectedAddons = this.addons.filter(a => (this.recommendedAddons.includes(a.uid) || suggestions.includes(a.id)))
.sort((a, b) => a.uid.toUpperCase().localeCompare(b.uid.toUpperCase())) // addons
this.addonsReady = true this.addons = data[4].sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase()))
})
}) })
} }
} }