FELIX-1988 Apply 12.bundles_plugin.patch by Valentin Valchev (thanks)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@911439 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index 007a6e6..2a1df16 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -17,42 +17,70 @@
 package org.apache.felix.webconsole.internal.core;
 
 
-import java.io.*;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
 import java.text.MessageFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.bundlerepository.*;
+import org.apache.felix.bundlerepository.R4Attribute;
+import org.apache.felix.bundlerepository.R4Export;
+import org.apache.felix.bundlerepository.R4Import;
+import org.apache.felix.bundlerepository.R4Package;
 import org.apache.felix.webconsole.ConfigurationPrinter;
+import org.apache.felix.webconsole.DefaultVariableResolver;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
-import org.apache.felix.webconsole.internal.*;
 import org.apache.felix.webconsole.internal.Logger;
+import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
-import org.json.*;
-import org.osgi.framework.*;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONWriter;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.component.ComponentConstants;
-import org.osgi.service.log.LogService;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
 import org.osgi.service.startlevel.StartLevel;
 
 
 /**
- * The <code>BundlesServlet</code> TODO
+ * The <code>BundlesServlet</code> provides the bundles plugins, used to display
+ * the list of bundles, installed on the framework. It also adds ability to control
+ * the lifecycle of the bundles, like start, stop, uninstall, install.
  */
