diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml new file mode 100644 index 0000000000..56e97e42e6 --- /dev/null +++ b/apps/routerconsole/java/build.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigAdvancedHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigAdvancedHelper.java new file mode 100644 index 0000000000..c901948604 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigAdvancedHelper.java @@ -0,0 +1,38 @@ +package net.i2p.router.web; + +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; + +import net.i2p.router.RouterContext; + +public class ConfigAdvancedHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ConfigAdvancedHelper() {} + + public String getSettings() { + StringBuffer buf = new StringBuffer(4*1024); + Set names = _context.router().getConfigSettings(); + TreeSet sortedNames = new TreeSet(names); + for (Iterator iter = sortedNames.iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String val = _context.router().getConfigSetting(name); + buf.append(name).append('=').append(val).append('\n'); + } + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java new file mode 100644 index 0000000000..5e2dcabdf9 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -0,0 +1,117 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Iterator; +import java.util.TreeMap; + +import net.i2p.util.Log; + +import net.i2p.router.RouterContext; +import net.i2p.router.ClientTunnelSettings; + +public class ConfigClientsHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** copied from the package private {@link net.i2p.router.tunnelmanager.TunnelPool} */ + public final static String TARGET_CLIENTS_PARAM = "router.targetClients"; + /** copied from the package private {@link net.i2p.router.tunnelmanager.TunnelPool} */ + public final static int TARGET_CLIENTS_DEFAULT = 3; + + public ConfigClientsHelper() {} + + public String getClientCountSelectBox() { + int count = TARGET_CLIENTS_DEFAULT; + String val = _context.router().getConfigSetting(TARGET_CLIENTS_PARAM); + if (val != null) { + try { + count = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + // ignore, use default from above + } + } + StringBuffer buf = new StringBuffer(1024); + buf.append("\n"); + return buf.toString(); + } + + public String getTunnelCountSelectBox() { + int count = ClientTunnelSettings.DEFAULT_NUM_INBOUND; + String val = _context.router().getConfigSetting(ClientTunnelSettings.PROP_NUM_INBOUND); + if (val != null) { + try { + count = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + // ignore, use default from above + } + } + StringBuffer buf = new StringBuffer(1024); + buf.append("\n"); + return buf.toString(); + } + + public String getTunnelDepthSelectBox() { + int count = ClientTunnelSettings.DEFAULT_DEPTH_INBOUND; + String val = _context.router().getConfigSetting(ClientTunnelSettings.PROP_DEPTH_INBOUND); + if (val != null) { + try { + count = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + // ignore, use default from above + } + } + StringBuffer buf = new StringBuffer(1024); + buf.append("\n"); + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java new file mode 100644 index 0000000000..9230d2166e --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java @@ -0,0 +1,113 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Iterator; +import java.util.TreeMap; + +import net.i2p.util.Log; + +import net.i2p.router.RouterContext; + +public class ConfigLoggingHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ConfigLoggingHelper() {} + + public String getLogFilePattern() { + return _context.logManager().getBaseLogfilename(); + } + public String getRecordPattern() { + return new String(_context.logManager().getFormat()); + } + public String getDatePattern() { + return _context.logManager().getDateFormatPattern(); + } + public String getMaxFileSize() { + int bytes = _context.logManager().getFileSize(); + if (bytes == 0) return "1m"; + if (bytes > 1024*1024*1024) + return (bytes/(1024*1024*1024)) + "g"; + else if (bytes > 1024*1024) + return (bytes/(1024*1024)) + "m"; + else + return (bytes/(1024)) + "k"; + } + public String getLogLevelTable() { + StringBuffer buf = new StringBuffer(32*1024); + buf.append("
\n"); + buf.append("Valid levels are DEBUG, INFO, WARN, ERROR, CRIT\n"); + return buf.toString(); + } + public String getLogLevelTableDetail() { + StringBuffer buf = new StringBuffer(8*1024); + buf.append("\n"); + buf.append("\n"); + List logs = _context.logManager().getLogs(); + TreeMap sortedLogs = new TreeMap(); + for (int i = 0; i < logs.size(); i++) { + Log l = (Log)logs.get(i); + sortedLogs.put(l.getName(), l); + } + int i = 0; + for (Iterator iter = sortedLogs.values().iterator(); iter.hasNext(); i++) { + Log l = (Log)iter.next(); + buf.append("\n \n"); + buf.append("\n\n"); + } + buf.append("
Package/classLevel
\n"); + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java new file mode 100644 index 0000000000..9054cbaa99 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -0,0 +1,135 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Iterator; +import java.util.TreeMap; + +import net.i2p.util.Log; + +import net.i2p.router.RouterContext; +import net.i2p.router.ClientTunnelSettings; + +public class ConfigNetHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ConfigNetHelper() {} + + /** copied from various private TCP components */ + private final static String PROP_I2NP_TCP_HOSTNAME = "i2np.tcp.hostname"; + private final static String PROP_I2NP_TCP_PORT = "i2np.tcp.port"; + + public String getHostname() { + return _context.getProperty(PROP_I2NP_TCP_HOSTNAME); + } + public String getPort() { + int port = 8887; + String val = _context.getProperty(PROP_I2NP_TCP_PORT); + if (val != null) { + try { + port = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + // ignore, use default from above + } + } + return "" + port; + } + + public String getEnableTimeSyncChecked() { + String enabled = System.getProperty("timestamper.enabled"); + if ( (enabled == null) || (!"true".equals(enabled)) ) + return ""; + else + return " checked "; + } + + public static final String PROP_INBOUND_KBPS = "i2np.bandwidth.inboundKBytesPerSecond"; + public static final String PROP_OUTBOUND_KBPS = "i2np.bandwidth.outboundKBytesPerSecond"; + public static final String PROP_INBOUND_BURST = "i2np.bandwidth.inboundBurstKBytes"; + public static final String PROP_OUTBOUND_BURST = "i2np.bandwidth.outboundBurstKBytes"; + + public String getInboundRate() { + String rate = _context.getProperty(PROP_INBOUND_KBPS); + if (rate != null) + return rate; + else + return "-1"; + } + public String getOutboundRate() { + String rate = _context.getProperty(PROP_OUTBOUND_KBPS); + if (rate != null) + return rate; + else + return "Unlimited"; + } + public String getInboundBurstFactorBox() { + String rate = _context.getProperty(PROP_INBOUND_KBPS); + String burst = _context.getProperty(PROP_INBOUND_BURST); + int numSeconds = 1; + if ( (burst != null) && (rate != null) ) { + int rateKBps = 0; + int burstKB = 0; + try { + rateKBps = Integer.parseInt(rate); + burstKB = Integer.parseInt(burst); + } catch (NumberFormatException nfe) { + // ignore + } + if ( (rateKBps > 0) && (burstKB > 0) ) { + numSeconds = burstKB / rateKBps; + } + } + return getBurstFactor(numSeconds, "inboundburstfactor"); + } + + public String getOutboundBurstFactorBox() { + String rate = _context.getProperty(PROP_OUTBOUND_KBPS); + String burst = _context.getProperty(PROP_OUTBOUND_BURST); + int numSeconds = 1; + if ( (burst != null) && (rate != null) ) { + int rateKBps = 0; + int burstKB = 0; + try { + rateKBps = Integer.parseInt(rate); + burstKB = Integer.parseInt(burst); + } catch (NumberFormatException nfe) { + // ignore + } + if ( (rateKBps > 0) && (burstKB > 0) ) { + numSeconds = burstKB / rateKBps; + } + } + return getBurstFactor(numSeconds, "outboundburstfactor"); + } + + private static String getBurstFactor(int numSeconds, String name) { + StringBuffer buf = new StringBuffer(256); + buf.append("\n"); + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java new file mode 100644 index 0000000000..2cdd0ba32d --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java @@ -0,0 +1,24 @@ +package net.i2p.router.web; + +import java.util.List; +import net.i2p.data.Hash; +import net.i2p.router.RouterContext; + +class ContextHelper { + public static RouterContext getContext(String contextId) { + List contexts = RouterContext.listContexts(); + if ( (contexts == null) || (contexts.size() <= 0) ) + throw new IllegalStateException("No contexts? wtf"); + if ( (contextId == null) || (contextId.trim().length() <= 0) ) + return (RouterContext)contexts.get(0); + for (int i = 0; i < contexts.size(); i++) { + RouterContext context = (RouterContext)contexts.get(i); + Hash hash = context.routerHash(); + if (hash == null) continue; + if (hash.toBase64().startsWith(contextId)) + return context; + } + // not found, so just give them the first we can find + return (RouterContext)contexts.get(0); + } +} \ No newline at end of file diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java new file mode 100644 index 0000000000..1dede7bc89 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java @@ -0,0 +1,42 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import net.i2p.router.RouterContext; + +public class LogsHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public LogsHelper() {} + + public String getLogs() { + List msgs = _context.logManager().getBuffer().getMostRecentMessages(); + StringBuffer buf = new StringBuffer(16*1024); + buf.append("

Most recent console messages:

\n"); + + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java new file mode 100644 index 0000000000..a4b2125e3a --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java @@ -0,0 +1,53 @@ +package net.i2p.router.web; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.i2p.router.RouterContext; + +public class NavHelper { + private static Map _apps = new HashMap(); + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public NavHelper() {} + + /** + * To register a new client application so that it shows up on the router + * console's nav bar, it should be registered with this singleton. + * + * @param name pretty name the app will be called in the link + * @param path full path pointing to the application's root + * (e.g. /i2ptunnel/index.jsp) + */ + public static void registerApp(String name, String path) { + _apps.put(name, path); + } + public static void unregisterApp(String name) { + _apps.remove(name); + } + + public String getClientAppLinks() { + StringBuffer buf = new StringBuffer(1024); + for (Iterator iter = _apps.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String path = (String)_apps.get(name); + buf.append(""); + buf.append(name).append(" |"); + } + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java new file mode 100644 index 0000000000..1e68254c32 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java @@ -0,0 +1,35 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import net.i2p.router.RouterContext; + +public class NetDbHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public NetDbHelper() {} + + public String getNetDbSummary() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024); + try { + _context.netDb().renderStatusHTML(baos); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return new String(baos.toByteArray()); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ProfilesHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ProfilesHelper.java new file mode 100644 index 0000000000..4a2eced84a --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ProfilesHelper.java @@ -0,0 +1,35 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import net.i2p.router.RouterContext; + +public class ProfilesHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ProfilesHelper() {} + + public String getProfileSummary() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(16*1024); + try { + _context.profileOrganizer().renderStatusHTML(baos); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return new String(baos.toByteArray()); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java new file mode 100644 index 0000000000..a16f8d6ea6 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -0,0 +1,50 @@ +package net.i2p.router.web; + +import java.io.IOException; +import org.mortbay.jetty.Server; +import org.mortbay.util.MultiException; + +public class RouterConsoleRunner { + private Server _server; + private String _listenPort = "7657"; + private String _listenHost = "0.0.0.0"; + private String _webAppsDir = "./webapps/"; + + public RouterConsoleRunner(String args[]) { + if (args.length == 3) { + _listenPort = args[0].trim(); + _listenHost = args[1].trim(); + _webAppsDir = args[2].trim(); + } + } + + public static void main(String args[]) { + RouterConsoleRunner runner = new RouterConsoleRunner(args); + runner.startConsole(); + } + + public void startConsole() { + _server = new Server(); + try { + _server.addListener(_listenHost + ':' + _listenPort); + _server.setRootWebApp("routerconsole"); + _server.addWebApplications(_webAppsDir); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + try { + _server.start(); + } catch (MultiException me) { + me.printStackTrace(); + } + } + + public void stopConsole() { + try { + _server.stop(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java new file mode 100644 index 0000000000..32ad3c2072 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -0,0 +1,377 @@ +package net.i2p.router.web; + +import java.text.DecimalFormat; + +import net.i2p.data.DataHelper; +import net.i2p.stat.Rate; +import net.i2p.stat.RateStat; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.RouterVersion; + +/** + * Simple helper to query the appropriate router for data necessary to render + * the summary sections on the router console. + */ +public class SummaryHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Retrieve the shortened 4 character ident for the router located within + * the current JVM at the given context. + * + */ + public String getIdent() { + if (_context == null) return "[no router]"; + + if (_context.routerHash() != null) + return _context.routerHash().toBase64().substring(0, 4); + else + return "[unknown]"; + } + /** + * Retrieve the version number of the router. + * + */ + public String getVersion() { + return RouterVersion.VERSION; + } + /** + * Retrieve a pretty printed uptime count (ala 4d or 7h or 39m) + * + */ + public String getUptime() { + if (_context == null) return "[no router]"; + + Router router = _context.router(); + if (router == null) + return "[not up]"; + else + return DataHelper.formatDuration(router.getUptime()); + } + + /** + * How many active peers the router has. + * + */ + public int getActivePeers() { + if (_context == null) + return 0; + else + return _context.profileOrganizer().countActivePeers(); + } + /** + * How many active peers the router ranks as fast. + * + */ + public int getFastPeers() { + if (_context == null) + return 0; + else + return _context.profileOrganizer().countFastPeers(); + } + /** + * How many active peers the router ranks as having a high capacity. + * + */ + public int getHighCapacityPeers() { + if (_context == null) + return 0; + else + return _context.profileOrganizer().countHighCapacityPeers(); + } + /** + * How many active peers the router ranks as well integrated. + * + */ + public int getWellIntegratedPeers() { + if (_context == null) + return 0; + else + return _context.profileOrganizer().countWellIntegratedPeers(); + } + /** + * How many peers the router ranks as failing. + * + */ + public int getFailingPeers() { + if (_context == null) + return 0; + else + return _context.profileOrganizer().countFailingPeers(); + } + /** + * How many peers totally suck. + * + */ + public int getShitlistedPeers() { + if (_context == null) + return 0; + else + return _context.shitlist().getRouterCount(); + } + + /** + * How fast we have been receiving data over the last minute (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getInboundMinuteKBps() { + if (_context == null) + return "0.0"; + + RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize"); + Rate rate = receiveRate.getRate(60*1000); + double bytes = rate.getLastTotalValue(); + double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + return fmt.format(bps); + } + /** + * How fast we have been sending data over the last minute (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getOutboundMinuteKBps() { + if (_context == null) + return "0.0"; + + RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize"); + Rate rate = receiveRate.getRate(60*1000); + double bytes = rate.getLastTotalValue(); + double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + return fmt.format(bps); + } + + /** + * How fast we have been receiving data over the last 5 minutes (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getInboundFiveMinuteKBps() { + if (_context == null) + return "0.0"; + + RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize"); + Rate rate = receiveRate.getRate(5*60*1000); + double bytes = rate.getLastTotalValue(); + double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + return fmt.format(bps); + } + + /** + * How fast we have been sending data over the last 5 minutes (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getOutboundFiveMinuteKBps() { + if (_context == null) + return "0.0"; + + RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize"); + Rate rate = receiveRate.getRate(5*60*1000); + double bytes = rate.getLastTotalValue(); + double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + return fmt.format(bps); + } + + /** + * How fast we have been receiving data since the router started (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getInboundLifetimeKBps() { + if (_context == null) + return "0.0"; + + long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes(); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + + // we use the unadjusted time, since thats what getWhenStarted is based off + long lifetime = _context.clock().now()-_context.clock().getOffset() + - _context.router().getWhenStarted(); + lifetime /= 1000; + if (received > 0) { + double receivedKBps = received / (lifetime*1024.0); + return fmt.format(receivedKBps); + } else { + return "0.0"; + } + } + + /** + * How fast we have been sending data since the router started (pretty printed + * string with 2 decimal places representing the KBps) + * + */ + public String getOutboundLifetimeKBps() { + if (_context == null) + return "0.0"; + + long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes(); + + DecimalFormat fmt = new DecimalFormat("##0.00"); + + // we use the unadjusted time, since thats what getWhenStarted is based off + long lifetime = _context.clock().now()-_context.clock().getOffset() + - _context.router().getWhenStarted(); + lifetime /= 1000; + if (sent > 0) { + double sendKBps = sent / (lifetime*1024.0); + return fmt.format(sendKBps); + } else { + return "0.0"; + } + } + + /** + * How much data have we received since the router started (pretty printed + * string with 2 decimal places and the appropriate units - GB/MB/KB/bytes) + * + */ + public String getInboundTransferred() { + if (_context == null) + return "0.0"; + + long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes(); + + return getTransferred(received); + } + + /** + * How much data have we sent since the router started (pretty printed + * string with 2 decimal places and the appropriate units - GB/MB/KB/bytes) + * + */ + public String getOutboundTransferred() { + if (_context == null) + return "0.0"; + + long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes(); + return getTransferred(sent); + } + + private static String getTransferred(long bytes) { + int scale = 0; + if (bytes > 1024*1024*1024) { + // gigs transferred + scale = 3; + bytes /= (1024*1024*1024); + } else if (bytes > 1024*1024) { + // megs transferred + scale = 2; + bytes /= (1024*1024); + } else if (bytes > 1024) { + // kbytes transferred + scale = 1; + bytes /= 1024; + } else { + scale = 0; + } + + DecimalFormat fmt = new DecimalFormat("##0.00"); + + String str = fmt.format(bytes); + switch (scale) { + case 1: return str + "KB"; + case 2: return str + "MB"; + case 3: return str + "GB"; + default: return bytes + "bytes"; + } + } + + /** + * How many free inbound tunnels we have. + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public int getInboundTunnels() { + if (_context == null) + return 0; + else + return _context.tunnelManager().getFreeTunnelCount(); + } + + /** + * How many active outbound tunnels we have. + * + */ + public int getOutboundTunnels() { + if (_context == null) + return 0; + else + return _context.tunnelManager().getOutboundTunnelCount(); + } + + /** + * How many tunnels we are participating in. + * + */ + public int getParticipatingTunnels() { + if (_context == null) + return 0; + else + return _context.tunnelManager().getParticipatingCount(); + } + + /** + * How lagged our job queue is over the last minute (pretty printed with + * the units attached) + * + */ + public String getJobLag() { + if (_context == null) + return "0ms"; + + Rate lagRate = _context.statManager().getRate("jobQueue.jobLag").getRate(60*1000); + return ((int)lagRate.getAverageValue()) + "ms"; + } + + /** + * How long it takes us to pump out a message, averaged over the last minute + * (pretty printed with the units attached) + * + */ + public String getMessageDelay() { + if (_context == null) + return "0ms"; + + Rate delayRate = _context.statManager().getRate("transport.sendProcessingTime").getRate(60*1000); + return ((int)delayRate.getAverageValue()) + "ms"; + } + + /** + * How long it takes us to test our tunnels, averaged over the last 10 minutes + * (pretty printed with the units attached) + * + */ + public String getTunnelLag() { + if (_context == null) + return "0ms"; + + Rate lagRate = _context.statManager().getRate("tunnel.testSuccessTime").getRate(10*60*1000); + return ((int)lagRate.getAverageValue()) + "ms"; + } +} \ No newline at end of file diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp new file mode 100644 index 0000000000..a66b99030c --- /dev/null +++ b/apps/routerconsole/jsp/config.jsp @@ -0,0 +1,53 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - logs + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + + +" /> + +
+ <%@include file="confignav.jsp" %> +
+ External hostname/IP address: + " /> +
+ Externally reachable TCP port: + " />
+ The hostname/IP address and TCP port must be reachable from the outside world. If + you are behind a firewall or NAT, this means you must poke a hole for this port. If + you are using DHCP and do not have a static IP address, you must use a service like + dyndns. The "guess" functionality makes an HTTP request + to www.whatismyip.com. +
+ Enable internal time synchronization? name="enabletimesync" />
+ If disabled, your machine must be NTP synchronized +
+ Bandwidth limiter
+ Inbound rate: + " /> KBytes per second
+ Inbound burst duration: +
+ Outbound rate: + " /> KBytes per second
+ Outbound burst duration: +
+ A negative rate means there is no limit
+
+ Reseed (from ): +
+
+ + +
+ + + diff --git a/apps/routerconsole/jsp/configadvanced.jsp b/apps/routerconsole/jsp/configadvanced.jsp new file mode 100644 index 0000000000..033d4f1c27 --- /dev/null +++ b/apps/routerconsole/jsp/configadvanced.jsp @@ -0,0 +1,26 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - config advanced + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + + +" /> + +
+ <%@include file="confignav.jsp" %> +
+
+ +
+
+ + + diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp new file mode 100644 index 0000000000..34eca7a7e4 --- /dev/null +++ b/apps/routerconsole/jsp/configclients.jsp @@ -0,0 +1,32 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - config clients + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + + +" /> + +
+ <%@include file="confignav.jsp" %> +
+ Estimated number of clients/destinations: +
+ Default number of inbound tunnels per client: +
+ Default number of hops per tunnel: +
+
+ + +
+ + + diff --git a/apps/routerconsole/jsp/configlogging.jsp b/apps/routerconsole/jsp/configlogging.jsp new file mode 100644 index 0000000000..23bef3e639 --- /dev/null +++ b/apps/routerconsole/jsp/configlogging.jsp @@ -0,0 +1,39 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - config clients + + + +" /> + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+ <%@include file="confignav.jsp" %> +
+ Logging filename: + " />
+ (the symbol '#' will be replaced during log rotation)
+ Log record format: + " />
+ (use 'd' = date, 'c' = class, 't' = thread, 'p' = priority, 'm' = message)
+ Log date format: + " />
+ ('MM' = month, 'dd' = day, 'HH' = hour, 'mm' = minute, 'ss' = second, 'SSS' = millisecond)
+ Max log file size: + " />
+
+ Log levels:
+ +
+ + +
+ + + diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp new file mode 100644 index 0000000000..c17b54a19a --- /dev/null +++ b/apps/routerconsole/jsp/confignav.jsp @@ -0,0 +1,8 @@ +

<% if (request.getRequestURI().indexOf("config.jsp") != -1) { + %>Network | <% } else { %>Network | <% } + if (request.getRequestURI().indexOf("configclients.jsp") != -1) { + %>Clients | <% } else { %>Clients | <% } + if (request.getRequestURI().indexOf("configlogging.jsp") != -1) { + %>Logging | <% } else { %>Logging | <% } + if (request.getRequestURI().indexOf("configadvanced.jsp") != -1) { + %>Advanced | <% } else { %>Advanced | <% } %>

diff --git a/apps/routerconsole/jsp/default.css b/apps/routerconsole/jsp/default.css new file mode 100644 index 0000000000..a0c1cb97d7 --- /dev/null +++ b/apps/routerconsole/jsp/default.css @@ -0,0 +1,60 @@ +body { + font-family: Verdana, Tahoma, Helvetica, sans-serif; + margin: 1em 0em; + padding: 0em; + text-align: center; + background-color: white; + color: black; +} + +.hide { + display: none; +} + +img { + border: none; +} + +div.logo { + float: left; + left: 1em; + top: 1em; + margin: 0em; + padding: .5em; + text-align: left; +} + +div.routersummary { + /* width: 8em; */ + /* height: 5em; */ + /* position: fixed; */ + float: left; + /* left: 1em; */ + /* top: 1em; */ + margin: 0em; + padding: .5em; + text-align: left; + border: medium solid #efefff; + background-color: #fafaff; + color: inherit; + font-size: small; + clear: left; /* fixes a bug in Opera */ +} + +div.warning { + margin: 0em 1em 1em 12em; + padding: .5em 1em; + background-color: #ffefef; + border: medium solid #ffafaf; + text-align: left; + color: inherit; +} + +div.main { + margin: 0em 1em 1em 12em; + padding: .5em 1em; + background-color: #ffffef; + border: medium solid #ffffd0; + text-align: left; + color: inherit; +} diff --git a/apps/routerconsole/jsp/help.jsp b/apps/routerconsole/jsp/help.jsp new file mode 100644 index 0000000000..cca7e74f82 --- /dev/null +++ b/apps/routerconsole/jsp/help.jsp @@ -0,0 +1,26 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - logs + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+hmm. we should probably have some help text here.
+This "routerconsole" application runs on top of a trimmed down Jetty +instance (trimmed down, as in, we do not include the demo apps or other add-ons), allowing you to deploy standard +JSP/Servlet web applications into your router. Jetty in turn makes use of Apache's javax.servlet (javax.servlet.jar) +implementation, as well as their xerces-j XML parser (xerces.jar). Their XML parser requires the Sun XML +APIs (JAXP) which is included in binary form (xml-apis.jar) as required by their binary code license. +This product includes software developed by the Apache Software Foundation (http://www.apache.org/). See the +I2P site or the source for more license details. +
+ + + diff --git a/apps/routerconsole/jsp/i2plogo.png b/apps/routerconsole/jsp/i2plogo.png new file mode 100644 index 0000000000..ee5c91da2a Binary files /dev/null and b/apps/routerconsole/jsp/i2plogo.png differ diff --git a/apps/routerconsole/jsp/index.jsp b/apps/routerconsole/jsp/index.jsp new file mode 100644 index 0000000000..ddb83d28eb --- /dev/null +++ b/apps/routerconsole/jsp/index.jsp @@ -0,0 +1,19 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - home + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+

Welcome to your router console

+
+ + + diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp new file mode 100644 index 0000000000..19a9997950 --- /dev/null +++ b/apps/routerconsole/jsp/logs.jsp @@ -0,0 +1,21 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - logs + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+ + " /> + +
+ + + diff --git a/apps/routerconsole/jsp/nav.jsp b/apps/routerconsole/jsp/nav.jsp new file mode 100644 index 0000000000..034ab1b5e0 --- /dev/null +++ b/apps/routerconsole/jsp/nav.jsp @@ -0,0 +1,18 @@ +<% + if (request.getParameter("i2p.contextId") != null) { + session.setAttribute("i2p.contextId", request.getParameter("i2p.contextId")); + }%> + + + +

+ Profiles | + Network Database | + Logs + + " /> + +

diff --git a/apps/routerconsole/jsp/netdb.jsp b/apps/routerconsole/jsp/netdb.jsp new file mode 100644 index 0000000000..86c94fcdc6 --- /dev/null +++ b/apps/routerconsole/jsp/netdb.jsp @@ -0,0 +1,21 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - network database summary + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+ + " /> + +
+ + + diff --git a/apps/routerconsole/jsp/notice.jsp b/apps/routerconsole/jsp/notice.jsp new file mode 100644 index 0000000000..f623cde1f9 --- /dev/null +++ b/apps/routerconsole/jsp/notice.jsp @@ -0,0 +1 @@ +<%=(null != request.getParameter("i2p.console.notice") ? request.getParameter("i2p.console.notice") : "")%> \ No newline at end of file diff --git a/apps/routerconsole/jsp/profiles.jsp b/apps/routerconsole/jsp/profiles.jsp new file mode 100644 index 0000000000..710e376e23 --- /dev/null +++ b/apps/routerconsole/jsp/profiles.jsp @@ -0,0 +1,21 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - peer profiles + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> +<%@include file="notice.jsp" %> + +
+ + " /> + +
+ + + diff --git a/apps/routerconsole/jsp/summary.jsp b/apps/routerconsole/jsp/summary.jsp new file mode 100644 index 0000000000..3c70f31197 --- /dev/null +++ b/apps/routerconsole/jsp/summary.jsp @@ -0,0 +1,40 @@ +<%@page import="net.i2p.router.web.SummaryHelper" %> + +" /> + +
+ General
+ Ident:
+ Version:
+ Uptime:
+
+ + Peers
+ Active:
+ Fast:
+ High capacity:
+ Well integrated:
+ Failing:
+ Shitlisted:
+
+ + Bandwidth in/out
+ 1m: /KBps
+ 5m: /KBps
+ Total: /KBps
+ Used: /
+
+ + Tunnels
+ Inbound:
+ Outbound:
+ Participating:
+
+ + Congestion
+ Job lag:
+ Message delay:
+ Tunnel lag:
+
+ +
diff --git a/apps/routerconsole/jsp/web.xml b/apps/routerconsole/jsp/web.xml new file mode 100644 index 0000000000..9a428440d4 --- /dev/null +++ b/apps/routerconsole/jsp/web.xml @@ -0,0 +1,17 @@ + + + + + + + 30 + + + + + index.jsp + + + \ No newline at end of file diff --git a/apps/routerconsole/readme.txt b/apps/routerconsole/readme.txt new file mode 100644 index 0000000000..dfb27d545c --- /dev/null +++ b/apps/routerconsole/readme.txt @@ -0,0 +1,25 @@ +The routerconsole application is an embedable web server / servlet container. +In it there is a bundled routerconsole.war containing JSPs (per jsp/*) that +implement a web based control panel for the router. This console gives the user +a quick view into how their router is operating and exposes some pages to +configure it. + +The web server itself is Jetty [1] and is contained within the various jar files +under lib/. To embed this web server and the included router console, the +startRouter script needs to be updated to include those jar files in the +class path, plus the router.config needs appropriate entries to start up the +server: + + clientApp.3.main=net.i2p.router.web.RouterConsoleRunner + clientApp.3.name=webConsole + clientApp.3.args=7657 0.0.0.0 ./webapps/ + +That instructs the router to fire up the webserver listening on port 7657 on +all of its interfaces (0.0.0.0), loading up any .war files under the ./webapps/ +directory. The RouterConsoleRunner itself configures the Jetty server to give +the ./webapps/routerconsole.war control over the root context, directing a +request to http://localhost:7657/index.jsp to the routerconsole.war's index.jsp. +Any other .war file will be mounted under their filename's context (e.g. +myi2p.war would be reachable at http://localhost:7657/myi2p/index.jsp). + +[1] http://jetty.mortbay.com/jetty/index.html \ No newline at end of file