'use strict'; import {Component, h, html, render, useEffect, useState, useRef} from './preact.min.js'; const Nav = props => html`
Your Product
Logged in as: ${props.user} logout
`; const Footer = props => html`
Copyright (c) Your Company
`; const Hero = props => html`

Interactive Device Dashboard

This device dashboard is developed with modern and compact Preact framework, in order to fit on a very small devices. This is a hybrid server which provides both static and dynamic content. Static files, like CSS/JS/HTML or images, are compiled into the server binary. Dynamic content is served via the REST API. This dashboard shows values kept in server memory. Many clients can open this page. The JS code that watches state changes, reconnects on network failures. That means if server restarts, dashboard on all connected clients refresh automatically. NOTE: administrators can change settings values, whilst users cannot.

This UI uses the REST API implemented by the server, which you can examine using curl command-line utility:

curl localhost:8000/api/config/get - get current device configuration
curl localhost:8000/api/config/set -d 'value1=7&value2=hello' - set device configuration
curl localhost:8000/api/message/send -d 'msg=hello' - send chat message
curl localhost:8000/api/watch - get notifications: chat, config, metrics
`; const ShowSettings = function(props) { return html`

Device configuration

value1:
value2:
Server's corresponding runtime configuration structure:
struct config {
  int value1;
  char *value2;
} s_config;
    
`; }; const ChangeSettings = function(props) { const [value1, setValue1] = useState(''); const [value2, setValue2] = useState(''); useEffect(() => { setValue1(props.config.value1); setValue2(props.config.value2); }, [props.config.value1, props.config.value2]) const update = (name, val) => fetch('/api/config/set', { method: 'post', body: `${name}=${encodeURIComponent(val)}` }).catch(err => err); const updatevalue1 = ev => update('value1', value1); const updatevalue2 = ev => update('value2', value2); return html`

Change configuration

value1: setValue1(ev.target.value)} />
value2: setValue2(ev.target.value)} />
As soon as administrator updates configuration, server iterates over all connected clients and sends update notifications to all of them - so they update automatically.
`; }; const Login = function(props) { const [user, setUser] = useState(''); const [pass, setPass] = useState(''); const login = ev => fetch( '/api/login', {headers: {Authorization: 'Basic ' + btoa(user + ':' + pass)}}) .then(r => r.json()) .then(r => r && props.login(r)) .catch(err => err); return html`

Device Dashboard Login

setUser(ev.target.value)} value=${user} />
setPass(ev.target.value)} value=${pass} onchange=${login} />
Valid logins: admin:pass0, user1:pass1, user2:pass2
`; }; const Message = text => html`
${text}
`; const Chat = function(props) { const [message, setMessage] = useState(''); const sendmessage = ev => fetch('/api/message/send', { method: 'post', body: `message=${encodeURIComponent(message)}` }).then(r => setMessage('')); const messages = props.messages.map( text => html`
${text}
`); return html`

User chat

${messages}
setMessage(ev.target.value)} />
Chat demonsrates real-time bidirectional data exchange between many clients and a server.
`; }; const datefmt = unix => (new Date(unix * 1000)).toISOString().substr(14, 5); const Chart = function(props) { const chartdiv = useRef(null); const refresh = () => { const labels = props.metrics.map(el => datefmt(el[0])); const series = props.metrics.map(el => el[1]); const options = {low: 0, high: 20, showArea: true}; // console.log([labels, [series]]); new Chartist.Line(chartdiv.current, {labels, series: [series]}, options); }; useEffect(() => {chartdiv && refresh()}, [props.metrics]); return html`

Data Chart

`; }; const App = function(props) { const [messages, setMessages] = useState([]); const [metrics, setMetrics] = useState([]); const [user, setUser] = useState(''); const [config, setConfig] = useState({}); const refresh = () => fetch('/api/config/get', {headers: {Authorization: ''}}) .then(r => r.json()) .then(r => setConfig(r)); const login = function(u) { document.cookie = `access_token=${u.token};path=/;max-age=3600`; setUser(u.user); refresh(); }; const logout = ev => { document.cookie = `access_token=;path=/;max-age=0`; setUser(''); }; const watch = function() { var f = function(reader) { return reader.read().then(function(result) { var data = String.fromCharCode.apply(null, result.value); var msg = JSON.parse(data); if (msg.name == 'config') { refresh(); } else if (msg.name == 'message') { setMessages(m => m.concat([msg.data])); } else if (msg.name == 'metrics') { setMetrics(m => m.concat([msg.data]).splice(-10)); } // console.log(msg); if (!result.done) return f(reader); }); }; fetch('/api/watch', {headers: {Authorization: ''}}) .then(r => r.body.getReader()) .then(f) .catch(e => setTimeout(watch, 1000)); }; useEffect(() => { fetch('/api/login', {headers: {Authorization: ''}}) .then(r => r.json()) .then(r => login(r)) .catch(err => setUser('')); refresh(); watch(); }, []); if (!user) return html`<${Login} login=${login} />`; const admin = user == 'admin'; const cs = admin ? html`<${ChangeSettings} config=${config} />` : ''; return html` <${Nav} user=${user} logout=${logout} />
<${Hero} />
<${Chart} metrics=${metrics} />
<${ShowSettings} config=${config} />
<${Chat} messages=${messages} />
${cs}
<${Footer} />
`; }; window.onload = () => render(h(App), document.body);