mirror of
https://github.com/cesanta/mongoose.git
synced 2026-09-30 21:42:42 +07:00
Enhance cube package
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Device dashboard
|
||||
|
||||
This is the AI specification file for the Mongoose based Web device dashboard functionality
|
||||
|
||||
Mongoose-base device dashboard requires following files:
|
||||
- mongoose.h
|
||||
- mongoose.c
|
||||
- dashboard.c
|
||||
- file_data.c
|
||||
|
||||
The `file_data.c` is a generated file from `dashboard.html` using Nodejs:
|
||||
```sh
|
||||
node html2c.js dashboard.html -o file_data.c
|
||||
```
|
||||
The `html2c.js` file can be taken from https://github.com/cesanta/mongoose/blob/master/resources/html2c.js
|
||||
|
||||
|
||||
## General rules
|
||||
|
||||
Your goal is to produce a dashboard.c and dashboard.html files
|
||||
If your project does not yet have those, fetch from here:
|
||||
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.c
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.html
|
||||
|
||||
If your project already has dashboard.c and dashboard.html, do NOT fetch anything.
|
||||
|
||||
The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h
|
||||
The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/
|
||||
The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/
|
||||
|
||||
## dashboard.html
|
||||
|
||||
For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax
|
||||
No vanilla JS event listeners, no fetch calls, no custom reactive logic.
|
||||
Do not make any changes to dashboard.html unless asked by a user.
|
||||
|
||||
## dashboard.c
|
||||
|
||||
Do not make any changes to dashboard.c unless asked by a user.
|
||||
|
||||
## CubeMX project
|
||||
|
||||
If you have installed I-CUBE-Mongoose pack, then all required files are installed
|
||||
in the `Middlewares/Third_Party/Cesanta_Mongoose/` directory. Do not fetch
|
||||
dashboard.html, or any other file, from the Mongoose repository.
|
||||
|
||||
Add `#include "mongoose.h"` to the "USER CODE BEGIN Includes" block
|
||||
|
||||
Add automatic `file_data.c` generation for the build step - either Makefile,
|
||||
or CMakeLists.txt or whatever is appropriate for the selected IDE.
|
||||
```sh
|
||||
node html2c.js dashboard.html -o file_data.c
|
||||
```
|
||||
|
||||
|
||||
To the "USER CODE BEGIN 0" block, add debug print redirect. If syscalls.c already
|
||||
has a weak _write override, ignore it and still add this.
|
||||
```c
|
||||
int _write(int fd, unsigned char *buf, int len) {
|
||||
HAL_UART_Transmit(&huart3, buf, len, HAL_MAX_DELAY);
|
||||
return len;
|
||||
}
|
||||
```
|
||||
|
||||
Make sure main() initialises and processes dashboard:
|
||||
|
||||
```c
|
||||
/* USER CODE BEGIN WHILE */
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_dash_init(&mgr);
|
||||
|
||||
while (1)
|
||||
{
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
mg_dash_poll(&mgr);
|
||||
/* USER CODE END WHILE */
|
||||
```
|
||||
|
||||
In order to verify the validity of your edits, compile `dashboard.c` using
|
||||
embedded or host compiler in `Middlewares/Third_Party/Cesanta_Mongoose/`:
|
||||
|
||||
```sh
|
||||
$(CC) -c dashboard.c
|
||||
```
|
||||
@@ -8,6 +8,7 @@
|
||||
<license>License.txt</license>
|
||||
<supportContact>https://mongoose.ws/contact/</supportContact>
|
||||
<releases>
|
||||
<release date="2026-05-09" version="7.21.6">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
<release date="2026-05-05" version="7.21.5">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
<release date="2026-04-24" version="7.21.4">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
<release date="2026-04-08" version="7.21.0">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
@@ -23,24 +24,26 @@
|
||||
</condition>
|
||||
</conditions>
|
||||
<components>
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.21.5" maxInstances="1">
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.21.6" maxInstances="1">
|
||||
<description>Networking Core</description>
|
||||
<files>
|
||||
<file category="doc" name="README.md"/>
|
||||
<file category="header" name="mongoose.h" />
|
||||
<file category="sourceC" name="mongoose.c" />
|
||||
<file category="header" name="mongoose_config.h"/>
|
||||
<file category="utility" name="README.md" public="false"/>
|
||||
</files>
|
||||
</component>
|
||||
<component Cclass="Mongoose" Cgroup="Web Device Dashboard" Cversion="7.21.5" condition="MongooseCore" maxInstances="1">
|
||||
<component Cclass="Mongoose" Cgroup="Web Device Dashboard" Cversion="7.21.6" condition="MongooseCore" maxInstances="1" generator="copyassets" >
|
||||
<description>Web Device Dashboard</description>
|
||||
<files>
|
||||
<file category="sourceC" name="dashboard.c"/>
|
||||
<file category="sourceC" name="file_data.c"/>
|
||||
<file category="other" name="dashboard.html"/>
|
||||
<file category="utility" name="dashboard.html" public="false"/>
|
||||
<file category="utility" name="html2c.js" public="false"/>
|
||||
</files>
|
||||
</component>
|
||||
<component Cclass="Mongoose" Cgroup="MQTT Client" Cversion="7.21.5" condition="MongooseCore" maxInstances="1">
|
||||
<component Cclass="Mongoose" Cgroup="MQTT Client" Cversion="7.21.6" condition="MongooseCore" maxInstances="1">
|
||||
<description>MQTT Client</description>
|
||||
<files>
|
||||
<file category="sourceC" name="mongoose_mqtt.c"/>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
VERSION = 7.21.5
|
||||
VERSION = 7.21.6
|
||||
REPO_ROOT = ../..
|
||||
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
||||
FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md
|
||||
GENERATED = License.txt \
|
||||
mongoose.h mongoose.c \
|
||||
dashboard.c file_data.c dashboard.html \
|
||||
dashboard.c file_data.c dashboard.html html2c.js \
|
||||
mongoose_mqtt.c
|
||||
|
||||
all: Cesanta.I-CUBE-Mongoose.$(VERSION).pack
|
||||
PACK = Cesanta.I-CUBE-Mongoose.$(VERSION).pack
|
||||
all: $(PACK)
|
||||
|
||||
Cesanta.I-CUBE-Mongoose.$(VERSION).pack: $(FILES) $(GENERATED)
|
||||
zip -q -r $@ $^
|
||||
@@ -33,6 +35,9 @@ file_data.c:
|
||||
make -C $(REPO_ROOT)/tutorials/device-dashboard/full $@
|
||||
cp $(REPO_ROOT)/tutorials/device-dashboard/full/$@ $@
|
||||
|
||||
html2c.js:
|
||||
cp $(REPO_ROOT)/resources/$@ $@
|
||||
|
||||
mongoose_mqtt.c:
|
||||
cp $(REPO_ROOT)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c $@
|
||||
|
||||
@@ -42,5 +47,23 @@ mongoose.h: Mongoose
|
||||
mongoose.c: Mongoose
|
||||
cp $(REPO_ROOT)/mongoose.c $@
|
||||
|
||||
pk: $(PACK)
|
||||
rm -f gen.mx
|
||||
echo swmgr remove Cesanta.I-CUBE-Mongoose.$(VERSION) >> gen.mx
|
||||
echo swmgr install `pwd`/Cesanta.I-CUBE-Mongoose.$(VERSION).pack ask >> gen.mx
|
||||
echo exit >> gen.mx
|
||||
$(MX) -q `pwd`/gen.mx
|
||||
|
||||
|
||||
mx: $(PACK)
|
||||
mkdir -p $@
|
||||
cp ../downloads/nucleo_h563zi.ioc $@
|
||||
V=`grep 'Mcu.ThirdParty0' $@/nucleo_h563zi.ioc | sed 's,^.*se.,,'`; sed -i '' "s,$$V,$(VERSION),g" $@/nucleo_h563zi.ioc
|
||||
rm -f gen.mx
|
||||
echo config load `pwd`/$@/nucleo_h563zi.ioc >> gen.mx
|
||||
echo project generate >> gen.mx
|
||||
echo exit >> gen.mx
|
||||
$(MX) -q `pwd`/gen.mx
|
||||
|
||||
clean:
|
||||
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED)
|
||||
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) gen.mx mx
|
||||
|
||||
@@ -102,6 +102,11 @@ Hi from Mongoose! Tick 38187
|
||||
## Add full device dashboard
|
||||
|
||||
Enable "Mongoose Web Device Dashboard" component in CubeMX. Regenerate code.
|
||||
This component installs the following files:
|
||||
- `dashboard.c` - a C backend side
|
||||
- `dashboard.html` - an HTML frontend side: the Web UI itself
|
||||
- `file_data.c` - this file is generated from `dashboard.html`
|
||||
- `html2c.js` - a node.js script that generates `file_data.c` using this command: `node html2c.js dashboard.html -o file_data.c`. This script first inlines all external resources into an amalgamated HTML file - so it does not depend on anything external, and then zips it and packs into a Mongoose embedded filesystem. This way it takes the minimum space on flash.
|
||||
|
||||
Remove the hello world server if you added it. Modify your while loop
|
||||
to look like this:
|
||||
@@ -122,6 +127,19 @@ while (1)
|
||||
Rebuild, reflash your board and enjoy the fully functional device dashboard
|
||||
that you can tailor to your firmware.
|
||||
|
||||
To customise your dashboard, edit `dashboard.c` and `dashboard.html`.
|
||||
After editing `dashboard.html`, regenerate `file_data.c` using this command:
|
||||
|
||||
```sh
|
||||
node html2c.js dashboard.html -o file_data.c
|
||||
```
|
||||
|
||||
For agentic development, use https://github.com/cesanta/mongoose/tree/master/resources/AGENTS.md
|
||||
Use a prompt like this:
|
||||
|
||||
> Read and apply https://github.com/cesanta/mongoose/tree/master/resources/AGENTS.md
|
||||
|
||||
|
||||
## Add MQTT Client
|
||||
|
||||
Enable "Mongoose MQTT Client" component in CubeMX. Regenerate code.
|
||||
|
||||
@@ -3,7 +3,7 @@ ST_TARGETS = nucleo_h723zg nucleo_f756zg nucleo_h563zi
|
||||
# ST_TARGETS = $(subst .ioc,,$(wildcard *.ioc))
|
||||
PROJECTS = full minimal
|
||||
TARGETS = desktop $(ST_TARGETS)
|
||||
MX ?= /Applications/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
||||
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
||||
|
||||
DIRS = $(foreach P,$(PROJECTS),$(foreach T,$(TARGETS),$(T)_$(P)))
|
||||
ZIPS = $(foreach D,$(DIRS),$(D).zip)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Generate a Mongoose packed filesystem C file from an HTML file after
|
||||
// inlining referenced CSS, JS, images, and icons.
|
||||
//
|
||||
// Usage:
|
||||
// node html2c.js [-o OUTPUT.c] HTML_FILE
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let outputFile = null;
|
||||
let inputFile = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '-o' && i + 1 < args.length) {
|
||||
outputFile = args[++i];
|
||||
} else if (!args[i].startsWith('-') && inputFile === null) {
|
||||
inputFile = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!inputFile) {
|
||||
console.error('Usage: node html2c.js [-o OUTPUT.c] HTML_FILE');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseDir = path.dirname(path.resolve(inputFile));
|
||||
const html = fs.readFileSync(inputFile, 'utf8');
|
||||
|
||||
function fetchUrl(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
client
|
||||
.get(url, res => {
|
||||
const chunks = [];
|
||||
res.on('data', chunk => chunks.push(chunk));
|
||||
res.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
res.on('error', reject);
|
||||
})
|
||||
.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function readAsset(name, encoding) {
|
||||
if (name.startsWith('http://') || name.startsWith('https://')) {
|
||||
const data = await fetchUrl(name);
|
||||
return encoding ? data.toString(encoding) : data;
|
||||
}
|
||||
return fs.readFileSync(path.join(baseDir, name), encoding || null);
|
||||
}
|
||||
|
||||
function attr(match, name) {
|
||||
const re = new RegExp(`${name}=["']([^"']+)["']`, 'i');
|
||||
const m = match.match(re);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
function replaceAttr(match, name, value) {
|
||||
const re = new RegExp(`${name}=(["'])[^"']+\\1`, 'i');
|
||||
return match.replace(re, `${name}="${value}"`);
|
||||
}
|
||||
|
||||
async function inlineCss(match) {
|
||||
const href = attr(match, 'href');
|
||||
if (!href) return match;
|
||||
const css = await readAsset(href, 'utf8');
|
||||
return `<style>${css}</style>`;
|
||||
}
|
||||
|
||||
async function inlineJs(match) {
|
||||
const src = attr(match, 'src');
|
||||
if (!src) return match;
|
||||
const js = await readAsset(src, 'utf8');
|
||||
return `<script>${js}</script>`;
|
||||
}
|
||||
|
||||
async function inlineImg(match) {
|
||||
const src = attr(match, 'src');
|
||||
if (!src) return match;
|
||||
const ext = path.extname(src).toLowerCase();
|
||||
let mime = 'image/png';
|
||||
if (ext === '.svg') mime = 'image/svg+xml';
|
||||
else if (ext === '.jpg' || ext === '.jpeg') mime = 'image/jpeg';
|
||||
else if (ext === '.gif') mime = 'image/gif';
|
||||
else if (ext === '.webp') mime = 'image/webp';
|
||||
else if (ext === '.ico') mime = 'image/x-icon';
|
||||
|
||||
const data = await readAsset(src);
|
||||
if (ext === '.svg') {
|
||||
const svg = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
||||
return replaceAttr(
|
||||
match,
|
||||
'src',
|
||||
`data:${mime};utf8,${encodeURIComponent(svg)}`,
|
||||
);
|
||||
}
|
||||
return replaceAttr(
|
||||
match,
|
||||
'src',
|
||||
`data:${mime};base64,${Buffer.from(data).toString('base64')}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function inlineSvg(match) {
|
||||
const href = attr(match, 'href');
|
||||
if (!href || !href.toLowerCase().endsWith('.svg')) return match;
|
||||
const data = await readAsset(href);
|
||||
return replaceAttr(
|
||||
match,
|
||||
'href',
|
||||
`data:image/svg+xml;base64,${Buffer.from(data).toString('base64')}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function processHtml(content) {
|
||||
let result = content;
|
||||
const cssRegex = /<link[^>]+rel=["']stylesheet["'][^>]*>/g;
|
||||
const jsRegex = /<script[^>]+src=["'][^"']+["'][^>]*><\/script>/g;
|
||||
const imgRegex = /<img[^>]+src=["'][^"']+["'][^>]*>/g;
|
||||
const svgRegex = /<link[^>]+rel=["']icon["'][^>]*>/g;
|
||||
|
||||
for (const match of content.match(cssRegex) || []) {
|
||||
if (!match.includes('data:')) result = result.replace(match, await inlineCss(match));
|
||||
}
|
||||
for (const match of content.match(jsRegex) || []) {
|
||||
if (!match.includes('data:')) result = result.replace(match, await inlineJs(match));
|
||||
}
|
||||
for (const match of content.match(imgRegex) || []) {
|
||||
if (!match.includes('data:')) result = result.replace(match, await inlineImg(match));
|
||||
}
|
||||
for (const match of content.match(svgRegex) || []) {
|
||||
if (!match.includes('data:')) result = result.replace(match, await inlineSvg(match));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function cArray(data) {
|
||||
return Array.from(Buffer.from(data, 'utf8')).concat(0).join(',');
|
||||
}
|
||||
|
||||
function generateC(data) {
|
||||
const destination = path.basename(inputFile);
|
||||
const stat = fs.statSync(inputFile);
|
||||
const mtime = parseInt(stat.mtimeMs / 1000);
|
||||
const bytes = cArray(data);
|
||||
|
||||
return `// DO NOT EDIT. This file is generated using this command:
|
||||
// ${process.argv.join(' ')}
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static const unsigned char v0[] = {${bytes}};
|
||||
|
||||
const struct mg_mem_file mg_packed_files[] = {
|
||||
{"/${destination}", v0, sizeof(v0) - 1, ${mtime}},
|
||||
{NULL, NULL, 0, 0}
|
||||
};
|
||||
`;
|
||||
}
|
||||
|
||||
processHtml(html)
|
||||
.then(result => {
|
||||
const c = generateC(result);
|
||||
if (outputFile) {
|
||||
fs.writeFileSync(outputFile, c);
|
||||
} else {
|
||||
process.stdout.write(c);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
PROG ?= example
|
||||
TOP ?= ../../..
|
||||
SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
SOURCES = dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA)
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 -DMAIN
|
||||
AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
|
||||
|
||||
all: $(PROG)
|
||||
@@ -17,13 +17,12 @@ $(PROG): $(SOURCES) $(TOP)/mongoose.h
|
||||
|
||||
# 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
|
||||
node $(TOP)/resources/html2c.js dashboard.html -o file_data.c
|
||||
@echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`"
|
||||
|
||||
# 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
|
||||
node $(TOP)/resources/watch.js -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose out.* file_data.c
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Device dashboard
|
||||
|
||||
This is the AI specification file for the Web device dashboard.
|
||||
|
||||
## General rules
|
||||
|
||||
Your goal is to produce a dashboard.c and dashboard.html files
|
||||
|
||||
Fetch the initial implementation of these two files from:
|
||||
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/dashboard.c
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/dashboard.html
|
||||
|
||||
The C API for dashboard.c is described at https://mongoose.ws/docs/api/dash/
|
||||
|
||||
The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h
|
||||
|
||||
The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/
|
||||
|
||||
The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/
|
||||
|
||||
For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax
|
||||
|
||||
No vanilla JS event listeners, no fetch calls, no custom reactive logic.
|
||||
|
||||
## dashboard.html
|
||||
|
||||
Change theme to dark
|
||||
|
||||
## dashboard.c
|
||||
|
||||
Don't touch it
|
||||
@@ -216,3 +216,21 @@ void mg_dash_poll(struct mg_mgr *mgr) {
|
||||
mg_dash_send_change(mgr, &set_actions);
|
||||
}
|
||||
}
|
||||
|
||||
// On desktop, build with -DMAIN flag to include main().
|
||||
// On embedded system, run this code in your main() function
|
||||
#ifdef MAIN
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_dash_init(&mgr);
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
mg_dash_poll(&mgr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -114,14 +114,6 @@
|
||||
Dashboard.login().catch(err => showLogin(true));
|
||||
|
||||
// GRAPHS
|
||||
// {stroke: '#14B8A6FF', fill: '#14B8A61A'}, // teal
|
||||
// {stroke: '#F59E0BFF', fill: '#F59E0B1A'}, // amber
|
||||
// {stroke: '#22C55EFF', fill: '#22C55E1A'}, // green
|
||||
// {stroke: '#EF4444FF', fill: '#EF44441A'}, // red
|
||||
// {stroke: '#8B5CF6FF', fill: '#8B5CF61A'}, // purple
|
||||
// {stroke: '#058DC7FF', fill: '#058DC71A'}, // azure
|
||||
// {stroke: '#ED7E17FF', fill: '#ED7E171A'}, // tangerine
|
||||
|
||||
const graphs = {
|
||||
graph1: {
|
||||
el: document.getElementById("graph1"),
|
||||
@@ -207,7 +199,6 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
.nav-icon { height: 1rem; width: 1rem; flex-shrink: 0; }
|
||||
.tab-stage { max-width: var(--max-width); margin: 0 auto; padding: 0.5rem var(--spacing-6) var(--spacing-4); }
|
||||
.tab-panel { min-height: calc(100vh - 6rem); }
|
||||
.metrics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
||||
.metric-tile { padding: 0.75rem 1.5rem 0.75rem; border-radius: 1rem; background: #ffffff; border: 1px solid rgba(203, 213, 225, 0.75); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0 2px 6px rgba(148, 163, 184, 0.1), 0 8px 24px rgba(148, 163, 184, 0.08); display: flex; flex-direction: column; gap: 0.35rem; transition: box-shadow 0.15s ease, transform 0.15s ease; }
|
||||
.metric-tile:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0 4px 12px rgba(148, 163, 184, 0.18), 0 12px 32px rgba(148, 163, 184, 0.12); transform: translateY(-1px); }
|
||||
.metric-label { color: #94a3b8; font-size: 0.9rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
|
||||
@@ -271,9 +262,8 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
.toggle.edited { border-color: #0fe; }
|
||||
.u-under { top: 0 !important; }
|
||||
.hidden { display: none; }
|
||||
@media (max-width: 900px) { .grid { grid-template-columns: 1fr !important; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } }
|
||||
@media (max-width: 900px) { .metrics-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 480px) { .metrics-grid { grid-template-columns: 1fr; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } }
|
||||
@media (max-width: 900px) { .grid { grid-template-columns: repeat(2, 1fr) !important; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } }
|
||||
@media (max-width: 640px) { .grid { grid-template-columns: 1fr !important; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } }
|
||||
</style>
|
||||
|
||||
<!-- uPlot graph library -->
|
||||
@@ -389,7 +379,7 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
</div>
|
||||
<main class="tab-stage">
|
||||
<section id="overview" class="tab-panel tab-panel-active" role="tabpanel" aria-labelledby="tab-overview">
|
||||
<div class="metrics-grid" aria-label="Device metrics overview">
|
||||
<div class="grid" style="grid-template-columns: repeat(5, 1fr); gap: 1rem;">
|
||||
<article class="metric-tile">
|
||||
<div class="metric-label">Temperature</div>
|
||||
<div class="metric-value-row">
|
||||
@@ -431,6 +421,12 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
<span class="metric-status metric-status-good">ok</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="metric-tile note">
|
||||
<div>
|
||||
To customize this dashboard, follow
|
||||
<a target="_blank" href="https://mongoose.ws/docs/guides/web-dashboard-guide/">https://mongoose.ws/docs/guides/web-dashboard-guide/</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr;">
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
PROG ?= example
|
||||
TOP ?= ../../..
|
||||
SOURCES = main.c dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
SOURCES = dashboard.c file_data.c $(TOP)/mongoose.c
|
||||
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA)
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1
|
||||
PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 -DMAIN
|
||||
AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
|
||||
|
||||
all: $(PROG)
|
||||
@@ -17,13 +17,12 @@ $(PROG): $(SOURCES) $(TOP)/mongoose.h
|
||||
|
||||
# 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
|
||||
node $(TOP)/resources/html2c.js dashboard.html -o file_data.c
|
||||
@echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`"
|
||||
|
||||
# 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
|
||||
node $(TOP)/resources/watch.js -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose out.* file_data.c
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose file_data.c
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Device dashboard
|
||||
|
||||
This is the AI specification file for the Web device dashboard.
|
||||
|
||||
## General rules
|
||||
|
||||
Your goal is to produce a dashboard.c and dashboard.html files
|
||||
|
||||
Fetch the initial implementation of these two files from:
|
||||
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.c
|
||||
https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/dashboard.html
|
||||
|
||||
The C API for dashboard.c is described at https://mongoose.ws/docs/api/dash/
|
||||
|
||||
The C API header file is at https://github.com/cesanta/mongoose/blob/master/src/dash.h
|
||||
|
||||
The minimal reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/minimal/
|
||||
|
||||
The full reference project is at https://github.com/cesanta/mongoose/blob/master/tutorials/device-dashboard/full/
|
||||
|
||||
For editing dashboard.html, use only Mongoose `data-bind` and `${...}` template syntax
|
||||
|
||||
No vanilla JS event listeners, no fetch calls, no custom reactive logic.
|
||||
|
||||
## dashboard.html
|
||||
|
||||
On a page, create single control panel titled "LED control"
|
||||
|
||||
On that control, add 3 LED toggles: LED1, LED2, LED3
|
||||
|
||||
Update Dashboard.init call accordingly. Update dashboard.c accordingly - have 3 LEDs in the "leds" data set.
|
||||
|
||||
## dashboard.c
|
||||
|
||||
Don't touch it
|
||||
@@ -45,3 +45,21 @@ void mg_dash_init(struct mg_mgr *mgr) {
|
||||
void mg_dash_poll(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
}
|
||||
|
||||
// On desktop, build with -DMAIN flag to include main().
|
||||
// On embedded system, run this code in your main() function
|
||||
#ifdef MAIN
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_dash_init(&mgr);
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
mg_dash_poll(&mgr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -54,6 +54,11 @@
|
||||
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 style="text-align: left; margin-top: 6px;">
|
||||
To customize this dashboard, follow
|
||||
<a target="_blank" href="https://mongoose.ws/docs/guides/web-dashboard-guide/">https://mongoose.ws/docs/guides/web-dashboard-guide/</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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