mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-27 11:17:49 +02:00
[webconsole] add external CSS support (#1682)
Signed-off-by: R4SAS <r4sas@i2pmail.org>
This commit is contained in:
parent
37f1a55147
commit
28a055bd78
9 changed files with 689 additions and 417 deletions
|
@ -59,55 +59,75 @@ namespace http {
|
|||
"JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ"
|
||||
"RU5ErkJggg==";
|
||||
|
||||
// Bundled style
|
||||
const std::string internalCSS =
|
||||
"<style>\r\n"
|
||||
" body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
|
||||
" a, .slide label { text-decoration: none; color: #894C84; }\r\n"
|
||||
" a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n"
|
||||
" a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n"
|
||||
" color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n"
|
||||
" .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n"
|
||||
" .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n"
|
||||
" .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; }\r\n"
|
||||
" .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n"
|
||||
" .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n"
|
||||
" .content { float: left; font-size: 1em; margin-left: 4em; max-width: 48em; overflow: auto; }\r\n"
|
||||
" .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n"
|
||||
" .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n"
|
||||
" caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n"
|
||||
" table { display: table; border-collapse: collapse; text-align: center; }\r\n"
|
||||
" table.extaddr { text-align: left; } table.services { width: 100%; }\r\n"
|
||||
" textarea { word-break: break-all; }\r\n"
|
||||
" .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n"
|
||||
" .slide div.slidecontent, .slide [type=\"checkbox\"] { display: none; }\r\n"
|
||||
" .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n"
|
||||
" .disabled { color: #D33F3F; } .enabled { color: #56B734; }\r\n"
|
||||
" @media screen and (max-width: 1150px) {\r\n" /* adaptive style */
|
||||
" .wrapper { max-width: 58em; } .menu { max-width: 10em; }\r\n"
|
||||
" .content { margin-left: 2em; max-width: 42em; }\r\n"
|
||||
" }\r\n"
|
||||
" @media screen and (max-width: 980px) {\r\n"
|
||||
" body { padding: 1.5em 0 0 0; }\r\n"
|
||||
" .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px;\r\n"
|
||||
" text-align: center; }\r\n"
|
||||
" .menu a, .commands a { display: inline-block; padding: 4px; }\r\n"
|
||||
" .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%;\r\n"
|
||||
" text-align: center; }\r\n"
|
||||
" a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n"
|
||||
" .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n"
|
||||
" a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n"
|
||||
" color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n"
|
||||
" input, select { width: 35%; text-align: center; padding: 5px;\r\n"
|
||||
" border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n"
|
||||
" table.extaddr { margin: auto; text-align: unset; }\r\n"
|
||||
" textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n"
|
||||
" -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n"
|
||||
" button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n"
|
||||
" -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 36px; display: -webkit-inline-box; margin-top: 10px; }\r\n"
|
||||
" }\r\n"
|
||||
"</style>\r\n";
|
||||
|
||||
// for external style sheet
|
||||
std::string externalCSS;
|
||||
|
||||
static void LoadExtCSS ()
|
||||
{
|
||||
std::stringstream s;
|
||||
std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css");
|
||||
if (i2p::fs::Exists(styleFile)) {
|
||||
std::ifstream f(styleFile, std::ifstream::binary);
|
||||
s << f.rdbuf();
|
||||
externalCSS = s.str();
|
||||
}
|
||||
}
|
||||
|
||||
static void GetStyles (std::stringstream& s)
|
||||
{
|
||||
s << "<style>\r\n"
|
||||
<< " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
|
||||
<< " a, .slide label { text-decoration: none; color: #894C84; }\r\n"
|
||||
<< " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n"
|
||||
<< " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n"
|
||||
<< " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n"
|
||||
<< " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n"
|
||||
<< " .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n"
|
||||
<< " .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; }\r\n"
|
||||
<< " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n"
|
||||
<< " .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n"
|
||||
<< " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 48em; overflow: auto; }\r\n"
|
||||
<< " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n"
|
||||
<< " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n"
|
||||
<< " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n"
|
||||
<< " table { display: table; border-collapse: collapse; text-align: center; }\r\n"
|
||||
<< " table.extaddr { text-align: left; } table.services { width: 100%; }\r\n"
|
||||
<< " textarea { word-break: break-all; }\r\n"
|
||||
<< " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n"
|
||||
<< " .slide div.slidecontent, .slide [type=\"checkbox\"] { display: none; }\r\n"
|
||||
<< " .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n"
|
||||
<< " .disabled:after { color: #D33F3F; content: \"" << tr("Disabled") << "\" }\r\n"
|
||||
<< " .enabled:after { color: #56B734; content: \"" << tr("Enabled") << "\" }\r\n"
|
||||
<< " @media screen and (max-width: 1150px) {\r\n" /* adaptive style */
|
||||
<< " .wrapper { max-width: 58em; } .menu { max-width: 10em; }\r\n"
|
||||
<< " .content { margin-left: 2em; max-width: 42em; }\r\n"
|
||||
<< " }\r\n"
|
||||
<< " @media screen and (max-width: 980px) {\r\n"
|
||||
<< " body { padding: 1.5em 0 0 0; }\r\n"
|
||||
<< " .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px;\r\n"
|
||||
<< " text-align: center; }\r\n"
|
||||
<< " .menu a, .commands a { display: inline-block; padding: 4px; }\r\n"
|
||||
<< " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%;\r\n"
|
||||
<< " text-align: center; }\r\n"
|
||||
<< " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n"
|
||||
<< " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n"
|
||||
<< " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n"
|
||||
<< " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n"
|
||||
<< " input, select { width: 35%; text-align: center; padding: 5px;\r\n"
|
||||
<< " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n"
|
||||
<< " table.extaddr { margin: auto; text-align: unset; }\r\n"
|
||||
<< " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n"
|
||||
<< " -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n"
|
||||
<< " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n"
|
||||
<< " -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 36px; display: -webkit-inline-box; margin-top: 10px; }\r\n"
|
||||
<< " }\r\n" /* adaptive style */
|
||||
<< "</style>\r\n";
|
||||
if (externalCSS.length() != 0)
|
||||
s << "<style>\r\n" << externalCSS << "</style>\r\n";
|
||||
else
|
||||
s << internalCSS;
|
||||
}
|
||||
|
||||
const char HTTP_PAGE_TUNNELS[] = "tunnels";
|
||||
|
@ -133,11 +153,19 @@ namespace http {
|
|||
const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit";
|
||||
const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string";
|
||||
const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage";
|
||||
const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css";
|
||||
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
|
||||
const char HTTP_PARAM_ADDRESS[] = "address";
|
||||
|
||||
static std::string ConvertTime (uint64_t time);
|
||||
std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens;
|
||||
static std::string ConvertTime (uint64_t time)
|
||||
{
|
||||
lldiv_t divTime = lldiv(time, 1000);
|
||||
time_t t = divTime.quot;
|
||||
struct tm *tm = localtime(&t);
|
||||
char date[128];
|
||||
snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem);
|
||||
return date;
|
||||
}
|
||||
|
||||
static void ShowUptime (std::stringstream& s, int seconds)
|
||||
{
|
||||
|
@ -210,9 +238,9 @@ namespace http {
|
|||
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
|
||||
|
||||
// Page language
|
||||
std::string lang, langCode; i2p::config::GetOption("http.lang", lang);
|
||||
if (lang == "russian") langCode = "ru";
|
||||
else langCode = "en";
|
||||
std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language
|
||||
auto it = i2p::i18n::languages.find(currLang);
|
||||
std::string langCode = it->second.ShortCode;
|
||||
|
||||
s <<
|
||||
"<!DOCTYPE html>\r\n"
|
||||
|
@ -395,14 +423,19 @@ namespace http {
|
|||
s << "<b>" << tr("Transit Tunnels") << ":</b> " << std::to_string(transitTunnelCount) << "<br>\r\n<br>\r\n";
|
||||
|
||||
if(outputFormat==OutputFormatEnum::forWebConsole) {
|
||||
bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
|
||||
bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false;
|
||||
bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false;
|
||||
bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false;
|
||||
bool sam = i2p::client::context.GetSAMBridge () ? true : false;
|
||||
bool i2cp = i2p::client::context.GetI2CPServer () ? true : false;
|
||||
bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
|
||||
s << "<table class=\"services\"><caption>" << tr("Services") << "</caption><tbody>\r\n";
|
||||
s << "<tr><td>" << "HTTP " << tr("Proxy") << "</td><td><div class='" << ((i2p::client::context.GetHttpProxy ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "SOCKS " << tr("Proxy") << "</td><td><div class='" << ((i2p::client::context.GetSocksProxy ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "BOB" << "</td><td><div class='" << ((i2p::client::context.GetBOBCommandChannel ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "SAM" << "</td><td><div class='" << ((i2p::client::context.GetSAMBridge ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "I2CP" << "</td><td><div class='" << ((i2p::client::context.GetI2CPServer ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "I2PControl" << "</td><td><div class='" << ((i2pcontrol) ? "enabled" : "disabled") << "'></div></td></tr>\r\n";
|
||||
s << "<tr><td>" << "HTTP " << tr("Proxy") << "</td><td class='" << (httpproxy ? "enabled" : "disabled") << "'>" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "<tr><td>" << "SOCKS " << tr("Proxy") << "</td><td class='" << (socksproxy ? "enabled" : "disabled") << "'>" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "<tr><td>" << "BOB" << "</td><td class='" << (bob ? "enabled" : "disabled") << "'>" << (bob ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "<tr><td>" << "SAM" << "</td><td class='" << (sam ? "enabled" : "disabled") << "'>" << (sam ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "<tr><td>" << "I2CP" << "</td><td class='" << (i2cp ? "enabled" : "disabled") << "'>" << (i2cp ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "<tr><td>" << "I2PControl" << "</td><td class='" << (i2pcontrol ? "enabled" : "disabled") << "'>" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
|
||||
s << "</tbody></table>\r\n";
|
||||
}
|
||||
}
|
||||
|
@ -709,7 +742,8 @@ namespace http {
|
|||
s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n";
|
||||
#endif
|
||||
|
||||
s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">" << tr("Force shutdown") << "</a>\r\n";
|
||||
s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">" << tr("Force shutdown") << "</a><br><br>\r\n";
|
||||
s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_CSS << "&token=" << token << "\">" << tr("Reload external CSS styles") << "</a>\r\n";
|
||||
s << "</div>";
|
||||
|
||||
s << "<br>\r\n<small>" << tr("<b>Note:</b> any action done here are not persistent and not changes your config files.") << "</small>\r\n<br>\r\n";
|
||||
|
@ -1003,16 +1037,6 @@ namespace http {
|
|||
}
|
||||
}
|
||||
|
||||
std::string ConvertTime (uint64_t time)
|
||||
{
|
||||
lldiv_t divTime = lldiv(time, 1000);
|
||||
time_t t = divTime.quot;
|
||||
struct tm *tm = localtime(&t);
|
||||
char date[128];
|
||||
snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem);
|
||||
return date;
|
||||
}
|
||||
|
||||
HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
|
||||
m_Socket (socket), m_BufferLen (0), expected_host(hostname)
|
||||
{
|
||||
|
@ -1139,6 +1163,8 @@ namespace http {
|
|||
SendReply (res, content);
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens;
|
||||
|
||||
uint32_t HTTPConnection::CreateToken ()
|
||||
{
|
||||
uint32_t token;
|
||||
|
@ -1359,6 +1385,10 @@ namespace http {
|
|||
if (currLang.compare(lang) != 0)
|
||||
i2p::i18n::SetLanguage(lang);
|
||||
}
|
||||
else if (cmd == HTTP_COMMAND_RELOAD_CSS)
|
||||
{
|
||||
LoadExtCSS();
|
||||
}
|
||||
else
|
||||
{
|
||||
res.code = 400;
|
||||
|
@ -1421,6 +1451,8 @@ namespace http {
|
|||
m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this)));
|
||||
m_Acceptor.listen ();
|
||||
Accept ();
|
||||
|
||||
LoadExtCSS();
|
||||
}
|
||||
|
||||
void HTTPServer::Stop ()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue