FELIX-566 Enable RESTful URLs for component list and unify display of bundles and components

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@669450 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
index 76f3ef6..365c135 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
@@ -107,7 +107,7 @@
 
         pw.println( "  Bundle" + component.getBundle().getSymbolicName() + " (" + component.getBundle().getBundleId()
             + ")" );
-        pw.println( "  State=" + ComponentRenderAction.toStateString( component.getState() ) );
+        pw.println( "  State=" + ComponentsServlet.toStateString( component.getState() ) );
         pw.println( "  DefaultState=" + ( component.isDefaultEnabled() ? "enabled" : "disabled" ) );
         pw.println( "  Activation=" + ( component.isImmediate() ? "immediate" : "delayed" ) );
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentRenderAction.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentRenderAction.java
deleted file mode 100644
index 9153ab5..0000000
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentRenderAction.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * 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.
- */
-package org.apache.felix.webconsole.internal.compendium;
-
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Iterator;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.felix.scr.Component;
-import org.apache.felix.scr.Reference;
-import org.apache.felix.scr.ScrService;
-import org.apache.felix.webconsole.Action;
-import org.apache.felix.webconsole.Render;
-import org.apache.felix.webconsole.internal.Util;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.component.ComponentConstants;
-
-
-public class ComponentRenderAction extends AbstractScrPlugin implements Render, Action
-{
-
-    public static final String NAME = "components";
-
-    public static final String LABEL = "Components";
-
-    public static final String COMPONENT_ID = "componentId";
-
-    public static final String OPERATION = "op";
-
-    public static final String OPERATION_DETAILS = "details";
-
-    public static final String OPERATION_ENABLE = "enable";
-
-    public static final String OPERATION_DISABLE = "disable";
-
-
-    public String getName()
-    {
-        return NAME;
-    }
-
-
-    public String getLabel()
-    {
-        return LABEL;
-    }
-
-
-    public boolean performAction( HttpServletRequest request, HttpServletResponse response ) throws IOException
-    {
-
-        ScrService scrService = getScrService();
-        if ( scrService != null )
-        {
-
-            long componentId = getComponentId( request );
-            Component component = scrService.getComponent( componentId );
-
-            if ( component != null )
-            {
-                String op = request.getParameter( OPERATION );
-                if ( OPERATION_DETAILS.equals( op ) )
-                {
-                    return sendAjaxDetails( component, response );
-                }
-                else if ( OPERATION_ENABLE.equals( op ) )
-                {
-                    component.enable();
-                }
-                else if ( OPERATION_DISABLE.equals( op ) )
-                {
-                    component.disable();
-                }
-            }
-
-        }
-
-        return true;
-    }
-
-
-    public void render( HttpServletRequest request, HttpServletResponse response ) throws IOException
-    {
-
-        PrintWriter pw = response.getWriter();
-
-        this.header( pw );
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td colspan='5' class='content'>&nbsp;</th>" );
-        pw.println( "</tr>" );
-
-        this.tableHeader( pw );
-
-        ScrService scrService = getScrService();
-        if ( scrService == null )
-        {
-            pw.println( "<tr class='content'>" );
-            pw
-                .println( "<td class='content' colspan='5'>Apache Felix Declarative Service required for this function</td>" );
-            pw.println( "</tr>" );
-        }
-        else
-        {
-            Component[] components = scrService.getComponents();
-            if ( components == null || components.length == 0 )
-            {
-                pw.println( "<tr class='content'>" );
-                pw.println( "<td class='content' colspan='5'>No " + this.getLabel() + " installed currently</td>" );
-                pw.println( "</tr>" );
-
-            }
-            else
-            {
-
-                // order components by id
-                TreeMap componentMap = new TreeMap();
-                for ( int i = 0; i < components.length; i++ )
-                {
-                    Component component = components[i];
-                    componentMap.put( component.getName(), component );
-                }
-
-                // render components
-                long previousComponent = -1;
-                for ( Iterator ci = componentMap.values().iterator(); ci.hasNext(); )
-                {
-                    Component component = ( Component ) ci.next();
-                    if ( previousComponent >= 0 )
-                    {
-                        // prepare for injected table information row
-                        pw.println( "<tr id='component" + previousComponent + "'></tr>" );
-                    }
-
-                    component( pw, component );
-
-                    previousComponent = component.getId();
-                }
-
-                if ( previousComponent >= 0 )
-                {
-                    // prepare for injected table information row
-                    pw.println( "<tr id='component" + previousComponent + "'></tr>" );
-                }
-            }
-        }
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td colspan='5' class='content'>&nbsp;</th>" );
-        pw.println( "</tr>" );
-
-        this.footer( pw );
-    }
-
-
-    private void header( PrintWriter pw )
-    {
-        Util.startScript( pw );
-        pw.println( "function showDetails(componentId) {" );
-        pw.println( "    var span = document.getElementById('component' + componentId);" );
-        pw.println( "    if (!span) {" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw.println( "    if (span.innerHTML) {" );
-        pw.println( "        span.innerHTML = '';" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw.println( "    var parm = '?" + Util.PARAM_ACTION + "=" + NAME + "&" + OPERATION + "=" + OPERATION_DETAILS
-            + "&" + COMPONENT_ID + "=' + componentId;" );
-        pw.println( "    sendRequest('GET', parm, displayComponentDetails);" );
-        pw.println( "}" );
-        pw.println( "function displayComponentDetails(obj) {" );
-        pw.println( "    var span = document.getElementById('component' + obj." + COMPONENT_ID + ");" );
-        pw.println( "    if (!span) {" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw
-            .println( "    var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"4\"><table broder=\"0\">';" );
-        pw.println( "    var props = obj.props;" );
-        pw.println( "    for (var i=0; i < props.length; i++) {" );
-        pw
-            .println( "        innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';" );
-        pw.println( "    }" );
-        pw.println( "    innerHtml += '</table></td>';" );
-        pw.println( "    span.innerHTML = innerHtml;" );
-        pw.println( "}" );
-        Util.endScript( pw );
-
-        pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-    }
-
-
-    private void tableHeader( PrintWriter pw )
-    {
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<th class='content'>ID</th>" );
-        pw.println( "<th class='content' width='100%'>Name</th>" );
-        pw.println( "<th class='content'>Status</th>" );
-        pw.println( "<th class='content' colspan='2'>Actions</th>" );
-        pw.println( "</tr>" );
-    }
-
-
-    private void footer( PrintWriter pw )
-    {
-        pw.println( "</table>" );
-    }
-
-
-    private void component( PrintWriter pw, Component component )
-    {
-        String name = component.getName();
-
-        pw.println( "<tr>" );
-        pw.println( "<td class='content right'>" + component.getId() + "</td>" );
-        pw.println( "<td class='content'><a href='javascript:showDetails(" + component.getId() + ")'>" + name
-            + "</a></td>" );
-        pw.println( "<td class='content center'>" + toStateString( component.getState() ) + "</td>" );
-
-        boolean enabled = component.getState() == Component.STATE_DISABLED;
-        this.actionForm( pw, enabled, component.getId(), OPERATION_ENABLE, "Enable" );
-
-        enabled = component.getState() != Component.STATE_DISABLED && component.getState() != Component.STATE_DESTROYED;
-        this.actionForm( pw, enabled, component.getId(), OPERATION_DISABLE, "Disable" );
-
-        pw.println( "</tr>" );
-    }
-
-
-    private void actionForm( PrintWriter pw, boolean enabled, long componentId, String op, String opLabel )
-    {
-        pw.println( "<form name='form" + componentId + "' method='post'>" );
-        pw.println( "<td class='content' align='right'>" );
-        pw.println( "<input type='hidden' name='" + Util.PARAM_ACTION + "' value='" + NAME + "' />" );
-        pw.println( "<input type='hidden' name='" + OPERATION + "' value='" + op + "' />" );
-        pw.println( "<input type='hidden' name='" + COMPONENT_ID + "' value='" + componentId + "' />" );
-        pw.println( "<input class='submit' type='submit' value='" + opLabel + "'" + ( enabled ? "" : "disabled" )
-            + " />" );
-        pw.println( "</td>" );
-        pw.println( "</form>" );
-    }
-
-
-    private boolean sendAjaxDetails( Component component, HttpServletResponse response ) throws IOException
-    {
-        JSONObject result = null;
-        try
-        {
-            if ( component != null )
-            {
-
-                JSONArray props = new JSONArray();
-                keyVal( props, "Bundle", component.getBundle().getSymbolicName() + " ("
-                    + component.getBundle().getBundleId() + ")" );
-                keyVal( props, "Default State", component.isDefaultEnabled() ? "enabled" : "disabled" );
-                keyVal( props, "Activation", component.isImmediate() ? "immediate" : "delayed" );
-
-                listServices( props, component );
-                listReferences( props, component );
-                listProperties( props, component );
-
-                result = new JSONObject();
-                result.put( ComponentRenderAction.COMPONENT_ID, component.getId() );
-                result.put( "props", props );
-            }
-        }
-        catch ( Exception exception )
-        {
-            // create an empty result on problems
-            result = new JSONObject();
-        }
-
-        // send the result
-        response.setContentType( "text/javascript" );
-        response.getWriter().print( result.toString() );
-
-        return false;
-    }
-
-
-    private void listServices( JSONArray props, Component component )
-    {
-        String[] services = component.getServices();
-        if ( services == null )
-        {
-            return;
-        }
-
-        keyVal( props, "Service Type", component.isServiceFactory() ? "service factory" : "service" );
-
-        StringBuffer buf = new StringBuffer();
-        for ( int i = 0; i < services.length; i++ )
-        {
-            if ( i > 0 )
-            {
-                buf.append( "<br />" );
-            }
-            buf.append( services[i] );
-        }
-
-        keyVal( props, "Services", buf.toString() );
-    }
-
-
-    private void listReferences( JSONArray props, Component component )
-    {
-        Reference[] refs = component.getReferences();
-        if ( refs != null )
-        {
-            for ( int i = 0; i < refs.length; i++ )
-            {
-                StringBuffer buf = new StringBuffer();
-                buf.append( refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied" ).append( "<br />" );
-                buf.append( "Service Name: " ).append( refs[i].getServiceName() ).append( "<br />" );
-                if ( refs[i].getTarget() != null )
-                {
-                    buf.append( "Target Filter: " ).append( refs[i].getTarget() ).append( "<br />" );
-                }
-                buf.append( "Multiple: " ).append( refs[i].isMultiple() ? "multiple" : "single" ).append( "<br />" );
-                buf.append( "Optional: " ).append( refs[i].isOptional() ? "optional" : "mandatory" ).append( "<br />" );
-                buf.append( "Policy: " ).append( refs[i].isStatic() ? "static" : "dynamic" ).append( "<br />" );
-
-                // list bound services
-                ServiceReference[] boundRefs = refs[i].getServiceReferences();
-                if ( boundRefs != null && boundRefs.length > 0 )
-                {
-                    for ( int j = 0; j < boundRefs.length; j++ )
-                    {
-                        buf.append( "Bound Service ID " );
-                        buf.append( boundRefs[j].getProperty( Constants.SERVICE_ID ) );
-
-                        String name = ( String ) boundRefs[j].getProperty( ComponentConstants.COMPONENT_NAME );
-                        if ( name == null )
-                        {
-                            name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_PID );
-                            if ( name == null )
-                            {
-                                name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_DESCRIPTION );
-                            }
-                        }
-                        if ( name != null )
-                        {
-                            buf.append( " (" );
-                            buf.append( name );
-                            buf.append( ")" );
-                        }
-                    }
-                }
-                else
-                {
-                    buf.append( "No Services bound" );
-                }
-                buf.append( "<br />" );
-
-                keyVal( props, "Reference " + refs[i].getName(), buf.toString() );
-            }
-        }
-    }
-
-
-    private void listProperties( JSONArray jsonProps, Component component )
-    {
-        Dictionary props = component.getProperties();
-        if ( props != null )
-        {
-            StringBuffer buf = new StringBuffer();
-            TreeSet keys = new TreeSet( Collections.list( props.keys() ) );
-            for ( Iterator ki = keys.iterator(); ki.hasNext(); )
-            {
-                String key = ( String ) ki.next();
-                buf.append( key ).append( " = " );
-
-                Object prop = props.get( key );
-                if ( prop.getClass().isArray() )
-                {
-                    prop = Arrays.asList( ( Object[] ) prop );
-                }
-                buf.append( prop );
-                if ( ki.hasNext() )
-                {
-                    buf.append( "<br />" );
-                }
-            }
-            keyVal( jsonProps, "Properties", buf.toString() );
-        }
-
-    }
-
-
-    private void keyVal( JSONArray props, String key, Object value )
-    {
-        if ( key != null && value != null )
-        {
-            try
-            {
-                JSONObject obj = new JSONObject();
-                obj.put( "key", key );
-                obj.put( "value", value );
-                props.put( obj );
-            }
-            catch ( JSONException je )
-            {
-                // don't care
-            }
-        }
-    }
-
-
-    static String toStateString( int state )
-    {
-        switch ( state )
-        {
-            case Component.STATE_DISABLED:
-                return "disabled";
-            case Component.STATE_ENABLED:
-                return "enabled";
-            case Component.STATE_UNSATISFIED:
-                return "unsatisifed";
-            case Component.STATE_ACTIVATING:
-                return "activating";
-            case Component.STATE_ACTIVE:
-                return "active";
-            case Component.STATE_REGISTERED:
-                return "registered";
-            case Component.STATE_FACTORY:
-                return "factory";
-            case Component.STATE_DEACTIVATING:
-                return "deactivating";
-            case Component.STATE_DESTROYED:
-                return "destroyed";
-            default:
-                return String.valueOf( state );
-        }
-    }
-
-
-    protected long getComponentId( HttpServletRequest request )
-    {
-        String componentIdPar = request.getParameter( ComponentRenderAction.COMPONENT_ID );
-        if ( componentIdPar != null )
-        {
-            try
-            {
-                return Long.parseLong( componentIdPar );
-            }
-            catch ( NumberFormatException nfe )
-            {
-                // TODO: log
-            }
-        }
-
-        // no bundleId or wrong format
-        return -1;
-    }
-
-}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
new file mode 100644
index 0000000..e9ec667
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
@@ -0,0 +1,475 @@
+/*
+ * 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.
+ */
+package org.apache.felix.webconsole.internal.compendium;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.Component;
+import org.apache.felix.scr.Reference;
+import org.apache.felix.scr.ScrService;
+import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
+import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.internal.servlet.OsgiManager;
+import org.json.JSONException;
+import org.json.JSONWriter;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+
+
+public class ComponentsServlet extends BaseWebConsolePlugin
+{
+
+    public static final String NAME = "components";
+
+    public static final String LABEL = "Components";
+
+    public static final String COMPONENT_ID = "componentId";
+
+    public static final String OPERATION = "action";
+
+    public static final String OPERATION_ENABLE = "enable";
+
+    public static final String OPERATION_DISABLE = "disable";
+
+    private static final String SCR_SERVICE = ScrService.class.getName();
+
+
+    public String getTitle()
+    {
+        return LABEL;
+    }
+
+
+    public String getLabel()
+    {
+        return NAME;
+    }
+
+
+    protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
+        ScrService scrService = getScrService();
+        if ( scrService != null )
+        {
+
+            long componentId = getComponentId( request );
+            Component component = scrService.getComponent( componentId );
+
+            if ( component != null )
+            {
+                String op = request.getParameter( OPERATION );
+                if ( OPERATION_ENABLE.equals( op ) )
+                {
+                    component.enable();
+                }
+                else if ( OPERATION_DISABLE.equals( op ) )
+                {
+                    component.disable();
+                }
+
+                sendAjaxDetails( component, response );
+            }
+
+        }
+    }
+
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
+        PrintWriter pw = response.getWriter();
+
+        String appRoot = ( String ) request.getAttribute( OsgiManager.ATTR_APP_ROOT );
+        pw.println( "<script src='" + appRoot + "/res/ui/datatable.js' language='JavaScript'></script>" );
+
+        Util.startScript( pw );
+
+        pw.print( "var components = " );
+        renderResult( request, pw );
+        pw.println( ";" );
+
+        pw.println( "renderDataTable( components );" );
+
+        Util.endScript( pw );
+    }
+
+
+    private void renderResult( HttpServletRequest request, PrintWriter pw ) throws IOException
+    {
+        JSONWriter jw = new JSONWriter( pw );
+        try
+        {
+            jw.object();
+
+            jw.key( "numActions" );
+            jw.value( 2 );
+
+            ScrService scrService = getScrService();
+            if ( scrService == null )
+            {
+                jw.key( "error" );
+                jw.value( "Apache Felix Declarative Service required for this function" );
+            }
+            else
+            {
+                Component[] components = null;
+                boolean details = false;
+
+                long componentId = getComponentId( request );
+                if ( componentId >= 0 )
+                {
+                    Component component = scrService.getComponent( componentId );
+                    if ( component != null )
+                    {
+                        components = new Component[]
+                            { component };
+                        details = true;
+                    }
+                }
+
+                if ( components == null )
+                {
+                    components = scrService.getComponents();
+                }
+
+                if ( components == null || components.length == 0 )
+                {
+                    jw.key( "error" );
+                    jw.value( "No components installed currently" );
+                }
+                else
+                {
+                    // order components by name
+                    TreeMap componentMap = new TreeMap();
+                    for ( int i = 0; i < components.length; i++ )
+                    {
+                        Component component = components[i];
+                        componentMap.put( component.getName(), component );
+                    }
+
+                    // render components
+                    jw.key( "data" );
+                    jw.array();
+                    for ( Iterator ci = componentMap.values().iterator(); ci.hasNext(); )
+                    {
+                        component( jw, ( Component ) ci.next(), details );
+                    }
+                    jw.endArray();
+                }
+            }
+
+            jw.endObject();
+        }
+        catch ( JSONException je )
+        {
+            throw new IOException( je.toString() );
+        }
+    }
+
+
+    private void sendAjaxDetails( Component component, HttpServletResponse response ) throws IOException
+    {
+
+        // send the result
+        response.setContentType( "text/javascript" );
+
+        JSONWriter jw = new JSONWriter( response.getWriter() );
+        try
+        {
+            if ( component != null )
+            {
+                component( jw, component, true );
+            }
+        }
+        catch ( JSONException je )
+        {
+            throw new IOException( je.toString() );
+        }
+    }
+
+
+    private void component( JSONWriter jw, Component component, boolean details ) throws JSONException
+    {
+        String id = String.valueOf( component.getId() );
+        String name = component.getName();
+        int state = component.getState();
+
+        jw.object();
+
+        // component information
+        jw.key( "id" );
+        jw.value( id );
+        jw.key( "name" );
+        jw.value( name );
+        jw.key( "state" );
+        jw.value( toStateString( state ) );
+
+        // component actions
+        jw.key( "actions" );
+        jw.array();
+
+        jw.object();
+        jw.key( "name" );
+        jw.value( "Enable" );
+        jw.key( "link" );
+        jw.value( OPERATION_ENABLE );
+        jw.key( "enabled" );
+        jw.value( state == Component.STATE_DISABLED );
+        jw.endObject();
+
+        jw.object();
+        jw.key( "name" );
+        jw.value( "Disable" );
+        jw.key( "link" );
+        jw.value( OPERATION_DISABLE );
+        jw.key( "enabled" );
+        jw.value( state != Component.STATE_DISABLED && state != Component.STATE_DESTROYED );
+        jw.endObject();
+
+        jw.endArray();
+
+        // component details
+        if ( details )
+        {
+            gatherComponentDetails( jw, component );
+        }
+
+        jw.endObject();
+    }
+
+
+    private void gatherComponentDetails( JSONWriter jw, Component component ) throws JSONException
+    {
+        jw.key( "props" );
+        jw.array();
+
+        keyVal( jw, "Bundle", component.getBundle().getSymbolicName() + " (" + component.getBundle().getBundleId()
+            + ")" );
+        keyVal( jw, "Default State", component.isDefaultEnabled() ? "enabled" : "disabled" );
+        keyVal( jw, "Activation", component.isImmediate() ? "immediate" : "delayed" );
+
+        listServices( jw, component );
+        listReferences( jw, component );
+        listProperties( jw, component );
+
+        jw.endArray();
+    }
+
+
+    private void listServices( JSONWriter jw, Component component )
+    {
+        String[] services = component.getServices();
+        if ( services == null )
+        {
+            return;
+        }
+
+        keyVal( jw, "Service Type", component.isServiceFactory() ? "service factory" : "service" );
+
+        StringBuffer buf = new StringBuffer();
+        for ( int i = 0; i < services.length; i++ )
+        {
+            if ( i > 0 )
+            {
+                buf.append( "<br />" );
+            }
+            buf.append( services[i] );
+        }
+
+        keyVal( jw, "Services", buf.toString() );
+    }
+
+
+    private void listReferences( JSONWriter jw, Component component )
+    {
+        Reference[] refs = component.getReferences();
+        if ( refs != null )
+        {
+            for ( int i = 0; i < refs.length; i++ )
+            {
+                StringBuffer buf = new StringBuffer();
+                buf.append( refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied" ).append( "<br />" );
+                buf.append( "Service Name: " ).append( refs[i].getServiceName() ).append( "<br />" );
+                if ( refs[i].getTarget() != null )
+                {
+                    buf.append( "Target Filter: " ).append( refs[i].getTarget() ).append( "<br />" );
+                }
+                buf.append( "Multiple: " ).append( refs[i].isMultiple() ? "multiple" : "single" ).append( "<br />" );
+                buf.append( "Optional: " ).append( refs[i].isOptional() ? "optional" : "mandatory" ).append( "<br />" );
+                buf.append( "Policy: " ).append( refs[i].isStatic() ? "static" : "dynamic" ).append( "<br />" );
+
+                // list bound services
+                ServiceReference[] boundRefs = refs[i].getServiceReferences();
+                if ( boundRefs != null && boundRefs.length > 0 )
+                {
+                    for ( int j = 0; j < boundRefs.length; j++ )
+                    {
+                        if ( j > 0 )
+                        {
+                            buf.append( "<br />" );
+                        }
+
+                        buf.append( "Bound Service ID " );
+                        buf.append( boundRefs[j].getProperty( Constants.SERVICE_ID ) );
+
+                        String name = ( String ) boundRefs[j].getProperty( ComponentConstants.COMPONENT_NAME );
+                        if ( name == null )
+                        {
+                            name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_PID );
+                            if ( name == null )
+                            {
+                                name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_DESCRIPTION );
+                            }
+                        }
+                        if ( name != null )
+                        {
+                            buf.append( " (" );
+                            buf.append( name );
+                            buf.append( ")" );
+                        }
+                    }
+                }
+                else
+                {
+                    buf.append( "No Services bound" );
+                }
+                buf.append( "<br />" );
+
+                keyVal( jw, "Reference " + refs[i].getName(), buf.toString() );
+            }
+        }
+    }
+
+
+    private void listProperties( JSONWriter jw, Component component )
+    {
+        Dictionary props = component.getProperties();
+        if ( props != null )
+        {
+            StringBuffer buf = new StringBuffer();
+            TreeSet keys = new TreeSet( Collections.list( props.keys() ) );
+            for ( Iterator ki = keys.iterator(); ki.hasNext(); )
+            {
+                String key = ( String ) ki.next();
+                buf.append( key ).append( " = " );
+
+                Object prop = props.get( key );
+                if ( prop.getClass().isArray() )
+                {
+                    prop = Arrays.asList( ( Object[] ) prop );
+                }
+                buf.append( prop );
+                if ( ki.hasNext() )
+                {
+                    buf.append( "<br />" );
+                }
+            }
+
+            keyVal( jw, "Properties", buf.toString() );
+        }
+
+    }
+
+
+    private void keyVal( JSONWriter jw, String key, Object value )
+    {
+        if ( key != null && value != null )
+        {
+            try
+            {
+                jw.object();
+                jw.key( "key" );
+                jw.value( key );
+                jw.key( "value" );
+                jw.value( value );
+                jw.endObject();
+            }
+            catch ( JSONException je )
+            {
+                // don't care
+            }
+        }
+    }
+
+
+    static String toStateString( int state )
+    {
+        switch ( state )
+        {
+            case Component.STATE_DISABLED:
+                return "disabled";
+            case Component.STATE_ENABLED:
+                return "enabled";
+            case Component.STATE_UNSATISFIED:
+                return "unsatisifed";
+            case Component.STATE_ACTIVATING:
+                return "activating";
+            case Component.STATE_ACTIVE:
+                return "active";
+            case Component.STATE_REGISTERED:
+                return "registered";
+            case Component.STATE_FACTORY:
+                return "factory";
+            case Component.STATE_DEACTIVATING:
+                return "deactivating";
+            case Component.STATE_DESTROYED:
+                return "destroyed";
+            default:
+                return String.valueOf( state );
+        }
+    }
+
+
+    protected long getComponentId( HttpServletRequest request )
+    {
+        String componentIdPar = request.getParameter( ComponentsServlet.COMPONENT_ID );
+        if ( componentIdPar == null )
+        {
+            String info = request.getPathInfo();
+            componentIdPar = info.substring( info.lastIndexOf( '/' ) + 1 );
+        }
+
+        try
+        {
+            return Long.parseLong( componentIdPar );
+        }
+        catch ( NumberFormatException nfe )
+        {
+            // TODO: log
+        }
+
+        // no bundleId or wrong format
+        return -1;
+    }
+
+
+    private ScrService getScrService()
+    {
+        return ( ScrService ) getService( SCR_SERVICE );
+    }
+}
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 7d194ba..4f9d7bb 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
@@ -179,7 +179,11 @@
 
         if ( bundle != null )
         {
-            if ( "start".equals( action ) )
+            if ( action == null )
+            {
+                success = true;
+            }
+            else if ( "start".equals( action ) )
             {
                 // start bundle
                 success = true;
@@ -305,7 +309,8 @@
 
         PrintWriter pw = response.getWriter();
 
-        String appRoot = (String) request.getAttribute( OsgiManager.ATTR_APP_ROOT );
+        String appRoot = ( String ) request.getAttribute( OsgiManager.ATTR_APP_ROOT );
+        pw.println( "<script src='" + appRoot + "/res/ui/datatable.js' language='JavaScript'></script>" );
         pw.println( "<script src='" + appRoot + "/res/ui/bundles.js' language='JavaScript'></script>" );
 
         Util.startScript( pw );
@@ -314,9 +319,13 @@
         try
         {
             jw.object();
-            jw.key( "startlevel" );
+
+            jw.key( "startLevel" );
             jw.value( getStartLevel().getInitialBundleStartLevel() );
 
+            jw.key( "numActions" );
+            jw.value( 4 );
+
             Bundle bundle = getBundle( request.getPathInfo() );
             Bundle[] bundles = ( bundle != null ) ? new Bundle[]
                 { bundle } : this.getBundles();
@@ -326,7 +335,7 @@
             {
                 Util.sort( bundles );
 
-                jw.key( "bundles" );
+                jw.key( "data" );
 
                 jw.array();
 
@@ -338,6 +347,11 @@
                 jw.endArray();
 
             }
+            else
+            {
+                jw.key( "error" );
+                jw.value( "No Bundles installed currently" );
+            }
 
             jw.endObject();
 
@@ -348,7 +362,7 @@
         }
 
         pw.println( ";" );
-        pw.println( "render(bundleListData.startlevel, bundleListData.bundles);" );
+        pw.println( "renderBundle( bundleListData );" );
         Util.endScript( pw );
     }
 
@@ -356,20 +370,31 @@
     private void bundleInfo( JSONWriter jw, Bundle bundle, boolean details ) throws JSONException
     {
         jw.object();
-        jw.key( "bundleId" );
+        jw.key( "id" );
         jw.value( bundle.getBundleId() );
         jw.key( "name" );
         jw.value( Util.getName( bundle ) );
         jw.key( "state" );
         jw.value( toStateString( bundle.getState() ) );
-        jw.key( "hasStart" );
-        jw.value( hasStart( bundle ) );
-        jw.key( "hasStop" );
-        jw.value( hasStop( bundle ) );
-        jw.key( "hasUpdate" );
-        jw.value( hasUpdate( bundle ) );
-        jw.key( "hasUninstall" );
-        jw.value( hasUninstall( bundle ) );
+
+        jw.key( "actions" );
+        jw.array();
+
+        if ( bundle.getBundleId() == 0 )
+        {
+            jw.value( false );
+            jw.value( false );
+            jw.value( false );
+            jw.value( false );
+        }
+        else
+        {
+            action( jw, hasStart( bundle ), "start", "Start" );
+            action( jw, hasStop( bundle ), "stop", "Stop" );
+            action( jw, hasUpdate( bundle ), "update", "Update" );
+            action( jw, hasUninstall( bundle ), "uninstall", "Uninstall" );
+        }
+        jw.endArray();
 
         if ( details )
         {
@@ -408,6 +433,19 @@
     }
 
 
+    private void action( JSONWriter jw, boolean enabled, String op, String opLabel ) throws JSONException
+    {
+        jw.object();
+        jw.key( "enabled" );
+        jw.value( enabled );
+        jw.key( "name" );
+        jw.value( opLabel );
+        jw.key( "link" );
+        jw.value( op );
+        jw.endObject();
+    }
+
+
     private boolean hasStart( Bundle bundle )
     {
         return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED;
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index b0c28c4..a45fd0b 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -41,7 +41,7 @@
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter;
-import org.apache.felix.webconsole.internal.compendium.ComponentRenderAction;
+import org.apache.felix.webconsole.internal.compendium.ComponentsServlet;
 import org.apache.felix.webconsole.internal.compendium.ConfigManager;
 import org.apache.felix.webconsole.internal.core.BundlesServlet;
 import org.apache.felix.webconsole.internal.core.InstallAction;
@@ -125,7 +125,7 @@
     private static final String DEFAULT_MANAGER_ROOT = "/system/console";
 
     private static final Class[] PLUGIN_CLASSES =
-        { ComponentConfigurationPrinter.class, ComponentRenderAction.class, ConfigManager.class, BundlesServlet.class,
+        { ComponentConfigurationPrinter.class, ComponentsServlet.class, ConfigManager.class, BundlesServlet.class,
             InstallAction.class, SetStartLevelAction.class, ConfigurationRender.class, GCAction.class,
             ShutdownAction.class, ShutdownRender.class, VMStatRender.class, BundleRepositoryRender.class,
             LicenseServlet.class };
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
index 35ff677..e272b51 100644
--- a/webconsole/src/main/resources/res/ui/bundles.js
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -15,111 +15,29 @@
  * limitations under the License.
  */
 
-function render(/* int */ startlevel, /* Array of Bundle Object */ bundles)
+function renderBundle( /* Array of Data Objects */ bundleData )
 {
 
-    header();
-
-    installForm( startlevel );
+    // number of actions plus 3 -- id, name and state
+    var columns = bundleData.numActions + 3;
+    var startLevel = bundleData.startLevel;
     
-    document.write( "<tr class='content'>" );
-    document.write( "<td colspan='7' class='content'>&nbsp;</th>" );
-    document.write( "</tr>" );
+    header( columns );
 
-    tableHeader();
-
-    if ( !bundles )
+    installForm( startLevel );
+    
+    if (bundleData.error)
     {
-        document.write( "<tr class='content'>" );
-        document.write( "<td class='content' colspan='6'>No Bundles installed currently</td>" );
-        document.write( "</tr>" );
+        error( columns, bundleData.error );
     }
     else
     {
-        for ( var i = 0; i < bundles.length; i++ )
-        {
-            bundle( bundles[i] );
-        }
+        data ( bundleData.data );
     }
 
-    document.write( "<tr class='content'>" );
-    document.write( "<td colspan='7' class='content'>&nbsp;</th>" );
-    document.write( "</tr>" );
+    installForm( startLevel );
 
-    installForm( startlevel );
-
-    footer();
-}
-
-
-function header()
-{
-    document.write( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-}
-
-
-function tableHeader()
-{
-    document.write( "<tr class='content'>" );
-    document.write( "<th class='content'>ID</th>" );
-    document.write( "<th class='content' width='100%'>Name</th>" );
-    document.write( "<th class='content'>Status</th>" );
-    document.write( "<th class='content' colspan='4'>Actions</th>" );
-    document.write( "</tr>" );
-}
-
-
-function footer()
-{
-    document.write( "</table>" );
-}
-
-
-function bundle( /* Bundle */ bundle )
-{  
-    document.write( "<tr id='bundle" + bundle.bundleId + "'>" );
-    document.write( bundleInternal( bundle ) );
-    document.write( "</tr>" );
-    document.write( "<tr id='bundle" + bundle.bundleId + "_details'>" );
-    if (bundle.props)
-    {
-        document.write( bundleDetails( bundle.props ) );
-    }
-    document.write( "</tr>" );
-}
-
-   
-/* String */ function bundleInternal( /* Bundle */ bundle )
-{
-    var icon = (bundle.props) ? "down" : "right";
-    var theBundle = "<td class='content right'>" + bundle.bundleId + "</td>";
-    theBundle += "<td class='content'><img src='" + appRoot + "/res/imgs/" + icon + ".gif' onClick='showDetails(" + bundle.bundleId + ")' id='bundle" + bundle.bundleId + "_inline' />";
-    theBundle += " <a href='" + appRoot + "/bundles/" + bundle.bundleId + "'>" + bundle.name + "</a></td>";
-    theBundle += "<td class='content center'>" + bundle.state + "</td>";
-
-    // no buttons for system bundle
-    if ( bundle.bundleId == 0 )
-    {
-        theBundle += "<td class='content' colspan='4'>&nbsp;</td>";
-    }
-    else
-    {
-        theBundle += actionForm( bundle.hasStart, bundle.bundleId, "start", "Start" );
-        theBundle += actionForm( bundle.hasStop, bundle.bundleId, "stop", "Stop" );
-        theBundle += actionForm( bundle.hasUpdate, bundle.bundleId, "update", "Update" );
-        theBundle += actionForm( bundle.hasUninstall, bundle.bundleId, "uninstall", "Uninstall" );
-    }
-
-    return theBundle;
-}
-
-
-/* String */ function actionForm( /* boolean */ enabled, /* long */ bundleId, /* String */ action, /* String */ actionLabel )
-{
-    var theButton = "<td class='content' align='right'>";
-    theButton += "<input class='submit' type='button' value='" + actionLabel + "'" + ( enabled ? "" : "disabled" ) + " onClick='changeBundle(" + bundleId + ", \"" + action + "\");' />";
-    theButton += "</td>";
-    return theButton;
+    footer( columns );
 }
 
 
@@ -142,108 +60,3 @@
     document.write( "</tr>" );
     document.write( "</form>" );
 }
-
-
-function changeBundle(/* long */ bundleId, /* String */ action)
-{
-    var parm = "bundles/" + bundleId + "?action=" + action;
-    sendRequest('POST', parm, bundleChanged);
-}
-
-    
-function bundleChanged(obj)
-{
-    if (obj.reload)
-    {
-        document.location = document.location;
-    }
-    else
-    {
-        var bundleId = obj.bundleId;
-        if (obj.state)
-        {
-            // has status, so draw the line
-            var span = document.getElementById('bundle' + bundleId);
-            if (span)
-            {
-                span.innerHTML = bundleInternal( obj );
-            }
-            
-            if (obj.props)
-            {
-                var span = document.getElementById('bundle' + bundleId + '_details');
-                if (span && span.innerHTML)
-                {
-                    span.innerHTML = bundleDetails( obj.props );
-                }
-            }
-            
-        }
-        else
-        {
-            // no status, bundle has been uninstalled
-            var span = document.getElementById('bundle' + bundleId);
-            if (span)
-            {
-                span.parentNode.removeChild(span);
-            }
-            var span = document.getElementById('bundle' + bundleId + '_details');
-            if (span)
-            {
-                span.parentNode.removeChild(span);
-            }
-        }
-    }    
-}
-
-    
-function showDetails(bundleId) {
-    var span = document.getElementById('bundle' + bundleId + '_details');
-    if (span)
-    {
-        if (span.innerHTML)
-        {
-            span.innerHTML = '';
-            newLinkValue(bundleId, appRoot + "/res/imgs/right.gif");
-        }
-        else
-        {
-            sendRequest('GET', appRoot + "/bundles/" + bundleId + ".json", displayBundleDetails);
-            newLinkValue(bundleId, appRoot + "/res/imgs/down.gif");
-        }
-    }
-}
-
-
-function displayBundleDetails(obj) {
-    var span = document.getElementById('bundle' + obj.bundleId + '_details');
-    if (span)
-    {
-        span.innerHTML = bundleDetails( obj.props );
-    }
-}
-
-
-function newLinkValue(bundleId, newLinkValue)
-{
-    
-    var link = document.getElementById("bundle" + bundleId + "_inline");
-    if (link)
-    {
-        link.src = newLinkValue;
-    }
-}
-
-
-/* String */ function bundleDetails( props )
-{
-        var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"6\"><table broder=\"0\">';
-        for (var i=0; i < props.length; i++)
-        {
-            innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';
-        }
-        innerHtml += '</table></td>';
-        
-        return innerHtml;
-}
-
diff --git a/webconsole/src/main/resources/res/ui/datatable.js b/webconsole/src/main/resources/res/ui/datatable.js
new file mode 100644
index 0000000..8ffb4d8
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/datatable.js
@@ -0,0 +1,258 @@
+/*
+ * 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.
+ */
+
+function renderDataTable( /* Array of Data Objects */ components )
+{
+    // number of actions plus 3 -- id, name and state
+    var columns = components.numActions + 3;
+    
+    header( columns );
+
+    if (components.error)
+    {
+        error( columns, components.error );
+    }
+    else
+    {
+        data ( components.data );
+    }
+
+    footer( columns );
+}
+
+
+function header( /* int */ columns )
+{
+    document.write( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
+
+    document.write( "<tr class='content'>" );
+    document.write( "<td colspan='" + columns + "' class='content'>&nbsp;</th>" );
+    document.write( "</tr>" );
+
+    document.write( "<tr class='content'>" );
+    document.write( "<th class='content'>ID</th>" );
+    document.write( "<th class='content' width='100%'>Name</th>" );
+    document.write( "<th class='content'>Status</th>" );
+    document.write( "<th class='content' colspan='" + (columns - 3) + "'>Actions</th>" );
+    document.write( "</tr>" );
+
+}
+
+
+function error( /* int */ columns, /* String */ message )
+{
+    document.write( "<tr class='content'>" );
+    document.write( "<td class='content'>&nbsp;</td>" );
+    document.write( "<td class='content' colspan='" + (columns - 1) + "'>" + message + "</td>" );
+    document.write( "</tr>" );
+}
+
+
+function data( /* Array of Object */ dataArray )
+{
+    // render components
+    for ( var idx in dataArray )
+    {
+        entry( dataArray[idx] );
+    }
+}
+
+
+function footer( /* int */ columns )
+{
+    document.write( "<tr class='content'>" );
+    document.write( "<td colspan='" + columns + "' class='content'>&nbsp;</th>" );
+    document.write( "</tr>" );
+
+    document.write( "</table>" );
+}
+
+
+function entry( /* Object */ dataEntry )
+{
+    document.write( "<tr id='entry" + dataEntry.id + "'>" );
+    document.write( entryInternal( dataEntry ) );
+    document.write( "</tr>" );
+
+    // dataEntry detailed properties
+    document.write( "<tr id='entry" + dataEntry.id + "_details'>" );
+    if (dataEntry.props)
+    {
+        document.write( getDataEntryDetails( dataEntry.props ) );
+    }
+    document.write( "</tr>" );
+}
+
+
+/* String */ function entryInternal( /* Object */ dataEntry )
+{
+    var id = dataEntry.id;
+    var name = dataEntry.name;
+    var state = dataEntry.state;
+    var icon = (dataEntry.props) ? "down" : "right";
+
+    var html = "<td class='content right'>" + id + "</td>";
+    html += "<td class='content'>";
+    html += "<img src='" + appRoot + "/res/imgs/" + icon + ".gif' onClick='showDataEntryDetails(" + id + ")' id='entry" + id + "_inline' />";
+    html += "<a href='" + pluginRoot + "/" + id + "'>" + name + "</a></td>";
+
+    html += "<td class='content center'>" + state + "</td>";
+
+    for ( var aidx in dataEntry.actions )
+    {
+        var action = dataEntry.actions[aidx];
+        html += actionButton( action.enabled, id, action.link, action.name );
+    }
+    
+    return html;
+}
+
+
+/* String */ function actionButton( /* boolean */ enabled, /* long */ id, /* String */ op, /* String */ opLabel )
+{
+    var theButton = "<td class='content' align='right'>";
+    if ( op )
+    {
+        theButton += "<input class='submit' type='button' value='" + opLabel + "'" + ( enabled ? "" : "disabled" ) + " onClick='changeDataEntryState(" + id + ", \"" + op + "\");' />";
+    }
+    else
+    {
+        theButton += "&nbsp;";
+    }
+    theButton += "</td>";
+    return theButton;
+}
+
+
+/* String */ function getDataEntryDetails( /* Array of Object */ details )
+{
+    var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"4\"><table broder=\"0\">';
+    for (var idx in details)
+    {
+        var prop = details[idx];
+        innerHtml += '<tr><td valign=\"top\" noWrap>' + prop.key + '</td><td valign=\"top\">' + prop.value + '</td></tr>';
+    }
+    innerHtml += '</table></td>';
+    return innerHtml;
+ }
+
+ 
+function showDetails(bundleId) {
+    var span = document.getElementById('bundle' + bundleId + '_details');
+}
+
+
+function showDataEntryDetails( id )
+{
+    var span = document.getElementById( 'entry' + id + '_details' );
+    if (span)
+    {
+        if (span.innerHTML)
+        {
+            span.innerHTML = '';
+            newLinkValue( id, appRoot + "/res/imgs/right.gif" );
+        }
+        else
+        {
+            sendRequest( 'POST', pluginRoot + '/' + id, displayDataEntryDetails );
+            newLinkValue( id, appRoot + "/res/imgs/down.gif" );
+        }
+    }
+}
+
+
+function newLinkValue( /* long */ id, /* String */ newLinkValue )
+{
+    
+    var link = document.getElementById( "entry" + id + "_inline" );
+    if (link)
+    {
+        link.src = newLinkValue;
+    }
+}
+
+
+function displayDataEntryDetails( obj )
+{
+    var span = document.getElementById('entry' + obj.id + '_details');
+    if (!span)
+    {
+        return;
+    }
+    
+    span.innerHTML = getDataEntryDetails( obj.props );
+}
+
+
+function changeDataEntryState(/* long */ id, /* String */ action)
+{
+    var parm = pluginRoot + "/" + id + "?action=" + action;
+    sendRequest('POST', parm, dataEntryStateChanged);
+}
+
+    
+function dataEntryStateChanged(obj)
+{
+    if (obj.reload)
+    {
+        document.location = document.location;
+    }
+    else
+    {
+        var id = obj.id;
+        if (obj.state)
+        {
+            // has status, so draw the line
+            if (obj.props)
+            {
+                var span = document.getElementById('entry' + id + '_details');
+                if (span && span.innerHTML)
+                {
+                    span.innerHTML = getDataEntryDetails( obj.props );
+                }
+                else
+                {
+                    obj.props = false;
+                }
+            }
+
+            var span = document.getElementById('entry' + id);
+            if (span)
+            {
+                span.innerHTML = entryInternal( obj );
+            }
+            
+            
+        }
+        else
+        {
+            // no status, dataEntry has been removed/uninstalled 
+            var span = document.getElementById('entry' + id);
+            if (span)
+            {
+                span.parentNode.removeChild(span);
+            }
+            var span = document.getElementById('entry' + id + '_details');
+            if (span)
+            {
+                span.parentNode.removeChild(span);
+            }
+        }
+    }    
+}
+
+