manageServices.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /* Declarations of global data. */
  2. var fetchClusterServicesPoller;
  3. var clusterServices;
  4. function performServiceManagement( action, serviceName, confirmationDataPanel ) {
  5. /* First, (temporarily) stop any further fetches. */
  6. fetchClusterServicesPoller.stop();
  7. var manageServicesRequestData = {
  8. action: action,
  9. services: {}
  10. };
  11. if( action == "reconfigure" ) {
  12. manageServicesRequestData.services = generateUserOpts();
  13. }
  14. else {
  15. /* Need to explicitly set a key named for serviceName this way because it's
  16. * a variable - in the future, the value will be a filled-out array (for
  17. * now, we only support managing a single service at a time).
  18. */
  19. manageServicesRequestData.services[serviceName] = {};
  20. }
  21. globalYui.io( "../php/frontend/manageServices.php?clusterName=" + clusterName, {
  22. method: 'POST',
  23. data: globalYui.JSON.stringify(manageServicesRequestData),
  24. timeout: 10000,
  25. on: {
  26. success: function(x, o) {
  27. globalYui.log("RAW JSON DATA: " + o.responseText);
  28. var manageServicesResponseJson;
  29. try {
  30. manageServicesResponseJson = globalYui.JSON.parse(o.responseText);
  31. }
  32. catch (e) {
  33. alert("JSON Parse failed!");
  34. return;
  35. }
  36. globalYui.log(globalYui.Lang.dump(manageServicesResponseJson));
  37. /* Check that manageServicesResponseJson actually indicates success. */
  38. if( manageServicesResponseJson.result == 0 ) {
  39. /* Only on success should we destroy confirmationDataPanel - on
  40. * failure, we depend on the fact that there'll be errors shown
  41. * inside the panel that the user will want/need to interact with.
  42. */
  43. destroyInformationalPanel(confirmationDataPanel);
  44. var manageServicesProgressStatusMessage = {
  45. success:
  46. '<p>' +
  47. 'Yabba Dabba Doo! Manage services ' +
  48. '<a href="javascript:void(null)" id=closeManageServicesProgressWidgetLinkId>' +
  49. 'even harder' +
  50. '</a>' +
  51. '?' +
  52. '</p>',
  53. failure:
  54. '<p>' +
  55. 'Scooby Doo, where are you? Perhaps in the ' +
  56. '<a href="javascript:void(null)" id=showManageServicesTxnLogsLinkId>Operation Logs</a>' +
  57. '?' +
  58. '</p>'
  59. };
  60. var manageServicesProgressPostCompletionFixup = {
  61. success: function( txnProgressWidget ) {
  62. /* Register a click-handler for the just-rendered
  63. * #closeManageServicesProgressWidgetLinkId.
  64. *
  65. * Don't worry about this being a double-registration - although
  66. * it looks that way, it's not, because (an identical, but that's
  67. * irrelevant, really) manageServicesProgressStatusMessage.success
  68. * is re-rendered afresh each time through, and thus this
  69. * click-handler must also be re-registered each time 'round.
  70. */
  71. globalYui.one("#closeManageServicesProgressWidgetLinkId").on( "click", function(e) {
  72. txnProgressWidget.hide();
  73. });
  74. /* Resume polling for information about the cluster's services. */
  75. fetchClusterServicesPoller.start();
  76. },
  77. failure: function( txnProgressWidget ) {
  78. /* <-------------------- REZXXX BEGIN -----------------------> */
  79. /* Create the panel that'll display our error info. */
  80. var errorInfoPanel =
  81. createInformationalPanel( '#informationalPanelContainerDivId', 'Operation Logs' );
  82. /* Prime the panel to start off showing our stock loading image. */
  83. var errorInfoPanelBodyContent =
  84. '<img id=errorInfoPanelLoadingImgId class=loadingImg src=../images/loading.gif />';
  85. /* Make the call to our backend to fetch the report for this txnId. */
  86. globalYui.io('../php/frontend/fetchTxnLogs.php?clusterName=' +
  87. txnProgressWidget.txnProgressContext.clusterName + '&txnId=' + txnProgressWidget.txnProgressContext.txnId, {
  88. timeout: 10000,
  89. on: {
  90. success: function (x,o) {
  91. globalYui.log("RAW JSON DATA: " + o.responseText);
  92. var errorInfoJson = null;
  93. // Process the JSON data returned from the server
  94. try {
  95. errorInfoJson = globalYui.JSON.parse(o.responseText);
  96. }
  97. catch (e) {
  98. alert("JSON Parse failed!");
  99. return;
  100. }
  101. /* TODO XXX Remove some of the noise from this to allow
  102. * for better corelation - for now, just dump a
  103. * pretty-printed version of the returned JSON.
  104. */
  105. errorInfoPanelBodyContent =
  106. '<pre>' +
  107. globalYui.JSON.stringify( errorInfoJson.logs, null, 4 ) +
  108. '</pre>';
  109. /* Update the contents of errorInfoPanel (which was, till
  110. * now, showing the loading image).
  111. */
  112. errorInfoPanel.set( 'bodyContent', errorInfoPanelBodyContent );
  113. },
  114. failure: function (x,o) {
  115. alert("Async call failed!");
  116. }
  117. }
  118. });
  119. var firstTimeShowingErrorInfoPanel = true;
  120. /* Register a click-handler for #showManageServicesTxnLogsLinkId
  121. * to render the contents inside errorInfoPanel (and make it visible).
  122. */
  123. globalYui.one("#showManageServicesTxnLogsLinkId").on( "click", function(e) {
  124. errorInfoPanel.set( 'bodyContent', errorInfoPanelBodyContent );
  125. errorInfoPanel.show();
  126. if( firstTimeShowingErrorInfoPanel ) {
  127. globalYui.one('#txnProgressStatusActionsDivId').setContent(
  128. '<a href="javascript:void(null)" id=closeManageServicesProgressWidgetLinkId>' +
  129. 'Close' +
  130. '</a>' );
  131. globalYui.one("#closeManageServicesProgressWidgetLinkId").on( "click", function(e) {
  132. txnProgressWidget.hide();
  133. });
  134. firstTimeShowingErrorInfoPanel = false;
  135. }
  136. });
  137. /* <--------------------- REZXXX END ------------------------> */
  138. /* Resume polling for information about the cluster's services. */
  139. /* TODO XXX Move this into the click handler for closing the widget after the first show of the panel... */
  140. fetchClusterServicesPoller.start();
  141. }
  142. };
  143. var manageServicesProgressWidget = new TxnProgressWidget
  144. ( manageServicesResponseJson, manageServicesProgressStatusMessage, manageServicesProgressPostCompletionFixup );
  145. /* And now that confirmationDataPanel is hidden, show manageServicesProgressWidget. */
  146. manageServicesProgressWidget.show();
  147. }
  148. else {
  149. /* No need to hide confirmationDataPanel here - there are errors
  150. * that need to be handled.
  151. */
  152. handleConfigureServiceErrors( manageServicesResponseJson );
  153. }
  154. },
  155. failure: function(x, o) {
  156. alert("Async call failed!");
  157. }
  158. }
  159. });
  160. }
  161. function getServiceConfigurationMarkup( serviceConfigurationData ) {
  162. return serviceConfigurationMarkup;
  163. }
  164. function serviceManagementActionClickHandler( action, serviceName ) {
  165. var affectedServices = [];
  166. var confirmationDataPanelTitle = '';
  167. var confirmationDataPanelBodyContent = '';
  168. var confirmationDataPanelWidth = 800;
  169. var confirmationDataPanelHeight = 400;
  170. var confirmationDataPanel;
  171. var confirmationDataPanelNoButton = {
  172. value: 'Cancel',
  173. action: function (e) {
  174. e.preventDefault();
  175. destroyInformationalPanel(confirmationDataPanel);
  176. },
  177. section: 'footer'
  178. };
  179. var confirmationDataPanelYesPanel = {
  180. value: 'OK',
  181. action: function (e) {
  182. e.preventDefault();
  183. performServiceManagement( action, serviceName, confirmationDataPanel );
  184. },
  185. classNames: 'yo',
  186. section: 'footer'
  187. };
  188. if( action == 'reconfigure' ) {
  189. confirmationDataPanelTitle = 'Reconfigure ' + serviceName + ' (will stop and start given service and services that depend on it)';
  190. confirmationDataPanelBodyContent =
  191. "<img id=errorInfoPanelLoadingImgId class=loadingImg src=../images/loading.gif />";
  192. executeStage( '../php/frontend/fetchClusterServices.php?clusterName=' + clusterName +
  193. '&getConfigs=true&serviceName=' + serviceName, function (serviceConfigurationData) {
  194. var serviceConfigurationMarkup = constructDOM( serviceConfigurationData );
  195. if( globalYui.Lang.trim( serviceConfigurationMarkup).length == 0 ) {
  196. serviceConfigurationMarkup = '<p>Move along folks, nothing to see here...</p>';
  197. }
  198. else {
  199. /* Augment confirmationDataPanel with the relevant buttons only if there
  200. * is something of value to show.
  201. */
  202. confirmationDataPanel.addButton( confirmationDataPanelNoButton );
  203. confirmationDataPanel.addButton( confirmationDataPanelYesPanel );
  204. }
  205. /* XXX Note that this must be kept in-sync with the corresponding markup
  206. * on the InstallationWizard page.
  207. */
  208. confirmationDataPanelBodyContent =
  209. '<div id=formStatusDivId class=formStatusBar style="visibility:hidden">'+
  210. 'Placeholder' +
  211. '</div>' +
  212. '<br/>' +
  213. '<div id=configureClusterAdvancedCoreDivId>' +
  214. '<form id=configureClusterAdvancedFormId>' +
  215. '<fieldset id=configureClusterAdvancedFieldSetId>' +
  216. '<div id=configureClusterAdvancedDynamicRenderDivId>' +
  217. serviceConfigurationMarkup +
  218. '</div>' +
  219. '</fieldset>' +
  220. '</form>' +
  221. '</div>';
  222. confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent );
  223. });
  224. confirmationDataPanelWidth = 1000;
  225. confirmationDataPanelHeight = 500;
  226. }
  227. else if( action == 'start' ) {
  228. var serviceDisplayName = clusterServices[serviceName].displayName;
  229. confirmationDataPanelTitle = 'Starting ' + serviceDisplayName;
  230. confirmationDataPanelBodyContent = "We are now going to start " + serviceDisplayName + "..<br/><br/>";
  231. affectedServices = clusterServices[serviceName].dependencies;
  232. }
  233. else if( action == 'stop' ) {
  234. var serviceDisplayName = clusterServices[serviceName].displayName;
  235. confirmationDataPanelTitle = 'Stopping ' + serviceDisplayName;
  236. confirmationDataPanelBodyContent = "We are now going to stop " + serviceDisplayName + "..<br/><br/>";
  237. affectedServices = clusterServices[serviceName].dependents;
  238. }
  239. else if( action == 'startAll' ) {
  240. confirmationDataPanelTitle = 'Start All Services';
  241. confirmationDataPanelBodyContent = "We are now going to start all services in the cluster";
  242. }
  243. else if( action == 'stopAll' ) {
  244. confirmationDataPanelTitle = 'Stop All Services';
  245. confirmationDataPanelBodyContent = "We are now going to stop all the services in the cluster";
  246. }
  247. // Add the list of dependencies
  248. if(action =='start' || action == 'stop') {
  249. // Clean up the affected-services list to only include appropriate installed long-running services
  250. var deps = affectedServices;
  251. affectedServices = [];
  252. for (dep in deps) {
  253. var svc = deps[dep];
  254. if (clusterServices.hasOwnProperty(svc) && (clusterServices[svc].isEnabled == 1) && clusterServices[svc].attributes.runnable ) {
  255. affectedServices.push(svc);
  256. }
  257. }
  258. var dependencyMarkup = "";
  259. for (affectedSrvc in affectedServices) {
  260. if (clusterServices[affectedServices[affectedSrvc]].attributes.runnable) {
  261. dependencyMarkup += '<tr><td>' + clusterServices[affectedServices[affectedSrvc]].displayName + '</td><td>' + titleCase(clusterServices[affectedServices[affectedSrvc]].state) + '</td></tr>';
  262. }
  263. }
  264. if (dependencyMarkup != '') {
  265. // Add this service at the top of the list
  266. dependencyMarkup = '<table><thead><th>Service name</th><th>Current state</th></thead><tr><td>' + serviceDisplayName + '</td><td>' + titleCase(clusterServices[serviceName].state) + '</td></tr>' + dependencyMarkup + '</table>';
  267. confirmationDataPanelBodyContent += 'Including this service and all its recursive dependencies, the following is the list of services that we will ' + action + ':' +
  268. '<br/>' +
  269. '<div id="manageServicesDisplayDepsOnAction">' +
  270. dependencyMarkup +
  271. '</div>';
  272. }
  273. }
  274. confirmationDataPanelBodyContent = '<div id="confirmationDataPanelBodyContent">' + confirmationDataPanelBodyContent + '</div>';
  275. /* Create the panel that'll display our confirmation/data dialog. */
  276. confirmationDataPanel =
  277. createInformationalPanel( '#informationalPanelContainerDivId', confirmationDataPanelTitle );
  278. confirmationDataPanel.set( 'height', confirmationDataPanelHeight );
  279. confirmationDataPanel.set( 'width', confirmationDataPanelWidth );
  280. confirmationDataPanel.set( 'bodyContent', confirmationDataPanelBodyContent );
  281. /* Augment confirmationDataPanel with the relevant buttons unconditionally in
  282. * the case of all actions other than "reconfigure" - for "reconfigure", we have
  283. * special logic above.
  284. */
  285. if( action != 'reconfigure' ) {
  286. confirmationDataPanel.addButton( confirmationDataPanelNoButton );
  287. confirmationDataPanel.addButton( confirmationDataPanelYesPanel );
  288. }
  289. confirmationDataPanel.show();
  290. }
  291. function deduceServiceManagementEntryCssClass( serviceInfo ) {
  292. var serviceManagementEntryCssClass = '';
  293. var serviceState = serviceInfo.state;
  294. if( serviceState.match(/^stop/i) || serviceState.match(/^fail/i) ) {
  295. serviceManagementEntryCssClass = "serviceManagementEntryStopped";
  296. }
  297. else if( serviceState.match(/^start/i) ) {
  298. serviceManagementEntryCssClass = "serviceManagementEntryStarted";
  299. }
  300. else if( serviceState.match(/^install/i) ) {
  301. serviceManagementEntryCssClass = "serviceManagementEntryInstalled";
  302. }
  303. else if( serviceState.match(/^uninstall/i) ) {
  304. serviceManagementEntryCssClass = "serviceManagementEntryUninstalled";
  305. }
  306. // globalYui.log( "Picking CSS class for" + serviceInfo.serviceName + ": " + serviceManagementEntryCssClass );
  307. return serviceManagementEntryCssClass;
  308. }
  309. function generateServiceManagementEntryMarkup( serviceName, serviceInfo ) {
  310. var generatedServiceManagementEntryMarkup = '';
  311. var serviceAttributes = serviceInfo.attributes;
  312. /* Only generate a Service Management entry for services that are:
  313. *
  314. * a) enabled
  315. * b) runnable
  316. * c) meant to be displayed
  317. */
  318. if( (serviceInfo.isEnabled == true) && !serviceAttributes.noDisplay ) {
  319. var serviceManagementEntryCssClass = deduceServiceManagementEntryCssClass( serviceInfo );
  320. generatedServiceManagementEntryMarkup +=
  321. '<li class="serviceManagementEntry '+ serviceManagementEntryCssClass + '">' +
  322. '<div>' +
  323. '<span class="serviceManagementEntryNameContainer">' +
  324. '<a href="javascript:void(null)" name="' + serviceName + '" class="serviceManagementEntryName">' +
  325. serviceInfo.displayName +
  326. '</a>' +
  327. '</span>' +
  328. '<div class="serviceManagementEntryStateContainer">' +
  329. titleCase(serviceInfo.state) +
  330. '</div>' +
  331. '<div class="serviceManagementEntryActionsContainer">';
  332. if( serviceAttributes.runnable ) {
  333. generatedServiceManagementEntryMarkup +=
  334. '<a href="javascript:void(null)" name="start" title="Start" ' +
  335. 'class="btn serviceManagementEntryAction serviceManagementEntryActionStart"><i class="iconic-play"></i></a>' +
  336. '<a href="javascript:void(null)" name="stop" title="Stop" ' +
  337. 'class="btn serviceManagementEntryAction serviceManagementEntryActionStop"><i class="iconic-stop"></i></a>';
  338. }
  339. generatedServiceManagementEntryMarkup +=
  340. '<a href="javascript:void(null)" name="reconfigure" title="Reconfigure" ' +
  341. 'class="btn serviceManagementEntryAction serviceManagementEntryActionReconfigure"><i class="iconic-cog"></i></a>' +
  342. '</div>' +
  343. '</div>' +
  344. '</li>';
  345. }
  346. return generatedServiceManagementEntryMarkup;
  347. }
  348. // Do Not Remove --> We'll uncomment this section when the Service names link to something meaningful.
  349. //
  350. // /* Register click handlers for the service links themselves. */
  351. // globalYui.one('#serviceManagementListId').delegate('click', function (e) {
  352. // alert(this.getAttribute('name'));
  353. // }, 'li.serviceManagementEntry span.serviceManagementEntryNameContainer a.serviceManagementEntryName' );
  354. /* Register click handlers for the global-action buttons. */
  355. globalYui.one('#serviceManagementGlobalActionsDivId').delegate('click', function (e) {
  356. var action = this.getAttribute('name');
  357. serviceManagementActionClickHandler( action );
  358. }, 'button' );
  359. /* Register click handlers for the action links for each service. */
  360. globalYui.one('#serviceManagementListId').delegate('click', function (e) {
  361. var action = this.getAttribute('name');
  362. var serviceName = this.ancestor('li.serviceManagementEntry').
  363. one('span.serviceManagementEntryNameContainer a.serviceManagementEntryName').getAttribute('name');
  364. serviceManagementActionClickHandler( action, serviceName );
  365. }, 'li.serviceManagementEntry div.serviceManagementEntryActionsContainer a.serviceManagementEntryAction' );
  366. /* Main() */
  367. /* The clusterName variable is set in the Javascript scaffolding spit out by manageServices.php */
  368. var fetchClusterServicesPollerContext = {
  369. source: '../php/frontend/fetchClusterServices.php',
  370. schema: {
  371. metaFields: {
  372. services: 'response.services'
  373. }
  374. },
  375. request: '?clusterName=' + clusterName,
  376. /* TODO XXX Change this from 5 seconds to 1 minute. */
  377. pollInterval: 5000,
  378. maxFailedAttempts: 5
  379. };
  380. var fetchClusterServicesPollerResponseHandler = {
  381. success: function (e, pdp) {
  382. /* Clear the screen of the loading image (in case it's currently showing). */
  383. hideLoadingImg();
  384. /* The data from our backend. */
  385. clusterServices = e.response.meta.services;
  386. /* What we're here to render. */
  387. var serviceManagementMarkup = '';
  388. // Separate block for client-only software
  389. var clientOnlySoftwareMarkup = '';
  390. for (var serviceName in clusterServices) {
  391. var serviceInfo = clusterServices[serviceName];
  392. if (clusterServices.hasOwnProperty(serviceName) && !serviceInfo.attributes.runnable) {
  393. clientOnlySoftwareMarkup += generateServiceManagementEntryMarkup( serviceName, serviceInfo );
  394. }
  395. }
  396. if (clientOnlySoftwareMarkup != '') {
  397. serviceManagementMarkup += '<div class="serviceManagementGroup"> Client-only software: <br/>';
  398. serviceManagementGroup += clientOnlySoftwareMarkup;
  399. serviceManagementMarkup += '</div>';
  400. }
  401. // Real services with server side components
  402. serviceManagementMarkup += '<div class="serviceManagementGroup"> Long running services: <br/><ul>';
  403. for (var serviceName in clusterServices) {
  404. var serviceInfo = clusterServices[serviceName];
  405. if (clusterServices.hasOwnProperty(serviceName) && serviceInfo.attributes.runnable) {
  406. serviceManagementMarkup += generateServiceManagementEntryMarkup( serviceName, serviceInfo );
  407. }
  408. }
  409. serviceManagementMarkup += '</ul></div>';
  410. /* Link the newly-generated serviceManagementMarkup into the DOM. */
  411. globalYui.one("#serviceManagementDynamicRenderDivId").setContent( serviceManagementMarkup );
  412. /* If serviceManagementMarkup is non-empty, unveil the contents of
  413. * #serviceManagementGlobalActionsDivId (which contains the StartAll
  414. * and StopAll buttons) as well.
  415. */
  416. if( globalYui.Lang.trim( serviceManagementMarkup ).length > 0 ) {
  417. globalYui.one("#serviceManagementGlobalActionsDivId").setStyle( 'display', 'block' );
  418. }
  419. },
  420. failure: function (e, pdp) {
  421. /* Clear the screen of the loading image (in case it's currently showing). */
  422. hideLoadingImg();
  423. alert('Failed to fetch cluster services!');
  424. }
  425. };
  426. fetchClusterServicesPoller = new PeriodicDataPoller
  427. ( fetchClusterServicesPollerContext, fetchClusterServicesPollerResponseHandler );
  428. /* Kick the polling loop off. */
  429. fetchClusterServicesPoller.start();