FELIX-1171 Refactor display of configuration status using tabbed
view. This includes the tabworld JQuery plugin with a preliminary
(yet completely open) license (still have to confirm whether this
can be licensed under a better known license such as BSD or MIT).

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@815280 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/LICENSE.tabworld b/webconsole/LICENSE.tabworld
new file mode 100644
index 0000000..214b980
--- /dev/null
+++ b/webconsole/LICENSE.tabworld
@@ -0,0 +1,9 @@
+Tanner Hildebrand's License
+
+TabWorld by Tanner Hildebrand is free to use for any purpose, commercial or
+otherwise. The user of the script may modify, distribute, sell, use, or
+publish this script at their discretion. The uses are unrestricted and may
+only be restricted if the user is found to be guilty (by a U.S. court only)
+of a criminal act that involves this script.
+
+Please enjoy and use this script as you wish to its fullest potential.
\ No newline at end of file
diff --git a/webconsole/NOTICE b/webconsole/NOTICE
index 7e3807b..0df574c 100644
--- a/webconsole/NOTICE
+++ b/webconsole/NOTICE
@@ -38,6 +38,10 @@
 Copyright (c) 2006 Mark James
 Licensed under the Creative Commons Attribution 2.5 License
 
+This product includes software from http://benchsketch.com/bquery/index.html
+Copyright (c) 2009 Tanner Hildebrand
+Licensed under Tanner Hildebrand's License
+
 
 II. Used Software
 
@@ -53,3 +57,4 @@
 - JSON License
 - MIT License
 - Creative Commons Attribution 2.5 License
+- Tanner Hildebrand's License
\ No newline at end of file
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 f51ed44..92a4b38 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
@@ -19,18 +19,45 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.*;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.MessageFormat;
+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.WebConsoleConstants;
 import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
 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;
@@ -42,7 +69,7 @@
 /**
  * The <code>BundlesServlet</code> TODO
  */
-public class BundlesServlet extends BaseWebConsolePlugin
+public class BundlesServlet extends BaseWebConsolePlugin implements ConfigurationPrinter
 {
 
     public static final String NAME = "bundles";
@@ -62,6 +89,7 @@
     // see #activate and #isBootDelegated
     private boolean[] bootPkgWildcards;
 
+    private ServiceRegistration configurationPrinter;
 
     public void activate( BundleContext bundleContext )
     {
@@ -83,6 +111,20 @@
             }
             bootPkgs[i] = bootDelegation;
         }
+
+        configurationPrinter = bundleContext.registerService( ConfigurationPrinter.SERVICE, this, null );
+    }
+
+
+    public void deactivate()
+    {
+        if ( configurationPrinter != null )
+        {
+            configurationPrinter.unregister();
+            configurationPrinter = null;
+        }
+
+        super.deactivate();
     }
 
 
@@ -98,6 +140,72 @@
     }
 
 
