/**
 * @file script.js
 * @author Tod Fitch
 * @brief Client Side JavaScript
 * @version $Id: script.js,v 1.1.1.1 2007/12/20 23:33:59 tfitch Exp $
 *
 * @par Description:
 * Includes data type definitions for objects implementing Web Interface
 */

/**
 *	For popup help boxes, specify the offset from the cursor for the window
 *	to appear.
 */
var xOffset = -200;
var yOffset = +10;
var displayDivTop = 150;
var displayDivLeft = 200;


// initialize hacks whenever the page loads
window.onload = initializeHacks;
// setup an event handler to hide popups for generic clicks on the document
document.onclick = hideCurrentPopup;

/**
 *	@brief Get element by ID
 *
 *	@par Description
 *	Based on several published and open source examples, return
 *	object for several versions of browsers.
 *
 *	@param[in] objectId ID of element to find
 *	@return object if successful, false otherwise
 */
function getObject(objectId)
{
	// W3C compliant
	if (document.getElementById &&
		document.getElementById(objectId))
	{
		return document.getElementById(objectId);
	}
	// Old MS IE
	else if (document.all && document.all(objectId))
	{
		return document.all(objectId);
	}
	// Old Netscape
	else if (document.layers && document.layers[objectId])
	{
		return document.layers[objectId];
	}
	else
	{
	   return false;
	}
}

/**
 *	@brief Get element style by object ID
 *
 *	@par Description
 *	Based on several published and open source examples, return
 *	style object for element for several versions of browsers.
 *
 *	@param[in] objectId ID of element to get style for
 *	@return object if successful, false otherwise
 */
function getStyleObject(objectId)
{
	// W3C compliant
	if (document.getElementById &&
		document.getElementById(objectId))
	{
		return document.getElementById(objectId).style;
	}
	// Old MS IE
	else if (document.all && document.all(objectId))
	{
		return document.all(objectId).style;
	}
	// Old Netscape
	else if (document.layers && document.layers[objectId])
	{
		return document.layers[objectId];
	}
	else
	{
	   return false;
	}
}

function listPropertyNames(obj)
{
	var names = "";
	for (var i in obj) names += i + ", ";
	alert(names);
}
/**
 *	Set a value into an object (typically an input field).
 *
 *	If this is the first time a set is performed then save
 *	the original value so that a resetValue() call can
 *	return it to the original value.
 */
function setValue(objectId, newValue)
{
	var obj = getObject(objectId);
	if (obj) {
		obj.value = newValue;
	}
}

/**
 *	Reset the value attribute on an object to that
 *	originally detected during the first setValue()
 *	call.
 */
function resetValue(objectId)
{
	var obj = getObject(objectId);
	if (obj) {
			obj.value = obj.defaultValue;
	}
}

/**
 *	@brief Change visibility attribute of an object
 *
 *	@param[in]	objectId		Object to work magic on
 *	@param[in]	newVisibility	New setting for visibility
 *	@return true if successful.
 */
function changeVisibility(objectId, newVisibility)
{
	var styleObject = getStyleObject(objectId);
	if(styleObject)
	{
		styleObject.visibility = newVisibility;
		return true;
	}
	else
	{
		return false;
	}
}

/**
 *	@brief Change enable attribute of an object
 *
 *	@param[in]	objectId		Object to work magic on
 *	@param[in]	newDisable		New setting for disable control
 *	@return true if successful.
 */
function changeDisable(objectId, newDisable)
{
	var ctlObject = getObject(objectId);
	if(ctlObject) {
		ctlObject.disabled = newDisable;
		return true;
	} else {
		return false;
	}
}

/**
 *	Move an object to a new location in the browser window
 *
 *	@param[in]	objectId	Unique identifier for object
 *	@param[in]	newX		New horizontal location
 *	@param[in]	newY		New vertical location
 *	@return true if successful otherwise false
 */
function moveObject(objectId, newX, newY) {
	var styleObject = getStyleObject(objectId);
	if(styleObject)
	{
		styleObject.left = newX;
		styleObject.top = newY;
		return true;
	}
	else
	{
		return false;
	}
} // moveObject

function showPopup(targetObjectId, eventObj)
{
	if(eventObj)
	{
		hideCurrentPopup();
		eventObj.cancelBubble = true;

		var newX = (eventObj.pageX)?
			(eventObj.pageX + xOffset):
			(eventObj.x + xOffset + ((document.body.scrollLeft)?document.body.scrollLeft:0));
		var newY = (eventObj.pageY)?
			(eventObj.pageY + yOffset):
			(eventObj.y + yOffset + ((document.body.scrollTop)?document.body.scrollTop:0));
		newX = newX - displayDivLeft;	  // Adjust for location of left edge of display DIV
		if (newX < 0) {
			newX = 0;
		}
		newY = newY - displayDivTop;	  // Adjust for location of top edge of display DIV
		moveObject(targetObjectId, newX, newY);
		// and make it visible
		if( changeVisibility(targetObjectId, 'visible') )
		{
			window.currentlyVisiblePopup = targetObjectId;
			return true;
		}
		else
		{
			return false;
		}
	}
	else
	{
		return false;
	}
}

function hideCurrentPopup()
{
	// note: we've stored the currently-visible popup on the global object window.currentlyVisiblePopup
	if(window.currentlyVisiblePopup)
	{
		changeVisibility(window.currentlyVisiblePopup, 'hidden');
		window.currentlyVisiblePopup = false;
	}
}

