const {h, render, Component} = preact; const html = htm.bind(h); class Login extends Component { state = {user: '', pass: ''}; onclick = (app) => { const authhdr = 'Basic ' + btoa(this.state.user + ':' + this.state.pass); const headers = {Authorization: authhdr}; return axios.get('/api/login', {headers}) .then(res => app.setUser(res.data.token)) .catch(err => alert('Login failed')); }; onpassinput = (ev) => this.setState({pass: ev.target.value}); onuserinput = (ev) => this.setState({user: ev.target.value}); render({app}, {user, pass, signup}) { return html`

Login

this.onclick(app)} />
Valid logins: admin:admin, user1:pass1, user2:pass2
` } }; class Dropdown extends Component { state = {show: false}; render(props, state) { const onclick = x => this.setState({show: x}); const show = state.show ? 'show' : ''; return html` `; } }; const NavLink = ({href, title, url}) => html`${title}`; class Header extends Component { state = {expanded: false}; ontoggle = () => this.setState({expanded: !this.state.expanded}); render(props, state) { const u = props.app.state.user || {}; return html` ` } }; const Info = () => html`
This dashboard shows values kept in server memory. Many clients can open this page. As soon as any client changes any value, all clients update automatically. The JS code that watches state changes, reconnects on network failures. That means if server restarts, dashboard on all connected clients refresh automatically. You can use curl command-line utility:
curl localhost:8000/api/config/get
curl localhost:8000/api/config/watch
curl localhost:8000/api/config/set -d '{"a":123}'
NOTE: administrators can change settings values, whilst users cannot.
`; class DashVideo extends Component { render(props, state) { const onclick = ev => axios.post( '/api/config/set', {value1: +state.value1, value2: state.value2}); // alert(JSON.stringify(state)); return html`
Change values
Value 1 (number):
Value 2 (string):
` } }; class DashForm extends Component { render(props, state) { const onclick = ev => axios.post( '/api/config/set', {value1: +state.value1, value2: state.value2}); // alert(JSON.stringify(state)); return html`
Change values
Value 1 (number):
Value 2 (string):
` } }; class DashSettings extends Component { state = {value1: null, value2: null}; componentDidMount() { axios.get('/api/config/get') .then(r => this.setState(r.data)) .catch(e => console.log(e)); var self = this; var f = function(reader) { return reader.read().then(function(result) { var data = String.fromCharCode.apply(null, result.value); self.setState(JSON.parse(data)); // console.log(JSON.parse(data)); if (!result.done) return f(reader); }); }; fetch('/api/config/watch') .then(r => r.body.getReader()) .then(f) .catch(e => setTimeout(x => self.componentDidMount(), 1000)); } render(props, state) { return html `
Value 1 (number):
${state.value1}
Value 2 (string):
${state.value2}
` } }; class AdminDashboard extends Component { render(props, state) { return html`
<${Info} />
Settings
<${DashSettings} />
Video stream
Change settings
<${DashForm} />
` } } class UserDashboard extends Component { render(props, state) { return html`
<${Info} />
Settings
<${DashSettings} />
Video stream
` } } class Logs extends Component { state = {live: '', static: ''}; componentDidMount() { var self = this; var f = function(r) { return r.read().then(function(result) { var data = String.fromCharCode.apply(null, result.value); var live = self.state.live + data; self.setState({live}); // Append live log if (!result.done) f(r); // Read next chunk }); }; fetch('/api/log/static') .then(r => r.text()) .then(r => { self.setState({static: r}); }) .then(r => fetch('/api/log/live')) .then(r => r.body.getReader()) .then(f); } render(props, state) { return html`
A div below shows file log.txt, which is produced by the server. The server appends a new log message to that file every second, and a div below also shows that, implementing a "live log" feature. JS code on this page first fetches /api/log/static that returns log.txt contents, then fetches /api/log/live that does not return, but feeds chunks of data as they arrive (live log).

You can also use curl command-line utility to see live logs:
curl localhost:8000/api/log/live
Static
${state.static}
Live
${state.live}
` } } class App extends Component { state = {user: null, url: '/'}; setUser(token) { const maxAge = token ? 86400 : 0; document.cookie = `access_token=${token};path=/;max-age=${maxAge}`; // this.setState({token}); return axios.get('/api/login') .then(res => this.setState({user: res.data})) .catch(err => this.setState({user: {}})); } componentDidMount() { const getCookie = name => { var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); return v ? v[2] : ''; }; this.setUser(getCookie('access_token')); // Move from state1 to 2 or 3 } render(props, state) { // We have three states: // 1. Beginning. We don't know if we have a valid auth token or not // 2. We sent an /api/login request, and failed to login // 3. We sent an /api/login request, and succeeded to login if (!state.user) return ''; // State 1 if (!state.user.user) return h(Login, {app: this}); // State 2 return h( // State 3 'div', {}, h(Header, {url: state.url, app: this}), h(preactRouter.Router, { history: History.createHashHistory(), onChange: ev => this.setState({url: ev.url}), }, h(state.user.user == 'admin' ? AdminDashboard : UserDashboard, {default: true, app: this}), h(Logs, {path: 'logs', app: this}))); } }; window.onload = () => render(h(App), document.body);