+    //---------- ConfigurationPrinter
+
+    public void printConfiguration( PrintWriter pw )
+    {
+        try
+        {
+            StringWriter w = new StringWriter();
+            writeJSON( w, null, null, true );
+            String jsonString = w.toString();
+            JSONObject json = new JSONObject( jsonString );
+
+            pw.println( "Status: " + json.get( "status" ) );
+            pw.println();
+
+            JSONArray data = json.getJSONArray( "data" );
+            for ( int i = 0; i < data.length(); i++ )
+            {
+                if ( !data.isNull( i ) )
+                {
+                    JSONObject bundle = data.getJSONObject( i );
+
+                    pw.println( MessageFormat.format( "Bundle {0} - {1} {2} (state: {3})", new Object[]
+                        { bundle.get( "id" ), bundle.get( "name" ), bundle.get( "version" ), bundle.get( "state" ) } ) );
+
+                    JSONArray props = bundle.getJSONArray( "props" );
+                    for ( int pi = 0; pi < props.length(); pi++ )
+                    {
+                        if ( !props.isNull( pi ) )
+                        {
+                            JSONObject entry = props.getJSONObject( pi );
+
+                            pw.print( "    " + entry.get( "key" ) + ": " );
+
+                            Object entryValue = entry.get( "value" );
+                            if ( entryValue instanceof JSONArray )
+                            {
+                                pw.println();
+                                JSONArray entryArray = ( JSONArray ) entryValue;
+                                for ( int ei = 0; ei < entryArray.length(); ei++ )
+                                {
+                                    if ( !entryArray.isNull( ei ) )
+                                    {
+                                        pw.println( "        " + entryArray.get( ei ) );
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                pw.println( entryValue );
+                            }
+                        }
+                    }
+
+                    pw.println();
+                }
+            }
+        }
+        catch ( Exception e )
+        {
+            getLog().log( LogService.LOG_ERROR, "Problem rendering Bundle details for configuration status", e );
+        }
+    }
+
+
+    //---------- BaseWebConsolePlugin
+
     protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
         IOException
     {
@@ -331,7 +439,15 @@
         writeJSON(pw, bundle, pluginRoot);
     }
 
-    private void writeJSON( final PrintWriter pw, final Bundle bundle, final String pluginRoot) throws IOException
+
+    private void writeJSON( final PrintWriter pw, final Bundle bundle, final String pluginRoot ) throws IOException
+    {
+        writeJSON( pw, bundle, pluginRoot, false );
+    }
+
+
+    private void writeJSON( final Writer pw, final Bundle bundle, final String pluginRoot,
+        final boolean fullDetails ) throws IOException
     {
         final Bundle[] allBundles = this.getBundles();
         final String statusLine = this.getStatusLine(allBundles);
@@ -354,7 +470,7 @@
 
             for ( int i = 0; i < bundles.length; i++ )
             {
-                bundleInfo( jw, bundles[i], bundle != null, pluginRoot );
+                bundleInfo( jw, bundles[i], fullDetails || bundle != null, pluginRoot );
             }
 
             jw.endArray();
@@ -1023,7 +1139,12 @@
     private String getBundleDescriptor( Bundle bundle, final String pluginRoot )
     {
         StringBuffer val = new StringBuffer();
-        val.append("<a href='").append(pluginRoot).append('/').append(bundle.getBundleId()).append("'>");
+
+        if ( pluginRoot != null )
+        {
+            val.append( "<a href='" ).append( pluginRoot ).append( '/' ).append( bundle.getBundleId() ).append( "'>" );
+        }
+
         if ( bundle.getSymbolicName() != null )
         {
             // list the bundle name if not null
@@ -1044,7 +1165,10 @@
             // only append the bundle
             val.append( bundle.getBundleId() );
         }
-        val.append("</a>");
+        if ( pluginRoot != null )
+        {
+            val.append( "</a>" );
+        }
         return val.toString();
     }
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
index 55a38f6..505d0c0 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
@@ -18,27 +18,33 @@
 
 
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.ConfigurationPrinter;
+import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -59,6 +65,20 @@
 
     public static final String TITLE = "Configuration Status";
 
+    /**
+     * Formatter pattern to generate a relative path for the generation
+     * of the plain text or zip file representation of the status. The file
+     * name consists of a base name and the current time of status generation.
+     */
+    private static final SimpleDateFormat FILE_NAME_FORMAT = new SimpleDateFormat( "'" + LABEL
+        + "/configuration-status-'yyyyMMdd'-'HHmmZ" );
+
+    /**
+     * Formatter pattern to render the current time of status generation.
+     */
+    private static final DateFormat DISPLAY_DATE_FORMAT = SimpleDateFormat.getDateTimeInstance( SimpleDateFormat.LONG,
+        SimpleDateFormat.LONG, Locale.US );
+
     private ServiceTracker cfgPrinterTracker;
 
     private int cfgPrinterTrackerCount;
@@ -78,28 +98,82 @@
     }
 
 
+    protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+        if ( request.getPathInfo().endsWith( ".txt" ) )
+        {
+            response.setContentType( "text/plain; charset=utf-8" );
+            ConfigurationWriter pw = new PlainTextConfigurationWriter( response.getWriter() );
+            printConfigurationStatus( pw );
+            pw.flush();
+        }
+        else if ( request.getPathInfo().endsWith( ".zip" ) )
+        {
+            String type = getServletContext().getMimeType( request.getPathInfo() );
+            if ( type == null )
+            {
+                type = "application/x-zip";
+            }
+            response.setContentType( type );
+
+            ZipOutputStream zip = new ZipOutputStream( response.getOutputStream() );
+            zip.setLevel( 9 );
+            zip.setMethod( ZipOutputStream.DEFLATED );
+
+            ConfigurationWriter pw = new ZipConfigurationWriter( zip );
+            printConfigurationStatus( pw );
+            pw.flush();
+
+            zip.finish();
+        }
+        else
+        {
+            super.doGet( request, response );
+        }
+    }
+
+
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
 
-        PrintWriter pw = response.getWriter();
+        ConfigurationWriter pw = new HtmlConfigurationWriter( response.getWriter() );
 
-        pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
+        String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
+        pw.println( "<link href='" + appRoot + "/res/ui/configurationrender.css' rel='stylesheet' type='text/css'>" );
+        pw.println( "<script src='" + appRoot + "/res/ui/tw-1.1.js' language='JavaScript'></script>" );
 
-        pw.println( "<tr class='content'>" );
-        pw.println( "<th class='content container'>Configuration Details</th>" );
-        pw.println( "</tr>" );
+        pw.println( "<script>$(document).ready(function(){ $('#cfgprttabs').tabworld() });</script>" );
 
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td class='content'>" );
-        pw.println( "<pre>" );
+        final Date currentTime = new Date();
+        synchronized ( DISPLAY_DATE_FORMAT )
+        {
+            pw.println( "<p>Date: " + DISPLAY_DATE_FORMAT.format( currentTime ) + "</p>" );
+        }
 
-        pw.println( "*** Date: "
-            + SimpleDateFormat.getDateTimeInstance( SimpleDateFormat.LONG, SimpleDateFormat.LONG, Locale.US ).format(
-                new Date() ) );
-        pw.println();
+        synchronized ( FILE_NAME_FORMAT )
+        {
+            String fileName = FILE_NAME_FORMAT.format( currentTime );
+            pw.println( "<p>Download as <a href='" + fileName + ".txt'>[Single File]</a> or as <a href='" + fileName
+                + ".zip'>[ZIP]</a></p>" );
+        }
 
+        pw.println( "<div id='divcfgprttabs'>" );
+
+        pw.println( "<ul id='cfgprttabs'>" );
+
+        printConfigurationStatus( pw );
+
+        pw.println( "</ul>" );
+        pw.println( "</div>" );
+
+        pw.flush();
+    }
+
+
+    private void printConfigurationStatus( ConfigurationWriter pw )
+    {
         this.printSystemProperties( pw );
-        this.printBundles( pw );
         this.printServices( pw );
         this.printPreferences( pw );
         this.printConfigurations( pw );
@@ -109,11 +183,6 @@
         {
             printConfigurationPrinter( pw, ( ConfigurationPrinter ) cpi.next() );
         }
-
-        pw.println( "</pre>" );
-        pw.println( "</td>" );
-        pw.println( "</tr>" );
-        pw.println( "</table>" );
     }
 
 
