FELIX-569 : Improve Configuration Page - Apply patch from Valentin Valchev

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@921443 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
index 5ae091e..fb8e90a 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
@@ -41,6 +41,7 @@
 
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
+import org.apache.felix.webconsole.internal.Util;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -131,6 +132,11 @@
 
                 WebConsoleUtil.sendRedirect(request, response, redirect);
             }
+            else
+            {
+                response.setContentType("text/plain");
+                response.getWriter().print("true");
+            }
 
             return;
         }
@@ -144,7 +150,8 @@
         if ( request.getParameter( "unbind" ) != null )
         {
             config.setBundleLocation( null );
-            WebConsoleUtil.sendRedirect( request, response, config.getPid() );
+            response.setContentType("text/plain");
+            response.getWriter().print("true");
             return;
         }
 
@@ -212,7 +219,7 @@
                 while ( i.hasNext() ) {
                     final String servicePid = i.next().toString();
 
-                    final Configuration config = this.getConfiguration(ca, servicePid);
+                    final Configuration config = getConfiguration(ca, servicePid);
                     if ( config != null ) {
                         if ( printColon ) {
                             pw.print(',');
@@ -283,7 +290,7 @@
         try
         {
             json.put("status", ca != null ? Boolean.TRUE : Boolean.FALSE);
-            listConfigurations(json, ca, pidFilter, locale);
+            listConfigurations(json, ca, pidFilter, locale, loc);
             listFactoryConfigurations(json, pidFilter, locale);
         }
         catch (JSONException e)
@@ -313,7 +320,7 @@
     }
 
 
-    private Configuration getConfiguration( ConfigurationAdmin ca, String pid )
+    private static final Configuration getConfiguration( ConfigurationAdmin ca, String pid )
     {
         if ( ca != null && pid != null )
         {
@@ -367,7 +374,7 @@
     }
 
     private final void listConfigurations(JSONObject json, ConfigurationAdmin ca,
-        String pidFilter, String locale)
+        String pidFilter, String locale, Locale loc)
     {
         try
         {
@@ -411,7 +418,26 @@
             {
                 String id = (String) i.next();
                 Object name = optionsPlain.get(id);
-                json.append("pids", new JSONObject().put("id", id).put("name", name));
+
+                final Configuration config = getConfiguration(ca, id);
+                JSONObject data = new JSONObject().put("id", id).put("name", name);
+                if (null != config)
+                {
+                    final String fpid = config.getFactoryPid();
+                    if (null != fpid)
+                    {
+                        data.put("fpid", fpid);
+                    }
+
+                    final Bundle bundle = getBoundBundle(config);
+                    if (null != bundle)
+                    {
+                        data.put("bundle", bundle.getBundleId());
+                        data.put("bundle_name", Util.getName(bundle, loc));
+                    }
+                }
+
+                json.append("pids", data);
             }
         }
         catch (Exception e)
@@ -420,6 +446,24 @@
         }
     }
 
+    private final Bundle getBoundBundle(Configuration config)
+    {
+        if (null == config)
+            return null;
+        final String location = config.getBundleLocation();
+        if (null == location)
+            return null;
+
+        final Bundle bundles[] = getBundleContext().getBundles();
+        for (int i = 0; bundles != null && i < bundles.length; i++)
+        {
+            if (bundles[i].getLocation().equals(location))
+                return bundles[i];
+
+        }
+        return null;
+    }
+
     private SortedMap getServices( String serviceClass, String serviceFilter, String locale, boolean ocdRequired )
         throws InvalidSyntaxException
     {
@@ -767,7 +811,7 @@
                 Configuration config = ca.getConfiguration( pid, null );
                 config.delete();
             }
-            return request.getHeader( "Referer" );
+            return null; // return request.getHeader( "Referer" );
         }
 
         String factoryPid = request.getParameter( ConfigManager.factoryPID );
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index 3e1eba6..c431bf1 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -170,7 +170,10 @@
 config.del.config=Configuration: 
 config.del.bundle=Bundle: 
 config.unbind.ask=Are you sure to unbind this configuration ?
