diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigHomeHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigHomeHandler.java new file mode 100644 index 0000000000..eecb0c5497 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigHomeHandler.java @@ -0,0 +1,98 @@ +package net.i2p.router.web; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Map; + +/** + * Simple home page configuration. + * + * @since 0.9 + */ +public class ConfigHomeHandler extends FormHandler { + + private Map _settings; + + @Override + protected void processForm() { + if (_action == null) return; + String group = getJettyString("group"); + boolean deleting = _action.equals(_("Delete selected")); + boolean adding = _action.equals(_("Add item")); + if (_action.equals(_("Save")) && "0".equals(group)) { + boolean old = _context.getBooleanProperty(HomeHelper.PROP_OLDHOME); + boolean nnew = getJettyString("oldHome") != null; + if (old != nnew) { + _context.router().saveConfig(HomeHelper.PROP_OLDHOME, "" + nnew); + addFormNotice(_("Home page changed")); + } + } else if (adding || deleting) { + String prop; + String dflt; + if ("1".equals(group)) { + prop = HomeHelper.PROP_FAVORITES; + dflt = HomeHelper.DEFAULT_FAVORITES; + } else if ("2".equals(group)) { + prop = HomeHelper.PROP_SERVICES; + dflt = HomeHelper.DEFAULT_SERVICES; + } else { + addFormError("Bad group"); + return; + } + String config = _context.getProperty(prop, dflt); + Collection apps = HomeHelper.buildApps(_context, config); + if (adding) { + String name = getJettyString("name"); + if (name == null || name.length() <= 0) { + addFormError(_("No name entered")); + return; + } + String url = getJettyString("url"); + if (url == null || url.length() <= 0) { + addFormError(_("No URL entered")); + return; + } + name = name.replace(";", ""); + url = url.replace(";", ""); + HomeHelper.App app = new HomeHelper.App(name, "", url, "/themes/console/images/itoopie_sm.png"); + apps.add(app); + addFormNotice(_("Added") + ": " + app.name); + } else { + // deleting + Set toDelete = new HashSet(); + for (Object o : _settings.keySet()) { + if (!(o instanceof String)) + continue; + String k = (String) o; + if (!k.startsWith("delete_")) + continue; + k = k.substring(7); + toDelete.add(k); + } + for (Iterator iter = apps.iterator(); iter.hasNext(); ) { + HomeHelper.App app = iter.next(); + if (toDelete.contains(app.name)) { + iter.remove(); + addFormNotice(_("Removed") + ": " + app.name); + } + } + } + HomeHelper.saveApps(_context, prop, apps); + } else { + addFormError(_("Unsupported")); + } + } + + public void setSettings(Map settings) { _settings = new HashMap(settings); } + + /** curses Jetty for returning arrays */ + private String getJettyString(String key) { + String[] arr = (String[]) _settings.get(key); + if (arr == null) + return null; + return arr[0].trim(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java index 5b6fecb8e6..eb8d504bb5 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java @@ -11,12 +11,13 @@ public class ConfigNavHelper extends HelperBase { /** configX.jsp */ private static final String pages[] = - {"", "net", "ui", "service", "update", "tunnels", + {"", "net", "ui", "home", "service", "update", "tunnels", "clients", "peer", "keyring", "logging", "stats", "reseed", "advanced" }; private static final String titles[] = - {_x("Bandwidth"), _x("Network"), _x("UI"), _x("Service"), _x("Update"), _x("Tunnels"), + {_x("Bandwidth"), _x("Network"), _x("UI"), _x("Home Page"), + _x("Service"), _x("Update"), _x("Tunnels"), _x("Clients"), _x("Peers"), _x("Keyring"), _x("Logging"), _x("Stats"), _x("Reseeding"), _x("Advanced") }; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java new file mode 100644 index 0000000000..6e8ba44ea0 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java @@ -0,0 +1,199 @@ +package net.i2p.router.web; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import net.i2p.router.RouterContext; +import net.i2p.util.PortMapper; + +/** + * For /home and /confighome + * + * @since 0.9 + */ +public class HomeHelper extends HelperBase { + + private static final char S = ';'; + private static final String I = "/themes/console/images/"; + static final String PROP_SERVICES = "routerconsole.services"; + static final String PROP_FAVORITES = "routerconsole.favorites"; + static final String PROP_OLDHOME = "routerconsole.oldHomePage"; + + static final String DEFAULT_SERVICES = + _x("Addressbook") + S + _x("Manage your I2P hosts file here (I2P domain name resolution)") + S + "/susidns/index" + S + I + "book_addresses.png" + S + + _x("Configure Bandwidth") + S + _x("I2P Bandwidth Configuration") + S + "/config" + S + I + "wrench_orange.png" + S + + _x("Customize Home Page") + S + _x("I2P Home Page Configuration") + S + "/confighome" + S + I + "wrench_orange.png" + S + + _x("Email") + S + _x("Anonymous webmail client") + S + "/susimail/susimail" + S + I + "email.png" + S + + _x("Help") + S + _x("I2P Router Help") + S + "/help" + S + I + "help.png" + S + + _x("Router Console") + S + _x("I2P Router Console") + S + "/console" + S + I + "wrench_orange.png" + S + + _x("Torrents") + S + _x("Built-in anonymous BitTorrent Client") + S + "/i2psnark/" + S + I + "film.png" + S + + _x("Website") + S + _x("Local web server") + S + "http://127.0.0.1:7658/" + S + I + "server.png" + S + + ""; + + static final String DEFAULT_FAVORITES = + _x("Bug Reports") + S + _x("Bug tracker") + S + "http://trac.i2p2.i2p/report/1" + S + I + "bug.png" + S + + _x("Dev Forum") + S + _x("Development forum") + S + "http://zzz.i2p/" + S + I + "itoopie_sm.png" + S + + _x("diftracker") + S + _x("Bittorrent tracker") + S + "http://diftracker.i2p/" + S + I + "itoopie_sm.png" + S + + "echelon.i2p" + S + _x("I2P Applications") + S + "http://echelon.i2p/" + S + I + "itoopie_sm.png" + S + + _x("FAQ") + S + _x("Frequently Asked Questions") + S + "http://www.i2p2.i2p/faq" + S + I + "help.png" + S + + _x("Forum") + S + _x("Community forum") + S + "http://forum.i2p/" + S + I + "itoopie_sm.png" + S + + "i2plugins.i2p" + S + _x("I2P Plugins") + S + "http://i2plugins.i2p/" + S + I + "itoopie_sm.png" + S + + "ident.i2p" + S + _x("Short message service") + S + "http://ident.i2p/" + S + I + "itoopie_sm.png" + S + + _x("Javadocs") + S + _x("Technical documentation") + S + "http://docs.i2p-projekt.i2p/javadoc/" + S + I + "book.png" + S + + _x("Pastebin") + S + _x("I2P Pastebin") + S + "http://pastethis.i2p/" + S + I + "itoopie_sm.png" + S + + "Planet I2P" + S + _x("I2P News") + S + "http://planet.i2p/" + S + I + "itoopie_sm.png" + S + + _x("Postman's Tracker") + S + _x("Bittorrent tracker") + S + "http://tracker2.postman.i2p/" + S + I + "itoopie_sm.png" + S + + _x("Project Website") + S + _x("I2P home page") + S + "http://www.i2p2.i2p/" + S + I + "help.png" + S + + "stats.i2p" + S + _x("I2P Netowrk Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "itoopie_sm.png" + S + + _x("Technical Docs") + S + _x("Technical documentation") + S + "http://www.i2p2.i2p/how" + S + I + "book.png" + S + + ""; + + + public String getServices() { + List plugins = NavHelper.getClientApps(_context); + return homeTable(PROP_SERVICES, DEFAULT_SERVICES, plugins); + } + + public String getFavorites() { + return homeTable(PROP_FAVORITES, DEFAULT_FAVORITES, null); + } + + public String getConfigServices() { + return configTable(PROP_SERVICES, DEFAULT_SERVICES); + } + + public String getConfigFavorites() { + return configTable(PROP_FAVORITES, DEFAULT_FAVORITES); + } + + public String getConfigHome() { + boolean oldHome = _context.getBooleanProperty(PROP_OLDHOME); + return oldHome ? "checked=\"true\"" : ""; + } + + public String getProxyStatus() { + int port = _context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY); + if (port <= 0) + return _("The HTTP proxy is not up"); + return "\"""; + } + + private String homeTable(String prop, String dflt, Collection toAdd) { + String config = _context.getProperty(prop, dflt); + Collection apps = buildApps(_context, config); + if (toAdd != null) + apps.addAll(toAdd); + return renderApps(apps); + } + + private String configTable(String prop, String dflt) { + String config = _context.getProperty(prop, dflt); + Collection apps = buildApps(_context, config); + return renderConfig(apps); + } + + static Collection buildApps(RouterContext ctx, String config) { + String[] args = config.split("" + S); + Set apps = new TreeSet(new AppComparator()); + for (int i = 0; i < args.length - 3; i += 4) { + String name = Messages.getString(args[i], ctx); + String desc = Messages.getString(args[i+1], ctx); + String url = args[i+2]; + String icon = args[i+3]; + apps.add(new App(name, desc, url, icon)); + } + return apps; + } + + static void saveApps(RouterContext ctx, String prop, Collection apps) { + StringBuilder buf = new StringBuilder(1024); + for (App app : apps) { + buf.append(app.name).append(S) + .append(app.desc).append(S) + .append(app.url).append(S) + .append(app.icon).append(S); + } + ctx.router().saveConfig(prop, buf.toString()); + } + + private static String renderApps(Collection apps) { + StringBuilder buf = new StringBuilder(1024); + buf.append("
"); + for (App app : apps) { + buf.append("
" + + "" + + "\"\"
\n" + + "
" + + "" + + "
" + + "
\n"); + } + buf.append("
\n"); + return buf.toString(); + } + + private String renderConfig(Collection apps) { + StringBuilder buf = new StringBuilder(1024); + buf.append("\n"); + for (App app : apps) { + buf.append("\n"); + } + buf.append("" + + ""); + buf.append("
") + .append(_("Remove")) + .append("") + .append(_("Name")) + .append("") + .append(_("URL")) + .append("
") + .append(app.name) + .append("") + .append(app.url) + .append("
") + .append(_("Add")).append(":" + + "
\n"); + return buf.toString(); + } + + static class App { + public final String name; + public final String desc; + public final String url; + public final String icon; + + public App(String name, String desc, String url, String icon) { + this.name = name; + this.desc = desc; + this.url = url; + this.icon = icon; + } + } + + /** ignore case, current locale */ + private static class AppComparator implements Comparator { + public int compare(App l, App r) { + return l.name.toLowerCase().compareTo(r.name.toLowerCase()); + } + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java index f34b1614d8..989d501303 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java @@ -36,6 +36,7 @@ public class NavHelper { /** * Translated string is loaded by PluginStarter + * @param ctx unused */ public static String getClientAppLinks(I2PAppContext ctx) { if (_apps.isEmpty()) @@ -55,4 +56,34 @@ public class NavHelper { } return buf.toString(); } + + /** + * For HomeHelper + * @param ctx unused + * @return non-null, possibly empty + * @since 0.9 + */ + static List getClientApps(I2PAppContext ctx) { + if (_apps.isEmpty()) + return Collections.EMPTY_LIST; + List rv = new ArrayList(_apps.size()); + for (Map.Entry e : _apps.entrySet()) { + String name = e.getKey(); + String path = e.getValue(); + if (path == null) + continue; + String tip = _tooltips.get(name); + if (tip == null) + tip = ""; + // hardcoded hack + String icon; + if (path.equals("/i2pbote/index.jsp")) + icon = "/themes/console/images/email.png"; + else + icon = "/themes/console/images/plugin.png"; + HomeHelper.App app = new HomeHelper.App(name, tip, path, icon); + rv.add(app); + } + return rv; + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java new file mode 100644 index 0000000000..876284c098 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java @@ -0,0 +1,101 @@ +package net.i2p.router.web; + +import java.util.Map; +import java.util.TreeMap; + +import net.i2p.util.PortMapper; + +/** + * Helper for searches. + * + * @since 0.9 + */ +public class SearchHelper extends HelperBase { + + private String _engine; + private String _query; + private Map _engines = new TreeMap(); + + private static final char S = ';'; + private static final String PROP_ENGINES = "routerconsole.searchEngines"; + private static final String PROP_DEFAULT = "routerconsole.searchEngine"; + + private static final String PROP_DEFAULTS = + "eepsites.i2p" + S + "http://eepsites.i2p/Content/Search/SearchResults.aspx?inpQuery=%s" + S + + "epsilon.i2p" + S + "http://epsilon.i2p/search.jsp?q=%s" + S + + "sprongle.i2p" + S + "http://sprongle.i2p/sprongle.php?q=%s" + S + + ""; + + public void setEngine(String s) { + _engine = s; + if (s != null) { + String dflt = _context.getProperty(PROP_DEFAULT); + if (!s.equals(dflt)) + _context.router().saveConfig(PROP_DEFAULT, s); + } + } + + public void setQuery(String s) { + _query = s; + } + + private void buildEngineMap() { + String config = _context.getProperty(PROP_ENGINES, PROP_DEFAULTS); + String[] args = config.split("" + S); + for (int i = 0; i < args.length - 1; i += 2) { + String name = args[i]; + String url = args[i+1]; + _engines.put(name, url); + } + } + + public String getSelector() { + buildEngineMap(); + if (_engines.isEmpty()) + return "No search engines specified"; + String dflt = _context.getProperty(PROP_DEFAULT); + if (dflt == null || !_engines.containsKey(dflt)) { + // pick a randome one as default and save it + int idx = _context.random().nextInt(_engines.size()); + int i = 0; + for (String name : _engines.keySet()) { + dflt = name; + if (i++ >= idx) { + _context.router().saveConfig(PROP_DEFAULT, dflt); + break; + } + } + } + StringBuilder buf = new StringBuilder(1024); + buf.append("\n"); + return buf.toString(); + } + + /** + * @return null on error + */ + public String getURL() { + if (_engine == null || _query == null) + return null; + _query = _query.trim(); + if (_query.length() <= 0) + return null; + buildEngineMap(); + String url = _engines.get(_engine); + if (url == null) + return null; + // _query = escape query + if (url.contains("%s")) + url = url.replace("%s", _query); + else + url += _query; + return url; + } +} diff --git a/apps/routerconsole/jsp/confighome.jsp b/apps/routerconsole/jsp/confighome.jsp new file mode 100644 index 0000000000..99a3c150c4 --- /dev/null +++ b/apps/routerconsole/jsp/confighome.jsp @@ -0,0 +1,59 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +<%@include file="css.jsi" %> +<%=intl.title("config home")%> + + +<%@include file="summary.jsi" %> +

<%=intl._("I2P Home Page Configuration")%>

+
+<%@include file="confignav.jsi" %> + + +<% formhandler.storeMethod(request.getMethod()); %> + +" /> + + +<% + String pageNonce = formhandler.getNewNonce(); +%> + +" /> + +

<%=intl._("Default Home Page")%>

+
+ + + > + <%=intl._("Use old home page")%> + " > +
+ +

<%=intl._("Recommended Eepsites")%>

+
+ + + +
+ " > + " > + " > +
+ + +

<%=intl._("Local Services")%>

+
+ + + +
+ " > + " > + " > +
+ +
diff --git a/apps/routerconsole/jsp/home.jsp b/apps/routerconsole/jsp/home.jsp new file mode 100644 index 0000000000..382a73aeed --- /dev/null +++ b/apps/routerconsole/jsp/home.jsp @@ -0,0 +1,149 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +<%@include file="css.jsi" %> +<%=intl.title("home")%> + + + + + + + + +<% + String consoleNonce = System.getProperty("router.consoleNonce"); + if (consoleNonce == null) { + consoleNonce = Long.toString(new java.util.Random().nextLong()); + System.setProperty("router.consoleNonce", consoleNonce); + } +%> + +
+
+
+ + I2P Router Console +
+
+
+ +<%@include file="xhr1.jsi" %> +
+
+
+ +
+
+ English + عربية + 中文 + Danish + Deutsch + Eesti + Español + Suomi + Français
+ Italiano + Nederlands + Polski + Português + Русский + Svenska + Ukrainian + Tiếng Việt +
+

<%=intl._("Welcome to I2P")%>

+
+ +
+ + " /> +<% + if (newshelper.shouldShowNews()) { + java.io.File fpath = new java.io.File(net.i2p.I2PAppContext.getGlobalContext().getRouterDir(), "docs/news.xml"); +%> +

<%=intl._("Latest I2P News")%>

+ + + +
+<% + } // shouldShowNews() +%> + + " /> +
+
+ +
+ + + " /> +
+

<%=intl._("Recommended Eepsites")%>

+
+
+
+

<%=intl._("Local Services")%>

+
+
+
+ diff --git a/apps/routerconsole/jsp/search.jsp b/apps/routerconsole/jsp/search.jsp new file mode 100644 index 0000000000..6e8ddf9b4b --- /dev/null +++ b/apps/routerconsole/jsp/search.jsp @@ -0,0 +1,34 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + +" /> +" /> +" /> + +<% + String url = searchhelper.getURL(); + if (url != null) { + response.setStatus(303, "Redirecting"); + response.setHeader("Location", url); +%> +Searching... +<% + } else { + response.setStatus(403, "Bad"); + String query = request.getParameter("query"); + if (query == null || query.trim().length() <= 0) { +%> +No search string specified! +<% + } else if (request.getParameter("engine") == null) { +%> +No search engine specified! +<% + } else { +%> +No search engines found! +<% + } + } +%> + diff --git a/apps/routerconsole/jsp/xhr1.jsi b/apps/routerconsole/jsp/xhr1.jsi new file mode 100644 index 0000000000..159d3f4d60 --- /dev/null +++ b/apps/routerconsole/jsp/xhr1.jsi @@ -0,0 +1,23 @@ + +" /> +" /> +
<%=intl._("Version")%>: + +
<%=intl._("Uptime")%>: + +

+" /> +" /> +<% + String reqURI = request.getRequestURI(); + if (reqURI != null) + reqURI = reqURI.replace("/xhr1.jsp", "/home"); + helper.setRequestURI(reqURI); +%> +

"><%=intl._("Network")%>: +

+
+ + +
+ diff --git a/apps/routerconsole/jsp/xhr1.jsp b/apps/routerconsole/jsp/xhr1.jsp new file mode 100644 index 0000000000..effeccbbea --- /dev/null +++ b/apps/routerconsole/jsp/xhr1.jsp @@ -0,0 +1,14 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> +<% + // http://www.crazysquirrel.com/computing/general/form-encoding.jspx + if (request.getCharacterEncoding() == null) + request.setCharacterEncoding("UTF-8"); + + if (request.getParameter("i2p.contextId") != null) { + session.setAttribute("i2p.contextId", request.getParameter("i2p.contextId")); + } +%> + +" /> +<%@include file="xhr1.jsi" %> diff --git a/installer/resources/themes/console/light/console.css b/installer/resources/themes/console/light/console.css index 0b795718f5..6db7c481ea 100644 --- a/installer/resources/themes/console/light/console.css +++ b/installer/resources/themes/console/light/console.css @@ -635,10 +635,22 @@ div.search { width: auto; } -img.app { - height: 48px; - width: 48px; +div.search table { + background: none; + margin: 20px; padding: 8px; + width: auto; +} + +img.app { + height: 40px; + width: 40px; + padding: 8px; +} + +img.app2p { + height: 48px; + padding: 3px 8px; } table.app {