@@ -147,9 +216,9 @@
     }
 
 
-    private void printSystemProperties( PrintWriter pw )
+    private void printSystemProperties( ConfigurationWriter pw )
     {
-        pw.println( "*** System properties:" );
+        pw.title( "System properties" );
 
         Properties props = System.getProperties();
         SortedSet keys = new TreeSet( props.keySet() );
@@ -159,7 +228,7 @@
             this.infoLine( pw, null, ( String ) key, props.get( key ) );
         }
 
-        pw.println();
+        pw.end();
     }
 
 
@@ -194,31 +263,10 @@
     //        pw.println();
     //    }
 
-    private void printBundles( PrintWriter pw )
+
+    private void printServices( ConfigurationWriter pw )
     {
-        pw.println( "*** Bundles:" );
-        // biz.junginger.freemem.FreeMem (1.3.0) "FreeMem Eclipse Memory
-        // Monitor" [Resolved]
-
-        Bundle[] bundles = getBundleContext().getBundles();
-        SortedSet keys = new TreeSet();
-        for ( int i = 0; i < bundles.length; i++ )
-        {
-            keys.add( this.getBundleString( bundles[i], true ) );
-        }
-
-        for ( Iterator ki = keys.iterator(); ki.hasNext(); )
-        {
-            this.infoLine( pw, null, null, ki.next() );
-        }
-
-        pw.println();
-    }
-
-
-    private void printServices( PrintWriter pw )
-    {
-        pw.println( "*** Services:" );
+        pw.title(  "Services" );
 
         // get the list of services sorted by service ID (ascending)
         SortedMap srMap = new TreeMap();
@@ -242,17 +290,15 @@
 
             this.infoLine( pw, null, String.valueOf( sr.getProperty( Constants.SERVICE_ID ) ), sr
                 .getProperty( Constants.OBJECTCLASS ) );
-            this.infoLine( pw, "  ", "Bundle", this.getBundleString( sr.getBundle(), false ) );
+            this.infoLine( pw, "  ", "Bundle", this.getBundleString( sr.getBundle() ) );
 
             Bundle[] users = sr.getUsingBundles();
             if ( users != null && users.length > 0 )
             {
-                List userString = new ArrayList();
                 for ( int i = 0; i < users.length; i++ )
                 {
-                    userString.add( this.getBundleString( users[i], false ) );
+                    this.infoLine( pw, "  ", "Using Bundle", this.getBundleString( users[i] ) );
                 }
-                this.infoLine( pw, "  ", "Using Bundles", userString );
             }
 
             String[] keys = sr.getPropertyKeys();
@@ -267,21 +313,22 @@
 
             pw.println();
         }
