diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a9d991c93d73cac0abb926107b622a16d2371dd
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
@@ -0,0 +1,59 @@
+package net.i2p.router.web;
+
+import java.util.HashMap;
+import java.util.Map;
+import net.i2p.data.DataFormatException;
+import net.i2p.util.Log;
+
+/**
+ *
+ */
+public class ConfigClientsHandler extends FormHandler {
+    private Log _log;
+    private Map _settings;
+    private boolean _shouldSave;
+    
+    public ConfigClientsHandler() {
+        _shouldSave = false;
+    }
+    
+    protected void processForm() {
+        if (_shouldSave) {
+            saveChanges();
+        } else {
+            // noop
+            addFormError("Unimplemented");
+        }
+    }
+    
+    public void setShouldsave(String moo) { 
+        if ( (moo != null) && (moo.equals("Save changes")) )
+            _shouldSave = true; 
+    }
+    
+    public void setSettings(Map settings) { _settings = new HashMap(settings); }
+    
+    /**
+     * The user made changes to the network config and wants to save them, so
+     * lets go ahead and do so.
+     *
+     */
+    private void saveChanges() {
+        _log = _context.logManager().getLog(ConfigClientsHandler.class);
+        boolean saveRequired = false;
+        
+        int updated = 0;
+        int index = 0;
+        
+        if (updated > 0)
+            addFormNotice("Updated settings");
+        
+        if (saveRequired) {
+            boolean saved = _context.router().saveConfig();
+            if (saved) 
+                addFormNotice("Exploratory tunnel configuration saved successfully");
+            else
+                addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs");
+        }
+    }
+}
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 0000000000000000000000000000000000000000..715899c208f1ca1ee27498637d7eecbf23f0feaa
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
@@ -0,0 +1,83 @@
+package net.i2p.router.web;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import net.i2p.router.RouterContext;
+import net.i2p.router.startup.ClientAppConfig;
+
+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();
+        }
+    }
+
+    public ConfigClientsHelper() {}
+    
+    
+    public String getForm1() {
+        StringBuffer buf = new StringBuffer(1024);
+        buf.append("<table border=\"1\">\n");
+        buf.append("<tr><td>Client</td><td>Enabled?</td><td>Class and arguments</td></tr>\n");
+        
+        List clients = ClientAppConfig.getClientApps(_context);
+        for (int cur = 0; cur < clients.size(); cur++) {
+            ClientAppConfig ca = (ClientAppConfig) clients.get(cur);
+            renderForm(buf, cur, ca.clientName, false, !ca.disabled, "webConsole".equals(ca.clientName), ca.className + " " + ca.args);
+        }
+        
+        buf.append("</table>\n");
+        return buf.toString();
+    }
+
+    public String getForm2() {
+        StringBuffer buf = new StringBuffer(1024);
+        buf.append("<table border=\"1\">\n");
+        buf.append("<tr><td>WebApp</td><td>Enabled?</td><td>Description</td></tr>\n");
+        Properties props = RouterConsoleRunner.webAppProperties();
+        Set keys = new TreeSet(props.keySet());
+        int cur = 0;
+        for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
+            String name = (String)iter.next();
+            if (name.startsWith(RouterConsoleRunner.PREFIX) && name.endsWith(RouterConsoleRunner.ENABLED)) {
+                String app = name.substring(8, name.lastIndexOf(RouterConsoleRunner.ENABLED));
+                String val = props.getProperty(name);
+                renderForm(buf, cur, app, !"addressbook".equals(app), "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war");
+                cur++;
+            }
+        }
+        buf.append("</table>\n");
+        return buf.toString();
+    }
+
+    private void renderForm(StringBuffer buf, int index, String name, boolean urlify, boolean enabled, boolean ro, String desc) {
+        buf.append("<tr><td>");
+        if (urlify && enabled) {
+            String link = "/";
+            if (! RouterConsoleRunner.ROUTERCONSOLE.equals(name))
+                link += name + "/";
+            buf.append("<a href=\"").append(link).append("\">").append(name).append("</a>");
+        } else {
+            buf.append(name);
+        }
+        buf.append("</td><td align=\"center\"><input type=\"checkbox\" name=\"enable\" value=\"").append(index).append(".enabled\" ");
+        if (enabled) {
+            buf.append("checked=\"true\" ");
+            if (ro)
+                buf.append("disabled=\"true\" ");
+        }
+        buf.append("/><td>").append(desc).append("</td></tr>\n");
+    }
+}
diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..8f4f6c8e2733a27cc8614bb1944d7319156010cd
--- /dev/null
+++ b/apps/routerconsole/jsp/configclients.jsp
@@ -0,0 +1,63 @@
+<%@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" %>
+
+<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" %>
+  
+ <jsp:useBean class="net.i2p.router.web.ConfigClientsHandler" id="formhandler" scope="request" />
+ <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:setProperty name="formhandler" property="shouldsave" value="<%=request.getParameter("shouldsave")%>" />
+ <jsp:setProperty name="formhandler" property="action" value="<%=request.getParameter("action")%>" />
+ <jsp:setProperty name="formhandler" property="nonce" value="<%=request.getParameter("nonce")%>" />
+ <jsp:setProperty name="formhandler" property="settings" value="<%=request.getParameterMap()%>" />
+ <font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
+ <i><jsp:getProperty name="formhandler" property="notices" /></i>
+ 
+ <form action="configclients.jsp" method="POST">
+ <% String prev = System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce");
+    if (prev != null) System.setProperty("net.i2p.router.web.ConfigClientsHandler.noncePrev", prev);
+    System.setProperty("net.i2p.router.web.ConfigClientsHandler.nonce", new java.util.Random().nextLong()+""); %>
+ <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce")%>" />
+ <input type="hidden" name="action" value="blah" />
+ <h3>Client Configuration</h3>
+ <p>
+ The Java clients listed below are started by the router and run in the same JVM.
+ </p><p>
+ <jsp:getProperty name="clientshelper" property="form1" />
+ </p><p>
+ <input type="submit" name="action" value="Save Client Configuration" />
+ </p><p>
+ <i>All changes require restart to take effect. For other changes edit the clients.config file.</i>
+ </p>
+ <hr />
+ <h3>WebApp Configuration</h3>
+ <p>
+ The Java web applications listed below are started by the webConsole client and run in the same JVM as the router.
+ They are usually web applications accessible through the router console.
+ They may be complete applications (e.g. i2psnark),
+ front-ends to another client or application which must be separately enabled (e.g. susidns, i2ptunnel),
+ or have no web interface at all (e.g. addressbook).
+ </p><p>
+ <jsp:getProperty name="clientshelper" property="form2" />
+ </p><p>
+ <input type="submit" name="action" value="Save WebApp Configuration" />
+ </p><p>
+ <i>All changes require restart to take effect. For other changes edit the webapps.config file.</i>
+ </p>
+ </form>
+</div>
+
+</body>
+</html>
diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp
index 5bf9338c97d1589052d9a7ab8c0b5ed2a461f0f6..8625ca6877b87b0910fad00283c0e50f5fcb5359 100644
--- a/apps/routerconsole/jsp/confignav.jsp
+++ b/apps/routerconsole/jsp/confignav.jsp
@@ -4,6 +4,8 @@
  %>Service | <% } else { %><a href="configservice.jsp">Service</a> | <% }
  if (request.getRequestURI().indexOf("configupdate.jsp") != -1) {
  %>Update | <% } else { %><a href="configupdate.jsp">Update</a> | <% }
