mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-16 10:39:49 +00:00
Graph fixes
This commit is contained in:
1 parent
4b01dc1a78
commit
8030f8f8b5
10 files changed
+114
-77
No files matched your search
+28
-24
@@ -3342,38 +3342,42 @@ bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
|
||||
return found;
|
||||
}
|
||||
|
||||
bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
|
||||
size_t i, j;
|
||||
for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
|
||||
if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
|
||||
// \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
|
||||
// range. More complex chars would require dragging in a UTF8 library,
|
||||
// which is too much for us
|
||||
if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
|
||||
sizeof(uint8_t)) == false)
|
||||
return false;
|
||||
i += 5;
|
||||
} else if (s.buf[i] == '\\' && i + 1 < s.len) {
|
||||
char c = json_esc(s.buf[i + 1], 0);
|
||||
if (c == 0) return false;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s.buf[i];
|
||||
size_t mg_json_unescape(struct mg_str json, const char *path, char *to,
|
||||
size_t n) {
|
||||
struct mg_str s = mg_json_get_tok(json, path);
|
||||
size_t res = 0, i, j;
|
||||
if (s.len > 1 && s.buf[0] == '"') { // Is is a string?
|
||||
s.len -= 2, s.buf++; // Trim surrounding double-quotes
|
||||
for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
|
||||
if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
|
||||
// \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
|
||||
// range. More complex chars would require dragging in a UTF8 library,
|
||||
// which is too much for us
|
||||
if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
|
||||
sizeof(uint8_t)) == false)
|
||||
break;
|
||||
i += 5;
|
||||
} else if (s.buf[i] == '\\' && i + 1 < s.len) {
|
||||
char c = json_esc(s.buf[i + 1], 0);
|
||||
if (c == 0) return false;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s.buf[i];
|
||||
}
|
||||
}
|
||||
if (j < n) res = j;
|
||||
if (n > 0) to[j < n ? j : n - 1] = '\0';
|
||||
}
|
||||
if (j >= n) return false;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return true;
|
||||
return res;
|
||||
}
|
||||
|
||||
char *mg_json_get_str(struct mg_str json, const char *path) {
|
||||
char *result = NULL;
|
||||
int len = 0, off = mg_json_get(json, path, &len);
|
||||
if (off >= 0 && len > 1 && json.buf[off] == '"') {
|
||||
if (off >= 0 && len > 2 && json.buf[off] == '"') {
|
||||
if ((result = (char *) mg_calloc(1, (size_t) len)) != NULL &&
|
||||
!mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)),
|
||||
result, (size_t) len)) {
|
||||
mg_json_unescape(json, path, result, (size_t) len) == 0) {
|
||||
mg_free(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3099,8 +3099,8 @@ long mg_json_get_long(struct mg_str json, const char *path, long dflt);
|
||||
char *mg_json_get_str(struct mg_str json, const char *path);
|
||||
char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
|
||||
char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
|
||||
size_t mg_json_unescape(struct mg_str json, const char *path, char *, size_t);
|
||||
|
||||
bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
|
||||
size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
|
||||
struct mg_str *val);
|
||||
|
||||
|
||||
+19
-10
@@ -76,6 +76,22 @@
|
||||
const parent = keys.reduce((o, k) => o?.[k], obj);
|
||||
return parent && delete parent[last];
|
||||
};
|
||||
const eq = (a, b) =>
|
||||
a === b ||
|
||||
(a && b && typeof a === 'object' && typeof b === 'object' &&
|
||||
Object.keys(a).length === Object.keys(b).length &&
|
||||
Object.keys(a).every(k => eq(a[k], b[k])));
|
||||
const isEmpty = obj => obj && typeof obj === 'object' && Object.keys(obj).length === 0;
|
||||
function prune(o1, o2, p = '') {
|
||||
for (const k in p ? get(o2, p) : o2) {
|
||||
const path = p ? p + '.' + k : k;
|
||||
if (!has(o1, path)) continue;
|
||||
const x1 = get(o1, path), x2 = get(o2, path);
|
||||
if (eq(x1, x2)) del(o2, path);
|
||||
if (x2 && typeof (x2) === 'object') prune(o1, o2, path);
|
||||
if (isEmpty(get(o2, path))) del(o2, path);
|
||||
}
|
||||
}
|
||||
|
||||
function safeEval(expr, context) {
|
||||
const c = Object.freeze(context);
|
||||
@@ -234,12 +250,7 @@
|
||||
if (isMock) {
|
||||
return delay(750, true).then(val => console.log('response', name));
|
||||
} else {
|
||||
return rpc.call(name, args)
|
||||
.then(r => {
|
||||
//if (storedConfig?.debug) console.log('CALL', name, args, '->', r);
|
||||
return r;
|
||||
})
|
||||
.catch(err => console.log('CALL FAILED', name, args, err));
|
||||
return rpc.call(name, args).catch(err => console.log('CALL FAILED', name, args, err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -256,10 +267,8 @@
|
||||
};
|
||||
|
||||
function apply_and_rescan(target, patch) {
|
||||
apply(target, patch)
|
||||
for (const key of Object.keys(settings.data)) {
|
||||
if (settings.data[key] == settings.edits[key]) delete settings.edits[key];
|
||||
}
|
||||
apply(target, patch);
|
||||
prune(settings.data, settings.edits);
|
||||
rescan();
|
||||
};
|
||||
|
||||
|
||||
+28
-24
@@ -305,38 +305,42 @@ bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
|
||||
return found;
|
||||
}
|
||||
|
||||
bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
|
||||
size_t i, j;
|
||||
for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
|
||||
if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
|
||||
// \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
|
||||
// range. More complex chars would require dragging in a UTF8 library,
|
||||
// which is too much for us
|
||||
if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
|
||||
sizeof(uint8_t)) == false)
|
||||
return false;
|
||||
i += 5;
|
||||
} else if (s.buf[i] == '\\' && i + 1 < s.len) {
|
||||
char c = json_esc(s.buf[i + 1], 0);
|
||||
if (c == 0) return false;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s.buf[i];
|
||||
size_t mg_json_unescape(struct mg_str json, const char *path, char *to,
|
||||
size_t n) {
|
||||
struct mg_str s = mg_json_get_tok(json, path);
|
||||
size_t res = 0, i, j;
|
||||
if (s.len > 1 && s.buf[0] == '"') { // Is is a string?
|
||||
s.len -= 2, s.buf++; // Trim surrounding double-quotes
|
||||
for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
|
||||
if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
|
||||
// \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
|
||||
// range. More complex chars would require dragging in a UTF8 library,
|
||||
// which is too much for us
|
||||
if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
|
||||
sizeof(uint8_t)) == false)
|
||||
break;
|
||||
i += 5;
|
||||
} else if (s.buf[i] == '\\' && i + 1 < s.len) {
|
||||
char c = json_esc(s.buf[i + 1], 0);
|
||||
if (c == 0) return false;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s.buf[i];
|
||||
}
|
||||
}
|
||||
if (j < n) res = j;
|
||||
if (n > 0) to[j < n ? j : n - 1] = '\0';
|
||||
}
|
||||
if (j >= n) return false;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return true;
|
||||
return res;
|
||||
}
|
||||
|
||||
char *mg_json_get_str(struct mg_str json, const char *path) {
|
||||
char *result = NULL;
|
||||
int len = 0, off = mg_json_get(json, path, &len);
|
||||
if (off >= 0 && len > 1 && json.buf[off] == '"') {
|
||||
if (off >= 0 && len > 2 && json.buf[off] == '"') {
|
||||
if ((result = (char *) mg_calloc(1, (size_t) len)) != NULL &&
|
||||
!mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)),
|
||||
result, (size_t) len)) {
|
||||
mg_json_unescape(json, path, result, (size_t) len) == 0) {
|
||||
mg_free(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ long mg_json_get_long(struct mg_str json, const char *path, long dflt);
|
||||
char *mg_json_get_str(struct mg_str json, const char *path);
|
||||
char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
|
||||
char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
|
||||
size_t mg_json_unescape(struct mg_str json, const char *path, char *, size_t);
|
||||
|
||||
bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
|
||||
size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
|
||||
struct mg_str *val);
|
||||
+12
-5
@@ -3517,14 +3517,21 @@ static void test_json(void) {
|
||||
{
|
||||
char to[4], expect[4] = {0, 0, 0, 0};
|
||||
memset(to, 0, sizeof(to));
|
||||
ASSERT(mg_json_unescape(mg_str("\\u0000"), to, 4) &&
|
||||
ASSERT(mg_json_unescape(mg_str("\"\\u0000\""), "$", to, 4) &&
|
||||
memcmp(to, expect, 4) == 0);
|
||||
to[0] = 0;
|
||||
expect[0] = (char) 0xff;
|
||||
ASSERT(mg_json_unescape(mg_str("\\u00ff"), to, 4) &&
|
||||
ASSERT(mg_json_unescape(mg_str("\"\\u00ff\""), "$", to, 4) &&
|
||||
memcmp(to, expect, 4) == 0);
|
||||
ASSERT(!mg_json_unescape(mg_str("\\u0100"), to, 4));
|
||||
ASSERT(!mg_json_unescape(mg_str("\\u1000"), to, 4));
|
||||
ASSERT(!mg_json_unescape(mg_str("\"\\u0100\""), "$", to, 4));
|
||||
ASSERT(!mg_json_unescape(mg_str("\"\\u1000\""), "$", to, 4));
|
||||
json = mg_str("{\"a\":\"\"}");
|
||||
mg_snprintf(to, sizeof(to), "hi");
|
||||
ASSERT(mg_json_unescape(json, "$.a", to, sizeof(to)) == 0);
|
||||
ASSERT(to[0] == '\0');
|
||||
json = mg_str("{\"a\":\"b\"}");
|
||||
ASSERT(mg_json_unescape(json, "$.a", to, sizeof(to)) == 1);
|
||||
ASSERT(strcmp(to, "b") == 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -4548,7 +4555,7 @@ int main(void) {
|
||||
s_error = false;
|
||||
test_mqtt(); // sorry, MQTT_LOCALHOST is also skipped
|
||||
DASHBOARD("mqtt");
|
||||
#else
|
||||
#else
|
||||
(void) test_mqtt;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ static void get_metrics(union mg_val *val) {
|
||||
val->fn = (void (*)(void)) print_metrics;
|
||||
}
|
||||
|
||||
static bool s_motor = false;
|
||||
static double s_volume = 17.2;
|
||||
static char s_name[20] = "Dublin";
|
||||
// This function prints a JSON object with read-only metrics
|
||||
@@ -68,11 +67,14 @@ size_t print_settings(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
(void) ap;
|
||||
n += mg_xprintf(fn, arg, "{");
|
||||
n += mg_xprintf(fn, arg, "%s%m:%g", " ", MG_ESC("volume"), s_volume);
|
||||
n += mg_xprintf(fn, arg, "%s%m:%s", ",", MG_ESC("motor"), s_motor ? "true" : "false");
|
||||
n += mg_xprintf(fn, arg, "%s%m:%m", ",", MG_ESC("name"), MG_ESC(s_name));
|
||||
n += mg_xprintf(fn, arg, "}");
|
||||
return n;
|
||||
}
|
||||
static void set_settings(const union mg_val *val) {
|
||||
mg_json_unescape(val->s, "$.name", s_name, sizeof(s_name));
|
||||
mg_json_get_num(val->s, "$.volume", &s_volume);
|
||||
}
|
||||
static void get_settings(union mg_val *val) {
|
||||
val->fn = (void (*)(void)) print_settings;
|
||||
}
|
||||
@@ -100,7 +102,7 @@ static void get_controls(union mg_val *val) {
|
||||
// Modify this. This represents device's state to the dashboard
|
||||
static struct mg_field fields[] = {
|
||||
{"controls", MG_VAL_FN, get_controls, set_controls},
|
||||
{"settings", MG_VAL_FN, get_settings, NULL},
|
||||
{"settings", MG_VAL_FN, get_settings, set_settings},
|
||||
{"metrics", MG_VAL_FN, get_metrics, NULL},
|
||||
{"points", MG_VAL_FN, get_points, NULL},
|
||||
{NULL, 0, 0, 0},
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<div class="settings-fields">
|
||||
<div class="field">
|
||||
<label class="field-label" for="setting-name">Name</label>
|
||||
<input class="field-input" id="setting-name" type="text" data-bind="settings.name" />
|
||||
<input class="field-input" id="setting-name" type="text" data-bind="settings.name" data-autosave="1" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label" for="setting-volume">Volume</label>
|
||||
|
||||
@@ -2,6 +2,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const tabs = Array.from(document.querySelectorAll(".header-left .nav-link[href^='#']"));
|
||||
const panels = Array.from(document.querySelectorAll(".tab-panel"));
|
||||
|
||||
let chart = null;
|
||||
|
||||
const activateTab = targetId => {
|
||||
const targetPanel = document.getElementById(targetId);
|
||||
if (!targetPanel) return;
|
||||
@@ -18,6 +20,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
panel.classList.toggle("tab-panel-active", isActive);
|
||||
panel.hidden = !isActive;
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width <= 0 || rect.height <= 0) rect.height = rect.width = 10;
|
||||
|
||||
if (!chart) {
|
||||
options.width = rect.width;
|
||||
options.height = rect.height;
|
||||
chart = new uPlot(options, [], el);
|
||||
} else {
|
||||
chart.setSize({ width: rect.width, height: rect.height });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
tabs.forEach(tab => {
|
||||
@@ -34,11 +49,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
|
||||
const el = document.getElementById("chart");
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
const options = {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
width: 100,
|
||||
height: 100,
|
||||
tzDate: (ts) => uPlot.tzDate(new Date(ts * 1e3), "Europe/London"),
|
||||
plugins: [],
|
||||
legend: { show: false },
|
||||
@@ -53,17 +67,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
],
|
||||
};
|
||||
|
||||
// Create chart instance
|
||||
const chart = new uPlot(options, [], el);
|
||||
|
||||
function updateGraphPoints(args) {
|
||||
if (!args.points) return;
|
||||
if (!args.points || !chart) return;
|
||||
const data = [
|
||||
args.points.map(x => x[0]),
|
||||
args.points.map(x => x[1]),
|
||||
args.points.map(x => x[2]),
|
||||
];
|
||||
//console.log(123, args.points);
|
||||
chart.setData(data);
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ body {
|
||||
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
gap: var(--spacing-2);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-link-active {
|
||||
|
||||
Reference in new issue
Block a user