+
+        pw.end();
     }
 
 
-    private void printPreferences( PrintWriter pw )
+    private void printPreferences( ConfigurationWriter pw )
     {
-        pw.println( "*** System Preferences:" );
+        pw.title( "Preferences" );
 
         ServiceReference sr = getBundleContext().getServiceReference( PreferencesService.class.getName() );
         if ( sr == null )
         {
             pw.println( "  Preferences Service not registered" );
-            pw.println();
-            return;
         }
-
+        else
+        {
             PreferencesService ps = ( PreferencesService ) getBundleContext().getService( sr );
             try
             {
@@ -304,6 +351,9 @@
             }
         }
 
+        pw.end();
+    }
+
 
     private void printPreferences( PrintWriter pw, Preferences prefs ) throws BackingStoreException
     {
@@ -324,9 +374,9 @@
     }
 
 
-    private void printConfigurations( PrintWriter pw )
+    private void printConfigurations( ConfigurationWriter pw )
     {
-        pw.println( "*** Configurations:" );
+        pw.title(  "Configurations" );
 
         ServiceReference sr = getBundleContext().getServiceReference( ConfigurationAdmin.class.getName() );
         if ( sr == null )
@@ -368,15 +418,15 @@
             }
         }
 
-        pw.println();
+        pw.end();
     }
 
 
-    private void printConfigurationPrinter( PrintWriter pw, ConfigurationPrinter cp )
+    private void printConfigurationPrinter( ConfigurationWriter pw, ConfigurationPrinter cp )
     {
-        pw.println( "*** " + cp.getTitle() + ":" );
+        pw.title(  cp.getTitle() );
         cp.printConfiguration( pw );
-        pw.println();
+        pw.end();
     }
 
 
@@ -466,7 +516,7 @@
     }
 
 
-    private String getBundleString( Bundle bundle, boolean withState )
+    private String getBundleString( Bundle bundle )
     {
         StringBuffer buf = new StringBuffer();
 
@@ -494,38 +544,11 @@
             buf.append( " \"" ).append( headers.get( Constants.BUNDLE_NAME ) ).append( '"' );
         }
 
-        if ( withState )
-        {
-            buf.append( " [" );
-            switch ( bundle.getState() )
-            {
-                case Bundle.INSTALLED:
-                    buf.append( "Installed" );
-                    break;
-                case Bundle.RESOLVED:
-                    buf.append( "Resolved" );
-                    break;
-                case Bundle.STARTING:
-                    buf.append( "Starting" );
-                    break;
-                case Bundle.ACTIVE:
-                    buf.append( "Active" );
-                    break;
-                case Bundle.STOPPING:
-                    buf.append( "Stopping" );
-                    break;
-                case Bundle.UNINSTALLED:
-                    buf.append( "Uninstalled" );
-                    break;
-            }
-            buf.append( ']' );
-        }
-
         return buf.toString();
     }
 
 
-    private void printThreads( PrintWriter pw )
+    private void printThreads( ConfigurationWriter pw )
     {
         // first get the root thread group
         ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
@@ -534,7 +557,7 @@
             rootGroup = rootGroup.getParent();
         }
 
-        pw.println( "*** Threads:" );
+        pw.title(  "Threads" );
 
         printThreadGroup( pw, rootGroup );
 
@@ -546,7 +569,7 @@
             printThreadGroup( pw, groups[i] );
         }
 
-        pw.println();
+        pw.end();
     }
 
 
@@ -605,4 +628,116 @@
             infoLine( pw, "  ", null, info.toString() );
         }
     }
