hap-homematic
Version:
provides a homekit bridge to the ccu
1,153 lines (947 loc) • 37 kB
JavaScript
/*
* File: application.js
* Project: hap-homematic
* File Created: Tuesday, 10th March 2020 8:10:00 pm
* Author: Thomas Kluge (th.kluge@me.com)
* -----
* The MIT License (MIT)
*
* Copyright (c) Thomas Kluge <th.kluge@me.com> (https://github.com/thkl)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* ==========================================================================
*/
import {Network} from './network.js'
import {Button, Input, Grid, Label, DatabaseGrid, Dialog, CheckBox,ButtonInput} from './ui.js'
import {HAPWebSockets} from './sockets.js'
import {
NewDeviceWizzard, PublishDevicesSettingsWizzard, EditDeviceWizzard, DeleteDeviceWizzard,
NewHAPInstanceWizzard, DeleteHapInstanceWizzard, EditHapInstanceWizzard,
NewObjectWizzard, EditObjectWizzard, DeleteVariableWizzard,
RebootUpdateDialog, DeactivateInstanceWizzard, DeleteProgramWizzard,
InvalidCredentialsDialog, SettingsDialog, SupportDialog,BackupRestoreDialog,
SelectTriggerDialog,LostDevicesWizzard
} from './wizzards.js'
import {WelcomeWizzard} from './welcomewizzard.js'
import {Localization} from './localization.js'
export class Application {
constructor () {
// extract SID from the index url
var queryString = window.location.search
// prevent a bug
if (queryString.startsWith('?')) {
queryString = queryString.replace('?', '')
}
const urlParams = new URLSearchParams(queryString)
this.sid = urlParams.get('sid')
let network = new Network(this.sid)
this.makeApiRequest = network.makeApiRequest
this.makeFormRequest = network.makeFormRequest
}
createCookie (cookieName, cookieValue, daysToExpire) {
var date = new Date()
date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000))
document.cookie = cookieName + '=' + cookieValue + '; expires=' + date.toGMTString()
}
accessCookie (cookieName) {
var name = cookieName + '='
var allCookieArray = document.cookie.split(';')
for (var i = 0; i < allCookieArray.length; i++) {
var temp = allCookieArray[i].trim()
if (temp.indexOf(name) === 0) { return temp.substring(name.length, temp.length) }
}
return ''
}
userFriendlySeconds (seconds) {
if (seconds < 50) {
return this.__('less that 1 minute')
}
if (seconds < 3600) {
return this.__('%s min', Math.round((seconds / 60)))
}
if (seconds < 86400) {
let hr = Math.round((seconds / 60 / 60))
return (hr === 1) ? this.__('%s hour', hr) : this.__('%s hours', hr)
}
let d = Math.floor((seconds / 60 / 60 / 24))
return (d === 1) ? this.__('%s day', d) : this.__('%s days', d)
}
async buildOverview () {
let self = this
let oOv = $('#deviceOverview')
oOv.empty()
oOv.append(this.__('%s mapped device(s)', (this.deviceList)?this.deviceList.length :0 ))
oOv.append('<br /><br />')
if (this.variableList) {
oOv.append(this.__('%s mapped variable(s)', (this.variableList) ? this.variableList.length:0))
oOv.append('<br /><br />')
}
if (this.programList) {
oOv.append(this.__('%s mapped program(s)', (this.programList) ? this.programList.length : 0))
}
let bOv = $('#bridgeOverview')
bOv.empty()
bOv.append(this.__('%s running HAP instances', (this.bridges) ? this.bridges.length: 0 )).append('<br />')
if (this.bridges) {
this.bridges.map(bridge => {
bOv.append(bridge.displayName + ' (' + bridge.pincode + ')').append('<br />')
})
}
if (this.systemInfo) {
let numCores = this.systemInfo.cpu.length
let coreInfo = this.systemInfo.cpu[0].model || 'unknown cpu'
let mem = (parseInt(this.systemInfo.mem) / 1000000).toFixed(2)
let sOv = $('#sysOverview')
sOv.empty()
sOv.append(this.__('%s cores %s', numCores, coreInfo)).append('<br />')
sOv.append(this.__('%s MB free memory', mem)).append('<br />').append('<br />')
sOv.append(this.__('CCU Uptime %s', this.userFriendlySeconds(this.systemInfo.uptime))).append('<br />')
sOv.append(this.__('HAP Uptime %s', this.userFriendlySeconds(this.systemInfo.hapuptime))).append('<br />').append('<br />')
sOv.append(this.__('Version %s', this.systemInfo.version)).append('<br />')
if (this.systemInfo.version !== this.systemInfo.update) {
let update = $('<button>').attr('type', 'button').addClass('btn btn-info').append(this.__('update to %s', this.systemInfo.update))
sOv.append(update)
update.bind('click', async () => {
self.updateDialog = new RebootUpdateDialog(this)
self.updateDialog.setProceed(async () => {
try {
await this.makeApiRequest({method: 'update'})
} catch (e) {
// on some systems the call will run into a exception cause the server is gone allready
console.log('this error is expected')
}
setTimeout(() => {
self.waitForReboot()
}, 30000)
})
let chlog = await self.makeApiRequest({method: 'updateChangelog'}, 'text')
let converter = new showdown.Converter()
self.updateDialog.setProceedLabel(self.__('Update HAP Plugin'))
self.updateDialog.run(converter.makeHtml(chlog))
})
update.attr('style', 'cursor:pointer')
}
this.debugMode = ((self.systemInfo.debug === true) || (self.systemInfo.debug === 'true'))
$('#lbl_btn_debug').html(this.debugMode ? this.__('Disable Debug') : this.__('Enable Debug'))
}
}
buildDeviceList () {
let self = this
$('#containerTitle').html(self.__('Devices'))
this.activateMenuItem('showDevices')
let deviceContainer = $('#container')
let chartContainer = $('<div>')
chartContainer.attr('id','chart_container')
let containerFooter = $('#container_footer')
deviceContainer.empty()
deviceContainer.append(chartContainer)
let grid = new DatabaseGrid('deviceList', undefined, {})
grid.setBeforeQuery(()=> {
grid.dataset = self.deviceList // will fix #83
})
self.currentGrid = grid
grid.setTitleLabels([self.__('Address'), self.__('Homekit name'), self.__('Service'), self.__('Instance')])
grid.setColumns([
{sz: {sm: 6, md: 2, lg: 2, xl: 2}, sort: 0},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}, sort: 1},
{sz: {sm: 6, md: 2, lg: 2, xl: 3}, sort: 2},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}, sort: 3},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}},
{sz: {sm: 6, md: 2, lg: 2, xl: 1}}
])
grid.addSearchBar(self.__('Search'), self.__('Clear'), (element, filter) => {
return (((element.name) && (element.name.toLowerCase().indexOf(filter.toLowerCase()) > -1)) ||
((element.serial) && (element.serial.toLowerCase().indexOf(filter.toLowerCase()) > -1)))
})
grid.sortCallback = (column, a, b) => {
switch (column) {
case 0:
return (a.serial + ':' + a.channel).localeCompare(b.serial + ':' + b.channel)
case 1:
return a.name.localeCompare(b.name)
case 2:
return a.serviceClass.localeCompare(b.serviceClass)
case 3:
let ba = this.getBridgeWithId(a.instanceID)
let bb = this.getBridgeWithId(b.instanceID)
return (ba.displayName).localeCompare(bb.displayName)
default:
return true
}
}
grid.columnSort = 0
grid.setRenderer((row, item) => {
let editButton = new Button('success', self.__('Edit'), (e, btn) => {
new EditDeviceWizzard(this).run(item)
}, true)
let deleteButton = new Button('danger', self.__('Delete'), (e, btn) => {
new DeleteDeviceWizzard(this).run(item)
}, true)
let badgeState = (item.isPublished === true) ? 'badge-success' : 'badge-secondary'
//check if instanceID is String
let bridgeNames = ''
let bridge = self.getBridgeWithId(item.instanceID)
bridgeNames = $('<span>').attr('class', 'badge ' + badgeState).append(bridge.displayName)
if ((item.settings) && (item.settings.instance)) {
if ((typeof item.settings.instance !== 'string') && (item.settings.instance.length > 1)) {
bridgeNames.append(' +' + (item.settings.instance.length - 1))
}
}
return ([
item.serial + ':' + item.channel,
item.name,
item.serviceClass,
bridgeNames,
editButton.render(),
deleteButton.render()])
})
deviceContainer.append(grid.render())
let newButton = new Button('primary', self.__('New'), async (e, btn) => {
let mapDevices = await this.makeApiRequest({method: 'newDevice'})
new NewDeviceWizzard(this).run(mapDevices.devices)
})
newButton.setStyle('float:right')
containerFooter.empty()
containerFooter.append(newButton.render())
}
getDeviceList () {
return this.deviceList || []
}
getBridges () {
return this.bridges || []
}
getRooms () {
return this.roomList || []
}
getBridgeWithId (id) {
return this.getBridges().filter(bridge => bridge.id === id)[0] || undefined
}
getServiceByAddress (chAddress) {
return this.getDeviceList().filter(device => device.serial + ':' + device.channel === chAddress)[0] || undefined
}
getRoombyId (roomID) {
return this.getRooms().filter(room => room.id === roomID)[0] || undefined
}
getRoombyChannelId (channelID) {
return this.getRooms().filter(room => (room.channels.indexOf(channelID) > -1))[0] || undefined
}
getVariableBySerial (varSerial) {
return this.variableList.filter(variable => variable.nameInCCU === varSerial)[0] || undefined
}
getProgramBySerial (progSerial) {
return this.programList.filter(program => program.nameInCCU === progSerial)[0] || undefined
}
getHapInstanceByRoomId (roomId) {
return this.bridges.filter(bridge => bridge.roomId === roomId)[0] || undefined
}
getChannelByAddress(chAddress) {
let dAdr = chAddress.split(':')[0]
let matchedDevices = this.ccuDevices.filter(device => (device.address === dAdr))
if ((matchedDevices) && (matchedDevices.length > 0)) {
return matchedDevices[0].channels.filter(channel=>(channel.address === chAddress))[0] || undefined
}
return undefined
}
getPredictedHapInstanceForChannel (channel) {
// first get the room
if (channel) {
let room = this.getRoombyChannelId(channel.id)
if (room) {
// get the rooms hapInstance
return this.getHapInstanceByRoomId(room.id)
}
}
}
showQR(item) {
let dialog = new Dialog({
dialogId: 'selectorDialog',
buttons: [
self.dissmissButton
],
title: 'QR Setup Code',
dialogClass: 'modal-success',
scrollable: true,
size: 'modal-md'
})
var qr = qrcode(4, 'L');
qr.addData(item.setupURI)
qr.make()
let content = $('<div>')
let img = $('<div>').addClass('homeKitCode')
let pin = item.pincode.replace(/-/g,'')
let lbl = $('<span>').append(pin.substring(0,4)+ '<br />'+pin.substring(4,8))
img.append(lbl)
img.append(qr.createImgTag(6,0))
content.append(img)
dialog.setBody(content)
dialog.open()
}
showInstances () {
let self = this
$('#containerTitle').html(self.__('HomeKit Instances'))
this.activateMenuItem('hapInstances')
let hapCcontainer = $('#container')
let containerFooter = $('#container_footer')
hapCcontainer.empty()
let grid = new DatabaseGrid('instList', this.bridges, {})
grid.setTitleLabels([self.__('Instance name'), 'Port', self.__('PinCode'), self.__('Published devices'), self.__('CCU Room')])
grid.setColumns([
{sz: {sm: 6, md: 3, lg: 3, xl: 3}, sort: 0},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}, sort: 4},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}}
])
grid.sortCallback = (column, a, b) => {
switch (column) {
case 0:
return (a.displayName).localeCompare(b.displayName)
case 4:
let roomA = self.getRoombyId(a.roomId) || {name: ''}
let roomB = self.getRoombyId(b.roomId) || {name: ''}
return roomA.name.localeCompare(roomB.name)
default:
return true
}
}
grid.columnSort = 4
grid.setRenderer((row, item) => {
let editButton = new Button('success', self.__('Edit'), (e, btn) => {
new EditHapInstanceWizzard(this).run(item)
}, true)
let deleteButton = new Button('danger', self.__('Delete'), (e, btn) => {
new DeleteHapInstanceWizzard(this).run(item)
}, true)
let deactivateButton = new Button('danger', self.__('Deactivate'), (e, btn) => {
new DeactivateInstanceWizzard(this).run(item)
}, true)
// b6589fc6-ab0d-4c82-8f12-099d1c2d40ab is the default ID
if (item.id === 'b6589fc6-ab0d-4c82-8f12-099d1c2d40ab') {
deleteButton.setActive(false)
editButton.setActive(false)
}
deactivateButton.setActive(item.hasPublishedDevices)
var showFirewallHint = false
// show a hint when ccu will block this port
let strPortLabel = new Label('strPortLabel' + item.id)
strPortLabel.setLabel(item.port + ((item.ccuFirewall) ? '' : '(!!)'))
if (!item.ccuFirewall) {
strPortLabel.setStyle('color:red')
}
let room = self.getRoombyId(item.roomId)
let pinLabel = new Label('pinLabel' + item.id)
pinLabel.setLabel(item.pincode)
if (item.user === item.user.toUpperCase()) {
pinLabel.setStyle('cursor:pointer;text-decoration:underline;')
}
pinLabel.label.bind('click',(e)=>{
self.showQR(item)
})
return ([item.displayName,
strPortLabel.render(),
pinLabel.render(),
item.hasPublishedDevices ? self.__('Yes') : self.__('No'),
(room !== undefined) ? room.name : '',
editButton.render(),
deleteButton.render(),
deactivateButton.render()
])
})
hapCcontainer.append(grid.render())
let newButton = new Button('primary', self.__('New'), async (e, btn) => {
new NewHAPInstanceWizzard(this).run()
})
newButton.setStyle('float:right')
containerFooter.empty()
let fwHintlabel = new Label('fwHint')
fwHintlabel.setLabel(this.__('(!!) = Please make sure, that these ports are not blocked by your CCU firewall.'))
containerFooter.append(fwHintlabel.render())
containerFooter.append(newButton.render())
}
showSpecial () {
let self = this
$('#containerTitle').html(self.__('Special devices'))
this.activateMenuItem('showSpecialDevices')
let container = $('#container')
let containerFooter = $('#container_footer')
container.empty()
let grid = new DatabaseGrid('specialList', undefined , {})
grid.getDataset = ()=> {return self.specialList}
self.currentGrid = grid
grid.setTitleLabels([self.__('HomeKit name'), self.__('Service'), self.__('Instance name')])
grid.setColumns([
{sz: {sm: 6, md: 3, lg: 3, xl: 3}},
{sz: {sm: 6, md: 3, lg: 3, xl: 3}},
{sz: {sm: 6, md: 3, lg: 3, xl: 3}},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}},
{sz: {sm: 6, md: 1, lg: 1, xl: 1}}
])
grid.setRenderer((row, item) => {
let editButton = new Button('success', self.__('Edit'), (e, btn) => {
let wz = new EditDeviceWizzard(this)
wz.onExit = () => {
}
wz.run(item)
}, true)
let deleteButton = new Button('danger', self.__('Delete'), (e, btn) => {
let wz = new DeleteDeviceWizzard(this)
wz.onExit = () => {
}
wz.run(item)
}, true)
let bridge = self.getBridgeWithId(item.instanceID)
let badgeState = (item.isPublished === true) ? 'badge-success' : 'badge-secondary'
return ([item.name,
item.serviceClass,
$('<span>').attr('class', 'badge ' + badgeState).append(bridge.displayName),
editButton.render(),
deleteButton.render()
])
})
container.append(grid.render())
let newButton = new Button('primary', self.__('New'), async (e, btn) => {
let wzNew = new EditDeviceWizzard(this)
wzNew.onExit = () => {
}
wzNew.run({
settings: {settings: {}},
name: 'Name',
serial: 'new',
channel: 'special',
uuid: 'new'
})
})
newButton.setStyle('float:right')
containerFooter.empty()
containerFooter.append(newButton.render())
}
showVariables () {
let self = this
$('#containerTitle').html(self.__('Variables'))
this.activateMenuItem('showVariables')
let hapCcontainer = $('#container')
let containerFooter = $('#container_footer')
hapCcontainer.empty()
let grid = new DatabaseGrid('varList', undefined, {})
grid.setBeforeQuery(()=> {
grid.dataset = self.variableList // will fix #83
})
self.currentGrid = grid
grid.setTitleLabels([self.__('HomeKit name'), self.__('HomeMatic name'), self.__('Instance name')])
grid.setColumns([
{sz: {sm: 6, md: 3, lg: 3, xl: 3}, sort: 0},
{sz: {sm: 6, md: 3, lg: 3, xl: 3}, sort: 1},
{sz: {sm: 6, md: 2, lg: 3, xl: 3}, sort: 2},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}},
{sz: {sm: 6, md: 2, lg: 1, xl: 1}}
])
grid.sortCallback = (column, a, b) => {
switch (column) {
case 0:
return (a.nameInCCU).localeCompare(b.nameInCCU)
case 1:
return a.name.localeCompare(b.name)
case 2:
let ba = this.getBridgeWithId(a.instanceID)
let bb = this.getBridgeWithId(b.instanceID)
return (ba.displayName).localeCompare(bb.displayName)
default:
return true
}
}
grid.columnSort = 0
grid.setRenderer((row, item) => {
let editButton = new Button('success', self.__('Edit'), (e, btn) => {
let wz = new EditObjectWizzard(self, self.__('Edit variable'))
wz.setServices(self.variableServices)
wz.willSave((wizzard) => {
wizzard.objectData.method = 'saveVariable'
wizzard.objectData.settings = JSON.stringify(wizzard.objectData.settings)
})
wz.onClose(() => {
self.refreshVariables()
})
wz.run(item)
}, true)
let deleteButton = new Button('danger', self.__('Delete'), (e, btn) => {
new DeleteVariableWizzard(this).run(item)
}, true)
let bridge = self.getBridgeWithId(item.instanceID)
let badgeState = (item.isPublished === true) ? 'badge-success' : 'badge-secondary'
return ([item.name, item.nameInCCU, $('<span>').attr('class', 'badge ' + badgeState).append(bridge.displayName), editButton.render(), deleteButton.render()])
})
hapCcontainer.append(grid.render())
let newButton = new Button('primary', self.__('New'), async (e, btn) => {
let mapVariables = await this.makeApiRequest({method: 'newVariable'})
let wz = new NewObjectWizzard(this, this.__('Add new Variable'), this.__('Setup variable'))
wz.setServices(self.variableServices)
wz.willSave((wizzard) => {
wizzard.objectData.method = 'saveVariable'
wizzard.objectData.settings = JSON.stringify(wizzard.objectData.settings)
})
wz.onClose(() => {
self.refreshVariables()
})
wz.checkObjectIsMapped((item) => {
return self.getVariableBySerial(item.nameInCCU)
})
wz.setListTitles([self.__('Variable'), self.__('Description')])
wz.run(mapVariables.variables)
})
newButton.setStyle('float:right')
let varTrigger = new ButtonInput('varTrigger', this.variableTrigger, this.__('Select'), null, async (e, input) => {
let obj = await self.makeApiRequest({method : 'virtualKeys'})
var channels = []
obj.virtualKeys.map((device)=>{
device.channels.map((channel)=>{
channel.ifName = device.ifName
channels.push({title:channel.name,value:channel.ifName + '.' + channel.address + '.PRESS_SHORT'})
})
})
let dialog = new SelectTriggerDialog(self)
dialog.setTrigger(self.variableTrigger)
dialog.setKeyList(channels)
dialog.setProceed((newValue)=>{
varTrigger.setValue(newValue)
})
dialog.run()
})
varTrigger.setStyle('width:100%')
varTrigger.setGroupLabel('Trigger')
let autoUpdateControl = new CheckBox('autoUpdate', this.autoUpdateVarTriggerHelper, (e, input) => {
self.autoUpdateVarTriggerHelper = input.checked
})
autoUpdateControl.setLabel(this.__('Create/Update the CCU helper program.'))
let updateTriggerButton = new Button('primary', self.__('Update Trigger'), async (e, btn) => {
let triggerDp = varTrigger.getValue()
if (triggerDp) {
await self.makeApiRequest({method: 'saveVariableTrigger', datapoint: triggerDp,autoUpdateVarTriggerHelper:self.autoUpdateVarTriggerHelper})
self.refreshVariables()
}
})
updateTriggerButton.setStyle('float:right')
containerFooter.empty()
let gridFooter = new Grid()
gridFooter.addRow().addCell({sm: 12, md: 12, lg: 12, xl: 12}, newButton.render())
let triggerRow = gridFooter.addRow('', {rowStyle: 'margin-bottom:15px'})
triggerRow.addCell({sm: 12, md: 6, lg: 6, xl: 6}, varTrigger.render())
triggerRow.addCell({sm: 12, md: 6, lg: 3, xl: 3}, autoUpdateControl.render())
triggerRow.addCell({sm: 12, md: 6, lg: 2, xl: 2}, updateTriggerButton.render())
gridFooter.addRow().addCell({sm: 12, md: 12, lg: 12, xl: 12}, new Label(this.__('This has to be a KEY datapoint. All variables will be updated on an event at this KEY')).render())
containerFooter.append(gridFooter.render())
}
showPrograms () {
let self = this
$('#containerTitle').html(self.__('Programs'))
this.activateMenuItem('showPrograms')
let hapCcontainer = $('#container')
let containerFooter = $('#container_footer')
hapCcontainer.empty()
let grid = new DatabaseGrid('progList', this.programList, {})
grid.setTitleLabels([self.__('HomeKit name'), self.__('HomeMatic name'), self.__('Instance name')])
grid.setColumns([
{sz: {sm: 6, md: 3, lg: 3, xl: 3}, sort: 0},
{sz: {sm: 6, md: 3, lg: 3, xl: 3}, sort: 1},
{sz: {sm: 6, md: 2, lg: 3, xl: 3}, sort: 2},
{sz: {sm: 6, md: 2, lg: 2, xl: 2}},
{sz: {sm: 6, md: 2, lg: 1, xl: 1}}
])
grid.sortCallback = (column, a, b) => {
switch (column) {
case 0:
return a.name.localeCompare(b.name)
case 1:
return (a.nameInCCU).localeCompare(b.nameInCCU)
case 2:
let ba = this.getBridgeWithId(a.instanceID)
let bb = this.getBridgeWithId(b.instanceID)
return (ba.displayName).localeCompare(bb.displayName)
default:
return true
}
}
grid.columnSort = 0
grid.setRenderer((row, item) => {
let editButton = new Button('success', self.__('Edit'), (e, btn) => {
let wz = new EditObjectWizzard(self, self.__('Edit program'))
wz.willSave((wizzard) => {
wizzard.objectData.method = 'saveProgram'
})
wz.onClose(() => {
self.refreshPrograms()
})
wz.run(item)
}, true)
let deleteButton = new Button('danger', self.__('Delete'), (e, btn) => {
let wz = new DeleteProgramWizzard(self, self.__('Remove program'))
wz.run(item)
}, true)
let bridge = self.getBridgeWithId(item.instanceID)
let badgeState = (item.isPublished === true) ? 'badge-success' : 'badge-secondary'
return ([item.name, item.nameInCCU, $('<span>').attr('class', 'badge ' + badgeState).append(bridge.displayName), editButton.render(), deleteButton.render()])
})
hapCcontainer.append(grid.render())
let newButton = new Button('primary', self.__('New'), async (e, btn) => {
let mapPrograms = await this.makeApiRequest({method: 'newProgram'})
let wz = new NewObjectWizzard(this, this.__('Add new program'), this.__('Setup program'))
wz.willSave((wizzard) => {
wizzard.objectData.method = 'saveProgram'
})
wz.onClose(() => {
self.refreshPrograms()
})
wz.checkObjectIsMapped((item) => {
return self.getProgramBySerial(item.nameInCCU)
})
wz.setListTitles([self.__('Program'), self.__('Description')])
wz.run(mapPrograms.programs)
})
newButton.setStyle('float:right')
containerFooter.empty()
containerFooter.append(newButton.render())
}
async publish (bridgeId) {
var bridgesToPublishDevices = []
this.bridges.map(bridge => {
if ((bridge.publish === true) || (bridge.id === bridgeId)) {
bridgesToPublishDevices.push(bridge.id)
}
})
await this.makeApiRequest({method: 'publish', bridges: JSON.stringify(bridgesToPublishDevices)})
}
async refreshBridges () {
this.bridges = await this.makeApiRequest({method: 'bridges'})
this.buildOverview()
this.showInstances()
}
async refreshVariables () {
let tmp = await this.makeApiRequest({method: 'variablelist'})
if (tmp) {
this.variableList = tmp.variables
this.variableTrigger = tmp.trigger
this.autoUpdateVarTriggerHelper = tmp.autoUpdateVarTriggerHelper
}
this.showVariables()
}
async refreshPrograms () {
let tmp = await this.makeApiRequest({method: 'programlist'})
if (tmp) {
this.programList = tmp.programs
}
this.showPrograms()
}
checkPermissionFromError (e) {
let self = this
if ((e.status === 401) && (!this.errorShown)) {
this.errorShown = true
let dialog = new InvalidCredentialsDialog(this)
dialog.onClose = () => {
self.errorShown = false
}
dialog.run()
}
}
restoreBackupWizzaard() {
let self = this
let dialog = new BackupRestoreDialog(this)
dialog.setProceedRestore( async (fileToUpload)=>{
let frm = new FormData();
frm.append('method','restore')
frm.append('file',fileToUpload,'backup.tar.gz')
await self.makeFormRequest('/restore/',frm)
dialog.close()
})
dialog.setProceedBackup(()=>{
dialog.close()
window.location.href = '/api/?method=backup&sid=' + self.sid
})
dialog.run()
}
async refreshAll () {
try {
await this.makeApiRequest({method: 'refresh'})
} catch (e) {
this.checkPermissionFromError(e)
}
}
async getAllVariables() {
let vr = await this.makeApiRequest({method: 'allVariables'})
return vr.variables
}
activateMenuItem (item) {
let menuItems = ['showDevices', 'showVariables', 'showPrograms', 'showSpecialDevices', 'hapInstances']
menuItems.map(menuItem => {
if (menuItem !== item) {
$('#' + menuItem).removeClass('c-active')
} else {
$('#' + menuItem).addClass('c-active')
}
})
}
async loadGraphList() {
// Load the available graphes
let self = this
let chartContainer = $('#chart_container')
chartContainer.attr('style','margin-bottom:20px')
chartContainer.empty()
let graphList = await self.makeApiRequest({method:'listGraph'})
graphList.map( async (graph) => {
let graphId = graph.id
let data = await self.makeApiRequest({method:'graphDetail',id:graphId})
let labels = []
let cdata = []
let canvas = $('<canvas>')
canvas.attr('id',graphId)
canvas.attr('style','height:300px')
chartContainer.append(canvas)
data.map((dt)=>{
let date = new Date(dt.timestamp * 1000)
labels.push(date.getDate() +'.'+date.getMonth()+'.'+date.getFullYear()+' '+date.getHours()+':'+date.getMinutes())
cdata.push(parseInt(dt.value))
})
let chartColor = '#4a87ff'
var ctx = document.getElementById(graphId).getContext('2d')
var gradientFill = ctx.createLinearGradient(0, 200, 0, 50)
gradientFill.addColorStop(0, 'rgba(128, 182, 244, 0)')
gradientFill.addColorStop(1, 'rgba(255, 255, 255, 0.24)')
let chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: graph.name,
backgroundColor: gradientFill,
borderColor: chartColor,
pointBorderWidth: 0,
pointHoverRadius: 0,
pointHoverBorderWidth: 0,
pointRadius: 0,
data: cdata
}]
},
options: {
maintainAspectRatio: false,
responsive: true
}
})// eslint-disable-next-line no-unused-vars
})
}
async checkLostAndFound() {
// do not show when the wizzard is up
let lostDevices = await this.makeApiRequest({method:'checklost'})
if ((lostDevices) && (lostDevices.length > 0)) {
if (this.welcomeWizzard) {
return
}
let ldWizzard = new LostDevicesWizzard(this)
ldWizzard.run(lostDevices)
}
}
hook () {
let self = this
$('#showDevices').bind('click', (e) => {
$('#breadcrum_page').html(this.__('Devices'))
self.buildDeviceList(0,10)
self.loadGraphList()
})
$('#publishingSettings').bind('click', (e) => {
new PublishDevicesSettingsWizzard(this).run()
})
$('#hapInstances').bind('click', (e) => {
self.showInstances()
$('#breadcrum_page').html(this.__('HomeKit Instances'))
})
$('#showVariables').bind('click', (e) => {
self.showVariables()
$('#breadcrum_page').html(this.__('Variables'))
})
$('#showPrograms').bind('click', (e) => {
self.showPrograms()
$('#breadcrum_page').html(this.__('Programs'))
})
$('#showSpecialDevices').bind('click', (e) => {
self.showSpecial()
$('#breadcrum_page').html(this.__('Special devices'))
})
$('#btn_debug').bind('click', async (e) => {
await self.makeApiRequest({method: 'debug', enable: !self.debugMode})
})
$('#btn_support').bind('click', () => {
let dlg = new SupportDialog(this)
dlg.setProceed((serial) => {
window.location.href = '/api/?method=support&address=' + serial
})
dlg.run()
})
$('#btn_restart').bind('click', async () => {
self.updateDialog = new RebootUpdateDialog(this)
self.updateDialog.setProceed(async (enableLog) => {
self.makeApiRequest({method: 'restart', debug: enableLog})
setTimeout(() => {
self.waitForReboot()
}, 2000)
})
self.updateDialog.run(self.__('Restart HAP Plugin ?'))
})
$('#btn_changelog').bind('click', async () => {
let cl = await this.makeApiRequest({method: 'changelog'}, 'text')
let dialog = new Dialog({
dialogId: 'changeLog',
buttons: [
new Button('light', 'Oh, i got it', (e, btn) => {
dialog.close()
}, true)
],
title: self.__('Changelog'),
dialogClass: 'modal-info',
scrollable: true,
size: 'modal-xl'
})
let converter = new showdown.Converter()
dialog.setBody(converter.makeHtml(cl))
dialog.open()
})
$('#btn_settings').bind('click', () => {
let sad = new SettingsDialog(this)
sad.setProceed(async (settings) => {
await this.makeApiRequest({method: 'saveSettings', settings: JSON.stringify(settings)})
})
sad.run()
})
$('#btn_dnlog').bind('click', () => {
window.location.href = '/api/?method=getLog&sid=' + self.sid
})
$('#sidebartoggler').bind('click', () => {
let isMinimized = $('#sidebar').hasClass('c-sidebar-minimized')
// save this in cookie fucking yeah cookies theese are sooo delicoious
this.createCookie('sidebar', isMinimized, 365)
})
$('#btn_refreshCache').bind('click', async ()=>{
await this.makeApiRequest({method: 'refreshCache'})
$('#toastMessage').html(self.__('Cache updated ...'))
$('#toast').toast('show')
})
$('#btn_backup').bind('click',()=>{
this.restoreBackupWizzaard()
})
}
// this will drive me nuts .. i can feel it
__ () {
return this.localizer.localize.apply(this.localizer, arguments)
}
async checkWizzard() {
let self = this
// check if we have 1 bridge and no devices cause this may look like a new installation
if ((this.bridges) && (this.deviceList) && (this.deviceList.length === 0) && (this.welcomeWizzard === undefined)) {
let mapDevices = await this.makeApiRequest({method: 'wizzardRooms'})
this.welcomeWizzard = new WelcomeWizzard(this)
this.welcomeWizzard.setOnClose(()=>{
self.welcomeWizzard = undefined
})
if (this.bridges.length === 1) {
this.welcomeWizzard.run(mapDevices, 0)
} else {
this.welcomeWizzard.run(mapDevices, 2)
}
}
}
async waitForReboot () {
let self = this
this.makeApiRequest({method: 'bridges'}).then((result) => {
if (self.updateDialog) {
self.updateDialog.close()
}
self.refreshAll()
}).catch((e) => {
if (e.status === 401) {
self.checkPermissionFromError(e)
}
setTimeout(() => {
self.waitForReboot()
}, 2000)
})
}
async run () {
let self = this
$('#toast').toast({animation:true,autohide:true,delay:2000})
// check sidebar cookie
let isMinimized = this.accessCookie('sidebar')
if (isMinimized === 'true') {
$('#sidebar').addClass('c-sidebar-minimized')
var sidebarEl = document.getElementById('sidebar')
var sidebar = new coreui.Sidebar(sidebarEl, false)
sidebar.minimize()
}
this.localizer = new Localization()
await this.localizer.init()
this.localizer.localizePage()
this.deviceList = []
this.variableList = []
this.programList = []
this.rooms = []
this.hook()
this.socket = new HAPWebSockets()
this.socket.initSocket( (socket,data) => {
if (data) {
// Process Socket Messages
switch (data.message) {
case 'heartbeat':
self.systemInfo = data.payload
self.buildOverview()
break
case 'ackn':
self.systemInfo = data.payload
self.buildOverview()
self.buildDeviceList(0,10)
self.loadGraphList()
self.refreshAll()
break
case 'serverdata':
if (data.payload) {
self.bridges = data.payload.bridges
self.deviceList = data.payload.accessories
self.variableList = data.payload.variables
self.variableTrigger = data.payload.variableTrigger
self.variableServices = data.payload.variableServices
self.autoUpdateVarTriggerHelper = data.payload.autoUpdateVarTriggerHelper
self.programList = data.payload.programs
self.roomList = data.payload.rooms
self.specialList = data.payload.special
self.ccuDevices = data.payload.ccuDevices
}
self.buildOverview()
if (self.currentGrid) {
self.currentGrid.requestRefresh()
// implement a refresh
self.currentGrid.refresh()
}
self.checkWizzard()
self.checkLostAndFound()
break
}
}
})
setTimeout( async ()=>{
await self.makeApiRequest({method: 'refreshCache'})
},2000)
}
}