From b68463249e9d716a8d7ab798b87f2b9a696d1d3c Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Sat, 24 Jul 2004 02:06:07 +0000
Subject: [PATCH] first pass at the 0.4 architecture.  not ready for use or
 integration yet, but is functional with some manual build/config work

---
 apps/routerconsole/java/build.xml             |  57 +++
 .../i2p/router/web/ConfigAdvancedHelper.java  |  38 ++
 .../i2p/router/web/ConfigClientsHelper.java   | 117 ++++++
 .../i2p/router/web/ConfigLoggingHelper.java   | 113 ++++++
 .../net/i2p/router/web/ConfigNetHelper.java   | 135 +++++++
 .../src/net/i2p/router/web/ContextHelper.java |  24 ++
 .../src/net/i2p/router/web/LogsHelper.java    |  42 ++
 .../src/net/i2p/router/web/NavHelper.java     |  53 +++
 .../src/net/i2p/router/web/NetDbHelper.java   |  35 ++
 .../net/i2p/router/web/ProfilesHelper.java    |  35 ++
 .../i2p/router/web/RouterConsoleRunner.java   |  50 +++
 .../src/net/i2p/router/web/SummaryHelper.java | 377 ++++++++++++++++++
 apps/routerconsole/jsp/config.jsp             |  53 +++
 apps/routerconsole/jsp/configadvanced.jsp     |  26 ++
 apps/routerconsole/jsp/configclients.jsp      |  32 ++
 apps/routerconsole/jsp/configlogging.jsp      |  39 ++
 apps/routerconsole/jsp/confignav.jsp          |   8 +
 apps/routerconsole/jsp/default.css            |  60 +++
 apps/routerconsole/jsp/help.jsp               |  26 ++
 apps/routerconsole/jsp/i2plogo.png            | Bin 0 -> 925 bytes
 apps/routerconsole/jsp/index.jsp              |  19 +
 apps/routerconsole/jsp/logs.jsp               |  21 +
 apps/routerconsole/jsp/nav.jsp                |  18 +
 apps/routerconsole/jsp/netdb.jsp              |  21 +
 apps/routerconsole/jsp/notice.jsp             |   1 +
 apps/routerconsole/jsp/profiles.jsp           |  21 +
 apps/routerconsole/jsp/summary.jsp            |  40 ++
 apps/routerconsole/jsp/web.xml                |  17 +
 apps/routerconsole/readme.txt                 |  25 ++
 29 files changed, 1503 insertions(+)
 create mode 100644 apps/routerconsole/java/build.xml
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigAdvancedHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ProfilesHelper.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
 create mode 100644 apps/routerconsole/jsp/config.jsp
 create mode 100644 apps/routerconsole/jsp/configadvanced.jsp
 create mode 100644 apps/routerconsole/jsp/configclients.jsp
 create mode 100644 apps/routerconsole/jsp/configlogging.jsp
 create mode 100644 apps/routerconsole/jsp/confignav.jsp
 create mode 100644 apps/routerconsole/jsp/default.css
 create mode 100644 apps/routerconsole/jsp/help.jsp
 create mode 100644 apps/routerconsole/jsp/i2plogo.png
 create mode 100644 apps/routerconsole/jsp/index.jsp
 create mode 100644 apps/routerconsole/jsp/logs.jsp
 create mode 100644 apps/routerconsole/jsp/nav.jsp
 create mode 100644 apps/routerconsole/jsp/netdb.jsp
 create mode 100644 apps/routerconsole/jsp/notice.jsp
 create mode 100644 apps/routerconsole/jsp/profiles.jsp
 create mode 100644 apps/routerconsole/jsp/summary.jsp
 create mode 100644 apps/routerconsole/jsp/web.xml
 create mode 100644 apps/routerconsole/readme.txt

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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir="." default="all" name="routerconsole">
+    <target name="all" depends="clean, build" />
+    <target name="build" depends="builddep, jar" />
+    <target name="builddep" depends="jetty" >
+        <ant dir="../../../router/java/" target="build" />
+	<!-- router will build core -->
+    </target>
+    <target name="jetty">
+        <untar src="jetty-4.2.21-min.tar.bz2" compression="bzip2" dest="." />
+        <ant dir="jetty-4.2.21-min/extra/jdk1.2/" target="all" />
+    </target>
+    <target name="compile">
+        <mkdir dir="./build" />
+        <mkdir dir="./build/obj" />
+        <javac 
+            srcdir="./src" 
+            debug="true" deprecation="on" source="1.3" target="1.3" 
+            destdir="./build/obj" 
+            classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:jetty-4.2.21-min/extra/lib/org.mortbay.jetty-jdk1.2.jar" />
+    </target>
+    <target name="jar" depends="compile">
+        <jar destfile="./build/routerconsole.jar" basedir="./build/obj" includes="**/*.class">
+            <manifest>
+                <attribute name="Class-Path" value="i2p.jar router.jar" />
+            </manifest>
+        </jar>
+        <ant target="war" />
+    </target>
+    <target name="war"> 
+        <war destfile="build/routerconsole.war" webxml="../jsp/web.xml"
+             basedir="../jsp/" excludes="web.xml">
+        </war>
+    </target>
+    <target name="javadoc">
+        <mkdir dir="./build" />
+        <mkdir dir="./build/javadoc" />
+        <javadoc 
+            sourcepath="./src:../../../core/java/src:../../router/java/src" destdir="./build/javadoc" 
+            packagenames="*" 
+            use="true" 
+            splitindex="true" 
+            windowtitle="Router Console" />
+    </target>
+    <target name="clean">
+        <delete dir="./build" />
+    </target>
+    <target name="cleandep" depends="clean">
+	<!-- router will clean core -->
+        <ant dir="../../../router/java/" target="distclean" />
+    </target>
+    <target name="distclean" depends="clean">
+	<!-- router will clean core -->
+        <ant dir="../../../router/java/" target="distclean" />
+        <delete dir="./jetty-4.2.21-min" />
+    </target>
+</project>
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("<select name=\"clientcount\">\n");
+        for (int i = 0; i < 5; i++) {
+            buf.append("<option value=\"").append(i).append("\" ");
+            if (count == i)
+                buf.append("selected=\"true\" ");
+            buf.append(">").append(i).append("</option>\n");
+        }
+        if (count >= 5) {
+            buf.append("<option value=\"").append(count);
+            buf.append("\" selected>").append(count);
+            buf.append("</option>\n");
+        }
+        buf.append("</select>\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("<select name=\"tunnelcount\">\n");
+        for (int i = 0; i < 4; i++) {
+            buf.append("<option value=\"").append(i).append("\" ");
+            if (count == i)
+                buf.append("selected=\"true\" ");
+            buf.append(">").append(i).append("</option>\n");
+        }
+        if (count >= 4) {
+            buf.append("<option value=\"").append(count);
+            buf.append("\" selected>").append(count);
+            buf.append("</option>\n");
+        }
+        buf.append("</select>\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("<select name=\"tunneldepth\">\n");
+        for (int i = 0; i < 4; i++) {
+            buf.append("<option value=\"").append(i).append("\" ");
+            if (count == i)
+                buf.append("selected=\"true\" ");
+            buf.append(">").append(i).append("</option>\n");
+        }
+        if (count >= 4) {
+            buf.append("<option value=\"").append(count);
+            buf.append("\" selected>").append(count);
+            buf.append("</option>\n");
+        }
+        buf.append("</select>\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("<textarea rows=\"20\" cols=\"80\">");
+        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(l.getName()).append('=');
+            buf.append(Log.toLevelString(l.getMinimumPriority()));
+            buf.append("\n");
+        }
+        buf.append("</textarea><br />\n");
+        buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
+        return buf.toString();
+    }
+    public String getLogLevelTableDetail() {
+        StringBuffer buf = new StringBuffer(8*1024);
+        buf.append("<table border=\"1\">\n");
+        buf.append("<tr><td>Package/class</td><td>Level</td></tr>\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("<tr>\n <td><input size=\"50\" type=\"text\" name=\"logrecord.");
+            buf.append(i).append(".package\" value=\"").append(l.getName());
+            buf.append("\" /></td>\n");
+            buf.append("<td><select name=\"logrecord.").append(i);
+            buf.append(".level\">\n\t");
+            buf.append("<option value=\"DEBUG\" ");
+            if (l.getMinimumPriority() == Log.DEBUG)
+                buf.append("selected=\"true\" ");
+            buf.append(">Debug</option>\n\t");
+            buf.append("<option value=\"INFO\" ");
+            if (l.getMinimumPriority() == Log.INFO)
+                buf.append("selected=\"true\" ");
+            buf.append(">Info</option>\n\t");
+            buf.append("<option value=\"WARN\" ");
+            if (l.getMinimumPriority() == Log.WARN)
+                buf.append("selected=\"true\" ");
+            buf.append(">Warn</option>\n\t");
+            buf.append("<option value=\"ERROR\" ");
+            if (l.getMinimumPriority() == Log.ERROR)
+                buf.append("selected=\"true\" ");
+            buf.append(">Error</option>\n\t");
+            buf.append("<option value=\"CRIT\" ");
+            if (l.getMinimumPriority() == Log.CRIT)
+                buf.append("selected=\"true\" ");
+            buf.append(">Critical</option>\n\t");
+            buf.append("</select></td>\n</tr>\n");
+        }
+        buf.append("</table>\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("<select name=\"").append(name).append("\">\n");
+        for (int i = 1; i < 10; i++) {
+            buf.append("<option value=\"").append(i).append("\" ");
+            if ( (i == numSeconds) || (i == 10) )
+                buf.append("selected ");
+            buf.append(">");
+            if (i == 1)
+                buf.append("1 second (no burst)</option>\n");
+            else
+                buf.append(i).append(" seconds</option>\n");
+        }
+        buf.append("</select>\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("<h2>Most recent console messages:</h2><ul>");
+        buf.append("<code>\n");
+        for (int i = 0; i < msgs.size(); i++) { 
+            String msg = (String)msgs.get(i);
+            buf.append("<li>");
+            buf.append(msg);
+            buf.append("</li>\n");
+        }
+        buf.append("</code></ul>\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("<a href=\"").append(path).append("\">");
+            buf.append(name).append("</a> |");
+        }
+        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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - logs</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<jsp:useBean class="net.i2p.router.web.ConfigNetHelper" id="nethelper" scope="request" />
+<jsp:setProperty name="nethelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+
+<div class="main" id="main">
+ <%@include file="confignav.jsp" %>
+ <form action="config.jsp" method="POST">
+ <b>External hostname/IP address:</b> 
+    <input name="hostname" type="text" size="32" value="<jsp:getProperty name="nethelper" property="hostname" />" />
+    <input type="submit" name="guesshost" value="Guess" /><br />
+ <b>Externally reachable TCP port:</b>
+     <input name="port" type="text" size="4" value="<jsp:getProperty name="nethelper" property="port" />" /> <br />
+ <i>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
+ <a href="http://dyndns.org/">dyndns</a>.  The "guess" functionality makes an HTTP request
+ to <a href="http://www.whatismyip.com/">www.whatismyip.com</a>.</i>
+ <hr />
+ <b>Enable internal time synchronization?</b> <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
+ <i>If disabled, your machine <b>must</b> be NTP synchronized</i>
+ <hr />
+ <b>Bandwidth limiter</b><br />
+ <b>Inbound rate</b>: 
+    <input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second<br />
+ <b>Inbound burst duration:</b>
+    <jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
+ <b>Outbound rate:</b>
+    <input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second<br />
+ <b>Outbound burst duration:</b> 
+  <jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
+ <i>A negative rate means there is no limit</i><br />
+ <hr />
+ <b>Reseed</b> (from <input name="reseedfrom" type="text" size="40" value="http://dev.i2p.net/i2pdb/" />): 
+               <input type="submit" name="reseed" value="now" /><br />
+ <hr />
+ <input type="submit" value="Save changes" /> <input type="reset" value="Cancel" />
+ </form>
+</div>
+
+</body>
+</html>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - config advanced</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<jsp:useBean class="net.i2p.router.web.ConfigAdvancedHelper" id="advancedhelper" scope="request" />
+<jsp:setProperty name="advancedhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+
+<div class="main" id="main">
+ <%@include file="confignav.jsp" %>
+ <form action="configadvanced.jsp" method="POST">
+ <textarea rows="20" cols="80" name="config"><jsp:getProperty name="advancedhelper" property="settings" /></textarea><br />
+ <input type="submit" value="Apply" /> <input type="reset" value="Cancel" />
+ </form>
+</div>
+
+</body>
+</html>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - config clients</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<jsp:useBean class="net.i2p.router.web.ConfigClientsHelper" id="clientshelper" scope="request" />
+<jsp:setProperty name="clientshelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+
+<div class="main" id="main">
+ <%@include file="confignav.jsp" %>
+ <form action="configclients.jsp" method="POST">
+ <b>Estimated number of clients/destinations:</b> 
+    <jsp:getProperty name="clientshelper" property="clientCountSelectBox" /><br />
+ <b>Default number of inbound tunnels per client:</b>
+    <jsp:getProperty name="clientshelper" property="tunnelCountSelectBox" /><br />
+ <b>Default number of hops per tunnel:</b>
+    <jsp:getProperty name="clientshelper" property="tunnelDepthSelectBox" /><br />
+ <hr />
+ <input type="submit" value="Save changes" /> <input type="reset" value="Cancel" />
+ </form>
+</div>
+
+</body>
+</html>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - config clients</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+<jsp:useBean class="net.i2p.router.web.ConfigLoggingHelper" id="logginghelper" scope="request" />
+<jsp:setProperty name="logginghelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+ <%@include file="confignav.jsp" %>
+ <form action="configlogging.jsp" method="POST">
+ <b>Logging filename:</b> 
+    <input type="text" name="logfilename" size="40" value="<jsp:getProperty name="logginghelper" property="logFilePattern" />" /><br />
+    <i>(the symbol '#' will be replaced during log rotation)</i><br />
+ <b>Log record format:</b>
+    <input type="text" name="logformat" size="20" value="<jsp:getProperty name="logginghelper" property="recordPattern" />" /><br />
+    <i>(use 'd' = date, 'c' = class, 't' = thread, 'p' = priority, 'm' = message)</i><br />
+ <b>Log date format:</b>
+    <input type="text" name="logdateformat" size="20" value="<jsp:getProperty name="logginghelper" property="datePattern" />" /><br />
+    <i>('MM' = month, 'dd' = day, 'HH' = hour, 'mm' = minute, 'ss' = second, 'SSS' = millisecond)</i><br />
+ <b>Max log file size:</b>
+    <input type="text" name="logfilesize" size="4" value="<jsp:getProperty name="logginghelper" property="maxFileSize" />" /><br />
+  <hr />
+  <b>Log levels:</b> <br />
+   <jsp:getProperty name="logginghelper" property="logLevelTable" />
+ <hr />
+ <input type="submit" value="Apply changes" /> <input type="submit" value="Apply and Save" /> <input type="reset" value="Cancel" />
+ </form>
+</div>
+
+</body>
+</html>
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 @@
+<h4><% if (request.getRequestURI().indexOf("config.jsp") != -1) { 
+ %>Network | <% } else { %><a href="config.jsp">Network</a> | <% }
+ if (request.getRequestURI().indexOf("configclients.jsp") != -1) {
+ %>Clients | <% } else { %><a href="configclients.jsp">Clients</a> | <% }
+ if (request.getRequestURI().indexOf("configlogging.jsp") != -1) {
+ %>Logging | <% } else { %><a href="configlogging.jsp">Logging</a> | <% }
+ if (request.getRequestURI().indexOf("configadvanced.jsp") != -1) {
+ %>Advanced | <% } else { %><a href="configadvanced.jsp">Advanced</a> | <% } %></h4>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - logs</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+hmm.  we should probably have some help text here.<br />
+This "routerconsole" application runs on top of a trimmed down <a href="jetty.mortbay.com/jetty/index.html">Jetty</a>
+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
+<a href="http://www.i2p.net/">I2P</a> site or the source for more license details.
+</div>
+
+</body>
+</html>
diff --git a/apps/routerconsole/jsp/i2plogo.png b/apps/routerconsole/jsp/i2plogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee5c91da2a3fbb7b5bd589f18dada5146510eac3
GIT binary patch
literal 925
zcmV;O17iG%P)<h;3K|Lk000e1NJLTq006Q8001Kd0{{R3!k$gN00003b3#c}2nYz<
z;ZNWI000|MOjJb`2?YQD|Nj605&seV{}-A6ng0K_eRp&JW@h_}h=l+E010qNS#tmY
z3h)2`3h)6!tTdPa001C#MObuGZ)S9NVRB^vO<`klZ*65{X<;BnX>w(EZ*psMAWc}i
zkt!qr00QkvL_t(|oYj^wZz3@eM!AB}W<u6W8yAQ+vPh3)QQr!YItId}+-tJ+ZV}~Y
z-!~q6F%Fz^xl5$8w9<Y-Jlo^vA%nrE4f?F{MP&6)$z{_t&-_-_sLiP237Rnc->I9D
z>ROs;M>|WjTavO1JdU<=iKe0g&v6_)fH!1tsJkO7`a@v!L~D1YXVmE_S-Viyx@&7O
z$&!q@<v2EmR%V_>Prd-44lvB34(E$4Txs2QT9ei@d~X4&+ktHDvH*(X3MgadS&Zbe
zhq;t|;n&=5K<++Be&}I8Q}PHq0Cj?DZ1fFq4s^OkC3dK_F%`!#DzP%RS&U?Ve5&>#
zA#>TETB29%<Q22*iT2Jv{93Fft}9aOJVvrmN?&Lt75*VR_^@>k{W3@7Cm0FY$!s8Z
zGgPES2^?)^5>W|`D?^0B6sQD8c5*fMF<OT;H;u395xMr{XdP>Y!{m6vI*vGBoMM$*
z=SO*{b5D-eS!+SP;V5p{Aj5)XKxA`3tui-cd`I{e$>kuf*;@zApvldoCm&hIpZ<ni
zB-S-e9qbO)v1XlaIY#SNqFDjWIARh^=3`==AGNgA)H)LDo<y_Bi^LPw?ZwH*Dz`3H
zncoxg@WaxjWEHPQLWXo=-3uzxV$Y5>Q+Px(N7y>Yv8hl39}9DrS{G|^K`EIPXU#U!
z>s07<JF!j$%{W3cg#+cd&8;gV>OgKdz3p|2U|p1fW9UdH-KhJ^9By|Q=X>2d$>pI-
z#G4{Rp%T*Te4SY{6|Cb3496|5G)ow)+a}i4u@*U*CeB+&FXEMQq2Ea?s$d>o?<KB%
zJnH<<dyM;>{Fl~UCO%~3j;k^C)^+%m^}X(3o$qybq4o9FjStq<XUXXa9`}uSpxe+l
zPFpuPOHQoIdR>$dGPuzC-nvnoC;!o!$6?KpJWPJNK`xGb7VD<oI@FnCYF*OnGV3n=
zn#=QR7R^XBb0_0th?Q8!as5^QG5#;)PaD4g13P<{mEM+600000NkvXXu0mjfj)Jbf

literal 0
HcmV?d00001

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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - home</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+ <h2>Welcome to your router console</h2>
+</div>
+
+</body>
+</html>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - logs</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+ <jsp:useBean class="net.i2p.router.web.LogsHelper" id="logsHelper" scope="request" />
+ <jsp:setProperty name="logsHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:getProperty name="logsHelper" property="logs" />
+</div>
+
+</body>
+</html>
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")); 
+   }%>
+
+<div class="logo">
+ <a href="index.jsp"><img src="i2plogo.png" alt="Router Console" width="187" height="35" /></a><br />
+ [<a href="config.jsp">configuration</a> | <a href="help.jsp">help</a>]
+</div>
+
+<h3>
+ <a href="profiles.jsp">Profiles</a> |
+ <a href="netdb.jsp">Network Database</a> |
+ <a href="logs.jsp">Logs</a> 
+ <jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
+ <jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:getProperty name="navhelper" property="clientAppLinks" />
+</h3>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - network database summary</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+ <jsp:useBean class="net.i2p.router.web.NetDbHelper" id="netdbHelper" scope="request" />
+ <jsp:setProperty name="netdbHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:getProperty name="netdbHelper" property="netDbSummary" />
+</div>
+
+</body>
+</html>
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"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - peer profiles</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+<%@include file="notice.jsp" %>
+
+<div class="main" id="main">
+ <jsp:useBean class="net.i2p.router.web.ProfilesHelper" id="profilesHelper" scope="request" />
+ <jsp:setProperty name="profilesHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:getProperty name="profilesHelper" property="profileSummary" />
+</div>
+
+</body>
+</html>
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" %>
+<jsp:useBean class="net.i2p.router.web.SummaryHelper" id="helper" scope="request" />
+<jsp:setProperty name="helper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+
+<div class="routersummary">
+ <u><b>General</b></u><br />
+ <b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
+ <b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
+ <b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
+ <hr />
+ 
+ <u><b>Peers</b></u><br />
+ <b>Active:</b> <jsp:getProperty name="helper" property="activePeers" /><br />
+ <b>Fast:</b> <jsp:getProperty name="helper" property="fastPeers" /><br />
+ <b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
+ <b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
+ <b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
+ <b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
+ <hr />
+ 
+ <u><b>Bandwidth in/out</b></u><br />
+ <b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
+ <b>5m:</b> <jsp:getProperty name="helper" property="inboundFiveMinuteKBps" />/<jsp:getProperty name="helper" property="outboundFiveMinuteKBps" />KBps<br />
+ <b>Total:</b> <jsp:getProperty name="helper" property="inboundLifetimeKBps" />/<jsp:getProperty name="helper" property="outboundLifetimeKBps" />KBps<br />
+ <b>Used:</b> <jsp:getProperty name="helper" property="inboundTransferred" />/<jsp:getProperty name="helper" property="outboundTransferred" /><br />
+ <hr />
+ 
+ <u><b>Tunnels</b></u><br />
+ <b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br />
+ <b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br />
+ <b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br />
+ <hr />
+ 
+ <u><b>Congestion</b></u><br />
+ <b>Job lag:</b> <jsp:getProperty name="helper" property="jobLag" /><br />
+ <b>Message delay:</b> <jsp:getProperty name="helper" property="messageDelay" /><br />
+ <b>Tunnel lag:</b> <jsp:getProperty name="helper" property="tunnelLag" /><br />
+ <hr />
+ 
+</div>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE web-app
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
+    "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
+
+<web-app>
+    <session-config>
+        <session-timeout>
+            30
+        </session-timeout>
+    </session-config>
+    <welcome-file-list>
+        <welcome-file>
+            index.jsp
+        </welcome-file>
+    </welcome-file-list>
+</web-app>
\ 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
-- 
GitLab