mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-16 10:39:49 +00:00
Adjust dash to use core file manager
This commit is contained in:
1 parent
1be11a66e3
commit
f62f3545b4
9 files changed
+173
-646
No files matched your search
@@ -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 = {
|
||||
"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) {
|
||||
|
||||
@@ -27,7 +27,7 @@ static struct mg_field fields_actions[] = {
|
||||
{NULL, MG_VAL_INT, NULL, 0},
|
||||
};
|
||||
static struct mg_field_set set_actions = {"actions", fields_actions, actions_fn,
|
||||
NULL, NULL, NULL};
|
||||
NULL, NULL};
|
||||
|
||||
// Control panel
|
||||
// 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},
|
||||
};
|
||||
static struct mg_field_set set_leds = {"leds", fields_leds, leds_fn,
|
||||
NULL, NULL, NULL};
|
||||
NULL, NULL};
|
||||
|
||||
// Read-only device Metrics
|
||||
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,
|
||||
NULL, NULL, NULL};
|
||||
NULL, NULL};
|
||||
|
||||
// Read-write device settings
|
||||
static bool s_enable_login = false;
|
||||
@@ -105,7 +105,7 @@ static struct mg_field fields_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
|
||||
|
||||
@@ -150,7 +150,7 @@ static bool graph1_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
||||
return true;
|
||||
}
|
||||
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
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
int level = 0; // Authentication failure
|
||||
@@ -201,42 +201,14 @@ static int authenticate(char *user, size_t userlen, const char *pass) {
|
||||
return level;
|
||||
}
|
||||
|
||||
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;
|
||||
static bool files_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
||||
(void) u; // Same dir for every user. Key off u->name for per-user dirs
|
||||
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},
|
||||
};
|
||||
|
||||
// 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) {
|
||||
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_metrics);
|
||||
MG_DASH_ADD_FIELD_SET(&s_dash, &set_settings);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
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 },
|
||||
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>
|
||||
@@ -110,6 +109,7 @@
|
||||
Dashboard.on('ready', function() {
|
||||
setupAutoLogout();
|
||||
document.body.style.visibility = 'visible';
|
||||
refreshFiles();
|
||||
});
|
||||
Dashboard.login().catch(err => showLogin(true));
|
||||
|
||||
@@ -170,13 +170,80 @@
|
||||
]);
|
||||
};
|
||||
|
||||
// Intercept change notifications: re-plot graphs, reload the file
|
||||
// list whenever its size changes (file uploaded or deleted)
|
||||
// Intercept change notifications: re-plot graphs
|
||||
Dashboard.on('change', args => {
|
||||
args?.graph1?.data && updateGraph1(args?.graph1?.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>
|
||||
<style>
|
||||
@@ -691,7 +758,7 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
<div class="panel-title">
|
||||
<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>
|
||||
<div class="header-actions">
|
||||
<label class="btn btn-primary">
|
||||
@@ -701,43 +768,12 @@ a, a:visited, a:link { text-decoration: none; }
|
||||
<path d="M5 21h14" />
|
||||
</svg>
|
||||
Upload
|
||||
<input type="file" hidden data-upload="files" />
|
||||
<input type="file" hidden id="files-upload-input" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="file-list" data-repeat="files.data" data-iterator="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/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 class="file-list" id="files-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ static struct mg_field fields_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) {
|
||||
|
||||
Reference in new issue
Block a user