status.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. // Copyright (C) 2016 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Monitor the backup
  20. *
  21. * @copyright Copyright (C) 2016 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
  25. if (!defined('APPROOT')) require_once(__DIR__.'/../../approot.inc.php');
  26. require_once(APPROOT.'application/application.inc.php');
  27. require_once(APPROOT.'application/itopwebpage.class.inc.php');
  28. require_once(APPROOT.'application/startup.inc.php');
  29. require_once(APPROOT.'application/loginwebpage.class.inc.php');
  30. /////////////////////////////////////////////////////////////////////
  31. // Main program
  32. //
  33. LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
  34. //$sOperation = utils::ReadParam('operation', 'menu');
  35. //$oAppContext = new ApplicationContext();
  36. $oP = new iTopWebPage(Dict::S('bkp-status-title'));
  37. $oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/');
  38. try
  39. {
  40. $oP->add("<h1>".Dict::S('bkp-status-title')."</h1>");
  41. if (MetaModel::GetConfig()->Get('demo_mode'))
  42. {
  43. $oP->add("<div class=\"header_message message_info\">iTop is in <b>demonstration mode</b>: the feature is disabled.</div>");
  44. }
  45. $sImgOk = '<img src="../images/validation_ok.png"> ';
  46. $sImgError = '<img src="../images/validation_error.png"> ';
  47. $oP->add("<fieldset>");
  48. $oP->add("<legend>".Dict::S('bkp-status-checks')."</legend>");
  49. // Availability of mysqldump
  50. //
  51. $sMySQLBinDir = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', '');
  52. $sMySQLBinDir = utils::ReadParam('mysql_bindir', $sMySQLBinDir, true);
  53. if (empty($sMySQLBinDir))
  54. {
  55. $sMySQLDump = 'mysqldump';
  56. }
  57. else
  58. {
  59. //echo 'Info - Found mysql_bindir: '.$sMySQLBinDir;
  60. $sMySQLDump = '"'.$sMySQLBinDir.'/mysqldump"';
  61. }
  62. $sCommand = "$sMySQLDump -V 2>&1";
  63. $aOutput = array();
  64. $iRetCode = 0;
  65. exec($sCommand, $aOutput, $iRetCode);
  66. if ($iRetCode == 0)
  67. {
  68. $sMySqlDump = $sImgOk.Dict::Format("bkp-mysqldump-ok", $aOutput[0]);
  69. }
  70. elseif ($iRetCode == 1)
  71. {
  72. $sMySqlDump = $sImgError.Dict::Format("bkp-mysqldump-notfound", implode(' ', $aOutput));
  73. }
  74. else
  75. {
  76. $sMySqlDump = $sImgError.Dict::Format("bkp-mysqldump-issue", $iRetCode);
  77. }
  78. foreach($aOutput as $sLine)
  79. {
  80. //echo 'Info - mysqldump -V said: '.$sLine;
  81. }
  82. $oP->p($sMySqlDump);
  83. // Destination directory
  84. //
  85. // Make sure the target directory exists and is writeable
  86. $sBackupDir = APPROOT.'data/backups/';
  87. SetupUtils::builddir($sBackupDir);
  88. if (!is_dir($sBackupDir))
  89. {
  90. $oP->p($sImgError.Dict::Format('bkp-missing-dir', $sBackupDir));
  91. }
  92. else
  93. {
  94. $oP->p(Dict::Format('bkp-free-disk-space', SetupUtils::HumanReadableSize(SetupUtils::CheckDiskSpace($sBackupDir)), $sBackupDir));
  95. if (!is_writable($sBackupDir))
  96. {
  97. $oP->p($sImgError.Dict::Format('bkp-dir-not-writeable', $sBackupDir));
  98. }
  99. }
  100. $sBackupDirAuto = $sBackupDir.'auto/';
  101. SetupUtils::builddir($sBackupDirAuto);
  102. $sBackupDirManual = $sBackupDir.'manual/';
  103. SetupUtils::builddir($sBackupDirManual);
  104. // Wrong format
  105. //
  106. $sBackupFile = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'file_name_format', BACKUP_DEFAULT_FORMAT);
  107. $oBackup = new DBBackupScheduled();
  108. $sZipName = $oBackup->MakeName($sBackupFile);
  109. if ($sZipName == '')
  110. {
  111. $oP->p($sImgError.Dict::Format('bkp-wrong-format-spec', $sBackupFile, BACKUP_DEFAULT_FORMAT));
  112. }
  113. else
  114. {
  115. $oP->p(Dict::Format('bkp-name-sample', $sZipName));
  116. }
  117. // Week Days
  118. //
  119. $aWeekDayToString = array(
  120. 1 => Dict::S('DayOfWeek-Monday'),
  121. 2 => Dict::S('DayOfWeek-Tuesday'),
  122. 3 => Dict::S('DayOfWeek-Wednesday'),
  123. 4 => Dict::S('DayOfWeek-Thursday'),
  124. 5 => Dict::S('DayOfWeek-Friday'),
  125. 6 => Dict::S('DayOfWeek-Saturday'),
  126. 7 => Dict::S('DayOfWeek-Sunday')
  127. );
  128. $aDayLabels = array();
  129. $oBackupExec = new BackupExec();
  130. foreach ($oBackupExec->InterpretWeekDays() as $iDay)
  131. {
  132. $aDayLabels[] = $aWeekDayToString[$iDay];
  133. }
  134. $sDays = implode(', ', $aDayLabels);
  135. $sBackupTime = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'time', '23:30');
  136. $oP->p(Dict::Format('bkp-week-days', $sDays, $sBackupTime));
  137. $iRetention = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'retention_count', 5);
  138. $oP->p(Dict::Format('bkp-retention', $iRetention));
  139. $oP->add("</fieldset>");
  140. // List of backups
  141. //
  142. $aFiles = $oBackup->ListFiles($sBackupDirAuto);
  143. $aFilesToDelete = array();
  144. while (count($aFiles) > $iRetention - 1)
  145. {
  146. $aFilesToDelete[] = array_shift($aFiles);
  147. }
  148. $oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
  149. if ($oRestoreMutex->IsLocked())
  150. {
  151. $sDisableRestore = 'disabled="disabled"';
  152. }
  153. else
  154. {
  155. $sDisableRestore = '';
  156. }
  157. // 1st table: list the backups made in the background
  158. //
  159. $aDetails = array();
  160. foreach ($oBackup->ListFiles($sBackupDirAuto) as $sBackupFile)
  161. {
  162. $sFileName = basename($sBackupFile);
  163. $sFilePath = 'auto/'.$sFileName;
  164. if (MetaModel::GetConfig()->Get('demo_mode'))
  165. {
  166. $sName = $sFileName;
  167. }
  168. else
  169. {
  170. $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath));
  171. $sName = "<a href=\"$sAjax\">".$sFileName.'</a>';
  172. }
  173. $sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile));
  174. $sConfirmRestore = addslashes(Dict::Format('bkp-confirm-restore', $sFileName));
  175. $sFileEscaped = addslashes($sFilePath);
  176. $sRestoreBtn = '<button class="restore" onclick="LaunchRestoreNow(\''.$sFileEscaped.'\', \''.$sConfirmRestore.'\');" '.$sDisableRestore.'>'.Dict::S('bkp-button-restore-now').'</button>';
  177. if (in_array($sBackupFile, $aFilesToDelete))
  178. {
  179. $aDetails[] = array('file' => $sName.' <span class="next_to_delete" title="'.Dict::S('bkp-next-to-delete').'">*</span>', 'size' => $sSize, 'actions' => $sRestoreBtn);
  180. }
  181. else
  182. {
  183. $aDetails[] = array('file' => $sName, 'size' => $sSize, 'actions' => $sRestoreBtn);
  184. }
  185. }
  186. $aConfig = array(
  187. 'file' => array('label' => Dict::S('bkp-table-file'), 'description' => Dict::S('bkp-table-file+')),
  188. 'size' => array('label' => Dict::S('bkp-table-size'), 'description' => Dict::S('bkp-table-size+')),
  189. 'actions' => array('label' => Dict::S('bkp-table-actions'), 'description' => Dict::S('bkp-table-actions+')),
  190. );
  191. $oP->add("<fieldset>");
  192. $oP->add("<legend>".Dict::S('bkp-status-backups-auto')."</legend>");
  193. if (count($aDetails) > 0)
  194. {
  195. $oP->add('<div style="max-height:400px; overflow: auto;">');
  196. $oP->table($aConfig, array_reverse($aDetails));
  197. $oP->add('</div>');
  198. }
  199. else
  200. {
  201. $oP->p(Dict::S('bkp-status-backups-none'));
  202. }
  203. $oP->add("</fieldset>");
  204. // 2nd table: list the backups made manually
  205. //
  206. $aDetails = array();
  207. foreach ($oBackup->ListFiles($sBackupDirManual) as $sBackupFile)
  208. {
  209. $sFileName = basename($sBackupFile);
  210. $sFilePath = 'manual/'.$sFileName;
  211. if (MetaModel::GetConfig()->Get('demo_mode'))
  212. {
  213. $sName = $sFileName;
  214. }
  215. else
  216. {
  217. $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath));
  218. $sName = "<a href=\"$sAjax\">".$sFileName.'</a>';
  219. }
  220. $sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile));
  221. $sConfirmRestore = addslashes(Dict::Format('bkp-confirm-restore', $sFileName));
  222. $sFileEscaped = addslashes($sFilePath);
  223. $sRestoreBtn = '<button class="restore" onclick="LaunchRestoreNow(\''.$sFileEscaped.'\', \''.$sConfirmRestore.'\');" '.$sDisableRestore.'>'.Dict::S('bkp-button-restore-now').'</button>';
  224. $aDetails[] = array('file' => $sName, 'size' => $sSize, 'actions' => $sRestoreBtn);
  225. }
  226. $aConfig = array(
  227. 'file' => array('label' => Dict::S('bkp-table-file'), 'description' => Dict::S('bkp-table-file+')),
  228. 'size' => array('label' => Dict::S('bkp-table-size'), 'description' => Dict::S('bkp-table-size+')),
  229. 'actions' => array('label' => Dict::S('bkp-table-actions'), 'description' => Dict::S('bkp-table-actions+')),
  230. );
  231. $oP->add("<fieldset>");
  232. $oP->add("<legend>".Dict::S('bkp-status-backups-manual')."</legend>");
  233. if (count($aDetails) > 0)
  234. {
  235. $oP->add('<div style="max-height:400px; overflow: auto;">');
  236. $oP->table($aConfig, array_reverse($aDetails));
  237. $oP->add('</div>');
  238. }
  239. else
  240. {
  241. $oP->p(Dict::S('bkp-status-backups-none'));
  242. }
  243. $oP->add("</fieldset>");
  244. // Ongoing operation ?
  245. //
  246. $oBackupMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
  247. if ($oBackupMutex->IsLocked())
  248. {
  249. $oP->p(Dict::S('bkp-backup-running'));
  250. }
  251. $oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
  252. if ($oRestoreMutex->IsLocked())
  253. {
  254. $oP->p(Dict::S('bkp-restore-running'));
  255. }
  256. // Do backup now
  257. //
  258. $oBackupExec = new BackupExec();
  259. $oNext = $oBackupExec->GetNextOccurrence();
  260. $oP->p(Dict::Format('bkp-next-backup', $aWeekDayToString[$oNext->Format('N')], $oNext->Format('Y-m-d'), $oNext->Format('H:i')));
  261. $oP->p('<button onclick="LaunchBackupNow();">'.Dict::S('bkp-button-backup-now').'</button>');
  262. $oP->add('<div id="backup_success" class="header_message message_ok" style="display: none;"></div>');
  263. $oP->add('<div id="backup_errors" class="header_message message_error" style="display: none;"></div>');
  264. $oP->add('<input type="hidden" name="restore_token" id="restore_token"/>');
  265. $sConfirmBackup = addslashes(Dict::S('bkp-confirm-backup'));
  266. $sPleaseWaitBackup = addslashes(Dict::S('bkp-wait-backup'));
  267. $sPleaseWaitRestore = addslashes(Dict::S('bkp-wait-restore'));
  268. $sRestoreDone = addslashes(Dict::S('bkp-success-restore'));
  269. $sMySQLBinDir = addslashes(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
  270. $sDBHost = addslashes(MetaModel::GetConfig()->GetDBHost());
  271. $sDBUser = addslashes(MetaModel::GetConfig()->GetDBUser());
  272. $sDBPwd = addslashes(MetaModel::GetConfig()->GetDBPwd());
  273. $sDBName = addslashes(MetaModel::GetConfig()->GetDBName());
  274. $sDBSubName = addslashes(MetaModel::GetConfig()->GetDBSubName());
  275. $sEnvironment = addslashes(utils::GetCurrentEnvironment());
  276. $oP->add_script(
  277. <<<EOF
  278. function LaunchBackupNow()
  279. {
  280. $('#backup_success').hide();
  281. $('#backup_errors').hide();
  282. if (confirm('$sConfirmBackup'))
  283. {
  284. $.blockUI({ message: '<h1><img src="../images/indicator.gif" /> $sPleaseWaitBackup</h1>' });
  285. var oParams = {};
  286. oParams.operation = 'backup';
  287. $.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
  288. if (data.search(/error|exceptio|notice|warning/i) != -1)
  289. {
  290. $('#backup_errors').html(data);
  291. $('#backup_errors').show();
  292. }
  293. else
  294. {
  295. window.location.reload();
  296. }
  297. $.unblockUI();
  298. });
  299. }
  300. }
  301. function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
  302. {
  303. if (confirm(sConfirmationMessage))
  304. {
  305. $.blockUI({ message: '<h1><img src="../images/indicator.gif" /> $sPleaseWaitRestore</h1>' });
  306. $('#backup_success').hide();
  307. $('#backup_errors').hide();
  308. var oParams = {};
  309. oParams.operation = 'restore_get_token';
  310. oParams.file = sBackupFile;
  311. $.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
  312. // Get the value of restore_token
  313. $('#backup_errors').append(data);
  314. var oParams = {};
  315. oParams.operation = 'restore_exec';
  316. oParams.token = $("#restore_token").val();
  317. oParams.mysql_bindir = '$sMySQLBinDir';
  318. oParams.db_host = '$sDBHost';
  319. oParams.db_user = '$sDBUser';
  320. oParams.db_pwd = '$sDBPwd';
  321. oParams.db_name = '$sDBName';
  322. oParams.db_subname = '$sDBSubName';
  323. oParams.environment = '$sEnvironment';
  324. if (oParams.token.length > 0)
  325. {
  326. $.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
  327. if (data.search(/error|exceptio|notice|warning/i) != -1)
  328. {
  329. $('#backup_success').hide();
  330. $('#backup_errors').html(data);
  331. $('#backup_errors').show();
  332. }
  333. else
  334. {
  335. $('#backup_errors').hide();
  336. $('#backup_success').html('$sRestoreDone');
  337. $('#backup_success').show();
  338. }
  339. $.unblockUI();
  340. });
  341. }
  342. else
  343. {
  344. $('button.restore').attr('disabled', 'disabled');
  345. $.unblockUI();
  346. }
  347. });
  348. }
  349. }
  350. EOF
  351. );
  352. if (MetaModel::GetConfig()->Get('demo_mode'))
  353. {
  354. $oP->add_ready_script("$('button').attr('disabled', 'disabled').attr('title', 'Disabled in demonstration mode')");
  355. }
  356. }
  357. catch(Exception $e)
  358. {
  359. $oP->p('<b>'.$e->getMessage().'</b>');
  360. }
  361. $oP->output();
  362. ?>