Skip to content

Commit

Permalink
Setup wizard: Add primary IP config (#2565)
Browse files Browse the repository at this point in the history
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 openhab/openhab-core#4036.

---------

Also-by: Florian Hotze <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
  • Loading branch information
mherwege authored May 7, 2024
1 parent 3fa3192 commit 5f3d6da
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"setupwizard.skipSetup": "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.configureLater": "Configure in Settings Later",
"setupwizard.location.title": "Set your 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.",
Expand All @@ -17,7 +18,10 @@
"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.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.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.",
Expand Down Expand Up @@ -46,6 +50,7 @@
"setupwizard.addons.installingAddon": "Installing Add-on: {addon}",
"setupwizard.addons.progress": "{current} of {total}",
"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.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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
199 changes: 164 additions & 35 deletions bundles/org.openhab.ui/web/src/pages/wizards/setup-wizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@
<small v-t="'setupwizard.location.footer'" />
</f7-block-footer>
</f7-block>
<f7-block class="display-flex flex-direction-column padding">
<f7-block class="display-flex flex-direction-column padding" v-if="networksReady">
<div>
<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="persistence" ref="persistence">
<f7-tab id="network" ref="network">
<f7-block>
<f7-link
icon-ios="f7:arrow_left"
Expand All @@ -109,6 +109,36 @@
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>
</f7-block>
</f7-tab>

<f7-tab id="persistence" ref="persistence">
<f7-block>
<f7-link
icon-ios="f7:arrow_left"
icon-aurora="f7:arrow_left"
icon-md="material:arrow_back"
:tab-link="(networkConfigDescription && networkConfigDescription.options && networkConfigDescription.options.length > 1) ? '#network' : '#location'"
color="blue"
tab-link-active />
<f7-login-screen-title>
<div class="padding">
<f7-icon size="48" color="blue" f7="download_circle" />
Expand All @@ -120,15 +150,24 @@
{{ $t('setupwizard.persistence.header1') }} {{ $t('setupwizard.persistence.header2') }}
</f7-block>
<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')"
:preSelectedAddons="selectedAddons"
@update="updateAddonSelection(recommendedAddonsByType('persistence'), $event)" />
<f7-block-footer class="margin-bottom">
<small v-t="'setupwizard.persistence.footer'" />
</f7-block-footer>
<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" />
</div>
</f7-block>
Expand All @@ -155,7 +194,13 @@
<a class="text-color-blue external" target="_blank" href="https://www.openhab.org/addons/" v-t="'setupwizard.addons.browseAddonsOnWebsite'" />
</f7-block>
<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"
:addons="mainAddons"
:preSelectedAddons="selectedAddons"
Expand All @@ -164,7 +209,7 @@
<small v-t="'setupwizard.addons.footer'" />
</f7-block-footer>
<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"
:text="$tc('setupwizard.addons.installAddons', toInstallAddons.filter(a => (!preSelectedAddon(a) && !a.installed)).length)"
@click="installAddons" />
Expand Down Expand Up @@ -229,6 +274,12 @@
width 240px
.page-content
margin-top inherit
.network
.block-header
.item-label
text-align left
margin-left 0 !important
margin-right 0 !important
.tab-active
scroll-snap-align start
Expand All @@ -247,27 +298,29 @@
<script>
import i18n from '@/components/i18n-mixin'
import { loadLocaleMessages } from '@/js/i18n'
import AddonsSetupWizard from '@/components/addons/addons-setup-wizard'
export default {
mixins: [i18n],
components: {
'parameter-location': () => import('@/components/config/controls/parameter-location.vue'),
'parameter-options': () => import('@/components/config/controls/parameter-options.vue'),
AddonsSetupWizard
},
data () {
return {
i18nReady: false,
addonsReady: false,
availableLanguages: null,
availableRegions: null,
availableTimezones: null,
language: null,
region: null,
timezone: null,
location: null,
autocompleteAddons: null,
networksReady: false,
networkConfigDescription: null,
network: null,
addonSuggestionsReady: false,
addons: [],
// all recommended addons, pre-defined
recommendedAddons: ['persistence-rrd4j', 'persistence-mapdb', 'automation-jsscripting', 'ui-basic', 'binding-astro'],
Expand Down Expand Up @@ -323,14 +376,6 @@ export default {
timezone: this.timezone
}).then(() => {
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()
})
},
Expand Down Expand Up @@ -363,27 +408,102 @@ export default {
this.$oh.api.put('/rest/services/org.openhab.i18n/config', {
location: this.location
}).then(() => {
this.showPersistence()
this.showNetwork()
})
},
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()
},
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()
},
selectPersistence () {
this.showAddons()
},
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()
},
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()
},
/**
* 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) {
return (this.preSelectingAddonTypes.includes(addon.type) || this.preSelectingAddons.includes(addon.uid))
},
Expand Down Expand Up @@ -443,9 +563,9 @@ export default {
console.log('Installing add-on: ' + addon.uid)
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(() => {
checkAddonStatus(addon).then((addon) => {
checkAddonStatus(addon).then(() => {
clearInterval(checkTimer)
installNextAddon()
}).catch(() => {
Expand Down Expand Up @@ -494,14 +614,23 @@ export default {
default: [],
masonry: null
}
}).then((data) => {
}).then(() => {
// this will force the pages to be refreshed
this.$f7.emit('sidebarRefresh', null)
})
}
},
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.availableRegions = data[0].parameters.find(p => p.name === 'region').options
this.availableTimezones = data[0].parameters.find(p => p.name === 'timezone').options
Expand All @@ -517,21 +646,21 @@ export default {
}
}
// i18n config
if (data[1].language) this.language = data[1].language
if (data[1].location) this.location = data[1].location
if (data[1].region) this.region = data[1].region
if (data[1].timezone) this.timezone = data[1].timezone
this.i18nReady = true
})
this.$oh.api.get('/rest/addons/suggestions').then((suggestedAddons) => {
const suggestions = suggestedAddons.flatMap(s => s.id)
this.$oh.api.get('/rest/addons').then(data => {
this.addons = data.sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase()))
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()))
this.addonsReady = true
})
// network config description & config
this.networkConfigDescription = data[2].parameters.find(p => p.name === 'primaryAddress')
this.network = data[3].primaryAddress
this.networksReady = true
// addons
this.addons = data[4].sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase()))
})
}
}
Expand Down

0 comments on commit 5f3d6da

Please sign in to comment.