-public class BundlesServlet extends BaseWebConsolePlugin implements ConfigurationPrinter
+public class BundlesServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin, ConfigurationPrinter
 {
 
+    /** the label of the bundles plugin - used by other plugins to reference to plugin details */
     public static final String NAME = "bundles";
-
-    public static final String LABEL = "Bundles";
-
-    public static final String BUNDLE_ID = "bundleId";
+    private static final String TITLE = "Bundles";
+    private static final String CSS[] = { "/res/ui/bundles.css" };
 
     // bootdelegation property entries. wildcards are converted to package
     // name prefixes. whether an entry is a wildcard or not is set as a flag
@@ -66,7 +94,24 @@
     private boolean[] bootPkgWildcards;
 
     private ServiceRegistration configurationPrinter;
+    
+    // templates
+    private final String TEMPLATE_MAIN;
+    private final String TEMPLATE_UPLOAD;
 
+    /** Default constructor */
+    public BundlesServlet()
+    {
+        super(NAME, TITLE, CSS);
+
+        // load templates
+        TEMPLATE_MAIN = readTemplateFile( "/templates/bundles.html" ); 
+        TEMPLATE_UPLOAD = readTemplateFile( "/templates/bundles_upload.html" ); 
+    }
+
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#activate(org.osgi.framework.BundleContext)
+     */
     public void activate( BundleContext bundleContext )
     {
         super.activate( bundleContext );
@@ -92,6 +137,9 @@
     }
 
 
+    /**
+     * @see org.apache.felix.webconsole.SimpleWebConsolePlugin#deactivate()
+     */
     public void deactivate()
     {
         if ( configurationPrinter != null )
@@ -104,20 +152,11 @@
     }
 
 
-    public String getLabel()
-    {
-        return NAME;
-    }
-
-
-    public String getTitle()
-    {
-        return LABEL;
-    }
-
-
     //---------- ConfigurationPrinter
 
+    /**
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
     public void printConfiguration( PrintWriter pw )
     {
         try
@@ -175,13 +214,16 @@
         }
         catch ( Exception e )
         {
-            getLog().log( LogService.LOG_ERROR, "Problem rendering Bundle details for configuration status", e );
+            log( "Problem rendering Bundle details for configuration status", e );
         }
     }
 
 
     //---------- BaseWebConsolePlugin
 
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
         IOException
     {
@@ -209,6 +251,9 @@
         super.doGet( request, response );
     }
 
+    /**
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
     {
         final RequestInfo reqInfo = new RequestInfo(req);
@@ -239,7 +284,7 @@
                 }
                 catch ( BundleException be )
                 {
-                    getLog().log( LogService.LOG_ERROR, "Cannot start", be );
+                    log( "Cannot start", be );
                 }
             }
             else if ( "stop".equals( action ) )
@@ -252,7 +297,7 @@
                 }
                 catch ( BundleException be )
                 {
-                    getLog().log( LogService.LOG_ERROR, "Cannot stop", be );
+                    log( "Cannot stop", be );
                 }
             }
             else if ( "refresh".equals( action ) )
@@ -278,7 +323,7 @@
                 }
                 catch ( BundleException be )
                 {
-                    getLog().log( LogService.LOG_ERROR, "Cannot uninstall", be );
+                    log( "Cannot uninstall", be );
                 }
             }
         }
@@ -314,7 +359,7 @@
             "/" + ServicesServlet.LABEL + "/";
     }
 
-    private Bundle getBundle( String pathInfo )
+    Bundle getBundle( String pathInfo )
     {
         // only use last part of the pathInfo
         pathInfo = pathInfo.substring( pathInfo.lastIndexOf( '/' ) + 1 );
@@ -373,53 +418,39 @@
         buf.append(msg);
     }
 
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo(request);
-        final PrintWriter pw = response.getWriter();
 
-        final String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
+        final int startLevel = getStartLevel().getInitialBundleStartLevel();
 
-        Util.startScript( pw );
-        pw.println( "var imgRoot = '" + appRoot + "/res/imgs';");
-        pw.println( "var startLevel = " + getStartLevel().getInitialBundleStartLevel() + ";");
-        pw.println( "var drawDetails = " + reqInfo.bundleRequested + ";");
-        pw.println( "var currentBundle = " + (reqInfo.bundleRequested && reqInfo.bundle != null ? String.valueOf(reqInfo.bundle.getBundleId()) : "null") + ";");
-        Util.endScript( pw );
-
-        Util.script(pw, appRoot, "bundles.js");
+        // prepare variables
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        vars.put( "startLevel", String.valueOf(startLevel));
+        vars.put( "drawDetails", reqInfo.bundleRequested ? Boolean.TRUE : Boolean.FALSE );
+        vars.put( "currentBundle", (reqInfo.bundleRequested && reqInfo.bundle != null ? String.valueOf(reqInfo.bundle.getBundleId()) : "null"));
 
         if ( "upload".equals(reqInfo.pathInfo) )
         {
-            renderUploadForm(pw, appRoot);
+            response.getWriter().print(TEMPLATE_UPLOAD);
         }
         else
         {
-            pw.println( "<div id='plugin_content'/>");
-            Util.startScript( pw );
-            pw.print( "renderBundles(");
             final String pluginRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
             final String servicesRoot = getServicesRoot ( request );
-            writeJSON(pw, reqInfo.bundle, pluginRoot, servicesRoot );
-            pw.println(");" );
-            Util.endScript( pw );
+            StringWriter w = new StringWriter();
+            PrintWriter w2 = new PrintWriter(w);
+            writeJSON(w2, reqInfo.bundle, pluginRoot, servicesRoot );
+            vars.put( "__bundles__", w.toString());
+            
+            response.getWriter().print(TEMPLATE_MAIN);
         }
     }
 
-    private void renderUploadForm( final PrintWriter pw, final String appRoot ) throws IOException
-    {
-        Util.script(pw, appRoot, "jquery.multifile-1.4.6.min.js");
-        pw.println(" <div id='plugin_content'><div class='contentheader'>Upload / Install Bundles</div>");
-        pw.println( "<form method='post' enctype='multipart/form-data' action='../'>");
-        pw.println( "<input type='hidden' name='action' value='install'/>");
-        pw.println( "<div class='contentline'><div class='contentleft'>Start Bundle</div><div class='contentright'><input class='checkradio' type='checkbox' name='bundlestart' value='start'/></div></div>");
-        pw.println( "<div class='contentline'><div class='contentleft'>Start Level</div><div class='contentright'><input class='input' type='input' name='bundlestartlevel' value='" + getStartLevel().getInitialBundleStartLevel() + "' size='4'/></div></div>");
-        pw.println( "<div class='contentline'><input class='fileinput multi' accept='jar' type='file' name='bundlefile'/></div>");
-        pw.println( "<div class='contentline'><input type='submit' value='Install or Update'/></div>");
-        pw.println( "</form></div");
-    }
-
     private void renderJSON( final HttpServletResponse response, final Bundle bundle, final String pluginRoot, final String servicesRoot )
         throws IOException
     {
@@ -442,7 +473,8 @@
         final String servicesRoot, final boolean fullDetails ) throws IOException
     {
         final Bundle[] allBundles = this.getBundles();
-        final String statusLine = this.getStatusLine(allBundles);
+        final Object[] status = getStatusLine(allBundles);
+        final String statusLine = (String) status[5];
         final Bundle[] bundles = ( bundle != null ) ? new Bundle[]
             { bundle } : allBundles;
         Util.sort( bundles );
@@ -455,6 +487,12 @@
 
             jw.key( "status" );
             jw.value( statusLine );
+            
+            // add raw status
+            jw.key( "s" );
+            jw.array();
+            for ( int i = 0; i < 5; i++ ) jw.value(status[i]); 
+            jw.endArray();
 
             jw.key( "data" );
 
@@ -477,8 +515,9 @@
 
     }
 
-    private String getStatusLine(final Bundle[] bundles)
+    private Object[] getStatusLine(final Bundle[] bundles)
     {
+        Object[] ret = new Object[6];
         int active = 0, installed = 0, resolved = 0, fragments = 0;
         for ( int i = 0; i < bundles.length; i++ )
         {
@@ -534,7 +573,13 @@
             }
             buffer.append('.');
         }
-        return buffer.toString();
+        ret[0] = new Integer(bundles.length);
+        ret[1] = new Integer(active);
+        ret[2] = new Integer(fragments);
+        ret[3] = new Integer(resolved);
+        ret[4] = new Integer(installed);
+        ret[5] = buffer.toString();
+        return ret;
     }
 
     private void bundleInfo( JSONWriter jw, Bundle bundle, boolean details, final String pluginRoot, final String servicesRoot )
@@ -580,7 +625,7 @@
     }
 
 
-    protected Bundle[] getBundles()
+    private final Bundle[] getBundles()
     {
         return getBundleContext().getBundles();
     }
@@ -622,12 +667,12 @@
         jw.endObject();
     }
 
-    private boolean isFragmentBundle( Bundle bundle)
+    private final boolean isFragmentBundle( Bundle bundle)
     {
         return getPackageAdmin().getBundleType( bundle ) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
     }
 
-    private boolean hasStart( Bundle bundle )
+    private final boolean hasStart( Bundle bundle )
     {
         if ( isFragmentBundle(bundle) )
         {
@@ -637,7 +682,7 @@
     }
 
 
-    private boolean hasStop( Bundle bundle )
+    private final boolean hasStop( Bundle bundle )
     {
         if ( isFragmentBundle(bundle) )
         {
@@ -647,7 +692,7 @@
     }
 
 
-    private boolean hasUninstall( Bundle bundle )
+    private static final boolean hasUninstall( Bundle bundle )
     {
         return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED
             || bundle.getState() == Bundle.ACTIVE;
@@ -655,7 +700,7 @@
     }
 
 
-    private void bundleDetails( JSONWriter jw, Bundle bundle, final String pluginRoot, final String servicesRoot)
+    private final void bundleDetails( JSONWriter jw, Bundle bundle, final String pluginRoot, final String servicesRoot)
         throws JSONException
     {
         Dictionary headers = bundle.getHeaders();
@@ -700,7 +745,7 @@
     }
 
 
-    private Integer getStartLevel( Bundle bundle )
+    private final Integer getStartLevel( Bundle bundle )
     {
         StartLevel sl = getStartLevel();
         return ( sl != null ) ? new Integer( sl.getBundleStartLevel( bundle ) ) : null;
@@ -754,7 +799,7 @@
         }
         else
         {
-            WebConsoleUtil.keyVal( jw, "Exported Packages", "None" );
+            WebConsoleUtil.keyVal( jw, "Exported Packages", "---" );
         }
 
         exports = packageAdmin.getExportedPackages( ( Bundle ) null );
@@ -859,7 +904,7 @@
             }
             else
             {
-                WebConsoleUtil.keyVal( jw, "Exported Packages", "None" );
+                WebConsoleUtil.keyVal( jw, "Exported Packages", "---" );
             }
         }
 
@@ -924,7 +969,7 @@
                 else
                 {
                     // add description if there are no imports
-                    val.put( "None" );
+                    val.put( "---" );
                 }
 
                 WebConsoleUtil.keyVal( jw, "Imported Packages", val );
@@ -1296,8 +1341,30 @@
 
     }
 
-    public static RequestInfo getRequestInfo(final HttpServletRequest request)
+    static final RequestInfo getRequestInfo(final HttpServletRequest request)
     {
-        return (RequestInfo)request.getAttribute(BundlesServlet.class.getName());
+        return (RequestInfo)request.getAttribute( BundlesServlet.class.getName() );
+    }
+    
+    private final PackageAdmin getPackageAdmin()
+    {
+        return ( PackageAdmin ) getService( PackageAdmin.class.getName() );
+    }
+    
+    private final StartLevel getStartLevel()
+    {
+        return ( StartLevel ) getService( StartLevel.class.getName() );
+    }
+    
+    // TODO: may remove later, when BaseWebConsolePlugin is made to extend SimpleWebConsolePlugin
+    private Logger log;
+    Logger getLog()
+    {
+        if ( log == null )
+        {
+            log = new Logger( getBundleContext() );
+        }
+
+        return log;
     }
 }
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index b89c29f..20de138 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -35,7 +35,11 @@
 bundle=Bundle
 version=Version
 help=Help
-
+start=Start
+stop=Stop
+save=Save
+reset=Reset
+delete=Delete
 
 # VMStat plugin
 vmstat.stopped=Framework has been stopped.
@@ -91,4 +95,73 @@
 
 # Shell plugin
 shell.clear=Clear
-shell.status=Use the command prompt to execute shell commands.
\ No newline at end of file
+shell.status=Use the command prompt to execute shell commands.
+
+# Bundles plugin
+bundles.statline=Bundle information: {0} bundles in total, {1} bundles active, {2} active fragments, {3} bundles resolved, {4} bundles installed.
+bundles.install_or_update=Install or Update
+bundles.install_update=Install/Update...
+bundles.refreshPkg=Refresh Packages
+bundles.name=Name
+bundles.name.symb=Symbolic Name
+bundles.status=Status
+bundles.actions=Actions
+# bundle details
+bundles.location=Bundle Location
+bundles.lastMod=Last Modification
+bundles.doc=Bundle Documentation
+bundles.vendor=Vendor
+bundles.copyright=Copyright
+bundles.description=Description
+bundles.startlevel=Start Level
+bundles.classpath=Bundle Classpath
+bundles.pkg.exported=Exported Packages
+bundles.pkg.imported=Imported Packages
+bundles.pkg.importingBundles=Importing Bundles
+bundles.manifest.headers=Manifest Headers
+bundles.hosts=Host Bundles
+bundles.framents=Fragments Attached
+# actions
+bundles.update=Update
+bundles.uninstall=Uninstall
+bundles.refreshImports=Refresh Package Imports
+# upload form
+bundles.upload.caption=Upload / Install Bundles
+bundles.upload.start=Start Bundle
+bundles.upload.level=Start Level
+
+
+# Components plugin
+scr.status.no_service=Apache Felix Declarative Service required for this function!
+scr.status.no_components=No components installed currently!
+scr.status.ok=Number of installed components: {0}
+scr.action.enable=Enable
+scr.action.disable=Disable
+scr.action.configure=Configure
+scr.prop.bundle=Bundle
+scr.prop.defstate=Default State
+scr.prop.activation=Activation
+scr.prop.properties=Properties
+scr.serv.type=Service Type
+scr.serv=Services
+scr.title.actions=Actions
+scr.title.status=Status
+scr.title.name=Name
+
+
+# Configuration plugin
+config.status.ok=Configuration Admin Service is running.
+config.status.missing=Configuration Admin Service is not installed/running.
+config.properties=Properties
+config.properties.enter=Enter Name-Value pairs of configuration properties
+config.info.title=Configuration Information
+config.info.pid=Persistent Identity (PID)
+config.info.fpid=Factory Persistent Identifier (Factory PID)
+config.info.binding=Configuration Binding
+config.info.unbound=Unbound or new configuration 
+config.unbind.btn=Unbind
+config.unbind.tip=Unbind Configuration from Bundle
+config.del.ask=Are you sure to delete this configuration ?
+config.del.config=Configuration: 
+config.del.bundle=Bundle: 
+config.unbind.ask=Are you sure to unbind this configuration ?
diff --git a/webconsole/src/main/resources/res/lib/support.js b/webconsole/src/main/resources/res/lib/support.js
index 86e7620..427d0db 100644
--- a/webconsole/src/main/resources/res/lib/support.js
+++ b/webconsole/src/main/resources/res/lib/support.js
@@ -81,12 +81,8 @@
 });
 
 /* A helper function, used together with tablesorter, when the cells contains mixed text and links. As example:
-
 	elem.tablesorter({
-		headers: {
-			0: {textExtraction: mixedLinksExtraction},
-			2: {sorter: false}
-		}
+		textExtraction: mixedLinksExtraction
 	});
 */
 function mixedLinksExtraction(node) {
diff --git a/webconsole/src/main/resources/res/ui/bundles.css b/webconsole/src/main/resources/res/ui/bundles.css
new file mode 100644
index 0000000..5824bd6
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/bundles.css
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#optionalData {
+	margin: 0;
+	padding: 0;
+}
+
+ul.icons {margin: 0; padding: 0;}
+ul.icons li {margin: 1px; position: relative; padding: 1px 0; cursor: pointer; float: left;  list-style: none;}
+ul.icons span.ui-icon {float: left; margin: 0 1px;}
+
+.col_Status   { width: 50px;  }
+.col_Actions { width: 121px; }
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
index cef5de4..2660b74 100644
--- a/webconsole/src/main/resources/res/ui/bundles.js
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -14,72 +14,57 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-function renderStatusLine() {
-	$("#plugin_content").append( "<div class='fullwidth'><div class='statusline'/></div>" );
-}
-
-function renderView( /* Array of String */ columns, /* String */ buttons ) {
-    renderStatusLine();
-    renderButtons(buttons);
-    var txt = "<div class='table'><table id='plugin_table' class='tablelayout'><thead><tr>";
-    for ( var name in columns ) {
-    	txt = txt + "<th class='col_" + columns[name] + "'>" + columns[name] + "</th>";
-    }
-    txt = txt + "</tr></thead><tbody></tbody></table></div>";
-    $("#plugin_content").append( txt );
-    renderButtons(buttons);
-    renderStatusLine();	
-}
-
-function renderButtons( buttons ) {
-	$("#plugin_content").append( "<form method='post' enctype='multipart/form-data'><div class='fullwidth'><div class='buttons'>" +
-	                             buttons + "</div></div></form>" );
-}
 
 function renderData( eventData )  {
-	$(".statusline").empty().append(eventData.status);
-	$("#plugin_table > tbody > tr").remove();
+	var s = eventData.s;
+    $(".statline").html(i18n.statline.msgFormat(s[0], s[1], s[2], s[3], s[4]));
+    $("#plugin_table > tbody > tr").remove();
     for ( var idx in eventData.data ) {
-    	if ( currentBundle == null || !drawDetails || currentBundle == eventData.data[idx].id) {
+        if ( currentBundle == null || !drawDetails || currentBundle == eventData.data[idx].id) {
             entry( eventData.data[idx] );
-    	}
+        }
     }
-    $("#plugin_table").trigger("update");
     if ( drawDetails && eventData.data.length == 1 ) {
-	    renderDetails(eventData.data[0]);    
+        renderDetails(eventData.data[0]);    
     } else if ( currentBundle != null ) {
-    	var id = currentBundle;
-    	hideDetails(id);
-    	showDetails(id);
+        var id = currentBundle;
+        hideDetails(id);
+        showDetails(id);
     }
+    initStaticWidgets();
 }
 
 function entry( /* Object */ dataEntry ) {
     var trElement = tr( null, { id: "entry" + dataEntry.id } );
     entryInternal( trElement,  dataEntry );
-	$("#plugin_table > tbody").append(trElement);	
+    $("#plugin_table > tbody").append(trElement);   
 }
 
 function actionButton( /* Element */ parent, /* string */ id, /* Obj */ action ) {
-	if ( !action.enabled ) {
-		return;
-	}
-	var enabled = action.enabled;
-	var op = action.link;
-	var opLabel = action.name;
-	var img = action.image;
-	
-	var input = createElement( "input", null, {
-            type: 'image',
-            style: {"margin-left": "10px"},
-            title: opLabel,
-            alt: opLabel,
-            src: imgRoot + '/bundle_' + img + '.png'
-        });
-	$(input).click(function() {changeDataEntryState(id, op)});
-		
+    if ( !action.enabled ) {
+        return;
+    }
+    var enabled = action.enabled;
+    var op = action.link;
+    var opLabel = action.name;
+    var img = action.image;
+    // fixup JQuery UI icons
+    if(img == "start" ) img = "play";
+    if(img == "update") img = "transferthick-e-w";
+    if(img == "delete") img = "trash";
+
+	// apply i18n
+	opLabel = i18n[opLabel] ? i18n[opLabel] : opLabel;
+    
+    var input = createElement('li', 'dynhover', {
+        title: opLabel
+    });
+    $(input)
+        .html('<span class="ui-icon ui-icon-'+img+'"></span>')
+        .click(function() {changeDataEntryState(id, op)});
+
     if (!enabled) {
-    	$(input).attr("disabled", true);
+        $(input).attr("disabled", true).addClass("ui-state-disabled");
     }
     parent.appendChild( input );
 }
@@ -88,23 +73,20 @@
     var id = dataEntry.id;
     var name = dataEntry.name;
     var state = dataEntry.state;
-    
-    var inputElement = createElement("img", "rightButton", {
-    	src: appRoot + "/res/imgs/arrow_right.png",
-    	style: {border: "none"},
-    	id: 'img' + id,
-    	title: "Details",
-    	alt: "Details",
-    	width: 14,
-    	height: 14
+
+    // right arrow
+    var inputElement = createElement('span', 'ui-icon ui-icon-triangle-1-e', {
+        title: "Details",
+        id: 'img' + id,
+        style: {display: "inline-block"}
     });
     $(inputElement).click(function() {showDetails(id)});
     var titleElement;
     if ( drawDetails ) {
-    	titleElement = text(name);
+        titleElement = text(name);
     } else {
         titleElement = createElement ("a", null, {
-    	    href: window.location.pathname + "/" + id
+            href: window.location.pathname + "/" + id
         });
         titleElement.appendChild(text(name));
     }
@@ -115,147 +97,126 @@
     parent.appendChild( td( null, null, [ text( dataEntry.symbolicName ) ] ) );
     parent.appendChild( td( null, null, [ text( state ) ] ) );
     var actionsTd = td( null, null );
-    var div = createElement("div", null, {
-    	style: { "text-align" : "left"}
-    });
+    var div = createElement('ul', 'icons ui-widget');
     actionsTd.appendChild(div);
     
     for ( var a in dataEntry.actions ) {
-    	actionButton( div, id, dataEntry.actions[a] );
+        actionButton( div, id, dataEntry.actions[a] );
     }
     parent.appendChild( actionsTd );
 }
 
 function loadData() {
-	$.get(pluginRoot + "/.json", null, function(data) {
-	    renderData(data);
-	}, "json");	
+    $.get(pluginRoot + "/.json", null, function(data) {
+        renderData(data);
+    }, "json"); 
 }
 
 function changeDataEntryState(/* long */ id, /* String */ action) {
-	$.post(pluginRoot + "/" + id, {"action":action}, function(data) {
-	    renderData(data);
-	}, "json");	
+    $.post(pluginRoot + "/" + id, {"action":action}, function(data) {
+        renderData(data);
+    }, "json"); 
 }
 
 function refreshPackages() {
-	$.post(window.location.pathname, {"action": "refreshPackages"}, function(data) {
-	    renderData(data);
-	}, "json");	
+    $.post(window.location.pathname, {"action": "refreshPackages"}, function(data) {
+        renderData(data);
+    }, "json"); 
 }
 
 function showDetails( id ) {
-	currentBundle = id;
+    currentBundle = id;
     $.get(pluginRoot + "/" + id + ".json", null, function(data) {
-    	renderDetails(data.data[0]);
+        renderDetails(data.data[0]);
     }, "json");
 }
 
 function hideDetails( id ) {
-	currentBundle = null;
-	$("#img" + id).each(function() {
-		$("#pluginInlineDetails" + id).remove();
-		$(this).attr("src", appRoot + "/res/imgs/arrow_right.png");
-		$(this).attr("title", "Details");
-		$(this).attr("alt", "Details");
-		$(this).unbind('click').click(function() {showDetails(id)});
-	});
+    currentBundle = null;
+    $("#img" + id).each(function() {
+        $("#pluginInlineDetails" + id).remove();
+        $(this).
+            removeClass('ui-icon-triangle-1-w').//left
+            removeClass('ui-icon-triangle-1-s').//down
+            addClass('ui-icon-triangle-1-e').//right
+            attr("title", "Details").
+            unbind('click').click(function() {showDetails(id)});
+    });
 }
 
 function renderDetails( data ) {
-	$("#entry" + data.id + " > td").eq(1).append("<div id='pluginInlineDetails"  + data.id + "'/>");
-	$("#img" + data.id).each(function() {
-		if ( drawDetails ) {
-			$(this).attr("src", appRoot + "/res/imgs/arrow_left.png");
-			$(this).attr("title", "Back");
-			$(this).attr("alt", "Back");
-    	    var ref = window.location.pathname;
-    	    ref = ref.substring(0, ref.lastIndexOf('/'));
-			$(this).unbind('click').click(function() {window.location = ref;});
-		} else {
-			$(this).attr("src", appRoot + "/res/imgs/arrow_down.png");
-			$(this).attr("title", "Hide Details");
-			$(this).attr("alt", "Hide Details");
-			$(this).unbind('click').click(function() {hideDetails(data.id)});
-		}
-	});
-	$("#pluginInlineDetails" + data.id).append("<table border='0'><tbody></tbody></table>");
+    $("#entry" + data.id + " > td").eq(1).append("<div id='pluginInlineDetails"  + data.id + "'/>");
+    $("#img" + data.id).each(function() {
+        if ( drawDetails ) {
+            var ref = window.location.pathname;
+            ref = ref.substring(0, ref.lastIndexOf('/'));
+            $(this).
+                removeClass('ui-icon-triangle-1-e').//right
+                removeClass('ui-icon-triangle-1-s').//down
+                addClass('ui-icon-triangle-1-w').//left
+                attr("title", "Back").
+                unbind('click').click(function() {window.location = ref});
+        } else {
+            $(this).
+                removeClass('ui-icon-triangle-1-w').//left
+                removeClass('ui-icon-triangle-1-e').//right
+                addClass('ui-icon-triangle-1-s').//down
+                attr("title", "Hide Details").
+                unbind('click').click(function() {hideDetails(data.id)});
+        }
+    });
+    $("#pluginInlineDetails" + data.id).append("<table border='0'><tbody></tbody></table>");
     var details = data.props;
     for (var idx in details) {
         var prop = details[idx];
-        
-        var txt = "<tr><td class='aligntop' noWrap='true' style='border:0px none'>" + prop.key + "</td><td class='aligntop' style='border:0px none'>";	        
+		var key = i18n[prop.key] ? i18n[prop.key] : prop.key;
+
+        var txt = "<tr><td class='aligntop' noWrap='true' style='border:0px none'>" + key + "</td><td class='aligntop' style='border:0px none'>";          
         if (prop.value) {
-        	if ( prop.key == 'Bundle Documentation' )  {
-        		txt = txt + "<a href='" + prop.value + "' target='_blank'>" + prop.value + "</a>";
-        	} else  {
-        		if ( $.isArray(prop.value) ) {
-	        		var i = 0;
-	        		for(var pi in prop.value) {
-	        			var value = prop.value[pi];
-		                if (i > 0) { txt = txt + "<br/>"; }
-		                var span;
-		                if (value.substring(0, 6) == "INFO: ") {
-		                	txt = txt + "<span style='color: grey;'>!!" + value.substring(5) + "</span>";
-		                } else if (value.substring(0, 7) == "ERROR: ") {
-		                	txt = txt + "<span style='color: red;'>!!" + value.substring(6) + "</span>";
-		                } else {
-		                	txt = txt + value;
-		                }
-		                i++;
-	        		}
-        		} else {
-        			txt = txt + prop.value;
-        		}
-        	}
+            if ( prop.key == 'Bundle Documentation' )  {
+                txt = txt + "<a href='" + prop.value + "' target='_blank'>" + prop.value + "</a>";
+            } else  {
+                if ( $.isArray(prop.value) ) {
+                    var i = 0;
+                    for(var pi in prop.value) {
+                        var value = prop.value[pi];
+                        if (i > 0) { txt = txt + "<br/>"; }
+                        txt = txt + value;
+                        i++;
+                    }
+                } else {
+                    txt = txt + prop.value;
+                }
+            }
         } else {
-        	txt = txt + "\u00a0";
+            txt = txt + "\u00a0";
         }
         txt = txt + "</td></tr>";
         $("#pluginInlineDetails" + data.id + " > table > tbody").append(txt);
-	}
+    }
 }
 
-function renderBundles(data) {
-	$(document).ready(function(){
-    	renderView( ["Id", "Name", "Version", "Symbolic Name", "Status", "Actions"],
-        		"<input type='hidden' name='action' value='install'/>" +
-        		"<input type='hidden' name='bundlestart' value='start'/>" +
-        		"<input type='hidden' name='bundlestartlevel' value='" + startLevel + "'/>" +
-                "<input class='fileinput' type='file' name='bundlefile' style='margin-left:10px'/>" +
-         		"<input type='submit' value='Install or Update' style='margin-left:10px'/>" +
-         		"<button class='reloadButton' type='button' name='reload' style='margin-left:60px'>Reload</button>" +
-         		"<button class='installButton' type='button' name='install'>Install/Update...</button>" +
-         		"<button class='refreshPackages' type='button' name='refresh'>Refresh Packages</button>"
-        		 );
-        $(".refreshPackages").click(refreshPackages);
-	    $(".reloadButton").click(loadData);
-	    $(".installButton").click(function() {
-	    	document.location = pluginRoot + "/upload";
-	    });
-        renderData(data);
-        
-        var extractMethod = function(node) {
-        	var link = node.getElementsByTagName("a");
-            if ( link && link.length == 1 ) {
-            	return link[0].innerHTML;
-            }
-            return node.innerHTML;
-        };
-        // check for cookie
-        var cv = $.cookies.get("webconsolebundlelist");
-        var lo = (cv ? cv.split(",") : [1,0]);
-        $("#plugin_table").tablesorter({
-            headers: {
-        	    0: { sorter:"digit"},
-                5: { sorter: false }
-            },
-            sortList: [lo],
-            textExtraction:extractMethod 
-        });
-        $("#plugin_table").bind("sortEnd", function() {
-        	var table = $("#plugin_table").eq(0).attr("config");
-        	$.cookies.set("webconsolebundlelist", table.sortList.toString());
-        });
-    });
-}
+$(document).ready(function(){
+	$(".refreshPackages").click(refreshPackages);
+	$(".reloadButton").click(loadData);
+	$(".installButton").click(function() {
+		document.location = pluginRoot + "/upload";
+	});
+	renderData(__bundles__);
+
+	// check for cookie
+	var cv = $.cookies.get("webconsolebundlelist");
+	var lo = (cv ? cv.split(",") : [1,0]);
+	$("#plugin_table").tablesorter({
+		headers: {
+			0: { sorter:"digit" },
+			5: { sorter: false }
+		},
+		textExtraction:mixedLinksExtraction,
+		sortList: cv ? [lo] : false
+	}).bind("sortEnd", function() {
+		var table = $("#plugin_table").eq(0).attr("config");
+		$.cookies.set("webconsolebundlelist", table.sortList.toString());
+	});
+});
+
diff --git a/webconsole/src/main/resources/templates/bundles.html b/webconsole/src/main/resources/templates/bundles.html
new file mode 100644
index 0000000..4221f30
--- /dev/null
+++ b/webconsole/src/main/resources/templates/bundles.html
@@ -0,0 +1,86 @@
+<script type="text/javascript" src="${appRoot}/res/ui/bundles.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+var startLevel = ${startLevel};
+var drawDetails = ${drawDetails};
+var currentBundle = ${currentBundle};
+var __bundles__ = ${__bundles__};
+var i18n = {
+	'Symbolic Name'       : '${bundles.name.symb}',
+	'Version'             : '${version}',
+	'Bundle Location'     : '${bundles.location}',
+	'Last Modification'   : '${bundles.lastMod}',
+	'Bundle Documentation': '${bundles.doc}',
+	'Vendor'              : '${bundles.vendor}',
+	'Copyright'           : '${bundles.copyright}',
+	'Description'         : '${bundles.description}',
+	'Start Level'         : '${bundles.startlevel}',
+	'Bundle Classpath'    : '${bundles.classpath}',
+	'Exported Packages'   : '${bundles.pkg.exported}',
+	'Imported Packages'   : '${bundles.pkg.imported}',
+	'Importing Bundles'   : '${bundles.pkg.importingBundles}',
+	'Manifest Headers'    : '${bundles.manifest.headers}',
+	'Host Bundles'        : '${bundles.hosts}',
+	'Fragments Attached'  : '${bundles.framents}',
+	'Vendor'              : '${bundles.vendor}',
+	// actions
+	'Start'               : '${start}',
+	'Stop'                : '${stop}',
+	'Update'              : '${bundles.update}',
+	'Uninstall'           : '${bundles.uninstall}',
+	'Refresh Package Imports' : '${bundles.refreshImports}',
+	//
+	statline              : '${bundles.statline}'
+}
+// ]]>
+</script>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
+
+<!-- top header -->
+<form method="post" enctype="multipart/form-data" action="">
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<input name="action" value="install" type="hidden" />
+		<input name="bundlestart" value="start" type="hidden" />
+		<input name="bundlestartlevel" value="5" type="hidden" />
+		<input name="bundlefile" style="margin-left: 10px;" type="file" />
+		<input value="${bundles.install_or_update}" style="margin-left: 10px;" type="submit" />
+		<button class="reloadButton" type="button" name="reload" style="margin-left: 60px;">${reload}</button>
+		<button class="installButton" type="button" name="install">${bundles.install_update}</button>
+		<button class="refreshPackages" type="button" name="refresh">${bundles.refreshPkg}</button>
+	</div>
+</form>
+
+<table id="plugin_table" class="tablesorter nicetable noauto">
+	<thead>
+		<tr>
+			<th class="col_Id">${id}</th>
+			<th class="col_Name">${bundles.name}</th>
+			<th class="col_Version">${version}</th>
+			<th class="col_Symbolic_Name">${bundles.name.symb}</th>
+			<th class="col_Status">${bundles.status}</th>
+			<th class="col_Actions">${bundles.actions}</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr><td colspan="6">&nbsp;</td></tr>
+	</tbody>
+</table>
+
+<!-- bottom header -->
+<form method="post" enctype="multipart/form-data" action="">
+	<div class="ui-widget-header ui-corner-bottom buttonGroup">
+		<input name="action" value="install" type="hidden" />
+		<input name="bundlestart" value="start" type="hidden" />
+		<input name="bundlestartlevel" value="5" type="hidden" />
+		<input name="bundlefile" style="margin-left: 10px;" type="file" />
+		<input value="${bundles.install_or_update}" style="margin-left: 10px;" type="submit" />
+		<button class="reloadButton" type="button" name="reload" style="margin-left: 60px;">${reload}</button>
+		<button class="installButton" type="button" name="install">${bundles.install_update}</button>
+		<button class="refreshPackages" type="button" name="refresh">${bundles.refreshPkg}</button>
+	</div>
+</form>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
diff --git a/webconsole/src/main/resources/templates/bundles_upload.html b/webconsole/src/main/resources/templates/bundles_upload.html
new file mode 100644
index 0000000..18f9b52
--- /dev/null
+++ b/webconsole/src/main/resources/templates/bundles_upload.html
@@ -0,0 +1,33 @@
+<!--<script type="text/javascript" src="res/ui/bundles.js"></script>-->
+<script type="text/javascript" src="res/lib/jquery.multifile-1.4.6.js"></script>
+
+<form method="post" enctype="multipart/form-data" action="../" style="width:50%">
+
+	<!-- top heading -->
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<input type="hidden" name="action" value="install"/>
+		${bundles.upload.caption}
+	</div>
+
+	<table class="nicetable">
+		<tr>
+			<td style="text-align:right">${bundles.upload.start}</td>
+			<td><input type="checkbox" name="bundlestart" value="start"/></td>
+		</tr>
+		<tr>
+			<td style="text-align:right">${bundles.upload.level}</td>
+			<td><input class="input" type="text" name="bundlestartlevel" id="bundlestartlevel" value="${startLevel}" size="4"/></td>
+		</tr>
+		<tr>
+			<td>&nbsp;</td>
+			<td>
+				<input class="multi" accept="jar" type="file" name="bundlefile"/>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="2" style="text-align:center">
+				<input type="submit" value="${bundles.install_or_update}"/>
+			</td>
+		</tr>
+	</table>
+</form>