mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-29 12:41:03 +00:00
Add device-dashboard
This commit is contained in:
1 parent
5e01ae0c93
commit
e304a27b0d
23 files changed
+1208
-1
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
|
||||
|
||||
PROG ?= server
|
||||
MGDIR ?= ../../..
|
||||
SOURCES = server.c dashboard.html.c $(MGDIR)/mongoose.c
|
||||
CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -g3 -O0
|
||||
PFLAGS = -I$(MGDIR) -DMG_DATA_SIZE=64 -DMG_TLS=MG_TLS_BUILTIN
|
||||
AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
|
||||
|
||||
all: $(PROG)
|
||||
./$(PROG)
|
||||
|
||||
# Rebuild the program
|
||||
$(PROG): $(SOURCES)
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(PFLAGS) $(AFLAGS) -o $@
|
||||
|
||||
# Generate a single, self-contained dashboard.html from the UI in web_root/
|
||||
dashboard.html: $(wildcard web_root/*)
|
||||
curl https://purge.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js -s -o /dev/null
|
||||
monolith web_root/index.html > $@
|
||||
|
||||
# Create an embedded filesystem containing compressed dashboard.html
|
||||
dashboard.html.c: dashboard.html
|
||||
node $(MGDIR)/resources/pack.js dashboard.html::gzip > $@
|
||||
@echo "On-flash size: `grep -oE '[0-9]+' $@ | wc -l`"
|
||||
|
||||
# For development: rebuild and restart if any of the sources change
|
||||
run: $(SOURCES)
|
||||
watchexec -r -w server.c -w web_root -w ../../glue -e c,h,html,css,js -- make
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose dashboard.*
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2026 Cesanta Software Limited
|
||||
// SPDX-License-Identifier: GPL-2.0-only or commercial
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
|
||||
#define HTTP_ADDR "http://0.0.0.0:8000"
|
||||
#else
|
||||
#define HTTP_ADDR "http://0.0.0.0:80"
|
||||
#endif
|
||||
|
||||
static int s_threshold = 17;
|
||||
static char s_location[20] = "Galway";
|
||||
|
||||
static void location_get(union mg_val *val) {
|
||||
val->s = mg_str(s_location);
|
||||
}
|
||||
static void location_set(const union mg_val *val) {
|
||||
mg_json_unescape(val->s, s_location, sizeof(s_location));
|
||||
}
|
||||
|
||||
static void threshold_get(union mg_val *val) {
|
||||
val->i = s_threshold;
|
||||
}
|
||||
static void threshold_set(const union mg_val *val) {
|
||||
s_threshold = val->i;
|
||||
}
|
||||
|
||||
// Modify this. This represents device's state to the dashboard
|
||||
static struct mg_field fields[] = {
|
||||
{"location", MG_VAL_STR, location_get, location_set},
|
||||
{"threshold", MG_VAL_INT, threshold_get, threshold_set},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
void run_mongoose(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_dash dash = {fields};
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, HTTP_ADDR, mg_dash_ev_handler, &dash);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 10);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
run_mongoose();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Mongoose device dashboard -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js"></script>
|
||||
<script>
|
||||
Dashboard.init({
|
||||
debug: true,
|
||||
data: {
|
||||
location: 'Dublin',
|
||||
threshold: 42,
|
||||
firmware_version: '1.0.0',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="main">
|
||||
<h1>
|
||||
Settings panel example
|
||||
</h1>
|
||||
|
||||
<div class="panel" style="margin: 1rem 0;">
|
||||
<div class="panel-title">Settings panel</div>
|
||||
<div class="panel-row">
|
||||
<span>Location:</span>
|
||||
<input class="input" data-bind='location' data-autosave="1" />
|
||||
</div>
|
||||
<div class="panel-row">
|
||||
<span>Noise threshold:</span>
|
||||
<input class="input" data-bind="threshold" />
|
||||
</div>
|
||||
<div class="panel-row" style="justify-content: end;">
|
||||
<button class="button" data-cancel="loc.*|threshold">cancel</button>
|
||||
<button class="button" data-save="loc.*|threshold">save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-size:80%;color: #000000a0">
|
||||
When a field is edited, a local copy is created. For as long as that
|
||||
copy is different from the device's value, the field is added an
|
||||
"edited" CSS class. Bad values (e.g. non-numeric value for the field
|
||||
that is bound to a numeric data) give a "error" CSS class.
|
||||
|
||||
<p style="margin-top: .5rem;">The Location field has autosave flag set, so it is saved automatically
|
||||
as the user type text<//>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
:root {
|
||||
--btn-color: white;
|
||||
--btn-disabled-color: #d1d5db;
|
||||
--bg-color: '#d8dcdf';
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box; margin: 0; padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
body { background: #0004;}
|
||||
h1 {font-size: 1.25rem;}
|
||||
|
||||
.main {
|
||||
margin: 2rem auto; width: 380px; padding: 1em 2em;
|
||||
border-radius:0.3rem; background: #fff5;
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: flex; flex-direction: column; gap: 0.5rem;
|
||||
background: #fffe;
|
||||
border: 1px solid #0002;
|
||||
border-radius: 0.3rem;
|
||||
overflow: hidden;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.panel-title {
|
||||
margin: -0.5rem -0.75rem 0 -0.75rem;
|
||||
padding: 0.5rem 0.75rem 0.3rem 0.75rem;
|
||||
border-bottom: 1px solid #0003;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
background: #0001;
|
||||
font-weight: 600;
|
||||
color: #000c;
|
||||
}
|
||||
|
||||
.input, .dropdown {
|
||||
display:inline-flex;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: #475569;
|
||||
border: 1px solid #ccc;
|
||||
border-width: 1px;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin:0;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: #2563eb;
|
||||
color: var(--btn-color);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #3b82f6;
|
||||
}
|
||||
|
||||
.button.loading {
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button:disabled,.button:hover:disabled,.button.disabled,.button:hover.disabled {
|
||||
background: var(--btn-disabled-color) !important;
|
||||
border-color: var(--btn-disabled-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.panel-row {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input:disabled,.select:disabled,.input.disabled,.select.disabled,.slider.disabled {
|
||||
background: var(--bg-color) !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.input.edited, .select.edited, .slider.edited>* {
|
||||
border-color: #50500040;
|
||||
background: #ffff0020;
|
||||
}
|
||||
|
||||
.input.error, .select.error, .slider.error>* {
|
||||
border-color: #50000040;
|
||||
background: #ff000020;
|
||||
}
|
||||
Reference in new issue
Block a user