+ if (request.getRequestURI().indexOf("configclients.jsp") != -1) {
+ %>Clients | <% } else { %><a href="configclients.jsp">Clients</a> | <% }
  if (request.getRequestURI().indexOf("configtunnels.jsp") != -1) {
  %>Tunnels | <% } else { %><a href="configtunnels.jsp">Tunnels</a> | <% }
  if (request.getRequestURI().indexOf("configlogging.jsp") != -1) {
diff --git a/history.txt b/history.txt
index 379ed92fa632ff0de632e0862f950738caab69e3..e202dd2064c1a5ab818e5437cc7a4c7e09d9d98b 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,18 @@
+2008-06-16 zzz
+    * UDP: Prevent 100% CPU when UDP bind fails;
+      change bind fail message from ERROR to CRIT
+    * Refactor LoadClientAppsJob.java, move some functions to new
+      ClientAppConfig.java, to make them easily available to
+      new configclients.jsp
+    * RouterConsoleRunner: Use a new config file, webapps.config,
+      to control which .wars in webapps/ get run. Apps are enabled
+      by default; disable by (e.g.) webapps.syndie.startOnLoad=false
+      Config file is written if it does not exist.
+      Implement methods for use by new configclients.jsp.
+    * configclients.jsp: New. For both clients and webapps.
+      Saves are not yet implemented.
+
+
 2008-06-10 zzz
     * Floodfill: Add new FloodfillMonitorJob, which tracks active
       floodfills, and automatically enables/disables floodfill on
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index b3568c1fd8b5f70904588e6fd0a63ef1e4ea194c..da77a17e2f4600814eba8fed4e936b3eb8b34a40 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
 public class RouterVersion {
     public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
     public final static String VERSION = "0.6.2";
-    public final static long BUILD = 2;
+    public final static long BUILD = 3;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);