--- /dev/null
+// Open Websocket back to ControlPi we were loaded from:
+const websocket = new WebSocket("ws://" + window.location.host)
+
+// When Websocket is ready request all clients from bus:
+websocket.addEventListener('open', function (event) {
+ websocket.send(JSON.stringify({target: '', command: 'get clients'}))
+})
+
+// Create section for client:
+function createForClient(client) {
+ const section = document.createElement('section')
+ section.setAttribute('id', client)
+ section.setAttribute('class', 'client')
+ section.innerHTML = `
+ <h2>${client}</h2>
+ <div id="${client} Receives" class="templatecontainer">
+ <h3>=></h3>
+ </div>
+ <div id="${client} Sends" class="templatecontainer">
+ <h3><=</h3>
+ </div>
+ <div id="${client} Last" class="lastcontainer">
+ </div>
+ `
+ return section
+}
+
+// Create div and table for template or message:
+function createForMessage(message, isMessage) {
+ const div = document.createElement('div')
+ div.setAttribute('class', 'message')
+ if (isMessage) {
+ // Current (receive) time as heading:
+ const time = new Date().toLocaleTimeString()
+ const h4 = document.createElement('h4')
+ const h4Content = document.createTextNode(time)
+ h4.appendChild(h4Content)
+ div.appendChild(h4)
+ }
+ if (Object.keys(message).length === 0) {
+ // Create span with '*' for empty templates:
+ const span = document.createElement('span')
+ const spanContent = document.createTextNode('*')
+ span.appendChild(spanContent)
+ div.appendChild(span)
+ } else {
+ const table = document.createElement('table')
+ for (const key in message) {
+ if (isMessage && key == 'sender') {
+ // Ignore 'sender' for messages (redundant in client heading):
+ continue
+ }
+ // Append table row for key-value pair:
+ const tr = document.createElement('tr')
+ const keyTd = document.createElement('td')
+ const keyTdContent = document.createTextNode(key + ':')
+ keyTd.appendChild(keyTdContent)
+ tr.appendChild(keyTd)
+ const valueTd = document.createElement('td')
+ value = JSON.stringify(message[key])
+ if (value.startsWith('"<class') && value.endsWith('>"')) {
+ // Remove quotes if class type instead of literal value:
+ value = value.replace(/^"|"$/g, '')
+ }
+ const valueTdContent = document.createTextNode(value)
+ valueTd.appendChild(valueTdContent)
+ tr.appendChild(valueTd)
+ table.appendChild(tr)
+ }
+ div.appendChild(table)
+ }
+ return div
+}
+
+// Resize input fields to current input:
+function resizeInput() {
+ const tmp = document.createElement('span')
+ // Hack: Should get this from CSS class rather than hardcode it:
+ tmp.setAttribute('style', 'font-size: 0.8rem;')
+ const tmpContent = document.createTextNode(this.value)
+ tmp.appendChild(tmpContent)
+ document.body.appendChild(tmp);
+ const width = tmp.getBoundingClientRect().width
+ document.body.removeChild(tmp);
+ this.style.width = width + 'px'
+}
+
+// Create div, form and table for command template:
+function createForCommand(template) {
+ const div = document.createElement('div')
+ div.setAttribute('class', 'message')
+ const form = document.createElement('form')
+ form.addEventListener("submit", function(e) {
+ // Convert names and values of all form elements to key-value pairs
+ // and send as JSON over websocket:
+ e.preventDefault();
+ var data = {};
+ for (const element of form.elements) {
+ if (element.type == 'text' || element.type == 'hidden') {
+ data[element.name] = element.value
+ } else if (element.type == 'number') {
+ data[element.name] = element.valueAsNumber
+ } else if (element.type == 'select-one') {
+ if (element.value == 'true') {
+ data[element.name] = true
+ } else if (element.value == 'false') {
+ data[element.name] = false
+ } else {
+ data[element.name] = element.value
+ }
+ }
+ }
+ websocket.send(JSON.stringify(data))
+ })
+ const table = document.createElement('table')
+ for (const key in template) {
+ // Append table row for key-value pair:
+ const tr = document.createElement('tr')
+ const keyTd = document.createElement('td')
+ const keyTdContent = document.createTextNode(key + ':')
+ keyTd.appendChild(keyTdContent)
+ tr.appendChild(keyTd)
+ const valueTd = document.createElement('td')
+ value = template[key]
+ // Create submit button for command itself:
+ if (key == 'command') {
+ const hidden = document.createElement('input')
+ hidden.setAttribute('type', 'hidden')
+ hidden.setAttribute('name', key)
+ hidden.setAttribute('value', value)
+ valueTd.appendChild(hidden)
+ const submit = document.createElement('input')
+ submit.setAttribute('type', 'submit')
+ submit.setAttribute('value', value)
+ valueTd.appendChild(submit)
+ // Create appropriate input fields for '<class ...>':
+ } else if (value == '<class \'str\'>') {
+ const openquote = document.createTextNode('"')
+ valueTd.appendChild(openquote)
+ const input = document.createElement('input')
+ input.setAttribute('type', 'text')
+ input.setAttribute('name', key)
+ input.setAttribute('value', '')
+ input.setAttribute('required', '')
+ input.addEventListener('input', resizeInput)
+ resizeInput.call(input)
+ valueTd.appendChild(input)
+ const closequote = document.createTextNode('"')
+ valueTd.appendChild(closequote)
+ } else if (value == '<class \'int\'>') {
+ const input = document.createElement('input')
+ input.setAttribute('type', 'number')
+ input.setAttribute('step', '1')
+ input.setAttribute('name', key)
+ input.setAttribute('value', '')
+ input.setAttribute('required', '')
+ input.addEventListener('input', resizeInput)
+ resizeInput.call(input)
+ valueTd.appendChild(input)
+ } else if (value == '<class \'float\'>') {
+ const input = document.createElement('input')
+ input.setAttribute('type', 'number')
+ input.setAttribute('step', 'any')
+ input.setAttribute('name', key)
+ input.setAttribute('value', '')
+ input.setAttribute('required', '')
+ input.addEventListener('input', resizeInput)
+ resizeInput.call(input)
+ valueTd.appendChild(input)
+ } else if (value == '<class \'bool\'>') {
+ const select = document.createElement('select')
+ select.setAttribute('name', key)
+ const optionTrue = document.createElement('option')
+ optionTrue.setAttribute('value', true)
+ const optionTrueContent = document.createTextNode('true')
+ optionTrue.appendChild(optionTrueContent)
+ select.appendChild(optionTrue)
+ const optionFalse = document.createElement('option')
+ optionFalse.setAttribute('value', false)
+ const optionFalseContent = document.createTextNode('false')
+ optionFalse.appendChild(optionFalseContent)
+ select.appendChild(optionFalse)
+ valueTd.appendChild(select)
+ // Create readonly input fields for literals:
+ } else if (typeof(value) == 'string') {
+ const openquote = document.createTextNode('"')
+ valueTd.appendChild(openquote)
+ const input = document.createElement('input')
+ input.setAttribute('type', 'text')
+ input.setAttribute('name', key)
+ input.setAttribute('value', value)
+ input.setAttribute('readonly', '')
+ input.addEventListener('input', resizeInput)
+ resizeInput.call(input)
+ valueTd.appendChild(input)
+ const closequote = document.createTextNode('"')
+ valueTd.appendChild(closequote)
+ } else if (typeof(value) == 'number') {
+ const input = document.createElement('input')
+ input.setAttribute('type', 'number')
+ input.setAttribute('name', key)
+ input.setAttribute('value', value)
+ input.setAttribute('readonly', '')
+ input.addEventListener('input', resizeInput)
+ resizeInput.call(input)
+ valueTd.appendChild(input)
+ } else if (typeof(value) == 'boolean') {
+ const select = document.createElement('select')
+ select.setAttribute('name', key)
+ const optionTrue = document.createElement('option')
+ optionTrue.setAttribute('value', 'true')
+ if (value) {
+ optionTrue.setAttribute('selected', '')
+ } else {
+ optionTrue.setAttribute('disabled', '')
+ }
+ const optionTrueContent = document.createTextNode('true')
+ optionTrue.appendChild(optionTrueContent)
+ select.appendChild(optionTrue)
+ const optionFalse = document.createElement('option')
+ optionFalse.setAttribute('value', 'false')
+ if (value) {
+ optionTrue.setAttribute('disabled', '')
+ } else {
+ optionTrue.setAttribute('selected', '')
+ }
+ const optionFalseContent = document.createTextNode('false')
+ optionFalse.appendChild(optionFalseContent)
+ select.appendChild(optionFalse)
+ valueTd.appendChild(select)
+ }
+ tr.appendChild(valueTd)
+ table.appendChild(tr)
+ }
+ form.appendChild(table)
+ div.appendChild(form)
+ return div
+}
+
+// When receiving message from ControlPi through Websocket:
+websocket.addEventListener('message', function (event) {
+ const message = JSON.parse(event.data)
+ if (message.sender == '') {
+ // When message is from bus:
+ if (message.event == 'unregistered') {
+ // On deregistration delete section if it exists:
+ const clientElement = document.getElementById(message.client)
+ if (clientElement != null) {
+ clientElement.remove()
+ }
+ } else {
+ // On registration or initial answers for all clients:
+ const clientElement = document.getElementById(message.client)
+ if (clientElement == null) {
+ // Create section for client if not existent:
+ const main = document.getElementById('ControlPi Debug')
+ main.appendChild(createForClient(message.client))
+ }
+ const receiveContainer = document.getElementById(message.client + ' Receives')
+ receiveContainer.innerHTML = '<h3>=></h3>'
+ for (const template of message.receives) {
+ if (template.command != null) {
+ const templateElement = createForCommand(template)
+ receiveContainer.appendChild(templateElement)
+ } else {
+ const templateElement = createForMessage(template)
+ receiveContainer.appendChild(templateElement)
+ }
+ }
+ const sendContainer = document.getElementById(message.client + ' Sends')
+ sendContainer.innerHTML = '<h3><=</h3>'
+ for (const template of message.sends) {
+ const templateElement = createForMessage(template)
+ sendContainer.appendChild(templateElement)
+ }
+ }
+ } else {
+ // When message is from client:
+ const clientElement = document.getElementById(message.sender)
+ if (clientElement == null) {
+ // Create section for client if not existent:
+ const main = document.getElementById('ControlPi Debug')
+ main.appendChild(createForClient(message.sender))
+ }
+ // Update last received message:
+ const lastContainer = document.getElementById(message.sender + ' Last')
+ lastContainer.innerHTML = ''
+ const messageElement = createForMessage(message, true)
+ lastContainer.appendChild(messageElement)
+ if (message.state != null) {
+ // If this is some kind of state set background
+ // green or red depending on it:
+ if (message.state) {
+ messageElement.classList.remove('red')
+ messageElement.classList.add('green')
+ } else {
+ messageElement.classList.remove('green')
+ messageElement.classList.add('red')
+ }
+ }
+ }
+})