-
+config.factories.title=Factory Configurations
+config.configurations.title=Configurations
+config.create.tip=Create new factory configuration
+config.edit.tip=Edit the configuration values
 
 # License plugin
 license.status.ok=The following bundles contains license files.
diff --git a/webconsole/src/main/resources/res/ui/config.css b/webconsole/src/main/resources/res/ui/config.css
index 8f96a81..55e62f7 100644
--- a/webconsole/src/main/resources/res/ui/config.css
+++ b/webconsole/src/main/resources/res/ui/config.css
@@ -27,6 +27,8 @@
 	margin:0;
 	padding:0
 }
-.multiselect {
-    display: block;
-}
+.multiselect { display: block }
+.col_Actions { width: 6em !important }
+.pointer { cursor: pointer }
+#editor, div.ui-dialog-buttonpane button { font-size: 50% }
+#factoryTableCaption { margin-top: 1.5em }
diff --git a/webconsole/src/main/resources/res/ui/config.js b/webconsole/src/main/resources/res/ui/config.js
index 01d2624..7e96f45 100644
--- a/webconsole/src/main/resources/res/ui/config.js
+++ b/webconsole/src/main/resources/res/ui/config.js
@@ -14,58 +14,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+// tables container - will get hidden, when no config service available
+var configContent = false;
+
+// config table list
+var configTable = false;
+var configBody  = false;
+var configRow = false;
+
+// factories table list
+var factoryTable = false;
+var factoryBody  = false;
+var factoryRow = false;
 
 
-function configure() {
-    var select = document.getElementById('configSelection_pid');
-    var pid = select.options[select.selectedIndex].value;
-    var parm = pluginRoot + '/' + pid;
-	$.post(parm, null, displayConfigForm, "json");
-}
+// editor dialog
+var editor = false;
 
-
-function create() {
-    var select = document.getElementById('configSelection_factory');
-    var pid = select.options[select.selectedIndex].value;
-    var parm = pluginRoot + '/' + pid + '?create=true';
-	$.post(parm, null, displayConfigForm, "json");
+function configure(pid, create) {
+	var uri = pluginRoot + '/' + pid;
+	$.post(create ? uri + '?create=1' : uri, null, displayConfigForm, 'json');
 }
 
 function displayConfigForm(obj) {
-	var span1 = document.getElementById('configField');
-	var span2 = document.getElementById('factoryField');
-	if (!span1 && !span2) {
-		return;
-	} 
-    
-    var parent = span1 ? span1.parentNode : span2.parentNode;
-    
-    clearChildren( parent );
-    
-    if (span1) {
-        parent.appendChild( span1 );
-    }
-    if (span2) {
-        parent.appendChild( span2 );
-    }
-    
-    var trEl = tr( null );
-    var tdEl = createElement( "th", null, { colSpan: "2" } );
-    addText( tdEl, obj.title );
-    trEl.appendChild( tdEl );
-    parent.appendChild( trEl );
+	var parent = document.getElementById('editorTable');
+	clearChildren( parent )
 
-    trEl = tr( );
+    var trEl = tr( );
     parent.appendChild( trEl );
     
-    tdEl = td( );
-    addText( tdEl, "\u00a0" );
-    trEl.appendChild( tdEl );
-    
-    tdEl = td( );
+    var tdEl = td( null, { colSpan: "2" } );
     trEl.appendChild( tdEl );
     
     var formEl = createElement( "form", null, {
+			id    : "editorForm",
             method: "POST",
             action: pluginRoot + "/" + obj.pid
         });
@@ -133,28 +115,9 @@
     {
         printForm(bodyEl, obj);
     }
-    
-    trEl = tr( );
-    bodyEl.appendChild( trEl );
-    
-    tdEl = td( );
-    addText( tdEl, "\u00a0" );
-    trEl.appendChild( tdEl );
-    
-    tdEl = td( );
-    trEl.appendChild( tdEl );
-
-    // define this TD as innerHTML otherwise the onClick event handler
-    // of the Delete button is not accepted by IE6 (!)...    
-    var innerHTML = '<input type="submit" name="submit" value="'+i18n.save+'" />';
-    innerHTML += '&nbsp;&nbsp;&nbsp;';
-    innerHTML += '<input type="reset" name="reset" value="'+i18n.reset+'" />';
-    innerHTML += '&nbsp;&nbsp;&nbsp;';
-    innerHTML += '<input type="submit" name="delete" value="'+i18n.del+'" onClick="return confirmDelete(\'' + obj.pid + '\', \'' + obj.bundleLocation + '\');"/>';
-    tdEl.innerHTML = innerHTML;
 
     printConfigurationInfo(parent, obj);
-	initStaticWidgets($(parent));
+	initStaticWidgets(editor.attr('__pid', obj.pid).dialog('option', 'title', obj.title).dialog('open'));
 }
 
 function printTextArea(/* Element */ parent, props )
@@ -291,29 +254,7 @@
             ])
         ])
     );
