'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`
`;
};
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`
`;
};
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`
`;
};
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`
`;
};
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);