[Kimchi-devel] [PATCH 12/17] Ginger Base : base plugin ui/js files

Chandra Shehkhar Reddy Potula chandra at linux.vnet.ibm.com
Fri Sep 11 10:16:29 UTC 2015



On 09/10/2015 07:29 PM, Aline Manera wrote:
>
> Most of files in this patch are being duplicated from Kimchi/wok. We 
> need to think on how to avoid this code duplication.
java script files like form, grid, line-chart, circleGause, select etc 
... are duplicates and I thought of not deviating from the actual goal 
:-). Again my suggestion is let's list the code duplication's as one bug 
and try to avoid them.
>
>
> On 01/09/2015 15:01, chandra at linux.vnet.ibm.com wrote:
>> From: chandrureddy <chandra at linux.vnet.ibm.com>
>>
>> ---
>>   plugins/gingerbase/ui/js/Makefile.am               |  27 +
>>   plugins/gingerbase/ui/js/src/gingerbase.api.js     | 371 +++++++++
>>   plugins/gingerbase/ui/js/src/gingerbase.form.js    |  48 ++
>>   plugins/gingerbase/ui/js/src/gingerbase.grid.js    | 528 +++++++++++++
>>   plugins/gingerbase/ui/js/src/gingerbase.host.js    | 859 
>> +++++++++++++++++++++
>>   .../gingerbase/ui/js/src/gingerbase.line-chart.js  | 202 +++++
>>   plugins/gingerbase/ui/js/src/gingerbase.main.js    |  26 +
>>   .../ui/js/src/gingerbase.report_add_main.js        |  72 ++
>>   .../ui/js/src/gingerbase.report_rename_main.js     |  66 ++
>>   .../ui/js/src/gingerbase.repository_add_main.js    |  96 +++
>>   .../ui/js/src/gingerbase.repository_edit_main.js   |  74 ++
>>   plugins/gingerbase/ui/js/src/gingerbase.select.js  |  50 ++
>>   plugins/gingerbase/ui/js/widgets/circleGauge.js    | 100 +++
>>   plugins/kimchi/ui/js/src/kimchi.host.js            | 858 
>> --------------------
>>   plugins/kimchi/ui/js/src/kimchi.report_add_main.js |  72 --
>>   .../kimchi/ui/js/src/kimchi.report_rename_main.js  |  66 --
>>   .../kimchi/ui/js/src/kimchi.repository_add_main.js |  96 ---
>>   .../ui/js/src/kimchi.repository_edit_main.js       |  74 --
>>   18 files changed, 2519 insertions(+), 1166 deletions(-)
>>   create mode 100644 plugins/gingerbase/ui/js/Makefile.am
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.api.js
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.form.js
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.grid.js
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.host.js
>>   create mode 100644 
>> plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.main.js
>>   create mode 100644 
>> plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
>>   create mode 100644 
>> plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
>>   create mode 100644 
>> plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
>>   create mode 100644 
>> plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
>>   create mode 100644 plugins/gingerbase/ui/js/src/gingerbase.select.js
>>   create mode 100644 plugins/gingerbase/ui/js/widgets/circleGauge.js
>>   delete mode 100644 plugins/kimchi/ui/js/src/kimchi.host.js
>>   delete mode 100644 plugins/kimchi/ui/js/src/kimchi.report_add_main.js
>>   delete mode 100644 
>> plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
>>   delete mode 100644 
>> plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
>>   delete mode 100644 
>> plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
>>
>> diff --git a/plugins/gingerbase/ui/js/Makefile.am 
>> b/plugins/gingerbase/ui/js/Makefile.am
>> new file mode 100644
>> index 0000000..142ccdb
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/Makefile.am
>> @@ -0,0 +1,27 @@
>> +#
>> +# Kimchi
>> +#
>> +# Copyright IBM, Corp. 2013
>> +#
>> +# Licensed 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.
>> +
>> +EXTRA_DIST = src widgets
>> +
>> +jsdir = $(datadir)/wok/plugins/gingerbase/ui/js
>> +
>> +dist_js_DATA = gingerbase.min.js $(filter-out gingerbase.min.js, 
>> $(wildcard *.js))
>> +
>> +gingerbase.min.js: widgets/*.js src/*.js
>> +    cat $(sort $^) > $@
>> +
>> +CLEANFILES = gingerbase.min.js
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.api.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.api.js
>> new file mode 100644
>> index 0000000..480f6fb
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.api.js
>> @@ -0,0 +1,371 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2015
>> + *
>> + * Licensed 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.
>> + */
>> +var kimchi = {
>> +
>> +    widget: {},
>> +
>> +    trackingTasks: [],
>> +
>> +    /**
>> +     *
>> +     * Get host capabilities
>> +     * suc: callback if succeed err: callback if failed
>> +     */
>> +    getCapabilities : function(suc, err, done) {
>> +        done = typeof done !== 'undefined' ? done: function(){};
>> +        wok.requestJSON({
>> +            url : "plugins/gingerbase/host/capabilities",
>> +            type : "GET",
>> +            contentType : "application/json",
>> +            dataType : "json",
>> +            success: suc,
>> +            error: err,
>> +            complete: done
>> +        });
>> +    },
>> +
>> +    /**
>> +     * Get the i18 strings.
>> +     */
>> +    getI18n: function(suc, err, url, sync) {
>> +        wok.requestJSON({
>> +            url : url ? url : 'plugins/gingerbase/i18n.json',
>> +            type : 'GET',
>> +            resend: true,
>> +            dataType : 'json',
>> +            async : !sync,
>> +            success : suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    /**
>> +     * Get the host static information.
>> +     */
>> +    getHost: function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host',
>> +            type : 'GET',
>> +            resend: true,
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    /**
>> +     * Get the dynamic host stats (usually used for monitoring).
>> +     */
>> +    getHostStats : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/stats',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            headers: {'Wok-Robot': 'wok-robot'},
>> +            dataType : 'json',
>> +            success : suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    /**
>> +     * Get the historic host stats.
>> +     */
>> +    getHostStatsHistory : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/stats/history',
>> +            type : 'GET',
>> +            resend: true,
>> +            contentType : 'application/json',
>> +            headers: {'Wok-Robot': 'wok-robot'},
>> +            dataType : 'json',
>> +            success : suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    getTask : function(taskId, suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/tasks/' + 
>> encodeURIComponent(taskId),
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    getTasksByFilter : function(filter, suc, err, sync) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/tasks?' + filter,
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            async : !sync,
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    listReports : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/debugreports',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            resend: true,
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    trackTask : function(taskID, suc, err, progress) {
>> +        var onTaskResponse = function(result) {
>> +            var taskStatus = result['status'];
>> +            switch(taskStatus) {
>> +            case 'running':
>> +                progress && progress(result);
>> +                setTimeout(function() {
>> +                    kimchi.trackTask(taskID, suc, err, progress);
>> +                }, 2000);
>> +                break;
>> +            case 'finished':
>> +                suc && suc(result);
>> +                break;
>> +            case 'failed':
>> +                err && err(result);
>> +                break;
>> +            default:
>> +                break;
>> +            }
>> +        };
>> +
>> +        kimchi.getTask(taskID, onTaskResponse, err);
>> +        if(kimchi.trackingTasks.indexOf(taskID) < 0)
>> +            kimchi.trackingTasks.push(taskID);
>> +    },
>> +
>> +    createReport: function(settings, suc, err, progress) {
>> +        var onResponse = function(data) {
>> +            taskID = data['id'];
>> +            kimchi.trackTask(taskID, suc, err, progress);
>> +        };
>> +
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/debugreports',
>> +            type : "POST",
>> +            contentType : "application/json",
>> +            data : JSON.stringify(settings),
>> +            dataType : "json",
>> +            success : onResponse,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    renameReport : function(name, settings, suc, err) {
>> +        $.ajax({
>> +            url : "plugins/gingerbase/debugreports/" + 
>> encodeURIComponent(name),
>> +            type : 'PUT',
>> +            contentType : 'application/json',
>> +            data : JSON.stringify(settings),
>> +            dataType : 'json',
>> +            success: suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    deleteReport: function(settings, suc, err) {
>> +        var reportName = encodeURIComponent(settings['name']);
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/debugreports/' + reportName,
>> +            type : 'DELETE',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    downloadReport: function(settings, suc, err) {
>> +        window.open(settings['file']);
>> +    },
>> +
>> +    shutdown: function(settings, suc, err) {
>> +        var reboot = settings && settings['reboot'] === true;
>> +        var url = 'plugins/gingerbase/host/' + (reboot ? 'reboot' : 
>> 'shutdown');
>> +        wok.requestJSON({
>> +            url : url,
>> +            type : 'POST',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    listHostPartitions : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/partitions',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    listSoftwareUpdates : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/packagesupdate',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            resend: true,
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    updateSoftware : function(suc, err, progress) {
>> +        var taskID = -1;
>> +        var onResponse = function(data) {
>> +            taskID = data['id'];
>> +            trackTask();
>> +        };
>> +
>> +        var trackTask = function() {
>> +            kimchi.getTask(taskID, onTaskResponse, err);
>> +        };
>> +
>> +        var onTaskResponse = function(result) {
>> +            var taskStatus = result['status'];
>> +            switch(taskStatus) {
>> +            case 'running':
>> +                progress && progress(result);
>> +                setTimeout(function() {
>> +                    trackTask();
>> +                }, 200);
>> +                break;
>> +            case 'finished':
>> +            case 'failed':
>> +                suc(result);
>> +                break;
>> +            default:
>> +                break;
>> +            }
>> +        };
>> +
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/swupdate',
>> +            type : "POST",
>> +            contentType : "application/json",
>> +            dataType : "json",
>> +            success : onResponse,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    createRepository : function(settings, suc, err) {
>> +        wok.requestJSON({
>> +            url : "plugins/gingerbase/host/repositories",
>> +            type : "POST",
>> +            contentType : "application/json",
>> +            data : JSON.stringify(settings),
>> +            dataType : "json",
>> +            success: suc,
>> +            error: err
>> +        });
>> +    },
>> +
>> +    retrieveRepository : function(repository, suc, err) {
>> +        var reposID = encodeURIComponent(repository);
>> +        wok.requestJSON({
>> +            url : "plugins/gingerbase/host/repositories/" + reposID,
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    updateRepository : function(name, settings, suc, err) {
>> +        var reposID = encodeURIComponent(name);
>> +        $.ajax({
>> +            url : "plugins/gingerbase/host/repositories/" + reposID,
>> +            type : 'PUT',
>> +            contentType : 'application/json',
>> +            data : JSON.stringify(settings),
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    enableRepository : function(name, enable, suc, err) {
>> +        var reposID = encodeURIComponent(name);
>> +        $.ajax({
>> +            url : "plugins/gingerbase/host/repositories/" + reposID +
>> +                  '/' + (enable === true ? 'enable' : 'disable'),
>> +            type : 'POST',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    deleteRepository : function(repository, suc, err) {
>> +        var reposID = encodeURIComponent(repository);
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/repositories/' + reposID,
>> +            type : 'DELETE',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    listRepositories : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/repositories',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            resend: true,
>> +            success : suc,
>> +            error : err
>> +        });
>> +    },
>> +
>> +    getCPUInfo : function(suc, err) {
>> +        wok.requestJSON({
>> +            url : 'plugins/gingerbase/host/cpuinfo',
>> +            type : 'GET',
>> +            contentType : 'application/json',
>> +            dataType : 'json',
>> +            resend : true,
>> +            success : suc,
>> +            error : err ? err : function(data) {
>> +                wok.message.error(data.responseJSON.reason);
>> +            }
>> +        });
>> +    }
>> +};
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.form.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.form.js
>> new file mode 100644
>> index 0000000..0bb7c4b
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.form.js
>> @@ -0,0 +1,48 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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($) {
>> +    $.fn.serializeObject = function() {
>> +        var formDataArray = $(this).serializeArray();
>> +        var formData = new Object();
>> +        $.each(formDataArray, function(index, data) {
>> +            formData.setDeepValue(data.name, data.value);
>> +        });
>> +        return formData;
>> +    };
>> +}(jQuery));
>> +
>> +(function($) {
>> +    $.fn.fillWithObject = function(obj) {
>> +        $(this).find("input").each(function(){
>> +            switch($(this).attr('type')) {
>> +                case 'text':
>> + $(this).val(obj.getDeepValue($(this).attr("name")));
>> +                    break;
>> +                case 'radio':
>> +                case 'checkbox':
>> +                    var a=String($(this).val());
>> +                    var 
>> b=String(obj.getDeepValue($(this).attr("name")));
>> +                    $(this).prop("checked",(a==b));
>> +                    break;
>> +                default:
>> +                    break;
>> +                }
>> +            });
>> +     };
>> +}(jQuery));
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.grid.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.grid.js
>> new file mode 100644
>> index 0000000..4553135
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.grid.js
>> @@ -0,0 +1,528 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2015
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.widget.Grid = function(opts) {
>> +    this.opts = $.extend({}, this.opts, opts);
>> +    this.createDOM();
>> +    this.reload();
>> +};
>> +
>> +kimchi.widget.Grid.prototype = (function() {
>> +    var htmlStr = [
>> +        '<div id="{id}" class="grid">',
>> +            '<div class="grid-content">',
>> +                '<div class="grid-header">',
>> +                    '<div class="grid-frozen-header-view">',
>> +                        '<table class="grid-frozen-header-container">',
>> +                        '</table>',
>> +                    '</div>',
>> +                    '<div class="grid-header-view">',
>> +                        '<div class="grid-header-wrapper">',
>> +                            '<table class="grid-header-container">',
>> +                            '</table>',
>> +                        '</div>',
>> +                    '</div>',
>> +                '</div>',
>> +                '<div class="grid-body">',
>> +                    '<div class="grid-frozen-body-view">',
>> +                        '<div class="grid-frozen-body-wrapper">',
>> +                            '<table 
>> class="grid-frozen-body-container">',
>> +                            '</table>',
>> +                        '</div>',
>> +                    '</div>',
>> +                    '<div class="grid-body-view">',
>> +                        '<div class="grid-body-wrapper">',
>> +                            '<table class="grid-body-container">',
>> +                            '</table>',
>> +                        '</div>',
>> +                    '</div>',
>> +                '</div>',
>> +                '<div class="grid-resizer-leftmost hidden"></div>',
>> +                '<div class="grid-resizer hidden"></div>',
>> +            '</div>',
>> +            '<div class="grid-footer"></div>',
>> +            '<div class="grid-mask hidden">',
>> +                '<div class="grid-loading">',
>> +                    '<div class="grid-loading-icon"></div>',
>> +                    '<div class="grid-loading-text">',
>> +                        '{loading}',
>> +                    '</div>',
>> +                '</div>',
>> +            '</div>',
>> +            '<div class="grid-message hidden">',
>> +                '<div class="grid-message-text">',
>> +                    '{message}',
>> +                    '<button class="retry-button btn-small">',
>> +                        '{buttonLabel}',
>> +                    '</button>',
>> +                '</div>',
>> +                '<div class="detailed-title">',
>> +                    '{detailedLabel}',
>> +                '</div>',
>> +                '<div class="detailed-text"></div>',
>> +            '</div>',
>> +        '</div>'
>> +    ].join('');
>> +
>> +    var CONTAINER_NORMAL = 0, CONTAINER_FROZEN = 1;
>> +
>> +    var setupHeaders = function(header, body, fields) {
>> +        var colGroup = $('<colgroup></colgroup>').appendTo(header);
>> +        var headerHeader = $('<thead></thead>');
>> +        var headerRow = $('<tr></tr>').appendTo(headerHeader);
>> +        $.each(fields || [], function(i, field) {
>> +            $('<col class="' +
>> +                field['class'] +
>> +            '"/>')
>> +                .appendTo(colGroup);
>> +            $('<th><div class="cell-text-wrapper">' +
>> +                field['label'] +
>> +            '</div></th>').appendTo(headerRow);
>> +        });
>> +        headerHeader.appendTo(header);
>> +
>> +        var totalWidth = 0;
>> +        $('col', colGroup).each(function(index, col) {
>> +            var width = $(col).width();
>> +            totalWidth += width;
>> +            $(col).css('width', width + 'px');
>> +        });
>> +        $(body).append(colGroup.clone());
>> +        return totalWidth;
>> +    };
>> +
>> +    var getValue = function(name, obj) {
>> +        var result=undefined;
>> +        if(!Array.isArray(name)) {
>> +            name=name.parseKey();
>> +        }
>> +        if(name.length!=0) {
>> +            var tmpName=name.shift();
>> +            if(obj[tmpName]!=undefined) {
>> +                    result=obj[tmpName];
>> +            }
>> +            if(name.length!=0) {
>> +                    result=getValue(name,obj[tmpName]);
>> +            }
>> +        }
>> +        return(result);
>> +    };
>> +
>> +    var fillBody = function(container, fields) {
>> +        var data = this.data;
>> +        var tbody = ($('tbody', container).length && $('tbody', 
>> container))
>> +            || $('<tbody></tbody>').appendTo(container);
>> +        tbody.empty();
>> +        $.each(data, function(i, row) {
>> +            var rowNode = $('<tr></tr>').appendTo(tbody);
>> +            $.each(fields, function(fi, field) {
>> +                var value = getValue(field['name'], row);
>> +                $('<td><div class="cell-text-wrapper"' +
>> +                    (field['makeTitle'] === true
>> +                        ? ' title="' + value + '"'
>> +                        : ''
>> +                    ) + '>' + value.toString() + '</div></td>'
>> +                ).appendTo(rowNode);
>> +            });
>> +        });
>> +    };
>> +
>> +    var fixTableLayout = function(style) {
>> +        $.each([
>> +            this.frozenHeaderContainer,
>> +            this.headerContainer,
>> +            this.frozenBodyContainer,
>> +            this.bodyContainer
>> +        ], function(i, tableNode) {
>> +            $(tableNode).css('table-layout', style || 'fixed');
>> +        });
>> +    };
>> +
>> +    var initResizing = function(event) {
>> +        var resizer = event.data.resizer;
>> +        var pageX = event.pageX;
>> +        var tailPos = $(this).width() + $(this).offset()['left'];
>> +        var atResizer = Math.abs(pageX - tailPos) <= 2;
>> +        var isResizing = !$(resizer).hasClass('hidden');
>> +        $('body')[(atResizer || isResizing)
>> +            ? 'addClass'
>> +            : 'removeClass'
>> +        ]('resizing');
>> +    };
>> +
>> +    var clearResizing = function(event) {
>> +        $(event.data.resizer).hasClass('hidden') &&
>> +            $('body').removeClass('resizing');
>> +    };
>> +
>> +    var stylingRow = function(row, className, add) {
>> +        var index = $(row).index() + 1;
>> +        $('tr', this.frozenBodyContainer)
>> +            .removeClass(className);
>> +        $('tr', this.bodyContainer)
>> +            .removeClass(className);
>> +
>> +        if(add === false) {
>> +            return;
>> +        }
>> +
>> +        $('tr:nth-child(' + index + ')', this.frozenBodyContainer)
>> +            .addClass(className);
>> +        $('tr:nth-child(' + index + ')', this.bodyContainer)
>> +            .addClass(className);
>> +    };
>> +
>> +    var setBodyListeners = function() {
>> +        if(this['opts']['rowSelection'] != 'disabled') {
>> +            $('tr', this.gridBody).on('mouseover', {
>> +                grid: this
>> +            }, function(event) {
>> +                if (! $(this).hasClass('no-hover'))
>> +                    stylingRow.call(event.data.grid, this, 'hover');
>> +            });
>> +
>> +            $('tr', this.gridBody).on('mouseout', {
>> +                grid: this
>> +            }, function(event) {
>> +                stylingRow.call(event.data.grid, this, 'hover', false);
>> +            });
>> +
>> +            $('tr', this.gridBody).on('click', {
>> +                grid: this
>> +            }, function(event) {
>> +                var grid = event.data.grid;
>> +                grid.selectedIndex = $(this).index();
>> +                stylingRow.call(grid, this, 'selected');
>> +                grid['opts']['onRowSelected'] && 
>> grid['opts']['onRowSelected']();
>> +            });
>> +        }
>> +
>> +        $('.grid-body-view', this.domNode).on('scroll', {
>> +            grid: this
>> +        }, function(event) {
>> +            var grid = event.data.grid;
>> +            $('.grid-header .grid-header-view', grid.domNode)
>> +                .prop('scrollLeft', this.scrollLeft);
>> +            $('.grid-body .grid-frozen-body-view', grid.domNode)
>> +                .prop('scrollTop', this.scrollTop);
>> +        });
>> +    };
>> +
>> +    var setData = function(data) {
>> +        this.data = data;
>> +        fillBody.call(this, this.frozenBodyContainer, 
>> this['opts']['frozenFields']);
>> +        fillBody.call(this, this.bodyContainer, 
>> this['opts']['fields']);
>> +        setBodyListeners.call(this);
>> +    };
>> +
>> +    var getSelected = function() {
>> +        return this.selectedIndex >= 0
>> +            ? this.data[this.selectedIndex]
>> +            : null;
>> +    };
>> +
>> +    var startResizing = function(container, event) {
>> +        var grid = event.data.grid;
>> +        kimchi.widget.Grid.beingResized = grid;
>> +        if(!($('body').hasClass('resizing')
>> +                && $(grid.resizer).hasClass('hidden'))) {
>> +            return;
>> +        }
>> +
>> +        grid.columnBeingResized = container;
>> +        var pageX = event.pageX;
>> +        var gridOffsetX = grid.domNode.offset()['left'];
>> +        var leftmostOffsetX = $(container).offset()['left'] - 
>> gridOffsetX;
>> +        var left = pageX - gridOffsetX;
>> +        var contentHeight = $('.grid-content', grid.domNode).height();
>> +        $(grid.resizerLeftmost).css({
>> +            left: leftmostOffsetX + 'px',
>> +            height: contentHeight + 'px'
>> +        });
>> +        $(grid.resizer).css({
>> +            left: left + 'px',
>> +            height: contentHeight + 'px'
>> +        });
>> +        $(grid.resizerLeftmost).removeClass('hidden');
>> +        $(grid.resizer).removeClass('hidden');
>> +        event.preventDefault();
>> +    };
>> +
>> +    var endResizing = function(event) {
>> +        var grid = kimchi.widget.Grid.beingResized;
>> +        if(!$('body').hasClass('resizing')) {
>> +            return;
>> +        }
>> +        $(grid.resizerLeftmost).addClass('hidden');
>> +        $(grid.resizer).addClass('hidden');
>> +        $('body').removeClass('resizing');
>> +        var leftmostOffset = 
>> $(grid.columnBeingResized).offset()['left'];
>> +        var left = event.pageX;
>> +        if(leftmostOffset > left) {
>> +            return;
>> +        }
>> +        resizeColumnWidth.call(
>> +            grid,
>> +            $(grid.columnBeingResized).index(),
>> +            left - leftmostOffset
>> +        );
>> +        fixTableLayout.call(grid);
>> +        grid.columnBeingResized = null;
>> +        kimchi.widget.Grid.beingResized = null;
>> +    };
>> +
>> +    var resizeColumnWidth = function(index, width) {
>> +        var width = Math.ceil(width);
>> +        var widthArray = [];
>> +        var totalWidth = 0;
>> +        var header = this.headerContainer;
>> +        var body = this.bodyContainer;
>> +        if(this.containerBeingResized === CONTAINER_FROZEN) {
>> +            header = this.frozenHeaderContainer;
>> +            body = this.frozenBodyContainer;
>> +        }
>> +        $('col', header).each(function(i, colNode) {
>> +            var w = index === i ? width : $(colNode).width();
>> +            widthArray.push(w);
>> +            totalWidth += w;
>> +        });
>> +        $.each([header, body], function(i, container) {
>> +            container.css({
>> +                'table-layout': 'fixed',
>> +                width: totalWidth + 'px'
>> +            });
>> +            $('col:nth-child(' + (index + 1) + ')', container).css({
>> +                width: width + 'px'
>> +            });
>> +        });
>> +
>> +        if(this.containerBeingResized === CONTAINER_FROZEN) {
>> +            var headerView = $('.grid-header-view', this.domNode);
>> +            var bodyView = $('.grid-body-view', this.domNode);
>> +            $.each([headerView, bodyView], function(i, view) {
>> +                view.css({
>> +                    left: totalWidth + 'px'
>> +                });
>> +            });
>> +        }
>> +    };
>> +
>> +    var positionResizer = function(event) {
>> +        var grid = event.data.grid;
>> +        if($(grid.resizer).hasClass('hidden')) {
>> +            return;
>> +        }
>> +
>> +        var pageX = event.pageX;
>> +        var gridOffsetX = $(grid.domNode).offset()['left'];
>> +        var leftMost = $(grid.resizerLeftmost).position()['left'];
>> +        var offsetX = pageX - gridOffsetX;
>> +        offsetX = offsetX >= leftMost ? offsetX : leftMost;
>> +        $(grid.resizer).css('left', offsetX + 'px');
>> +    };
>> +
>> +    var showMessage = function(msg) {
>> +        $('.detailed-text', this.messageNode).text(msg);
>> +        $(this.messageNode).removeClass('hidden');
>> +    };
>> +
>> +    var hideMessage = function() {
>> +        $(this.messageNode).addClass('hidden');
>> +    };
>> +
>> +    var reload = function() {
>> +        var data = this['opts']['data'];
>> +        if(!data) {
>> +            return;
>> +        }
>> +
>> +        $(this.messageNode).addClass('hidden');
>> +
>> +        if($.isArray(data)) {
>> +            return this.setData(data);
>> +        }
>> +
>> +        if($.isFunction(data)) {
>> +            var loadData = data;
>> +            $(this.maskNode).removeClass('hidden');
>> +            loadData($.proxy(function(data) {
>> +                this.setData(data);
>> +                $(this.maskNode).addClass('hidden');
>> +            }, this));
>> +        }
>> +    };
>> +
>> +    var destroy = function() {
>> +        $('body').off('mousemove.grid#' + this['opts']['id'], 
>> positionResizer);
>> +        $('body').off('mouseup.grid#' + this['opts']['id'], 
>> endResizing);
>> +    };
>> +
>> +    var createDOM = function() {
>> +        var containerID = this['opts']['container'];
>> +        var container = $('#' + containerID);
>> +        var gridID = this['opts']['id'];
>> +        var rowSelection = this['opts']['rowSelection'] || 'single';
>> +        var domNode = $(wok.substitute(htmlStr, {
>> +            id: gridID,
>> +            loading: i18n['GGBGRD6001M'],
>> +            message: i18n['GGBGRD6002M'],
>> +            buttonLabel: i18n['GGBGRD6003M'],
>> +            detailedLabel: i18n['GGBGRD6004M']
>> +        })).appendTo(container);
>> +        this.domNode = domNode;
>> +
>> +        var height = domNode.height();
>> +        var width = domNode.width();
>> +
>> +        var title = this['opts']['title'];
>> +        var titleNode = null;
>> +        if(title) {
>> +            titleNode = $('<div class="grid-caption">' + title + 
>> '</div>')
>> +                .prependTo(domNode);
>> +        }
>> +
>> +        var toolbarButtons = this['opts']['toolbarButtons'];
>> +        var toolbarNode = null;
>> +        if(toolbarButtons) {
>> +            toolbarNode = $('<div class="grid-toolbar"></div>');
>> +            if(titleNode) {
>> +                titleNode.after(toolbarNode);
>> +            }
>> +            else {
>> +                toolbarNode.prependTo(domNode);
>> +            }
>> +
>> +            $.each(toolbarButtons, function(i, button) {
>> +                var btnHTML = [
>> +                    '<button',
>> +                        button['id'] ? (' id="' + button['id'] + 
>> '"') : '',
>> +                        ' class="grid-toolbar-button',
>> +                            button['class'] ? (' ' + 
>> button['class']) : '',
>> +                            '"',
>> +                            button['disabled'] === true ? ' 
>> disabled' : '',
>> +                            '>',
>> +                            button['label'],
>> +                    '</button>'
>> +                ].join('');
>> +                var btnNode = $(btnHTML).appendTo(toolbarNode);
>> +                button['onClick'] &&
>> +                    btnNode.on('click', button['onClick']);
>> +            });
>> +        }
>> +
>> +        var frozenHeaderContainer = 
>> $('.grid-frozen-header-container', domNode);
>> +        var frozenBodyContainer = $('.grid-frozen-body-container', 
>> domNode);
>> +        var frozenWidth = setupHeaders(
>> +                frozenHeaderContainer,
>> +                frozenBodyContainer,
>> +                this['opts']['frozenFields']
>> +        );
>> +        this.frozenHeaderContainer = frozenHeaderContainer;
>> +        this.frozenBodyContainer = frozenBodyContainer;
>> +
>> +        var headerContainer = $('.grid-header-container', domNode);
>> +        var bodyContainer = $('.grid-body-container', domNode);
>> +        setupHeaders(headerContainer, bodyContainer, 
>> this['opts']['fields']);
>> +        this.headerContainer = headerContainer;
>> +        this.bodyContainer = bodyContainer;
>> +
>> +        fixTableLayout.call(this, 'auto');
>> +
>> +        var gridContentNode = $('.grid-content', domNode);
>> +        var captionHeight = titleNode && $(titleNode).height() || 0;
>> +        var toolbarHeight = toolbarNode && $(toolbarNode).height() 
>> || 0;
>> +        gridContentNode.css('top', (captionHeight + toolbarHeight) + 
>> 'px');
>> +
>> +        var maskNode = $('.grid-mask', domNode);
>> +        maskNode.css('top', captionHeight + 'px');
>> +        this.maskNode = maskNode;
>> +
>> +        var messageNode = $('.grid-message', domNode);
>> +        messageNode.css('top', captionHeight + 'px');
>> +        this.messageNode = messageNode;
>> +
>> +        var headerView = $('.grid-header-view', domNode);
>> +        var bodyView = $('.grid-body-view', domNode);
>> +        headerView.css('left', (frozenWidth) + 'px');
>> +        bodyView.css('left', (frozenWidth) + 'px');
>> +
>> +        var bodyWidth = width - frozenWidth;
>> +        headerContainer.css('width', bodyWidth + 'px');
>> +        bodyContainer.css('width', bodyWidth + 'px');
>> +
>> +        fixTableLayout.call(this);
>> +
>> +        var gridBody = $('.grid-body', domNode);
>> +        this.gridBody = gridBody;
>> +        this.resizerLeftmost = $('.grid-resizer-leftmost', domNode);
>> +        this.resizer = $('.grid-resizer', domNode);
>> +        var gridHeader = $('.grid-header', domNode);
>> +        $('th', gridHeader).on('mouseover mousemove', {
>> +            resizer: this.resizer
>> +        }, initResizing);
>> +
>> +        $('th', gridHeader).on('mouseout', {
>> +            resizer: this.resizer
>> +        }, clearResizing);
>> +
>> +        this.containerBeingResized = CONTAINER_NORMAL;
>> +        $('th', frozenHeaderContainer).on('mousedown', {
>> +            grid: this
>> +        }, function(event) {
>> +                event.data.grid.containerBeingResized = 
>> CONTAINER_FROZEN;
>> +                startResizing(this, event);
>> +        });
>> +        $('th', headerContainer).on('mousedown', {
>> +            grid: this
>> +        }, function(event) {
>> +                event.data.grid.containerBeingResized = 
>> CONTAINER_NORMAL;
>> +                startResizing(this, event);
>> +        });
>> +
>> +        $('body').on('mousemove.grid#' + this['opts']['id'], {
>> +            grid: this
>> +        }, positionResizer);
>> +        $('body').on('mouseup.grid#' + this['opts']['id'], 
>> endResizing);
>> +
>> +        var data = this['opts']['data'];
>> +
>> +        $('.retry-button', domNode).on('click', {
>> +            grid: this
>> +        }, function(event) {
>> +            event.data.grid.reload();
>> +        });
>> +    };
>> +
>> +    return {
>> +        opts: {
>> +            container: null,
>> +            id: null,
>> +            rowSelection: 'single',
>> +            onRowSelected: null,
>> +            title: null,
>> +            toolbarButtons: null,
>> +            frozenFields: null,
>> +            fields: null
>> +        },
>> +        createDOM: createDOM,
>> +        setData: setData,
>> +        getSelected: getSelected,
>> +        reload: reload,
>> +        destroy: destroy,
>> +        showMessage: showMessage
>> +    };
>> +})();
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.host.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.host.js
>> new file mode 100644
>> index 0000000..989461c
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.host.js
>> @@ -0,0 +1,859 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.host={};
>> +
>> +kimchi.host_main = function() {
>> +    var expand = function(header, toExpand) {
>> +        var controlledNode = $(header).attr('aria-controls');
>> +        $('#' + controlledNode)[toExpand ? 'removeClass' : 
>> 'addClass']('hidden');
>> +        $(header).attr('aria-expanded', toExpand ? 'true' : 'false');
>> +    };
>> +
>> +    var repositoriesGrid = null;
>> +    var initRepositoriesGrid = function(repo_type) {
>> +        var gridFields=[];
>> +        if (repo_type == "yum") {
>> +                gridFields=[{
>> +                    name: 'repo_id',
>> +                    label: i18n['GGBREPO6004M'],
>> +                    'class': 'repository-id'
>> +                }, {
>> +                    name: 'config[repo_name]',
>> +                    label: i18n['GGBREPO6005M'],
>> +                    'class': 'repository-name'
>> +                }, {
>> +                    name: 'enabled',
>> +                    label: i18n['GGBREPO6009M'],
>> +                    'class': 'repository-enabled'
>> +                }];
>> +        }
>> +        else if (repo_type == "deb") {
>> +                gridFields=[{
>> +                    name: 'baseurl',
>> +                    label: i18n['GGBREPO6006M'],
>> +                    makeTitle: true,
>> +                    'class': 'repository-baseurl deb'
>> +                }, {
>> +                    name: 'enabled',
>> +                    label: i18n['GGBREPO6009M'],
>> +                    'class': 'repository-enabled deb'
>> +                }, {
>> +                    name: 'config[dist]',
>> +                    label: "dist",
>> +                    'class': 'repository-gpgcheck deb'
>> +                }, {
>> +                    name: 'config[comps]',
>> +                    label: "comps",
>> +                    'class': 'repository-gpgcheck deb'
>> +                }];
>> +        }
>> +        else {
>> +            gridFields=[{
>> +                name: 'repo_id',
>> +                label: i18n['GGBREPO6004M'],
>> +                'class': 'repository-id'
>> +                }, {
>> +                    name: 'enabled',
>> +                    label: i18n['GGBREPO6009M'],
>> +                    'class': 'repository-enabled'
>> +                }, {
>> +                    name: 'baseurl',
>> +                    label: i18n['GGBREPO6006M'],
>> +                    makeTitle: true,
>> +                    'class': 'repository-baseurl'
>> +                }];
>> +        }
>> +        repositoriesGrid = new kimchi.widget.Grid({
>> +            container: 'repositories-grid-container',
>> +            id: 'repositories-grid',
>> +            title: i18n['GGBREPO6003M'],
>> +            toolbarButtons: [{
>> +                id: 'repositories-grid-add-button',
>> +                label: i18n['GGBREPO6012M'],
>> +                onClick: function(event) {
>> + wok.window.open({url:'plugins/gingerbase/repository-add.html',
>> +                                    class: repo_type});
>> +                }
>> +            }, {
>> +                id: 'repositories-grid-enable-button',
>> +                label: i18n['GGBREPO6016M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var repository = repositoriesGrid.getSelected();
>> +                    if(!repository) {
>> +                        return;
>> +                    }
>> +                    var name = repository['repo_id'];
>> +                    var enable = !repository['enabled'];
>> +                    $(this).prop('disabled', true);
>> +                    kimchi.enableRepository(name, enable, function() {
>> + wok.topic('kimchi/repositoryUpdated').publish();
>> +                    });
>> +                }
>> +            }, {
>> +                id: 'repositories-grid-edit-button',
>> +                label: i18n['GGBREPO6013M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var repository = repositoriesGrid.getSelected();
>> +                    if(!repository) {
>> +                        return;
>> +                    }
>> +                    kimchi.selectedRepository = repository['repo_id'];
>> + wok.window.open({url:'plugins/gingerbase/repository-edit.html',
>> +                                    class: repo_type});
>> +                }
>> +            }, {
>> +                id: 'repositories-grid-remove-button',
>> +                label: i18n['GGBREPO6014M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var repository = repositoriesGrid.getSelected();
>> +                    if(!repository) {
>> +                        return;
>> +                    }
>> +
>> +                    var settings = {
>> +                        title : i18n['GGBREPO6001M'],
>> +                        content : i18n['GGBREPO6002M'],
>> +                        confirm : i18n['GGBAPI6004M'],
>> +                        cancel : i18n['GGBAPI6003M']
>> +                    };
>> +
>> +                    wok.confirm(settings, function() {
>> +                        kimchi.deleteRepository(
>> +                            repository['repo_id'],
>> +                            function(result) {
>> + wok.topic('kimchi/repositoryDeleted').publish(result);
>> +                            }, function(error) {
>> +                            }
>> +                        );
>> +                    });
>> +                }
>> +            }],
>> +            onRowSelected: function(row) {
>> +                var repository = repositoriesGrid.getSelected();
>> +                if(!repository) {
>> +                    return;
>> +                }
>> + $('#repositories-grid-remove-button').prop('disabled', false);
>> + $('#repositories-grid-edit-button').prop('disabled', false);
>> +                var enabled = repository['enabled'];
>> +                $('#repositories-grid-enable-button')
>> +                    .text(i18n[enabled ? 'GGBREPO6017M' : 
>> 'GGBREPO6016M'])
>> +                    .prop('disabled', false);
>> +            },
>> +            frozenFields: [],
>> +            fields: gridFields,
>> +            data: listRepositories
>> +        });
>> +    };
>> +
>> +    var listRepositories = function(gridCallback) {
>> +        kimchi.listRepositories(function(repositories) {
>> +            if($.isFunction(gridCallback)) {
>> +                gridCallback(repositories);
>> +            }
>> +            else {
>> +                if(repositoriesGrid) {
>> +                    repositoriesGrid.setData(repositories);
>> +                }
>> +                else {
>> +                    initRepositoriesGrid();
>> +                    repositoriesGrid.setData(repositories);
>> +                }
>> +            }
>> +        },
>> +        function(error) {
>> +            var message = error && error['responseJSON'] && 
>> error['responseJSON']['reason'];
>> +
>> +            if($.isFunction(gridCallback)) {
>> +                gridCallback([]);
>> +            }
>> +            repositoriesGrid &&
>> +                repositoriesGrid.showMessage(message || 
>> i18n['GGBUPD6008M']);
>> +        });
>> +
>> +        $('#repositories-grid-remove-button').prop('disabled', true);
>> +        $('#repositories-grid-edit-button').prop('disabled', true);
>> +        $('#repositories-grid-enable-button').prop('disabled', true);
>> +    };
>> +
>> +    var softwareUpdatesGridID = 'software-updates-grid';
>> +    var softwareUpdatesGrid = null;
>> +    var progressAreaID = 'software-updates-progress-textarea';
>> +    var reloadProgressArea = function(result) {
>> +        var progressArea = $('#' + progressAreaID)[0];
>> +        $(progressArea).text(result['message']);
>> +        var scrollTop = $(progressArea).prop('scrollHeight');
>> +        $(progressArea).prop('scrollTop', scrollTop);
>> +    };
>> +
>> +    var initSoftwareUpdatesGrid = function(softwareUpdates) {
>> +        softwareUpdatesGrid = new kimchi.widget.Grid({
>> +            container: 'software-updates-grid-container',
>> +            id: softwareUpdatesGridID,
>> +            title: i18n['GGBUPD6001M'],
>> +            rowSelection: 'disabled',
>> +            toolbarButtons: [{
>> +                id: softwareUpdatesGridID + '-update-button',
>> +                label: i18n['GGBUPD6006M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var updateButton = $(this);
>> +                    var progressArea = $('#' + progressAreaID)[0];
>> + $('#software-updates-progress-container').removeClass('hidden');
>> +                    $(progressArea).text('');
>> +                    !wok.isElementInViewport(progressArea) &&
>> +                        progressArea.scrollIntoView();
>> + $(updateButton).text(i18n['GGBUPD6007M']).prop('disabled', true);
>> +
>> +                    kimchi.updateSoftware(function(result) {
>> +                        reloadProgressArea(result);
>> + $(updateButton).text(i18n['GGBUPD6006M']).prop('disabled', false);
>> + wok.topic('kimchi/softwareUpdated').publish({
>> +                            result: result
>> +                        });
>> +                    }, function(error) {
>> +                        var message = error && error['responseJSON'] 
>> && error['responseJSON']['reason'];
>> +                        wok.message.error(message || 
>> i18n['GGBUPD6009M']);
>> + $(updateButton).text(i18n['GGBUPD6006M']).prop('disabled', false);
>> +                    }, reloadProgressArea);
>> +                }
>> +            }],
>> +            frozenFields: [],
>> +            fields: [{
>> +                name: 'package_name',
>> +                label: i18n['GGBUPD6002M'],
>> +                'class': 'software-update-name'
>> +            }, {
>> +                name: 'version',
>> +                label: i18n['GGBUPD6003M'],
>> +                'class': 'software-update-version'
>> +            }, {
>> +                name: 'arch',
>> +                label: i18n['GGBUPD6004M'],
>> +                'class': 'software-update-arch'
>> +            }, {
>> +                name: 'repository',
>> +                label: i18n['GGBUPD6005M'],
>> +                'class': 'software-update-repos'
>> +            }],
>> +            data: listSoftwareUpdates
>> +        });
>> +    };
>> +
>> +    var listSoftwareUpdates = function(gridCallback) {
>> +        kimchi.listSoftwareUpdates(function(softwareUpdates) {
>> +            if($.isFunction(gridCallback)) {
>> +                gridCallback(softwareUpdates);
>> +            }
>> +            else {
>> +                if(softwareUpdatesGrid) {
>> + softwareUpdatesGrid.setData(softwareUpdates);
>> +                }
>> +                else {
>> +                    initSoftwareUpdatesGrid(softwareUpdates);
>> +                }
>> +            }
>> +
>> +            var updateButton = $('#' + softwareUpdatesGridID + 
>> '-update-button');
>> +            $(updateButton).prop('disabled', softwareUpdates.length 
>> === 0);
>> +        }, function(error) {
>> +            var message = error && error['responseJSON'] && 
>> error['responseJSON']['reason'];
>> +            if($.isFunction(gridCallback)) {
>> +                gridCallback([]);
>> +            }
>> +            softwareUpdatesGrid &&
>> +                softwareUpdatesGrid.showMessage(message || 
>> i18n['GGBUPD6008M']);
>> +        });
>> +    };
>> +
>> +    var reportGridID = 'available-reports-grid';
>> +    var reportGrid = null;
>> +    var enableReportButtons = function(toEnable) {
>> +        var buttonID = '#{grid}-{btn}-button';
>> +        $.each(['rename', 'remove', 'download'], function(i, n) {
>> +            $(wok.substitute(buttonID, {
>> +                grid: reportGridID,
>> +                btn: n
>> +            })).prop('disabled', !toEnable);
>> +        });
>> +    };
>> +    var initReportGrid = function(reports) {
>> +        reportGrid = new kimchi.widget.Grid({
>> +            container: 'available-reports-grid-container',
>> +            id: reportGridID,
>> +            title: i18n['GGBDR6002M'],
>> +            toolbarButtons: [{
>> +                id: reportGridID + '-generate-button',
>> +                label: i18n['GGBDR6006M'],
>> +                onClick: function(event) {
>> + wok.window.open('plugins/gingerbase/report-add.html');
>> +                }
>> +            }, {
>> +                id: reportGridID + '-rename-button',
>> +                label: i18n['GGBDR6008M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var report = reportGrid.getSelected();
>> +                    if(!report) {
>> +                        return;
>> +                    }
>> +
>> +                    kimchi.selectedReport = report['name'];
>> + wok.window.open('plugins/gingerbase/report-rename.html');
>> +                }
>> +            }, {
>> +                id: reportGridID + '-remove-button',
>> +                label: i18n['GGBDR6009M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var report = reportGrid.getSelected();
>> +                    if(!report) {
>> +                        return;
>> +                    }
>> +
>> +                    var settings = {
>> +                        title : i18n['GGBAPI6004M'],
>> +                        content : i18n['GGBDR6001M'],
>> +                        confirm : i18n['GGBAPI6002M'],
>> +                        cancel : i18n['GGBAPI6003M']
>> +                    };
>> +
>> +                    wok.confirm(settings, function() {
>> +                        kimchi.deleteReport({
>> +                            name: report['name']
>> +                        }, function(result) {
>> +                            listDebugReports();
>> +                        }, function(error) {
>> + wok.message.error(error.responseJSON.reason);
>> +                        });
>> +                    });
>> +                }
>> +            }, {
>> +                id: reportGridID + '-download-button',
>> +                label: i18n['GGBDR6010M'],
>> +                disabled: true,
>> +                onClick: function(event) {
>> +                    var report = reportGrid.getSelected();
>> +                    if(!report) {
>> +                        return;
>> +                    }
>> +
>> +                    kimchi.downloadReport({
>> +                        file: report['uri']
>> +                    });
>> +                }
>> +            }],
>> +            onRowSelected: function(row) {
>> +                var report = reportGrid.getSelected();
>> +                // Only enable report buttons if the selected line 
>> is not a
>> +                // pending report
>> +                if (report['time'] == i18n['GGBDR6007M']) {
>> +                    var gridElement = $('#'+ reportGridID);
>> +                    var row = $('tr:contains(' + report['name'] + 
>> ')', gridElement);
>> +                    enableReportButtons(false);
>> +                    row.attr('class', '');
>> +                }
>> +                else {
>> +                    enableReportButtons(true);
>> +                }
>> +            },
>> +            frozenFields: [],
>> +            fields: [{
>> +                name: 'name',
>> +                label: i18n['GGBDR6003M'],
>> +                'class': 'debug-report-name'
>> +            }, {
>> +                name: 'time',
>> +                label: i18n['GGBDR6005M'],
>> +                'class': 'debug-report-time'
>> +            }],
>> +            data: reports
>> +        });
>> +    };
>> +
>> +    var getPendingReports = function() {
>> +        var reports = []
>> +        var filter = 'status=running&target_uri=' + 
>> encodeURIComponent('^/plugins/gingerbase/debugreports/*')
>> +
>> +        kimchi.getTasksByFilter(filter, function(tasks) {
>> +            for(var i = 0; i < tasks.length; i++) {
>> +                reportName = 
>> tasks[i].target_uri.replace(/^\/plugins\/gingerbase\/debugreports\//, 
>> '') || i18n['GGBDR6012M'];
>> +                reports.push({'name': reportName, 'time': 
>> i18n['GGBDR6007M']})
>> +
>> +                if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) {
>> +                    continue;
>> +                }
>> +
>> +                kimchi.trackTask(tasks[i].id, function(result) {
>> + wok.topic('kimchi/debugReportAdded').publish();
>> +                }, function(result) {
>> +                    // Error message from Async Task status
>> +                    if (result['message']) {
>> +                        var errText = result['message'];
>> +                    }
>> +                    // Error message from standard kimchi exception
>> +                    else {
>> +                        var errText = result['responseJSON']['reason'];
>> +                    }
>> +                    result && wok.message.error(errText);
>> + wok.topic('kimchi/debugReportAdded').publish();
>> +                }, null);
>> +            }
>> +        }, null, true);
>> +
>> +        return reports;
>> +    };
>> +
>> +    var listDebugReports = function() {
>> +        kimchi.listReports(function(reports) {
>> +            pendingReports = getPendingReports();
>> +            allReports = pendingReports.concat(reports);
>> +            $('#debug-report-section').removeClass('hidden');
>> +
>> +            // Row selection will be cleared so disable buttons here
>> +            enableReportButtons(false);
>> +
>> +            if(reportGrid) {
>> +                reportGrid.setData(allReports);
>> +            }
>> +            else {
>> +                initReportGrid(allReports);
>> +            }
>> +
>> +            // Set id-debug-img to pending reports
>> +            // It will display a loading icon
>> +            var gridElement = $('#' + reportGridID);
>> +                $.each($('td:contains(' + i18n['GGBDR6007M']  + ')', 
>> gridElement), function(index, row) {
>> +                $(row).parent().addClass('no-hover');
>> +                $(row).attr('id', 'id-debug-img');
>> +            });
>> +        }, function(error) {
>> +            if(error['status'] == 403) {
>> +                $('#debug-report-section').addClass('hidden');
>> +                return;
>> +            }
>> +            $('#debug-report-section').removeClass('hidden');
>> +        });
>> +    };
>> +
>> +    var shutdownButtonID = '#host-button-shutdown';
>> +    var restartButtonID = '#host-button-restart';
>> +    var shutdownHost = function(params) {
>> +        var settings = {
>> +            title : i18n['GGBAPI6004M'],
>> +            content : i18n['GGBHOST6008M'],
>> +            confirm : i18n['GGBAPI6002M'],
>> +            cancel : i18n['GGBAPI6003M']
>> +        };
>> +
>> +        wok.confirm(settings, function() {
>> +            kimchi.shutdown(params);
>> +            $(shutdownButtonID).prop('disabled', true);
>> +            $(restartButtonID).prop('disabled', true);
>> +            // Check if there is any VM is running.
>> +            // FIXME : Find alternative way to figure out if any vms 
>> running
>> +            // kimchi.listVMs(function(vms) {
>> +            //     for(var i = 0; i < vms.length; i++) {
>> +            //         if(vms[i]['state'] === 'running') {
>> +            // wok.message.error.code('GGBHOST6001E');
>> +            //             $(shutdownButtonID).prop('disabled', false);
>> +            //             $(restartButtonID).prop('disabled', false);
>> +            //             return;
>> +            //         }
>> +            //     }
>> +            //
>> +            // });
>> +        }, function() {
>> +        });
>> +    };
>> +
>> +    var initPage = function() {
>> +        $('#host-info-container .section-header').each(function(i, 
>> header) {
>> +            $('<span class="arrow"></span>').prependTo(header);
>> +            var toExpand = $(header).attr('aria-expanded') !== 'false';
>> +            expand(header, toExpand);
>> +        });
>> +
>> +        $('#host-info-container').on('click', '.section-header', 
>> function(event) {
>> +            var toExpand = $(this).attr('aria-expanded') === 'false';
>> +            expand(this, toExpand);
>> +        });
>> +
>> +        $('#host-button-shutdown').on('click', function(event) {
>> +            shutdownHost(null);
>> +        });
>> +
>> +        $('#host-button-restart').on('click', function(event) {
>> +            shutdownHost({
>> +                reboot: true
>> +            });
>> +        });
>> +
>> +        var setupUI = function() {
>> +            if (kimchi.capabilities == undefined) {
>> +                setTimeout(setupUI, 2000);
>> +                return;
>> +            }
>> +
>> +            if((kimchi.capabilities['repo_mngt_tool']) && 
>> (kimchi.capabilities['repo_mngt_tool']!="None")) {
>> + initRepositoriesGrid(kimchi.capabilities['repo_mngt_tool']);
>> + $('#repositories-section').switchClass('hidden', 
>> kimchi.capabilities['repo_mngt_tool']);
>> +                wok.topic('kimchi/repositoryAdded')
>> +                    .subscribe(listRepositories);
>> +                wok.topic('kimchi/repositoryUpdated')
>> +                    .subscribe(listRepositories);
>> +                wok.topic('kimchi/repositoryDeleted')
>> +                    .subscribe(listRepositories);
>> +            }
>> +
>> +            if(kimchi.capabilities['update_tool']) {
>> + $('#software-update-section').removeClass('hidden');
>> +                initSoftwareUpdatesGrid();
>> +                wok.topic('kimchi/softwareUpdated')
>> +                    .subscribe(listSoftwareUpdates);
>> + $('#software-updates-progress-container').accordion({
>> +                    collapsible: true
>> +                });
>> +            }
>> +
>> +            if(kimchi.capabilities['system_report_tool']) {
>> +                listDebugReports();
>> +                wok.topic('kimchi/debugReportAdded')
>> +                    .subscribe(listDebugReports);
>> +                wok.topic('kimchi/debugReportRenamed')
>> +                    .subscribe(listDebugReports);
>> +            }
>> +        };
>> +        setupUI();
>> +    };
>> +
>> +    kimchi.getHost(function(data) {
>> +        var htmlTmpl = $('#host-tmpl').html();
>> +        data['logo'] = data['logo'] || '';
>> +        data['memory'] = wok.formatMeasurement(data['memory'], {
>> +            fixed: 2
>> +        });
>> +        var templated = wok.substitute(htmlTmpl, data);
>> +        $('#host-content-container').html(templated);
>> +
>> +        initPage();
>> +        initTracker();
>> +    });
>> +
>> +    var StatsMgr = function() {
>> +        var statsArray = {
>> +            cpu: {
>> +                u: {
>> +                    type: 'percent',
>> +                    legend: i18n['GGBHOST6002M'],
>> +                    points: []
>> +                }
>> +            },
>> +            memory: {
>> +                u: {
>> +                    type: 'value',
>> +                    base: 2,
>> +                    fixed: 2,
>> +                    legend: i18n['GGBHOST6003M'],
>> +                    points: []
>> +                }
>> +            },
>> +            diskIO: {
>> +                r: {
>> +                    type: 'value',
>> +                    base: 2,
>> +                    fixed: 2,
>> +                    unit: 'B/s',
>> +                    legend: i18n['GGBHOST6004M'],
>> +                    points: []
>> +                },
>> +                w: {
>> +                    type: 'value',
>> +                    base: 2,
>> +                    fixed: 2,
>> +                    unit: 'B/s',
>> +                    legend: i18n['GGBHOST6005M'],
>> +                    'class': 'disk-write',
>> +                    points: []
>> +                }
>> +            },
>> +            networkIO: {
>> +                r: {
>> +                    type: 'value',
>> +                    base: 2,
>> +                    fixed: 2,
>> +                    unit: 'B/s',
>> +                    legend: i18n['GGBHOST6006M'],
>> +                    points: []
>> +                },
>> +                s: {
>> +                    type: 'value',
>> +                    base: 2,
>> +                    fixed: 2,
>> +                    unit: 'B/s',
>> +                    legend: i18n['GGBHOST6007M'],
>> +                    'class': 'network-sent',
>> +                    points: []
>> +                }
>> +            }
>> +        };
>> +        var SIZE = 20;
>> +        var cursor = SIZE;
>> +
>> +        var add = function(stats) {
>> +            for(var key in stats) {
>> +                var item = stats[key];
>> +                for(var metrics in item) {
>> +                    var value = item[metrics]['v'];
>> +                    var max = item[metrics]['max'];
>> +                    var unifiedMetrics = statsArray[key][metrics];
>> +                    var ps = unifiedMetrics['points'];
>> +                    if(!Array.isArray(value)){
>> +                        ps.push(value);
>> +                        if(ps.length > SIZE + 1) {
>> +                            ps.shift();
>> +                        }
>> +                    }
>> +                    else{
>> +                        ps=ps.concat(value);
>> +                        ps.splice(0, ps.length-SIZE-1);
>> +                        unifiedMetrics['points']=ps;
>> +                    }
>> +                    if(max !== undefined) {
>> +                        unifiedMetrics['max'] = max;
>> +                    }
>> +                    else {
>> +                        if(unifiedMetrics['type'] !== 'value') {
>> +                            continue;
>> +                        }
>> +                        max = -Infinity;
>> +                        $.each(ps, function(i, value) {
>> +                            if(value > max) {
>> +                                max = value;
>> +                            }
>> +                        });
>> +                        if(max === 0) {
>> +                            ++max;
>> +                        }
>> +                        max *= 1.1;
>> +                        unifiedMetrics['max'] = max;
>> +                    }
>> +                }
>> +            }
>> +            cursor++;
>> +        };
>> +
>> +        var get = function(which) {
>> +            var stats = statsArray[which];
>> +            var lines = [];
>> +            for(var k in stats) {
>> +                var obj = stats[k];
>> +                var line = {
>> +                    type: obj['type'],
>> +                    base: obj['base'],
>> +                    unit: obj['unit'],
>> +                    fixed: obj['fixed'],
>> +                    legend: obj['legend']
>> +                };
>> +                if(obj['max']) {
>> +                    line['max'] = obj['max'];
>> +                }
>> +                if(obj['class']) {
>> +                    line['class'] = obj['class'];
>> +                }
>> +                var ps = obj['points'];
>> +                var numStats = ps.length;
>> +                var unifiedPoints = [];
>> +                $.each(ps, function(i, value) {
>> +                    unifiedPoints.push({
>> +                        x: cursor - numStats + i,
>> +                        y: value
>> +                    });
>> +                });
>> +                line['points'] = unifiedPoints;
>> +                lines.push(line);
>> +            }
>> +            return lines;
>> +        };
>> +
>> +        return {
>> +            add: add,
>> +            get: get
>> +        };
>> +    };
>> +
>> +    var Tracker = function(charts) {
>> +      var charts = charts;
>> +      var timer = null;
>> +      var statsPool = new StatsMgr();
>> +      var setCharts = function(newCharts) {
>> +          charts = newCharts;
>> +          for(var key in charts) {
>> +              var chart = charts[key];
>> +              chart.updateUI(statsPool.get(key));
>> +          }
>> +      };
>> +
>> +      var self = this;
>> +
>> +      var UnifyStats = function(stats) {
>> +          var result= {
>> +              cpu: {
>> +                  u: {
>> +                      v: stats['cpu_utilization']
>> +                  }
>> +              },
>> +              memory: {
>> +                  u: {
>> +                  }
>> +              },
>> +              diskIO: {
>> +                  r: {
>> +                      v: stats['disk_read_rate']
>> +                  },
>> +                  w: {
>> +                      v: stats['disk_write_rate']
>> +                  }
>> +              },
>> +              networkIO: {
>> +                  r: {
>> +                      v: stats['net_recv_rate']
>> +                  },
>> +                  s: {
>> +                      v: stats['net_sent_rate']
>> +                  }
>> +              }
>> +          };
>> +          if(Array.isArray(stats['memory'])){
>> +              result.memory.u['v']=[];
>> +              result.memory.u['max']=-Infinity;
>> +              for(var i=0;i<stats['memory'].length;i++){
>> + result.memory.u['v'].push(stats['memory'][i]['avail']);
>> + 
>> result.memory.u['max']=Math.max(result.memory.u['max'],stats['memory'][i]['total']);
>> +              }
>> +          }
>> +          else {
>> +              result.memory.u['v']=stats['memory']['avail'],
>> +              result.memory.u['max']=stats['memory']['total']
>> +          }
>> +          return(result);
>> +      };
>> +
>> +
>> +      var statsCallback = function(stats) {
>> +              var unifiedStats = UnifyStats(stats);
>> +              statsPool.add(unifiedStats);
>> +              for(var key in charts) {
>> +                  var chart = charts[key];
>> +                  chart.updateUI(statsPool.get(key));
>> +              }
>> +              timer = setTimeout(function() {
>> +                  continueTrack();
>> +              }, 1000);
>> +          };
>> +
>> +      var track = function() {
>> +          kimchi.getHostStatsHistory(statsCallback,
>> +            function() {
>> +                continueTrack();
>> +            });
>> +      };
>> +
>> +      var continueTrack = function() {
>> +          kimchi.getHostStats(statsCallback,
>> +            function() {
>> +                continueTrack();
>> +            });
>> +      };
>> +
>> +      var destroy = function() {
>> +          timer && clearTimeout(timer);
>> +          timer = null;
>> +      };
>> +
>> +      return {
>> +        setCharts: setCharts,
>> +        start: track,
>> +        stop: destroy
>> +      };
>> +    };
>> +
>> +    var initTracker = function() {
>> +        // TODO: Extend tabs with onUnload event to unregister timers.
>> +        if(kimchi.hostTimer) {
>> +            kimchi.hostTimer.stop();
>> +            delete kimchi.hostTimer;
>> +        }
>> +
>> +        var trackedCharts = {
>> +            cpu: new kimchi.widget.LineChart({
>> +                id: 'chart-cpu',
>> +                node: 'container-chart-cpu',
>> +                type: 'percent'
>> +            }),
>> +            memory: new kimchi.widget.LineChart({
>> +                id: 'chart-memory',
>> +                node: 'container-chart-memory',
>> +                type: 'value'
>> +            }),
>> +            diskIO: new kimchi.widget.LineChart({
>> +                id: 'chart-disk-io',
>> +                node: 'container-chart-disk-io',
>> +                type: 'value'
>> +            }),
>> +            networkIO: new kimchi.widget.LineChart({
>> +                id: 'chart-network-io',
>> +                node: 'container-chart-network-io',
>> +                type: 'value'
>> +            })
>> +        };
>> +
>> +        if(kimchi.hostTimer) {
>> +            kimchi.hostTimer.setCharts(trackedCharts);
>> +        }
>> +        else {
>> +            kimchi.hostTimer = new Tracker(trackedCharts);
>> +            kimchi.hostTimer.start();
>> +        }
>> +    };
>> +
>> +    $('#host-root-container').on('remove', function() {
>> +        if(kimchi.hostTimer) {
>> +            kimchi.hostTimer.stop();
>> +            delete kimchi.hostTimer;
>> +            }
>> +
>> +        repositoriesGrid && repositoriesGrid.destroy();
>> +        wok.topic('kimchi/repositoryAdded')
>> +            .unsubscribe(listRepositories);
>> +        wok.topic('kimchi/repositoryUpdated')
>> +            .unsubscribe(listRepositories);
>> +        wok.topic('kimchi/repositoryDeleted')
>> +            .unsubscribe(listRepositories);
>> +
>> +        softwareUpdatesGrid && softwareUpdatesGrid.destroy();
>> + wok.topic('kimchi/softwareUpdated').unsubscribe(listSoftwareUpdates);
>> +
>> +        reportGrid && reportGrid.destroy();
>> + wok.topic('kimchi/debugReportAdded').unsubscribe(listDebugReports);
>> + wok.topic('kimchi/debugReportRenamed').unsubscribe(listDebugReports);
>> +    });
>> +};
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
>> new file mode 100644
>> index 0000000..13bbee1
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.line-chart.js
>> @@ -0,0 +1,202 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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.
>> + */
>> +
>> +/**
>> + * new kimchi.widget.LineChart({
>> + *   node: 'line-chart-cpu',
>> + *   id: 'line-chart',
>> + *   type: 'value'
>> + * });
>> + */
>> +kimchi.widget.LineChart = function(params) {
>> +    var container = $('#' + params['node']);
>> +    container.addClass('chart-container');
>> +    var height = container.height();
>> +    var width = container.width();
>> +    var numHLines = 4;
>> +    var linesSpace = height / numHLines;
>> +    var period = params['period'] || 20;
>> +    var xFactor = width / period;
>> +    var yFactor = height / 100;
>> +    var xStart = params['xStart'] || 0;
>> +    var linesOffset = 0;
>> +    var canvasID = params['id'];
>> +    var maxValue = params['maxValue'] || -Infinity;
>> +    var type = params['type'];
>> +    var chartVAxis = null;
>> +    var chartTitle = null;
>> +    var chartLegend = null;
>> +    var seriesMap = {};
>> +    var formatSettings = {};
>> +
>> +    var setMaxValue = function(newValue) {
>> +        maxValue = newValue;
>> +    };
>> +
>> +    /**
>> +     *
>> +     * settings: {
>> +     *   'class': 'disk-read-rate'
>> +     * }
>> +     */
>> +    var updateUI = function(data) {
>> +        var container = $('#' + params['node']);
>> +        if(!container.length) {
>> +            return;
>> +        }
>> +
>> +        if(!$.isArray(data)) {
>> +            data = [data];
>> +        }
>> +        var seriesCount = 0;
>> +        var singleSeries = data.length === 1;
>> +        var firstSeries = data[0];
>> +
>> +        // TODO: Multiple axes support.
>> +        if(type === 'value') {
>> +            $.each(data, function(i, series) {
>> +                if(series['max'] > maxValue) {
>> +                    maxValue = series['max'];
>> +                    formatSettings = {
>> +                        base: series['base'],
>> +                        unit: series['unit'],
>> +                        fixed: series['fixed']
>> +                    };
>> +                }
>> +            });
>> +        }
>> +
>> +        var canvasNode = $('#' + canvasID);
>> +        canvasNode.length && canvasNode.remove();
>> +        var htmlStr = [
>> +          '<svg id="', canvasID, '" class="line-chart"',
>> +              ' height="', height, '" width="', width, '"',
>> +          '>',
>> +            '<rect height="', height, '" width="', width, '" 
>> class="background" />'
>> +        ];
>> +
>> +        for(var x = linesOffset; x < width; x += linesSpace) {
>> +            htmlStr.push(
>> +                '<line x1="', x, '" y1="', 0, '" x2="', x, '" y2="', 
>> height, '" />'
>> +            );
>> +        }
>> +
>> +        linesOffset -= xFactor;
>> +        while(linesOffset < 0) {
>> +            linesOffset = linesSpace + linesOffset;
>> +        }
>> +
>> +        for(var y = height - linesSpace; y > 0; y -= linesSpace) {
>> +            htmlStr.push(
>> +                '<line x1="', 0, '" y1="', y, '" x2="', width, '" 
>> y2="', y, '" />'
>> +            );
>> +        }
>> +
>> +        var maxValueLabel = i18n['GGBHOST6001M'] + ' ' +
>> +            (type === 'value'
>> +                ? wok.formatMeasurement(maxValue, formatSettings)
>> +                : '100%');
>> +        if(!chartVAxis) {
>> +            chartVAxis = $('<div class="chart-vaxis-container">' +
>> +                maxValueLabel +
>> +                '</div>'
>> +            );
>> +            container.before(chartVAxis);
>> +        }
>> +        else {
>> +            chartVAxis.text(maxValueLabel);
>> +        }
>> +
>> +        seriesNames = [];
>> +        $.each(data, function(i, series) {
>> +            var points = series['points'];
>> +            var className = series['class'];
>> +            var latestPoint = points.slice(-1).pop();
>> +            xStart = latestPoint['x'] - period;
>> +
>> +            htmlStr.push('<polyline',
>> +                ' class="series', className ? ' ' + className : '', 
>> '"',
>> +                ' points="'
>> +            );
>> +            var first = true;
>> +            $.each(points, function(i, point) {
>> +                if(first) {
>> +                    first = false;
>> +                }
>> +                else {
>> +                    htmlStr.push(' ');
>> +                }
>> +
>> +                var x = xFactor * (point['x'] - xStart);
>> +                var y = height - yFactor * (type === 'value' ?
>> +                    point['y'] * 100 / maxValue :
>> +                    point['y']
>> +                );
>> +                htmlStr.push(x, ',', y);
>> +            });
>> +            htmlStr.push('" />');
>> +        });
>> +
>> +        htmlStr.push('</svg>');
>> +
>> +        var canvasNode = $(htmlStr.join('')).appendTo(container);
>> +
>> +        if(!chartLegend) {
>> +            chartLegend = $('<div 
>> class="chart-legend-container"></div>');
>> +            container.after(chartLegend);
>> +        }
>> +        else {
>> +            chartLegend.empty();
>> +        }
>> +        $('polyline.series', canvasNode).each(function(i, polyline) {
>> +            var wrapper = $('<div class="legend-wrapper"></div>')
>> +                .appendTo(chartLegend);
>> +            $([
>> +                '<svg class="legend-icon" width="20" height="10">',
>> +                    '<line x1="0" y1="5" x2="20" y2="5"/>',
>> +                '</svg>'
>> +            ].join('')).appendTo(wrapper);
>> +            $('line', wrapper).css({
>> +                stroke: $(polyline).css('stroke'),
>> +                'stroke-width': $(polyline).css('stroke-width')
>> +            });
>> +            var label = data[i]['legend'];
>> +            var base = data[i]['base'];
>> +            $('<label class="legend-label">' + label + '</label>')
>> +                .appendTo(wrapper);
>> +            var latestPoint = data[i]['points'].slice(-1).pop();
>> +            var latestValue = latestPoint['y'];
>> +            if(type === 'value') {
>> +                latestValue = wok.formatMeasurement(
>> +                    latestValue,
>> +                    formatSettings
>> +                );
>> +            }
>> +            else {
>> +                latestValue += '%';
>> +            }
>> +            $('<div class="latest-value">' + latestValue + '</div>')
>> +                .appendTo(wrapper);
>> +        });
>> +    };
>> +
>> +    return {
>> +        setMaxValue: setMaxValue,
>> +        updateUI: updateUI
>> +    }
>> +};
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.main.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.main.js
>> new file mode 100644
>> index 0000000..2fdeb85
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.main.js
>> @@ -0,0 +1,26 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.capabilities = undefined;
>> +kimchi.getCapabilities(function(result) {
>> +    kimchi.capabilities = result;
>> +
>> +    if(kimchi.capabilities.federation=="on")
>> +        $('#peers').removeClass('hide-content');
>> +}, function() {
>> +    kimchi.capabilities = {};
>> +});
>> diff --git 
>> a/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
>> new file mode 100644
>> index 0000000..87010b1
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.report_add_main.js
>> @@ -0,0 +1,72 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.report_add_main = function() {
>> +    var reportGridID = 'available-reports-grid';
>> +    var addReportForm = $('#form-report-add');
>> +    var submitButton = $('#button-report-add');
>> +    var nameTextbox = $('input[name="name"]', addReportForm);
>> +    nameTextbox.select();
>> +
>> +    var submitForm = function(event) {
>> +        if(submitButton.prop('disabled')) {
>> +            return false;
>> +        }
>> +        var reportName = nameTextbox.val();
>> +        var validator = RegExp("^[_A-Za-z0-9-]*$");
>> +        if (!validator.test(reportName)) {
>> +            wok.message.error.code('GGBDR6011M');
>> +            return false;
>> +        }
>> +        var formData = addReportForm.serializeObject();
>> +        var taskAccepted = false;
>> +        var onTaskAccepted = function() {
>> +            if(taskAccepted) {
>> +                return;
>> +            }
>> +            taskAccepted = true;
>> +            wok.window.close();
>> +            wok.topic('kimchi/debugReportAdded').publish();
>> +        };
>> +
>> +        kimchi.createReport(formData, function(result) {
>> +            onTaskAccepted();
>> +            wok.topic('kimchi/debugReportAdded').publish();
>> +        }, function(result) {
>> +            // Error message from Async Task status
>> +            if (result['message']) {
>> +                var errText = result['message'];
>> +            }
>> +            // Error message from standard kimchi exception
>> +            else {
>> +                var errText = result['responseJSON']['reason'];
>> +            }
>> +            result && wok.message.error(errText);
>> +
>> +            taskAccepted &&
>> +                $('.grid-body-view table tr:first-child',
>> +                    '#' + reportGridID).remove();
>> +            submitButton.prop('disabled', false);
>> +            nameTextbox.select();
>> +        }, onTaskAccepted);
>> +
>> +        event.preventDefault();
>> +    };
>> +
>> +    addReportForm.on('submit', submitForm);
>> +    submitButton.on('click', submitForm);
>> +};
>> diff --git 
>> a/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
>> new file mode 100644
>> index 0000000..6134b2e
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.report_rename_main.js
>> @@ -0,0 +1,66 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.report_rename_main = function() {
>> +    var renameReportForm = $('#form-report-rename');
>> +    var submitButton = $('#button-report-rename');
>> +    var nameTextbox = $('input[name="name"]', renameReportForm);
>> +    var submitForm = function(event) {
>> +        if(submitButton.prop('disabled')) {
>> +            return false;
>> +        }
>> +        var reportName = nameTextbox.val();
>> +
>> +        // if the user hasn't changed the report's name,
>> +        // nothing should be done.
>> +        if (reportName == kimchi.selectedReport) {
>> +            wok.message.error.code('GGBDR6013M');
>> +            return false;
>> +        }
>> +
>> +        var validator = RegExp("^[A-Za-z0-9-]*$");
>> +        if (!validator.test(reportName)) {
>> +            wok.message.error.code('GGBDR6011M');
>> +            return false;
>> +        }
>> +        var formData = renameReportForm.serializeObject();
>> +        submitButton.prop('disabled', true);
>> +        nameTextbox.prop('disabled', true);
>> +        kimchi.renameReport(kimchi.selectedReport, formData, 
>> function(result) {
>> +            submitButton.prop('disabled', false);
>> +            nameTextbox.prop('disabled', false);
>> +            wok.window.close();
>> +            wok.topic('kimchi/debugReportRenamed').publish({
>> +                result: result
>> +            });
>> +        }, function(result) {
>> +            var errText = result &&
>> +                result['responseJSON'] &&
>> +                result['responseJSON']['reason'];
>> +            wok.message.error(errText);
>> +            submitButton.prop('disabled', false);
>> +            nameTextbox.prop('disabled', false).focus();
>> +        });
>> +
>> +        event.preventDefault();
>> +    };
>> +
>> +    renameReportForm.on('submit', submitForm);
>> +    submitButton.on('click', submitForm);
>> +
>> +    nameTextbox.val(kimchi.selectedReport).select();
>> +};
>> diff --git 
>> a/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
>> new file mode 100644
>> index 0000000..656306b
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.repository_add_main.js
>> @@ -0,0 +1,96 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.repository_add_main = function() {
>> +
>> +    var addForm = $('#form-repository-add');
>> +    var addButton = $('#button-repository-add');
>> +
>> +    var validateField = function(event) {
>> +        var valid=($(this).val()!=='');
>> +        $(addButton).prop('disabled', !valid);
>> +        return(valid);
>> +    };
>> +
>> +    var validateForm = function(event) {
>> +        var valid=false;
>> +        addForm.find('input.required').each( function() {
>> +            valid=($(this).val()!=='');
>> +            return(!valid);
>> +        });
>> +        return(valid);
>> +    }
>> +
>> +    addForm.find('input.required').on('input propertychange', 
>> validateField);
>> +
>> +    var weedObject = function(obj) {
>> +        for (var key in obj) {
>> +            if (obj.hasOwnProperty(key)) {
>> +                if((typeof(obj[key])==="object") && 
>> !Array.isArray(obj[key])) {
>> +                    weedObject(obj[key]);
>> +                }
>> +                else if(obj[key] == '') {
>> +                    delete obj[key];
>> +                }
>> +            }
>> +        }
>> +    }
>> +
>> +    var addRepository = function(event) {
>> +        var valid = validateForm();
>> +        if(!valid) {
>> +            return false;
>> +        }
>> +
>> +        var formData = $(addForm).serializeObject();
>> +
>> +        if (formData && formData.isMirror!=undefined) {
>> + formData.isMirror=(String(formData.isMirror).toLowerCase() === 
>> 'true');
>> +        }
>> +        if(formData.isMirror) {
>> +            if(formData.config==undefined) {
>> +                formData.config=new Object();
>> +            }
>> +            formData.config.mirrorlist=formData.baseurl;
>> +            delete formData.baseurl;
>> +            delete formData.isMirror;
>> +        }
>> +        weedObject(formData);
>> +        if(formData.config && formData.config.comps) {
>> + formData.config.comps=formData.config.comps.split(/[,\s]/);
>> +            for(var i=0; i>formData.config.comps.length; i++) {
>> + formData.config.comps[i]=formData.config.comps[i].trim();
>> +            }
>> +            for (var j=formData.config.comps.indexOf(""); j!=-1; 
>> j=formData.config.comps.indexOf("")) {
>> +                formData.config.comps.splice(j, 1);
>> +            }
>> +        }
>> +
>> +        kimchi.createRepository(formData, function() {
>> +            wok.topic('kimchi/repositoryAdded').publish();
>> +            wok.window.close();
>> +        }, function(jqXHR, textStatus, errorThrown) {
>> +            var reason = jqXHR &&
>> +                jqXHR['responseJSON'] &&
>> +                jqXHR['responseJSON']['reason'];
>> +            wok.message.error(reason);
>> +        });
>> +        return false;
>> +    };
>> +
>> +    $(addForm).on('submit', addRepository);
>> +};
>> diff --git 
>> a/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
>> new file mode 100644
>> index 0000000..5bfc51e
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.repository_edit_main.js
>> @@ -0,0 +1,74 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2014
>> + *
>> + * Licensed 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.
>> + */
>> +kimchi.repository_edit_main = function() {
>> +
>> +    var editForm = $('#form-repository-edit');
>> +
>> +    var saveButton = $('#repository-edit-button-save');
>> +
>> +    if(kimchi.capabilities['repo_mngt_tool']=="yum") {
>> +        editForm.find('input.deb').prop('disabled', true);
>> +    }
>> +    else if(kimchi.capabilities['repo_mngt_tool']=="deb") {
>> +        editForm.find('input.yum').prop('disabled', true);
>> +    }
>> +
>> +    kimchi.retrieveRepository(kimchi.selectedRepository, 
>> function(repository) {
>> +        editForm.fillWithObject(repository);
>> +
>> +        $('input', editForm).on('input propertychange', 
>> function(event) {
>> +            if($(this).val() !== '') {
>> +                $(saveButton).prop('disabled', false);
>> +            }
>> +        });
>> +    });
>> +
>> +
>> +    var editRepository = function(event) {
>> +        var formData = $(editForm).serializeObject();
>> +
>> +        if (formData && formData.config) {
>> + 
>> formData.config.gpgcheck=(String(formData.config.gpgcheck).toLowerCase() 
>> === 'true');
>> +        }
>> +
>> +        if(formData.config && formData.config.comps) {
>> + formData.config.comps=formData.config.comps.split(/[,\s]/);
>> +            for(var i=0; i>formData.config.comps.length; i++) {
>> + formData.config.comps[i]=formData.config.comps[i].trim();
>> +            }
>> +            for (var j=formData.config.comps.indexOf(""); j!=-1; 
>> j=formData.config.comps.indexOf("")) {
>> +                formData.config.comps.splice(j, 1);
>> +            }
>> +        }
>> +
>> +        kimchi.updateRepository(kimchi.selectedRepository, formData, 
>> function() {
>> +            wok.topic('kimchi/repositoryUpdated').publish();
>> +            wok.window.close();
>> +        }, function(jqXHR, textStatus, errorThrown) {
>> +            var reason = jqXHR &&
>> +                jqXHR['responseJSON'] &&
>> +                jqXHR['responseJSON']['reason'];
>> +            wok.message.error(reason);
>> +        });
>> +
>> +        return false;
>> +    };
>> +
>> +    $(editForm).on('submit', editRepository);
>> +    $(saveButton).on('click', editRepository);
>> +};
>> diff --git a/plugins/gingerbase/ui/js/src/gingerbase.select.js 
>> b/plugins/gingerbase/ui/js/src/gingerbase.select.js
>> new file mode 100644
>> index 0000000..751167f
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/src/gingerbase.select.js
>> @@ -0,0 +1,50 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2013-2014
>> + *
>> + * Licensed 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.
>> + */
>> +
>> +kimchi.select = function(id, options) {
>> +    var listControl = $('#'+ id);
>> +    var targetId = listControl.data('target');
>> +    var labelId = listControl.data('label');
>> +    var value = $('#' + targetId).val();
>> +    var item;
>> +    var itemTag = 'li';
>> +    var selectedClass = 'active';
>> +    $.each(options, function(index, option) {
>> +        item = $('<' + itemTag + '></' + itemTag + '>');
>> +        item.text(option.label);
>> +        item.data('value', option.value);
>> +        if(option.value === value) {
>> +            item.addClass(selectedClass);
>> +            $('#' + labelId).text(option.label);
>> +        }
>> +        listControl.append(item);
>> +    });
>> +
>> +    listControl.on('click', itemTag, function() {
>> +        listControl.children().removeClass(selectedClass);
>> +        $(this).addClass(selectedClass);
>> +        $('#' + labelId).text($(this).text());
>> +        var target = $('#' + targetId);
>> +        var oldValue = target.val();
>> +        var newValue = $(this).data('value');
>> +        target.val(newValue);
>> +        if(oldValue !== newValue) {
>> +            target.change();
>> +        }
>> +    });
>> +};
>> diff --git a/plugins/gingerbase/ui/js/widgets/circleGauge.js 
>> b/plugins/gingerbase/ui/js/widgets/circleGauge.js
>> new file mode 100644
>> index 0000000..32973ac
>> --- /dev/null
>> +++ b/plugins/gingerbase/ui/js/widgets/circleGauge.js
>> @@ -0,0 +1,100 @@
>> +/*
>> + * Project Kimchi
>> + *
>> + * Copyright IBM, Corp. 2014
>> + *
>> + * Licensed 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($) {
>> +    $.widget('kimchi.circleGauge', {
>> +
>> +        options : {
>> +            color : '#87C004',
>> +            fillColor : '#87C004',
>> +            lineWidth : 20,
>> +            shadowSize : '2px',
>> +            font : 'bold 13px Geneva, sans-serif',
>> +            textAlign : 'center',
>> +            radius : 35,
>> +            peakRate : 100,
>> +            display : 0,
>> +            circle : 0,
>> +            label : ''
>> +        },
>> +
>> +        _create : function() {
>> +            //valuesAttr="{" + this.element.data('value')+ "}";
>> +            //console.info(valuesAttr);
>> +            //values=eval("(" + valuesAttr + ")");
>> +            //$.extend(this.options, values);
>> +            this.options.display=this.element.data('display');
>> + this.options.percentage=this.element.data('percentage');
>> +            this._fixupPeakRate();
>> +            this._draw();
>> +        },
>> +
>> +        setValues : function(values) {
>> +            $.extend(this.options, values);
>> +            this._fixupPeakRate();
>> +            this._draw();
>> +        },
>> +
>> +        _fixupPeakRate : function() {
>> +            if (this.options.circle>this.options.peakRate) {
>> +                this.options.peakRate=this.options.circle;
>> +            }
>> +        },
>> +
>> +        _draw : function() {
>> +            this.element.empty();
>> +            var canvas = document.createElement('canvas');
>> +            //this.element.append($(canvas));  //I don't quite 
>> understand this line so trying the one below...
>> +            this.element.append(canvas);
>> +
>> +            var ctx = canvas.getContext('2d');
>> +            var radius = this.options.radius;
>> +
>> +            var shadowSize = 2;
>> +            var width = height = radius * 2;
>> +            $(canvas).attr('height', height);
>> +            $(canvas).attr('width', width);
>> +
>> +            $(canvas).css({
>> +                'boxShadow' : shadowSize + 'px ' + shadowSize + 'px 
>> ' + shadowSize + 'px #fff, -' + shadowSize + 'px -' + shadowSize + 
>> 'px ' + shadowSize + 'px #eaeaea',
>> +                borderRadius : radius + 'px'
>> +            });
>> +
>> +            ctx.clearRect(0, 0, width, height);
>> +            ctx.fillStyle = this.options.fillColor;
>> +            ctx.font = this.options.font;
>> +            ctx.textAlign = 'center';
>> +            var originPos = radius;
>> +            ctx.textBaseline = 'middle';
>> +            ctx.fillText(this.options.display, originPos, originPos);
>> +            ctx.strokeStyle = this.options.color;
>> +            ctx.lineWidth = this.options.lineWidth;
>> +            ctx.beginPath();
>> +            ctx.arc(originPos, originPos, radius, -.5 * Math.PI, 
>> (this.options.percentage / 50 - .5) * Math.PI);
>> +            ctx.stroke();
>> +        },
>> +
>> +        destroy : function() {
>> +            this.element.empty();
>> +            $.Widget.prototype.destroy.call(this);
>> +        }
>> +    });
>> +}(jQuery));
>> +
>> +kimchi.circleGauge = function(selector) {
>> +    $(selector).circleGauge();
>> +};
>> diff --git a/plugins/kimchi/ui/js/src/kimchi.host.js 
>> b/plugins/kimchi/ui/js/src/kimchi.host.js
>> deleted file mode 100644
>> index ab02333..0000000
>> --- a/plugins/kimchi/ui/js/src/kimchi.host.js
>> +++ /dev/null
>> @@ -1,858 +0,0 @@
>> -/*
>> - * Project Kimchi
>> - *
>> - * Copyright IBM, Corp. 2013-2014
>> - *
>> - * Licensed 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.
>> - */
>> -kimchi.host={};
>> -
>> -kimchi.host_main = function() {
>> -    var expand = function(header, toExpand) {
>> -        var controlledNode = $(header).attr('aria-controls');
>> -        $('#' + controlledNode)[toExpand ? 'removeClass' : 
>> 'addClass']('hidden');
>> -        $(header).attr('aria-expanded', toExpand ? 'true' : 'false');
>> -    };
>> -
>> -    var repositoriesGrid = null;
>> -    var initRepositoriesGrid = function(repo_type) {
>> -        var gridFields=[];
>> -        if (repo_type == "yum") {
>> -                gridFields=[{
>> -                    name: 'repo_id',
>> -                    label: i18n['KCHREPO6004M'],
>> -                    'class': 'repository-id'
>> -                }, {
>> -                    name: 'config[repo_name]',
>> -                    label: i18n['KCHREPO6005M'],
>> -                    'class': 'repository-name'
>> -                }, {
>> -                    name: 'enabled',
>> -                    label: i18n['KCHREPO6009M'],
>> -                    'class': 'repository-enabled'
>> -                }];
>> -        }
>> -        else if (repo_type == "deb") {
>> -                gridFields=[{
>> -                    name: 'baseurl',
>> -                    label: i18n['KCHREPO6006M'],
>> -                    makeTitle: true,
>> -                    'class': 'repository-baseurl deb'
>> -                }, {
>> -                    name: 'enabled',
>> -                    label: i18n['KCHREPO6009M'],
>> -                    'class': 'repository-enabled deb'
>> -                }, {
>> -                    name: 'config[dist]',
>> -                    label: "dist",
>> -                    'class': 'repository-gpgcheck deb'
>> -                }, {
>> -                    name: 'config[comps]',
>> -                    label: "comps",
>> -                    'class': 'repository-gpgcheck deb'
>> -                }];
>> -        }
>> -        else {
>> -            gridFields=[{
>> -                name: 'repo_id',
>> -                label: i18n['KCHREPO6004M'],
>> -                'class': 'repository-id'
>> -                }, {
>> -                    name: 'enabled',
>> -                    label: i18n['KCHREPO6009M'],
>> -                    'class': 'repository-enabled'
>> -                }, {
>> -                    name: 'baseurl',
>> -                    label: i18n['KCHREPO6006M'],
>> -                    makeTitle: true,
>> -                    'class': 'repository-baseurl'
>> -                }];
>> -        }
>> -        repositoriesGrid = new kimchi.widget.Grid({
>> -            container: 'repositories-grid-container',
>> -            id: 'repositories-grid',
>> -            title: i18n['KCHREPO6003M'],
>> -            toolbarButtons: [{
>> -                id: 'repositories-grid-add-button',
>> -                label: i18n['KCHREPO6012M'],
>> -                onClick: function(event) {
>> - wok.window.open({url:'plugins/kimchi/repository-add.html',
>> -                                    class: repo_type});
>> -                }
>> -            }, {
>> -                id: 'repositories-grid-enable-button',
>> -                label: i18n['KCHREPO6016M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var repository = repositoriesGrid.getSelected();
>> -                    if(!repository) {
>> -                        return;
>> -                    }
>> -                    var name = repository['repo_id'];
>> -                    var enable = !repository['enabled'];
>> -                    $(this).prop('disabled', true);
>> -                    kimchi.enableRepository(name, enable, function() {
>> - wok.topic('kimchi/repositoryUpdated').publish();
>> -                    });
>> -                }
>> -            }, {
>> -                id: 'repositories-grid-edit-button',
>> -                label: i18n['KCHREPO6013M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var repository = repositoriesGrid.getSelected();
>> -                    if(!repository) {
>> -                        return;
>> -                    }
>> -                    kimchi.selectedRepository = repository['repo_id'];
>> - wok.window.open({url:'plugins/kimchi/repository-edit.html',
>> -                                    class: repo_type});
>> -                }
>> -            }, {
>> -                id: 'repositories-grid-remove-button',
>> -                label: i18n['KCHREPO6014M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var repository = repositoriesGrid.getSelected();
>> -                    if(!repository) {
>> -                        return;
>> -                    }
>> -
>> -                    var settings = {
>> -                        title : i18n['KCHREPO6001M'],
>> -                        content : i18n['KCHREPO6002M'],
>> -                        confirm : i18n['KCHAPI6004M'],
>> -                        cancel : i18n['KCHAPI6003M']
>> -                    };
>> -
>> -                    wok.confirm(settings, function() {
>> -                        kimchi.deleteRepository(
>> -                            repository['repo_id'],
>> -                            function(result) {
>> - wok.topic('kimchi/repositoryDeleted').publish(result);
>> -                            }, function(error) {
>> -                            }
>> -                        );
>> -                    });
>> -                }
>> -            }],
>> -            onRowSelected: function(row) {
>> -                var repository = repositoriesGrid.getSelected();
>> -                if(!repository) {
>> -                    return;
>> -                }
>> - $('#repositories-grid-remove-button').prop('disabled', false);
>> - $('#repositories-grid-edit-button').prop('disabled', false);
>> -                var enabled = repository['enabled'];
>> -                $('#repositories-grid-enable-button')
>> -                    .text(i18n[enabled ? 'KCHREPO6017M' : 
>> 'KCHREPO6016M'])
>> -                    .prop('disabled', false);
>> -            },
>> -            frozenFields: [],
>> -            fields: gridFields,
>> -            data: listRepositories
>> -        });
>> -    };
>> -
>> -    var listRepositories = function(gridCallback) {
>> -        kimchi.listRepositories(function(repositories) {
>> -            if($.isFunction(gridCallback)) {
>> -                gridCallback(repositories);
>> -            }
>> -            else {
>> -                if(repositoriesGrid) {
>> -                    repositoriesGrid.setData(repositories);
>> -                }
>> -                else {
>> -                    initRepositoriesGrid();
>> -                    repositoriesGrid.setData(repositories);
>> -                }
>> -            }
>> -        },
>> -        function(error) {
>> -            var message = error && error['responseJSON'] && 
>> error['responseJSON']['reason'];
>> -
>> -            if($.isFunction(gridCallback)) {
>> -                gridCallback([]);
>> -            }
>> -            repositoriesGrid &&
>> -                repositoriesGrid.showMessage(message || 
>> i18n['KCHUPD6008M']);
>> -        });
>> -
>> -        $('#repositories-grid-remove-button').prop('disabled', true);
>> -        $('#repositories-grid-edit-button').prop('disabled', true);
>> -        $('#repositories-grid-enable-button').prop('disabled', true);
>> -    };
>> -
>> -    var softwareUpdatesGridID = 'software-updates-grid';
>> -    var softwareUpdatesGrid = null;
>> -    var progressAreaID = 'software-updates-progress-textarea';
>> -    var reloadProgressArea = function(result) {
>> -        var progressArea = $('#' + progressAreaID)[0];
>> -        $(progressArea).text(result['message']);
>> -        var scrollTop = $(progressArea).prop('scrollHeight');
>> -        $(progressArea).prop('scrollTop', scrollTop);
>> -    };
>> -
>> -    var initSoftwareUpdatesGrid = function(softwareUpdates) {
>> -        softwareUpdatesGrid = new kimchi.widget.Grid({
>> -            container: 'software-updates-grid-container',
>> -            id: softwareUpdatesGridID,
>> -            title: i18n['KCHUPD6001M'],
>> -            rowSelection: 'disabled',
>> -            toolbarButtons: [{
>> -                id: softwareUpdatesGridID + '-update-button',
>> -                label: i18n['KCHUPD6006M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var updateButton = $(this);
>> -                    var progressArea = $('#' + progressAreaID)[0];
>> - $('#software-updates-progress-container').removeClass('hidden');
>> -                    $(progressArea).text('');
>> -                    !wok.isElementInViewport(progressArea) &&
>> -                        progressArea.scrollIntoView();
>> - $(updateButton).text(i18n['KCHUPD6007M']).prop('disabled', true);
>> -
>> -                    kimchi.updateSoftware(function(result) {
>> -                        reloadProgressArea(result);
>> - $(updateButton).text(i18n['KCHUPD6006M']).prop('disabled', false);
>> - wok.topic('kimchi/softwareUpdated').publish({
>> -                            result: result
>> -                        });
>> -                    }, function(error) {
>> -                        var message = error && error['responseJSON'] 
>> && error['responseJSON']['reason'];
>> -                        wok.message.error(message || 
>> i18n['KCHUPD6009M']);
>> - $(updateButton).text(i18n['KCHUPD6006M']).prop('disabled', false);
>> -                    }, reloadProgressArea);
>> -                }
>> -            }],
>> -            frozenFields: [],
>> -            fields: [{
>> -                name: 'package_name',
>> -                label: i18n['KCHUPD6002M'],
>> -                'class': 'software-update-name'
>> -            }, {
>> -                name: 'version',
>> -                label: i18n['KCHUPD6003M'],
>> -                'class': 'software-update-version'
>> -            }, {
>> -                name: 'arch',
>> -                label: i18n['KCHUPD6004M'],
>> -                'class': 'software-update-arch'
>> -            }, {
>> -                name: 'repository',
>> -                label: i18n['KCHUPD6005M'],
>> -                'class': 'software-update-repos'
>> -            }],
>> -            data: listSoftwareUpdates
>> -        });
>> -    };
>> -
>> -    var listSoftwareUpdates = function(gridCallback) {
>> -        kimchi.listSoftwareUpdates(function(softwareUpdates) {
>> -            if($.isFunction(gridCallback)) {
>> -                gridCallback(softwareUpdates);
>> -            }
>> -            else {
>> -                if(softwareUpdatesGrid) {
>> - softwareUpdatesGrid.setData(softwareUpdates);
>> -                }
>> -                else {
>> -                    initSoftwareUpdatesGrid(softwareUpdates);
>> -                }
>> -            }
>> -
>> -            var updateButton = $('#' + softwareUpdatesGridID + 
>> '-update-button');
>> -            $(updateButton).prop('disabled', softwareUpdates.length 
>> === 0);
>> -        }, function(error) {
>> -            var message = error && error['responseJSON'] && 
>> error['responseJSON']['reason'];
>> -            if($.isFunction(gridCallback)) {
>> -                gridCallback([]);
>> -            }
>> -            softwareUpdatesGrid &&
>> -                softwareUpdatesGrid.showMessage(message || 
>> i18n['KCHUPD6008M']);
>> -        });
>> -    };
>> -
>> -    var reportGridID = 'available-reports-grid';
>> -    var reportGrid = null;
>> -    var enableReportButtons = function(toEnable) {
>> -        var buttonID = '#{grid}-{btn}-button';
>> -        $.each(['rename', 'remove', 'download'], function(i, n) {
>> -            $(wok.substitute(buttonID, {
>> -                grid: reportGridID,
>> -                btn: n
>> -            })).prop('disabled', !toEnable);
>> -        });
>> -    };
>> -    var initReportGrid = function(reports) {
>> -        reportGrid = new kimchi.widget.Grid({
>> -            container: 'available-reports-grid-container',
>> -            id: reportGridID,
>> -            title: i18n['KCHDR6002M'],
>> -            toolbarButtons: [{
>> -                id: reportGridID + '-generate-button',
>> -                label: i18n['KCHDR6006M'],
>> -                onClick: function(event) {
>> - wok.window.open('plugins/kimchi/report-add.html');
>> -                }
>> -            }, {
>> -                id: reportGridID + '-rename-button',
>> -                label: i18n['KCHDR6008M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var report = reportGrid.getSelected();
>> -                    if(!report) {
>> -                        return;
>> -                    }
>> -
>> -                    kimchi.selectedReport = report['name'];
>> - wok.window.open('plugins/kimchi/report-rename.html');
>> -                }
>> -            }, {
>> -                id: reportGridID + '-remove-button',
>> -                label: i18n['KCHDR6009M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var report = reportGrid.getSelected();
>> -                    if(!report) {
>> -                        return;
>> -                    }
>> -
>> -                    var settings = {
>> -                        title : i18n['KCHAPI6004M'],
>> -                        content : i18n['KCHDR6001M'],
>> -                        confirm : i18n['KCHAPI6002M'],
>> -                        cancel : i18n['KCHAPI6003M']
>> -                    };
>> -
>> -                    wok.confirm(settings, function() {
>> -                        kimchi.deleteReport({
>> -                            name: report['name']
>> -                        }, function(result) {
>> -                            listDebugReports();
>> -                        }, function(error) {
>> - wok.message.error(error.responseJSON.reason);
>> -                        });
>> -                    });
>> -                }
>> -            }, {
>> -                id: reportGridID + '-download-button',
>> -                label: i18n['KCHDR6010M'],
>> -                disabled: true,
>> -                onClick: function(event) {
>> -                    var report = reportGrid.getSelected();
>> -                    if(!report) {
>> -                        return;
>> -                    }
>> -
>> -                    kimchi.downloadReport({
>> -                        file: report['uri']
>> -                    });
>> -                }
>> -            }],
>> -            onRowSelected: function(row) {
>> -                var report = reportGrid.getSelected();
>> -                // Only enable report buttons if the selected line 
>> is not a
>> -                // pending report
>> -                if (report['time'] == i18n['KCHDR6007M']) {
>> -                    var gridElement = $('#'+ reportGridID);
>> -                    var row = $('tr:contains(' + report['name'] + 
>> ')', gridElement);
>> -                    enableReportButtons(false);
>> -                    row.attr('class', '');
>> -                }
>> -                else {
>> -                    enableReportButtons(true);
>> -                }
>> -            },
>> -            frozenFields: [],
>> -            fields: [{
>> -                name: 'name',
>> -                label: i18n['KCHDR6003M'],
>> -                'class': 'debug-report-name'
>> -            }, {
>> -                name: 'time',
>> -                label: i18n['KCHDR6005M'],
>> -                'class': 'debug-report-time'
>> -            }],
>> -            data: reports
>> -        });
>> -    };
>> -
>> -    var getPendingReports = function() {
>> -        var reports = []
>> -        var filter = 'status=running&target_uri=' + 
>> encodeURIComponent('^/plugins/kimchi/debugreports/*')
>> -
>> -        kimchi.getTasksByFilter(filter, function(tasks) {
>> -            for(var i = 0; i < tasks.length; i++) {
>> -                reportName = 
>> tasks[i].target_uri.replace(/^\/plugins\/kimchi\/debugreports\//, '') 
>> || i18n['KCHDR6012M'];
>> -                reports.push({'name': reportName, 'time': 
>> i18n['KCHDR6007M']})
>> -
>> -                if(kimchi.trackingTasks.indexOf(tasks[i].id) >= 0) {
>> -                    continue;
>> -                }
>> -
>> -                kimchi.trackTask(tasks[i].id, function(result) {
>> - wok.topic('kimchi/debugReportAdded').publish();
>> -                }, function(result) {
>> -                    // Error message from Async Task status
>> -                    if (result['message']) {
>> -                        var errText = result['message'];
>> -                    }
>> -                    // Error message from standard kimchi exception
>> -                    else {
>> -                        var errText = result['responseJSON']['reason'];
>> -                    }
>> -                    result && wok.message.error(errText);
>> - wok.topic('kimchi/debugReportAdded').publish();
>> -                }, null);
>> -            }
>> -        }, null, true);
>> -
>> -        return reports;
>> -    };
>> -
>> -    var listDebugReports = function() {
>> -        kimchi.listReports(function(reports) {
>> -            pendingReports = getPendingReports();
>> -            allReports = pendingReports.concat(reports);
>> -            $('#debug-report-section').removeClass('hidden');
>> -
>> -            // Row selection will be cleared so disable buttons here
>> -            enableReportButtons(false);
>> -
>> -            if(reportGrid) {
>> -                reportGrid.setData(allReports);
>> -            }
>> -            else {
>> -                initReportGrid(allReports);
>> -            }
>> -
>> -            // Set id-debug-img to pending reports
>> -            // It will display a loading icon
>> -            var gridElement = $('#' + reportGridID);
>> -                $.each($('td:contains(' + i18n['KCHDR6007M']  + ')', 
>> gridElement), function(index, row) {
>> -                $(row).parent().addClass('no-hover');
>> -                $(row).attr('id', 'id-debug-img');
>> -            });
>> -        }, function(error) {
>> -            if(error['status'] == 403) {
>> -                $('#debug-report-section').addClass('hidden');
>> -                return;
>> -            }
>> -            $('#debug-report-section').removeClass('hidden');
>> -        });
>> -    };
>> -
>> -    var shutdownButtonID = '#host-button-shutdown';
>> -    var restartButtonID = '#host-button-restart';
>> -    var shutdownHost = function(params) {
>> -        var settings = {
>> -            title : i18n['KCHAPI6004M'],
>> -            content : i18n['KCHHOST6008M'],
>> -            confirm : i18n['KCHAPI6002M'],
>> -            cancel : i18n['KCHAPI6003M']
>> -        };
>> -
>> -        wok.confirm(settings, function() {
>> -            kimchi.shutdown(params);
>> -            $(shutdownButtonID).prop('disabled', true);
>> -            $(restartButtonID).prop('disabled', true);
>> -            // Check if there is any VM is running.
>> -            kimchi.listVMs(function(vms) {
>> -                for(var i = 0; i < vms.length; i++) {
>> -                    if(vms[i]['state'] === 'running') {
>> -                        wok.message.error.code('KCHHOST6001E');
>> -                        $(shutdownButtonID).prop('disabled', false);
>> -                        $(restartButtonID).prop('disabled', false);
>> -                        return;
>> -                    }
>> -                }
>> -
>> -            });
>> -        }, function() {
>> -        });
>> -    };
>> -
>> -    var initPage = function() {
>> -        $('#host-info-container .section-header').each(function(i, 
>> header) {
>> -            $('<span class="arrow"></span>').prependTo(header);
>> -            var toExpand = $(header).attr('aria-expanded') !== 'false';
>> -            expand(header, toExpand);
>> -        });
>> -
>> -        $('#host-info-container').on('click', '.section-header', 
>> function(event) {
>> -            var toExpand = $(this).attr('aria-expanded') === 'false';
>> -            expand(this, toExpand);
>> -        });
>> -
>> -        $('#host-button-shutdown').on('click', function(event) {
>> -            shutdownHost(null);
>> -        });
>> -
>> -        $('#host-button-restart').on('click', function(event) {
>> -            shutdownHost({
>> -                reboot: true
>> -            });
>> -        });
>> -
>> -        var setupUI = function() {
>> -            if (kimchi.capabilities == undefined) {
>> -                setTimeout(setupUI, 2000);
>> -                return;
>> -            }
>> -
>> -            if((kimchi.capabilities['repo_mngt_tool']) && 
>> (kimchi.capabilities['repo_mngt_tool']!="None")) {
>> - initRepositoriesGrid(kimchi.capabilities['repo_mngt_tool']);
>> - $('#repositories-section').switchClass('hidden', 
>> kimchi.capabilities['repo_mngt_tool']);
>> -                wok.topic('kimchi/repositoryAdded')
>> -                    .subscribe(listRepositories);
>> -                wok.topic('kimchi/repositoryUpdated')
>> -                    .subscribe(listRepositories);
>> -                wok.topic('kimchi/repositoryDeleted')
>> -                    .subscribe(listRepositories);
>> -            }
>> -
>> -            if(kimchi.capabilities['update_tool']) {
>> - $('#software-update-section').removeClass('hidden');
>> -                initSoftwareUpdatesGrid();
>> -                wok.topic('kimchi/softwareUpdated')
>> -                    .subscribe(listSoftwareUpdates);
>> - $('#software-updates-progress-container').accordion({
>> -                    collapsible: true
>> -                });
>> -            }
>> -
>> -            if(kimchi.capabilities['system_report_tool']) {
>> -                listDebugReports();
>> -                wok.topic('kimchi/debugReportAdded')
>> -                    .subscribe(listDebugReports);
>> -                wok.topic('kimchi/debugReportRenamed')
>> -                    .subscribe(listDebugReports);
>> -            }
>> -        };
>> -        setupUI();
>> -    };
>> -
>> -    kimchi.getHost(function(data) {
>> -        var htmlTmpl = $('#host-tmpl').html();
>> -        data['logo'] = data['logo'] || '';
>> -        data['memory'] = wok.formatMeasurement(data['memory'], {
>> -            fixed: 2
>> -        });
>> -        var templated = wok.substitute(htmlTmpl, data);
>> -        $('#host-content-container').html(templated);
>> -
>> -        initPage();
>> -        initTracker();
>> -    });
>> -
>> -    var StatsMgr = function() {
>> -        var statsArray = {
>> -            cpu: {
>> -                u: {
>> -                    type: 'percent',
>> -                    legend: i18n['KCHHOST6002M'],
>> -                    points: []
>> -                }
>> -            },
>> -            memory: {
>> -                u: {
>> -                    type: 'value',
>> -                    base: 2,
>> -                    fixed: 2,
>> -                    legend: i18n['KCHHOST6003M'],
>> -                    points: []
>> -                }
>> -            },
>> -            diskIO: {
>> -                r: {
>> -                    type: 'value',
>> -                    base: 2,
>> -                    fixed: 2,
>> -                    unit: 'B/s',
>> -                    legend: i18n['KCHHOST6004M'],
>> -                    points: []
>> -                },
>> -                w: {
>> -                    type: 'value',
>> -                    base: 2,
>> -                    fixed: 2,
>> -                    unit: 'B/s',
>> -                    legend: i18n['KCHHOST6005M'],
>> -                    'class': 'disk-write',
>> -                    points: []
>> -                }
>> -            },
>> -            networkIO: {
>> -                r: {
>> -                    type: 'value',
>> -                    base: 2,
>> -                    fixed: 2,
>> -                    unit: 'B/s',
>> -                    legend: i18n['KCHHOST6006M'],
>> -                    points: []
>> -                },
>> -                s: {
>> -                    type: 'value',
>> -                    base: 2,
>> -                    fixed: 2,
>> -                    unit: 'B/s',
>> -                    legend: i18n['KCHHOST6007M'],
>> -                    'class': 'network-sent',
>> -                    points: []
>> -                }
>> -            }
>> -        };
>> -        var SIZE = 20;
>> -        var cursor = SIZE;
>> -
>> -        var add = function(stats) {
>> -            for(var key in stats) {
>> -                var item = stats[key];
>> -                for(var metrics in item) {
>> -                    var value = item[metrics]['v'];
>> -                    var max = item[metrics]['max'];
>> -                    var unifiedMetrics = statsArray[key][metrics];
>> -                    var ps = unifiedMetrics['points'];
>> -                    if(!Array.isArray(value)){
>> -                        ps.push(value);
>> -                        if(ps.length > SIZE + 1) {
>> -                            ps.shift();
>> -                        }
>> -                    }
>> -                    else{
>> -                        ps=ps.concat(value);
>> -                        ps.splice(0, ps.length-SIZE-1);
>> -                        unifiedMetrics['points']=ps;
>> -                    }
>> -                    if(max !== undefined) {
>> -                        unifiedMetrics['max'] = max;
>> -                    }
>> -                    else {
>> -                        if(unifiedMetrics['type'] !== 'value') {
>> -                            continue;
>> -                        }
>> -                        max = -Infinity;
>> -                        $.each(ps, function(i, value) {
>> -                            if(value > max) {
>> -                                max = value;
>> -                            }
>> -                        });
>> -                        if(max === 0) {
>> -                            ++max;
>> -                        }
>> -                        max *= 1.1;
>> -                        unifiedMetrics['max'] = max;
>> -                    }
>> -                }
>> -            }
>> -            cursor++;
>> -        };
>> -
>> -        var get = function(which) {
>> -            var stats = statsArray[which];
>> -            var lines = [];
>> -            for(var k in stats) {
>> -                var obj = stats[k];
>> -                var line = {
>> -                    type: obj['type'],
>> -                    base: obj['base'],
>> -                    unit: obj['unit'],
>> -                    fixed: obj['fixed'],
>> -                    legend: obj['legend']
>> -                };
>> -                if(obj['max']) {
>> -                    line['max'] = obj['max'];
>> -                }
>> -                if(obj['class']) {
>> -                    line['class'] = obj['class'];
>> -                }
>> -                var ps = obj['points'];
>> -                var numStats = ps.length;
>> -                var unifiedPoints = [];
>> -                $.each(ps, function(i, value) {
>> -                    unifiedPoints.push({
>> -                        x: cursor - numStats + i,
>> -                        y: value
>> -                    });
>> -                });
>> -                line['points'] = unifiedPoints;
>> -                lines.push(line);
>> -            }
>> -            return lines;
>> -        };
>> -
>> -        return {
>> -            add: add,
>> -            get: get
>> -        };
>> -    };
>> -
>> -    var Tracker = function(charts) {
>> -      var charts = charts;
>> -      var timer = null;
>> -      var statsPool = new StatsMgr();
>> -      var setCharts = function(newCharts) {
>> -          charts = newCharts;
>> -          for(var key in charts) {
>> -              var chart = charts[key];
>> -              chart.updateUI(statsPool.get(key));
>> -          }
>> -      };
>> -
>> -      var self = this;
>> -
>> -      var UnifyStats = function(stats) {
>> -          var result= {
>> -              cpu: {
>> -                  u: {
>> -                      v: stats['cpu_utilization']
>> -                  }
>> -              },
>> -              memory: {
>> -                  u: {
>> -                  }
>> -              },
>> -              diskIO: {
>> -                  r: {
>> -                      v: stats['disk_read_rate']
>> -                  },
>> -                  w: {
>> -                      v: stats['disk_write_rate']
>> -                  }
>> -              },
>> -              networkIO: {
>> -                  r: {
>> -                      v: stats['net_recv_rate']
>> -                  },
>> -                  s: {
>> -                      v: stats['net_sent_rate']
>> -                  }
>> -              }
>> -          };
>> -          if(Array.isArray(stats['memory'])){
>> -              result.memory.u['v']=[];
>> -              result.memory.u['max']=-Infinity;
>> -              for(var i=0;i<stats['memory'].length;i++){
>> - result.memory.u['v'].push(stats['memory'][i]['avail']);
>> - 
>> result.memory.u['max']=Math.max(result.memory.u['max'],stats['memory'][i]['total']);
>> -              }
>> -          }
>> -          else {
>> -              result.memory.u['v']=stats['memory']['avail'],
>> -              result.memory.u['max']=stats['memory']['total']
>> -          }
>> -          return(result);
>> -      };
>> -
>> -
>> -      var statsCallback = function(stats) {
>> -              var unifiedStats = UnifyStats(stats);
>> -              statsPool.add(unifiedStats);
>> -              for(var key in charts) {
>> -                  var chart = charts[key];
>> -                  chart.updateUI(statsPool.get(key));
>> -              }
>> -              timer = setTimeout(function() {
>> -                  continueTrack();
>> -              }, 1000);
>> -          };
>> -
>> -      var track = function() {
>> -          kimchi.getHostStatsHistory(statsCallback,
>> -            function() {
>> -                continueTrack();
>> -            });
>> -      };
>> -
>> -      var continueTrack = function() {
>> -          kimchi.getHostStats(statsCallback,
>> -            function() {
>> -                continueTrack();
>> -            });
>> -      };
>> -
>> -      var destroy = function() {
>> -          timer && clearTimeout(timer);
>> -          timer = null;
>> -      };
>> -
>> -      return {
>> -        setCharts: setCharts,
>> -        start: track,
>> -        stop: destroy
>> -      };
>> -    };
>> -
>> -    var initTracker = function() {
>> -        // TODO: Extend tabs with onUnload event to unregister timers.
>> -        if(kimchi.hostTimer) {
>> -            kimchi.hostTimer.stop();
>> -            delete kimchi.hostTimer;
>> -        }
>> -
>> -        var trackedCharts = {
>> -            cpu: new kimchi.widget.LineChart({
>> -                id: 'chart-cpu',
>> -                node: 'container-chart-cpu',
>> -                type: 'percent'
>> -            }),
>> -            memory: new kimchi.widget.LineChart({
>> -                id: 'chart-memory',
>> -                node: 'container-chart-memory',
>> -                type: 'value'
>> -            }),
>> -            diskIO: new kimchi.widget.LineChart({
>> -                id: 'chart-disk-io',
>> -                node: 'container-chart-disk-io',
>> -                type: 'value'
>> -            }),
>> -            networkIO: new kimchi.widget.LineChart({
>> -                id: 'chart-network-io',
>> -                node: 'container-chart-network-io',
>> -                type: 'value'
>> -            })
>> -        };
>> -
>> -        if(kimchi.hostTimer) {
>> -            kimchi.hostTimer.setCharts(trackedCharts);
>> -        }
>> -        else {
>> -            kimchi.hostTimer = new Tracker(trackedCharts);
>> -            kimchi.hostTimer.start();
>> -        }
>> -    };
>> -
>> -    $('#host-root-container').on('remove', function() {
>> -        if(kimchi.hostTimer) {
>> -            kimchi.hostTimer.stop();
>> -            delete kimchi.hostTimer;
>> -            }
>> -
>> -        repositoriesGrid && repositoriesGrid.destroy();
>> -        wok.topic('kimchi/repositoryAdded')
>> -            .unsubscribe(listRepositories);
>> -        wok.topic('kimchi/repositoryUpdated')
>> -            .unsubscribe(listRepositories);
>> -        wok.topic('kimchi/repositoryDeleted')
>> -            .unsubscribe(listRepositories);
>> -
>> -        softwareUpdatesGrid && softwareUpdatesGrid.destroy();
>> - wok.topic('kimchi/softwareUpdated').unsubscribe(listSoftwareUpdates);
>> -
>> -        reportGrid && reportGrid.destroy();
>> - wok.topic('kimchi/debugReportAdded').unsubscribe(listDebugReports);
>> - wok.topic('kimchi/debugReportRenamed').unsubscribe(listDebugReports);
>> -    });
>> -};
>> diff --git a/plugins/kimchi/ui/js/src/kimchi.report_add_main.js 
>> b/plugins/kimchi/ui/js/src/kimchi.report_add_main.js
>> deleted file mode 100644
>> index 5f098d3..0000000
>> --- a/plugins/kimchi/ui/js/src/kimchi.report_add_main.js
>> +++ /dev/null
>> @@ -1,72 +0,0 @@
>> -/*
>> - * Project Kimchi
>> - *
>> - * Copyright IBM, Corp. 2013-2014
>> - *
>> - * Licensed 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.
>> - */
>> -kimchi.report_add_main = function() {
>> -    var reportGridID = 'available-reports-grid';
>> -    var addReportForm = $('#form-report-add');
>> -    var submitButton = $('#button-report-add');
>> -    var nameTextbox = $('input[name="name"]', addReportForm);
>> -    nameTextbox.select();
>> -
>> -    var submitForm = function(event) {
>> -        if(submitButton.prop('disabled')) {
>> -            return false;
>> -        }
>> -        var reportName = nameTextbox.val();
>> -        var validator = RegExp("^[_A-Za-z0-9-]*$");
>> -        if (!validator.test(reportName)) {
>> -            wok.message.error.code('KCHDR6011M');
>> -            return false;
>> -        }
>> -        var formData = addReportForm.serializeObject();
>> -        var taskAccepted = false;
>> -        var onTaskAccepted = function() {
>> -            if(taskAccepted) {
>> -                return;
>> -            }
>> -            taskAccepted = true;
>> -            wok.window.close();
>> -            wok.topic('kimchi/debugReportAdded').publish();
>> -        };
>> -
>> -        kimchi.createReport(formData, function(result) {
>> -            onTaskAccepted();
>> -            wok.topic('kimchi/debugReportAdded').publish();
>> -        }, function(result) {
>> -            // Error message from Async Task status
>> -            if (result['message']) {
>> -                var errText = result['message'];
>> -            }
>> -            // Error message from standard kimchi exception
>> -            else {
>> -                var errText = result['responseJSON']['reason'];
>> -            }
>> -            result && wok.message.error(errText);
>> -
>> -            taskAccepted &&
>> -                $('.grid-body-view table tr:first-child',
>> -                    '#' + reportGridID).remove();
>> -            submitButton.prop('disabled', false);
>> -            nameTextbox.select();
>> -        }, onTaskAccepted);
>> -
>> -        event.preventDefault();
>> -    };
>> -
>> -    addReportForm.on('submit', submitForm);
>> -    submitButton.on('click', submitForm);
>> -};
>> diff --git a/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js 
>> b/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
>> deleted file mode 100644
>> index 1bdb8d9..0000000
>> --- a/plugins/kimchi/ui/js/src/kimchi.report_rename_main.js
>> +++ /dev/null
>> @@ -1,66 +0,0 @@
>> -/*
>> - * Project Kimchi
>> - *
>> - * Copyright IBM, Corp. 2014
>> - *
>> - * Licensed 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.
>> - */
>> -kimchi.report_rename_main = function() {
>> -    var renameReportForm = $('#form-report-rename');
>> -    var submitButton = $('#button-report-rename');
>> -    var nameTextbox = $('input[name="name"]', renameReportForm);
>> -    var submitForm = function(event) {
>> -        if(submitButton.prop('disabled')) {
>> -            return false;
>> -        }
>> -        var reportName = nameTextbox.val();
>> -
>> -        // if the user hasn't changed the report's name,
>> -        // nothing should be done.
>> -        if (reportName == kimchi.selectedReport) {
>> -            wok.message.error.code('KCHDR6013M');
>> -            return false;
>> -        }
>> -
>> -        var validator = RegExp("^[A-Za-z0-9-]*$");
>> -        if (!validator.test(reportName)) {
>> -            wok.message.error.code('KCHDR6011M');
>> -            return false;
>> -        }
>> -        var formData = renameReportForm.serializeObject();
>> -        submitButton.prop('disabled', true);
>> -        nameTextbox.prop('disabled', true);
>> -        kimchi.renameReport(kimchi.selectedReport, formData, 
>> function(result) {
>> -            submitButton.prop('disabled', false);
>> -            nameTextbox.prop('disabled', false);
>> -            wok.window.close();
>> -            wok.topic('kimchi/debugReportRenamed').publish({
>> -                result: result
>> -            });
>> -        }, function(result) {
>> -            var errText = result &&
>> -                result['responseJSON'] &&
>> -                result['responseJSON']['reason'];
>> -            wok.message.error(errText);
>> -            submitButton.prop('disabled', false);
>> -            nameTextbox.prop('disabled', false).focus();
>> -        });
>> -
>> -        event.preventDefault();
>> -    };
>> -
>> -    renameReportForm.on('submit', submitForm);
>> -    submitButton.on('click', submitForm);
>> -
>> -    nameTextbox.val(kimchi.selectedReport).select();
>> -};
>> diff --git a/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js 
>> b/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
>> deleted file mode 100644
>> index 656306b..0000000
>> --- a/plugins/kimchi/ui/js/src/kimchi.repository_add_main.js
>> +++ /dev/null
>> @@ -1,96 +0,0 @@
>> -/*
>> - * Project Kimchi
>> - *
>> - * Copyright IBM, Corp. 2014
>> - *
>> - * Licensed 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.
>> - */
>> -kimchi.repository_add_main = function() {
>> -
>> -    var addForm = $('#form-repository-add');
>> -    var addButton = $('#button-repository-add');
>> -
>> -    var validateField = function(event) {
>> -        var valid=($(this).val()!=='');
>> -        $(addButton).prop('disabled', !valid);
>> -        return(valid);
>> -    };
>> -
>> -    var validateForm = function(event) {
>> -        var valid=false;
>> -        addForm.find('input.required').each( function() {
>> -            valid=($(this).val()!=='');
>> -            return(!valid);
>> -        });
>> -        return(valid);
>> -    }
>> -
>> -    addForm.find('input.required').on('input propertychange', 
>> validateField);
>> -
>> -    var weedObject = function(obj) {
>> -        for (var key in obj) {
>> -            if (obj.hasOwnProperty(key)) {
>> -                if((typeof(obj[key])==="object") && 
>> !Array.isArray(obj[key])) {
>> -                    weedObject(obj[key]);
>> -                }
>> -                else if(obj[key] == '') {
>> -                    delete obj[key];
>> -                }
>> -            }
>> -        }
>> -    }
>> -
>> -    var addRepository = function(event) {
>> -        var valid = validateForm();
>> -        if(!valid) {
>> -            return false;
>> -        }
>> -
>> -        var formData = $(addForm).serializeObject();
>> -
>> -        if (formData && formData.isMirror!=undefined) {
>> - formData.isMirror=(String(formData.isMirror).toLowerCase() === 
>> 'true');
>> -        }
>> -        if(formData.isMirror) {
>> -            if(formData.config==undefined) {
>> -                formData.config=new Object();
>> -            }
>> -            formData.config.mirrorlist=formData.baseurl;
>> -            delete formData.baseurl;
>> -            delete formData.isMirror;
>> -        }
>> -        weedObject(formData);
>> -        if(formData.config && formData.config.comps) {
>> - formData.config.comps=formData.config.comps.split(/[,\s]/);
>> -            for(var i=0; i>formData.config.comps.length; i++) {
>> - formData.config.comps[i]=formData.config.comps[i].trim();
>> -            }
>> -            for (var j=formData.config.comps.indexOf(""); j!=-1; 
>> j=formData.config.comps.indexOf("")) {
>> -                formData.config.comps.splice(j, 1);
>> -            }
>> -        }
>> -
>> -        kimchi.createRepository(formData, function() {
>> -            wok.topic('kimchi/repositoryAdded').publish();
>> -            wok.window.close();
>> -        }, function(jqXHR, textStatus, errorThrown) {
>> -            var reason = jqXHR &&
>> -                jqXHR['responseJSON'] &&
>> -                jqXHR['responseJSON']['reason'];
>> -            wok.message.error(reason);
>> -        });
>> -        return false;
>> -    };
>> -
>> -    $(addForm).on('submit', addRepository);
>> -};
>> diff --git a/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js 
>> b/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
>> deleted file mode 100644
>> index 5bfc51e..0000000
>> --- a/plugins/kimchi/ui/js/src/kimchi.repository_edit_main.js
>> +++ /dev/null
>> @@ -1,74 +0,0 @@
>> -/*
>> - * Project Kimchi
>> - *
>> - * Copyright IBM, Corp. 2014
>> - *
>> - * Licensed 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.
>> - */
>> -kimchi.repository_edit_main = function() {
>> -
>> -    var editForm = $('#form-repository-edit');
>> -
>> -    var saveButton = $('#repository-edit-button-save');
>> -
>> -    if(kimchi.capabilities['repo_mngt_tool']=="yum") {
>> -        editForm.find('input.deb').prop('disabled', true);
>> -    }
>> -    else if(kimchi.capabilities['repo_mngt_tool']=="deb") {
>> -        editForm.find('input.yum').prop('disabled', true);
>> -    }
>> -
>> -    kimchi.retrieveRepository(kimchi.selectedRepository, 
>> function(repository) {
>> -        editForm.fillWithObject(repository);
>> -
>> -        $('input', editForm).on('input propertychange', 
>> function(event) {
>> -            if($(this).val() !== '') {
>> -                $(saveButton).prop('disabled', false);
>> -            }
>> -        });
>> -    });
>> -
>> -
>> -    var editRepository = function(event) {
>> -        var formData = $(editForm).serializeObject();
>> -
>> -        if (formData && formData.config) {
>> - 
>> formData.config.gpgcheck=(String(formData.config.gpgcheck).toLowerCase() 
>> === 'true');
>> -        }
>> -
>> -        if(formData.config && formData.config.comps) {
>> - formData.config.comps=formData.config.comps.split(/[,\s]/);
>> -            for(var i=0; i>formData.config.comps.length; i++) {
>> - formData.config.comps[i]=formData.config.comps[i].trim();
>> -            }
>> -            for (var j=formData.config.comps.indexOf(""); j!=-1; 
>> j=formData.config.comps.indexOf("")) {
>> -                formData.config.comps.splice(j, 1);
>> -            }
>> -        }
>> -
>> -        kimchi.updateRepository(kimchi.selectedRepository, formData, 
>> function() {
>> -            wok.topic('kimchi/repositoryUpdated').publish();
>> -            wok.window.close();
>> -        }, function(jqXHR, textStatus, errorThrown) {
>> -            var reason = jqXHR &&
>> -                jqXHR['responseJSON'] &&
>> -                jqXHR['responseJSON']['reason'];
>> -            wok.message.error(reason);
>> -        });
>> -
>> -        return false;
>> -    };
>> -
>> -    $(editForm).on('submit', editRepository);
>> -    $(saveButton).on('click', editRepository);
>> -};
>
>




More information about the Kimchi-devel mailing list