-    
-    if (obj.bundleLocation)
-    {         
-        var form = createElement( "form", null, {
-                        method: "POST",
-                        action: pluginRoot + "/" + obj.pid
-                    });
 
-        // define this form contents as innerHTML otherwise the onClick
-        // event handler of the Unbind button is not accepted by IE6 (!)...
-        var formInner = '<input type="hidden" name="unbind" value="true"/>';
-        formInner += '<input type="submit" name="submit" value="'+i18n.unbind_btn+'" class="ui-state-default ui-corner-all" title="'+i18n.unbind_tip+'"  onClick="return confirmUnbind(\'' + obj.pid + '\', \'' + obj.bundleLocation + '\');"/>';
-        form.innerHTML = formInner;
-    
-        parent.appendChild( tr( null, null, [
-                td( null, null, [
-                    text( " " )
-                ]),
-                td( null, null, [ form ] )
-            ])
-        );
-    }
-	//$(form).ready(initStaticWidgets);
 }
 
 
@@ -475,7 +416,7 @@
 
 function confirmDelete(/* String */ title, /* String */ location)
 {
-    return configConfirm(i18n.unbind_ask, title, location);
+    return configConfirm(i18n.del_ask, title, location);
 }
 
 function confirmUnbind(/* String */ title, /* String */ location)
@@ -483,26 +424,95 @@
     return configConfirm(i18n.unbind_ask, title, location);
 }
 