+
+    private abstract static class ConfigurationWriter extends PrintWriter
+    {
+
+        ConfigurationWriter( Writer delegatee )
+        {
+            super( delegatee );
+        }
+
+
+        abstract void title( String title );
+
+
+        abstract void end();
+
+    }
+
+    private static class HtmlConfigurationWriter extends ConfigurationWriter
+    {
+
+        HtmlConfigurationWriter( Writer delegatee )
+        {
+            super( delegatee );
+        }
+
+
+        public void title( String title )
+        {
+            println( "<li>" );
+            println( title );
+            println( "<q><pre>" );
+        }
+
+
+        public void end()
+        {
+            println( "</pre>" );
+            println( "</q>" );
+            println( "</li>" );
+        }
+    }
+
+    private static class PlainTextConfigurationWriter extends ConfigurationWriter
+    {
+
+        PlainTextConfigurationWriter( Writer delegatee )
+        {
+            super( delegatee );
+        }
+
+
+        public void title( String title )
+        {
+            print( "*** " );
+            print( title );
+            println( ":" );
+        }
+
+
+        public void end()
+        {
+            println();
+        }
+    }
+
+    private static class ZipConfigurationWriter extends ConfigurationWriter
+    {
+        private final ZipOutputStream zip;
+
+        private int counter;
+
+
+        ZipConfigurationWriter( ZipOutputStream zip )
+        {
+            super( new OutputStreamWriter( zip ) );
+            this.zip = zip;
+        }
+
+
+        public void title( String title )
+        {
+            String name = MessageFormat.format( "{0,number,000}-{1}.txt", new Object[]
+                { new Integer( counter ), title } );
+
+            counter++;
+
+            ZipEntry entry = new ZipEntry( name );
+            try
+            {
+                zip.putNextEntry( entry );
+            }
+            catch ( IOException ioe )
+            {
+                // should handle
+            }
+        }
+
+
+        public void end()
+        {
+            flush();
+
+            try
+            {
+                zip.closeEntry();
+            }
+            catch ( IOException ioe )
+            {
+                // should handle
+            }
+        }
+    }
 }
