diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index f09289fc..8aea3ae7 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -35,6 +35,14 @@ const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; +HTTPConnection::HTTPConnection(boost::asio::ip::tcp::socket* socket, + std::shared_ptr session) + : m_Socket(socket), m_Timer(socket->get_io_service()), + m_BufferLen(0), m_Session(session) +{ + +} + void HTTPConnection::Terminate() { m_Socket->close(); @@ -62,8 +70,18 @@ void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size void HTTPConnection::RunRequest() { - m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); - HandleRequest(); + try { + m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); + if(m_Request.getMethod() == "GET") + return HandleRequest(); + if(m_Request.getHeader("Content-Type").find("application/json") != std::string::npos) + return HandleI2PControlRequest(); + } catch(...) { + // Ignore the error for now, probably Content-Type doesn't exist + } + // Unsupported method + m_Reply = i2p::util::http::Response(502, ""); + SendReply(); } void HTTPConnection::ExtractParams(const std::string& str, std::map& params) @@ -119,6 +137,23 @@ void HTTPConnection::HandleRequest() ifs.close(); m_Reply = i2p::util::http::Response(200, str); + + // TODO: get rid of this hack, actually determine the MIME type + if(address.substr(address.find_last_of(".")) == ".css") + m_Reply.setHeader("Content-Type", "text/css"); + else if(address.substr(address.find_last_of(".")) == ".js") + m_Reply.setHeader("Content-Type", "text/javascript"); + else + m_Reply.setHeader("Content-Type", "text/html"); + SendReply(); +} + +void HTTPConnection::HandleI2PControlRequest() +{ + std::stringstream ss(m_Request.getContent()); + const client::I2PControlSession::Response rsp = m_Session->handleRequest(ss); + m_Reply = i2p::util::http::Response(200, rsp.toJsonString()); + m_Reply.setHeader("Content-Type", "application/json"); SendReply(); } @@ -141,7 +176,6 @@ void HTTPConnection::SendReply() if(std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) { m_Reply.setHeader("Date", std::string(time_buff)); m_Reply.setContentLength(); - m_Reply.setHeader("Content-Type", "text/html"); } boost::asio::async_write( *m_Socket, boost::asio::buffer(m_Reply.toString()), @@ -153,7 +187,9 @@ HTTPServer::HTTPServer(const std::string& address, int port): m_Thread(nullptr), m_Work(m_Service), m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint( boost::asio::ip::address::from_string(address), port) - ), m_NewSocket(nullptr) + ), + m_NewSocket(nullptr), + m_Session(std::make_shared(m_Service)) { } @@ -167,11 +203,13 @@ void HTTPServer::Start() { m_Thread = new std::thread(std::bind(&HTTPServer::Run, this)); m_Acceptor.listen(); + m_Session->start(); Accept(); } void HTTPServer::Stop() { + m_Session->stop(); m_Acceptor.close(); m_Service.stop(); if(m_Thread) @@ -196,18 +234,17 @@ void HTTPServer::Accept() void HTTPServer::HandleAccept(const boost::system::error_code& ecode) { - if(!ecode) - { + if(!ecode) { CreateConnection(m_NewSocket); Accept(); } } -void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) +void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket) { - auto conn = std::make_shared(m_NewSocket); + auto conn = std::make_shared(m_NewSocket, m_Session); conn->Receive(); } -} -} +} +} diff --git a/client/HTTPServer.h b/client/HTTPServer.h index 5bf253f2..9cc149fe 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -6,6 +6,7 @@ #include #include #include +#include "i2pcontrol/I2PControl.h" #include "util/HTTP.h" namespace i2p { @@ -17,9 +18,9 @@ const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds class HTTPConnection: public std::enable_shared_from_this { public: - HTTPConnection(boost::asio::ip::tcp::socket * socket) - : m_Socket(socket), m_Timer(socket->get_io_service()), - m_BufferLen(0) {}; + HTTPConnection(boost::asio::ip::tcp::socket* socket, + std::shared_ptr session); + ~HTTPConnection() { delete m_Socket; } void Receive(); @@ -32,6 +33,7 @@ private: void SendReply(); void HandleRequest(); + void HandleI2PControlRequest(); void ExtractParams(const std::string& str, std::map& params); bool isAllowed(const std::string& address); @@ -43,6 +45,7 @@ private: size_t m_BufferLen; util::http::Request m_Request; util::http::Response m_Reply; + std::shared_ptr m_Session; }; class HTTPServer { @@ -67,9 +70,10 @@ private: boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::tcp::socket * m_NewSocket; + std::shared_ptr m_Session; protected: - virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); + void CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket); }; } } diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 28c5236d..1f94c6c5 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -26,8 +26,13 @@ Request::Request(const std::string& data) std::getline(ss, line); parseRequestLine(line); - while(std::getline(ss, line)) + while(std::getline(ss, line) && !boost::trim_copy(line).empty()) parseHeaderLine(line); + + if(ss) { + const std::string current = ss.str(); + content = current.substr(ss.tellg()); + } } std::string Request::getMethod() const @@ -55,6 +60,11 @@ std::string Request::getHeader(const std::string& name) const return headers.at(name); } +std::string Request::getContent() const +{ + return content; +} + Response::Response(int status, const std::string& content) : status(status), content(content), headers() { diff --git a/core/util/HTTP.h b/core/util/HTTP.h index b587dd30..a559938d 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -31,10 +31,13 @@ public: */ std::string getHeader(const std::string& name) const; + std::string getContent() const; + private: std::string method; std::string uri; std::string host; + std::string content; int port; std::map headers; }; diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 07d77c53..795fa517 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -113,6 +113,21 @@ BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithHeaders) BOOST_CHECK_EQUAL(req2.getHeader("Host"), "localhost:123"); } +BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithContent) +{ + Request req1( + "GET /index.html HTTP/1.1\r\n" + "Host: localhost\r\n\r\n" + "Random content." + ); + Request req2( + "GET /index.html HTTP/1.0\r\n\r\n" + "Random content.\r\nTest content." + ); + BOOST_CHECK_EQUAL(req1.getContent(), "Random content."); + BOOST_CHECK_EQUAL(req2.getContent(), "Random content.\r\nTest content."); +} + BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage) { BOOST_CHECK_EQUAL(Response(0).getStatusMessage(), ""); diff --git a/webui/css/main.css b/webui/css/main.css new file mode 100644 index 00000000..b6f47251 --- /dev/null +++ b/webui/css/main.css @@ -0,0 +1,257 @@ +/*! +Pure v0.6.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yahoo/pure/blob/master/LICENSE.md +*/ +/*! +normalize.css v^3.0 | MIT License | git.io/normalize +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}body { + color: #777; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* +Add transition to containers so they can push in and out. +*/ +#layout, +#menu, +.menu-link { + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* +This is the parent `
` that contains the menu and the content area. +*/ +#layout { + position: relative; + padding-left: 0; +} + #layout.active #menu { + left: 150px; + width: 150px; + } + + #layout.active .menu-link { + left: 150px; + } +/* +The content `
` is where all your content goes. +*/ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; + } + .header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; + } + .header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; + } + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + + + +/* +The `#menu` `
` is the parent `
` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; /* "#menu" width */ + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + /* + All anchors inside the menu should be styled like this. + */ + #menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; + } + + /* + Remove all background/borders, since we are applying them to #menu. + */ + #menu .pure-menu, + #menu .pure-menu ul { + border: none; + background: transparent; + } + + /* + Add that light border to separate items into groups. + */ + #menu .pure-menu ul, + #menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; + } + /* + Change color of the anchor links on hover/focus. + */ + #menu .pure-menu li a:hover, + #menu .pure-menu li a:focus { + background: #333; + } + + /* + This styles the selected menu item `
  • `. + */ + #menu .pure-menu-selected, + #menu .pure-menu-heading { + background: #1f8dd6; + } + /* + This styles a link within a selected menu item `
  • `. + */ + #menu .pure-menu-selected a { + color: #fff; + } + + /* + This styles the menu heading. + */ + #menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + } + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ +.menu-link { + position: fixed; + display: block; /* show this only on small screens */ + top: 0; + left: 0; /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + + .menu-link:hover, + .menu-link:focus { + background: #000; + } + + .menu-link span { + position: relative; + display: block; + } + + .menu-link span, + .menu-link span:before, + .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; + } + + .menu-link span:before, + .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; + } + + .menu-link span:after { + margin-top: 0.6em; + } + + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ +@media (min-width: 48em) { + + .header, + .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 48em) { + /* Only apply this when the window is small. Otherwise, the following + case results in extra padding on the left: + * Make the window small. + * Tap the menu to trigger the active state. + * Make the window large again. + */ + #layout.active { + position: relative; + left: 150px; + } +} diff --git a/webui/index.html b/webui/index.html index 8d82aeb0..8ddbdda3 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4,8 +4,94 @@ Purple I2P 0.10.0 Webconsole + + + + -

    Nothing here yet.

    +
    +

    i2pd router console

    +

    Version: , uptime:

    +

    Network status:

    +

    + + + +

    +
    + +
    +

    Tunnels participating:

    +

    Active peers:

    +

    Known peers:

    +

    Bandwidth: + in Bps / + out Bps +

    +
    + +
    +

    I2P configuration

    +
    + +
    +

    Not yet implemented :)

    +
    + + +
    + + + + + + +
    + +
    +
    diff --git a/webui/javascript/I2PControl.js b/webui/javascript/I2PControl.js new file mode 100644 index 00000000..6473ad3b --- /dev/null +++ b/webui/javascript/I2PControl.js @@ -0,0 +1,84 @@ +var I2PControl = I2PControl || {} + +I2PControl.Session = function(password) { + this.token = ""; + this.ready = false; + this.error = false; + this.password = password; +}; + +I2PControl.Session.prototype = { + + request : function(method, params, handler) { + var request = new XMLHttpRequest(); + request.open("POST", "", true); + request.setRequestHeader('Content-Type', 'application/json'); + var self = this; + request.onreadystatechange = function() { + if(this.readyState == 4 && this.status == "200" && this.responseText != "") { + var result = JSON.parse(this.responseText).result; + if(result.hasOwnProperty("error")) { + self.error = true; + } + handler(result, self); + } + }; + if(this.token != "") + params["Token"] = this.token; + + var rpc = { + "id" : 0, + "method" : method , + "params" : params, + "jsonrpc": "2.0" + } + request.send(JSON.stringify(rpc)); + }, + + start : function(onReady) { + var self = this; + + var handleAuthenticate = function(result) { + self.token = result["Token"]; + self.ready = true; + onReady(); + }; + + this.request( + "Authenticate", + {"API" : 1, "Password" : this.password}, + handleAuthenticate + ); + }, + +}; + +I2PControl.statusToString = function(status) { + switch(status) { + case 0: return "OK"; + case 1: return "TESTING"; + case 2: return "FIREWALLED"; + case 3: return "HIDDEN"; + case 4: return "WARN_FIREWALLED_AND_FAST"; + case 5: return "WARN_FIREWALLED_AND_FLOODFILL"; + case 6: return "WARN_FIREWALLED_WITH_INBOUND_TCP"; + case 7: return "WARN_FIREWALLED_WITH_UDP_DISABLED"; + case 8: return "ERROR_I2CP"; + case 9: return "ERROR_CLOCK_SKEW"; + case 10: return "ERROR_PRIVATE_TCP_ADDRESS"; + case 11: return "ERROR_SYMMETRIC_NAT"; + case 12: return "ERROR_UDP_PORT_IN_USE"; + case 13: return "ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL"; + case 14: return "ERROR_UDP_DISABLED_AND_TCP_UNSET"; + default: return "UNKNOWN"; + } +}; + +I2PControl.updateDocument = function(values) { + + for(id in values) { + if(!values.hasOwnProperty(id)) + continue; + document.getElementById(id).innerHTML = values[id]; + } +};