* i2ptunnel:

- Refactor TCG to use ClientApp interface
  - Remove 'reload config' button
  - Synchronization fixes
  - Don't instantiate early, to allow router to hold
    a reference. TCG.getInstance() may now
    return null when in RouterContext.
  - Jsps display message when TCG not initialized
This commit is contained in:
zzz
2012-10-27 18:51:50 +00:00
parent 80e7ee46fb
commit 6868047ee4
6 changed files with 246 additions and 67 deletions

View File

@@ -12,6 +12,8 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.app.*;
import static net.i2p.app.ClientAppState.*;
import net.i2p.client.I2PSession; import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@@ -23,16 +25,21 @@ import net.i2p.util.OrderedProperties;
* Coordinate a set of tunnels within the JVM, loading and storing their config * Coordinate a set of tunnels within the JVM, loading and storing their config
* to disk, and building new ones as requested. * to disk, and building new ones as requested.
* *
* Warning - this is a singleton. Todo: fix * This is the entry point from clients.config.
*/ */
public class TunnelControllerGroup { public class TunnelControllerGroup implements ClientApp {
private Log _log; private final Log _log;
private static TunnelControllerGroup _instance; private volatile ClientAppState _state;
private final I2PAppContext _context;
private final ClientAppManager _mgr;
private static volatile TunnelControllerGroup _instance;
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config"; static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
private final List<TunnelController> _controllers; private final List<TunnelController> _controllers;
private String _configFile = DEFAULT_CONFIG_FILE; private final String _configFile;
private static final String REGISTERED_NAME = "i2ptunnel";
/** /**
* Map of I2PSession to a Set of TunnelController objects * Map of I2PSession to a Set of TunnelController objects
* using the session (to prevent closing the session until * using the session (to prevent closing the session until
@@ -41,48 +48,143 @@ public class TunnelControllerGroup {
*/ */
private final Map<I2PSession, Set<TunnelController>> _sessions; private final Map<I2PSession, Set<TunnelController>> _sessions;
/**
* In I2PAppContext will instantiate if necessary and always return non-null.
* As of 0.9.4, when in RouterContext, will return null
* if the TCG has not yet been started by the router.
*
* @throws IllegalArgumentException if unable to load from i2ptunnel.config
*/
public static TunnelControllerGroup getInstance() { public static TunnelControllerGroup getInstance() {
synchronized (TunnelControllerGroup.class) { synchronized (TunnelControllerGroup.class) {
if (_instance == null) if (_instance == null) {
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE); I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext()) {
_instance = new TunnelControllerGroup(ctx, null, null);
_instance.startup();
} // else wait for the router to start it
}
return _instance; return _instance;
} }
} }
private TunnelControllerGroup(String configFile) { /**
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelControllerGroup.class); * Instantiation only. Caller must call startup().
_controllers = Collections.synchronizedList(new ArrayList()); * Config file problems will not throw exception until startup().
_configFile = configFile; *
* @param mgr may be null
* @param args one arg, the config file, if not absolute will be relative to the context's config dir,
* if empty or null, the default is i2ptunnel.config
* @since 0.9.4
*/
public TunnelControllerGroup(I2PAppContext context, ClientAppManager mgr, String[] args) {
_state = UNINITIALIZED;
_context = context;
_mgr = mgr;
_log = _context.logManager().getLog(TunnelControllerGroup.class);
_controllers = new ArrayList();
if (args == null || args.length <= 0)
_configFile = DEFAULT_CONFIG_FILE;
else if (args.length == 1)
_configFile = args[0];
else
throw new IllegalArgumentException("Usage: TunnelControllerGroup [filename]");
_sessions = new HashMap(4); _sessions = new HashMap(4);
loadControllers(_configFile); synchronized (TunnelControllerGroup.class) {
I2PAppContext.getGlobalContext().addShutdownTask(new Shutdown()); if (_instance == null)
_instance = this;
}
if (_instance != this) {
_log.logAlways(Log.WARN, "New TunnelControllerGroup, now you have two");
if (_log.shouldLog(Log.WARN))
_log.warn("I did it", new Exception());
}
_state = INITIALIZED;
} }
/**
* @param args one arg, the config file, if not absolute will be relative to the context's config dir,
* if no args, the default is i2ptunnel.config
* @throws IllegalArgumentException if unable to load from config from file
*/
public static void main(String args[]) { public static void main(String args[]) {
synchronized (TunnelControllerGroup.class) { synchronized (TunnelControllerGroup.class) {
if (_instance != null) return; // already loaded through the web if (_instance != null) return; // already loaded through the web
_instance = new TunnelControllerGroup(I2PAppContext.getGlobalContext(), null, args);
if ( (args == null) || (args.length <= 0) ) { _instance.startup();
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
} else if (args.length == 1) {
_instance = new TunnelControllerGroup(args[0]);
} else {
System.err.println("Usage: TunnelControllerGroup [filename]");
return;
}
} }
} }
/**
* ClientApp interface
* @throws IllegalArgumentException if unable to load config from file
* @since 0.9.4
*/
public void startup() {
loadControllers(_configFile);
if (_mgr != null)
_mgr.register(this);
_context.addShutdownTask(new Shutdown());
}
/**
* ClientApp interface
* @since 0.9.4
*/
public ClientAppState getState() {
return _state;
}
/**
* ClientApp interface
* @since 0.9.4
*/
public String getName() {
return REGISTERED_NAME;
}
/**
* ClientApp interface
* @since 0.9.4
*/
public String getDisplayName() {
return REGISTERED_NAME;
}
/**
* @since 0.9.4
*/
private void changeState(ClientAppState state) {
changeState(state, null);
}
/**
* @since 0.9.4
*/
private synchronized void changeState(ClientAppState state, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, null, e);
}
/** /**
* Warning - destroys the singleton! * Warning - destroys the singleton!
* @since 0.8.8 * @since 0.8.8
*/ */
private static class Shutdown implements Runnable { private class Shutdown implements Runnable {
public void run() { public void run() {
shutdown(); shutdown();
} }
} }
/**
* ClientApp interface
* @since 0.9.4
*/
public void shutdown(String[] args) {
shutdown();
}
/** /**
* Warning - destroys the singleton! * Warning - destroys the singleton!
* Caller must root a new context before calling instance() or main() again. * Caller must root a new context before calling instance() or main() again.
@@ -91,28 +193,31 @@ public class TunnelControllerGroup {
* *
* @since 0.8.8 * @since 0.8.8
*/ */
public static void shutdown() { public void shutdown() {
changeState(STOPPING);
if (_mgr != null)
_mgr.unregister(this);
unloadControllers();
synchronized (TunnelControllerGroup.class) { synchronized (TunnelControllerGroup.class) {
if (_instance == null) return; if (_instance == this)
_instance.unloadControllers(); _instance = null;
_instance._log = null;
_instance = null;
} }
/// fixme static
I2PTunnelClientBase.killClientExecutor(); I2PTunnelClientBase.killClientExecutor();
changeState(STOPPED);
} }
/** /**
* Load up all of the tunnels configured in the given file (but do not start * Load up all of the tunnels configured in the given file (but do not start
* them) * them)
* *
* DEPRECATED for use outside this class. Use startup() or getInstance().
*
* @throws IllegalArgumentException if unable to load from file
*/ */
public void loadControllers(String configFile) { public synchronized void loadControllers(String configFile) {
changeState(STARTING);
Properties cfg = loadConfig(configFile); Properties cfg = loadConfig(configFile);
if (cfg == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the config from " + configFile);
return;
}
int i = 0; int i = 0;
while (true) { while (true) {
String type = cfg.getProperty("tunnel." + i + ".type"); String type = cfg.getProperty("tunnel." + i + ".type");
@@ -127,20 +232,28 @@ public class TunnelControllerGroup {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info(i + " controllers loaded from " + configFile); _log.info(i + " controllers loaded from " + configFile);
changeState(RUNNING);
} }
private class StartControllers implements Runnable { private class StartControllers implements Runnable {
public void run() { public void run() {
for (int i = 0; i < _controllers.size(); i++) { synchronized(TunnelControllerGroup.this) {
TunnelController controller = _controllers.get(i); for (int i = 0; i < _controllers.size(); i++) {
if (controller.getStartOnLoad()) TunnelController controller = _controllers.get(i);
controller.startTunnel(); if (controller.getStartOnLoad())
controller.startTunnel();
}
} }
} }
} }
/**
public void reloadControllers() { * Stop all tunnels, reload config, and restart those configured to do so.
* WARNING - Does NOT simply reload the configuration!!! This is probably not what you want.
*
* @throws IllegalArgumentException if unable to reload config file
*/
public synchronized void reloadControllers() {
unloadControllers(); unloadControllers();
loadControllers(_configFile); loadControllers(_configFile);
} }
@@ -150,7 +263,7 @@ public class TunnelControllerGroup {
* file or do other silly things) * file or do other silly things)
* *
*/ */
public void unloadControllers() { public synchronized void unloadControllers() {
stopAllControllers(); stopAllControllers();
_controllers.clear(); _controllers.clear();
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
@@ -162,14 +275,14 @@ public class TunnelControllerGroup {
* a config file or start it or anything) * a config file or start it or anything)
* *
*/ */
public void addController(TunnelController controller) { _controllers.add(controller); } public synchronized void addController(TunnelController controller) { _controllers.add(controller); }
/** /**
* Stop and remove the given tunnel * Stop and remove the given tunnel
* *
* @return list of messages from the controller as it is stopped * @return list of messages from the controller as it is stopped
*/ */
public List<String> removeController(TunnelController controller) { public synchronized List<String> removeController(TunnelController controller) {
if (controller == null) return new ArrayList(); if (controller == null) return new ArrayList();
controller.stopTunnel(); controller.stopTunnel();
List<String> msgs = controller.clearMessages(); List<String> msgs = controller.clearMessages();
@@ -183,7 +296,7 @@ public class TunnelControllerGroup {
* *
* @return list of messages the tunnels generate when stopped * @return list of messages the tunnels generate when stopped
*/ */
public List<String> stopAllControllers() { public synchronized List<String> stopAllControllers() {
List<String> msgs = new ArrayList(); List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) { for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i); TunnelController controller = _controllers.get(i);
@@ -200,7 +313,7 @@ public class TunnelControllerGroup {
* *
* @return list of messages the tunnels generate when started * @return list of messages the tunnels generate when started
*/ */
public List<String> startAllControllers() { public synchronized List<String> startAllControllers() {
List<String> msgs = new ArrayList(); List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) { for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i); TunnelController controller = _controllers.get(i);
@@ -218,7 +331,7 @@ public class TunnelControllerGroup {
* *
* @return list of messages the tunnels generate when restarted * @return list of messages the tunnels generate when restarted
*/ */
public List<String> restartAllControllers() { public synchronized List<String> restartAllControllers() {
List<String> msgs = new ArrayList(); List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) { for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i); TunnelController controller = _controllers.get(i);
@@ -235,7 +348,7 @@ public class TunnelControllerGroup {
* *
* @return list of messages the tunnels have generated * @return list of messages the tunnels have generated
*/ */
public List<String> clearAllMessages() { public synchronized List<String> clearAllMessages() {
List<String> msgs = new ArrayList(); List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) { for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i); TunnelController controller = _controllers.get(i);
@@ -257,8 +370,7 @@ public class TunnelControllerGroup {
* Save the configuration of all known tunnels to the given file * Save the configuration of all known tunnels to the given file
* *
*/ */
public void saveConfig(String configFile) throws IOException { public synchronized void saveConfig(String configFile) throws IOException {
_configFile = configFile;
File cfgFile = new File(configFile); File cfgFile = new File(configFile);
if (!cfgFile.isAbsolute()) if (!cfgFile.isAbsolute())
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile); cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
@@ -279,16 +391,17 @@ public class TunnelControllerGroup {
/** /**
* Load up the config data from the file * Load up the config data from the file
* *
* @return properties loaded or null if there was an error * @return properties loaded
* @throws IllegalArgumentException if unable to load from file
*/ */
private Properties loadConfig(String configFile) { private synchronized Properties loadConfig(String configFile) {
File cfgFile = new File(configFile); File cfgFile = new File(configFile);
if (!cfgFile.isAbsolute()) if (!cfgFile.isAbsolute())
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile); cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
if (!cfgFile.exists()) { if (!cfgFile.exists()) {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath()); _log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath());
return null; throw new IllegalArgumentException("Unable to load the controllers from " + cfgFile.getAbsolutePath());
} }
Properties props = new Properties(); Properties props = new Properties();
@@ -298,7 +411,7 @@ public class TunnelControllerGroup {
} catch (IOException ioe) { } catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe); _log.error("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
return null; throw new IllegalArgumentException("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
} }
} }
@@ -307,7 +420,9 @@ public class TunnelControllerGroup {
* *
* @return list of TunnelController objects * @return list of TunnelController objects
*/ */
public List<TunnelController> getControllers() { return _controllers; } public synchronized List<TunnelController> getControllers() {
return new ArrayList(_controllers);
}
/** /**

View File

@@ -29,8 +29,8 @@ import net.i2p.util.Addresses;
/** /**
* Ugly little accessor for the edit page * Ugly little accessor for the edit page
* *
* Warning - This class is not part of the i2ptunnel API, and at some point * Warning - This class is not part of the i2ptunnel API,
* it will be moved from the jar to the war. * it has been moved from the jar to the war.
* Usage by classes outside of i2ptunnel.war is deprecated. * Usage by classes outside of i2ptunnel.war is deprecated.
*/ */
public class EditBean extends IndexBean { public class EditBean extends IndexBean {
@@ -38,6 +38,8 @@ public class EditBean extends IndexBean {
public static boolean staticIsClient(int tunnel) { public static boolean staticIsClient(int tunnel) {
TunnelControllerGroup group = TunnelControllerGroup.getInstance(); TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return false;
List controllers = group.getControllers(); List controllers = group.getControllers();
if (controllers.size() > tunnel) { if (controllers.size() > tunnel) {
TunnelController cur = (TunnelController)controllers.get(tunnel); TunnelController cur = (TunnelController)controllers.get(tunnel);
@@ -55,6 +57,7 @@ public class EditBean extends IndexBean {
else else
return "127.0.0.1"; return "127.0.0.1";
} }
public String getTargetPort(int tunnel) { public String getTargetPort(int tunnel) {
TunnelController tun = getController(tunnel); TunnelController tun = getController(tunnel);
if (tun != null && tun.getTargetPort() != null) if (tun != null && tun.getTargetPort() != null)
@@ -62,6 +65,7 @@ public class EditBean extends IndexBean {
else else
return ""; return "";
} }
public String getSpoofedHost(int tunnel) { public String getSpoofedHost(int tunnel) {
TunnelController tun = getController(tunnel); TunnelController tun = getController(tunnel);
if (tun != null && tun.getSpoofedHost() != null) if (tun != null && tun.getSpoofedHost() != null)
@@ -69,12 +73,13 @@ public class EditBean extends IndexBean {
else else
return ""; return "";
} }
public String getPrivateKeyFile(int tunnel) { public String getPrivateKeyFile(int tunnel) {
TunnelController tun = getController(tunnel); TunnelController tun = getController(tunnel);
if (tun != null && tun.getPrivKeyFile() != null) if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile(); return tun.getPrivKeyFile();
if (tunnel < 0) if (tunnel < 0)
tunnel = _group.getControllers().size(); tunnel = _group == null ? 1 : _group.getControllers().size() + 1;
return "i2ptunnel" + tunnel + "-privKeys.dat"; return "i2ptunnel" + tunnel + "-privKeys.dat";
} }

View File

@@ -42,14 +42,15 @@ import net.i2p.util.PasswordManager;
/** /**
* Simple accessor for exposing tunnel info, but also an ugly form handler * Simple accessor for exposing tunnel info, but also an ugly form handler
* *
* Warning - This class is not part of the i2ptunnel API, and at some point * Warning - This class is not part of the i2ptunnel API,
* it will be moved from the jar to the war. * it has been moved from the jar to the war.
* Usage by classes outside of i2ptunnel.war is deprecated. * Usage by classes outside of i2ptunnel.war is deprecated.
*/ */
public class IndexBean { public class IndexBean {
protected final I2PAppContext _context; protected final I2PAppContext _context;
protected final Log _log; protected final Log _log;
protected final TunnelControllerGroup _group; protected final TunnelControllerGroup _group;
private final String _fatalError;
private String _action; private String _action;
private int _tunnel; private int _tunnel;
//private long _prevNonce; //private long _prevNonce;
@@ -110,7 +111,18 @@ public class IndexBean {
public IndexBean() { public IndexBean() {
_context = I2PAppContext.getGlobalContext(); _context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(IndexBean.class); _log = _context.logManager().getLog(IndexBean.class);
_group = TunnelControllerGroup.getInstance(); TunnelControllerGroup tcg;
String error;
try {
tcg = TunnelControllerGroup.getInstance();
error = tcg == null ? _("Tunnels are not initialized yet, please reload in two minutes.")
: null;
} catch (IllegalArgumentException iae) {
tcg = null;
error = iae.toString();
}
_group = tcg;
_fatalError = error;
_tunnel = -1; _tunnel = -1;
_curNonce = "-1"; _curNonce = "-1";
addNonce(); addNonce();
@@ -118,6 +130,13 @@ public class IndexBean {
_otherOptions = new ConcurrentHashMap(4); _otherOptions = new ConcurrentHashMap(4);
} }
/**
* @since 0.9.4
*/
public boolean isInitialized() {
return _group != null;
}
public static String getNextNonce() { public static String getNextNonce() {
synchronized (_nonces) { synchronized (_nonces) {
return _nonces.get(0); return _nonces.get(0);
@@ -164,6 +183,8 @@ public class IndexBean {
private String processAction() { private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action))) if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action)))
return ""; return "";
if (_group == null)
return "Error - tunnels are not initialized yet";
// If passwords are turned on, all is assumed good // If passwords are turned on, all is assumed good
if (!_context.getBooleanProperty(PROP_PW_ENABLE) && if (!_context.getBooleanProperty(PROP_PW_ENABLE) &&
!haveNonce(_curNonce)) !haveNonce(_curNonce))
@@ -197,27 +218,27 @@ public class IndexBean {
else else
return "Action " + _action + " unknown"; return "Action " + _action + " unknown";
} }
private String stopAll() { private String stopAll() {
if (_group == null) return "";
List<String> msgs = _group.stopAllControllers(); List<String> msgs = _group.stopAllControllers();
return getMessages(msgs); return getMessages(msgs);
} }
private String startAll() { private String startAll() {
if (_group == null) return "";
List<String> msgs = _group.startAllControllers(); List<String> msgs = _group.startAllControllers();
return getMessages(msgs); return getMessages(msgs);
} }
private String restartAll() { private String restartAll() {
if (_group == null) return "";
List<String> msgs = _group.restartAllControllers(); List<String> msgs = _group.restartAllControllers();
return getMessages(msgs); return getMessages(msgs);
} }
private String reloadConfig() { private String reloadConfig() {
if (_group == null) return "";
_group.reloadControllers(); _group.reloadControllers();
return _("Configuration reloaded for all tunnels"); return _("Configuration reloaded for all tunnels");
} }
private String start() { private String start() {
if (_tunnel < 0) return "Invalid tunnel"; if (_tunnel < 0) return "Invalid tunnel";
@@ -372,7 +393,7 @@ public class IndexBean {
*/ */
public String getMessages() { public String getMessages() {
if (_group == null) if (_group == null)
return ""; return _fatalError;
StringBuilder buf = new StringBuilder(512); StringBuilder buf = new StringBuilder(512);
if (_action != null) { if (_action != null) {

View File

@@ -31,7 +31,11 @@
<body id="tunnelEditPage"> <body id="tunnelEditPage">
<div id="pageHeader"> <div id="pageHeader">
</div> </div>
<%
if (editBean.isInitialized()) {
%>
<form method="post" action="list"> <form method="post" action="list">
<div id="tunnelEditPanel" class="panel"> <div id="tunnelEditPanel" class="panel">
@@ -508,5 +512,12 @@
</form> </form>
<div id="pageFooter"> <div id="pageFooter">
</div> </div>
<%
} else {
%>Tunnels are not initialized yet, please reload in two minutes.<%
} // isInitialized()
%>
</body> </body>
</html> </html>

View File

@@ -31,7 +31,11 @@
<body id="tunnelEditPage"> <body id="tunnelEditPage">
<div id="pageHeader"> <div id="pageHeader">
</div> </div>
<%
if (editBean.isInitialized()) {
%>
<form method="post" action="list"> <form method="post" action="list">
<div id="tunnelEditPanel" class="panel"> <div id="tunnelEditPanel" class="panel">
@@ -518,5 +522,12 @@
</form> </form>
<div id="pageFooter"> <div id="pageFooter">
</div> </div>
<%
} else {
%>Tunnels are not initialized yet, please reload in two minutes.<%
} // isInitialized()
%>
</body> </body>
</html> </html>

View File

@@ -55,12 +55,23 @@
</div> </div>
</div> </div>
</div> </div>
<%
if (indexBean.isInitialized()) {
%>
<div id="globalOperationsPanel" class="panel"> <div id="globalOperationsPanel" class="panel">
<div class="header"></div> <div class="header"></div>
<div class="footer"> <div class="footer">
<div class="toolbox"> <div class="toolbox">
<a class="control" href="wizard"><%=intl._("Tunnel Wizard")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Stop%20all"><%=intl._("Stop All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Start%20all"><%=intl._("Start All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Restart%20all"><%=intl._("Restart All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Reload%20configuration"><%=intl._("Reload Config")%></a> <a class="control" href="wizard"><%=intl._("Tunnel Wizard")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Stop%20all"><%=intl._("Stop All")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Start%20all"><%=intl._("Start All")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Restart%20all"><%=intl._("Restart All")%></a>
<%--
//this is really bad because it stops and restarts all tunnels, which is probably not what you want
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Reload%20configuration"><%=intl._("Reload Config")%></a>
--%>
</div> </div>
</div> </div>
</div> </div>
@@ -327,6 +338,11 @@
</form> </form>
</div> </div>
</div> </div>
<%
} // isInitialized()
%>
<div id="pageFooter"> <div id="pageFooter">
</div> </div>
</body> </body>