').append(loader);
+
+ // Configure xCAT tables
+ tab.add('configTablesTab', 'Tables', loader);
+
+ // Get list of tables and their descriptions
+ $.ajax( {
+ url : 'lib/cmd.php',
+ dataType : 'json',
+ data : {
+ cmd : 'tabdump',
+ tgt : '',
+ args : '-d',
+ msg : ''
+ },
+
+ success : loadTableNames
+ });
+}
+
+/**
+ * Load xCAT table names and their descriptions
+ *
+ * @param data
+ * Data returned from HTTP request
+ * @return Nothing
+ */
+function loadTableNames(data) {
+ // Get output
+ var tables = data.rsp;
+
+ // Remove loader
+ var tabId = 'configTablesTab';
+ $('#' + tabId).find('img').hide();
+
+ // Create a groups division
+ var tablesDIV = $('');
+ $('#' + tabId).append(tablesDIV);
+
+ // Create info bar
+ var infoBar = createInfoBar('Select a table to edit');
+ tablesDIV.append(infoBar);
+
+ // Create a list for the tables
+ var list = $('
');
+ // Loop through each table
+ for ( var i = 0; i < tables.length; i++) {
+ // Create a link for each table
+ var args = tables[i].split(':');
+ var link = $('' + args[0] + '');
+
+ // Open table on click
+ link.bind('click', function(e) {
+ // Get table ID that was clicked
+ var id = (e.target) ? e.target.id : e.srcElement.id;
+
+ // Create loader
+ var loader = createLoader();
+ loader = $('
').append(loader);
+
+ // Add a new tab for this table
+ var configTab = getConfigTab();
+ if (!$('#' + id + 'Tab').length) {
+ configTab.add(id + 'Tab', id, loader);
+
+ // Get contents of selected table
+ $.ajax( {
+ url : 'lib/cmd.php',
+ dataType : 'json',
+ data : {
+ cmd : 'tabdump',
+ tgt : '',
+ args : id,
+ msg : id
+ },
+
+ success : loadTable
+ });
+ }
+
+ // Select new tab
+ configTab.select(id + 'Tab');
+ });
+
+ var item = $('');
+ item.append(link);
+
+ // Append the table description
+ item.append(': ' + args[1]);
+
+ // Append item to list
+ list.append(item);
+ }
+
+ tablesDIV.append(list);
+}
+
+/**
+ * Load a given table
+ *
+ * @param data
+ * Data returned from HTTP request
+ * @return Nothing
+ */
+function loadTable(data) {
+ // Get response
+ var rsp = data.rsp;
+ // Get table ID
+ var id = data.msg;
+
+ // Remove loader
+ var tabId = id + 'Tab';
+ $('#' + tabId).find('img').remove();
+
+ // Create info bar
+ var infoBar = createInfoBar('Click on a cell to edit. Click outside the table to write to the cell. Once you are satisfied with how the table looks, click on Save.');
+ $('#' + tabId).append(infoBar);
+
+ // Create action bar
+ var actionBar = $('');
+ $('#' + tabId).append(actionBar);
+
+ // Get table headers
+ var args = rsp[0].replace('#', '');
+ var headers = args.split(',');
+
+ // Create container for original table contents
+ var origCont = new Array(); // Original table content
+ origCont[0] = rsp[0].split(','); // Headers
+
+ // Create container for new table contents
+ var newCont = new Object();
+ var tmp = new Object();
+ tmp[0] = '#' + headers[0]; // Put a # in front of the header
+ for ( var i = 1; i < headers.length; i++) {
+ tmp[i] = headers[i];
+ }
+ newCont[0] = tmp;
+
+ // Create a new datatable
+ var tableId = id + 'DataTable';
+ var table = new DataTable(tableId);
+
+ // Add column for the remove button
+ headers.unshift('');
+ table.init(headers);
+ headers.shift();
+
+ // Append datatable to tab
+ $('#' + tabId).append(table.object());
+
+ // Data table
+ var dTable;
+
+ // Add table rows
+ // Start with the 2nd row (1st row is the headers)
+ for ( var i = 1; i < rsp.length; i++) {
+ // Split into columns
+ var cols = rsp[i].split(',');
+
+ // Go through each column
+ for ( var j = 0; j < cols.length; j++) {
+
+ // If the column is not complete
+ if (cols[j].count('"') == 1) {
+ while (cols[j].count('"') != 2) {
+ // Merge this column with the adjacent one
+ cols[j] = cols[j] + "," + cols[j + 1];
+
+ // Remove merged row
+ cols.splice(j + 1, 1);
+ }
+ }
+
+ // Replace quote
+ cols[j] = cols[j].replace(new RegExp('"', 'g'), '');
+ }
+
+ // Add remove button
+ cols
+ .unshift('');
+
+ // Add row
+ table.add(cols);
+
+ // Save original table content
+ origCont[i] = cols;
+ }
+
+ /**
+ * Enable editable columns
+ */
+ // Do not make 1st column editable
+ $('#' + tableId + ' td:not(td:nth-child(1))').editable(
+ function(value, settings) {
+ // Get column index
+ var colPos = this.cellIndex;
+ // Get row index
+ var rowPos = dTable.fnGetPosition(this.parentNode);
+
+ // Update datatable
+ dTable.fnUpdate(value, rowPos, colPos);
+
+ return (value);
+ }, {
+ onblur : 'submit', // Clicking outside editable area submits
+ // changes
+ type : 'textarea',
+ height : '30px' // The height of the text area
+ });
+
+ // Turn table into datatable
+ dTable = $('#' + id + 'DataTable').dataTable();
+ setConfigDatatable(id + 'DataTable', dTable);
+
+ // Create add row button
+ var addBar = $('');
+ $('#' + tabId).append(addBar);
+ var addRowBtn = createButton('Add row');
+ addBar.append(addRowBtn);
+
+ // Create save and undo buttons
+ var saveBtn = createButton('Save');
+ var undoBtn = createButton('Undo');
+ actionBar.append(saveBtn);
+ actionBar.append(undoBtn);
+
+ /**
+ * Add row
+ */
+ addRowBtn
+ .bind(
+ 'click',
+ function(event) {
+ // Create an empty row
+ var row = new Array();
+
+ /**
+ * Remove button
+ */
+ row
+ .push('');
+ for ( var i = 0; i < headers.length; i++) {
+ row.push('');
+ }
+
+ // Get tab ID
+ var tabId = $(this).parent().parent().attr('id');
+ // Get table name
+ var tableName = tabId.replace('Tab', '');
+ // Get table ID
+ var tableId = tableName + 'DataTable';
+
+ // Get datatable
+ var dTable = getConfigDatatable(tableId);
+ // Add the row to the data table
+ dTable.fnAddData(row);
+
+ // Enable editable columns (again)
+ // Do not make 1st column editable
+ $('#' + tabId + ' td:not(td:nth-child(1))').editable(
+ function(value, settings) {
+ // Get column index
+ var colPos = this.cellIndex;
+ // Get row index
+ var rowPos = dTable.fnGetPosition(this.parentNode);
+
+ // Update datatable
+ dTable.fnUpdate(value, rowPos, colPos);
+
+ return (value);
+ }, {
+ onblur : 'submit', // Clicking outside editable area
+ // submits changes
+ type : 'textarea',
+ height : '30px' // The height of the text area
+ });
+ });
+
+ /**
+ * Save changes
+ */
+ saveBtn.bind('click', function(event) {
+ // Get tab ID
+ var tabId = $(this).parent().parent().attr('id');
+ // Get table name
+ var tableName = tabId.replace('Tab', '');
+ // Get table ID
+ var tableId = tableName + 'DataTable';
+
+ // Get datatable
+ var dTable = getConfigDatatable(tableId);
+ // Get the nodes from the table
+ var dRows = dTable.fnGetNodes();
+
+ // Go through each row
+ for ( var i = 0; i < dRows.length; i++) {
+ // If there is row with values
+ if (dRows[i]) {
+ // Go through each column
+ // Ignore the 1st column because it is a button
+ var cols = dRows[i].childNodes;
+ var vals = new Object();
+ for ( var j = 1; j < cols.length; j++) {
+ var val = cols.item(j).firstChild.nodeValue;
+
+ // Insert quotes
+ if (val == 'Click to edit') {
+ vals[j - 1] = '';
+ } else {
+ vals[j - 1] = val;
+ }
+ }
+
+ // Save row
+ newCont[i + 1] = vals;
+ }
+ }
+
+ // Update datatable
+ setConfigDatatable(tableId, dTable);
+
+ // Update xCAT table
+ $.ajax( {
+ type : 'POST',
+ url : 'lib/tabRestore.php',
+ dataType : 'json',
+ data : {
+ table : tableName,
+ cont : newCont
+ },
+ success : function(data) {
+ alert('Changes saved');
+ }
+ });
+ });
+
+ /**
+ * Undo changes
+ */
+ undoBtn.bind('click', function(event) {
+ // Get tab ID
+ var tabId = $(this).parent().parent().attr('id');
+ // Get table name
+ var tableName = tabId.replace('Tab', '');
+ // Get table ID
+ var tableId = tableName + 'DataTable';
+
+ // Get datatable
+ var dTable = getConfigDatatable(tableId);
+ // Get the nodes from the table
+
+ // Clear entire datatable
+ dTable.fnClearTable();
+
+ // Add original content back into datatable
+ for ( var i = 1; i < origCont.length; i++) {
+ dTable.fnAddData(origCont[i], true);
+ }
+
+ // Enable editable columns (again)
+ // Do not make 1st column editable
+ $('#' + tableId + ' td:not(td:nth-child(1))').editable(
+ function(value, settings) {
+ // Get column index
+ var colPos = this.cellIndex;
+ // Get row index
+ var rowPos = dTable.fnGetPosition(this.parentNode);
+
+ // Update datatable
+ dTable.fnUpdate(value, rowPos, colPos);
+
+ return (value);
+ }, {
+ onblur : 'submit', // Clicking outside editable area submits
+ // changes
+ type : 'textarea',
+ height : '30px' // The height of the text area
+ });
+ });
+}
+
+/**
+ * Delete a row in the data table
+ *
+ * @param obj
+ * The object that was clicked
+ * @return Nothing
+ */
+function deleteRow(obj) {
+ // Get table ID
+ var tableId = $(obj).parent().parent().parent().parent().attr('id');
+
+ // Get datatable
+ var dTable = getConfigDatatable(tableId);
+
+ // Get all nodes within the datatable
+ var rows = dTable.fnGetNodes();
+ // Get target row
+ var tgtRow = $(obj).parent().parent().get(0);
+
+ // Find the target row in the datatable
+ for ( var i in rows) {
+ // If the row matches the target row
+ if (rows[i] == tgtRow) {
+ // Remove row
+ dTable.fnDeleteRow(i, null, true);
+ break;
+ }
+ }
+}
+
+/**
+ * Count the number of occurrences of a specific character in a string
+ *
+ * @param char
+ * Character to count
+ * @return The number of occurrences
+ */
+String.prototype.count = function(char) {
+ return (this.length - this.replace(new RegExp(char, "g"), '').length)
+ / char.length;
+};
\ No newline at end of file
diff --git a/xCAT-UI/js/hoverIntent.js b/xCAT-UI/js/hoverIntent.js
new file mode 100644
index 000000000..af7be848b
--- /dev/null
+++ b/xCAT-UI/js/hoverIntent.js
@@ -0,0 +1,117 @@
+(function($) {
+ /* hoverIntent by Brian Cherne */
+ $.fn.hoverIntent = function(f, g) {
+ // default configuration options
+ var cfg = {
+ sensitivity : 7,
+ interval : 100,
+ timeout : 0
+ };
+ // override configuration options with user supplied object
+ cfg = $.extend(cfg, g ? {
+ over : f,
+ out : g
+ } : f);
+
+ // instantiate variables
+ // cX, cY = current X and Y position of mouse, updated by mousemove
+ // event
+ // pX, pY = previous X and Y position of mouse, set by mouseover and
+ // polling interval
+ var cX, cY, pX, pY;
+
+ // A private function for getting mouse position
+ var track = function(ev) {
+ cX = ev.pageX;
+ cY = ev.pageY;
+ };
+
+ // A private function for comparing current and previous mouse position
+ var compare = function(ev, ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ // compare mouse positions to see if they've crossed the threshold
+ if ((Math.abs(pX - cX) + Math.abs(pY - cY)) < cfg.sensitivity) {
+ $(ob).unbind("mousemove", track);
+ // set hoverIntent state to true (so mouseOut can be called)
+ ob.hoverIntent_s = 1;
+ return cfg.over.apply(ob, [ ev ]);
+ } else {
+ // set previous coordinates for next time
+ pX = cX;
+ pY = cY;
+ // use self-calling timeout, guarantees intervals are spaced out
+ // properly (avoids JavaScript timer bugs)
+ ob.hoverIntent_t = setTimeout(function() {
+ compare(ev, ob);
+ }, cfg.interval);
+ }
+ };
+
+ // A private function for delaying the mouseOut function
+ var delay = function(ev, ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ ob.hoverIntent_s = 0;
+ return cfg.out.apply(ob, [ ev ]);
+ };
+
+ // A private function for handling mouse 'hovering'
+ var handleHover = function(e) {
+ // next three lines copied from jQuery.hover, ignore children
+ // onMouseOver/onMouseOut
+ var p = (e.type == "mouseover" ? e.fromElement : e.toElement)
+ || e.relatedTarget;
+ while (p && p != this) {
+ try {
+ p = p.parentNode;
+ } catch (e) {
+ p = this;
+ }
+ }
+ if (p == this) {
+ return false;
+ }
+
+ // copy objects to be passed into t (required for event object to be
+ // passed in IE)
+ var ev = jQuery.extend( {}, e);
+ var ob = this;
+
+ // cancel hoverIntent timer if it exists
+ if (ob.hoverIntent_t) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ }
+
+ // else e.type == "onmouseover"
+ if (e.type == "mouseover") {
+ // set "previous" X and Y position based on initial entry point
+ pX = ev.pageX;
+ pY = ev.pageY;
+ // update "current" X and Y position based on mousemove
+ $(ob).bind("mousemove", track);
+ // start polling interval (self-calling timeout) to compare
+ // mouse coordinates over time
+ if (ob.hoverIntent_s != 1) {
+ ob.hoverIntent_t = setTimeout(function() {
+ compare(ev, ob);
+ }, cfg.interval);
+ }
+
+ // else e.type == "onmouseout"
+ } else {
+ // unbind expensive mousemove event
+ $(ob).unbind("mousemove", track);
+ // if hoverIntent state is true, then call the mouseOut function
+ // after the specified delay
+ if (ob.hoverIntent_s == 1) {
+ ob.hoverIntent_t = setTimeout(function() {
+ delay(ev, ob);
+ }, cfg.timeout);
+ }
+ }
+ };
+
+ // bind the function to the two event listeners
+ return this.mouseover(handleHover).mouseout(handleHover);
+ };
+
+})(jQuery);
\ No newline at end of file
diff --git a/xCAT-UI/js/jquery-1.4.2.min.js b/xCAT-UI/js/jquery-1.4.2.min.js
new file mode 100644
index 000000000..7c2430802
--- /dev/null
+++ b/xCAT-UI/js/jquery-1.4.2.min.js
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b