homebridge-deconz
Version:
Homebridge plugin for deCONZ
296 lines (270 loc) • 9.67 kB
HTML
<!--
homebridge-deconz/homebridge-ui/public/index.html
Homebridge plug-in for deCONZ.
Copyright © 2022-2025 Erik Baauw. All rights reserved.
-->
<link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/vue@3"></script>
<p align="center">
<a href="https://github.com/ebaauw/homebridge-deconz/wiki/Configuration" target="_blank">
<img src="homebridge-deconz.png" height="200px">
</a>
</p>
<div id="app">
<!-- v-if can be used on any element, it will only render the element if the condition is true -->
<div v-if="view === 'gateways'">
<div class="w-100 d-flex justify-content-between align-items-center mb-1">
<h4 class="mb-0">Gateways</h4>
<!-- @click will call the addGateway method -->
<button class="btn btn-primary mr-0" @click="addGateway">Add <i class="fas fa-plus" ></i></button>
</div>
<ul class="list-group">
<!-- here we are looping over data.pluginConfig.gateways using v-for -->
<li class="list-group-item d-flex justify-content-between align-items-center" v-for="(gateway, $index) in pluginConfig.gateways" :key="$index">
<div>
{{ gateway.name }}
<div class="grey-text">
{{ gateway.host }}
</div>
</div>
<div>
<div class="btn-group" role="group" aria-label="Basic example">
<!-- @click will call the editGateway(gateway) method with the selected gateway as the first argument -->
<button class="btn btn-primary" @click="editGateway(gateway)">
<i class="fas fa-cog"></i>
</button>
<!-- @click will call the deleteGateway($index) method with its position in the array / index as the first argument -->
<button class="btn btn-danger" @click="deleteGateway($index)">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
</ul>
</div>
<div v-if="view === 'edit-gateway'">
<h4 class="mb-2">
Configure Gateway
<span v-if="selectedGateway">
- {{ selectedGateway.name }}
</span>
</h4>
<!-- @click will call the connect method -->
<button class="btn btn-primary ml-0" @click="connect">Connect</button>
<!-- @click will call the getApiKey method -->
<button class="btn btn-primary" @click="getApiKey">Get API Key</button>
</div>
</div>
<script>
const { createApp } = Vue;
const gatewaySchema = {
schema: {
type: 'object',
properties: {
host: {
description: 'Gateway hostname and port.',
default: 'localhost:80',
type: 'string',
required: true
},
name: {
description: 'Homebridge log plugin name.',
default: 'deCONZ',
type: 'string'
},
forceHttp: {
description: "Use plain http instead of https.",
type: "boolean"
},
noResponse: {
description: "Report unreachable lights as <i>No Response</i> in HomeKit.",
type: "boolean"
},
parallelRequests: {
description:" The number of ansynchronous requests Homebridge deCONZ sends in parallel to a deCONZ gateway. Default: 10.",
type: "integer",
minimum: 1,
maximum: 30
},
stealth: {
description: "Stealth mode: don't make any calls to the Internet. Default: false.",
type: "boolean"
},
timeout: {
description: "The timeout in seconds to wait for a response from a deCONZ gateway. Default: 5.",
type: "integer",
minimum: 1,
maximum: 30
},
waitTimePut: {
description: "The time, in milliseconds, to wait after sending a PUT request, before sending the next PUT request. Default: 50.",
type: "integer",
minimum: 0,
maximum: 50
},
waitTimePutGroup: {
description: "The time, in milliseconds, to wait after sending a PUT request to a group, before sending the next PUT request. Default: 1000.",
type: "integer",
minimum: 0,
maximum: 1000
},
waitTimeResend: {
description: "The time, in milliseconds, to wait before resending a request after an ECONNRESET or http status 503 error. Default: 300.",
type: "integer",
minimum: 100,
maximum: 1000
},
waitTimeReset: {
description: "The timeout in milliseconds, to wait before resetting a characteristic value. Default: 500.",
type: "integer",
minimum: 10,
maximum: 2000
},
waitTimeUpdate: {
description: "The time, in milliseconds, to wait for a change from HomeKit to another characteristic for the same light or group, before updating the deCONZ gateway. Default: 100.",
type: "integer",
minimum: 0,
maximum: 500
}
}
},
layout: [
"host",
"name",
{
type: "fieldset",
expandable: true,
title: "Advanced Settings",
description: "Don't change these, unless you understand what you're doing.",
items: [
"forceHttp",
"parallelRequests",
"stealth",
"timeout",
"waitTimePut",
"waitTimePutGroup",
"waitTimeResend",
"waitTimeReset",
"waitTimeUpdate"
]
}
]
};
const myApp = createApp({
/**
* This is called when the app is loaded. It's the entry point.
*/
async created() {
const config = await homebridge.getPluginConfig();
if (!config.length) {
// if no config yet, create the basic config required
this.pluginConfig = {
gateways: [],
};
} else {
// if config does exist, we pretty safely assume only one config block and take this out
this.pluginConfig = config[0];
if (!Array.isArray(this.pluginConfig.gateways)) {
this.pluginConfig.gateways = [];
}
}
},
data() {
/**
* This is reactive data - it can be accessed in any of the methods via this.key
* It can be used in the HTML directly via {{ key }}
*/
return {
view: 'gateways',
selectedGateway: null,
pluginConfig: {
gateways: [],
},
}
},
watch: {
/**
* This handler will be called whenever the object of data.pluginConfig changes
* Doing it likes this means we don't need to worry about keeping the UI in sync with plugin changes
* This will take care of everything as long as we keep data.pluginConfig correct
*/
pluginConfig: {
deep: true,
handler(newValue, oldValue) {
// need to do a deep copy to clean the object before sending it to the Homebridge UI
const config = JSON.parse(JSON.stringify(newValue))
homebridge.updatePluginConfig([config]);
}
},
},
methods: {
/**
* These are methods that can be called from the HTML.
* eg. <button @click="methodName"> or <button @click="methodName(someArg)">
*/
addGateway() {
// create an empty selected gateway
this.selectedGateway = {};
// set the view to edit-gateway
this.view = "edit-gateway";
// start the form
const gatewayForm = homebridge.createForm(gatewaySchema, {}, 'OK', 'Cancel');
gatewayForm.onChange((form) => {
// push changes as they happen into the selectedGateway object
this.selectedGateway = form;
});
gatewayForm.onSubmit((form) => {
// on save, push the new gateway object into the pluginConfig.gateways array
this.pluginConfig.gateways.push(form);
this.view = "gateways";
gatewayForm.end();
});
gatewayForm.onCancel((form) => {
this.view = "gateways";
gatewayForm.end();
});
},
editGateway(gateway) {
this.view = "edit-gateway";
// create a copy of the current gateway object
const source = JSON.parse(JSON.stringify(gateway))
// set the selectedGateway so we can access it in the template easily
this.selectedGateway = source;
// load the form
const gatewayForm = homebridge.createForm(gatewaySchema, source, 'OK', 'Cancel');
// on changes, update the selectedGateway
gatewayForm.onChange((form) => {
this.selectedGateway = form;
});
// on save, update the gateway object in the pluginConfig.gateways array
gatewayForm.onSubmit((form) => {
Object.assign(gateway, form);
this.view = "gateways";
gatewayForm.end();
});
// on cancel, just go back to the gateways view, not updating the pluginConfig.gateways array with any changes
gatewayForm.onCancel((form) => {
this.view = "gateways";
gatewayForm.end();
});
},
deleteGateway(index) {
// on delete, just remove it from the gateways array
this.pluginConfig.gateways.splice(index, 1);
},
connect() {
console.log('connect clicked for ', this.selectedGateway);
},
getApiKey() {
console.log('get api key clicked for ', this.selectedGateway);
}
}
});
/**
* Watch for the ready event, then start the vue app
*/
homebridge.addEventListener('ready', async () => {
console.log('ready')
myApp.mount('#app')
});
</script>