-function addOption(list, target) 
-{
-	var html = "";
-	for (i in list) {
-		var sel = list[i].id == selectedPid ? '" selected="selected' : '';
-		html += '<option value="' + list[i].id + sel + '">' + list[i].name + '</option>';
-	}
-	if (html) target.html(html);
+function addConfig(conf) {
+	var tr = configRow.clone().appendTo(configBody);
+	tr.find('td:eq(0)').text(conf.fpid ? conf.fpid : '-'); // fpid
+	tr.find('td:eq(1)').text(conf.name).click(function() { // name & edit
+		configure(conf.id);
+	});
+	tr.find('td:eq(2)').html(conf.bundle ? '<a href="' + pluginRoot + '/../bundles/' + conf.bundle + '">' + conf.bundle_name + '</a>' : '-'); // binding
+	
+	// buttons
+	tr.find('li:eq(0)').click(function() { // edit
+		configure(conf.id);
+	});
+	tr.find('li:eq(2)').click(function() { // delete
+		if ( confirmDelete(conf.id, conf.bundle_name) ) {
+			$.post(pluginRoot + '/' + conf.id + '?apply=1&delete=1', null, function() {
+				document.location.href = pluginRoot;
+			}, 'json');
+		}
+	});
+	if (conf.bundle) tr.find('li:eq(1)').click(function() { // unbind
+		if ( confirmUnbind(conf.id, conf.bundle_name) ) {
+			$.post(pluginRoot + '/' + conf.id + '?apply=1&delete=1', null, function() {
+				document.location.href = pluginRoot + '/' + conf.id;
+			}, 'json');
+		}
+	}).removeClass('ui-state-disabled');
 }
 
-var configSelection_pid = false;
-var configSelection_factory = false;
+function addFactoryConfig(conf) {
+	var tr = factoryRow.clone().appendTo(factoryBody);
+	tr.find('td:eq(0)').text(conf.id); // fpid
+	tr.find('td:eq(1)').text(conf.name).click(function() { // name & edit
+		configure(conf.id, true);
+	});
+	// buttons
+	tr.find('li:eq(0)').click(function() { // edit
+		configure(conf.id, true);
+	});
+}
+
 $(document).ready(function() {
-	configSelection_pid = $('#configSelection_pid');
-	configSelection_factory = $('#configSelection_factory');
-	$(".statline").html(configData.status ? i18n.stat_ok : i18n.stat_missing);
-	$("#config_content").css("display", configData.status ? "block" : "none");
-	if (configData.status) {
-		addOption(configData.pids, $("#configSelection_pid"));
-		addOption(configData.fpids, $("#configSelection_factory"));
+	configContent = $('#configContent');
+	// config table list
+	configTable   = configContent.find('table:eq(0)').tablesorter({
+		headers: { 3: { sorter: false } },
+		textExtraction:mixedLinksExtraction
+	});
+	configBody    = configTable.find('tbody');
+	configRow     = configBody.find('tr').clone();
+
+	// factories table list
+	factoryTable  = configContent.find('table:eq(1)').tablesorter({
+		headers: { 2: { sorter: false } }
+	});
+	factoryBody   = factoryTable.find('tbody');
+	factoryRow    = factoryBody.find('tr').clone();
+	
+	// setup button - cannot inline in dialog option because of i18n
+	var _buttons = {};
+	_buttons[i18n.abort] = function() {
+		$(this).dialog('close');
 	}
-	if (selectedPid) configure();
+	_buttons[i18n.reset] = function() {
+		var form = document.getElementById('editorForm');
+		if (form) form.reset();
+	}
+	_buttons[i18n.save] = function() {
+		$.post(pluginRoot + '/' + $(this).attr('__pid') + '?' + $(this).find('form').serialize());
+		$(this).dialog('close');
+	}
+	// prepare editor, but don't open yet!
+	editor = $('#editor').dialog({
+		autoOpen : false,
+		modal    : true,
+		width    : '90%',
+		closeText: i18n.abort,
+		buttons  : _buttons
+	});
+
+	// display the configuration data
+	$(".statline").html(configData.status ? i18n.stat_ok : i18n.stat_missing);
+	if (configData.status) {
+		configBody.empty(); factoryBody.empty();
+
+		for(var i in configData.pids) addConfig(configData.pids[i]);
+		for(var i in configData.fpids) addFactoryConfig(configData.fpids[i]);
+		initStaticWidgets(configContent);
+	} else {
+		configContent.addClass('ui-helper-hidden');
+	}
+	if (selectedPid) configure(selectedPid);
 });
\ No newline at end of file
diff --git a/webconsole/src/main/resources/templates/config.html b/webconsole/src/main/resources/templates/config.html
index 6c4b3d2..34629c5 100644
--- a/webconsole/src/main/resources/templates/config.html
+++ b/webconsole/src/main/resources/templates/config.html
@@ -1,4 +1,4 @@
-<script type="text/javascript" src="res/ui/config.js"></script>
+<script type="text/javascript" src="res/ui/config.js"></script>
 <script type="text/javascript">
 // <![CDATA[
 // data
@@ -10,6 +10,7 @@
 	save         : '${save}',
 	reset        : '${reset}',
 	del          : '${delete}',
+	abort        : '${abort}',
 	props_title  : '${config.properties}',
 	props_enter  : '${config.properties.enter}', // "Enter Name-Value pairs of configuration properties"
 	cfg_title    : '${config.info.title}', // "Configuration Information"
@@ -22,10 +23,7 @@
 	del_ask      : '${config.del.ask}', // "Are you sure to delete this configuration ?";
 	del_config   : '${config.del.config}', // "Configuration: ";
 	del_bundle   : '${config.del.bundle}', // "Bundle: ";
-	unbind_ask   : '${config.unbind.ask}', // "Are you sure to unbind this configuration ?"
-	'xx' : '${config.form.properties}',
-	'xx' : '${config.form.properties}',
-
+	unbind_ask   : '${config.unbind.ask}' // "Are you sure to unbind this configuration ?"
 }
 // ]]>
 </script>
@@ -33,27 +31,65 @@
 <!-- status line -->
 <p class="statline">&nbsp;</p>
 
-<div id="config_content" >
-	<table class="nicetable">
-	<tr id="configField">
-		<td>Configurations</td>
-		<td>
-			<select name="pid" id="configSelection_pid" onchange="configure();">
-				<option>&nbsp;</option>
-			</select>
-			&nbsp;
-			<button onclick="configure();">Configure</button>
-		</td>
-	</tr>
-	<tr id="factoryField">
-		<td>Factory Configurations</td>
-		<td>
-			<select name="pid" id="configSelection_factory" onchange="create();">
-				<option>&nbsp;</option>
-			</select>
-			&nbsp;
-			<button onclick="create();">Create</button>
-		</td>
-	</tr>
+
+<div id="configContent" >
+	<div id="fPidsSelect" class="ui-widget-header ui-corner-top buttonGroup">
+		${config.configurations.title}
+	</div>
+	<table class="tablesorter nicetable noauto">
+		<thead>
+			<tr>
+				<th class="col_FPID">Factory PID</th>
+				<th class="col_Name">Name</th>
+				<th class="col_Binding">Bundle</th>
+				<th class="col_Actions" style="width: 7em">Actions</th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr>
+				<td>&nbsp;</td>
+				<td class="pointer">&nbsp;</td>
+				<td>&nbsp;</td>
+				<td>
+					<ul class="icons">
+						<li class="dynhover" title="${config.edit.tip}"><span class="ui-icon ui-icon-pencil">&nbsp;</span></li>
+						<li class="ui-state-disabled dynhover" title="${config.unbind.tip}"><span class="ui-icon ui-icon-transferthick-e-w">&nbsp;</span></li>
+						<li class="dynhover" title="${delete}"><span class="ui-icon ui-icon-trash">&nbsp;</span></li>
+					</ul>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+
+	<div id="factoryTableCaption" class="ui-widget-header ui-corner-top buttonGroup">
+		${config.factories.title}
+	</div>
+	<table class="tablesorter nicetable noauto">
+		<thead>
+			<tr>
+				<th class="col_FPID">Factory PID</th>
+				<th class="col_Name">Name</th>
+				<th class="col_Actions" style="width: 7em">Actions</th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr>
+				<td>&nbsp;</td>
+				<td class="pointer">&nbsp;</td>
+				<td>
+					<ul class="icons">
+						<li class="dynhover" title="${config.create.tip}"><span class="ui-icon ui-icon-plusthick">&nbsp;</span></li>
+					</ul>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+	
+</div>
+
+<!-- placeholder for property editor -->
+<div id="editor" class="ui-helper-hidden">
+	<table id="editorTable" class="nicetable">
+		<tr><td>&nbsp;</td></tr>
 	</table>
 </div>