mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-01 05:52:32 +07:00
Add device-dashboard/minimal
This commit is contained in:
+4
-4
@@ -3414,7 +3414,7 @@ struct mg_upload_state {
|
||||
|
||||
static inline void mg_dash_file_add(struct mg_dash *dash, struct mg_str name,
|
||||
size_t size) {
|
||||
struct mg_dash_file *f = mg_calloc(1, sizeof(*f));
|
||||
struct mg_dash_file *f = (struct mg_dash_file *) mg_calloc(1, sizeof(*f));
|
||||
f->name = mg_strdup(name).buf;
|
||||
f->size = size;
|
||||
f->next = dash->files;
|
||||
@@ -3477,16 +3477,16 @@ static void file_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
// Serve file
|
||||
char path[128];
|
||||
mg_snprintf(path, sizeof(path), "/tmp/%.*s", hm->uri.len, hm->uri.buf);
|
||||
mg_http_serve_file(c, ev_data, path, NULL);
|
||||
mg_http_serve_file(c, hm, path, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && us->marker == 'U') {
|
||||
// Write uploaded data
|
||||
MG_DEBUG(("Uploading.. recv.len=%u", c->recv.len));
|
||||
us->received += c->recv.len;
|
||||
fwrite(c->recv.buf, 1, c->recv.len, us->ctx);
|
||||
fwrite(c->recv.buf, 1, c->recv.len, (FILE *) us->ctx);
|
||||
c->recv.len = 0;
|
||||
if (us->received >= us->expected) {
|
||||
fclose(us->ctx);
|
||||
fclose((FILE *) us->ctx);
|
||||
MG_DEBUG(("Uploaded %lu", us->expected));
|
||||
mg_http_reply(c, 200, NULL, "%lu uploaded\n", us->expected);
|
||||
mg_dash_send_change(c->mgr, dash, "files");
|
||||
|
||||
+4
-4
@@ -269,7 +269,7 @@ struct mg_upload_state {
|
||||
|
||||
static inline void mg_dash_file_add(struct mg_dash *dash, struct mg_str name,
|
||||
size_t size) {
|
||||
struct mg_dash_file *f = mg_calloc(1, sizeof(*f));
|
||||
struct mg_dash_file *f = (struct mg_dash_file *) mg_calloc(1, sizeof(*f));
|
||||
f->name = mg_strdup(name).buf;
|
||||
f->size = size;
|
||||
f->next = dash->files;
|
||||
@@ -332,16 +332,16 @@ static void file_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
// Serve file
|
||||
char path[128];
|
||||
mg_snprintf(path, sizeof(path), "/tmp/%.*s", hm->uri.len, hm->uri.buf);
|
||||
mg_http_serve_file(c, ev_data, path, NULL);
|
||||
mg_http_serve_file(c, hm, path, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && us->marker == 'U') {
|
||||
// Write uploaded data
|
||||
MG_DEBUG(("Uploading.. recv.len=%u", c->recv.len));
|
||||
us->received += c->recv.len;
|
||||
fwrite(c->recv.buf, 1, c->recv.len, us->ctx);
|
||||
fwrite(c->recv.buf, 1, c->recv.len, (FILE *) us->ctx);
|
||||
c->recv.len = 0;
|
||||
if (us->received >= us->expected) {
|
||||
fclose(us->ctx);
|
||||
fclose((FILE *) us->ctx);
|
||||
MG_DEBUG(("Uploaded %lu", us->expected));
|
||||
mg_http_reply(c, 200, NULL, "%lu uploaded\n", us->expected);
|
||||
mg_dash_send_change(c->mgr, dash, "files");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
PROG ?= example
|
||||
TOP ?= ../../..
|
||||
SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA)
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_TLS=MG_TLS_BUILTIN -DMG_ENABLE_LINES=1
|
||||
AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static struct mg_dash s_dashboard;
|
||||
|
||||
// Control panel
|
||||
// s_led1, s_led2, s_led3 are used to communicate LED status
|
||||
static bool s_led1, s_led2, s_led3;
|
||||
@@ -11,7 +13,7 @@ static struct mg_field fields_leds[] = {
|
||||
{"led1", MG_VAL_BOOL, &s_led1, sizeof(s_led1)},
|
||||
{"led2", MG_VAL_BOOL, &s_led2, sizeof(s_led2)},
|
||||
{"led3", MG_VAL_BOOL, &s_led3, sizeof(s_led3)},
|
||||
{NULL, 0, NULL, 0},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
|
||||
static void write_leds(void) {
|
||||
@@ -34,7 +36,7 @@ static struct mg_field fields_metrics[] = {
|
||||
{"ram", MG_VAL_INT, &s_ram, sizeof(s_ram)},
|
||||
{"cpu", MG_VAL_INT, &s_cpu, sizeof(s_cpu)},
|
||||
{"temperature", MG_VAL_DBL, &s_temperature, sizeof(s_temperature)},
|
||||
{NULL, 0, NULL, 0},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
|
||||
static void read_metrics(void) {
|
||||
@@ -52,7 +54,7 @@ static struct mg_field fields_settings[] = {
|
||||
{"volume", MG_VAL_DBL, &s_volume, sizeof(s_volume)},
|
||||
{"name", MG_VAL_STR, &s_name, sizeof(s_name)},
|
||||
{"log_level", MG_VAL_INT, &s_log_level, sizeof(s_log_level)},
|
||||
{NULL, 0, NULL, 0},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
|
||||
static void write_settings(void) {
|
||||
@@ -81,7 +83,7 @@ static char s_chart1_data[NUM_POINTS * POINT_SIZE + 2 + 1];
|
||||
|
||||
static struct mg_field fields_chart1[] = {
|
||||
{"data", MG_VAL_RAW, s_chart1_data, sizeof(s_chart1_data)},
|
||||
{NULL, 0, NULL, 0},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
|
||||
// Randomly generate graph points and serialise them into a string
|
||||
@@ -103,9 +105,8 @@ void read_chart1(void) {
|
||||
static char s_files[1024];
|
||||
static struct mg_field fields_files[] = {
|
||||
{"data", MG_VAL_RAW, s_files, sizeof(s_files)},
|
||||
{NULL, 0, NULL, 0},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
static struct mg_dash s_dashboard;
|
||||
static void read_files(void) {
|
||||
size_t len = 0;
|
||||
struct mg_dash_file *f;
|
||||
@@ -127,9 +128,9 @@ static struct mg_field_set field_sets[] = {
|
||||
{"files", fields_files, read_files, NULL, 0, 0},
|
||||
{0, 0, NULL, NULL, 0, 0},
|
||||
};
|
||||
static struct mg_dash s_dashboard = {field_sets, NULL, NULL};
|
||||
|
||||
void mg_dash_init(struct mg_mgr *mgr) {
|
||||
s_dashboard.sets = field_sets;
|
||||
mg_dash_file_add(&s_dashboard, mg_str("device-config.json"), 1234);
|
||||
mg_dash_file_add(&s_dashboard, mg_str("device-log-2026-04-25.txt"), 1327854);
|
||||
mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dashboard);
|
||||
|
||||
@@ -118,7 +118,7 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
.header-container { max-width: var(--max-width); margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-4); padding: var(--spacing-3) var(--spacing-6); }
|
||||
.header-left { display: flex; align-items: center; gap: var(--spacing-4); }
|
||||
.header-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--spacing-3); flex-wrap: wrap; }
|
||||
.logo { height: 2.8rem; margin-right: 1rem; width: auto; }
|
||||
.logo { height: 2.8rem; margin: 0 1rem; width: auto; }
|
||||
.connection-badge { display: inline-flex; align-items: center; gap: 0.4rem; padding: 4px 14px; border-radius: 14px; font-weight: 600; }
|
||||
.connection-badge-online { color: rgb(4, 120, 87); border: 1px solid rgb(167, 243, 208); background: rgb(236, 253, 245); }
|
||||
.connection-badge-offline { color: rgb(120, 44, 57); border: 1px solid rgb(243, 177, 208); background: rgb(253, 246, 245); }
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
|
||||
|
||||
PROG ?= example
|
||||
TOP ?= ../../..
|
||||
SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA)
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_TLS=MG_TLS_BUILTIN -DMG_ENABLE_LINES=1
|
||||
AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
|
||||
|
||||
all: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
# Rebuild the program
|
||||
$(PROG): $(SOURCES) $(TOP)/mongoose.h
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(PFLAGS) $(AFLAGS) -o $@
|
||||
|
||||
# Create an embedded filesystem containing compressed dashboard.html
|
||||
file_data.c: dashboard.html
|
||||
node $(TOP)/resources/inline.js dashboard.html -o out.html
|
||||
node $(TOP)/resources/pack.js out.html:dashboard.html:gzip > file_data.c
|
||||
@echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`"
|
||||
|
||||
purge:
|
||||
curl -s https://purge.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js
|
||||
|
||||
# For development: rebuild and restart if any of the sources change
|
||||
watch:
|
||||
node $(TOP)/resources/watch.js -w main.c -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose out.* file_data.c
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2026 Cesanta Software Limited
|
||||
// SPDX-License-Identifier: GPL-2.0-only or commercial
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static bool s_led1;
|
||||
static char s_version[10];
|
||||
|
||||
static struct mg_field fields_status[] = {
|
||||
{"led1", MG_VAL_BOOL, &s_led1, sizeof(s_led1)},
|
||||
{"version", MG_VAL_STR, &s_version, sizeof(s_version)},
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
|
||||
static void read_status(void) {
|
||||
MG_INFO(("READING STATUS"));
|
||||
//s_led1 = gpio_read(LED1_PIN);
|
||||
mg_snprintf(s_version, sizeof(s_version), "1.2.3");
|
||||
}
|
||||
|
||||
static void write_status(void) {
|
||||
MG_INFO(("WRITING STATUS"));
|
||||
//gpio_write(LED1_PIN, s_led1);
|
||||
}
|
||||
|
||||
// Each field set translates to REST API /api/NAME
|
||||
static struct mg_field_set field_sets[] = {
|
||||
{"status", fields_status, read_status, write_status, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0},
|
||||
};
|
||||
static struct mg_dash s_dashboard = {field_sets, NULL, NULL};
|
||||
|
||||
void mg_dash_init(struct mg_mgr *mgr) {
|
||||
mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dashboard);
|
||||
}
|
||||
|
||||
void mg_dash_poll(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>LED Control Dashboard</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js"></script>
|
||||
<script>
|
||||
Dashboard.init({
|
||||
debug: true,
|
||||
data: { status: { led1: false, version: '1.0.0' } },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body { background:#f8fafc; font:500 14px/1.35 Arial,sans-serif; }
|
||||
code {white-space: nowrap; background: #ddd; border-radius: 4px; padding: 1px 4px;}
|
||||
.toggle { display:grid; justify-items:center; }
|
||||
.toggle { -webkit-appearance:none; appearance:none; cursor:pointer; width:56px; height:32px; border-radius:999px; border:1px solid #0f172a1a; background:#e2e8f0; position:relative; outline:0; transition:background .18s ease,box-shadow .18s ease; }
|
||||
.toggle::after { content:""; position:absolute; top:50%; left:3px; width:26px; height:26px; border-radius:999px; background:#fff; transform:translate(0,-50%); box-shadow:0 10px 20px #0f172a1a; transition:transform .18s ease; }
|
||||
.toggle:checked { background:linear-gradient(180deg,#22c55e,#16a34a); box-shadow:0 0 0 4px #22c55e26; }
|
||||
.toggle:checked::after { transform:translate(24px,-50%); }
|
||||
.toggle:focus-visible { box-shadow:0 0 0 4px #60a5fa55; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main style="margin: 2rem; color: #64748b;">
|
||||
<h1>LED control version ${status.version}</h1>
|
||||
|
||||
<div style="display: flex; gap: 1rem; ">
|
||||
<section aria-label="LED status" style="width: 12rem; border: 1px solid #ccc; border-radius: .5rem; padding: 1rem; ">
|
||||
<input type="checkbox" class="toggle" data-bind="status.led1" data-autosave="1" aria-label="Toggle LED" />
|
||||
<div>LED ${status.led1 ? 'ON' : 'OFF'}</div>
|
||||
</section>
|
||||
|
||||
<div style="max-width: 40em; color: #075985; background: #e0f2fe; border: 1px solid #7dd3fc; border-radius: 0.5rem; padding: 0.75rem 0.9rem; font-size: 0.86rem; line-height: 1.55; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75); display: flex; flex-direction: column; gap: 0.5rem; ">
|
||||
<div>
|
||||
This toggle button is a stylised checkbox.
|
||||
<code>data-bind="status.led1"</code> binds it to the device variable.
|
||||
<code>data-autosave="1"</code> makes it send POST /api/status
|
||||
<code>{"led1": VALUE}</code> to the device every time the user toggles the
|
||||
button. On the device side, <code>sync_status()</code> is called every time
|
||||
the LED state is read or written.
|
||||
</div>
|
||||
|
||||
<div style="text-align: left; margin-top: 6px;">
|
||||
Open this page in two or more browser windows and see how the dashboards
|
||||
stay synchronised when changes are made in any of them.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2026 Cesanta Software Limited
|
||||
// SPDX-License-Identifier: GPL-2.0-only or commercial
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_dash_init(&mgr);
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 10);
|
||||
mg_dash_poll(&mgr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user