function initializeHacks() {
	// this ugly little hack resizes a blank div to make sure you can click
	// anywhere in the window for Mac MSIE 5
	if ((navigator.appVersion.indexOf('MSIE 5') != -1)
	&& (navigator.platform.indexOf('Mac') != -1)
	&& getStyleObject('blankDiv')) {
	window.onresize = explorerMacResizeFix;
	}
	resizeBlankDiv();
	// this next function creates a placeholder object for older browsers
	createFakeEventObj();
}

function createFakeEventObj() {
	// create a fake event object for older browsers to avoid errors in function call
	// when we need to pass the event object to functions
	if (!window.event) {
	window.event = false;
	}
} // createFakeEventObj

function resizeBlankDiv() {
	// resize blank placeholder div so IE 5 on mac will get all clicks in window
	if ((navigator.appVersion.indexOf('MSIE 5') != -1)
	&& (navigator.platform.indexOf('Mac') != -1)
	&& getStyleObject('blankDiv')) {
	getStyleObject('blankDiv').width = document.body.clientWidth - 20;
	getStyleObject('blankDiv').height = document.body.clientHeight - 20;
	}
}

function explorerMacResizeFix () {
	location.reload(false);
}

/**
 *	@brief Finish Field Validation
 *
 *	@par Description
 *	Finishes off the field validation. If the error message
 *	is blank then the validated value is placed back in the field
 *	value. Otherwise the error message is output and focus is
 *	placed back on the offending field.
 *
 *	@param[out] objId	The ID of the field being validated
 *	@parma[in]	msg		The Error message
 *	@param[in]	val		The validated value
 */
function fieldFin(objId, msg, val)
{
	var valObj = getObject(objId);
	if (valObj)
	{
		var errId = errorLoc[objId];
		var errObj = getObject(errId);
		if (errObj)
		{
			if (msg != "")
			{
				errObj.innerHTML = msg;
				changeVisibility(errId, 'visible');
				valObj.style.backgroundColor = "#FF6666";
//				valObj.focus();
			}
			else
			{
				changeVisibility(errId, 'hidden');
				errObj.innerHTML = "&nbsp;"
				valObj.style.backgroundColor = "white";
				valObj.value = val;
			}
		}
	}
	return (msg == "");
}

function checkNumField(fieldId,minValue,maxValue)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((val.match(/ /gi) != null) ||
			(val.match(/,/gi) != null))
		{
			msg = "Spaces and commas are not allowed";
		}
		if (val.length == 0)
		{
			msg = "Value cannot be blank"
		}
		var num = val - 0;
		if ((msg == "") &&
			(num.toString() != val)) {
			msg = "Not a valid number";
		}
		if ((msg == "") && (minValue == 0) && (maxValue == 0))	{
			return fieldFin(fieldId, msg, val);
		}
		if ((msg == "") &&
			((num < minValue) || (num > maxValue))) {
			msg = "Must be in range " + minValue + ".." + maxValue;
		}

		return fieldFin(fieldId, msg, val);
	}
	return false;
}

function checkSizeField(fieldId, blank_ok)
{

	var msg = "";
	var valObj = getObject(fieldId);
	var newval;

	if (valObj)	 {

		val = valObj.value;

		val = val.replace(/ /gi, "");

		newval = val;
		newval.toLowerCase();
		if (newval.match(/kb/gi) != null)  {
			newval = newval.replace(/kb/gi, "");
		}
		if (val.match(/mb/gi) != null)	{
			newval = newval.replace(/mb/gi, "");
		}
		if (val.match(/gb/gi) != null)	{
			newval = newval.replace(/gb/gi, "");
		}
		if (val.match(/tb/gi) != null)	{
			newval = newval.replace(/tb/gi, "");
		}

		if (blank_ok && (newval.length == 0))  {
			return fieldFin(fieldId, msg, val);
		}
		if (newval.length == 0)	 {
			msg = "Value cannot be blank"
		}

		var valNum = newval - 0;
		if ((msg == "") &&
			(valNum.toString() != newval))	{
			valNum = parseFloat(newval);
			if ((msg == "") && isNaN(valNum))  {
				msg = "Size must be a number";
			}
		}

		if ((msg == "") && (valNum < 0))  {
			msg = "Size must be a positive integer";
		}

		return fieldFin(fieldId, msg, val);

	}

	return false;

}

