mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-09 16:21:45 +00:00
Merge pull request #3707 from cesanta/dash
Adjust dash to use core file manager
This commit is contained in:
9 files changed
+173
-646
No files matched your search
+30
-220
@@ -1053,12 +1053,7 @@ static int mg_dash_array_size(struct mg_field_set *set,
|
|||||||
struct mg_dash_user *u) {
|
struct mg_dash_user *u) {
|
||||||
int saved = *set->index, sz = -1;
|
int saved = *set->index, sz = -1;
|
||||||
*set->index = -1;
|
*set->index = -1;
|
||||||
if (set->fn) {
|
if (set->fn && set->fn(MG_DASH_READ, u)) sz = *set->index;
|
||||||
if (set->fn(MG_DASH_READ, u)) sz = *set->index;
|
|
||||||
} else if (set->get_dir) {
|
|
||||||
mg_dash_dir_read(set, u);
|
|
||||||
sz = *set->index;
|
|
||||||
}
|
|
||||||
*set->index = saved;
|
*set->index = saved;
|
||||||
return sz;
|
return sz;
|
||||||
}
|
}
|
||||||
@@ -1076,10 +1071,7 @@ static size_t mg_dash_print_array(mg_pfn_t fn, void *arg, va_list *ap) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
bool done = to >= 0 && *set->index > to;
|
bool done = to >= 0 && *set->index > to;
|
||||||
if (!done) {
|
if (!done) {
|
||||||
if (set->fn)
|
if (set->fn) set->fn(MG_DASH_READ, u);
|
||||||
set->fn(MG_DASH_READ, u);
|
|
||||||
else if (set->get_dir)
|
|
||||||
mg_dash_dir_read(set, u);
|
|
||||||
done = *set->index < 0;
|
done = *set->index < 0;
|
||||||
}
|
}
|
||||||
if (done) break;
|
if (done) break;
|
||||||
@@ -1161,8 +1153,7 @@ void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
|
|||||||
MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz);
|
MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz);
|
||||||
} else {
|
} else {
|
||||||
int saved_idx = set->index != NULL ? *set->index : 0;
|
int saved_idx = set->index != NULL ? *set->index : 0;
|
||||||
bool ok = set->fn ? set->fn(MG_DASH_READ, u)
|
bool ok = set->fn ? set->fn(MG_DASH_READ, u) : true;
|
||||||
: (set->get_dir ? mg_dash_dir_read(set, u) : true);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (set->index != NULL) *set->index = saved_idx;
|
if (set->index != NULL) *set->index = saved_idx;
|
||||||
continue;
|
continue;
|
||||||
@@ -1209,8 +1200,7 @@ static int mg_dash_parse_field(struct mg_str json, struct mg_field *f) {
|
|||||||
mg_json_get_num(json, json_path, (double *) f->value);
|
mg_json_get_num(json, json_path, (double *) f->value);
|
||||||
} else if (f->type == MG_VAL_STR && f->value_size > 0) {
|
} else if (f->type == MG_VAL_STR && f->value_size > 0) {
|
||||||
struct mg_str tok = mg_json_get_tok(json, json_path);
|
struct mg_str tok = mg_json_get_tok(json, json_path);
|
||||||
ok = tok.len >= 2 && tok.buf[0] == '"' &&
|
ok = tok.len >= 2 && tok.buf[0] == '"' && tok.buf[tok.len - 1] == '"';
|
||||||
tok.buf[tok.len - 1] == '"';
|
|
||||||
if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size);
|
if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size);
|
||||||
} else if (f->type == MG_VAL_RAW && f->value_size > 0) {
|
} else if (f->type == MG_VAL_RAW && f->value_size > 0) {
|
||||||
ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len,
|
ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len,
|
||||||
@@ -1247,68 +1237,6 @@ static int mg_dash_apply(struct mg_connection *c, struct mg_dash *dash,
|
|||||||
return total_count;
|
return total_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u) {
|
|
||||||
char dir[256], fname[128] = "";
|
|
||||||
struct mg_fs *fs = u->dash->upload_fs ? u->dash->upload_fs : &mg_fs_posix;
|
|
||||||
struct mg_field *name_field = NULL, *size_field = NULL;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) return false;
|
|
||||||
|
|
||||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
|
||||||
if (name_field == NULL && set->fields[i].type == MG_VAL_STR &&
|
|
||||||
strcmp(set->fields[i].name, "name") == 0)
|
|
||||||
name_field = &set->fields[i];
|
|
||||||
if (size_field == NULL && strcmp(set->fields[i].name, "size") == 0)
|
|
||||||
size_field = &set->fields[i];
|
|
||||||
}
|
|
||||||
if (name_field == NULL) return false;
|
|
||||||
|
|
||||||
if (*set->index == -1) { // Size query: count all files
|
|
||||||
int count = 0;
|
|
||||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) count++;
|
|
||||||
*set->index = count;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Regular read: scan to *set->index
|
|
||||||
int target = *set->index, cur = 0;
|
|
||||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) {
|
|
||||||
if (cur++ == target) {
|
|
||||||
mg_snprintf((char *) name_field->value, name_field->value_size, "%s",
|
|
||||||
fname);
|
|
||||||
if (size_field != NULL) {
|
|
||||||
char path[512];
|
|
||||||
size_t sz = 0;
|
|
||||||
mg_snprintf(path, sizeof(path), "%s/%s", dir, fname);
|
|
||||||
fs->st(path, &sz, NULL);
|
|
||||||
if (size_field->type == MG_VAL_UINT64)
|
|
||||||
*(uint64_t *) size_field->value = (uint64_t) sz;
|
|
||||||
else if (size_field->type == MG_VAL_INT)
|
|
||||||
*(int *) size_field->value = (int) sz;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*set->index = -1; // No more entries
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool mg_dash_set_file_name(struct mg_field_set *set,
|
|
||||||
struct mg_str name) {
|
|
||||||
size_t i;
|
|
||||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
|
||||||
struct mg_field *f = &set->fields[i];
|
|
||||||
if (f->type == MG_VAL_STR && strcmp(f->name, "name") == 0) {
|
|
||||||
mg_snprintf((char *) f->value, f->value_size, "%.*s", (int) name.len,
|
|
||||||
name.buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void mg_log_http_req(struct mg_connection *c,
|
static inline void mg_log_http_req(struct mg_connection *c,
|
||||||
struct mg_http_message *hm) {
|
struct mg_http_message *hm) {
|
||||||
int len = 0;
|
int len = 0;
|
||||||
@@ -1341,77 +1269,6 @@ static inline void mg_log_http_req(struct mg_connection *c,
|
|||||||
c->send.buf + n));
|
c->send.buf + n));
|
||||||
}
|
}
|
||||||
|
|
||||||
// State of one file upload to a dashboard file array. Owned by the upload
|
|
||||||
// callback, which frees it in the end call
|
|
||||||
struct mg_dash_upload {
|
|
||||||
struct mg_mgr *mgr; // Manager, to notify WebSocket clients
|
|
||||||
struct mg_dash *dash; // Dashboard whose file arrays get notified
|
|
||||||
struct mg_fd *fd; // Destination file, opened by the request handler
|
|
||||||
size_t expected; // Number of body bytes to receive
|
|
||||||
size_t received; // Number of body bytes written
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload callback for mg_http_stream_body(). Notifies clients only if the whole
|
|
||||||
// body was written. A dropped or failed upload sends no notification
|
|
||||||
static bool mg_dash_upload_cb(struct mg_http_message *hm, struct mg_str *data,
|
|
||||||
void **p) {
|
|
||||||
struct mg_dash_upload *up = (struct mg_dash_upload *) *p;
|
|
||||||
bool ok;
|
|
||||||
if (hm != NULL) return true; // Start, the file is already open
|
|
||||||
if (data != NULL) { // Next chunk
|
|
||||||
ok = up->fd->fs->wr(up->fd->fd, data->buf, data->len) == data->len;
|
|
||||||
if (ok) up->received += data->len;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
ok = up->received == up->expected; // End
|
|
||||||
mg_fs_close(up->fd);
|
|
||||||
if (ok) {
|
|
||||||
// Notify every file-backed array: the upload could belong to any of them,
|
|
||||||
// and re-querying get_dir() per recipient is what mg_dash_send_change()
|
|
||||||
// does anyway (directories can be user-specific)
|
|
||||||
struct mg_field_set *set;
|
|
||||||
for (set = up->dash->sets; set != NULL; set = set->next) {
|
|
||||||
if (set->get_dir == NULL) continue;
|
|
||||||
*set->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
|
||||||
mg_dash_send_change(up->mgr, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mg_free(up);
|
|
||||||
*p = NULL;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens dir/name and starts streaming the request body into it. Returns true
|
|
||||||
// if streaming started, otherwise the error reply has been sent already
|
|
||||||
static bool mg_dash_start_upload(struct mg_connection *c,
|
|
||||||
struct mg_http_message *hm,
|
|
||||||
struct mg_dash *dash, struct mg_fs *fs,
|
|
||||||
const char *dir, struct mg_str name) {
|
|
||||||
char path[MG_PATH_MAX];
|
|
||||||
struct mg_dash_upload *up = NULL;
|
|
||||||
struct mg_fd *fd = NULL;
|
|
||||||
size_t n = mg_snprintf(path, sizeof(path), "%s%c%.*s", dir, MG_DIRSEP,
|
|
||||||
(int) name.len, name.buf);
|
|
||||||
if (n < sizeof(path)) fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
|
||||||
if (fd != NULL) up = (struct mg_dash_upload *) mg_calloc(1, sizeof(*up));
|
|
||||||
if (up == NULL) {
|
|
||||||
if (fd != NULL) mg_fs_close(fd);
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Open failed\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
up->mgr = c->mgr;
|
|
||||||
up->dash = dash;
|
|
||||||
up->fd = fd;
|
|
||||||
up->expected = hm->body.len;
|
|
||||||
if (mg_http_stream_body(c, hm, mg_dash_upload_cb, up)) {
|
|
||||||
return true; // The callback owns up and fd now
|
|
||||||
}
|
|
||||||
mg_fs_close(fd); // Chunked body or unexpected method: not streamed
|
|
||||||
mg_free(up);
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad request\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
||||||
unsigned t = (unsigned) dash->session_auto_expiration_seconds;
|
unsigned t = (unsigned) dash->session_auto_expiration_seconds;
|
||||||
if (t == 0) t = 3600; // Default expiration time in seconds
|
if (t == 0) t = 3600; // Default expiration time in seconds
|
||||||
@@ -1666,6 +1523,25 @@ static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
|||||||
return mg_ota_end();
|
return mg_ota_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
||||||
|
struct mg_dash_user *u, bool stream) {
|
||||||
|
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||||
|
char root[128] = "/fs/=";
|
||||||
|
if (u == NULL) {
|
||||||
|
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||||
|
} else if (dash->files_dir(u, root + 5, sizeof(root) - 5) == false) {
|
||||||
|
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||||
|
} else {
|
||||||
|
struct mg_http_serve_opts o;
|
||||||
|
memset(&o, 0, sizeof(o));
|
||||||
|
o.root_dir = root;
|
||||||
|
o.fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
||||||
|
o.allow_delete = o.allow_upload = true;
|
||||||
|
if (stream) mg_http_serve_upload(c, hm, &o);
|
||||||
|
else mg_http_serve_dir(c, hm, &o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||||
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
||||||
@@ -1679,8 +1555,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
// Received headers - check authentication and possibly start uploads/ota
|
// Received headers - check authentication and possibly start uploads/ota
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
||||||
struct mg_str parts[3];
|
|
||||||
memset(parts, 0, sizeof(parts));
|
|
||||||
|
|
||||||
if (mg_match(hm->uri, mg_str("/api/hi"), NULL) ||
|
if (mg_match(hm->uri, mg_str("/api/hi"), NULL) ||
|
||||||
mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
|
mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
|
||||||
@@ -1691,45 +1565,14 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
} else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) {
|
} else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) {
|
||||||
mg_handle_login(c, u);
|
mg_handle_login(c, u);
|
||||||
d->marker = CONN_HANDLED;
|
d->marker = CONN_HANDLED;
|
||||||
|
} else if (u != NULL && dash->files_dir != NULL &&
|
||||||
|
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||||
|
mg_serve_fs(c, hm, u, true);
|
||||||
|
return; // Important - the serve_fs() call can invalidate hm
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||||
// Authenticated above. If streaming started, the stream handler owns
|
// Authenticated above. If streaming started, the stream handler owns
|
||||||
// the connection and c->data, so skip the marker check below
|
// the connection and c->data, so skip the marker check below
|
||||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
||||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) &&
|
|
||||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
|
||||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
|
||||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
|
||||||
struct mg_str name = parts[1];
|
|
||||||
int len =
|
|
||||||
mg_url_decode(name.buf, name.len, (char *) name.buf, name.len + 1, 0);
|
|
||||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
|
||||||
if (set == NULL || set->get_dir == NULL) {
|
|
||||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (u == NULL) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (!mg_path_is_sane(name)) {
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else {
|
|
||||||
mg_dash_set_file_name(set, name);
|
|
||||||
if (set->fn != NULL && !set->fn(MG_DASH_WRITE, u)) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else {
|
|
||||||
char dir[256];
|
|
||||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (mg_dash_start_upload(c, hm, dash, fs, dir, name)) {
|
|
||||||
return; // The stream handler owns the connection and c->data
|
|
||||||
} else {
|
|
||||||
d->marker = CONN_HANDLED; // Error reply has been sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (d->marker != '\0') mg_log_http_req(c, hm);
|
if (d->marker != '\0') mg_log_http_req(c, hm);
|
||||||
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
||||||
@@ -1752,42 +1595,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
} else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) {
|
||||||
d->u = u;
|
d->u = u;
|
||||||
mg_ws_upgrade(c, hm, NULL);
|
mg_ws_upgrade(c, hm, NULL);
|
||||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts)) {
|
} else if (dash->files_dir != NULL &&
|
||||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||||
if (set == NULL || set->get_dir == NULL) {
|
mg_serve_fs(c, hm, u, false);
|
||||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found");
|
|
||||||
} else if (u == NULL) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
} else {
|
|
||||||
char dir[256], path[512];
|
|
||||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
|
||||||
struct mg_str name = parts[1];
|
|
||||||
int len = mg_url_decode(name.buf, name.len, (char *) name.buf,
|
|
||||||
name.len + 1, 0);
|
|
||||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
|
||||||
if (!mg_path_is_sane(name)) {
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mg_snprintf(path, sizeof(path), "%s/%.*s", dir, name.len, name.buf);
|
|
||||||
if (mg_strcasecmp(hm->method, mg_str("DELETE")) == 0) {
|
|
||||||
mg_dash_set_file_name(set, name);
|
|
||||||
if (set->fn != NULL && !set->fn(MG_DASH_DELETE, u)) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
} else {
|
|
||||||
fs->rm(path);
|
|
||||||
*set->index = -1; // Signal mg_dash_send_change() to send new size
|
|
||||||
mg_dash_send_change(c->mgr, set);
|
|
||||||
mg_http_reply(c, 200, NULL, "true");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mg_http_serve_file(c, hm, path, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
|
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
|
||||||
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
|
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
|
||||||
mg_dash_handle_del(c, dash, u, parts);
|
mg_dash_handle_del(c, dash, u, parts);
|
||||||
|
|||||||
+4
-40
@@ -4647,9 +4647,7 @@ struct mg_field_set {
|
|||||||
// fn sets *index = -1 to signal end of iteration.
|
// fn sets *index = -1 to signal end of iteration.
|
||||||
// Size query: framework sets *index = -1 before fn(READ); fn
|
// Size query: framework sets *index = -1 before fn(READ); fn
|
||||||
// sets *index = total size
|
// sets *index = total size
|
||||||
bool (*get_dir)(const struct mg_dash_user *u, char *buf,
|
struct mg_field_set *next; // Next set in the list
|
||||||
size_t len); // Non-NULL: file array
|
|
||||||
struct mg_field_set *next; // Next set in the list
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER()
|
// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER()
|
||||||
@@ -4670,6 +4668,9 @@ struct mg_dash {
|
|||||||
int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default
|
int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default
|
||||||
struct mg_dash_user *guest; // Used when authenticate == NULL
|
struct mg_dash_user *guest; // Used when authenticate == NULL
|
||||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
||||||
|
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||||
|
// upload directories. NULL: file manager disabled
|
||||||
|
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MG_DASH_ADD_FIELD_SET(dash_, set_) \
|
#define MG_DASH_ADD_FIELD_SET(dash_, set_) \
|
||||||
@@ -4695,43 +4696,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data);
|
|||||||
// array size if *set->index < 0, or a single element update otherwise
|
// array size if *set->index < 0, or a single element update otherwise
|
||||||
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *);
|
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *);
|
||||||
|
|
||||||
// Default reader for file-backed array sets (set->get_dir != NULL)
|
|
||||||
// Usage example - this is a complete implemenation of the file manager:
|
|
||||||
// ```c
|
|
||||||
// static struct file {
|
|
||||||
// int index;
|
|
||||||
// char name[64];
|
|
||||||
// size_t size;
|
|
||||||
// uint64_t checksum;
|
|
||||||
// } s_file;
|
|
||||||
// static struct mg_field_set set_files;
|
|
||||||
//
|
|
||||||
// static bool get_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
|
||||||
// (void) u;
|
|
||||||
// mkdir("/tmp/dashboard", 0755);
|
|
||||||
// mg_snprintf(buf, len, "%s", "/tmp/dashboard");
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static struct mg_field fields_files[] = {
|
|
||||||
// {"name", MG_VAL_STR, s_file.name, sizeof(s_file.name)},
|
|
||||||
// {"size", MG_VAL_UINT64, &s_file.size, 0},
|
|
||||||
// {"checksum", MG_VAL_UINT64, &s_file.checksum, sizeof(s_file.checksum)},
|
|
||||||
// {NULL, MG_VAL_INT, NULL, 0},
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// static bool files_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|
||||||
// if (op == MG_DASH_WRITE) return s_uploads_enabled && u->level >= 7;
|
|
||||||
// if (op != MG_DASH_READ) return false;
|
|
||||||
// if (!mg_dash_dir_read(&set_files, u)) return false;
|
|
||||||
// s_file.checksum = 0; // Mock to show how to set custom fields
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static struct mg_field_set set_files = {"files", fields_files, files_fn, &s_file.index, get_dir, NULL};
|
|
||||||
// ```
|
|
||||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u);
|
|
||||||
|
|
||||||
// Helper forward declarations for Mongoose CMSIS pack modules
|
// Helper forward declarations for Mongoose CMSIS pack modules
|
||||||
extern struct mg_mgr g_mgr;
|
extern struct mg_mgr g_mgr;
|
||||||
extern void mg_dash_init(struct mg_mgr *);
|
extern void mg_dash_init(struct mg_mgr *);
|
||||||
|
|||||||
+30
-220
@@ -78,12 +78,7 @@ static int mg_dash_array_size(struct mg_field_set *set,
|
|||||||
struct mg_dash_user *u) {
|
struct mg_dash_user *u) {
|
||||||
int saved = *set->index, sz = -1;
|
int saved = *set->index, sz = -1;
|
||||||
*set->index = -1;
|
*set->index = -1;
|
||||||
if (set->fn) {
|
if (set->fn && set->fn(MG_DASH_READ, u)) sz = *set->index;
|
||||||
if (set->fn(MG_DASH_READ, u)) sz = *set->index;
|
|
||||||
} else if (set->get_dir) {
|
|
||||||
mg_dash_dir_read(set, u);
|
|
||||||
sz = *set->index;
|
|
||||||
}
|
|
||||||
*set->index = saved;
|
*set->index = saved;
|
||||||
return sz;
|
return sz;
|
||||||
}
|
}
|
||||||
@@ -101,10 +96,7 @@ static size_t mg_dash_print_array(mg_pfn_t fn, void *arg, va_list *ap) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
bool done = to >= 0 && *set->index > to;
|
bool done = to >= 0 && *set->index > to;
|
||||||
if (!done) {
|
if (!done) {
|
||||||
if (set->fn)
|
if (set->fn) set->fn(MG_DASH_READ, u);
|
||||||
set->fn(MG_DASH_READ, u);
|
|
||||||
else if (set->get_dir)
|
|
||||||
mg_dash_dir_read(set, u);
|
|
||||||
done = *set->index < 0;
|
done = *set->index < 0;
|
||||||
}
|
}
|
||||||
if (done) break;
|
if (done) break;
|
||||||
@@ -186,8 +178,7 @@ void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
|
|||||||
MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz);
|
MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz);
|
||||||
} else {
|
} else {
|
||||||
int saved_idx = set->index != NULL ? *set->index : 0;
|
int saved_idx = set->index != NULL ? *set->index : 0;
|
||||||
bool ok = set->fn ? set->fn(MG_DASH_READ, u)
|
bool ok = set->fn ? set->fn(MG_DASH_READ, u) : true;
|
||||||
: (set->get_dir ? mg_dash_dir_read(set, u) : true);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (set->index != NULL) *set->index = saved_idx;
|
if (set->index != NULL) *set->index = saved_idx;
|
||||||
continue;
|
continue;
|
||||||
@@ -234,8 +225,7 @@ static int mg_dash_parse_field(struct mg_str json, struct mg_field *f) {
|
|||||||
mg_json_get_num(json, json_path, (double *) f->value);
|
mg_json_get_num(json, json_path, (double *) f->value);
|
||||||
} else if (f->type == MG_VAL_STR && f->value_size > 0) {
|
} else if (f->type == MG_VAL_STR && f->value_size > 0) {
|
||||||
struct mg_str tok = mg_json_get_tok(json, json_path);
|
struct mg_str tok = mg_json_get_tok(json, json_path);
|
||||||
ok = tok.len >= 2 && tok.buf[0] == '"' &&
|
ok = tok.len >= 2 && tok.buf[0] == '"' && tok.buf[tok.len - 1] == '"';
|
||||||
tok.buf[tok.len - 1] == '"';
|
|
||||||
if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size);
|
if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size);
|
||||||
} else if (f->type == MG_VAL_RAW && f->value_size > 0) {
|
} else if (f->type == MG_VAL_RAW && f->value_size > 0) {
|
||||||
ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len,
|
ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len,
|
||||||
@@ -272,68 +262,6 @@ static int mg_dash_apply(struct mg_connection *c, struct mg_dash *dash,
|
|||||||
return total_count;
|
return total_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u) {
|
|
||||||
char dir[256], fname[128] = "";
|
|
||||||
struct mg_fs *fs = u->dash->upload_fs ? u->dash->upload_fs : &mg_fs_posix;
|
|
||||||
struct mg_field *name_field = NULL, *size_field = NULL;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) return false;
|
|
||||||
|
|
||||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
|
||||||
if (name_field == NULL && set->fields[i].type == MG_VAL_STR &&
|
|
||||||
strcmp(set->fields[i].name, "name") == 0)
|
|
||||||
name_field = &set->fields[i];
|
|
||||||
if (size_field == NULL && strcmp(set->fields[i].name, "size") == 0)
|
|
||||||
size_field = &set->fields[i];
|
|
||||||
}
|
|
||||||
if (name_field == NULL) return false;
|
|
||||||
|
|
||||||
if (*set->index == -1) { // Size query: count all files
|
|
||||||
int count = 0;
|
|
||||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) count++;
|
|
||||||
*set->index = count;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Regular read: scan to *set->index
|
|
||||||
int target = *set->index, cur = 0;
|
|
||||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) {
|
|
||||||
if (cur++ == target) {
|
|
||||||
mg_snprintf((char *) name_field->value, name_field->value_size, "%s",
|
|
||||||
fname);
|
|
||||||
if (size_field != NULL) {
|
|
||||||
char path[512];
|
|
||||||
size_t sz = 0;
|
|
||||||
mg_snprintf(path, sizeof(path), "%s/%s", dir, fname);
|
|
||||||
fs->st(path, &sz, NULL);
|
|
||||||
if (size_field->type == MG_VAL_UINT64)
|
|
||||||
*(uint64_t *) size_field->value = (uint64_t) sz;
|
|
||||||
else if (size_field->type == MG_VAL_INT)
|
|
||||||
*(int *) size_field->value = (int) sz;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*set->index = -1; // No more entries
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool mg_dash_set_file_name(struct mg_field_set *set,
|
|
||||||
struct mg_str name) {
|
|
||||||
size_t i;
|
|
||||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
|
||||||
struct mg_field *f = &set->fields[i];
|
|
||||||
if (f->type == MG_VAL_STR && strcmp(f->name, "name") == 0) {
|
|
||||||
mg_snprintf((char *) f->value, f->value_size, "%.*s", (int) name.len,
|
|
||||||
name.buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void mg_log_http_req(struct mg_connection *c,
|
static inline void mg_log_http_req(struct mg_connection *c,
|
||||||
struct mg_http_message *hm) {
|
struct mg_http_message *hm) {
|
||||||
int len = 0;
|
int len = 0;
|
||||||
@@ -366,77 +294,6 @@ static inline void mg_log_http_req(struct mg_connection *c,
|
|||||||
c->send.buf + n));
|
c->send.buf + n));
|
||||||
}
|
}
|
||||||
|
|
||||||
// State of one file upload to a dashboard file array. Owned by the upload
|
|
||||||
// callback, which frees it in the end call
|
|
||||||
struct mg_dash_upload {
|
|
||||||
struct mg_mgr *mgr; // Manager, to notify WebSocket clients
|
|
||||||
struct mg_dash *dash; // Dashboard whose file arrays get notified
|
|
||||||
struct mg_fd *fd; // Destination file, opened by the request handler
|
|
||||||
size_t expected; // Number of body bytes to receive
|
|
||||||
size_t received; // Number of body bytes written
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload callback for mg_http_stream_body(). Notifies clients only if the whole
|
|
||||||
// body was written. A dropped or failed upload sends no notification
|
|
||||||
static bool mg_dash_upload_cb(struct mg_http_message *hm, struct mg_str *data,
|
|
||||||
void **p) {
|
|
||||||
struct mg_dash_upload *up = (struct mg_dash_upload *) *p;
|
|
||||||
bool ok;
|
|
||||||
if (hm != NULL) return true; // Start, the file is already open
|
|
||||||
if (data != NULL) { // Next chunk
|
|
||||||
ok = up->fd->fs->wr(up->fd->fd, data->buf, data->len) == data->len;
|
|
||||||
if (ok) up->received += data->len;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
ok = up->received == up->expected; // End
|
|
||||||
mg_fs_close(up->fd);
|
|
||||||
if (ok) {
|
|
||||||
// Notify every file-backed array: the upload could belong to any of them,
|
|
||||||
// and re-querying get_dir() per recipient is what mg_dash_send_change()
|
|
||||||
// does anyway (directories can be user-specific)
|
|
||||||
struct mg_field_set *set;
|
|
||||||
for (set = up->dash->sets; set != NULL; set = set->next) {
|
|
||||||
if (set->get_dir == NULL) continue;
|
|
||||||
*set->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
|
||||||
mg_dash_send_change(up->mgr, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mg_free(up);
|
|
||||||
*p = NULL;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens dir/name and starts streaming the request body into it. Returns true
|
|
||||||
// if streaming started, otherwise the error reply has been sent already
|
|
||||||
static bool mg_dash_start_upload(struct mg_connection *c,
|
|
||||||
struct mg_http_message *hm,
|
|
||||||
struct mg_dash *dash, struct mg_fs *fs,
|
|
||||||
const char *dir, struct mg_str name) {
|
|
||||||
char path[MG_PATH_MAX];
|
|
||||||
struct mg_dash_upload *up = NULL;
|
|
||||||
struct mg_fd *fd = NULL;
|
|
||||||
size_t n = mg_snprintf(path, sizeof(path), "%s%c%.*s", dir, MG_DIRSEP,
|
|
||||||
(int) name.len, name.buf);
|
|
||||||
if (n < sizeof(path)) fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
|
||||||
if (fd != NULL) up = (struct mg_dash_upload *) mg_calloc(1, sizeof(*up));
|
|
||||||
if (up == NULL) {
|
|
||||||
if (fd != NULL) mg_fs_close(fd);
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Open failed\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
up->mgr = c->mgr;
|
|
||||||
up->dash = dash;
|
|
||||||
up->fd = fd;
|
|
||||||
up->expected = hm->body.len;
|
|
||||||
if (mg_http_stream_body(c, hm, mg_dash_upload_cb, up)) {
|
|
||||||
return true; // The callback owns up and fd now
|
|
||||||
}
|
|
||||||
mg_fs_close(fd); // Chunked body or unexpected method: not streamed
|
|
||||||
mg_free(up);
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad request\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
||||||
unsigned t = (unsigned) dash->session_auto_expiration_seconds;
|
unsigned t = (unsigned) dash->session_auto_expiration_seconds;
|
||||||
if (t == 0) t = 3600; // Default expiration time in seconds
|
if (t == 0) t = 3600; // Default expiration time in seconds
|
||||||
@@ -691,6 +548,25 @@ static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
|||||||
return mg_ota_end();
|
return mg_ota_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
||||||
|
struct mg_dash_user *u, bool stream) {
|
||||||
|
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||||
|
char root[128] = "/fs/=";
|
||||||
|
if (u == NULL) {
|
||||||
|
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||||
|
} else if (dash->files_dir(u, root + 5, sizeof(root) - 5) == false) {
|
||||||
|
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||||
|
} else {
|
||||||
|
struct mg_http_serve_opts o;
|
||||||
|
memset(&o, 0, sizeof(o));
|
||||||
|
o.root_dir = root;
|
||||||
|
o.fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
||||||
|
o.allow_delete = o.allow_upload = true;
|
||||||
|
if (stream) mg_http_serve_upload(c, hm, &o);
|
||||||
|
else mg_http_serve_dir(c, hm, &o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||||
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
||||||
@@ -704,8 +580,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
// Received headers - check authentication and possibly start uploads/ota
|
// Received headers - check authentication and possibly start uploads/ota
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
||||||
struct mg_str parts[3];
|
|
||||||
memset(parts, 0, sizeof(parts));
|
|
||||||
|
|
||||||
if (mg_match(hm->uri, mg_str("/api/hi"), NULL) ||
|
if (mg_match(hm->uri, mg_str("/api/hi"), NULL) ||
|
||||||
mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
|
mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
|
||||||
@@ -716,45 +590,14 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
} else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) {
|
} else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) {
|
||||||
mg_handle_login(c, u);
|
mg_handle_login(c, u);
|
||||||
d->marker = CONN_HANDLED;
|
d->marker = CONN_HANDLED;
|
||||||
|
} else if (u != NULL && dash->files_dir != NULL &&
|
||||||
|
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||||
|
mg_serve_fs(c, hm, u, true);
|
||||||
|
return; // Important - the serve_fs() call can invalidate hm
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||||
// Authenticated above. If streaming started, the stream handler owns
|
// Authenticated above. If streaming started, the stream handler owns
|
||||||
// the connection and c->data, so skip the marker check below
|
// the connection and c->data, so skip the marker check below
|
||||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
||||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) &&
|
|
||||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
|
||||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
|
||||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
|
||||||
struct mg_str name = parts[1];
|
|
||||||
int len =
|
|
||||||
mg_url_decode(name.buf, name.len, (char *) name.buf, name.len + 1, 0);
|
|
||||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
|
||||||
if (set == NULL || set->get_dir == NULL) {
|
|
||||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (u == NULL) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (!mg_path_is_sane(name)) {
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else {
|
|
||||||
mg_dash_set_file_name(set, name);
|
|
||||||
if (set->fn != NULL && !set->fn(MG_DASH_WRITE, u)) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else {
|
|
||||||
char dir[256];
|
|
||||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
|
||||||
d->marker = CONN_HANDLED;
|
|
||||||
} else if (mg_dash_start_upload(c, hm, dash, fs, dir, name)) {
|
|
||||||
return; // The stream handler owns the connection and c->data
|
|
||||||
} else {
|
|
||||||
d->marker = CONN_HANDLED; // Error reply has been sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (d->marker != '\0') mg_log_http_req(c, hm);
|
if (d->marker != '\0') mg_log_http_req(c, hm);
|
||||||
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
||||||
@@ -777,42 +620,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
} else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) {
|
||||||
d->u = u;
|
d->u = u;
|
||||||
mg_ws_upgrade(c, hm, NULL);
|
mg_ws_upgrade(c, hm, NULL);
|
||||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts)) {
|
} else if (dash->files_dir != NULL &&
|
||||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||||
if (set == NULL || set->get_dir == NULL) {
|
mg_serve_fs(c, hm, u, false);
|
||||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found");
|
|
||||||
} else if (u == NULL) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
} else {
|
|
||||||
char dir[256], path[512];
|
|
||||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
|
||||||
struct mg_str name = parts[1];
|
|
||||||
int len = mg_url_decode(name.buf, name.len, (char *) name.buf,
|
|
||||||
name.len + 1, 0);
|
|
||||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
|
||||||
if (!mg_path_is_sane(name)) {
|
|
||||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
|
||||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mg_snprintf(path, sizeof(path), "%s/%.*s", dir, name.len, name.buf);
|
|
||||||
if (mg_strcasecmp(hm->method, mg_str("DELETE")) == 0) {
|
|
||||||
mg_dash_set_file_name(set, name);
|
|
||||||
if (set->fn != NULL && !set->fn(MG_DASH_DELETE, u)) {
|
|
||||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
|
||||||
} else {
|
|
||||||
fs->rm(path);
|
|
||||||
*set->index = -1; // Signal mg_dash_send_change() to send new size
|
|
||||||
mg_dash_send_change(c->mgr, set);
|
|
||||||
mg_http_reply(c, 200, NULL, "true");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mg_http_serve_file(c, hm, path, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
|
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
|
||||||
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
|
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
|
||||||
mg_dash_handle_del(c, dash, u, parts);
|
mg_dash_handle_del(c, dash, u, parts);
|
||||||
|
|||||||
+4
-40
@@ -73,9 +73,7 @@ struct mg_field_set {
|
|||||||
// fn sets *index = -1 to signal end of iteration.
|
// fn sets *index = -1 to signal end of iteration.
|
||||||
// Size query: framework sets *index = -1 before fn(READ); fn
|
// Size query: framework sets *index = -1 before fn(READ); fn
|
||||||
// sets *index = total size
|
// sets *index = total size
|
||||||
bool (*get_dir)(const struct mg_dash_user *u, char *buf,
|
struct mg_field_set *next; // Next set in the list
|
||||||
size_t len); // Non-NULL: file array
|
|
||||||
struct mg_field_set *next; // Next set in the list
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER()
|
// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER()
|
||||||
@@ -96,6 +94,9 @@ struct mg_dash {
|
|||||||
int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default
|
int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default
|
||||||
struct mg_dash_user *guest; // Used when authenticate == NULL
|
struct mg_dash_user *guest; // Used when authenticate == NULL
|
||||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
||||||
|
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||||
|
// upload directories. NULL: file manager disabled
|
||||||
|
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MG_DASH_ADD_FIELD_SET(dash_, set_) \
|
#define MG_DASH_ADD_FIELD_SET(dash_, set_) \
|
||||||
@@ -121,43 +122,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data);
|
|||||||
// array size if *set->index < 0, or a single element update otherwise
|
// array size if *set->index < 0, or a single element update otherwise
|
||||||
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *);
|
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *);
|
||||||
|
|
||||||
// Default reader for file-backed array sets (set->get_dir != NULL)
|
|
||||||
// Usage example - this is a complete implemenation of the file manager:
|
|
||||||
// ```c
|
|
||||||
// static struct file {
|
|
||||||
// int index;
|
|
||||||
// char name[64];
|
|
||||||
// size_t size;
|
|
||||||
// uint64_t checksum;
|
|
||||||
// } s_file;
|
|
||||||
// static struct mg_field_set set_files;
|
|
||||||
//
|
|
||||||
// static bool get_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
|
||||||
// (void) u;
|
|
||||||
// mkdir("/tmp/dashboard", 0755);
|
|
||||||
// mg_snprintf(buf, len, "%s", "/tmp/dashboard");
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static struct mg_field fields_files[] = {
|
|
||||||
// {"name", MG_VAL_STR, s_file.name, sizeof(s_file.name)},
|
|
||||||
// {"size", MG_VAL_UINT64, &s_file.size, 0},
|
|
||||||
// {"checksum", MG_VAL_UINT64, &s_file.checksum, sizeof(s_file.checksum)},
|
|
||||||
// {NULL, MG_VAL_INT, NULL, 0},
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// static bool files_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|
||||||
// if (op == MG_DASH_WRITE) return s_uploads_enabled && u->level >= 7;
|
|
||||||
// if (op != MG_DASH_READ) return false;
|
|
||||||
// if (!mg_dash_dir_read(&set_files, u)) return false;
|
|
||||||
// s_file.checksum = 0; // Mock to show how to set custom fields
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static struct mg_field_set set_files = {"files", fields_files, files_fn, &s_file.index, get_dir, NULL};
|
|
||||||
// ```
|
|
||||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u);
|
|
||||||
|
|
||||||
// Helper forward declarations for Mongoose CMSIS pack modules
|
// Helper forward declarations for Mongoose CMSIS pack modules
|
||||||
extern struct mg_mgr g_mgr;
|
extern struct mg_mgr g_mgr;
|
||||||
extern void mg_dash_init(struct mg_mgr *);
|
extern void mg_dash_init(struct mg_mgr *);
|
||||||
|
|||||||
+20
-49
@@ -4798,23 +4798,7 @@ static struct mg_field test_files_fields[] = {
|
|||||||
{NULL, MG_VAL_INT, NULL, 0},
|
{NULL, MG_VAL_INT, NULL, 0},
|
||||||
};
|
};
|
||||||
static struct mg_field_set test_files_set = {
|
static struct mg_field_set test_files_set = {
|
||||||
"files", test_files_fields, NULL, &s_test_file_index, test_upload_dir, NULL};
|
"files", test_files_fields, NULL, &s_test_file_index, NULL};
|
||||||
static struct test_file_entry s_delete_file;
|
|
||||||
static char s_deleted_file[sizeof(s_delete_file.name)];
|
|
||||||
static char s_uploaded_file[sizeof(s_delete_file.name)];
|
|
||||||
static struct mg_field_set *s_delete_file_set;
|
|
||||||
static bool delete_file_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|
||||||
if (op == MG_DASH_READ) return mg_dash_dir_read(s_delete_file_set, u);
|
|
||||||
if (op == MG_DASH_WRITE) {
|
|
||||||
mg_snprintf(s_uploaded_file, sizeof(s_uploaded_file), "%s",
|
|
||||||
s_delete_file.name);
|
|
||||||
}
|
|
||||||
if (op == MG_DASH_DELETE) {
|
|
||||||
mg_snprintf(s_deleted_file, sizeof(s_deleted_file), "%s",
|
|
||||||
s_delete_file.name);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
static bool arr_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
static bool arr_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
||||||
(void) u;
|
(void) u;
|
||||||
if (op == MG_DASH_READ) {
|
if (op == MG_DASH_READ) {
|
||||||
@@ -4861,20 +4845,18 @@ static void test_dash(void) {
|
|||||||
{"admin", MG_VAL_INT, &admin, sizeof(admin)},
|
{"admin", MG_VAL_INT, &admin, sizeof(admin)},
|
||||||
{NULL, MG_VAL_INT, NULL, 0},
|
{NULL, MG_VAL_INT, NULL, 0},
|
||||||
};
|
};
|
||||||
struct mg_field_set set1 = {"set1", fields1, set1_fn, NULL, NULL, NULL};
|
struct mg_field_set set1 = {"set1", fields1, set1_fn, NULL, NULL};
|
||||||
struct mg_field_set set2 = {"set2", fields2, set2_fn, NULL, NULL, NULL};
|
struct mg_field_set set2 = {"set2", fields2, set2_fn, NULL, NULL};
|
||||||
struct mg_field_set set3 = {"set3", fields3, set3_fn, NULL, NULL, NULL};
|
struct mg_field_set set3 = {"set3", fields3, set3_fn, NULL, NULL};
|
||||||
struct mg_field arr_fields[] = {
|
struct mg_field arr_fields[] = {
|
||||||
{"val", MG_VAL_INT, &s_arr_val, sizeof(s_arr_val)},
|
{"val", MG_VAL_INT, &s_arr_val, sizeof(s_arr_val)},
|
||||||
{NULL, MG_VAL_INT, NULL, 0},
|
{NULL, MG_VAL_INT, NULL, 0},
|
||||||
};
|
};
|
||||||
struct mg_field_set arr_set = {"arr", arr_fields, arr_fn, &s_arr_index, NULL,
|
struct mg_field_set arr_set = {"arr", arr_fields, arr_fn, &s_arr_index, NULL};
|
||||||
NULL};
|
|
||||||
const char *get_all_expected =
|
const char *get_all_expected =
|
||||||
"{\"arr\":17,\"set3\":{\"admin\":7},"
|
"{\"arr\":17,\"set3\":{\"admin\":7},"
|
||||||
"\"set2\":{\"two\":false},"
|
"\"set2\":{\"two\":false},"
|
||||||
"\"set1\":{\"one\":1,\"three\":\"t: 1\"},"
|
"\"set1\":{\"one\":1,\"three\":\"t: 1\"}}\n";
|
||||||
"\"files\":0}\n";
|
|
||||||
const char *set1_req = "POST /api/set HTTP/1.0\nContent-Length: 18\n\n"
|
const char *set1_req = "POST /api/set HTTP/1.0\nContent-Length: 18\n\n"
|
||||||
"{\"set1\":{\"one\":2}}";
|
"{\"set1\":{\"one\":2}}";
|
||||||
const char *set1_set3_req = "POST /api/set HTTP/1.0\n"
|
const char *set1_set3_req = "POST /api/set HTTP/1.0\n"
|
||||||
@@ -5227,51 +5209,40 @@ static void test_dash(void) {
|
|||||||
ASSERT(mg_http_listen(&mgr2, url2, mg_dash_ev_handler, &dash2) != NULL);
|
ASSERT(mg_http_listen(&mgr2, url2, mg_dash_ev_handler, &dash2) != NULL);
|
||||||
ASSERT(fetch(&mgr2, buf, url2, "GET /api/get HTTP/1.0\n\n") == 200);
|
ASSERT(fetch(&mgr2, buf, url2, "GET /api/get HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(strstr(buf, files_probe) == NULL); // no files fieldset
|
ASSERT(strstr(buf, files_probe) == NULL); // no files fieldset
|
||||||
ASSERT(fetch(&mgr2, buf, url2, "GET /fs/files/test.txt HTTP/1.0\n\n") == 404);
|
ASSERT(fetch(&mgr2, buf, url2, "GET /fs/test.txt HTTP/1.0\n\n") == 404);
|
||||||
ASSERT(fetch(&mgr2, buf, url2,
|
ASSERT(fetch(&mgr2, buf, url2,
|
||||||
"POST /fs/files/test.txt HTTP/1.0\nContent-Length: 5\n\nhello") ==
|
"POST /fs/test.txt HTTP/1.0\nContent-Length: 5\n\nhello") ==
|
||||||
404);
|
404);
|
||||||
mg_mgr_free(&mgr2);
|
mg_mgr_free(&mgr2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test files fieldset with get_dir: /fs/ endpoints active
|
// Test the built-in file manager (struct mg_dash::files_dir): /fs/ active
|
||||||
{
|
{
|
||||||
const char *url3 = "http://localhost:26354";
|
const char *url3 = "http://localhost:26354";
|
||||||
struct mg_mgr mgr3;
|
struct mg_mgr mgr3;
|
||||||
struct mg_dash dash3;
|
struct mg_dash dash3;
|
||||||
static int idx3;
|
struct mg_str uploaded;
|
||||||
static struct mg_field ff3[] = {
|
|
||||||
{"name", MG_VAL_STR, s_delete_file.name, sizeof(s_delete_file.name)},
|
|
||||||
{"size", MG_VAL_UINT64, &s_delete_file.size, 0},
|
|
||||||
{NULL, MG_VAL_INT, NULL, 0},
|
|
||||||
};
|
|
||||||
static struct mg_field_set fset3 = {"files", ff3, delete_file_fn, &idx3,
|
|
||||||
test_upload_dir, NULL};
|
|
||||||
memset(&dash3, 0, sizeof(dash3));
|
memset(&dash3, 0, sizeof(dash3));
|
||||||
s_deleted_file[0] = '\0';
|
dash3.files_dir = test_upload_dir;
|
||||||
s_uploaded_file[0] = '\0';
|
|
||||||
s_delete_file_set = &fset3;
|
|
||||||
MG_DASH_ADD_FIELD_SET(&dash3, &fset3);
|
|
||||||
mg_mgr_init(&mgr3);
|
mg_mgr_init(&mgr3);
|
||||||
mg_mem_files = mg_packed_files;
|
mg_mem_files = mg_packed_files;
|
||||||
ASSERT(mg_http_listen(&mgr3, url3, mg_dash_ev_handler, &dash3) != NULL);
|
ASSERT(mg_http_listen(&mgr3, url3, mg_dash_ev_handler, &dash3) != NULL);
|
||||||
ASSERT(fetch(&mgr3, buf, url3, "GET /api/get HTTP/1.0\n\n") == 200);
|
ASSERT(fetch(&mgr3, buf, url3, "GET /api/get HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(strstr(buf, files_probe) != NULL); // files fieldset present
|
ASSERT(strstr(buf, files_probe) == NULL); // files manager is not a fieldset
|
||||||
ASSERT(fetch(&mgr3, buf, url3,
|
ASSERT(fetch(&mgr3, buf, url3,
|
||||||
"DELETE /fs/files/nofile.txt HTTP/1.0\n\n") == 200);
|
"DELETE /fs/nofile.txt HTTP/1.0\n\n") == 404);
|
||||||
ASSERT(strcmp(s_deleted_file, "nofile.txt") == 0);
|
|
||||||
ASSERT(fetch(&mgr3, buf, url3,
|
ASSERT(fetch(&mgr3, buf, url3,
|
||||||
"DELETE /fs/files/..%2Fevil.txt HTTP/1.0\n\n") == 400);
|
"DELETE /fs/..%2Fevil.txt HTTP/1.0\n\n") == 400);
|
||||||
ASSERT(strcmp(s_deleted_file, "nofile.txt") == 0);
|
|
||||||
ASSERT(fetch(&mgr3, buf, url3,
|
ASSERT(fetch(&mgr3, buf, url3,
|
||||||
"PUT /fs/files/test.bin HTTP/1.0\nContent-Length: 4\n\ndata") ==
|
"PUT /fs/test.bin HTTP/1.0\nContent-Length: 4\n\ndata") ==
|
||||||
200);
|
200);
|
||||||
ASSERT(strcmp(s_uploaded_file, "test.bin") == 0);
|
uploaded = mg_file_read(&mg_fs_posix, "mongoose_dash_test/test.bin");
|
||||||
|
ASSERT(mg_strcmp(uploaded, mg_str("data")) == 0);
|
||||||
|
mg_free((void *) uploaded.buf);
|
||||||
ASSERT(fetch(&mgr3, buf, url3,
|
ASSERT(fetch(&mgr3, buf, url3,
|
||||||
"PUT /fs/files/..%2Fevil.bin HTTP/1.0\nContent-Length: "
|
"PUT /fs/..%2Fevil.bin HTTP/1.0\nContent-Length: "
|
||||||
"4\n\ndata") == 400);
|
"4\n\ndata") == 400);
|
||||||
ASSERT(strcmp(s_uploaded_file, "test.bin") == 0);
|
ASSERT(fetch(&mgr3, buf, url3, "GET /fs/ HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(fetch(&mgr3, buf, url3, "GET /api/get/files/0 HTTP/1.0\n\n") == 200);
|
|
||||||
ASSERT(strstr(buf, test_bin_probe) != NULL);
|
ASSERT(strstr(buf, test_bin_probe) != NULL);
|
||||||
mg_mgr_free(&mgr3);
|
mg_mgr_free(&mgr3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ static bool arr_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct mg_field_set field_set_arr = {
|
static struct mg_field_set field_set_arr = {
|
||||||
"event", fields_arr, arr_fn, &s_index, NULL, NULL,
|
"event", fields_arr, arr_fn, &s_index, NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
void mg_dash_init(struct mg_mgr *mgr) {
|
void mg_dash_init(struct mg_mgr *mgr) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ static struct mg_field fields_actions[] = {
|
|||||||
{NULL, MG_VAL_INT, NULL, 0},
|
{NULL, MG_VAL_INT, NULL, 0},
|
||||||
};
|
};
|
||||||
static struct mg_field_set set_actions = {"actions", fields_actions, actions_fn,
|
static struct mg_field_set set_actions = {"actions", fields_actions, actions_fn,
|
||||||
NULL, NULL, NULL};
|
NULL, NULL};
|
||||||
|
|
||||||
// Control panel
|
// Control panel
|
||||||
// s_led1, s_led2, s_led3 are used to communicate LED status
|
// s_led1, s_led2, s_led3 are used to communicate LED status
|
||||||
@@ -43,7 +43,7 @@ static struct mg_field fields_leds[] = {
|
|||||||
{NULL, MG_VAL_INT, NULL, 0},
|
{NULL, MG_VAL_INT, NULL, 0},
|
||||||
};
|
};
|
||||||
static struct mg_field_set set_leds = {"leds", fields_leds, leds_fn,
|
static struct mg_field_set set_leds = {"leds", fields_leds, leds_fn,
|
||||||
NULL, NULL, NULL};
|
NULL, NULL};
|
||||||
|
|
||||||
// Read-only device Metrics
|
// Read-only device Metrics
|
||||||
static int s_ram = 32, s_cpu = 7;
|
static int s_ram = 32, s_cpu = 7;
|
||||||
@@ -66,7 +66,7 @@ static struct mg_field fields_metrics[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct mg_field_set set_metrics = {"metrics", fields_metrics, metrics_fn,
|
static struct mg_field_set set_metrics = {"metrics", fields_metrics, metrics_fn,
|
||||||
NULL, NULL, NULL};
|
NULL, NULL};
|
||||||
|
|
||||||
// Read-write device settings
|
// Read-write device settings
|
||||||
static bool s_enable_login = false;
|
static bool s_enable_login = false;
|
||||||
@@ -105,7 +105,7 @@ static struct mg_field fields_settings[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct mg_field_set set_settings = {
|
static struct mg_field_set set_settings = {
|
||||||
"settings", fields_settings, settings_fn, NULL, NULL, NULL};
|
"settings", fields_settings, settings_fn, NULL, NULL};
|
||||||
|
|
||||||
#define NUM_POINTS_GRAPH1 7 // How many graph1 data points to send
|
#define NUM_POINTS_GRAPH1 7 // How many graph1 data points to send
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ static bool graph1_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static struct mg_field_set set_graph1 = {"graph1", fields_graph1, graph1_fn,
|
static struct mg_field_set set_graph1 = {"graph1", fields_graph1, graph1_fn,
|
||||||
NULL, NULL, NULL};
|
NULL, NULL};
|
||||||
|
|
||||||
#define NUM_POINTS_GRAPH2 100 // How many graph2 data points to send
|
#define NUM_POINTS_GRAPH2 100 // How many graph2 data points to send
|
||||||
static char s_graph2_data[NUM_POINTS_GRAPH2 * 4 + 2 + 1];
|
static char s_graph2_data[NUM_POINTS_GRAPH2 * 4 + 2 + 1];
|
||||||
@@ -187,7 +187,7 @@ static bool graph2_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|||||||
return op == MG_DASH_READ || op == MG_DASH_WRITE;
|
return op == MG_DASH_READ || op == MG_DASH_WRITE;
|
||||||
}
|
}
|
||||||
static struct mg_field_set set_graph2 = {"graph2", fields_graph2, graph2_fn,
|
static struct mg_field_set set_graph2 = {"graph2", fields_graph2, graph2_fn,
|
||||||
NULL, NULL, NULL};
|
NULL, NULL};
|
||||||
|
|
||||||
static int authenticate(char *user, size_t userlen, const char *pass) {
|
static int authenticate(char *user, size_t userlen, const char *pass) {
|
||||||
int level = 0; // Authentication failure
|
int level = 0; // Authentication failure
|
||||||
@@ -201,42 +201,14 @@ static int authenticate(char *user, size_t userlen, const char *pass) {
|
|||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct file {
|
static bool files_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
||||||
int index;
|
(void) u; // Same dir for every user. Key off u->name for per-user dirs
|
||||||
char name[64];
|
|
||||||
size_t size;
|
|
||||||
uint64_t checksum;
|
|
||||||
} s_file;
|
|
||||||
static struct mg_field_set set_files;
|
|
||||||
|
|
||||||
static bool get_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
|
||||||
(void) u;
|
|
||||||
mg_snprintf(buf, len, "%s", "/tmp/dashboard");
|
mg_snprintf(buf, len, "%s", "/tmp/dashboard");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mg_field fields_files[] = {
|
|
||||||
{"name", MG_VAL_STR, s_file.name, sizeof(s_file.name)},
|
|
||||||
{"size", MG_VAL_UINT64, &s_file.size, 0},
|
|
||||||
{"checksum", MG_VAL_UINT64, &s_file.checksum, sizeof(s_file.checksum)},
|
|
||||||
{NULL, MG_VAL_INT, NULL, 0},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Custom reader: let the framework fill "name" and "size", then add our
|
|
||||||
// own field. A real implementation would hash the file instead of
|
|
||||||
// hardcoding zero
|
|
||||||
static bool files_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
|
||||||
if (op != MG_DASH_READ) return false;
|
|
||||||
if (!mg_dash_dir_read(&set_files, u)) return false;
|
|
||||||
s_file.checksum = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct mg_field_set set_files = {"files", fields_files, files_fn,
|
|
||||||
&s_file.index, get_dir, NULL};
|
|
||||||
|
|
||||||
void mg_dash_init(struct mg_mgr *mgr) {
|
void mg_dash_init(struct mg_mgr *mgr) {
|
||||||
MG_DASH_ADD_FIELD_SET(&s_dash, &set_files);
|
s_dash.files_dir = files_dir; // Built-in file manager, see mg_dash.h
|
||||||
MG_DASH_ADD_FIELD_SET(&s_dash, &set_leds);
|
MG_DASH_ADD_FIELD_SET(&s_dash, &set_leds);
|
||||||
MG_DASH_ADD_FIELD_SET(&s_dash, &set_metrics);
|
MG_DASH_ADD_FIELD_SET(&s_dash, &set_metrics);
|
||||||
MG_DASH_ADD_FIELD_SET(&s_dash, &set_settings);
|
MG_DASH_ADD_FIELD_SET(&s_dash, &set_settings);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
leds: { led1: true, led2: false, led3: false },
|
leds: { led1: true, led2: false, led3: false },
|
||||||
settings: { volume: 12, log_level: 3, name: 'Galway', enable_login: true, ota_version: '1.2.3', ota_status: 'No checks yet', ota_url: 'http://my-product.com/ota.json', ota_interval: 30 },
|
settings: { volume: 12, log_level: 3, name: 'Galway', enable_login: true, ota_version: '1.2.3', ota_status: 'No checks yet', ota_url: 'http://my-product.com/ota.json', ota_interval: 30 },
|
||||||
metrics: { temperature: 1.2, cpu: 4, ram: 5, version: '1.2.3' },
|
metrics: { temperature: 1.2, cpu: 4, ram: 5, version: '1.2.3' },
|
||||||
files: {start: 0, end: 100, data: [{name: 'device-log-2026-04-25.txt', size: 8625563}]},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -110,6 +109,7 @@
|
|||||||
Dashboard.on('ready', function() {
|
Dashboard.on('ready', function() {
|
||||||
setupAutoLogout();
|
setupAutoLogout();
|
||||||
document.body.style.visibility = 'visible';
|
document.body.style.visibility = 'visible';
|
||||||
|
refreshFiles();
|
||||||
});
|
});
|
||||||
Dashboard.login().catch(err => showLogin(true));
|
Dashboard.login().catch(err => showLogin(true));
|
||||||
|
|
||||||
@@ -170,13 +170,80 @@
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Intercept change notifications: re-plot graphs, reload the file
|
// Intercept change notifications: re-plot graphs
|
||||||
// list whenever its size changes (file uploaded or deleted)
|
|
||||||
Dashboard.on('change', args => {
|
Dashboard.on('change', args => {
|
||||||
args?.graph1?.data && updateGraph1(args?.graph1?.data);
|
args?.graph1?.data && updateGraph1(args?.graph1?.data);
|
||||||
args?.graph2?.data && updateGraph2(args?.graph2?.data);
|
args?.graph2?.data && updateGraph2(args?.graph2?.data);
|
||||||
typeof args?.files === 'number' && Dashboard.call('get', { name: 'files', start: 0, end: 100 });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// File manager: served directly by the built-in mg_dash file manager
|
||||||
|
// (see struct mg_dash::files_dir), not by the generic data-repeat
|
||||||
|
// binding, since the listing endpoint returns a plain JSON array
|
||||||
|
const filesListEl = document.getElementById('files-list');
|
||||||
|
const filesCountEl = document.getElementById('files-count');
|
||||||
|
const filesUploadEl = document.getElementById('files-upload-input');
|
||||||
|
|
||||||
|
function renderFiles(list) {
|
||||||
|
filesCountEl.textContent = list.length + ' Files';
|
||||||
|
filesListEl.innerHTML = list.map(f => `
|
||||||
|
<div class="file-row">
|
||||||
|
<div class="file-info">
|
||||||
|
<span class="file-icon" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Z" />
|
||||||
|
<path d="M14 2v5h5" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div class="file-name">${f.name}</div>
|
||||||
|
</div>
|
||||||
|
<div class="file-size">${f.size}</div>
|
||||||
|
<div class="file-actions">
|
||||||
|
<a href="fs/${encodeURIComponent(f.name)}" target="_blank" class="btn btn-secondary">
|
||||||
|
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M12 3v12" />
|
||||||
|
<path d="m7 10 5 5 5-5" />
|
||||||
|
<path d="M5 21h14" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<button data-del="${encodeURIComponent(f.name)}" class="btn btn-secondary">
|
||||||
|
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M3 6h18" />
|
||||||
|
<path d="M8 6V4h8v2" />
|
||||||
|
<path d="M19 6l-1 14H6L5 6" />
|
||||||
|
<path d="M10 11v5" />
|
||||||
|
<path d="M14 11v5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`).join('');
|
||||||
|
filesListEl.querySelectorAll('[data-del]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
fetch('fs/' + btn.dataset.del, { method: 'DELETE' }).then(refreshFiles);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFiles() {
|
||||||
|
fetch('fs/')
|
||||||
|
.then(r => r.ok ? r.json() : Promise.reject(r.statusText))
|
||||||
|
.then(list => renderFiles(list.filter(f => !f.dir)))
|
||||||
|
.catch(err => console.log('Files list failed', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
filesUploadEl.addEventListener('change', ev => {
|
||||||
|
const f = ev.target.files[0];
|
||||||
|
ev.target.value = '';
|
||||||
|
if (!f) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(f);
|
||||||
|
reader.onload = () => {
|
||||||
|
fetch('fs/' + encodeURIComponent(f.name), { method: 'POST', body: reader.result })
|
||||||
|
.then(refreshFiles)
|
||||||
|
.catch(err => console.log('Upload failed', err));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@@ -691,7 +758,7 @@ a, a:visited, a:link { text-decoration: none; }
|
|||||||
<div class="panel-title">
|
<div class="panel-title">
|
||||||
<span>
|
<span>
|
||||||
<span>File Manager</span>
|
<span>File Manager</span>
|
||||||
<span class="metric-status">${files.size ?? files.data.length} Files</span>
|
<span class="metric-status" id="files-count">0 Files</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
@@ -701,43 +768,12 @@ a, a:visited, a:link { text-decoration: none; }
|
|||||||
<path d="M5 21h14" />
|
<path d="M5 21h14" />
|
||||||
</svg>
|
</svg>
|
||||||
Upload
|
Upload
|
||||||
<input type="file" hidden data-upload="files" />
|
<input type="file" hidden id="files-upload-input" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="file-list" data-repeat="files.data" data-iterator="f">
|
<div class="file-list" id="files-list"></div>
|
||||||
<div class="file-row">
|
|
||||||
<div class="file-info">
|
|
||||||
<span class="file-icon" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Z" />
|
|
||||||
<path d="M14 2v5h5" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div class="file-name">${f.name}</div>
|
|
||||||
</div>
|
|
||||||
<div class="file-size">${f.size}</div>
|
|
||||||
<div class="file-actions">
|
|
||||||
<a href="fs/files/${encodeURIComponent(f.name)}" target="_blank" class="btn btn-secondary">
|
|
||||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M12 3v12" />
|
|
||||||
<path d="m7 10 5 5 5-5" />
|
|
||||||
<path d="M5 21h14" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<button onclick="fetch('fs/files/${encodeURIComponent(f.name)}', {method: 'DELETE'}); return false;" class="btn btn-secondary">
|
|
||||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M3 6h18" />
|
|
||||||
<path d="M8 6V4h8v2" />
|
|
||||||
<path d="M19 6l-1 14H6L5 6" />
|
|
||||||
<path d="M10 11v5" />
|
|
||||||
<path d="M14 11v5" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ static struct mg_field fields_status[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct mg_field_set field_set_status = {
|
static struct mg_field_set field_set_status = {
|
||||||
"status", fields_status, status_fn, NULL, NULL, NULL,
|
"status", fields_status, status_fn, NULL, NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
void mg_dash_init(struct mg_mgr *mgr) {
|
void mg_dash_init(struct mg_mgr *mgr) {
|
||||||
|
|||||||
Reference in new issue
Block a user