prod_monitor.admin.inc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. <?php
  2. /**
  3. * Build status page.
  4. */
  5. function prod_monitor_status($id) {
  6. $site = _prod_monitor_get_site($id, TRUE);
  7. drupal_set_title(t('Production monitor status for') .' '. _prod_monitor_sanitize_url($site['url']));
  8. $functions = $site['settings']['functions'];
  9. $nodata = t('No data recieved yet.');
  10. $output = '';
  11. // General status block
  12. $modules = _prod_monitor_get_site_modules($id);
  13. if(!empty($modules) && isset($site['data']['prod_mon'])) {
  14. $prod_mon = $site['data']['prod_mon'];
  15. $output .= _prod_monitor_status_general($prod_mon, $modules);
  16. }
  17. unset($site['data']['prod_mon']);
  18. // Performance data not needed here.
  19. unset($site['data']['perf_data']);
  20. // Display results of all checks.
  21. foreach ($functions as $set => $data) {
  22. if (isset($site['data'][$set])) {
  23. $output .= '<h2>'.t($data['title']).'</h2>'."\n";
  24. $output .= '<div class="description"><p><em>'.t($data['description']).'</em></p></div>'."\n";
  25. if (!empty($site['data'][$set])) {
  26. $output .= theme('prod_monitor_status_report', array('requirements' => $site['data'][$set]));
  27. }
  28. else {
  29. $output .= '<p>'.$nodata.'</p><p>&nbsp;</p>';
  30. }
  31. }
  32. }
  33. if (empty($output)) {
  34. $output = '<p>'.$nodata.'</p><p>&nbsp;</p>';
  35. }
  36. // TODO: do not use drupal_render but change this so that hook_page_alter can
  37. // be used as well.
  38. $output .= drupal_render(drupal_get_form('_prod_monitor_update_data_form', $id, $site));
  39. return $output;
  40. }
  41. /**
  42. * Helper function to provide general status block on status overview page
  43. */
  44. function _prod_monitor_status_general($prod_mon, $modules) {
  45. // TODO: Should we hide the rows for which no data is being retrieved?
  46. $cron = t('Unknown');
  47. if (isset($prod_mon['prod_check_cron_last'])) {
  48. $cron = format_date($prod_mon['prod_check_cron_last'], 'large');
  49. }
  50. $updates = _prod_monitor_generate_updates_link($modules['id'], $modules['updates']);
  51. $output = '<h2>'.t('Overall status').'</h2>'."\n";
  52. $rows = array(
  53. array(
  54. array('data' => t('Drupal core version'), 'header' => TRUE),
  55. $modules['projects']['drupal']['info']['version'],
  56. ),
  57. array(
  58. array('data' => t('Last cron run'), 'header' => TRUE),
  59. $cron,
  60. ),
  61. array(
  62. array('data' => t('Update status'), 'header' => TRUE),
  63. $updates,
  64. ),
  65. );
  66. $output .= theme('table', array('header' => array(), 'rows' => $rows));
  67. return $output;
  68. }
  69. /**
  70. * Helper function to generate update status link for tables
  71. */
  72. function _prod_monitor_generate_updates_link($id, $update_status) {
  73. $updates = t('Unknown');
  74. if ($update_status > 0) {
  75. switch ($update_status) {
  76. case 1:
  77. $class = '';
  78. $title = t('None');
  79. break;
  80. case 2:
  81. $class = 'warning';
  82. $title = t('Available');
  83. break;
  84. case 3:
  85. $class = 'error';
  86. $title = t('Security risk!');
  87. break;
  88. }
  89. $updates = array('data' => '<strong>'.l($title, 'admin/reports/prod-monitor/site/'.$id.'/view/updates', array('attributes' => array('title' => $title, 'class' => $class))).'</strong>', 'class' => $class);
  90. }
  91. return $updates;
  92. }
  93. /**
  94. * Callback for performance page.
  95. */
  96. function prod_monitor_performance($data) {
  97. drupal_set_title(t('Performance logs for') .' '. _prod_monitor_get_url($data['id']));
  98. // TODO: add 'get stats now' button.
  99. //$site = _prod_monitor_get_site($id, TRUE);
  100. return array(
  101. 'performance_data' => array(
  102. '#data' => $data['data'],
  103. '#theme' => 'prod_monitor_performance',
  104. ),
  105. );
  106. }
  107. /**
  108. * Callback for module update status page
  109. */
  110. function prod_monitor_updates($modules) {
  111. $output = '';
  112. $id = $modules['id'];
  113. drupal_set_title(t('Module update status for') .' '. _prod_monitor_get_url($id));
  114. // Only show a report if the available updates have been fetched!
  115. if (!empty($modules) && !empty($modules['projects']) && !empty($modules['available'])) {
  116. module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.update');
  117. $data = _prod_monitor_calculate_project_data($id, $modules['projects'], $modules['available']);
  118. $output .= theme('prod_monitor_update_report', array('id' => $id, 'last' => $modules['lastupdate'], 'data' => $data));
  119. }
  120. // No data, so report this to the user and instruct him/her on how to retrieve it.
  121. else {
  122. $destination = drupal_get_destination();
  123. $output .= theme('prod_monitor_update_report',
  124. array(
  125. 'id' => $id,
  126. 'last' => $modules['lastupdate'],
  127. 'data' => t(
  128. 'No information is available about potential new releases for currently installed modules and themes. To check for updates, you may need to !cron or you can !check. Please note that checking for available updates can take a long time, so please be patient.',
  129. array(
  130. '!cron' => l(t('run cron'), 'admin/reports/status/run-cron', array('attributes' => array('title' => t('run cron')), 'query' => $destination)),
  131. '!check' => l(t('check manually'), 'admin/reports/prod-monitor/site/'.$id.'/update-check', array('attributes' => array('title' => t('check manually')))),
  132. )
  133. )
  134. )
  135. );
  136. }
  137. return $output;
  138. }
  139. /**
  140. * Callback to refresh the module update status page
  141. */
  142. function prod_monitor_updates_check($id) {
  143. // Get module data.
  144. $modules = _prod_monitor_get_site_modules($id);
  145. if (!empty($modules) && !empty($modules['projects'])) {
  146. module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.update');
  147. // ALWAYS do a full refresh.
  148. $result = _prod_monitor_update_refresh($id, $modules['projects'], $modules['sitekey']);
  149. if (!empty($result)) {
  150. drupal_set_message(t('Information about all available new releases and updates sucessfully fetched.'));
  151. }
  152. else {
  153. drupal_set_message(t('Failed to fetch all available new releases and updates!'), 'error');
  154. }
  155. }
  156. else {
  157. drupal_set_message(t('No module data available: cannot check for updates!'), 'error');
  158. }
  159. drupal_goto('admin/reports/prod-monitor/site/'.$id.'/view/updates');
  160. }
  161. /**
  162. * Build settings form.
  163. */
  164. function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
  165. drupal_set_title(t('Production monitor settings'));
  166. $base = drupal_get_path('module', 'prod_monitor');
  167. drupal_add_css($base.'/css/prod-monitor.css', 'file');
  168. drupal_add_js($base.'/js/jquery.equalheights.js', 'file');
  169. drupal_add_js($base.'/js/prod-monitor.js', 'file');
  170. $form = array();
  171. $collapsed = FALSE;
  172. if (!$edit) {
  173. // Add new site situation.
  174. $sites = _prod_monitor_get_sites();
  175. $api_key = $url = '';
  176. $options = array();
  177. $button = t('Get settings');
  178. if (!empty($sites)) {
  179. $form['table'] = array(
  180. '#markup' => _prod_monitor_overview_form_table($sites),
  181. );
  182. $collapsed = TRUE;
  183. }
  184. if (!empty($form_state['storage']['get_settings'])) {
  185. // Second step of add new site situation.
  186. $api_key = $form_state['values']['api_key'];
  187. $url = $form_state['values']['url'];
  188. $button = t('Add site');
  189. $collapsed = FALSE;
  190. $msg = TRUE;
  191. }
  192. }
  193. else {
  194. // Edit site situation.
  195. $site = _prod_monitor_get_site($edit);
  196. $url = $site['url'];
  197. // Allow user to correct faulty url.
  198. if (isset($form_state['values']['url']) && $url != $form_state['values']['url']) {
  199. $url = $form_state['values']['url'];
  200. }
  201. drupal_set_title(t('Production monitor settings for !url', array('!url' => _prod_monitor_sanitize_url($url))));
  202. $api_key = $site['settings']['api_key'];
  203. $options = $site['settings']['checks'];
  204. if (isset($site['settings']['checks']['perf_data'])) {
  205. $perf_enabled = $site['settings']['checks']['perf_data'];
  206. }
  207. $button = t('Save site');
  208. $msg = FALSE;
  209. }
  210. // Add/edit form.
  211. $form['sites'] = array(
  212. '#type' => 'fieldset',
  213. '#title' => t('Add a site to monitor'),
  214. '#description' => t('You can add sites here that you wish to monitor.'),
  215. '#collapsible' => TRUE,
  216. '#collapsed' => $collapsed,
  217. );
  218. $form['sites']['url'] = array(
  219. '#type' => 'textfield',
  220. '#title' => t('Website URL'),
  221. '#default_value' => $url,
  222. '#description' => t('Enter the <strong>full</strong> url of the website to be monitored. This site must be running the <em>Production check</em> module. This <strong>must</strong> include a protocol like <em>http://</em> or <em>https://</em>!'),
  223. '#size' => 60,
  224. '#required' => TRUE,
  225. );
  226. $form['sites']['api_key'] = array(
  227. '#type' => 'textfield',
  228. '#title' => t('The website\'s API key'),
  229. '#default_value' => $api_key,
  230. '#description' => t('Enter the API key you have configured for this site using the <em>Production check</em> module.'),
  231. '#size' => 60,
  232. '#maxlength' => 128,
  233. '#required' => TRUE,
  234. );
  235. // Only show on second step of add form or when editing.
  236. if (!empty($form_state['storage']['get_settings']) || $edit) {
  237. // Get the settings from the remote site. We always do this when the form is
  238. // displayed and don't store this locally. Logic here is that you won't be
  239. // editing these settings all that much.
  240. $functions = _prod_monitor_retrieve_functions($url, $api_key, $msg);
  241. // Parse the array of functions into a form.
  242. if ($functions) {
  243. // Save data to store in DB on submit.
  244. $form_state['storage']['functions'] = $functions;
  245. // Parse functions into form data
  246. $form['sites']['prod_check_settings'] = array(
  247. '#type' => 'fieldset',
  248. '#title' => t('Configure what data you wish to monitor for this site.'),
  249. '#collapsible' => TRUE,
  250. '#collapsed' => FALSE,
  251. );
  252. $form['sites']['prod_check_settings']['monitor_settings'] = array(
  253. '#type' => 'markup',
  254. '#prefix' => '<div id="prod-check-settings">',
  255. '#suffix' => '</div>',
  256. '#tree' => TRUE,
  257. );
  258. // Single out perf_data if present to treat differently.
  259. if (isset($functions['perf_data'])) {
  260. $performance = $functions['perf_data'];
  261. unset($functions['perf_data']);
  262. }
  263. $i = 1;
  264. foreach ($functions as $set => $data) {
  265. $rest = $i%2;
  266. $form['sites']['prod_check_settings']['monitor_settings'][$set] = array(
  267. '#type' => 'checkboxes',
  268. '#title' => t($data['title']),
  269. '#description' => t($data['description']),
  270. '#options' => $data['functions'],
  271. '#default_value' => array_keys($data['functions']),
  272. '#prefix' => '<div class="prod-check-settings '.(($rest) ? 'odd' : 'even').'">',
  273. '#suffix' => '</div>',
  274. );
  275. $i++;
  276. }
  277. if ($edit) {
  278. // Just to increase readability of the source code here.
  279. $monitor_settings = &$form['sites']['prod_check_settings']['monitor_settings'];
  280. // Set default values to last saved state
  281. foreach (element_children($monitor_settings) as $set) {
  282. if (isset($options[$set])) {
  283. $monitor_settings[$set]['#default_value'] = $options[$set];
  284. }
  285. else {
  286. // No settings available, so uncheck all.
  287. $monitor_settings[$set]['#default_value'] = array();
  288. }
  289. }
  290. }
  291. // Add performance logging section.
  292. if (!empty($performance['functions'])) {
  293. $form['sites']['prod_check_performance'] = array(
  294. '#type' => 'fieldset',
  295. '#title' => t("Configure which module's performance data you wish to log."),
  296. '#collapsible' => TRUE,
  297. '#collapsed' => FALSE,
  298. );
  299. $perf_options = array();
  300. foreach ($performance['functions'] as $function => $title) {
  301. $perf_options[$function] = t($title);
  302. }
  303. $form['sites']['prod_check_performance']['performance'] = array(
  304. '#type' => 'checkboxes',
  305. '#options' => $perf_options,
  306. '#default_value' => array(),
  307. '#description' => t('Indicate which performance data you want to store. This data can be provided by the modules listed here.'),
  308. );
  309. if ($edit && isset($perf_enabled)) {
  310. $form['sites']['prod_check_performance']['performance']['#default_value'] = $perf_enabled;
  311. }
  312. }
  313. $form['sites']['fetch'] = array(
  314. '#type' => 'checkbox',
  315. '#title' => t('Fetch data immediately'),
  316. '#default_value' => 0,
  317. '#description' => t('Will attempt to fetch the data immediately when the site has been added.'),
  318. );
  319. }
  320. else {
  321. // Error, so show retry button.
  322. $button = t('Retry');
  323. }
  324. }
  325. if ($edit) {
  326. $form['site_id'] = array(
  327. '#type' => 'value',
  328. '#value' => $edit,
  329. );
  330. }
  331. $form['sites']['submit'] = array(
  332. '#type' => 'submit',
  333. '#value' => $button,
  334. );
  335. return $form;
  336. }
  337. /**
  338. * Helper function to theme all sites into a table
  339. */
  340. function _prod_monitor_overview_form_table($sites) {
  341. $home = array('destination' => 'admin/reports/prod-monitor');
  342. // Set headers.
  343. $headers = array(
  344. t('URL'),
  345. t('Data'),
  346. t('Status'),
  347. t('Updates'),
  348. t('Date added'),
  349. t('Last update'),
  350. array('data' => t('Actions'), 'colspan' => 5),
  351. );
  352. // Compose rows.
  353. $rows = array();
  354. foreach ($sites as $id => $site_info) {
  355. // Set view and flush links.
  356. $view = t('View');
  357. $flush = t('Flush');
  358. if ($site_info['data']) {
  359. $view = l(t('View'), 'admin/reports/prod-monitor/site/'.$id, array('attributes' => array('title' => t('View'))));
  360. $flush = l(t('Flush'), 'admin/reports/prod-monitor/site/'.$id.'/flush', array('attributes' => array('title' => t('Flush'))));
  361. }
  362. $update_status = _prod_monitor_get_update_status($id);
  363. $updates = _prod_monitor_generate_updates_link($id, $update_status);
  364. if (!empty($site_info['status'])) {
  365. $title = t(ucwords($site_info['status']));
  366. $status = array('data' => '<strong>'.l($title, 'admin/reports/prod-monitor/site/'.$id, array('attributes' => array('title' => $title, 'class' => $site_info['status']))).'</strong>', 'class' => array($site_info['status']));
  367. }
  368. else {
  369. $status = '';
  370. }
  371. // Actually compose the rows.
  372. $row = array(
  373. 'data' => array(
  374. _prod_monitor_sanitize_url($site_info['url']),
  375. (!$site_info['data']) ? t('Not yet retrieved.') : t('Stored.'),
  376. $status,
  377. $updates,
  378. $site_info['added'],
  379. (!$site_info['lastupdate']) ? t('Not yet updated.') : $site_info['lastupdate'],
  380. /* Compose links. */
  381. $view,
  382. l(t('Edit'), 'admin/reports/prod-monitor/site/'.$id.'/edit', array('query' => $home, 'attributes' => array('title' => t('Edit')))),
  383. l(t('Fetch data'), 'admin/reports/prod-monitor/site/'.$id.'/fetch', array('attributes' => array('title' => t('Fetch & View')))),
  384. $flush,
  385. l(t('Delete'), 'admin/reports/prod-monitor/site/'.$id.'/delete', array('attributes' => array('title' => t('Delete')))),
  386. ),
  387. 'class' => array($site_info['status']),
  388. );
  389. $rows[] = $row;
  390. }
  391. return theme('table', array('header' => $headers, 'rows' => $rows));
  392. }
  393. /**
  394. * Validation function
  395. */
  396. function prod_monitor_overview_form_validate($form, &$form_state) {
  397. if (!preg_match('/^https?:\/\/.*/i', $form_state['values']['url'])) {
  398. form_set_error('url', t('The url must start with a valid protocol: either http:// or https://'));
  399. }
  400. }
  401. /**
  402. * Submit function
  403. */
  404. function prod_monitor_overview_form_submit($form, &$form_state) {
  405. switch ($form_state['values']['op']) {
  406. case t('Get settings'):
  407. case t('Retry'):
  408. // Make sure the storage is not empty so we go to step 2
  409. $form_state['storage']['get_settings'] = TRUE;
  410. $form_state['rebuild'] = TRUE;
  411. break;
  412. case t('Add site'):
  413. case t('Save site'):
  414. // Prevent from ending on step 2 again.
  415. unset($form_state['storage']['get_settings']);
  416. $site = new stdClass();
  417. // Edit situation, so force an update.
  418. if (isset($form_state['values']['site_id']) && is_numeric($form_state['values']['site_id'])) {
  419. $update = array('id');
  420. $site->id = $form_state['values']['site_id'];
  421. }
  422. else {
  423. // Add situation, insert.
  424. $update = array();
  425. $site->added = time();
  426. }
  427. $site->url = $form_state['values']['url'];
  428. // Get enabled checks.
  429. $checks = array();
  430. foreach ($form_state['values']['monitor_settings'] as $set => $data) {
  431. foreach ($data as $check => $value) {
  432. if ($value) {
  433. $checks[$set][] = $value;
  434. }
  435. }
  436. }
  437. // Get enabled performance logs.
  438. if (!empty($form_state['values']['performance'])) {
  439. $checks['perf_data'] = array();
  440. foreach ($form_state['values']['performance'] as $value) {
  441. if ($value) {
  442. $checks['perf_data'][] = $value;
  443. }
  444. }
  445. }
  446. // Prepare settings data.
  447. $site->settings = serialize(
  448. array(
  449. 'api_key' => $form_state['values']['api_key'],
  450. 'functions' => $form_state['storage']['functions'],
  451. 'checks' => $checks,
  452. )
  453. );
  454. $result = drupal_write_record('prod_monitor_sites', $site, $update);
  455. if ($result) {
  456. drupal_set_message(t('Website %url correctly saved.', array('%url' => $site->url)));
  457. if ($form_state['values']['fetch']) {
  458. $site_info = _prod_monitor_get_site($site->id, TRUE);
  459. _prod_monitor_retrieve_data($site->id, $site_info, TRUE);
  460. $form_state['redirect'] = 'admin/reports/prod-monitor/site/'.$site->id;
  461. }
  462. }
  463. else {
  464. drupal_set_message(t('Website %url not saved! Please try again.', array('%url' => $site->url)), 'error');
  465. }
  466. break;
  467. }
  468. }
  469. /**
  470. * Callback to fetch site data
  471. */
  472. function prod_monitor_fetch_data($id) {
  473. $site_info = _prod_monitor_get_site($id, TRUE);
  474. _prod_monitor_retrieve_data($id, $site_info, TRUE);
  475. drupal_goto('admin/reports/prod-monitor/site/'.$id);
  476. }
  477. /**
  478. * Form to delete a site's data
  479. */
  480. function prod_monitor_flush_form($form, &$form_state, $id) {
  481. $form = array();
  482. $form['site_id'] = array(
  483. '#type' => 'value',
  484. '#value' => $id,
  485. );
  486. $url = _prod_monitor_get_url($id);
  487. $form['url'] = array(
  488. '#type' => 'value',
  489. '#value' => $url,
  490. );
  491. return confirm_form($form, t('Are you sure you wish to delete all fetched data for %url?', array('%url' => $url)), 'admin/reports/prod-monitor', t('Note that the module update status data will not be flushed!').'<br />'.t('This action cannot be undone.'));
  492. }
  493. /**
  494. * Delete a site's data
  495. */
  496. function prod_monitor_flush_form_submit($form, &$form_state) {
  497. if ($form_state['values']['site_id']) {
  498. $result = _prod_monitor_flush_data($form_state['values']['site_id']);
  499. if ($result === FALSE) {
  500. drupal_set_message(t('Unable to flush data for %url!', array('%url' => $form_state['values']['url'])), 'error');
  501. }
  502. else {
  503. drupal_set_message(t('Stored data for %url successfully flushed.', array('%url' => $form_state['values']['url'])));
  504. }
  505. }
  506. $form_state['redirect'] = 'admin/reports/prod-monitor';
  507. }
  508. /**
  509. * Form to delete a site
  510. */
  511. function prod_monitor_delete_form($form, &$form_state, $id) {
  512. $form = array();
  513. $form['site_id'] = array(
  514. '#type' => 'value',
  515. '#value' => $id,
  516. );
  517. $url = _prod_monitor_get_url($id);
  518. $form['url'] = array(
  519. '#type' => 'value',
  520. '#value' => $url,
  521. );
  522. return confirm_form($form, t('Are you sure you wish to delete the website %url?', array('%url' => $url)), 'admin/reports/prod-monitor');
  523. }
  524. /**
  525. * Delete a site
  526. */
  527. function prod_monitor_delete_form_submit($form, &$form_state) {
  528. if ($form_state['values']['site_id']) {
  529. $result = _prod_monitor_delete_site($form_state['values']['site_id']);
  530. if ($result) {
  531. drupal_set_message(t('Website %url successfully deleted.', array('%url' => $form_state['values']['url'])));
  532. }
  533. else {
  534. drupal_set_message(t('Unable to delete %url!', array('%url' => $form_state['values']['url'])), 'error');
  535. }
  536. }
  537. $form_state['redirect'] = 'admin/reports/prod-monitor';
  538. }
  539. function _prod_monitor_update_data_form($form, $form_state, $id, $site_info) {
  540. $form['site_id'] = array(
  541. '#type' => 'value',
  542. '#value' => $id,
  543. );
  544. $form['site_info'] = array(
  545. '#type' => 'value',
  546. '#value' => $site_info,
  547. );
  548. // Markup field for proper styling.
  549. $form['buttons'] = array(
  550. '#type' => 'markup',
  551. '#prefix' => '<div class="buttons">',
  552. '#value' => '&nbsp;',
  553. '#suffix' => '</div>',
  554. );
  555. $form['buttons']['submit'] = array(
  556. '#type' => 'submit',
  557. '#value' => t('Fetch data now'),
  558. );
  559. return $form;
  560. }
  561. function _prod_monitor_update_data_form_submit($form, &$form_state) {
  562. _prod_monitor_retrieve_data($form_state['values']['site_id'], $form_state['values']['site_info'], TRUE);
  563. }