/** @brief Validate a text entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a single non-empty token. It will remove leading and
 *	trailing spaces to normalize it. If any error is detected
 *	an error message will be placed in the innerHTML of the HTML
 *	element specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkTextField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length > maxLen) {
 			msg = "Too long...maximum length is " + maxLen;
 		}
// 		if ((val.match(/ /gi) != null) ||
// 			(val.match(/,/gi) != null)) {
// 			msg = "Spaces and commas are not allowed";
// 		} else if (val.length == 0) {
// 			msg = "Value cannot be blank"
// 		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

function checkNonBlankTextField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length > maxLen) {
 			msg = "Too long...maximum length is " + maxLen;
 		} else if (val.length == 0) {
 			msg = "Value cannot be blank";
 		}
// 		if ((val.match(/ /gi) != null) ||
// 			(val.match(/,/gi) != null)) {
// 			msg = "Spaces and commas are not allowed";
// 		} else if (val.length == 0) {
// 			msg = "Value cannot be blank"
// 		} else if (val.length > maxLen) {
// 			msg = "Too long...maximum length is " + maxLen;
// 		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}
/** @brief Validate a Windows username entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a single non-empty token. It will remove leading and
 *	trailing spaces to normalize it. If any error is detected
 *	an error message will be placed in the innerHTML of the HTML
 *	element specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkWinUsernameField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	var bad_logon_chars = "\"/\\[]:;|=+*?<>%";
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((val.match(/ /gi) != null) ||
			(val.match(/,/gi) != null)) {
			msg = "Spaces and commas are not allowed";
		} else if (val.length == 0) {
			msg = "Value cannot be blank"
		} else if (val.length > maxLen) {
			msg = "Too long...maximum length is " + maxLen;
		} else

		/* Make sure that none of the invalid characters for a
		 * logon name are present.
		 */
		if (hasInvalidChar(val, bad_logon_chars)) {
			msg = "Windows logon name has invalid characters";
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/** @brief Validate a Windows password entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a single non-empty token. It will remove leading and
 *	trailing spaces to normalize it. If any error is detected
 *	an error message will be placed in the innerHTML of the HTML
 *	element specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkWinPasswordField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	var bad_logon_chars = "%";
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length == 0) {
			msg = "Value cannot be blank"
		} else if (val.length > maxLen) {
			msg = "Too long...maximum length is " + maxLen;
		} else

		/* Make sure that none of the invalid characters for a
		 * logon name are present.
		 */
		if (hasInvalidChar(val, bad_logon_chars)) {
			msg = "Password has invalid characters";
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/** @brief Validate a Windows domain entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a single non-empty token. It will remove leading and
 *	trailing spaces to normalize it. If any error is detected
 *	an error message will be placed in the innerHTML of the HTML
 *	element specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkWinDomainField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((val.match(/ /gi) != null) ||
			(val.match(/,/gi) != null)) {
			msg = "Spaces and commas are not allowed";
		} else if (val.length == 0) {
			msg = "Value cannot be blank"
		} else if (val.length > maxLen) {
			msg = "Too long...maximum length is " + maxLen;
		} else

		/* Make sure the first character is alpha */
		if ((uppercase + lowercase).indexOf(val.charAt(0), 0) == -1)  {
			msg = "Must start with alphabetic character";
		}
		else if (!isWinDomain(val))  {
			msg = "Invalid Windows domain name";
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/** @brief Validate a text entry field that can be empty
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a single non-empty token. It will remove leading and
 *	trailing spaces to normalize it. If any error is detected
 *	an error message will be placed in the innerHTML of the HTML
 *	element specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkTextBlankField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((val.match(/ /gi) != null) ||
			(val.match(/,/gi) != null))
		{
			msg = "Spaces and commas are not allowed";
		}
		else if (val.length > maxLen) {
			msg = "Too long...maximum length is " + maxLen;
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/**
 *	@brief Validate a text list entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a at least non-empty token. It will remove leading and
 *	trailing spaces and it will replace runs of commas and spaces with a
 *	single space to normalize it. If any error is detected an error
 *	message will be placed in the innerHTML of the HTML element
 *	specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkTextListField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/[, ]+/gi, " ");// Make into canonical form
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length == 0) {
			msg = "Value cannot be blank"
		} else if (val.length > maxLen) {
			msg = "Too long...maximum length is " + maxLen;
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/**
 *	@brief Validate a IP list entry field
 *
 *	@par Description
 *	Verifies that the text entered in the text input field
 *	consists of a at least non-empty token. It will remove leading and
 *	trailing spaces and it will replace runs of commas and spaces with a
 *	single space to normalize it. All tokesn are verfied to assure they
 *	are IPv4 compliant. If any error is detected an error
 *	message will be placed in the innerHTML of the HTML element
 *	specified by msgId.
 *
 *	@param[in]	fieldId		ID of field to perform a validity
 *							check on.
 *	@param[in]	maxEntries	Maximum number of tokens allowed in list
 *	@param[in]	eventObj	The event that caused this function to
 *							be run.
 */
function checkIpListField(fieldId, maxEntries)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/[, ]+/gi, " ");// Make into canonical form
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length == 0) {
			msg = "Value cannot be blank"
		} else {
			var a = val.split(" ");			  // Split into separate IP addresses
			if (a.length > maxEntries) {
				msg = "Too many items in list"
			} else {
				for (var i=0; i<a.length; i++) {
					if (!ipWellFormed(a[i])) {
						msg = a[i]+" is not a valid IPv4 address";
						break;
					}
				}
			}
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

/**
 *	@brief Check IP address field
 *
 *	@par Description
 *	Checks the ipId input field for being a valid v4 IP address. If
 *	it is not then outputs an error.
 *
 *	In addition, if a classless domain routing mask is found (a /nn suffix)
 *	on the IP address, the fieled identified by maskId will be updated.
 *
 *	If the next hop IP address field is found, it is validated to assure
 *	that the IP address therein is in the same subnet as our IP address.
 *
 *	@param[in]	ipId		IP address field ID
 *	@param[in]	maskId		IP subnet mask field ID
 *	@param[in]	hopId		Default route IP address field ID
 */
function checkIpField(ipId, maskId, hopId)
{
	var validIpMasks = ["0.0.0.0",
		"128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0",
		"248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0",
		"255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0",
		"255.248.0.0", "255.252.0.0", "255.252.0.0", "255.255.0.0",
		"255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0",
		"255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0",
		"255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240",
		"255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255"];

	var result = false;
	var msg = "";
	var ipObj = getObject(ipId);
	var maskObj = getObject(maskId);
	if (ipObj)
	{
		var ip = ipObj.value;
		ip = ip.replace(/^ +/gi, "");	  // Remove leading spaces
		ip = ip.replace(/ +$/gi, "");	  // Remove trailing spaces
		if ((ip.match(/ /gi) != null) ||
			(ip.match(/,/gi) != null))
		{
			msg = "Spaces and commas are not allowed";
		}
		var x = ip.split("/");
		ip = x[0];
		if (x[1])
		{
			var maskSize = parseInt(x[1]);
			if ((maskSize < 0) || (maskSize > 32))
			{
				msg = "Subnet value must be between 0 and 32"
			}
			else
			{
				if (maskObj)
				{
					maskObj.value = validIpMasks[maskSize];
				}
				else
				{
					msg = "Subnet mask specification not valid on this IP address";
				}
			}
		}
		if (!ipWellFormed(ip))
		{
			msg = "Not a valid IPv4 address";
		}
		result = fieldFin(ipId, msg, ip);
		if (msg == "")
			setDefaultIpRoute(ipId, maskId, hopId);
	}
	return result;
}

/**
 *	@brief Return classless domain bit count from IP subnet mask string
 *
 *	@param[in] maskStr	IP subnet mask string
 *	@return Number of bits in mask or -1 if not a valid subnet mask
 */
function maskBits(maskStr)
{
	var validIpMasks = ["0.0.0.0",
		"128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0",
		"248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0",
		"255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0",
		"255.248.0.0", "255.252.0.0", "255.252.0.0", "255.255.0.0",
		"255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0",
		"255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0",
		"255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240",
		"255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255"];
	var bits=-1;

	maskStr = maskStr.replace(/^ +/gi, "");	  // Remove leading spaces
	maskStr = maskStr.replace(/ +$/gi, "");	  // Remove trailing spaces
	if ((maskStr.match(/ /gi) != null) ||
		(maskStr.match(/,/gi) != null))
	{
		return bits;
	}

	for (var i=0; i<validIpMasks.length; i++)
	{
		if (maskStr == validIpMasks[i])
		{
			bits = i;
			break;
		}
	}
	return bits;
}

/**
 *	@brief Check IP mask field
 *
 *	@par Description
 *	Checks the ipId input field for being a valid v4 IP address mask. If
 *	it is not then outputs an error.
 *
 *	If the next hop IP address field is found, it is validated to assure
 *	that the IP address therein is in the same subnet as our IP address.
 *
 *	@param[in]	ipId		IP address field ID
 *	@param[in]	maskId		IP subnet mask field ID
 *	@param[in]	hopId		Default route IP address field ID
 */
function checkMaskField(ipId, maskId, hopId)
{
	var msg;

	msg = "";
	var ipObj = getObject(ipId);
	var maskObj = getObject(maskId);
	if (maskObj)
	{
		var mask = maskObj.value;
		mask = mask.replace(/^ +/gi, "");	  // Remove leading spaces
		mask = mask.replace(/ +$/gi, "");	  // Remove trailing spaces
		if ((mask.match(/ /gi) != null) ||
			(mask.match(/,/gi) != null))
		{
			msg = "Spaces and commas are not allowed";
		}

		if (maskBits(mask) < 0)
		{
			msg = "Not a valid IPv4 subnet mask"
		}
		fieldFin(maskId, msg, mask);
		if (msg == "") {
			setDefaultIpRoute(ipId, maskId, hopId);
			return true;
	   }
	}
	return false;
}

/**
 *	@brief Check Default route IP address field
 *
 *	@par Description
 *	Checks the ipId input field for being a valid v4 IP address. If
 *	it is not then outputs an error.
 *
 *	In addition, if we are given a subnet mask and another IP address
 *	we will validate that our IP address is in the same subnet.
 *
 *	@param[in]	ipId		IP address field ID
 *	@param[in]	maskId		IP subnet mask field ID
 *	@param[in]	hopId		Default route IP address field ID
 */
function checkHopField(ipId, maskId, hopId)
{
	var msg;

	msg = "";
	var hopObj = getObject(hopId);
	if (hopObj)
	{
		var hop = hopObj.value;
		hop = hop.replace(/^ +/gi, "");		// Remove leading spaces
		hop = hop.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((hop.match(/ /gi) != null) ||
			(hop.match(/,/gi) != null))
		{
			msg = "Spaces and commas are not allowed";
		}
		if (!ipWellFormed(hop))
		{
			msg = "Not a valid IPv4 address";
		}
		var ipObj = getObject(ipId);
		var maskObj = getObject(maskId);
		if (ipObj && maskObj && (msg == ""))
		{
			var s1 = subnet(ipObj.value, maskObj.value);
			var s2 = subnet(hop, maskObj.value);
			if (s1 != s2)
			{
				msg = "Must be in same subnet as "+ipObj.value+"/"+
					  maskBits(maskObj.value);
			}
		}
		return fieldFin(hopId, msg, hop);
	}
	return false;
}

/**
 *	@brief Check subnet entry field
 *
 *	@par Description
 *	Verifies that the given IP address is actually a subnet address.
 *
 *	@param[in]	ipId		IP address field ID
 *	@param[in]	maskId		IP subnet mask field ID
 */
function checkSubnetField(ipId, maskId)
{
	var msg;

	msg = "";
	if (checkIpField(ipId, maskId, "invalid"))
	{
		var subnetObj = getObject(ipId);
		var maskObj = getObject(maskId);
		if (subnetObj && maskObj)
		{
			var errId = errorLoc[ipId];
			var errObj = getObject(errId);
			var newVal = subnet(subnetObj.value, maskObj.value);
			if (newVal != subnetObj.value)
			{
				var msg = subnetObj.value+" not a valid subnet address for "+maskObj.value+"...corrected";
				subnetObj.value = newVal;
				if (errObj)
				{
					errObj.innerHTML = msg;
					changeVisibility(errId, 'visible');
				}
			} else {
				changeVisibility(errId, 'hidden');
				errObj.innerHTML = "&nbsp;"
			}
		}
	}
	return true;
}
/**
 *	@brief Check interface map fields
 *
 *	@par Description
 *	Given IDs to locate the list of available, public, cluster, iSCSI
 *	and other interfaces, this routine validates the public, cluster and
 *	iSCSI interfaces.
 */
function checkIntfMapField(availId, pubId, clusId, iScsiId, otherId, otherDspId,
						   pubErrId, clusErrId, iScsiErrId)
{
	var errMsg = "";
	var errFieldId;
	var errMsgId;
	var errVal;
	var avail;

	var availObj = getObject(availId);
	var pubObj = getObject(pubId);
	var clusObj = getObject(clusId);
	var iScsiObj = getObject(iScsiId);
	var otherObj = getObject(otherId);
	var otherDspObj = getObject(otherDspId);

	if (availObj && pubObj && clusObj && iScsiObj && otherObj)
	{
		/*
		 *	Build easy to access associative array of available interfaces
		 *	and mark them as available.
		 */
		var a = availObj.value.split(" ");
		avail = new Array();
		var item;
		for (item=0; item<a.length; item++) {
			var intf = a[item];
			avail[intf] = true;
		}
		checkIntfMap(pubObj, pubId, pubErrId);
		if (pubObj.value == "")
		{
			fieldFin(pubId, "At least one public interface is required.",
					 pubObj.value);
		}
		else
		{
			if (errMsg == "") {
				checkIntfMap(clusObj, clusId, clusErrId);
			}
			if (errMsg == "") {
				checkIntfMap(iScsiObj, iScsiId, iScsiErrId);
			}
		}
		var unused = "";
		for (item in avail)
		{
			if ((item != "containsValue") && avail[item])
				unused = unused + " " + item;
		}
		unused = unused.replace(/^ +/gi, "");		// Remove leading spaces
		if (otherObj)
		{
			otherObj.value = unused;
		}
		if (otherDspObj)
		{
			otherDspObj.value = unused;
		}
		return (errMsg == "");
	}

	function checkIntfMap(fieldObj,fieldId, errId)
	{
		var item;
		var found = false;
		var errObj = getObject(errId);
		/*
		 *	Split field value into list of interfaces
		 */
		var val = fieldObj.value;
		val = val.replace(/[, ]+/gi, " ");// Make into canonical form
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		var f = val.split(" ");
		for (item=0; item<f.length;item++)
		{
			var intf = f[item];
			if ((intf == "") ||
				avail[intf])
			{
				avail[intf] = false;
				if (errObj)
				{
					changeVisibility(errId, 'hidden');
					errObj.innerHTML = "&nbsp;"
					fieldObj.style.backgroundColor = "white";
					fieldObj.value = val;
				}
			}
			else
			{
				errMsg = intf + " does not exist or has already been assigned";
				if (errObj)
				{
					errObj.innerHTML = errMsg;
					changeVisibility(errId, 'visible');
					fieldObj.style.backgroundColor = "#FF6666";
//					fieldObj.focus();
				}
				break;
			}
		}
		return;
	} // checkIntfMap

} // checkIntfMapField

function checkDnsLabelField(fieldId, maxLen)
{
	var msg;

	msg = "";
	var fieldObj = getObject(fieldId);
	if (fieldObj)
	{
		var fieldVal = fieldObj.value;
		fieldVal = fieldVal.replace(/^ +/gi, "");		// Remove leading spaces
		fieldVal = fieldVal.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((fieldVal.match(/ /gi) != null) ||
			(fieldVal.match(/,/gi) != null)) {
			msg = "Spaces and commas are not allowed";
		} else if (!isDnsLabel(fieldVal)) {
			msg = "\""+fieldVal+"\" is not a valid DNS label";
		} else if (fieldVal.length > maxLen) {
			msg = "Name is too long...maximum is " + maxLen;
		}
		return fieldFin(fieldId, msg, fieldVal);
	}
	return false;
}

function checkDnsNameField(fieldId, maxLen)
{
	var msg;

	msg = "";
	var fieldObj = getObject(fieldId);
	if (fieldObj)
	{
		var fieldVal = fieldObj.value;
		fieldVal = fieldVal.replace(/^ +/gi, "");		// Remove leading spaces
		fieldVal = fieldVal.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((fieldVal.match(/ /gi) != null) ||
			(fieldVal.match(/,/gi) != null)) {
			msg = "Spaces and commas are not allowed";
		} else if (!isDnsName(fieldVal)) {
			msg = "\"" + fieldVal + "\" is not a valid DNS name";
		} else if (fieldVal.length > maxLen) {
			msg = "Name is too long...maximum is " + maxLen;
		}
		return fieldFin(fieldId, msg, fieldVal);
	}
	return false;
}

function checkDnsNamePortField(fieldId, maxLen)
{
	var msg;

	msg = "";
	var fieldObj = getObject(fieldId);
	if (fieldObj)
	{
		var fieldVal = fieldObj.value;
		fieldVal = fieldVal.replace(/^ +/gi, "");		// Remove leading spaces
		fieldVal = fieldVal.replace(/ +$/gi, "");		// Remove trailing spaces
		if ((fieldVal.match(/ /gi) != null) ||
			(fieldVal.match(/,/gi) != null)) {
			msg = "Spaces and commas are not allowed";
		} else if (fieldVal.length > maxLen) {
			msg = "Name is too long...maximum is " + maxLen;
		} else {
			var a = fieldVal.split(":");
			if (a.length > 2) {
				msg = "No more than one \":\" allowed.";
			} else {
				if (!isDnsName(a[0])) {
					msg = "\"" + a[0] + "\" is not a valid DNS name or IP address";
				} else {
					if (a.length == 2) {
						var portNum = a[1] - 0;
						if (a[1] != portNum.toString()) {
							msg = "Port value \""+a[1]+"\" is not a number";
						} else if ((portNum < 0) ||
							(portNum > 65535)) {
							msg = "Port number "+portNum+" must be in range 0..65535";
						} // if port number out of range
					} // if port number specified
				} // else good DNS name
			} // else right number of fields
		} // else length, spaces and commas okay
		return fieldFin(fieldId, msg, fieldVal);
	}
	return false;
}

function checkDnsListField(fieldId, maxLen)
{
	var msg = "";
	var valObj = getObject(fieldId);
	if (valObj)
	{
		val = valObj.value;
		val = val.replace(/[, ]+/gi, " ");// Make into canonical form
		val = val.replace(/^ +/gi, "");		// Remove leading spaces
		val = val.replace(/ +$/gi, "");		// Remove trailing spaces
		if (val.length == 0) {
			msg = "Value cannot be blank"
		} else if (val.length > maxLen) {
			msg = "Too many characters in entry...maximum is " + maxLen;
		} else {
			var a = val.split(" ");
			for (var i=0; i<a.length; i++)
			{
				var item = a[i];
				if (!isDnsName(item)) {
					msg = "\""+item+"\" is not a legal DNS name";
					break;
				}
			}
		}
		return fieldFin(fieldId, msg, val);
	}
	return false;
}

function isDnsLabel(labelStr)
{
	if (labelStr.length == 0) {
		return false;
	} else {
		for (var i=0; i<labelStr.length; i++) {
			var charCode = labelStr.charCodeAt(i);
			if ((i == 0) || (i == (labelStr.length-1))) {
				// First and last characters must be id-prefix
				if (!isIdPrefix(charCode)) {
					return false;
				}
			} else {
				// Middle characters must be dns_char
				if (!isDnsChar(charCode)) {
					return false;
				}
			}
		}
	}
	return true;

	function isIdPrefix(charCode)
	{
		// ASCII Alpha (upper and lower) or Digit
		return ((charCode >= 0x41) && (charCode <= 0x5A)) ||
			   ((charCode >= 0x61) && (charCode <= 0x7A)) ||
			   ((charCode >= 0x30) && (charCode <= 0x39));
	}
	function isDnsChar(charCode)
	{
		// id-prefix or dash
		return (isIdPrefix(charCode)) ||
			   (charCode == 0x2D);
	}
}

function isDnsName(nameStr)
{
	// A valid DNS name is a sequence of DNS labels separated by dots.
	var a = nameStr.split(".");
	for (var i=0; i<a.length; i++)
	{
		var item = a[i];
		if (!isDnsLabel(item))
			return false;
	}
	return true
}

/**
 *	@brief Test IP address string to assure that it represents
 *		   a well formev IPv4 address.
 */
function ipWellFormed(ipStr)
{
	var a = ipStr.split(".");
	var ip = 0;
	var valid = 1;
	var octet;
	/*
	 * Verify that all octets are in the range of
	 * 0..255 and that no other characters exist in
	 * each octet.
	 */
	for (var i=0; i<a.length; i++)
	{
		octet = parseInt(a[i]);
		if ((octet < 0) || (octet > 255))
		{
		   valid = 0;
		   break;
		}
		if (octet.toString() != a[i])
		{
		  valid = 0;
		  break;
		}
	}
	/*
	 * Verify there are only four octets
	 */
	if (a.length != 4)
		valid = 0;
	return valid;
}

/**
 *	@brief Return an IP address string for the subnet that
 *		   an address is in (given the subnet mask).
 */
function subnet(ipStr, maskStr)
{
	var ip = ipStr.split(".");
	var m = maskStr.split(".");
	var s = "";
	var iOct, mOct, sOct;

	for (i=0; i<4; i++)
	{
		iOct = parseInt(ip[i]);
		mOct = parseInt(m[i]);
		sOct = iOct & mOct;
		if (s != "")
			s = s+"."+sOct;
		else
			s = sOct.toString();
	}
	return s;
}

/**
 *	@brief If the next hop IP address is not in the subnet
 *		   given by the IP address and subnet mask, set the
 *		   hop to a good default next hop (default route).
 */
function setDefaultIpRoute(ipId, maskId, hopId)
{
	var ipObj = getObject(ipId);
	var maskObj = getObject(maskId);
	var hopObj = getObject(hopId);

	/*
	 *	If we have a valid IP number (error message empty)
	 *	and we have a subnet mask and next hop address,
	 *	verify that the next hop is in the subnet. If not
	 *	then set a default next hop that is legal.
	 */
	if (maskObj && hopObj && ipObj)
	{
		var s1 = subnet(ipObj.value, maskObj.value);
		var s2 = subnet(hopObj.value, maskObj.value);
		if (s1 != s2)
		{
			var ip = s1.split(".");
			ip[3] = parseInt(ip[3])+1;
			hopObj.value = ip.join(".");;
		}

	}
}
/**
 *	@brief Copy help text from into help display DIV
 *
 *	@param[in]	helpId		ID for hidden DIV that contains help
 *							text to be copied into help DIV.
 *	@param[in]	fieldId		ID for field that help is for. The
 *							help DIV will be moved vertically
 *							to match the location of the entry
 *							field.
 */
function set_help(helpId, fieldId)
{
	var helpDivObj = getObject('help');
	var helpSrcObj = getObject(helpId);
	if (helpDivObj && helpSrcObj)
	{
		helpDivObj.innerHTML = helpSrcObj.innerHTML;
	}
}

/**
 *	@brief Setup wizard step 1 volume selection button
 *
 *	@par Description
 *	Entered when a volume selection button was selected
 *	in the first screen (DIV) of the setup wizard. We
 *	make the volume entry area (contained in a DIV) visable
 *	and enable the next button. We also copy the device
 *	path information we are given into the hidden device
 *	path entry area so that it will be availble to the
 *	final form processing when we are done with setup
 *	entry.
 *
 *	@param[in] divId	ID of DIV where next button is to
 *						be enabled.
 *	@param[in] opId		ID of field to record operation type
 *						('create' or 'join')
 *	@param[in] devPathId Id of field to put device path
 *	@param[in] pubIpId	A field to be reset if we are doing
 *						a create operation.
 *	@param[in] pubMaskId A field to be reset if we are doing
 *						a create operation.
 *	@param[in] pathVal	String indicating the device volume
 *						on which to create a cluster.
 *	@param[in] volNamDivId The ID of the DIV that wraps the
 *						volume name entry field and is used
 *						to control visibilty of the entry
 *						field.
 */
function step1VolSelect(divId, opId, volNameDivId, devPathId, pubIpId,
						pubMaskId, pathVal, formErrId)
{
	var devObj = getObject(devPathId);
	var volObj = getObject(volNameDivId);
	var formErrObj = getObject(formErrId);
	if (volObj)
	{
		// To make the volume entry field visible we delete
		// the visibility attribute that was there rather than
		// directly making it visible. If we simply make it
		// visable then it stays visible when we go to the next
		// setup DIV.
		changeVisibility(volNameDivId, '');
		setValue(opId, 'create');
		resetValue(pubIpId);
		resetValue(pubMaskId);
		if (devObj)
		{
		   devObj.value = pathVal;
		   changeDisable(wizButtonIds[divId], false);
		} // if radio button found
		if (formErrObj) {
			formErrObj.innerHTML = "&nbsp;";
			changeVisibility(formErrId, 'hidden');
		}
	} // if volume name entry div found
}

/**
 *	@brief Setup wizard step 1 join selection button
 *
 *	@par Description
 *
 *	@param[in] divId	ID of DIV where next button is to
 *						be enabled.
 *	@param[in] opId		ID of field to record operation type
 *						('create' or 'join')
 *	@param[in] devPathId Id of field to put device path
 *	@param[in] pubIpId	Field to set VIP address into.
 *	@param[in] pubMaskId Field to set public IP subnet mask into.
 *	@param[in] volNamDivId Field to be reset when join is selected.
 *	@param[in] vipIp	 Virtual IP address to use on join
 *	@param[in] vipIpMask Sunbnet mask for virtual IP address
 */
function step1ClusJoin(divId, opId, volNameDivId, devPathId, pubIpId,
					   pubMaskId, vipIp, vipIpMask, formErrId)
{
	var volObj = getObject(volNameDivId);
	var formErrObj = getObject(formErrId);
	if (volObj)
	{
		changeVisibility(volNameDivId, 'hidden');
		setValue(opId, 'join');
		resetValue(devPathId);
		resetValue(volNameDivId);
		setValue(pubIpId, vipIp);
		setValue(pubMaskId, vipIpMask);
		changeDisable(wizButtonIds[divId], false);
		if (formErrObj) {
			formErrObj.innerHTML = "&nbsp;";
			changeVisibility(formErrId, 'hidden');
		}
	} // if volume name entry div found
}

function clusMemNodeSel(nameFldId,nodeName,ipFldId,ipAddr,guidFldId,guid,submitId)
{
	setValue(nameFldId, nodeName);
	setValue(ipFldId, ipAddr);
	setValue(guidFldId, guid);
	changeDisable(submitId, false);
}

function checkVipField(ipId, maskId, hopId, nextButtonId, clusSubNetId)
{
	if (checkIpField(ipId, maskId, hopId))
	{
		// Set up a reasonable private LAN subnet based on the VIP
		var subNetObj = getObject(clusSubNetId);
		var ipObj = getObject(ipId);
		if (subNetObj && ipObj) {
			var ip = ipObj.value.split(".");
			ip[0] = "172";
			ip[1] = "16";
			ip[2] = ip[3];
			ip[3] = "0";
			subNetObj.value = ip.join(".");
//			   alert("Setting subnet to "+subNetObj.value+" from "+ipObj.value);
//		   } else {
//			   alert("subNetObj="+subNetObj+" ipObj="+ipObj);
		}

		// Allow the next step to be taken
		changeDisable('step2Next', false);
		return true;
	}
	return false;
}

function checkWizOpField(opId,errId)
{
	var result;
	var opObj = getObject(opId);
	var errObj = getObject(errId);

	result = ((opObj) && ((opObj.value == "join") ||
						  (opObj.value == "create")));
	if (errObj) {
		if (result) {
			errObj.innerHTML = "&nbsp;";
			changeVisibility(errId, 'hidden');
		} else {
			errObj.innerHTML = "Select operation to be performed.";
			changeVisibility(errId, 'visible');
		}
	}
	return result;
}

function wizReset(step1div)
{
	wizChgDiv(0,step1div,step1div);
//	changeDisable(wizButtonIds[step1div], true);
	changeVisibility('volNameDiv', 'hidden');
	return true;
}

/**
 *	@brief change wizard DIV
 *
 *	@par Description
 *	If the operation field is "join" then we go to the finish DIV
 *	otherwise go to the next DIV.
 */
function wizChgDiv(opId, nextDivId, finishDivId)
{
	// Disable all next buttons except our destination which we enable
	var opObj = getObject(opId);
	var destDivId = nextDivId;
	
	if ((opObj) && (opObj.value == "join")) {
	   destDivId = finishDivId;
	}
	for (i in wizButtonIds) {
		if (destDivId == i) {
			changeDisable(wizButtonIds[i], false);
			changeVisibility(i, 'visible');
		} else {
			changeDisable(wizButtonIds[i], true);
			changeVisibility(i, 'hidden');
		}
	}
	return true;
}

function balanceHeight(div1Id, div2Id)
{
	var div1Obj = getObject(div1Id);
	var div2Obj = getObject(div2Id);
	if (div1Obj && div2Obj) {
		var minHeight = (window.innerHeight) ? window.innerHeight : document.body.offsetHeight;
		minHeight = minHeight - 80; // Adjust for banner DIV height
		var x = div1Obj.offsetHeight;
		var y = div2Obj.offsetHeight;
		if (y > x) {
			x = y;
		}
		if (x < minHeight) {
			x = minHeight;
		}
		div1Obj.style.height = x + "px";
		div1Obj.style.height = x + "px";
	}
}

function setClusSg(quota, grow_size)
{

	var maxSizeObj = getObject("max_size");
	var growSizeObj = getObject("grow_size");

	changeDisable('commit_change', false);
	maxSizeObj.value = quota;
	growSizeObj.value = grow_size;

	return true;

}

function setDsProtectObjects(protect_path)
{

	var protPath = getObject('protect_path');

	changeDisable('start_protect', false);
	protPath.value = protect_path;

	/* If we have selected something that doesn't have
	 * a protection path, we want to display the hidden
	 * div that holds the protect path string.
	 */
	if (protect_path.length == 0)  {
		changeVisibility('protect_input', 'visible');
	}
	else {
		changeVisibility('protect_input', 'hidden');
	}

	return true;

}

function selectOp(op_selected, op_guid, op_type, op_status)
{

	var opGuid = getObject('op_guid');

	if (op_selected)  {
		changeDisable('purge_operation', false);
		changeDisable('stop_operation', false);
		if ((op_type == "Recovery") && (op_status == "Completed"))  {
			changeDisable('show_report', false);
			opGuid.value = op_guid;
		}
		else {
			changeDisable('show_report', true);
			opGuid.value = "";
		}
	}
	else {
		changeDisable('purge_operation', true);
		changeDisable('stop_operation', true);
		changeDisable('show_report', true);
		opGuid.value = "";
	}

	return true;

}

function showOperation(op_id)
{

	var opGuid = getObject('op_guid');
	var vodrReport = getObject('vodr_report');

	if (op_id == "")  {
		changeVisibility('ops', 'visible');
		changeVisibility('report', 'hidden');
		changeDisable('show_as_pdf', true);
		vodrReport.value = "";
	}
	else if (op_id == "op")  {
		xajax_get_vodr_report(opGuid.value);
		changeVisibility('ops', 'hidden');
		changeVisibility('report', 'visible');
		changeDisable('show_as_pdf', false);
	}

	return true;

}

var numbers = '0123456789';
var lowercase = 'abcdefghijklmnopqrstuvwxyz';
var uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var windomain = '!@#$%^&()-_\'{}~';

function isValidChar(param, validchars)
{
	if (param == "")
		return false;
	for (i = 0; i < param.length; i += 1)  {
		if (validchars.indexOf(param.charAt(i), 0) == -1)
			return false;
	}
	return true;
}

function hasInvalidChar(param, invalidchars)
{
	if (param == "")
		return false;
	for (i = 0; i < param.length; i += 1)  {
		if (invalidchars.indexOf(param.charAt(i), 0) != -1)
			return true;
	}
	return false;
}

function isNumber(param) { return isValidChar(param, numbers); }
function isLower(param) { return isValidChar(param, lowercase); }
function isUpper(param) { return isValidChar(param, uppercase); };
function isAlpha(param) { return isValidChar(param, lowercase + uppercase); }
function isAlphanum(param) { return isValidChar(param, lowercase + uppercase + numbers); }
function isWinDomain(param) { return isValidChar(param, lowercase + uppercase + numbers + windomain); }
