OVMS3/OVMS.V3/components/ovms_webserver/src/web_framework.cpp

862 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2018 Michael Balzer
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
//static const char *TAG = "webserver";
#include <string.h>
#include <stdio.h>
#include "ovms_webserver.h"
#include "ovms_config.h"
#include "ovms_metrics.h"
#include "ovms_housekeeping.h"
#include "buffered_shell.h"
#include "metrics_standard.h"
#include "vehicle.h"
/**
* encode_html: HTML encode value
* " → &quot;
* ' → &#x27; (Note: instead of &apos; for IE compatibility)
* < → &lt;
* > → &gt;
* & → &amp;
*/
std::string PageContext::encode_html(const char* text) {
int len = strlen(text);
std::string buf;
buf.reserve(len);
for (int i=0; i < len; i++) {
char ch = text[i];
switch(ch) {
case '\"':
buf.append("&quot;");
break;
case '\'':
buf.append("&#x27;");
break;
case '<':
buf.append("&lt;");
break;
case '>':
buf.append("&gt;");
break;
case '&':
buf.append("&amp;");
break;
default:
buf.append(&ch,1);
}
}
return buf;
}
std::string PageContext::encode_html(std::string text) {
return encode_html(text.c_str());
}
extram::string PageContext::encode_html(const extram::string& text) {
extram::string buf;
buf.reserve(text.length() + 500);
for (int i=0; i<text.length(); i++) {
char ch = text[i];
switch (ch) {
case '\"':
buf.append("&quot;");
break;
case '\'':
buf.append("&#x27;");
break;
case '<':
buf.append("&lt;");
break;
case '>':
buf.append("&gt;");
break;
case '&':
buf.append("&amp;");
break;
default:
buf.append(&ch,1);
}
}
return buf;
}
#define _attr(text) (encode_html(text).c_str())
#define _html(text) (encode_html(text).c_str())
#define __attr(text) (c.encode_html(text).c_str())
#define __html(text) (c.encode_html(text).c_str())
/**
* make_id: derive DOM id from text
*
*/
std::string PageContext::make_id(const char* text) {
std::string buf;
char lc = 0;
int len = strlen(text);
for (int i=0; i<len; i++) {
if (isalnum(text[i]))
buf += (lc = tolower(text[i]));
else if (lc && lc != '-')
buf += (lc = '-');
}
while (lc == '-') {
lc = buf.back();
buf.pop_back();
}
return buf;
}
std::string PageContext::make_id(std::string text) {
return make_id(text.c_str());
}
std::string PageContext::getvar(const std::string& name, size_t maxlen /*=200*/) {
std::string res;
char* varbuf = new char[maxlen];
if (!varbuf)
return res;
if (method == "POST")
mg_get_http_var(&hm->body, name.c_str(), varbuf, maxlen);
else
mg_get_http_var(&hm->query_string, name.c_str(), varbuf, maxlen);
res.assign(varbuf);
delete[] varbuf;
return res;
}
bool PageContext::getvar(const std::string& name, extram::string& dst) {
int len;
if (method == "POST") {
dst.resize(hm->body.len, '\0');
len = mg_get_http_var(&hm->body, name.c_str(), &dst[0], hm->body.len);
} else {
dst.resize(hm->query_string.len, '\0');
len = mg_get_http_var(&hm->query_string, name.c_str(), &dst[0], hm->query_string.len);
}
dst.resize((len >= 0) ? len : 0);
return (len >= 0);
}
/**
* HTML generation utils (Bootstrap widgets)
*/
void PageContext::error(int code, const char* text) {
mg_http_send_error(nc, code, text);
}
void PageContext::head(int code, const char* headers /*=NULL*/) {
if (!headers) {
headers =
"Content-Type: text/html; charset=utf-8\r\n"
"Cache-Control: no-cache";
}
mg_send_head(nc, code, -1, headers);
}
void PageContext::print(const std::string text) {
mg_send_http_chunk(nc, text.data(), text.size());
}
void PageContext::print(const extram::string text) {
mg_send_http_chunk(nc, text.data(), text.size());
}
void PageContext::print(const char* text) {
mg_send_http_chunk(nc, text, strlen(text));
}
void PageContext::printf(const char *fmt, ...) {
char* buf = NULL;
int len;
va_list ap;
va_start(ap, fmt);
len = vasprintf(&buf, fmt, ap);
va_end(ap);
if (len >= 0)
mg_send_http_chunk(nc, buf, len);
if (buf)
free(buf);
}
void PageContext::done() {
mg_send_http_chunk(nc, "", 0);
}
void PageContext::panel_start(const char* type, const char* title) {
mg_printf_http_chunk(nc,
"<div class=\"panel panel-%s\" id=\"panel-%s\">"
"<div class=\"panel-heading\">%s</div>"
"<div class=\"panel-body\">"
, _attr(type), make_id(title).c_str(), title);
}
void PageContext::panel_end(const char* footer) {
mg_printf_http_chunk(nc, (footer && footer[0])
? "</div><div class=\"panel-footer\">%s</div></div>"
: "</div></div>"
, footer);
}
void PageContext::form_start(std::string action, const char* target /*=NULL*/) {
mg_printf_http_chunk(nc,
"<form class=\"form-horizontal\" method=\"post\" action=\"%s\" target=\"%s\">"
, _attr(action)
, target ? _attr(target) : "#main");
}
void PageContext::form_end() {
mg_printf_http_chunk(nc, "</form>");
}
void PageContext::input(const char* type, const char* label, const char* name, const char* value,
const char* placeholder /*=NULL*/, const char* helptext /*=NULL*/, const char* moreattrs /*=NULL*/,
const char* unit /*=NULL*/) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s%s</label>"
"<div class=\"col-sm-9\">"
"%s" // unit (input-group)
"<input type=\"%s\" class=\"form-control\" placeholder=\"%s%s\" name=\"%s\" id=\"input-%s\" value=\"%s\" %s>"
"%s%s%s" // unit
"%s%s%s" // helptext
"</div>"
"</div>"
, _attr(name)
, label ? label : "", label ? ":" : ""
, unit ? "<div class=\"input-group\">" : ""
, _attr(type)
, placeholder ? "" : "Enter "
, placeholder ? _attr(placeholder) : _attr(label)
, _attr(name), _attr(name), _attr(value)
, moreattrs ? moreattrs : ""
, unit ? "<div class=\"input-group-addon input-unit\">" : ""
, unit ? unit : ""
, unit ? "</div></div>" : ""
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : ""
);
}
void PageContext::input_text(const char* label, const char* name, const char* value,
const char* placeholder /*=NULL*/, const char* helptext /*=NULL*/, const char* moreattrs /*=NULL*/) {
input("text", label, name, value, placeholder, helptext, moreattrs);
}
void PageContext::input_password(const char* label, const char* name, const char* value,
const char* placeholder /*=NULL*/, const char* helptext /*=NULL*/, const char* moreattrs /*=NULL*/) {
input("password", label, name, value, placeholder, helptext, moreattrs);
}
void PageContext::input_select_start(const char* label, const char* name) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s:</label>"
"<div class=\"col-sm-9\">"
"<select class=\"form-control\" size=\"1\" name=\"%s\" id=\"input-%s\">"
, _attr(name), label, _attr(name), _attr(name));
}
void PageContext::input_select_option(const char* label, const char* value, bool selected) {
mg_printf_http_chunk(nc,
"<option value=\"%s\"%s>%s</option>"
, _attr(value), selected ? " selected" : "", label);
}
void PageContext::input_select_end(const char* helptext /*=NULL*/) {
mg_printf_http_chunk(nc, "</select>%s%s%s</div></div>"
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : "");
}
void PageContext::input_radiobtn_start(const char* label, const char* name) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s:</label>"
"<div class=\"col-sm-9\">"
"<div id=\"input-%s\" class=\"btn-group\" data-toggle=\"buttons\">"
, _attr(name), label, _attr(name));
}
void PageContext::input_radiobtn_option(const char* name, const char* label, const char* value, bool selected) {
mg_printf_http_chunk(nc,
"<label class=\"btn btn-default %s\">"
"<input type=\"radio\" name=\"%s\" value=\"%s\" %s autocomplete=\"off\"> %s"
"</label>"
, selected ? "active" : ""
, _attr(name), _attr(value)
, selected ? "checked" : ""
, label);
}
void PageContext::input_radiobtn_end(const char* helptext /*=NULL*/) {
mg_printf_http_chunk(nc, "</div>%s%s%s</div></div>"
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : "");
}
void PageContext::input_radio_start(const char* label, const char* name) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s:</label>"
"<div class=\"col-sm-9\">"
, _attr(name), label, _attr(name));
}
void PageContext::input_radio_option(const char* name, const char* label, const char* value, bool selected) {
mg_printf_http_chunk(nc,
"<div class=\"radio\"><label><input type=\"radio\" name=\"%s\"" " value=\"%s\" %s>%s</label></div>"
, _attr(name), _attr(value)
, selected ? "checked" : ""
, label);
}
void PageContext::input_radio_end(const char* helptext /*=NULL*/) {
mg_printf_http_chunk(nc, "%s%s%s</div></div>"
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : "");
}
void PageContext::input_checkbox(const char* label, const char* name, bool value,
const char* helptext /*=NULL*/) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<div class=\"col-sm-9 col-sm-offset-3\">"
"<div class=\"checkbox\">"
"<label><input type=\"checkbox\" name=\"%s\" value=\"yes\" %s> %s</label>"
"</div>"
"%s%s%s"
"</div>"
"</div>"
, _attr(name), value ? "checked" : "", label
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : ""
);
}
void PageContext::input_slider(const char* label, const char* name, int size, const char* unit,
int enabled, double value, double defval, double min, double max, double step /*=1*/,
const char* helptext /*=NULL*/) {
int width = 50 + size * 10;
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s:</label>"
"<div class=\"col-sm-9\">"
"<div class=\"form-control slider\" data-default=\"%g\" data-reset=\"false\""
" data-value=\"%g\" data-min=\"%g\" data-max=\"%g\" data-step=\"%g\" data-checked=\"%s\">"
"<div class=\"slider-control form-inline\">"
"<input class=\"slider-enable\" type=\"%s\" %s> "
"<input class=\"form-control slider-value\" %s type=\"number\" style=\"width:%dpx;\""
" id=\"input-%s\" name=\"%s\"> "
"%s%s%s"
"<input class=\"btn btn-default slider-down\" %s type=\"button\" value=\"\"> "
"<input class=\"btn btn-default slider-set\" %s type=\"button\" value=\"\" data-set=\"%g\"> "
"<input class=\"btn btn-default slider-up\" %s type=\"button\" value=\"\">"
"</div>"
"<input class=\"slider-input\" %s type=\"range\">"
"</div>"
"%s%s%s"
"</div>"
"</div>"
"<script>$('#input-%s').slider()</script>"
, _attr(name)
, label
, defval, value, min, max, step
, (enabled != 0) ? "true" : "false"
, (enabled < 0) ? "hidden" : "checkbox" // -1 => no checkbox
, (enabled > 0) ? "checked" : ""
, (enabled == 0) ? "disabled" : ""
, width
, _attr(name)
, _attr(name)
, unit ? "<span class=\"slider-unit\">" : ""
, unit ? unit : ""
, unit ? "</span> " : ""
, (enabled == 0) ? "disabled" : ""
, (enabled == 0) ? "disabled" : ""
, defval
, (enabled == 0) ? "disabled" : ""
, (enabled == 0) ? "disabled" : ""
, helptext ? "<span class=\"help-block\">" : ""
, helptext ? helptext : ""
, helptext ? "</span>" : ""
, _attr(name)
);
}
void PageContext::input_button(const char* btnclass, const char* label,
const char* name /*=NULL*/, const char* value /*=NULL*/) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<div class=\"col-sm-offset-3 col-sm-9\">"
"<button type=\"submit\" class=\"btn btn-%s\" %s%s%s %s%s%s>%s</button>"
"</div>"
"</div>"
, _attr(btnclass)
, name ? "name=\"" : "", name ? _attr(name) : "", name ? "\"" : ""
, value ? "value=\"" : "", value ? _attr(value) : "", value ? "\"" : ""
, label);
}
void PageContext::input_info(const char* label, const char* text) {
mg_printf_http_chunk(nc,
"<div class=\"form-group\">"
"<label class=\"control-label col-sm-3\">%s:</label>"
"<div class=\"col-sm-9\">"
"<div class=\"form-control-static\">%s</div>"
"</div>"
"</div>"
, label, text);
}
void PageContext::alert(const char* type, const char* text) {
mg_printf_http_chunk(nc,
"<div class=\"alert alert-%s\">%s</div>"
, _attr(type), text);
}
void PageContext::fieldset_start(const char* title, const char* css_class /*=NULL*/) {
mg_printf_http_chunk(nc,
"<fieldset class=\"%s\" id=\"fieldset-%s\"><legend>%s</legend>"
, css_class ? css_class : ""
, make_id(title).c_str()
, title);
}
void PageContext::fieldset_end() {
mg_printf_http_chunk(nc, "</fieldset>");
}
void PageContext::hr() {
mg_printf_http_chunk(nc, "<hr>");
}
/**
* CreateMenu:
*/
std::string OvmsWebServer::CreateMenu(PageContext_t& c)
{
std::string main, tools, config, vehicle;
// collect menu items:
for (PageEntry& e : MyWebServer.m_pagemap) {
if (e.menu == PageMenu_Main)
main += "<li><a href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Tools)
tools += "<li><a href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Config)
config += "<li><a href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Vehicle)
vehicle += "<li><a href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
}
// assemble menu:
std::string menu =
"<ul class=\"nav navbar-nav\">"
+ main;
menu +=
"<li class=\"dropdown\" id=\"menu-tools\">"
"<a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">Tools <span class=\"caret\"></span></a>"
"<ul class=\"dropdown-menu\">"
+ tools +
"</ul>"
"</li>";
if (vehicle != "") {
std::string vehiclename = MyVehicleFactory.ActiveVehicleShortName();
menu +=
"<li class=\"dropdown\" id=\"menu-vehicle\">"
"<a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">"
+ vehiclename +
" <span class=\"caret\"></span></a>"
"<ul class=\"dropdown-menu\">"
+ vehicle +
"</ul>"
"</li>";
}
menu +=
"<li class=\"dropdown\" id=\"menu-cfg\">"
"<a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">Config <span class=\"caret\"></span></a>"
"<ul class=\"dropdown-menu\">"
+ config +
"</ul>"
"</li>"
"</ul>"
"<ul class=\"nav navbar-nav navbar-right\">"
"<li class=\"hidden-xs\"><a href=\"#\" class=\"toggle-fullscreen\">◱</a></li>"
"<li class=\"hidden-xs\"><a href=\"#\" class=\"toggle-night\">◐</a></li>"
+ std::string(c.session
? "<li><a href=\"/logout\" target=\"#main\">Logout</a></li><script>loggedin=true</script>"
: "<li><a href=\"/login\" target=\"#main\">Login</a></li><script>loggedin=false</script>") +
"</ul>";
return menu;
}
/**
* HandleHome: show home / intro menu
*/
void OvmsWebServer::OutputHome(PageEntry_t& p, PageContext_t& c)
{
// collect menu items:
std::string main, config, tools, vehicle;
for (PageEntry& e : MyWebServer.m_pagemap) {
if (e.menu == PageMenu_Main)
main += "<li><a class=\"btn btn-default\" href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Tools)
tools += "<li><a class=\"btn btn-default\" href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Config)
config += "<li><a class=\"btn btn-default\" href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
else if (e.menu == PageMenu_Vehicle)
vehicle += "<li><a class=\"btn btn-default\" href=\"" + std::string(e.uri) + "\" target=\"#main\">" + std::string(e.label) + "</a></li>";
}
// show setup warning:
std::string init_step = MyConfig.GetParamValue("module", "init");
if (init_step.empty()) {
c.alert("danger",
"<p class=\"lead\">Initial module setup:</p>"
"<p>Your module is currently in <strong>insecure factory default</strong> configuration.</p>"
"<p><a class=\"btn btn-success\" href=\"/cfg/init\" target=\"#main\">Start setup now</a></p>");
}
// show password warning:
else if (MyConfig.GetParamValue("password", "module").empty()) {
c.alert("danger",
"<p><strong>Warning:</strong> no admin password set. <strong>Web access is open to the public!</strong></p>"
"<p><a class=\"btn btn-success\" href=\"/cfg/password\" target=\"#main\">Change password now</a></p>");
}
c.panel_start("primary", "Home");
c.printf(
"<fieldset class=\"menu\" id=\"fieldset-menu-main\"><legend>Main menu</legend>"
"<ul class=\"list-inline\">%s</ul>"
"</fieldset>"
, main.c_str());
c.printf(
"<fieldset class=\"menu\" id=\"fieldset-menu-tools\"><legend>Tools</legend>"
"<ul class=\"list-inline\">%s</ul>"
"</fieldset>"
, tools.c_str());
if (vehicle != "") {
const char* vehiclename = MyVehicleFactory.ActiveVehicleName();
mg_printf_http_chunk(c.nc,
"<fieldset class=\"menu\" id=\"fieldset-menu-vehicle\"><legend>%s</legend>"
"<ul class=\"list-inline\">%s</ul>"
"</fieldset>"
, vehiclename, vehicle.c_str());
}
c.printf(
"<fieldset class=\"menu\" id=\"fieldset-menu-config\"><legend>Configuration</legend>"
"<ul class=\"list-inline\">%s</ul>"
"</fieldset>"
, config.c_str());
c.panel_end();
// check auto init, show warning if disabled:
if (!MyConfig.GetParamValueBool("auto", "init", true)) {
c.alert("warning", "<p><strong>Warning:</strong> auto start disabled. Check auto start configuration.</p>");
}
}
void OvmsWebServer::HandleHome(PageEntry_t& p, PageContext_t& c)
{
// redirect to setup wizard?
std::string init_step = MyConfig.GetParamValue("module", "init");
if (!init_step.empty() && init_step != "done") {
c.head(200);
c.print("<script>loaduri('#main', 'get', '/cfg/init');</script>");
c.done();
return;
}
std::string moduleid = MyConfig.GetParamValue("vehicle", "id", "OVMS");
std::string info = "<p class=\"lead\">Welcome to the " + moduleid + " web console.</p>";
c.head(200);
c.alert("info", info.c_str());
PAGE_HOOK("body.pre");
OutputHome(p, c);
PAGE_HOOK("body.post");
c.done();
}
/**
* HandleRoot: output AJAX framework & main menu
*/
void OvmsWebServer::HandleRoot(PageEntry_t& p, PageContext_t& c)
{
std::string menu = CreateMenu(c);
std::string moduleid = MyConfig.GetParamValue("vehicle", "id", "OVMS");
// output page framework:
c.head(200);
PAGE_HOOK("html.pre");
c.printf(
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"utf-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<meta name=\"mobile-web-app-capable\" content=\"yes\">"
"<title data-moduleid=\"%s\">%s Console</title>"
"<link rel=\"stylesheet\" href=\"" URL_ASSETS_STYLE_CSS "\">"
"<link rel=\"shortcut icon\" sizes=\"192x192\" href=\"" URL_ASSETS_FAVICON_PNG "\">"
"<link rel=\"apple-touch-icon\" href=\"" URL_ASSETS_FAVICON_PNG "\">",
__attr(moduleid), __attr(moduleid));
PAGE_HOOK("head.post");
c.print(
"</head>"
"<body>"
"<nav id=\"nav\" class=\"navbar navbar-inverse navbar-fixed-top\">"
"<div class=\"container-fluid\">"
"<div class=\"navbar-header\">"
"<button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#menu\" aria-expanded=\"false\" aria-controls=\"menu\">"
"<span class=\"sr-only\">Toggle navigation</span>"
"<span class=\"icon-bar\"></span>"
"<span class=\"icon-bar\"></span>"
"<span class=\"icon-bar\"></span>"
"</button>"
"<button type=\"button\" class=\"navbar-toggle collapsed toggle-night\">◐</button>"
"<button type=\"button\" class=\"navbar-toggle collapsed toggle-fullscreen\">◱</button>"
"<a class=\"navbar-brand\" href=\"/home\" target=\"#main\" title=\"Home\"><img alt=\"OVMS\" src=\"" URL_ASSETS_FAVICON_PNG "\"></a>"
"</div>"
"<div role=\"menu\" id=\"menu\" class=\"navbar-collapse collapse\">");
c.print(menu);
c.print(
"</div>"
"</div>"
"</nav>"
"<div role=\"main\" id=\"main\" class=\"container-fluid\">"
"</div>"
"<script>window.assets={"
"\"charts_js\":\"" URL_ASSETS_CHARTS_JS "\","
"\"tables_js\":\"" URL_ASSETS_TABLES_JS "\""
"}</script>"
"<script src=\"" URL_ASSETS_SCRIPT_JS "\"></script>");
PAGE_HOOK("body.post");
c.print(
"</body>"
"</html>");
c.done();
}
/**
* HandleMenu: output main menu
*/
void OvmsWebServer::HandleMenu(PageEntry_t& p, PageContext_t& c)
{
std::string menu = CreateMenu(c);
c.head(200);
mg_send_http_chunk(c.nc, menu.data(), menu.length());
c.done();
}
/**
* OutputReboot: output reboot script
*/
void OvmsWebServer::OutputReboot(PageEntry_t& p, PageContext_t& c)
{
c.alert("warning",
"<p class=\"lead\">Rebooting now.</p>"
"<p>Please wait a moment until the window reloads.</p>"
"<p id=\"dots\">•</p>"
"<script>"
"after(0.1, function(){"
"var data = { \"command\": \"module reset\" };"
"$.ajax({ \"type\": \"post\", \"url\": \"/api/execute\", \"data\": data,"
"\"timeout\": 60000,"
"\"beforeSend\": function(){"
"$(\"html\").addClass(\"loading\");"
"ws_inhibit = 10;"
"ws.close();"
"window.setInterval(function(){ $(\"#dots\").append(\"\"); }, 1000);"
"},"
"\"complete\": function(){"
"location.reload();"
"},"
"});"
"});"
"</script>");
}
/**
* OutputReconnect: output reconnect script
*/
void OvmsWebServer::OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info /*=NULL*/,
const char* cmd /*=NULL*/)
{
c.printf(
"<div class=\"alert alert-warning\">"
"<p class=\"lead\">%s</p>"
"<p>The window will automatically reload when the browser reconnects to the module.</p>"
"<p id=\"dots\">•</p>"
"</div>"
"<script>"
"$(\"html\").addClass(\"loading\");"
"ws_inhibit = 10;"
"if (ws) ws.close();"
"window.setInterval(function(){"
"if (ws && ws.readyState == ws.OPEN){"
"location.reload();"
"} else {"
"$(\"#dots\").append(\"\");"
"}"
"}, 1000);"
, info ? info : "Reconnecting…");
if (cmd) {
c.printf(
"loadcmd(\"%s\");"
, __attr(cmd));
}
c.print(
"</script>");
}
/**
* HandleAsset: output gzip assets
* Note: no check for Accept-Encoding, we can't unzip & a modern browser is required anyway
*/
extern const uint8_t script_js_gz_start[] asm("_binary_script_js_gz_start");
extern const uint8_t script_js_gz_end[] asm("_binary_script_js_gz_end");
extern const uint8_t charts_js_gz_start[] asm("_binary_charts_js_gz_start");
extern const uint8_t charts_js_gz_end[] asm("_binary_charts_js_gz_end");
extern const uint8_t tables_js_gz_start[] asm("_binary_tables_js_gz_start");
extern const uint8_t tables_js_gz_end[] asm("_binary_tables_js_gz_end");
extern const uint8_t style_css_gz_start[] asm("_binary_style_css_gz_start");
extern const uint8_t style_css_gz_end[] asm("_binary_style_css_gz_end");
extern const uint8_t favicon_png_start[] asm("_binary_favicon_png_start");
extern const uint8_t favicon_png_end[] asm("_binary_favicon_png_end");
extern const uint8_t zones_json_gz_start[] asm("_binary_zones_json_gz_start");
extern const uint8_t zones_json_gz_end[] asm("_binary_zones_json_gz_end");
void OvmsWebServer::HandleAsset(PageEntry_t& p, PageContext_t& c)
{
const uint8_t* data = NULL;
size_t size;
time_t mtime;
const char* type;
bool gzip_encoded = true;
if (c.uri == "/assets/style.css") {
data = style_css_gz_start;
size = style_css_gz_end - style_css_gz_start;
mtime = MTIME_ASSETS_STYLE_CSS;
type = "text/css";
}
else if (c.uri == "/assets/script.js") {
data = script_js_gz_start;
size = script_js_gz_end - script_js_gz_start;
mtime = MTIME_ASSETS_SCRIPT_JS;
type = "application/javascript";
}
else if (c.uri == "/assets/charts.js") {
data = charts_js_gz_start;
size = charts_js_gz_end - charts_js_gz_start;
mtime = MTIME_ASSETS_CHARTS_JS;
type = "application/javascript";
}
else if (c.uri == "/assets/tables.js") {
data = tables_js_gz_start;
size = tables_js_gz_end - tables_js_gz_start;
mtime = MTIME_ASSETS_TABLES_JS;
type = "application/javascript";
}
else if (c.uri == "/assets/zones.json") {
data = zones_json_gz_start;
size = zones_json_gz_end - zones_json_gz_start;
mtime = MTIME_ASSETS_ZONES_JSON;
type = "application/json";
}
else if (c.uri == "/favicon.ico" || c.uri == "/apple-touch-icon.png") {
data = favicon_png_start;
size = favicon_png_end - favicon_png_start;
mtime = MTIME_ASSETS_FAVICON_PNG;
type = "image/png";
gzip_encoded = false;
}
else {
mg_http_send_error(c.nc, 404, "Not found");
return;
}
char etag[50], current_time[50], last_modified[50];
time_t t = (time_t) mg_time();
snprintf(etag, sizeof(etag), "\"%lx.%" INT64_FMT "\"", (unsigned long) mtime, (int64_t) size);
strftime(current_time, sizeof(current_time), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime));
mg_send_response_line(c.nc, 200, NULL);
mg_printf(c.nc,
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Content-Type: %s\r\n"
"%s"
"Transfer-Encoding: chunked\r\n"
"Etag: %s\r\n"
"\r\n"
, current_time
, last_modified
, type
, gzip_encoded ? "Content-Encoding: gzip\r\n" : ""
, etag);
// start chunked transfer:
new HttpDataSender(c.nc, data, size);
}