/* Declarations of global data. */
var fetchClusterServicesPoller;
var clusterServices;
// Storing globally for the sake of multiple screens in reconfigure
var localReconfigureServiceData = {};
var remoteReconfigureServiceData = {};
var confirmationDataPanelBodyContent = '';
var confirmationDataPanel;
var panelNoButton = {
value: 'Cancel',
action: function (e) {
e.preventDefault();
hideAndDestroyPanel();
},
section: 'footer'
};
var panelYesButton;
// Only one service can be reconfigured at a time.
var reconfigLevelOneYesButton;
var reconfigLevelTwoNoButton;
function showPanel() {
showPanel(function() {});
}
function showPanel(postShowFn) {
confirmationDataPanel.set('y', 200);
confirmationDataPanel.set('x', (globalYui.one('body').get('region').width - confirmationDataPanel.get('width'))/2);
confirmationDataPanel.show();
if (postShowFn != null) {
postShowFn.call();
}
}
function hidePanel(postHideFn) {
if (postHideFn != null) {
postHideFn.call();
}
}
function hideAndDestroyPanel() {
hidePanel(function() {
confirmationDataPanel.hide();
destroyInformationalPanel(confirmationDataPanel);
});
}
function getTitleForReconfiguration(serviceName) {
return 'Make configuration changes for ' + serviceName ;
}
function setupReconfigureFirstScreen(serviceName) {
var panelTitle = getTitleForReconfiguration(serviceName);
confirmationDataPanel.set( 'headerContent', panelTitle);
confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent);
// Remove buttons from previous stage
confirmationDataPanel.removeButton(0);
confirmationDataPanel.removeButton(0);
confirmationDataPanel.addButton( panelNoButton );
confirmationDataPanel.addButton( reconfigLevelOneYesButton );
}
function setupReconfigureSecondScreen(serviceName) {
var affectedServices = clusterServices[serviceName].dependencies;
var dependents = clusterServices[serviceName].dependents;
for (dep in dependents) {
affectedServices.push(dependents[dep]);
}
var panelContent = 'Affected services:' + getAffectedDependenciesMarkup(affectedServices, serviceName, 'reconfigure');
var panelTitle = 'Review changes to ' + serviceName + '\'s configuration';
confirmationDataPanel.set( 'headerContent', panelTitle);
confirmationDataPanel.set( 'bodyContent', panelContent);
// Remove buttons from previous stage
confirmationDataPanel.removeButton(0);
confirmationDataPanel.removeButton(0);
confirmationDataPanel.addButton( reconfigLevelTwoNoButton );
confirmationDataPanel.addButton( panelYesButton );
}
// Clean up the affected-services list to only include appropriate installed long-running services
function getAffectedDependenciesMarkup(affectedServices, serviceName, action) {
var affectedDependenciesMarkup = '';
var serviceDisplayName = clusterServices[serviceName].displayName;
var deps = affectedServices;
affectedServices = [];
for (dep in deps) {
var svc = deps[dep];
if (clusterServices.hasOwnProperty(svc) && (clusterServices[svc].isEnabled == 1) && clusterServices[svc].attributes.runnable ) {
affectedServices.push(svc);
}
}
var dependencyMarkup = "";
for (affectedSrvc in affectedServices) {
if (clusterServices[affectedServices[affectedSrvc]].attributes.runnable) {
dependencyMarkup += '
' + clusterServices[affectedServices[affectedSrvc]].displayName + ' | ' + titleCase(clusterServices[affectedServices[affectedSrvc]].state) + ' |
';
}
}
if (dependencyMarkup != '') {
// Add this service at the top of the list
dependencyMarkup = 'Service name | Current state | ' + serviceDisplayName + ' | ' + titleCase(clusterServices[serviceName].state) + ' |
' + dependencyMarkup + '
';
affectedDependenciesMarkup += 'Including this service and all its recursive dependencies, the following is the list of services that will be affected by ' + action + ' of ' + serviceName + ' :' +
'
' +
'' +
dependencyMarkup +
'
';
}
return affectedDependenciesMarkup;
}
function setupStartServiceScreen(serviceName) {
setupStartStopServiceScreen('start', serviceName);
}
function setupStopServiceScreen(serviceName) {
setupStartStopServiceScreen('stop', serviceName);
}
function setupStartStopServiceScreen(action, serviceName) {
var serviceDisplayName = clusterServices[serviceName].displayName;
var affectedServices;
var confirmationDataPanelTitle;
if ( action == 'start') {
confirmationDataPanelTitle = 'Starting ' + serviceDisplayName;
confirmationDataPanelBodyContent = "We are now going to start " + serviceDisplayName + "...
";
affectedServices = clusterServices[serviceName].dependencies;
} else if (action == 'stop') {
confirmationDataPanelTitle = 'Stopping ' + serviceDisplayName;
confirmationDataPanelBodyContent = "We are now going to stop " + serviceDisplayName + "...
";
affectedServices = clusterServices[serviceName].dependents;
}
confirmationDataPanelBodyContent += getAffectedDependenciesMarkup(affectedServices, serviceName, action);
confirmationDataPanelBodyContent = '' + confirmationDataPanelBodyContent + '
';
var confirmationDataPanelWidth = 800;
var confirmationDataPanelHeight = 400;
confirmationDataPanel.set( 'headerContent', confirmationDataPanelTitle);
confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent);
confirmationDataPanel.set( 'height', confirmationDataPanelHeight );
confirmationDataPanel.set( 'width', confirmationDataPanelWidth );
confirmationDataPanel.addButton( panelNoButton);
confirmationDataPanel.addButton( panelYesButton );
showPanel();
}
function setupStartAllServicesScreen() {
setupStartStopAllServicesScreen('startAll');
}
function setupStopAllServicesScreen() {
setupStartStopAllServicesScreen('stopAll');
}
function setupStartStopAllServicesScreen(action) {
var confirmationDataPanelTitle;
var confirmationDataPanelBodyContent;
if ( action == 'startAll' ) {
confirmationDataPanelTitle = 'Start All Services';
confirmationDataPanelBodyContent = "We are now going to start all services in the cluster";
} else if ( action == 'stopAll' ) {
confirmationDataPanelTitle = 'Stop All Services';
confirmationDataPanelBodyContent = "We are now going to stop all the services in the cluster";
}
var confirmationDataPanelWidth = 800;
var confirmationDataPanelHeight = 400;
confirmationDataPanel.set( 'headerContent', confirmationDataPanelTitle);
confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent);
confirmationDataPanel.set( 'height', confirmationDataPanelHeight );
confirmationDataPanel.set( 'width', confirmationDataPanelWidth );
confirmationDataPanel.addButton( panelNoButton);
confirmationDataPanel.addButton( panelYesButton );
showPanel();
}
function setupReconfigureScreens(serviceName) {
// TODO: Needed for others too?
/* First, (temporarily) stop any further fetches. */
fetchClusterServicesPoller.stop();
reconfigLevelOneYesButton = {
value: 'Apply Changes',
action: function (e) {
e.preventDefault();
localReconfigureServiceData = generateUserOpts();
var remoteProps = remoteReconfigureServiceData.services[serviceName].properties;
var localProps = localReconfigureServiceData[serviceName].properties;
var allEqual = true;
for (key in localProps) {
var remoteValue = remoteProps[key].value;
var localValue = localProps[key]["value"];
if ( localValue != remoteValue) {
allEqual = false;
}
}
if (allEqual) {
alert("You haven't made any changes");
return;
}
hidePanel(function() {
// Store the requestData and the html
confirmationDataPanelBodyContent = confirmationDataPanel.get( 'bodyContent' );
setupReconfigureSecondScreen(serviceName);
showPanel();
});
},
classNames: 'okButton',
section: 'footer'
};
reconfigLevelTwoNoButton = {
value: 'Go back and re-edit',
action: function (e) {
e.preventDefault();
hidePanel(function() {
setupReconfigureFirstScreen(serviceName);
showPanel();
});
},
section: 'footer'
};
// Render first with a loading image and then get config items
confirmationDataPanelBodyContent =
"
";
var confirmationDataPanelWidth = 1000;
var confirmationDataPanelHeight = 500;
var confirmationDataPanelTitle = getTitleForReconfiguration(serviceName);
confirmationDataPanel.set( 'height', confirmationDataPanelHeight );
confirmationDataPanel.set( 'width', confirmationDataPanelWidth );
confirmationDataPanel.set( 'headerContent', confirmationDataPanelTitle);
confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent );
showPanel();
executeStage( '../php/frontend/fetchClusterServices.php?clusterName=' + clusterName +
'&getConfigs=true&serviceName=' + serviceName, function (serviceConfigurationData) {
// Store the remote data
remoteReconfigureServiceData = serviceConfigurationData;
var serviceConfigurationMarkup = constructDOM( serviceConfigurationData );
if( globalYui.Lang.trim( serviceConfigurationMarkup).length == 0 ) {
serviceConfigurationMarkup = 'Move along folks, nothing to see here...
';
}
else {
/* Augment confirmationDataPanel with the relevant buttons only if there
* is something of value to show.
*/
confirmationDataPanel.addButton( panelNoButton );
confirmationDataPanel.addButton( reconfigLevelOneYesButton );
}
/* XXX Note that this must be kept in-sync with the corresponding markup
* on the InstallationWizard page.
*/
confirmationDataPanelBodyContent =
''+
'Placeholder' +
'
' +
'';
confirmationDataPanelBodyContent = '' + confirmationDataPanelBodyContent + '
';
confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent );
});
}
function performServiceManagement( action, serviceName, confirmationDataPanel ) {
/* First, (temporarily) stop any further fetches. */
fetchClusterServicesPoller.stop();
var manageServicesRequestData = {
action: action,
services: {}
};
if( action == "reconfigure" ) {
manageServicesRequestData.services = localReconfigureServiceData;
}
else {
/* Need to explicitly set a key named for serviceName this way because it's
* a variable - in the future, the value will be a filled-out array (for
* now, we only support managing a single service at a time).
*/
manageServicesRequestData.services[serviceName] = {};
}
globalYui.io( "../php/frontend/manageServices.php?clusterName=" + clusterName, {
method: 'POST',
data: globalYui.JSON.stringify(manageServicesRequestData),
timeout: 10000,
on: {
success: function(x, o) {
globalYui.log("RAW JSON DATA: " + o.responseText);
var manageServicesResponseJson;
try {
manageServicesResponseJson = globalYui.JSON.parse(o.responseText);
}
catch (e) {
alert("JSON Parse failed!");
return;
}
globalYui.log(globalYui.Lang.dump(manageServicesResponseJson));
/* Check that manageServicesResponseJson actually indicates success. */
if( manageServicesResponseJson.result == 0 ) {
/* Only on success should we destroy confirmationDataPanel - on
* failure, we depend on the fact that there'll be errors shown
* inside the panel that the user will want/need to interact with.
*/
hideAndDestroyPanel();
var manageServicesProgressStatusMessage = {
success:
'' +
'Yabba Dabba Doo! Manage services ' +
'' +
'even harder' +
'' +
'?' +
'
',
failure:
'' +
'Scooby Doo, where are you? Perhaps in the ' +
'Operation Logs' +
'?' +
'
'
};
var manageServicesProgressPostCompletionFixup = {
success: function( txnProgressWidget ) {
/* Register a click-handler for the just-rendered
* #closeManageServicesProgressWidgetLinkId.
*
* Don't worry about this being a double-registration - although
* it looks that way, it's not, because (an identical, but that's
* irrelevant, really) manageServicesProgressStatusMessage.success
* is re-rendered afresh each time through, and thus this
* click-handler must also be re-registered each time 'round.
*/
globalYui.one("#closeManageServicesProgressWidgetLinkId").on( "click", function(e) {
txnProgressWidget.hide();
});
/* Resume polling for information about the cluster's services. */
fetchClusterServicesPoller.start();
},
failure: function( txnProgressWidget ) {
/* <-------------------- REZXXX BEGIN -----------------------> */
/* Create the panel that'll display our error info. */
var errorInfoPanel =
createInformationalPanel( '#informationalPanelContainerDivId', 'Operation Logs' );
/* Prime the panel to start off showing our stock loading image. */
var errorInfoPanelBodyContent =
'
';
/* Make the call to our backend to fetch the report for this txnId. */
globalYui.io('../php/frontend/fetchTxnLogs.php?clusterName=' +
txnProgressWidget.txnProgressContext.clusterName + '&txnId=' + txnProgressWidget.txnProgressContext.txnId, {
timeout: 10000,
on: {
success: function (x,o) {
globalYui.log("RAW JSON DATA: " + o.responseText);
var errorInfoJson = null;
// Process the JSON data returned from the server
try {
errorInfoJson = globalYui.JSON.parse(o.responseText);
}
catch (e) {
alert("JSON Parse failed!");
return;
}
/* TODO XXX Remove some of the noise from this to allow
* for better corelation - for now, just dump a
* pretty-printed version of the returned JSON.
*/
errorInfoPanelBodyContent =
'' +
globalYui.JSON.stringify( errorInfoJson.logs, null, 4 ) +
'
';
/* Update the contents of errorInfoPanel (which was, till
* now, showing the loading image).
*/
errorInfoPanel.set( 'bodyContent', errorInfoPanelBodyContent );
},
failure: function (x,o) {
alert("Async call failed!");
}
}
});
var firstTimeShowingErrorInfoPanel = true;
/* Register a click-handler for #showManageServicesTxnLogsLinkId
* to render the contents inside errorInfoPanel (and make it visible).
*/
globalYui.one("#showManageServicesTxnLogsLinkId").on( "click", function(e) {
errorInfoPanel.set( 'bodyContent', errorInfoPanelBodyContent );
errorInfoPanel.show();
if( firstTimeShowingErrorInfoPanel ) {
globalYui.one('#txnProgressStatusActionsDivId').setContent(
'' +
'Close' +
'' );
globalYui.one("#closeManageServicesProgressWidgetLinkId").on( "click", function(e) {
txnProgressWidget.hide();
});
firstTimeShowingErrorInfoPanel = false;
}
});
/* <--------------------- REZXXX END ------------------------> */
/* Resume polling for information about the cluster's services. */
/* TODO XXX Move this into the click handler for closing the widget after the first show of the panel... */
fetchClusterServicesPoller.start();
}
};
var manageServicesProgressWidget = new TxnProgressWidget
( manageServicesResponseJson, manageServicesProgressStatusMessage, manageServicesProgressPostCompletionFixup );
/* And now that confirmationDataPanel is hidden, show manageServicesProgressWidget. */
manageServicesProgressWidget.show();
}
else {
/* No need to hide confirmationDataPanel here - there are errors
* that need to be handled.
*/
if (action == 'reconfigure') {
hidePanel(function() {
setupReconfigureFirstScreen(serviceName);
showPanel( function() {
handleConfigureServiceErrors( manageServicesResponseJson );
});
});
} else {
// Can't do anything for others
alert('Got error during ' + action + ' : ' + globalYui.Lang.dump(manageServicesResponseJson));
}
}
},
failure: function(x, o) {
alert("Async call failed!");
}
}
});
}
function getServiceConfigurationMarkup( serviceConfigurationData ) {
return serviceConfigurationMarkup;
}
function serviceManagementActionClickHandler( action, serviceName ) {
// Reinit the global content
confirmationDataPanelBodyContent = '';
var confirmationDataPanelTitle = ''; // Set title later
/* Create the panel that'll display our confirmation/data dialog. */
confirmationDataPanel =
createInformationalPanel( '#informationalPanelContainerDivId', confirmationDataPanelTitle );
panelYesButton = {
value: 'OK',
action: function (e) {
e.preventDefault();
performServiceManagement( action, serviceName, confirmationDataPanel );
},
classNames: 'okButton',
section: 'footer'
};
if ( action == 'start') {
setupStartServiceScreen(serviceName);
} else if ( action == 'stop') {
setupStopServiceScreen(serviceName);
} else if( action == 'startAll' ) {
setupStartAllServicesScreen();
} else if( action == 'stopAll' ) {
setupStopAllServicesScreen();
} else if( action == 'reconfigure' ) {
setupReconfigureScreens(serviceName);
}
}
function deduceServiceManagementEntryCssClass( serviceInfo ) {
var serviceManagementEntryCssClass = '';
var serviceState = serviceInfo.state;
if( serviceState.match(/^stop/i) || serviceState.match(/^fail/i) ) {
serviceManagementEntryCssClass = "serviceManagementEntryStopped";
}
else if( serviceState.match(/^start/i) ) {
serviceManagementEntryCssClass = "serviceManagementEntryStarted";
}
else if( serviceState.match(/^install/i) ) {
serviceManagementEntryCssClass = "serviceManagementEntryInstalled";
}
else if( serviceState.match(/^uninstall/i) ) {
serviceManagementEntryCssClass = "serviceManagementEntryUninstalled";
}
// globalYui.log( "Picking CSS class for" + serviceInfo.serviceName + ": " + serviceManagementEntryCssClass );
return serviceManagementEntryCssClass;
}
function generateServiceManagementEntryMarkup( serviceName, serviceInfo ) {
var generatedServiceManagementEntryMarkup = '';
var serviceAttributes = serviceInfo.attributes;
/* Only generate a Service Management entry for services that are:
*
* a) enabled
* b) runnable
* c) meant to be displayed
*/
if( (serviceInfo.isEnabled == true) && !serviceAttributes.noDisplay ) {
var serviceManagementEntryCssClass = deduceServiceManagementEntryCssClass( serviceInfo );
generatedServiceManagementEntryMarkup +=
'' +
'' +
'
' +
'' +
serviceInfo.displayName +
'' +
'' +
'
' +
titleCase(serviceInfo.state) +
'
' +
'
';
if( serviceAttributes.runnable ) {
var serviceManagementEntryAnchorName = '';
var serviceManagementEntryAnchorTitle = '';
var serviceManagementEntryAnchorCssClasses = 'serviceManagementEntryAction btn ';
var serviceManagementEntryIconCssClass = '';
/* Already-started/stopped services shouldn't allow a start/stop operation on them. */
if( serviceInfo.state == 'STOPPED' || serviceInfo.state == 'FAILED') {
serviceManagementEntryAnchorName = 'start';
serviceManagementEntryAnchorTitle = 'Start';
serviceManagementEntryAnchorCssClasses += 'serviceManagementEntryActionStart';
serviceManagementEntryIconCssClass = 'iconic-play';
}
else if ( serviceInfo.state == 'STARTED' ) {
serviceManagementEntryAnchorName = 'stop';
serviceManagementEntryAnchorTitle = 'Stop';
serviceManagementEntryAnchorCssClasses += 'serviceManagementEntryActionStop';
serviceManagementEntryIconCssClass = 'iconic-stop';
}
generatedServiceManagementEntryMarkup +=
'
';
}
generatedServiceManagementEntryMarkup +=
'
' +
'
' +
'
' +
'';
}
return generatedServiceManagementEntryMarkup;
}
// Do Not Remove --> We'll uncomment this section when the Service names link to something meaningful.
//
// /* Register click handlers for the service links themselves. */
// globalYui.one('#serviceManagementListId').delegate('click', function (e) {
// alert(this.getAttribute('name'));
// }, 'li.serviceManagementEntry span.serviceManagementEntryNameContainer a.serviceManagementEntryName' );
/* Register click handlers for the global-action buttons. */
globalYui.one('#serviceManagementGlobalActionsDivId').delegate('click', function (e) {
var action = this.getAttribute('name');
serviceManagementActionClickHandler( action );
}, 'button' );
/* Register click handlers for the action links for each service. */
globalYui.one('#serviceManagementListId').delegate('click', function (e) {
var action = this.getAttribute('name');
var serviceName = this.ancestor('li.serviceManagementEntry').
one('span.serviceManagementEntryNameContainer a.serviceManagementEntryName').getAttribute('name');
serviceManagementActionClickHandler( action, serviceName );
}, 'li.serviceManagementEntry div.serviceManagementEntryActionsContainer a.serviceManagementEntryAction' );
/* Main() */
/* The clusterName variable is set in the Javascript scaffolding spit out by manageServices.php */
var fetchClusterServicesPollerContext = {
source: '../php/frontend/fetchClusterServices.php',
schema: {
metaFields: {
services: 'response.services'
}
},
request: '?clusterName=' + clusterName,
/* TODO XXX Change this from 5 seconds to 1 minute. */
pollInterval: 5000,
maxFailedAttempts: 5
};
var fetchClusterServicesPollerResponseHandler = {
success: function (e, pdp) {
/* Clear the screen of the loading image (in case it's currently showing). */
hideLoadingImg();
/* The data from our backend. */
clusterServices = e.response.meta.services;
/* What we're here to render. */
var serviceManagementMarkup = '';
// Separate block for client-only software
var clientOnlySoftwareMarkup = '';
for (var serviceName in clusterServices) {
var serviceInfo = clusterServices[serviceName];
if (clusterServices.hasOwnProperty(serviceName) && !serviceInfo.attributes.runnable) {
clientOnlySoftwareMarkup += generateServiceManagementEntryMarkup( serviceName, serviceInfo );
}
}
if (clientOnlySoftwareMarkup != '') {
serviceManagementMarkup += 'Client-only software
';
serviceManagementMarkup += clientOnlySoftwareMarkup;
serviceManagementMarkup += '
';
}
// Real services with server side components
serviceManagementMarkup += 'Long-running services
';
for (var serviceName in clusterServices) {
var serviceInfo = clusterServices[serviceName];
if (clusterServices.hasOwnProperty(serviceName) && serviceInfo.attributes.runnable) {
serviceManagementMarkup += generateServiceManagementEntryMarkup( serviceName, serviceInfo );
}
}
serviceManagementMarkup += '
';
/* Link the newly-generated serviceManagementMarkup into the DOM. */
globalYui.one("#serviceManagementDynamicRenderDivId").setContent( serviceManagementMarkup );
/* If serviceManagementMarkup is non-empty, unveil the contents of
* #serviceManagementGlobalActionsDivId (which contains the StartAll
* and StopAll buttons) as well.
*/
if( globalYui.Lang.trim( serviceManagementMarkup ).length > 0 ) {
globalYui.one("#serviceManagementGlobalActionsDivId").setStyle( 'display', 'block' );
}
},
failure: function (e, pdp) {
/* Clear the screen of the loading image (in case it's currently showing). */
hideLoadingImg();
alert('Failed to fetch cluster services!');
}
};
fetchClusterServicesPoller = new PeriodicDataPoller
( fetchClusterServicesPollerContext, fetchClusterServicesPollerResponseHandler );
/* Kick the polling loop off. */
fetchClusterServicesPoller.start();