forked from platypush/platypush
Migration completed for the zigbee.mqtt web panel
This commit is contained in:
parent
3cf91a3f27
commit
2427cceb5e
5 changed files with 482 additions and 215 deletions
|
@ -1,6 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="item device" :class="{selected: selected}">
|
<div class="item device" :class="{selected: selected}">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
|
<Modal class="groups-modal" ref="groupsModal" title="Device groups">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
|
<form class="content" @submit.prevent="manageGroups">
|
||||||
|
<div class="groups">
|
||||||
|
<label class="row group" v-for="(group, id) in groups" :key="id">
|
||||||
|
<input type="checkbox" :value="id" :checked="associatedGroups.has(parseInt(group.id))">
|
||||||
|
<span class="name" v-text="group.friendly_name?.length ? group.friendly_name : `[Group #${group.id}]`" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer buttons">
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<div class="row name header vertical-center" :class="{selected: selected}"
|
<div class="row name header vertical-center" :class="{selected: selected}"
|
||||||
v-text="device.friendly_name || device.ieee_address" @click="$emit('select')" />
|
v-text="device.friendly_name || device.ieee_address" @click="$emit('select')" />
|
||||||
|
|
||||||
|
@ -159,17 +177,32 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
<div class="row" @click="$refs.groupsModal.show()">
|
||||||
|
<div class="param-name">Manage groups</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-network-wired" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" @click="otaUpdatesAvailable ? installOtaUpdates() : checkOtaUpdates()">
|
||||||
|
<div class="param-name" v-if="!otaUpdatesAvailable">Check for updates</div>
|
||||||
|
<div class="param-name" v-else>Install updates</div>
|
||||||
|
<div class="param-value">
|
||||||
|
<i class="fa fa-sync-alt" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" @click="remove(false)">
|
<div class="row" @click="remove(false)">
|
||||||
<div class="param-name">Remove Device</div>
|
<div class="param-name">Remove Device</div>
|
||||||
<div class="param-value">
|
<div class="param-value">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row error" @click="remove(true)">
|
<div class="row error" @click="remove(true)">
|
||||||
<div class="param-name">Force Remove Device</div>
|
<div class="param-name">Force Remove Device</div>
|
||||||
<div class="param-value">
|
<div class="param-value">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -184,12 +217,13 @@ import Slider from "@/components/elements/Slider";
|
||||||
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
import {ColorConverter} from "@/components/panels/Light/color";
|
import {ColorConverter} from "@/components/panels/Light/color";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Device",
|
name: "Device",
|
||||||
components: {ToggleSwitch, Slider, Loading},
|
components: {Modal, ToggleSwitch, Slider, Loading},
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
emits: ['select', 'rename', 'remove'],
|
emits: ['select', 'rename', 'remove', 'groups-edit'],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
device: {
|
device: {
|
||||||
|
@ -197,6 +231,11 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
groups: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
selected: {
|
selected: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -208,6 +247,7 @@ export default {
|
||||||
editName: false,
|
editName: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
status: {},
|
status: {},
|
||||||
|
otaUpdatesAvailable: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -272,7 +312,10 @@ export default {
|
||||||
if (!this.displayedValues.color)
|
if (!this.displayedValues.color)
|
||||||
return
|
return
|
||||||
|
|
||||||
const color = this.displayedValues.color.value
|
const color = this.displayedValues.color?.value
|
||||||
|
if (!color)
|
||||||
|
return
|
||||||
|
|
||||||
if (color.x != null && color.y != null) {
|
if (color.x != null && color.y != null) {
|
||||||
const converter = new ColorConverter({
|
const converter = new ColorConverter({
|
||||||
bri: [this.displayedValues.brightness?.value_min || 0, this.displayedValues.brightness?.value_max || 255],
|
bri: [this.displayedValues.brightness?.value_min || 0, this.displayedValues.brightness?.value_max || 255],
|
||||||
|
@ -293,6 +336,13 @@ export default {
|
||||||
|
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
||||||
|
associatedGroups() {
|
||||||
|
return new Set(Object.values(this.groups)
|
||||||
|
.filter((group) => new Set(
|
||||||
|
(group.members || []).map((member) => member.ieee_address)).has(this.device.ieee_address))
|
||||||
|
.map((group) => parseInt(group.id)))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -423,18 +473,158 @@ export default {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async manageGroups(event) {
|
||||||
|
const groups = [...event.target.querySelectorAll('input[type=checkbox]')].reduce((obj, element) => {
|
||||||
|
const groupId = parseInt(element.value)
|
||||||
|
if (element.checked && !this.associatedGroups.has(groupId))
|
||||||
|
obj.add.add(groupId)
|
||||||
|
else if (!element.checked && this.associatedGroups.has(groupId))
|
||||||
|
obj.remove.add(groupId)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}, {add: new Set(), remove: new Set()})
|
||||||
|
|
||||||
|
const editGroups = async (action) => {
|
||||||
|
await Promise.all([...groups[action]].map(async (groupId) => {
|
||||||
|
await this.request(`zigbee.mqtt.group_${action}_device`, {
|
||||||
|
group: this.groups[groupId].friendly_name,
|
||||||
|
device: this.device.friendly_name?.length ? this.device.friendly_name : this.device.ieee_address,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
await Promise.all(Object.keys(groups).map(editGroups))
|
||||||
|
this.$emit('groups-edit', groups)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
async checkOtaUpdates() {
|
||||||
this.refresh()
|
this.loading = true
|
||||||
|
try {
|
||||||
|
this.otaUpdatesAvailable = (await this.request('zigbee.mqtt.device_check_ota_updates', {
|
||||||
|
device: this.device.friendly_name?.length ? this.device.friendly_name : this.device.ieee_address,
|
||||||
|
})).update_available
|
||||||
|
|
||||||
|
if (this.otaUpdatesAvailable)
|
||||||
|
this.notify({
|
||||||
|
text: 'A firmware update is available for the device',
|
||||||
|
image: {
|
||||||
|
iconClass: 'fa fa-sync-alt',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else
|
||||||
|
this.notify({
|
||||||
|
text: 'The device is up to date',
|
||||||
|
image: {
|
||||||
|
iconClass: 'fa fa-check',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async installOtaUpdates() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
await this.request('zigbee.mqtt.device_install_ota_updates', {
|
||||||
|
device: this.device.friendly_name?.length ? this.device.friendly_name : this.device.ieee_address,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
this.$watch(() => this.selected, (newValue) => {
|
this.$watch(() => this.selected, (newValue) => {
|
||||||
if (newValue)
|
if (newValue)
|
||||||
this.refresh()
|
this.refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.$watch(() => this.status.update_available, (newValue) => {
|
||||||
|
this.otaUpdatesAvailable = newValue
|
||||||
|
})
|
||||||
|
|
||||||
|
this.subscribe((event) => {
|
||||||
|
if (event.device !== this.device.friendly_name && event.device !== this.device.ieee_address)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.status = {...this.status, ...event.properties}
|
||||||
|
}, `on-property-change-${this.device.ieee_address}`,
|
||||||
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePropertySetEvent')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
this.unsubscribe(`on-property-change-${this.device.ieee_address}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "common";
|
@import "common";
|
||||||
|
|
||||||
|
.groups-modal {
|
||||||
|
.content {
|
||||||
|
min-width: 20em;
|
||||||
|
margin: -2em;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: .5em 1em !important;
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 3.5em);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
height: 3.5em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
background: $default-bg-2;
|
||||||
|
border-top: $default-border-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $tablet) {
|
||||||
|
.section.actions {
|
||||||
|
.row {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: left;
|
||||||
|
|
||||||
|
.param-name {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-value {
|
||||||
|
width: 1.5em;
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,48 +5,22 @@
|
||||||
v-text="group.friendly_name" @click="$emit('select')" />
|
v-text="group.friendly_name" @click="$emit('select')" />
|
||||||
|
|
||||||
<div class="params" v-if="selected">
|
<div class="params" v-if="selected">
|
||||||
<div class="section values">
|
<div class="section devices">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">Values</div>
|
<div class="title">Devices</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<!-- <div class="row" v-for="(value, name) in properties" :key="name">-->
|
<form>
|
||||||
<!-- <div class="param-name" v-text="name"></div>-->
|
<label class="row" v-for="(device, id) in devices" :key="id">
|
||||||
<!-- <div class="param-value">-->
|
<input type="checkbox" :checked="members.has(device.ieee_address)" :value="device.ieee_address"
|
||||||
<!-- <div v-if="name === 'state'">-->
|
@change="toggleDevice(device.ieee_address)" />
|
||||||
<!-- <toggle-switch :value="value" @toggled="toggleState"></toggle-switch>-->
|
<span class="label" v-text="device.friendly_name?.length ? device.friendly_name : device.ieee_address" />
|
||||||
<!-- </div>-->
|
</label>
|
||||||
<!-- <div v-else>-->
|
</form>
|
||||||
<!-- <input type="text" :value="value" :data-name="name" @change="setValue">-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="section devices">-->
|
|
||||||
<!-- <div class="header">-->
|
|
||||||
<!-- <div class="title col-10">Devices</div>-->
|
|
||||||
<!-- <div class="buttons col-2">-->
|
|
||||||
<!-- <button class="btn btn-default" title="Add Devices" @click="bus.$emit('openAddToGroupModal')">-->
|
|
||||||
<!-- <i class="fa fa-plus"></i>-->
|
|
||||||
<!-- </button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<!-- <div class="body">-->
|
|
||||||
<!-- <div class="row" v-for="device in group.devices">-->
|
|
||||||
<!-- <div class="col-10" v-text="device.friendly_name"></div>-->
|
|
||||||
<!-- <div class="buttons col-2">-->
|
|
||||||
<!-- <button class="btn btn-default" title="Remove from group" @click="removeFromGroup(device.friendly_name)">-->
|
|
||||||
<!-- <i class="fa fa-trash"></i>-->
|
|
||||||
<!-- </button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<div class="section actions">
|
<div class="section actions">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">Actions</div>
|
<div class="title">Actions</div>
|
||||||
|
@ -74,14 +48,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
// import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Group",
|
name: "Group",
|
||||||
emits: ['select', 'remove'],
|
emits: ['select', 'remove', 'edit'],
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
// components: {Loading, ToggleSwitch},
|
|
||||||
components: {Loading},
|
components: {Loading},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -90,6 +62,11 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
devices: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { return {} },
|
||||||
|
},
|
||||||
|
|
||||||
selected: {
|
selected: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -99,9 +76,24 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
values: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
devicesByAddress() {
|
||||||
|
return Object.entries(this.devices).reduce((obj, entry) => {
|
||||||
|
const device = entry[1]
|
||||||
|
obj[device.ieee_address] = device
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
|
||||||
|
members() {
|
||||||
|
return new Set((this.group.members || []).map((member) => member.ieee_address))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async remove() {
|
async remove() {
|
||||||
if (!confirm('Are you sure that you want to remove this group?'))
|
if (!confirm('Are you sure that you want to remove this group?'))
|
||||||
|
@ -117,10 +109,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async rename() {
|
async rename() {
|
||||||
const name = prompt('New group name', this.group.friendly_name).trim()
|
let name = prompt('New group name', this.group.friendly_name)
|
||||||
if (!name.length)
|
if (!name?.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
name = name.trim()
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -134,10 +127,52 @@ export default {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async toggleDevice(ieeeAddress) {
|
||||||
|
const device = this.devicesByAddress[ieeeAddress]
|
||||||
|
const name = device.friendly_name?.length ? device.friendly_name : ieeeAddress
|
||||||
|
const method = this.members.has(ieeeAddress) ? 'remove' : 'add'
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
await this.request(`zigbee.mqtt.group_${method}_device`, {
|
||||||
|
group: this.group.friendly_name,
|
||||||
|
device: name,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$emit('edit', {device: name, method: method})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "common";
|
@import "common";
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding-left: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
background: none !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-left: .75em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,7 +2,56 @@
|
||||||
<div class="zigbee-container">
|
<div class="zigbee-container">
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
<!-- Include group modal -->
|
<Modal title="Network Info" ref="infoModal">
|
||||||
|
<div class="info-body" v-if="status.info">
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">State</div>
|
||||||
|
<div class="param-value" v-text="status.state" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Permit Join</div>
|
||||||
|
<div class="param-value" v-text="status.info.permit_join" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.network">
|
||||||
|
<div class="param-name">Network Channel</div>
|
||||||
|
<div class="param-value" v-text="status.info.network.channel" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="param-name">Zigbee2MQTT Version</div>
|
||||||
|
<div class="param-value" v-text="status.info.version" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.config?.mqtt">
|
||||||
|
<div class="param-name">MQTT Server</div>
|
||||||
|
<div class="param-value" v-text="status.info.config.mqtt.server" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.config?.serial">
|
||||||
|
<div class="param-name">Serial Port</div>
|
||||||
|
<div class="param-value" v-text="status.info.config.serial.port" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.coordinator?.type">
|
||||||
|
<div class="param-name">Firmware Type</div>
|
||||||
|
<div class="param-value" v-text="status.info.coordinator.type" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.coordinator?.meta">
|
||||||
|
<div class="param-name">Firmware Version</div>
|
||||||
|
<div class="param-value">
|
||||||
|
{{ status.info.coordinator.meta.maintrel }}.{{ status.info.coordinator.meta.majorrel }}.{{ status.info.coordinator.meta.minorrel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="status.info.coordinator?.meta">
|
||||||
|
<div class="param-name">Firmware Revision</div>
|
||||||
|
<div class="param-value" v-text="status.info.coordinator.meta.revision" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<div class="view-options">
|
<div class="view-options">
|
||||||
<div class="view-selector col-s-8 col-m-9 col-l-10">
|
<div class="view-selector col-s-8 col-m-9 col-l-10">
|
||||||
|
@ -23,7 +72,10 @@
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Dropdown ref="networkCommandsDropdown" icon-class="fa fa-cog" title="Network commands">
|
<Dropdown ref="networkCommandsDropdown" icon-class="fa fa-cog" title="Network commands">
|
||||||
<DropdownItem text="Permit Join" :disabled="loading" @click="permitJoin(true)" />
|
<DropdownItem text="Network Info" :disabled="loading" @click="$refs.infoModal.show()" />
|
||||||
|
<DropdownItem text="Permit Join" :disabled="loading" @click="permitJoin(true)"
|
||||||
|
v-if="!status.info?.permit_join" />
|
||||||
|
<DropdownItem text="Disable Join" :disabled="loading" @click="permitJoin(false)" v-else/>
|
||||||
<DropdownItem text="Factory Reset" :disabled="loading" @click="factoryReset" />
|
<DropdownItem text="Factory Reset" :disabled="loading" @click="factoryReset" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
|
@ -41,11 +93,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Device v-for="(device, id) in devices" :key="id"
|
<Device v-for="(device, id) in devices" :key="id"
|
||||||
:device="device" :selected="selected.deviceId === id"
|
:device="device" :groups="groups" :selected="selected.deviceId === id"
|
||||||
@select="selected.deviceId = selected.deviceId === id ? null : id"
|
@select="selected.deviceId = selected.deviceId === id ? null : id"
|
||||||
@rename="refreshDevices" @remove="refreshDevices" />
|
@rename="refreshDevices" @remove="refreshDevices" @groups-edit="refreshGroups" />
|
||||||
|
|
||||||
<!-- <dropdown ref="addToGroupDropdown" :items="addToGroupDropdownItems"></dropdown>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="view groups" v-else-if="selected.view === 'groups'">
|
<div class="view groups" v-else-if="selected.view === 'groups'">
|
||||||
|
@ -54,10 +104,10 @@
|
||||||
<div class="empty" v-else>No groups available on the network</div>
|
<div class="empty" v-else>No groups available on the network</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Group v-for="(group, id) in groups" :key="id" :group="group"
|
<Group v-for="(group, id) in groups" :key="id" :group="group" :devices="devices"
|
||||||
:selected="selected.groupId === id"
|
:selected="selected.groupId === id"
|
||||||
@select="selected.groupId = selected.groupId === id ? null : id"
|
@select="selected.groupId = selected.groupId === id ? null : id"
|
||||||
@rename="refreshGroups" @remove="refreshGroups" />
|
@rename="refreshGroups" @remove="refreshGroups" @edit="refreshGroups" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,17 +121,18 @@ import Utils from "@/Utils"
|
||||||
|
|
||||||
import Device from "@/components/panels/ZigbeeMqtt/Device";
|
import Device from "@/components/panels/ZigbeeMqtt/Device";
|
||||||
import Group from "@/components/panels/ZigbeeMqtt/Group";
|
import Group from "@/components/panels/ZigbeeMqtt/Group";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ZigbeeMqtt",
|
name: "ZigbeeMqtt",
|
||||||
components: {Dropdown, DropdownItem, Loading, Device, Group},
|
components: {Modal, Dropdown, DropdownItem, Loading, Device, Group},
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
status: {},
|
|
||||||
devices: {},
|
devices: {},
|
||||||
groups: {},
|
groups: {},
|
||||||
|
status: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
selected: {
|
selected: {
|
||||||
view: 'devices',
|
view: 'devices',
|
||||||
|
@ -100,34 +151,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
// addToGroupDropdownItems: function() {
|
|
||||||
// const self = this
|
|
||||||
// return Object.values(this.groups).filter((group) => {
|
|
||||||
// return !group.values || !group.values.length || !(this.selected.valueId in this.scene.values)
|
|
||||||
// }).map((group) => {
|
|
||||||
// return {
|
|
||||||
// text: group.name,
|
|
||||||
// disabled: this.loading,
|
|
||||||
// click: async function () {
|
|
||||||
// if (!self.selected.valueId) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// self.loading = true
|
|
||||||
// await this.request('zwave.scene_add_value', {
|
|
||||||
// id_on_network: self.selected.valueId,
|
|
||||||
// scene_id: group.scene_id,
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// self.loading = false
|
|
||||||
// self.refresh()
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async refreshDevices() {
|
async refreshDevices() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
@ -151,17 +174,29 @@ export default {
|
||||||
|
|
||||||
async refreshGroups() {
|
async refreshGroups() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
try {
|
||||||
this.groups = (await this.request('zigbee.mqtt.groups')).reduce((groups, group) => {
|
this.groups = (await this.request('zigbee.mqtt.groups')).reduce((groups, group) => {
|
||||||
groups[group.id] = group
|
groups[group.id] = group
|
||||||
return groups
|
return groups
|
||||||
}, {})
|
}, {})
|
||||||
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshInfo() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
this.status = await this.request('zigbee.mqtt.info')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.refreshDevices()
|
this.refreshDevices()
|
||||||
this.refreshGroups()
|
this.refreshGroups()
|
||||||
|
this.refreshInfo()
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProperties(device, props) {
|
updateProperties(device, props) {
|
||||||
|
@ -184,31 +219,17 @@ export default {
|
||||||
await this.refreshGroups()
|
await this.refreshGroups()
|
||||||
},
|
},
|
||||||
|
|
||||||
async startNetwork() {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
await this.request('zigbee.mqtt.start_network')
|
|
||||||
} finally {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async stopNetwork() {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
await this.request('zigbee.mqtt.stop_network')
|
|
||||||
} finally {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async permitJoin(permit) {
|
async permitJoin(permit) {
|
||||||
|
const args = {permit: !!permit}
|
||||||
|
if (permit) {
|
||||||
let seconds = prompt('Join allow period in seconds (0 or empty for no time limits)', '60')
|
let seconds = prompt('Join allow period in seconds (0 or empty for no time limits)', '60')
|
||||||
seconds = seconds.length ? parseInt(seconds) : null
|
args.seconds = seconds.length ? parseInt(seconds) : null
|
||||||
this.loading = true
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
await this.request('zigbee.mqtt.permit_join', {permit: !!permit, timeout: seconds || null})
|
await this.request('zigbee.mqtt.permit_join', args)
|
||||||
|
setTimeout(this.refreshInfo, 1000)
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
@ -228,11 +249,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// openAddToGroupDropdown(event) {
|
|
||||||
// this.selected.valueId = event.valueId
|
|
||||||
// openDropdown(this.$refs.addToGroupDropdown)
|
|
||||||
// },
|
|
||||||
|
|
||||||
async addToGroup(device, group) {
|
async addToGroup(device, group) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
await this.request('zigbee.mqtt.group_add_device', {
|
await this.request('zigbee.mqtt.group_add_device', {
|
||||||
|
@ -248,103 +264,93 @@ export default {
|
||||||
self.refreshGroups()
|
self.refreshGroups()
|
||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeNodeFromGroup(event) {
|
|
||||||
if (!confirm('Are you sure that you want to remove this value from the group?')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = true
|
|
||||||
await this.request('zigbee.mqtt.group_remove_device', {
|
|
||||||
group: event.group,
|
|
||||||
device: event.device,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.loading = false
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
// const self = this
|
this.subscribe(() => {
|
||||||
// this.bus.$on('refresh', this.refresh)
|
this.notify({
|
||||||
// this.bus.$on('refreshDevices', this.refreshDevices)
|
text: 'WARNING: The controller is offline',
|
||||||
// this.bus.$on('refreshGroups', this.refreshGroups)
|
error: true,
|
||||||
// this.bus.$on('openAddToGroupModal', () => {self.modal.group.visible = true})
|
})
|
||||||
// this.bus.$on('openAddToGroupDropdown', this.openAddToGroupDropdown)
|
}, 'on-zigbee-offline', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent')
|
||||||
// this.bus.$on('removeFromGroup', this.removeNodeFromGroup)
|
|
||||||
//
|
this.subscribe(() => {
|
||||||
// registerEventHandler(() => {
|
this.notify({
|
||||||
// createNotification({
|
text: 'The controller is now online',
|
||||||
// text: 'WARNING: The controller is now offline',
|
iconClass: 'fas fa-check',
|
||||||
// error: true,
|
})
|
||||||
// })
|
}, 'on-zigbee-online', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOnlineEvent')
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent')
|
|
||||||
//
|
this.subscribe(() => {
|
||||||
// registerEventHandler(() => {
|
this.notify({
|
||||||
// createNotification({
|
text: 'Failed to remove the device',
|
||||||
// text: 'The controller is now online',
|
error: true,
|
||||||
// iconClass: 'fas fa-check',
|
})
|
||||||
// })
|
}, 'on-zigbee-device-remove-failed', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedFailedEvent')
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent')
|
|
||||||
//
|
this.subscribe(() => {
|
||||||
// registerEventHandler(() => {
|
this.notify({
|
||||||
// createNotification({
|
text: 'Failed to add the group',
|
||||||
// text: 'Failed to remove the device',
|
error: true,
|
||||||
// error: true,
|
})
|
||||||
// })
|
}, 'on-zigbee-group-add-failed', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedFailedEvent')
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedFailedEvent')
|
|
||||||
//
|
this.subscribe(() => {
|
||||||
// registerEventHandler(() => {
|
this.notify({
|
||||||
// createNotification({
|
text: 'Failed to remove group',
|
||||||
// text: 'Failed to add the group',
|
error: true,
|
||||||
// error: true,
|
})
|
||||||
// })
|
}, 'on-zigbee-group-remove-failed', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedFailedEvent')
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedFailedEvent')
|
|
||||||
//
|
this.subscribe(() => {
|
||||||
// registerEventHandler(() => {
|
this.notify({
|
||||||
// createNotification({
|
text: 'Failed to remove the devices from group',
|
||||||
// text: 'Failed to remove the group',
|
error: true,
|
||||||
// error: true,
|
})
|
||||||
// })
|
}, 'on-zigbee-remove-all-failed',
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedFailedEvent')
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllFailedEvent')
|
||||||
//
|
|
||||||
// registerEventHandler(() => {
|
this.subscribe((event) => {
|
||||||
// createNotification({
|
this.notify({
|
||||||
// text: 'Failed to remove the devices from the group',
|
text: event.error || '[Unknown error]',
|
||||||
// error: true,
|
error: true,
|
||||||
// })
|
})
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllFailedEvent')
|
}, 'on-zigbee-error', 'platypush.message.event.zigbee.mqtt.ZigbeeMqttErrorEvent')
|
||||||
//
|
|
||||||
// registerEventHandler((event) => {
|
this.subscribe(this.refresh, 'on-zigbee-device-update',
|
||||||
// createNotification({
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttOnlineEvent',
|
||||||
// text: 'Unhandled Zigbee error: ' + (event.error || '[Unknown error]'),
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePairingEvent',
|
||||||
// error: true,
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceConnectedEvent',
|
||||||
// })
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBannedEvent',
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttErrorEvent')
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedEvent',
|
||||||
//
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceWhitelistedEvent',
|
||||||
// registerEventHandler((event) => {
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRenamedEvent',
|
||||||
// self.updateProperties(event.device, event.properties)
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBindEvent',
|
||||||
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePropertySetEvent')
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceUnbindEvent',
|
||||||
//
|
)
|
||||||
// registerEventHandler(this.refresh,
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOnlineEvent',
|
this.subscribe(this.refreshGroups, 'on-zigbee-group-update',
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePairingEvent',
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedEvent',
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceConnectedEvent',
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedEvent',
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBannedEvent',
|
'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllEvent',
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedEvent',
|
)
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceWhitelistedEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRenamedEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBindEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceUnbindEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedEvent',
|
|
||||||
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllEvent',
|
|
||||||
// )
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.refresh()
|
this.refresh()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
this.unsubscribe('on-zigbee-error')
|
||||||
|
this.unsubscribe('on-zigbee-remove-all-failed')
|
||||||
|
this.unsubscribe('on-zigbee-group-remove-failed')
|
||||||
|
this.unsubscribe('on-zigbee-group-add-failed')
|
||||||
|
this.unsubscribe('on-zigbee-device-remove-failed')
|
||||||
|
this.unsubscribe('on-zigbee-online')
|
||||||
|
this.unsubscribe('on-zigbee-offline')
|
||||||
|
this.unsubscribe('on-zigbee-device-update')
|
||||||
|
this.unsubscribe('on-zigbee-group-update')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -515,5 +521,37 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-body {
|
||||||
|
margin: -2em;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
padding: 1em .5em;
|
||||||
|
|
||||||
|
.param-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $tablet) {
|
||||||
|
.info-body {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
.info-body {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $desktop) {
|
||||||
|
.info-body {
|
||||||
|
width: 60vw;
|
||||||
|
max-width: 30em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
@import "vars";
|
@import "vars";
|
||||||
|
|
||||||
.zigbee-container {
|
.zigbee-container {
|
||||||
.view {
|
.params, .info-body {
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 1em;
|
|
||||||
padding: .3em;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background: $param-even-row-bg;
|
background: $param-even-row-bg;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +10,15 @@
|
||||||
&:nth-child(odd) {
|
&:nth-child(odd) {
|
||||||
background: $param-odd-row-bg;
|
background: $param-odd-row-bg;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.view, .info-body {
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 1em;
|
||||||
|
padding: .3em;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $hover-bg;
|
background: $hover-bg;
|
||||||
|
@ -71,7 +75,7 @@
|
||||||
|
|
||||||
.params {
|
.params {
|
||||||
.section {
|
.section {
|
||||||
padding: 0;
|
padding: 1.5em 0 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,5 @@ $param-odd-row-bg: white;
|
||||||
$param-section-header-border: 1px solid #e8e8e8;
|
$param-section-header-border: 1px solid #e8e8e8;
|
||||||
$selected-item-box-shadow: 0 2px 4px 0 #bbb;
|
$selected-item-box-shadow: 0 2px 4px 0 #bbb;
|
||||||
$error-color: #aa0000;
|
$error-color: #aa0000;
|
||||||
$header-bg: #f8f8f8;
|
$header-bg: #f9fafa;
|
||||||
$header-height: 3.5em;
|
$header-height: 3.5em;
|
||||||
|
|
Loading…
Reference in a new issue