Add chart example

This commit is contained in:
Sergey Lyubka 2022-05-23 15:27:19 +01:00
parent b3a4c12d67
commit c2d2d085cd
9 changed files with 5209 additions and 748 deletions

View File

@ -9,7 +9,7 @@ INCS ?= -Isrc -I.
SSL ?= MBEDTLS
CWD ?= $(realpath $(CURDIR))
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(CWD):$(CWD) -w $(CWD)
VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS)
VCFLAGS = /nologo /W3 /O2 /MD /I. $(DEFS) $(TFLAGS)
IPV6 ?= 1
ASAN ?= -fsanitize=address,undefined -fno-sanitize-recover=all
ASAN_OPTIONS ?= detect_leaks=1
@ -116,6 +116,10 @@ vc2017: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/vc2017 wine64 cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
$(DOCKER) mdashnet/vc2017 wine64 $@.exe
vc22: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/vc22 wine64 cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
$(DOCKER) mdashnet/vc22 wine64 $@.exe
mingw: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/mingw i686-w64-mingw32-gcc $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o test.exe
$(DOCKER) mdashnet/vc98 wine test.exe

View File

@ -7,6 +7,7 @@ void device_dashboard_fn(struct mg_connection *, int, void *, void *);
int main(void) {
struct mg_mgr mgr;
mg_log_set("3");
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:8000", device_dashboard_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 1000);

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

@ -70,15 +70,25 @@ static void send_notification(struct mg_mgr *mgr, const char *name,
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W')
mg_http_printf_chunk(c, "{\"name\": \"%s\", \"data\": \"%s\"}", name,
data == NULL ? "" : data);
mg_http_printf_chunk(c, "{\"name\": \"%s\", \"data\": %s}", name, data);
}
}
// Send simulated metrics data to the dashboard, for chart rendering
static void timer_func(void *param) {
char buf[50];
mg_snprintf(buf, sizeof(buf), "[ %lu, %d ]", (unsigned long) time(NULL),
10 + (int) ((double) rand() * 10 / RAND_MAX));
// MG_INFO(("%s", buf));
send_notification(param, "metrics", buf);
}
// HTTP request handler function
void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_OPEN && c->is_listening) {
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_func, c->mgr);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct user *u = getuser(hm);
// MG_INFO(("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
@ -97,14 +107,15 @@ void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
// Admins only
if (strcmp(u->name, "admin") == 0) {
if (update_config(hm, &s_config))
send_notification(fn_data, "config", NULL);
send_notification(fn_data, "config", "null");
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
} else {
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
}
} else if (mg_http_match_uri(hm, "/api/message/send")) {
char buf[256];
if (mg_http_get_var(&hm->body, "message", buf, sizeof(buf)) > 0) {
if (mg_http_get_var(&hm->body, "message", buf + 1, sizeof(buf) - 2) > 0) {
buf[0] = buf[strlen(buf)] = '"';
send_notification(fn_data, "message", buf);
}
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
@ -118,7 +129,7 @@ void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
mg_http_printf_chunk(c, "");
} else {
struct mg_http_serve_opts opts = {0};
#if 1
#if 0
opts.root_dir = "/web_root";
opts.fs = &mg_fs_packed;
#else

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,10 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="chartist.min.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body></body>
<script src="chartist.min.js"></script>
<script type="module" src="main.js"></script>
</html>

View File

@ -1,5 +1,5 @@
'use strict';
import {Component, h, html, render, useEffect, useState} from './preact.min.js';
import {Component, h, html, render, useEffect, useState, useRef} from './preact.min.js';
const Nav = props => html`
<div style="background: #333; padding: 0.5em; color: #fff;">
@ -50,7 +50,7 @@ const Hero = props => html`
<div><code>curl localhost:8000/api/config/get</code> - get current device configuration</div>
<div><code>curl localhost:8000/api/config/set -d 'value1=7&value2=hello'</code> - set device configuration</div>
<div><code>curl localhost:8000/api/message/send -d 'msg=hello'</code> - send chat message</div>
<div><code>curl localhost:8000/api/watch</code> - get notifications on config changes or chat messages</div>
<div><code>curl localhost:8000/api/watch</code> - get notifications: chat, config, metrics</div>
</div>`;
@ -182,8 +182,31 @@ const Chat = function(props) {
</div>`;
};
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`
<div style="margin: 0 0.3em;">
<h3 style="background: #ec3; color: #fff; padding: 0.4em;">Data Chart</h3>
<div style="height: 14em; overflow: auto; padding: 0.5em; " class="border">
<div ref=${chartdiv} style="height: 100%;" />
</div>
</div>`;
};
const App = function(props) {
const [messages, setMessages] = useState([]);
const [metrics, setMetrics] = useState([]);
const [user, setUser] = useState('');
const [config, setConfig] = useState({});
@ -206,12 +229,15 @@ const App = function(props) {
var f = function(reader) {
return reader.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
var notification = JSON.parse(data);
if (notification.name == 'config') refresh();
if (notification.name == 'message') {
setMessages(m => m.concat([notification.data]))
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(notification);
// console.log(msg);
if (!result.done) return f(reader);
});
};
@ -232,16 +258,16 @@ const App = function(props) {
if (!user) return html`<${Login} login=${login} />`;
const admin = user == 'admin';
const colsize = admin ? 'c4' : 'c6';
const cs = admin ? html`<${ChangeSettings} config=${config} />` : '';
return html`
<${Nav} user=${user} logout=${logout} />
<div class="container">
<${Hero} />
<div class="row">
<div class="col ${colsize}"><${ShowSettings} config=${config} /></div>
<div class="col ${colsize}">${cs}</div>
<div class="col ${colsize}"><${Chat} messages=${messages} /></div>
<div class="col c6"><${Chart} metrics=${metrics} /></div>
<div class="col c6"><${ShowSettings} config=${config} /></div>
<div class="col c6"><${Chat} messages=${messages} /></div>
<div class="col c6">${cs}</div>
</div>
<${Footer} />
</div>`;