mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-01-12 18:38:07 +00:00
Added basic webui (layout from l-n-s).
This commit is contained in:
parent
e7350a3af4
commit
719bfbc89b
@ -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<i2p::client::I2PControlSession> 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<std::string, std::string>& 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<i2p::client::I2PControlSession>(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<HTTPConnection>(m_NewSocket);
|
||||
auto conn = std::make_shared<HTTPConnection>(m_NewSocket, m_Session);
|
||||
conn->Receive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/array.hpp>
|
||||
#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<HTTPConnection> {
|
||||
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<i2p::client::I2PControlSession> 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<std::string, std::string>& 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<i2p::client::I2PControlSession> 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<i2p::client::I2PControlSession> m_Session;
|
||||
|
||||
protected:
|
||||
virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket);
|
||||
void CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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<std::string, std::string> headers;
|
||||
};
|
||||
|
@ -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(), "");
|
||||
|
257
webui/css/main.css
Normal file
257
webui/css/main.css
Normal file
File diff suppressed because one or more lines are too long
@ -4,8 +4,94 @@
|
||||
<title>Purple I2P 0.10.0 Webconsole</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<script type="text/javascript" src="javascript/I2PControl.js"></script>
|
||||
<script type="text/javascript">
|
||||
function updateRouterInfo(result, session) {
|
||||
if(session.error)
|
||||
alert("Error: " + result["error"]);
|
||||
I2PControl.updateDocument({
|
||||
"version" : result["i2p.router.version"],
|
||||
"status" : I2PControl.statusToString(result["i2p.router.net.status"]),
|
||||
});
|
||||
|
||||
}
|
||||
window.onload = function() {
|
||||
var session = new I2PControl.Session("itoopie");
|
||||
session.start(function() {
|
||||
session.request("RouterInfo", {
|
||||
"i2p.router.version" : "", "i2p.router.net.status" : ""
|
||||
}, updateRouterInfo);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Nothing here yet.</p>
|
||||
<div class="header">
|
||||
<h1>i2pd router console</h1>
|
||||
<h2>Version: <span id="version"></span>, uptime: <span id="uptime"></span></h2>
|
||||
<h2>Network status: <span id="status"></span></h2>
|
||||
<p>
|
||||
<button id="shutdown">shutdown</button>
|
||||
<button id="restart" disabled>restart</button>
|
||||
<button id="reseed">reseed</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2 class="content-subhead">Tunnels participating: <span id="tunnels-participating"></span></h2>
|
||||
<h2 class="content-subhead">Active peers: <span id="activepeers"></span></h2>
|
||||
<h2 class="content-subhead">Known peers: <span id="knownpeers"></span></h2>
|
||||
<h2 class="content-subhead">Bandwidth:
|
||||
in <span id="bw-in"></span> Bps /
|
||||
out <span id="bw-out"></span> Bps
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1>I2P configuration</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2 class="content-subhead">Not yet implemented :)</h2>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="help">
|
||||
<div class="header">
|
||||
<h1>I2P help</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2 class="content-subhead">Need help? Join us at IRC: #i2pd-dev at irc.freenode.net</h2>
|
||||
<h2 class="content-subhead">
|
||||
<a href="https://github.com/PurpleI2P/i2pd">i2pd at GitHub</a>
|
||||
</h2>
|
||||
<h2 class="content-subhead"><a href="https://geti2p.net/en/">I2P Project</a> </h2>
|
||||
</div>
|
||||
</script>
|
||||
<div id="layout">
|
||||
<a href="#menu" id="menuLink" class="menu-link">
|
||||
<span></span>
|
||||
</a>
|
||||
|
||||
<div id="menu">
|
||||
<div class="pure-menu">
|
||||
<span class="pure-menu-heading">i2pd</span>
|
||||
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item"><a href="#/" class="pure-menu-link">Home</a></li>
|
||||
<li class="pure-menu-item"><a href="#/config" class="pure-menu-link">Configure</a></li>
|
||||
<li class="pure-menu-item"><a href="#/help" class="pure-menu-link">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main">
|
||||
<noscript>
|
||||
<div class="header"><h1>Please, enable JavaScript!</h1></div>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
84
webui/javascript/I2PControl.js
Normal file
84
webui/javascript/I2PControl.js
Normal file
@ -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];
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user