platypush/platypush/backend/http/webapp/src/components/panels/ZigbeeMqtt/Index.vue

522 lines
14 KiB
Vue

<template>
<div class="zigbee-container">
<Loading v-if="loading" />
<!-- Include group modal -->
<div class="view-options">
<div class="view-selector col-s-8 col-m-9 col-l-10">
<label>
<select :value="selected.view" @change="this.selected.view = $event.target.value">
<option v-for="(enabled, view) in views"
v-text="(view[0].toUpperCase() + view.slice(1)).replace('_', ' ')"
:key="view" :selected="enabled" :value="view">
</option>
</select>
</label>
</div>
<div class="buttons">
<button class="btn btn-default" title="Add Group" v-if="selected.view === 'groups'"
:disabled="loading" @click="addGroup">
<i class="fa fa-plus"></i>
</button>
<Dropdown ref="networkCommandsDropdown" icon-class="fa fa-cog" title="Network commands">
<DropdownItem text="Start Network" :disabled="loading" @click="startNetwork" />
<DropdownItem text="Stop Network" :disabled="loading" @click="stopNetwork" />
<DropdownItem text="Permit Join" :disabled="loading" @click="permitJoin(true)" />
<DropdownItem text="Reset" :disabled="loading" @click="reset" />
<DropdownItem text="Factory Reset" :disabled="loading" @click="factoryReset" />
</Dropdown>
<button class="btn btn-default" title="Refresh network" :disabled="loading" @click="refresh">
<i class="fa fa-sync-alt"></i>
</button>
</div>
</div>
<div class="view-container">
<div class="view devices" v-if="selected.view === 'devices'">
<div class="no-items" v-if="!Object.keys(devices).length">
<div class="loading" v-if="loading">Loading devices...</div>
<div class="empty" v-else>No devices found on the network</div>
</div>
<ZigbeeDevice v-for="(device, id) in devices" :key="id"
:device="device" :selected="selected.deviceId === id"
@select="selected.deviceId = selected.deviceId === id ? null : id"
@rename="refreshDevices" />
<!-- <dropdown ref="addToGroupDropdown" :items="addToGroupDropdownItems"></dropdown>-->
</div>
<div class="view groups" v-else-if="selected.view === 'groups'">
<div class="no-items" v-if="!Object.keys(groups).length">
<div class="loading" v-if="loading">Loading groups...</div>
<div class="empty" v-else>No groups available on the network</div>
</div>
<!-- <zigbee-group-->
<!-- v-for="(group, groupId) in groups"-->
<!-- :key="groupId"-->
<!-- :group="group"-->
<!-- :selected="selected.groupId === groupId"-->
<!-- :bus="bus">-->
<!-- </zigbee-group>-->
</div>
</div>
</div>
</template>
<script>
import Dropdown from "../../elements/Dropdown"
import DropdownItem from "@/components/elements/DropdownItem"
import Loading from "@/components/Loading"
import Utils from "@/Utils"
import ZigbeeDevice from "@/components/panels/ZigbeeMqtt/Device";
export default {
name: "ZigbeeMqtt",
components: {Dropdown, DropdownItem, Loading, ZigbeeDevice},
mixins: [Utils],
data() {
return {
status: {},
devices: {},
groups: {},
loading: false,
selected: {
view: 'devices',
deviceId: undefined,
groupId: undefined,
},
views: {
devices: true,
groups: true,
},
modal: {
group: {
visible: false,
},
},
}
},
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: {
async refreshDevices() {
this.loading = true
try {
this.devices = (await this.request('zigbee.mqtt.devices')).reduce((devices, device) => {
if (device.friendly_name in this.devices) {
device = {
values: this.devices[device.friendly_name].values || {},
...this.devices[device.friendly_name],
}
}
devices[device.friendly_name] = device
return devices
}, {})
} finally {
this.loading = false
}
},
async refreshGroups() {
this.loading = true
this.groups = (await this.request('zigbee.mqtt.groups')).reduce((groups, group) => {
groups[group.id] = group
return groups
}, {})
this.loading = false
},
refresh() {
this.refreshDevices()
this.refreshGroups()
},
updateProperties(device, props) {
this.devices[device].values = props
},
async addGroup() {
const name = prompt('Group name')
if (!(name && name.length)) {
return
}
this.loading = true
try {
await this.request('zigbee.mqtt.group_add', {name: name})
} finally {
this.loading = false
}
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) {
let seconds = prompt('Join allow period in seconds (type 0 for no time limits)', '60')
if (!seconds) {
return
}
seconds = parseInt(seconds)
this.loading = true
try {
await this.request('zigbee.mqtt.permit_join', {permit: !!permit, timeout: seconds || null})
} finally {
this.loading = false
}
},
async reset() {
if (!confirm('Are you sure that you want to reset the device?')) {
return
}
this.loading = true
try {
await this.request('zigbee.mqtt.reset')
} finally {
this.loading = false
}
},
async factoryReset() {
if (!confirm('Are you SURE that you want to do a device factory reset?')) {
if (!confirm('Are you REALLY sure? ALL network information and custom firmware will be lost!!'))
return
}
this.loading = true
try {
await this.request('zigbee.mqtt.factory_reset')
} finally {
this.loading = false
}
},
// openAddToGroupDropdown(event) {
// this.selected.valueId = event.valueId
// openDropdown(this.$refs.addToGroupDropdown)
// },
async addToGroup(device, group) {
this.loading = true
await this.request('zigbee.mqtt.group_add_device', {
device: device,
group: group,
})
this.loading = false
const self = this
setTimeout(() => {
self.refresh()
self.refreshGroups()
}, 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() {
// const self = this
// this.bus.$on('refresh', this.refresh)
// this.bus.$on('refreshDevices', this.refreshDevices)
// this.bus.$on('refreshGroups', this.refreshGroups)
// this.bus.$on('openAddToGroupModal', () => {self.modal.group.visible = true})
// this.bus.$on('openAddToGroupDropdown', this.openAddToGroupDropdown)
// this.bus.$on('removeFromGroup', this.removeNodeFromGroup)
//
// registerEventHandler(() => {
// createNotification({
// text: 'WARNING: The controller is now offline',
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent')
//
// registerEventHandler(() => {
// createNotification({
// text: 'The controller is now online',
// iconClass: 'fas fa-check',
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent')
//
// registerEventHandler(() => {
// createNotification({
// text: 'Failed to remove the device',
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedFailedEvent')
//
// registerEventHandler(() => {
// createNotification({
// text: 'Failed to add the group',
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedFailedEvent')
//
// registerEventHandler(() => {
// createNotification({
// text: 'Failed to remove the group',
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedFailedEvent')
//
// registerEventHandler(() => {
// createNotification({
// text: 'Failed to remove the devices from the group',
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllFailedEvent')
//
// registerEventHandler((event) => {
// createNotification({
// text: 'Unhandled Zigbee error: ' + (event.error || '[Unknown error]'),
// error: true,
// })
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttErrorEvent')
//
// registerEventHandler((event) => {
// self.updateProperties(event.device, event.properties)
// }, 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePropertySetEvent')
//
// registerEventHandler(this.refresh,
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttOnlineEvent',
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePairingEvent',
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceConnectedEvent',
// 'platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBannedEvent',
// '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() {
this.refresh()
},
}
</script>
<style lang="scss">
@import "common";
.zigbee-container {
width: 100%;
height: 100%;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
overflow: auto;
.no-items {
padding: 2em;
font-size: 1.5em;
color: $no-items-color;
display: flex;
align-items: center;
justify-content: center;
}
.view-options {
display: flex;
width: 100%;
height: $header-height;
justify-content: space-between;
align-items: center;
padding: 0;
background: $header-bg;
border-bottom: $default-border-2;
box-shadow: $border-shadow-bottom;
.view-selector {
display: inline-flex;
padding-left: .5em;
label {
width: 100%;
}
}
.buttons {
display: inline-flex;
margin: 0;
button {
background: none;
border: none;
padding: 0 .75em;
&:hover {
color: $default-hover-fg;
}
}
.dropdown {
.item {
padding: .5em 2em .5em .5em;
}
}
}
}
.view-container {
width: 100%;
height: calc(100% - #{$header-height});
display: flex;
justify-content: center;
overflow: auto;
padding-top: 2em;
}
.view {
min-width: 400pt;
max-width: 750pt;
height: max-content;
background: $view-bg;
border: $view-border;
border-radius: 1.5em;
box-shadow: $view-box-shadow;
}
.params {
background: $params-bg;
padding-bottom: 1em;
.section {
display: flex;
flex-direction: column;
padding: 0 1em;
&:not(:first-child) {
padding-top: 1em;
}
.header {
display: flex;
align-items: center;
font-weight: bold;
border-bottom: $param-section-header-border;
}
}
}
.btn-value-name-edit {
padding: 0;
}
.modal {
.section {
.header {
background: none;
padding: .5em 0;
}
.body {
padding: 0;
}
}
.network-info {
min-width: 600pt;
}
}
.error {
color: $error-color;
}
.device, .group {
.actions {
.row {
cursor: pointer;
}
}
form {
margin-bottom: 0;
}
.param-value {
input[type=text] {
text-align: right;
}
}
}
}
</style>