diff --git a/webconsole/src/main/resources/res/ui/configurationrender.css b/webconsole/src/main/resources/res/ui/configurationrender.css
new file mode 100644
index 0000000..9bcba5a
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/configurationrender.css
@@ -0,0 +1,70 @@
+/* tabbed view */
+
+#divcfgprttabs {
+}
+
+#divcfgprttabs ul { 
+    list-style: none;
+}
+
+#divcfgprttabs ul li {
+    display: inline; 
+    background: #fff;
+    height: 21px;
+    margin: 0 2px 0 0 ;
+    border: 1px solid #999;
+    float: left;
+    padding: 1px 1px 0 1px
+}
+
+#divcfgprttabs ul li.tabactive {
+    border-bottom-color: white;
+}
+
+#divcfgprttabs ul li a {
+    color: #6181A9;
+    background-color: white;
+    display: block;
+    float: left;
+    height: 100%;
+    line-height: 2;
+    padding: 0 10px 0 10px;
+    cursor: pointer;
+}
+
+#divcfgprttabs ul li a.tabactive {
+}
+
+#divcfgprttabs ul li a:hover {
+    color: white;
+    background-color: #6181A9;
+}
+
+.menu {
+    background: none;
+    height: 23px;
+    margin-bottom: 0;
+    padding-left: 0;
+}
+.area {
+    background: #fff;
+    border: 1px solid #999;
+    padding: 1px;
+}
+
+/* Contents container of the tabs */
+.tabcont {
+    background: #fff;
+    border: 1px solid #999;
+    border-right: none;
+    padding: 1px;
+}
+
+/* The actual content of the tabs */
+.space {
+    padding: 10px;
+    
+    /* don't let this area make the display wider */
+    width: 100px;
+    overflow: visible;
+}
diff --git a/webconsole/src/main/resources/res/ui/tw-1.1.js b/webconsole/src/main/resources/res/ui/tw-1.1.js
new file mode 100644
index 0000000..7a313ab
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/tw-1.1.js
@@ -0,0 +1,130 @@
+// JavaScript Document
+// Tab World From BenchSketch.com
+// Benchsketch.com/bquery/tab.html for documentation
+// You can use it FREE!!! Yay.
+// Let me know how it works, or send suggestions, comments, requests to benchsketch@gmail.com
+// Thanks
+	
+(function($){
+  $.fn.extend({
+			  
+   // tabworld function
+   tabworld: function(options){
+	 
+     // Get the user extensions and defaults
+      var opts = $.extend({}, $.fn.tabworld.defaults, options);
+	 
+	 // Content location
+	 	if(opts.contloc=="none"){
+			contloc=$(this).parent();
+		}else{
+			contloc=opts.contloc;
+		}
+	 // Content location
+	 	if(opts.tabloc=="none"){
+			tabloc=$(this).parent();
+		}else{
+			tabloc=opts.tabloc;
+		}
+	 
+	 // better define some stuff for safety
+	  var newli="",newdiv="";
+	 
+	 // Start Building Tabs
+	  return this.each(function(i){
+
+		
+		//start developing basis
+		now=$(this);	
+		nowid=now.attr("id");
+		now.addClass(opts.color);
+		
+	// tab maker function	
+	  // $("#"+nowid+" li").each(function(i){ // lets hide that for now
+		$("#"+nowid+" > li").each(function(i){
+		
+		tabli = $(this);
+		// taba = $('#'+nowid+" > li q");
+		taba = tabli.children("q");
+		$(this).addClass("removeme");
+		tabcont = taba.html();
+		$(".removeme q").remove();
+		licont = tabli.html();
+		$(this).remove();
+		
+		newli += "<li rel='"+nowid+"-"+i+"'><a>"+licont+"</a></li>";
+		newdiv += "<div id='"+nowid+"-"+i+"'>"+tabcont+"</div>";
+
+	  });
+
+	// Something weird to gain the location
+	 now.remove();
+	 $(tabloc).append("<ul id='"+nowid+"-tabworld' class='"+opts.color+"'>"+newli+"</ul>");
+	// Fix the ul
+	// $("#"+nowid).append(newli);
+	// Find the Parent then append the divs
+	 // var parent = $("#"+nowid).parent();
+	 newdiv = "<div id='"+nowid+"content' class='tabcont'><div class='"+opts.area+"'>"+newdiv+"</div></div>";
+	 newdiv = newdiv.replace(/\/>/,">");
+	 $(contloc).append(newdiv);	
+	
+	
+	// Find the default
+	 ndef = nowid+"-"+opts.dopen;
+	 ncon = nowid+"content ."+opts.area+" > div";
+	 $('#'+ncon).hide();
+	 $('#'+ndef).show();
+	 //$('#'+ndef+" > div").show();
+	 
+	 deftab = $('li[rel='+ndef+"]");
+	 deftab.addClass(opts.tabactive);
+	 deftab.children("a").addClass(opts.tabactive);
+	// Seperate function to start the tabbing
+	$("#"+nowid+"-tabworld >li").click(function(){
+		here=$(this);
+		nbound=here.attr("rel");
+		
+		// Make the active class / remove it from others
+		$("#"+nowid+"-tabworld > li").removeClass(opts.tabactive);
+		$("#"+nowid+"-tabworld > li a").removeClass(opts.tabactive);
+		here.addClass(opts.tabactive);
+		$("."+opts.tabactive+">a").addClass(opts.tabactive);
+		
+			// The real action! Also detirmine transition
+			 if(opts.transition=="slide"){
+				 $('#'+ncon+':visible').slideUp(opts.speed, function(){	
+					$('#'+nbound).find("div").show();
+					$('#'+nbound).slideDown(opts.speed);
+				 });
+			 }else if (opts.transition=="fade"){
+				 $('#'+ncon+':visible').fadeOut(opts.speed, function(){	
+					$('#'+nbound).find("div").show();
+					$('#'+nbound).fadeIn(opts.speed);
+				 });
+			 }else{
+				$('#'+ncon+':visible').hide(opts.speed, function(){	
+					$('#'+nbound).find("div").show();
+					$('#'+nbound).show(opts.speed);
+				 }); 
+			 }
+	 
+	});
+	
+	  });// end return each (i) 
+   }// end tabworld		
+  })// end $.fn.extend
+  
+// Defaults
+$.fn.tabworld.defaults = {
+ mislpace:'none',
+ speed:'fast',
+ color:'menu',
+ area:'space',
+ tabloc:'none',
+ contloc:'none',
+ dopen:0,
+ transition:'fade',
+ tabactive:'tabactive'
+}; // end defaults
+  
+})(jQuery);// end function($)
\ No newline at end of file