FELIX-2162, FELIX-2171: rewrite the OBR webconsole page to be more scalable and display detailed informations about a given bundle
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@919175 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/resources/res/ui/jquery.uuid.js b/webconsole/src/main/resources/res/ui/jquery.uuid.js
new file mode 100644
index 0000000..f22bf2d
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/jquery.uuid.js
@@ -0,0 +1,24 @@
+/*
+Usage 1: define the default prefix by using an object with the property prefix as a parameter which contains a string value; {prefix: 'id'}
+Usage 2: call the function jQuery.uuid() with a string parameter p to be used as a prefix to generate a random uuid;
+Usage 3: call the function jQuery.uuid() with no parameters to generate a uuid with the default prefix; defaul prefix: '' (empty string)
+*/
+
+/*
+Generate fragment of random numbers
+*/
+jQuery._uuid_default_prefix = '';
+jQuery._uuidlet = function () {
+ return(((1+Math.random())*0x10000)|0).toString(16).substring(1);
+};
+/*
+Generates random uuid
+*/
+jQuery.uuid = function (p) {
+ if (typeof(p) == 'object' && typeof(p.prefix) == 'string') {
+ jQuery._uuid_default_prefix = p.prefix;
+ } else {
+ p = p || jQuery._uuid_default_prefix || '';
+ return(p+jQuery._uuidlet()+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+jQuery._uuidlet()+jQuery._uuidlet());
+ };
+};
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/obr.css b/webconsole/src/main/resources/res/ui/obr.css
new file mode 100644
index 0000000..a6ef84c
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/obr.css
@@ -0,0 +1,6 @@
+label {
+ font-family: Verdana, Arial, sans-serif;
+ font-size: 1em;
+ font-style: normal;
+ font-weight: normal;
+}
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/obr.js b/webconsole/src/main/resources/res/ui/obr.js
index c770d71..3b2c9fc 100644
--- a/webconsole/src/main/resources/res/ui/obr.js
+++ b/webconsole/src/main/resources/res/ui/obr.js
@@ -22,6 +22,29 @@
var searchField = false;
var ifStatusOK = false;
+//This prototype is provided by the Mozilla foundation and
+//is distributed under the MIT license.
+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
+if (!Array.prototype.map)
+{
+ Array.prototype.map = function(fun /*, thisp*/)
+ {
+ var len = this.length;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ res[i] = fun.call(thisp, this[i], i, this);
+ }
+
+ return res;
+ };
+}
+
/* displays a date in the user's local timezone */
function localTm(time) {
return (time ? new Date(time) : new Date()).toLocaleString();
@@ -38,43 +61,337 @@
}
}
+function showDetails( symbolicname, version ) {
+ window.location.href = window.location.pathname + '?details&symbolicname=' + symbolicname + '&version=' + version;
+}
+
+function showVersions( symbolicname ) {
+ var _id = symbolicname.replace(/\./g, '_');
+ $("#block" + _id).append("<div id='pluginInlineVersions" + _id + "' style='margin-left: 4em'><ul/></div>");
+ $("#img" + _id).each(function() {
+ $(this).
+ removeClass('ui-icon-triangle-1-e').//right
+ addClass('ui-icon-triangle-1-s');//down
+ });
+ $("#entry" + _id).each(function() {
+ $(this).
+ unbind('click').
+ click(function() {hideVersions(symbolicname)}).
+ attr("title", "Hide Versions");
+ });
+ var versions = [];
+ for (var i in obrData.resources ) {
+ if (obrData.resources[i].symbolicname == symbolicname) {
+ versions.push(obrData.resources[i].version);
+ }
+ }
+ versions.sort();
+ for (var i in versions) {
+ var txt = "<li><a href='javascript: showDetails(\"" + symbolicname + "\",\"" + versions[i] + "\")'>" + versions[i] + "</a></li>";
+ $("#pluginInlineVersions" + _id + " > ul").append(txt);
+ }
+}
+
+function hideVersions( symbolicname ) {
+ var _id = symbolicname.replace(/\./g, '_');
+ $("#img" + _id).each(function() {
+ $(this).
+ removeClass('ui-icon-triangle-1-s').//down
+ addClass('ui-icon-triangle-1-e');//right
+ });
+ $("#pluginInlineVersions" + _id).each(function() {
+ $(this).
+ remove();
+ });
+ $("#entry" + _id).each(function() {
+ $(this).
+ unbind('click').
+ click(function() {showVersions(symbolicname)}).
+ attr("title", "Show Versions");
+ });
+}
+
function renderResource(res) {
- // aply filtering
- var match = searchField.val();
- if (match) {
- match = new RegExp( match );
- if ( !match.test(res.presentationname) ) return;
- }
-
// proceed with resource
var _id = res.symbolicname.replace(/\./g, '_');
- var _tr = resTable.find('#' + _id);
+ var _tr = resTable.find('#row' + _id);
if (_tr.length == 0) { // not created yet, create it
- var _select = createElement('select', null, { name : 'bundle' }, [
- createElement( 'option', null, { value : '-' }, [
- text( i18n.selectVersion)
- ]),
- createElement( 'option', null, { value : res.id }, [
- text( res.version + (res.installed ? ' *' : '') )
- ])
- ]);
- _tr = tr( null, { 'id' : _id } , [
- td( null, null, [ _select ] ),
- td( null, null, [ text(res.presentationname) ] ),
+ var blockElement = createElement('span', '', {
+ id: 'block' + _id
+ });
+ var titleElement = createElement('span', '', {
+ id: 'entry' + _id,
+ title: "Show Versions"
+ });
+ var inputElement = createElement('span', 'ui-icon ui-icon-triangle-1-e', {
+ id: 'img' + _id,
+ style: {display: "inline-block"}
+ });
+ blockElement.appendChild(titleElement);
+ titleElement.appendChild(inputElement);
+ titleElement.appendChild(text(" "));
+ titleElement.appendChild(text(res.presentationname ? res.presentationname : res.symbolicname));
+ $(titleElement).click(function() {showVersions(res.symbolicname)});
+
+ _tr = tr( null, { 'id' : 'row' + _id } , [
+ td( null, null, [ blockElement ] ),
td( null, null, [ text(res.installed ? res.version : '') ] )
]);
resTable.append( _tr );
- } else { // append the additional version
- _tr.find( 'select' ).append (
- createElement( 'option', null, { value : res.id }, [
- text( res.version + (res.installed ? ' *' : '') )
- ])
- );
- if (res.installed) _tr.find( 'td:eq(2)' ).text( res.version );
}
}
+function getCapabilitiesByName(res, name) {
+ var caps = [];
+ for (var v in res.capabilities) {
+ if (res.capabilities[v].name == name) {
+ caps.push(res.capabilities[v]);
+ }
+ }
+ return caps;
+}
+
+function getRequirementsByName(res, name) {
+ var caps = [];
+ for (var v in res.requirements) {
+ if (res.requirements[v].name == name) {
+ caps.push(res.requirements[v]);
+ }
+ }
+ return caps;
+}
+
+function createDetailedTable(enclosing, name, headers, rows, callback) {
+ if (rows && rows.length > 0) {
+ var uuid = jQuery.uuid();
+ var title = createElement('span', null, null, [
+ createElement('span', 'ui-icon ui-icon-triangle-1-e', { id: "img"+uuid, style: {display: "inline-block"} }),
+ text(" "),
+ text(name)
+ ]);
+ enclosing.append(tr(null, null, [
+ td(null, null, [ title ]),
+ td(null, null, [ createElement('table', 'nicetable ui-widget ui-helper-hidden', { id: "alt1"+uuid }, [
+ createElement('thead', null, null, [
+ tr(null, null, headers.map(function(x) {
+ return th('ui-widget-header', null, [text(x)]);
+ }))
+ ]),
+ createElement('tbody', null, null,
+ rows.map(function(x) {
+ var values = callback(x);
+ var tds = values.map(function(x) {
+ return td(null, null, [x]);
+ });
+ return tr(null, null, tds);
+ })
+ )
+ ]),
+ createElement('span', null, { id: "alt2"+uuid }, [
+ text(rows.length)
+ ])
+ ])
+ ]));
+ $(title).
+ unbind('click').
+ click(function(event) {
+ event.preventDefault();
+ $("#img"+uuid).toggleClass('ui-icon-triangle-1-s').//down
+ toggleClass('ui-icon-triangle-1-e');//right
+ $("#alt1"+uuid).toggle();
+ $("#alt2"+uuid).toggle();
+ });
+ }
+}
+
+function trim(stringToTrim) {
+ return stringToTrim.replace(/^\s+|\s+$/g,"");
+}
+
+function parseSimpleFilter(filter) {
+ filter = filter.substring(1, filter.length-1);
+ var start = 0;
+ var pos = 0;
+ var c = filter.charAt(pos);
+ while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') {
+ if (c == '<' && filterChars[pos+1] == '*') {
+ break;
+ }
+ if (c == '*' && filterChars[pos+1] == '>') {
+ break;
+ }
+ pos++;
+ c = filter.charAt(pos);
+ }
+ if (pos == start) {
+ throw ("Missing attr: " + filter.substring(pos));
+ }
+
+ var attr = trim(filter.substring(start, pos));
+ var oper = filter.substring(pos, pos+2);
+ var value;
+ if (oper == '*>' || oper == '~=' || oper == '>=' || oper == '<=' || oper == '<*') {
+ value = trim(filter.substring(pos+2));
+ if (value == '') {
+ throw ("Missing value: " + filter.substring(pos));
+ }
+
+ return { operator: oper, operands: [ attr, value ]};
+ } else {
+ if (c != '=') {
+ throw ("Invalid operator: " + filter.substring(pos));
+ }
+ oper = '=';
+ value = filter.substring(pos+1);
+ if (value == '*' ) {
+ return { operator: '=*', operands: [ attr ]};
+ }
+ return { operator: '=', operands: [ attr, value ]};
+ }
+}
+
+function parseFilter(filter) {
+ if (filter.charAt(0) != "(" || filter.charAt(filter.length-1) != ")") {
+ throw "Wrong parenthesis: " + filter;
+ }
+ if (filter.charAt(1) == "!") {
+ return { operator: filter.charAt(1), operands: [ parseFilter(filter.substring(2, filter.length-1)) ] };
+ }
+ if (filter.charAt(1) == "|" || filter.charAt(1) == "&") {
+ var inner = filter.substring(2, filter.length-1);
+ var flts = inner.match(/\([^\(\)]*(\([^\(\)]*(\([^\(\)]*(\([^\(\)]*\))*[^\(\)]*\))*[^\(\)]*\))*[^\(\)]*\)/g);
+ return { operator: filter.charAt(1), operands: flts.map(function(x) { return parseFilter(x); }) };
+ }
+ return parseSimpleFilter(filter);
+}
+
+function simplify(filter) {
+ if (filter.operator == '&' || filter.operator == '|') {
+ filter.operands = filter.operands.map(function(x) { return simplify(x); });
+ } else if (filter.operator == '!') {
+ if (filter.operands[0].operator == '<=') {
+ filter.operator = '>';
+ filter.operands = filter.operands[0].operands;
+ } else if (filter.operands[0].operator == '>=') {
+ filter.operator = '<';
+ filter.operands = filter.operands[0].operands;
+ }
+ }
+ return filter;
+}
+
+function addRow(tbody, key, value) {
+ if (value) {
+ tbody.append( tr(null, null, [
+ td(null, null, [ text(key) ]),
+ td(null, null, [ text(value) ])
+ ]));
+ }
+}
+
+function renderDetailedResource(res) {
+ var tbody = $('#detailsTableBody');
+
+ tbody.append( tr(null, null, [
+ th('ui-widget-header', null, [
+ text("Resource")
+ ]),
+ th('ui-widget-header', null, [
+ createElement('form', 'button-group', { method: "post"}, [
+ createElement('input', null, { type: "hidden", name: "bundle", value: res.id}),
+ createElement('input', 'ui-state-default ui-corner-all', { type: "submit", name: "deploy", value: "Deploy" }, [ text("dummy")]),
+ createElement('input', 'ui-state-default ui-corner-all', { type: "submit", name: "deploystart", value: "Start" }, [ text("dummy")]),
+ text(" "),
+ createElement('input', 'ui-state-default ui-corner-all', { id: "optional", type: "checkbox", name: "optional" }),
+ text(" "),
+ createElement('label', 'ui-widget', { 'for': "optional" }, [ text("optional") ])
+ ])
+ ])
+ ]));
+
+ addRow(tbody, "Name", res.presentationname);
+ addRow(tbody, "Description", res.description);
+ addRow(tbody, "Symbolic name", res.symbolicname);
+ addRow(tbody, "Version", res.version);
+ addRow(tbody, "URI", res.uri);
+ addRow(tbody, "Documentation", res.documentation);
+ addRow(tbody, "Javadoc", res.javadoc);
+ addRow(tbody, "Source", res.source);
+ addRow(tbody, "License", res.license);
+ addRow(tbody, "Copyright", res.copyright);
+ addRow(tbody, "Size", res.size);
+
+ // Exported packages
+ createDetailedTable(tbody, "Exported packages", ["Package", "Version"],
+ getCapabilitiesByName(res, "package").sort(function(a,b) {
+ var pa = a.properties['package'], pb = b.properties['package']; return pa == pb ? 0 : pa < pb ? -1 : +1;
+ }),
+ function(p) {
+ return [ text(p.properties['package']), text(p.properties['version']) ];
+ });
+ // Exported services
+ createDetailedTable(tbody, "Exported services", ["Service"], getCapabilitiesByName(res, "service"), function(p) {
+ return [ text(p.properties['service']) ];
+ });
+ // Imported packages
+ createDetailedTable(tbody, "Imported packages", ["Package", "Version", "Optional"], getRequirementsByName(res, "package"), function(p) {
+ var f = parseFilter(p.filter);
+ simplify(f);
+ var n, vmin = "[0.0.0", vmax = "infinity)";
+ if (f.operator == '&') {
+ for (var i in f.operands) {
+ var fi = f.operands[i];
+ if (fi.operands[0] == 'package' && fi.operator == '=') {
+ n = fi.operands[1];
+ }
+ if (fi.operands[0] == 'version') {
+ if (fi.operator == '>=') {
+ vmin = '[' + fi.operands[1];
+ }
+ if (fi.operator == '>') {
+ vmin = '(' + fi.operands[1];
+ }
+ if (fi.operator == '<=') {
+ vmax = fi.operands[1] + "]";
+ }
+ if (fi.operator == '<') {
+ vmax = fi.operands[1] + ")";
+ }
+ }
+ }
+ }
+ return [ text(n ? n : p.filter), text(vmin + ", " + vmax), text(p.optional) ];
+ });
+ // Imported bundles
+ createDetailedTable(tbody, "Imported bundles", ["Bundle", "Version", "Optional"], getRequirementsByName(res, "bundle"), function(p) {
+ return [ text(p.filter), text(""), text(p.optional) ];
+ });
+ // Imported services
+ createDetailedTable(tbody, "Imported bundles", ["Service", "Optional"], getRequirementsByName(res, "service"), function(p) {
+ return [ text(p.filter), text(p.optional) ];
+ });
+ // Required dependencies
+ createDetailedTable(tbody, "Dependencies", ["Name", "Version"], res.required, function(p) {
+ var a = createElement('a', null, { href: (window.location.pathname + '?details&symbolicname=' + p.symbolicname + '&version=' + p.version) });
+ a.appendChild(text(p.presentationname ? p.presentationname : p.symbolicname));
+ return [ a, text(p.version) ];
+ });
+ // Optional dependencies
+ createDetailedTable(tbody, "Optional Dependencies", ["Name", "Version"], res.optional, function(p) {
+ var a = createElement('a', null, { href: (window.location.pathname + '?details&symbolicname=' + p.symbolicname + '&version=' + p.version) });
+ a.appendChild(text(p.presentationname ? p.presentationname : p.symbolicname));
+ return [ a, text(p.version) ];
+ });
+ // Unsatisfied requirements
+ createDetailedTable(tbody, "Unsatisfied Requirements", ["Requirement", "Optional"], res.unsatisfied, function(p) {
+ return [ text(p.filter), text(p.optional) ];
+ });
+
+// $('#detailsTableBody').append( tr(null, null, [ th('ui-widget-header', { colspan: 2 }, [ text("Resource") ]) ]) );
+// $('#detailsTableBody').append( tbody );
+}
+
function renderRepository(repo) {
var _tr = repoTableTemplate.clone();
_tr.find('td:eq(0)').text( repo.name );
@@ -87,21 +404,27 @@
doRepoAction('delete', repo.url);
});
repoTable.append(_tr);
-
- for(var i in repo.resources) {
- renderResource( repo.resources[i] );
- }
}
-function renderData(data) {
- obrData = data;
+function renderData() {
repoTable.empty();
resTable.empty();
- if ( data.status ) {
+ if ( obrData.status ) {
$('.statline').html(i18n.status_ok);
ifStatusOK.removeClass('ui-helper-hidden');
- for (var i in data.repositories ) {
- renderRepository( data.repositories[i] );
+ for (var i in obrData.repositories ) {
+ renderRepository( obrData.repositories[i] );
+ }
+ if ($.getUrlVar('details')) {
+ $('#resTable').addClass('ui-helper-hidden');
+ $('#detailsTable').removeClass('ui-helper-hidden');
+ for (var i in obrData.resources ) {
+ renderDetailedResource( obrData.resources[i] );
+ }
+ } else {
+ for (var i in obrData.resources ) {
+ renderResource( obrData.resources[i] );
+ }
}
} else {
$('.statline').html(i18n.status_no);
@@ -109,6 +432,31 @@
}
}
+
+$.extend({
+ getUrlVars: function(){
+ var vars = [], hash;
+ var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
+ for(var i = 0; i < hashes.length; i++)
+ {
+ var j = hashes[i].indexOf('=');
+ if (j > 0) {
+ var k = hashes[i].slice(0, j);
+ var v = hashes[i].slice(j + 1);
+ vars.push(k);
+ vars[k] = v;
+ } else {
+ vars.push(hashes[i]);
+ vars[hashes[i]] = true;
+ }
+ }
+ return vars;
+ },
+ getUrlVar: function(name){
+ return $.getUrlVars()[name];
+ }
+});
+
$(document).ready( function() {
repoTable = $('#repoTable tbody');
repoTableTemplate = repoTable.find('tr').clone();
@@ -116,14 +464,24 @@
resTable = $('#resTable tbody').empty();
searchField = $('#searchField');
ifStatusOK = $('#ifStatusOK');
+ searchField.val($.getUrlVar('query'));
- $('#addRepoBtn').click(function() {
+ $('#addRepoBtn').click(function(event) {
+ event.preventDefault();
doRepoAction('add', addRepoUri.val());
});
- $('#searchBtn').click(function() {
- renderData(obrData);
- return false;
+ $('#searchBtn').click(function(event) {
+ event.preventDefault();
+ window.location.href = window.location.pathname + '?query=' + searchField.val();
+ });
+ searchField.keypress(function(event) {
+ if (event.keyCode == 13) {
+ event.preventDefault();
+ $('#searchBtn').click();
+ }
});
- renderData(obrData);
-});
\ No newline at end of file
+ renderData();
+ initStaticWidgets();
+});
+
diff --git a/webconsole/src/main/resources/templates/obr.html b/webconsole/src/main/resources/templates/obr.html
index 4cc686d..9797d5c 100644
--- a/webconsole/src/main/resources/templates/obr.html
+++ b/webconsole/src/main/resources/templates/obr.html
@@ -1,4 +1,5 @@
-<script type="text/javascript" src="res/ui/obr.js"></script>
+<script type="text/javascript" src="res/ui/jquery.uuid.js"></script>
+<script type="text/javascript" src="res/ui/obr.js"></script>
<script type="text/javascript">
var i18n = {
status_ok : '${obr.status.ok}',
@@ -44,28 +45,57 @@
<br/>
-<form id="installForm" method="post" action="">
- <div class="ui-widget-header ui-corner-top buttonGroup">
- <span style="float: left; margin-left: 1em">${obr.res.title}</span>
- <input type="text" id="searchField" />
- <button id="searchBtn">${obr.action.search}</button>
- <input type="submit" name="deploy" value="${obr.action.deploy}" />
- <input type="submit" name="deploystart" value="${obr.action.deploystart}" />
- </div>
+<div class="ui-widget-header ui-corner-top buttonGroup">
+ <span style="float: left; margin-left: 1em">${obr.res.title}</span>
+ <span>
+ <a href="obr?list=a">A</a>
+ <a href="obr?list=b">B</a>
+ <a href="obr?list=c">C</a>
+ <a href="obr?list=d">D</a>
+ <a href="obr?list=e">E</a>
+ <a href="obr?list=f">F</a>
+ <a href="obr?list=g">G</a>
+ <a href="obr?list=h">H</a>
+ <a href="obr?list=i">I</a>
+ <a href="obr?list=j">J</a>
+ <a href="obr?list=k">K</a>
+ <a href="obr?list=l">L</a>
+ <a href="obr?list=m">M</a>
+ <a href="obr?list=n">N</a>
+ <a href="obr?list=o">O</a>
+ <a href="obr?list=p">P</a>
+ <a href="obr?list=q">Q</a>
+ <a href="obr?list=r">R</a>
+ <a href="obr?list=s">S</a>
+ <a href="obr?list=t">T</a>
+ <a href="obr?list=u">U</a>
+ <a href="obr?list=v">V</a>
+ <a href="obr?list=w">W</a>
+ <a href="obr?list=x">X</a>
+ <a href="obr?list=y">Y</a>
+ <a href="obr?list=z">Z</a>
+ <a href="obr?list=-">?</a>
+ </span>
+ <input type="text" id="searchField"/>
+ <button id="searchBtn">${obr.action.search}</button>
+</div>
- <table id="resTable" class="nicetable">
- <thead>
- <tr>
- <th class="col_Version">${version}</th>
- <th class="col_ResName">${obr.res.name}</th>
- <th class="col_VersionInst">${obr.res.installedVer}</th>
- </tr>
- </thead>
- <tbody>
- <tr><td colspan="2">dummy</td></tr>
- </tbody>
- </table>
-</form>
+<table id="resTable" class="nicetable">
+ <thead>
+ <tr>
+ <th class="col_ResName">${obr.res.name}</th>
+ <th class="col_VersionInst">${obr.res.installedVer}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr><td colspan="2">dummy</td></tr>
+ </tbody>
+</table>
+
+<table id="detailsTable" class="nicetable ui-helper-hidden">
+ <tbody id="detailsTableBody">
+ </tbody>
+</table>
<br/>